let adminToken = null; async function loadAdmin(name, pass) { try { const headers = new Headers(); headers.set('Authorization', 'Bearer ' + adminToken.access_token); const tableData = await fetch(`/admin/api/table/${name}?passcode=${pass}`, { method: 'GET', headers: headers, }); const mapImgs = await fetch (`/admin/api/upload/${name}/map/?passcode=${pass}`, { method: 'GET', headers: headers, }); const tokenImgs = await fetch(`/admin/api/upload/${name}/token/?passcode=${pass}`, { method: 'GET', headers: headers, }); let mgmtHTML = "" if (tableData.ok) { $("input_table_name").value = name; $("input_table_pass").value = pass; dial(); const table = await tableData.json() mgmtHTML = `← table list
\n` + `
\n` + `
\n` + `
\n` if (mapImgs.ok) { const imgs = (await mapImgs.json()).sort(); mgmtHTML = imgs.reduce((s, i) => { const parts = i.split("/"); return s + `
  • ${parts[parts.length - 1]}
  • \n`; }, mgmtHTML + ""; } else { mgmtHTML += ""; } $("table_management").innerHTML = mgmtHTML; let spriteListHTML = `
    `; if (tokenImgs.ok) { const sprites = (await tokenImgs.json()).sort(); spriteListHTML = sprites.reduce((s, t) => { const parts = t.split("/"); return s + `
  • ${parts[parts.length - 1]}
  • \n`; }, spriteListHTML + ""; fillSpriteDropdown(sprites); } else { spriteListHTML += "" } $("sprite_zone").innerHTML = spriteListHTML; $("token_management").style.display = "inline"; } else { console.log(res.status); } } catch (err) { console.dir(err) setErr(`${err.name}: ${err.message}`); } } function fillSpriteDropdown(sprites) { const dropdown = $("sprite_dropdown"); let options = ""; dropdown.innerHTML = sprites.reduce((s, t) => { const parts = t.split("/"); return s + `\n`; }, ""); } function previewSprite(source) { if (source) { switch (source.id) { case "sprite_dropdown": reinitializeSpritePreview(); break; case "token_cx": case "token_cy": drawTokenOrigin(); break; default: scaleSpritePreview(source); console.log("default case"); } } } function toggleAspectLock() { try { const locked = $("token_aspect_lock").checked; $("aspect_lock_label").innerHTML = locked ? "🔒" : "🔓" } catch {} } function scaleSpritePreview(source) { const tokenHeight = $("token_height"); const tokenWidth = $("token_width"); const tokenCX = $("token_cx"); const tokenCY = $("token_cy"); const tokenAspect = $("token_aspect_lock"); const preview = $("token_admin_preview"); if (mapImg && mapImg._image && tokenHeight && tokenWidth && tokenCX && tokenCY && tokenAspect && preview) { const scaleFactor = mapImg._image.clientWidth / mapImg._image.naturalWidth; const keepAspect = tokenAspect.checked; const img = preview.children[0]; tokenHeight.value = Math.floor(Number(tokenHeight.value)) tokenWidth.value = Math.floor(Number(tokenWidth.value)) 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 = Math.floor(Number(tokenWidth.value)/currentAspect); break; case "token_height": img.height = Number(tokenHeight.value) * scaleFactor; img.width = (img.clientHeight / img.naturalHeight) * img.naturalWidth; tokenWidth.value = Math.floor(currentAspect * Number(tokenHeight.value)); break; } } if (!tokenCX.value || source) { tokenCX.value = Number(tokenWidth.value)/2; } if (!tokenCY.value || source) { tokenCY.value = Number(tokenHeight.value)/2; } drawTokenOrigin(); } } } function drawTokenOrigin() { const dropdown = $("sprite_dropdown"); const tokenWidth = $("token_width"); const tokenHeight = $("token_height"); const tokenCX = $("token_cx"); const tokenCY = $("token_cy"); const preview = $("token_admin_preview"); if (dropdown && tokenWidth && tokenHeight && tokenCX && tokenCY && preview && dropdown.selectedIndex >= 0) { 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 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 (preview.children.length > 1) { preview.replaceChild(originImg, preview.children[1]); } else { preview.appendChild(originImg); } } } function reinitializeSpritePreview(existing = false) { const dropdown = $("sprite_dropdown"); const tokenName = $("token_name"); const tokenWidth = $("token_width"); const tokenHeight = $("token_height"); const tokenCX = $("token_cx"); const tokenCY = $("token_cy"); const preview = $("token_admin_preview"); if (dropdown && tokenName && tokenWidth && tokenHeight && tokenCX && tokenCY && preview) { const img = document.createElement("img"); img.src = dropdown[dropdown.selectedIndex].value; if (!existing) { const tokenNameParts = dropdown[dropdown.selectedIndex].text.split("."); tokenNameParts.pop(); tokenName.value = tokenNameParts.join("."); } img.onload = () => { const w = img.naturalWidth; const h = img.naturalHeight; if (!existing) { tokenWidth.value = w; tokenHeight.value = h; tokenCX.value = ""; tokenCY.value = ""; } scaleSpritePreview(); } preview.innerHTML = ""; preview.appendChild(img); } } function createToken() { const dropdown = $("sprite_dropdown"); try { const w = Number($("token_width").value); const h = Number($("token_height").value); const oX = Number($("token_cx").value); const oY = Number($("token_cy").value); const img = dropdown[dropdown.selectedIndex].value; const name = $("token_name").value; if (!isNaN(w) && !isNaN(h) && !isNaN(oX) && !isNaN(oY) && img && name) { // send it on the websocket and wait for it to come back const [x, y] = [0, 0]; sendToken({ w: w, h: h, ox:oX, oy:oY, x: x, y: y, sprite: img, name: name, active: false }); setTokenCreateFormVisible(false); return; } setErr("All token fields are required"); } catch {} } function destroyToken(id) { const existing = tokens.find(t=>t.t.id == id); if (existing) { const self = Object.assign({}, existing.t); self.active = false; self.x = null; self.y = null; sendToken(self); } } function previewExistingToken(id) { try { const existing = tokens.find(t=>t.t.id == id); const dropdown = $("sprite_dropdown"); if (existing) { $("token_width").value = existing.t.w; $("token_height").value = existing.t.h; $("token_cx").value = existing.t.oX; $("token_cy").value = existing.t.oY; $("token_name").value = existing.t.name; for (let i = 0; i < dropdown.options.length; i++ ) { if (dropdown.options[i].value === existing.t.sprite) { dropdown.selectedIndex = i; break; } } reinitializeSpritePreview(true); } } catch (err) { console.log(err); } } function copyToken(id) { setTokenCreateFormVisible(true); previewExistingToken(id); } function renderTokenMasterList() { const tokenList = $("token_list"); if (tokenList) { const scroll = tokenList.scrollTop; tokenList.innerHTML = tokens.reduce((s, t) => { return s + `
  • ${t.t.name}
  • \n`; }, ""); tokenList.scrollTop = scroll; } } function publishAuxMsg() { const txtArea = $("aux_msg_input"); if (txtArea) { publish({auxMsg: txtArea.value, auth: adminToken.access_token}); } } function sendMapImg(url) { publish({mapImg: url, auth: adminToken.access_token}); } function sendToken(t) { publish({token: t, auth: adminToken.access_token}); } async function uploadImg(imgType) { try { var input = $(`${imgType}_img_upload`); var data = new FormData(); data.append('file', input.files[0]); data.append('name', tableKey.name); data.append('passcode', tableKey.passcode); const headers = new Headers(); headers.set('Authorization', 'Bearer ' + adminToken.access_token); res = await fetch(`/admin/api/upload/${tableKey.name}/${imgType}/`, { headers: headers, method: "POST", body: data, }); if (res.ok) { loadAdmin(tableKey.name, tableKey.passcode); } else { throw new Error("Something went wrong uploading the ${imgType} image..."); } } catch (err) { setErr(`${err.name}: ${err.message}`); } } async function deleteImg(url) { try { if (url.startsWith("/uploads/")) { const parts = url.split("/"); parts.shift(); const table = parts[1]; const imgType = parts[2] const file = parts[3]; const headers = new Headers(); headers.set('Authorization', 'Bearer ' + adminToken.access_token); const res = await fetch(`/admin/api/upload/${table}/${imgType}/${file}?passcode=${tableKey.passcode}`, { headers: headers, method: "DELETE", }); if (res.ok) { loadAdmin(tableKey.name, tableKey.passcode); } else { throw new Error ("Something went wrong deleting the image..."); } } } catch (err) { setErr(`${err.name}: ${err.message}`); } } async function destroyTable() { try { if (confirm("You really want to destroy this table?")) { const headers = new Headers(); headers.set('Authorization', 'Bearer ' + adminToken.access_token); const res = await fetch(`/admin/api/table/${tableKey.name}`, { method: 'DELETE', headers: headers, body: JSON.stringify(tableKey) }); if (res.ok) { leave(); getTables(); } else { setErr("Error destroying table"); } } } catch (err) { setErr(`${err.name}: ${err.message}`); } } function sortByName(a, b) { return (a.name < b.name) ? -1 : ((a.name > b.name) ? 1 : 0) } async function getTables() { try { const headers = new Headers(); headers.set('Authorization', 'Bearer ' + adminToken.access_token); const res = await fetch('/admin/api/table/', { method: 'GET', headers: headers }); if (res.ok) { const tblMgmt = $("table_management"); const tableList = (await res.json()).sort(sortByName); tblMgmt.innerHTML = tableList.reduce((s, t)=>{ return s + `
  • ${t.name}
  • \n` }, ""; tableListHTML += "" tblMgmt.style.display = "none"; } else { // fail silently } } catch { // fail silently } } async function doLogin() { const adminUsrInput = $("name_entry"); const adminPassInput = $("input_admin_pass"); const adminUI = $("admin_ui"); const tblMgmt = $("table_management"); const tokenMgmt = $("token_management"); if (adminUsrInput && adminPassInput && adminUI && tblMgmt && tokenMgmt) { adminToken = await getAdminToken(adminUsrInput.value, adminPassInput.value); if (adminToken) { getTables(); tblMgmt.style.display = "block"; tokenMgmt.style.display = "none"; adminUI.style.display="inline"; closeErr(); replaceAdminModal(); } else { setErr("Incorrect credentials"); } } } function replaceAdminModal() { const adminModal = $("admin_modal"); if (adminModal) { adminModal.innerHTML = "Logout"; } } async function getAdminToken(user, pass) { const headers = new Headers(); headers.set('Authorization', 'Basic ' + btoa(user + ":" + pass)); try { const res = await fetch('/admin/api/auth/', { method: 'POST', headers: headers }); if (res.ok) { return await res.json(); } return null; } catch (err) { return null; } } function setTableCreateFormVisible(v) { try { $("table_creation_form").style.display = v ? "block" : "none"; if (!v) { $("new_table_name").value = ""; $("new_table_pass").value = ""; } } catch {} } function setTokenCreateFormVisible(v) { try { if (v) { $("token_width").value = ""; $("token_height").value = ""; $("token_cx").value = ""; $("token_cy").value = ""; $("token_name").value = ""; $("sprite_dropdown").selectedIndex = 0; } $("token_creation_form").style.display = v ? "block" : "none"; $("token_list").style.display = v ? "none" : "block"; $("token_admin_preview").innerHTML = ""; } catch {} } async function createTable() { const newTableName = $("new_table_name"); const newTablePass = $("new_table_pass"); if (newTableName && newTablePass) { const headers = new Headers(); headers.set('Authorization', 'Bearer ' + adminToken.access_token); const formData = new FormData(); formData.set("name", newTableName.value); formData.set("passcode", newTablePass.value); let bodyStr = "{"; for (const pair of formData.entries()) { bodyStr += `"${pair[0]}": "${pair[1]}",`; } bodyStr = bodyStr.slice(0, -1); bodyStr += "}"; try { const res = await fetch('/admin/api/table/', { method: 'POST', headers: headers, body: bodyStr, }); if (res.ok) { getTables(); setTableCreateFormVisible(false); } else if (res.status === 422) { setErr("Table name and passcode must be only alphanumeric and underscores"); } else { setErr("Error creating table"); } } catch { setErr("Error creating table"); } } }