felt/static/admin.js

521 lines
No EOL
16 KiB
JavaScript

let adminToken = null;
const adminWrapper = document.getElementById("adminWrapper");
const adminZone = document.getElementById("adminZone");
const spriteZone = document.getElementById("spriteZone");
const createTableForm = document.getElementById("createTableForm");
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");
const tokenZone = document.getElementById("tokenZone");
async function rebindUi(name, pass) {
try {
const headers = new Headers();
headers.set('Authorization', 'Bearer ' + adminToken.access_token);
const res = 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 infoHtml = ""
if (res.ok) {
document.getElementById("input_table_name").value = name;
document.getElementById("input_table_pass").value = pass;
dial();
const table = await res.json()
infoHtml = "<a href='#' onclick='getTables();return false;'>&larr; table list</a><br>";
infoHtml += `<textarea id='auxMsgZone'>${table.auxMsg}</textarea><br><button onclick='publishAuxMsg()'>Set Status</button>`
infoHtml += "<button onclick='destroyTable()'>Destroy Table</button><br/>";
infoHtml += "<input id='map_img_upload' type='file'/><button onclick='uploadMapImg()'>Upload Map</button><br/>"
if (mapImgs.ok) {
infoHtml += "<label>Available Maps</label>";
const imgs = await mapImgs.json();
infoHtml += "<ul class='two_btn_list'>";
for (const i of imgs) {
const parts = i.split("/");
infoHtml += `<li><a href="${i}" target="_blank">${parts[parts.length - 1]}</a> <button onclick="sendMapImg('${i}');">Set</button> <button onclick="deleteImg('${i}')">Delete</button></li>\n`;
}
infoHtml += "</ul>";
} else {
infoHtml += "<label>Maps couldn't be retrieved</label>";
}
adminZone.innerHTML = infoHtml;
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();
tokenListHTML += "<ul class='single_btn_list'>";
for (const t of tokens) {
const parts = t.split("/");
tokenListHTML += `<li><a href="${t}" target="_blank">${parts[parts.length - 1]}</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";
} else {
console.log(res.status);
}
} catch (err) {
setErr(`${err.name}: ${err.message}`);
}
}
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) {
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() {
if (tokenSpriteDropdown.selectedIndex >= 0) {
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();
}
previewZone.innerHTML = "";
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;
if (!isNaN(w) && !isNaN(h) && !isNaN(oX) && !isNaN(oY) && img && name) {
console.log("all green");
// create on the frontend for testing
/*
const self = NewToken(w, h, oX, oY, img, name);
tokens.push(self);
self.m.addTo(map);
resizeMarkers();
*/
// really though we have to send it on the websocket and wait for it to come back
const [x, y] = getCascadingPos();
sendToken({
w: w,
h: h,
ox:oX,
oy:oY,
x: x,
y: y,
sprite: img,
name: name,
active: false}
);
return;
}
setErr("All token fields are required");
}
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) {
const existing = tokens.find(t=>t.t.id == id);
if (existing) {
tokenWidth.value = existing.t.w;
tokenHeight.value = existing.t.h;
tokenCX.value = existing.t.oX;
tokenCY.value = existing.t.oY;
tokenName.value = existing.t.name;
for (let i = 0; i < tokenSpriteDropdown.options.length; i++) {
if (tokenSpriteDropdown.options[i].value == existing.t.sprite) {
tokenSpriteDropdown.selectedIndex = i;
break;
}
}
reinitializeSpritePreview();
}
}
function renderTokenMasterList() {
if (tokenZone) {
let tokenMasterListHTML = "<label>Available Tokens</label><br/><ul class='single_btn_list'>";
for (const t of tokens) {
tokenMasterListHTML += `<li><a href="#" onclick="previewExistingToken('${t.t.id}');return false">${t.t.name}</a><button onclick="destroyToken('${t.t.id}')">Destroy</button></li>\n`;
}
tokenMasterListHTML += "</ul>";
tokenZone.innerHTML = tokenMasterListHTML;
}
}
function publishAuxMsg() {
const txtArea = document.getElementById("auxMsgZone");
if (txtArea != null) {
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 uploadMapImg() {
try {
var input = document.getElementById("map_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}/map/`, {
headers: headers,
method: "POST",
body: data,
});
if (res.ok) {
// refresh so we can see the new entry in the list
rebindUi(tableKey.name, tableKey.passcode);
} else {
throw new Error("Something went wrong uploading the map BG...");
}
} catch (err) {
setErr(`${err.name}: ${err.message}`);
}
}
async function uploadTokenImg() {
try {
var input = document.getElementById("token_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}/token/`, {
headers: headers,
method: "POST",
body: data,
});
if (res.ok) {
// refresh so we can see the new entry in the list
rebindUi(tableKey.name, tableKey.passcode);
} else {
throw new Error("Something went wrong uploading the token sprite...");
}
} 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) {
// refresh UI
rebindUi(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) {
conn.close(1000);
initializeMap("");
getTables();
} else {
setErr(await res.json());
}
}
} catch (err) {
setErr(`${err.name}: ${err.message}`);
}
}
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 tableList = await res.json();
let tableListHTML = "<ul>\n";
for (const t of tableList) {
tableListHTML += `<li><a href="#" onclick="rebindUi('${t.name}','${t.passcode}');return false;">${t.name}</a></li>\n`
}
tableListHTML += "</ul>"
adminZone.innerHTML = tableListHTML;
tokenWrapper.style.display = "none";
} else {
if (res.status == 404) {
return;
}
setErr(await res.headers.get("Quartzgun-Error"));
}
} catch {
}
}
async function doLogin() {
const adminUsrInput = document.getElementById("name_entry");
const adminPassInput = document.getElementById("input_admin_pass");
if (adminUsrInput && adminPassInput) {
adminToken = await getAdminToken(adminUsrInput.value, adminPassInput.value);
if (adminToken) {
getTables();
adminWrapper.style.display="inline";
adminZone.style.display = "block";
closeErr();
replaceAdminModal();
} else {
setErr("Incorrect credentials");
}
}
}
function replaceAdminModal() {
const adminModal = document.getElementById("admin_modal");
if (adminModal) {
adminModal.innerHTML = "<a href='./'>Logout</a>";
}
}
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) {
if (createTableForm) {
createTableForm.style.display = v ? "block" : "none";
}
if (!v) {
if (newTableName) {
newTableName.value = "";
}
if (newTablePass) {
newTablePass.value = "";
}
}
}
function setTokenCreateFormVisible(v) {
if (createTokenForm && tokenZone) {
if (v) {
// clear the form when displaying because we may have values from a preview of an existing token
tokenWidth.value = "";
tokenHeight.value = "";
tokenCX.value = "";
tokenCY.value = "";
tokenName.value = "";
tokenSpriteDropdown.selectedIndex = 0;
}
createTokenForm.style.display = v ? "block" : "none";
tokenZone.style.display = v ? "none" : "inline";
reinitializeSpritePreview();
}
}
async function createTable() {
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 += "}";
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');
}
}