Add support for hero and villain decks

Should definitely have been smaller commits, but oh well, too lazy to
split them up now

important note: switched editor from XMLHttpRequest to fetch
This commit is contained in:
Adam Goldsmith 2017-10-12 02:27:17 -04:00
parent 7b3ca422c0
commit 933c275243
8 changed files with 122 additions and 68 deletions

View File

@ -10,10 +10,13 @@ document.title = "Editor|" + deckName;
window.addEventListener("load", () => {
// load deck input json
getJSON("deck.input.json", json => {
deckJSON = json;
makeSVGs(deckJSON);
});
fetch("deck.input.json")
.then(data => data.json())
.then(json => {
deckJSON = json;
makeSVGs(deckJSON);
})
.catch(error => console.error(error));
// deck JSON uploader
document.querySelector('#jsonUpload').addEventListener('change', event => {
@ -96,55 +99,54 @@ function downloadFile(file, name) {
document.body.removeChild(dl);
}
function getJSON(filename, callback) {
let xhr = new XMLHttpRequest();
xhr.addEventListener("load", () => {
if (xhr.status === 200) {
callback(JSON.parse(xhr.responseText));
}
});
xhr.open("GET", filename);
xhr.send();
}
function getSVGTemplate(name, callback) {
let xhr = new XMLHttpRequest();
xhr.addEventListener("load", () => {
let respSVG = xhr.responseXML.children[0];
callback(respSVG);
});
xhr.open("GET", "/template/" + name + ".svg");
xhr.send();
return fetch("/template/" + name + ".svg")
.then(response => response.text())
.then(str => (new window.DOMParser()).parseFromString(str, "text/xml").activeElement);
}
function makeSVGs(deckJSON) {
async function makeSVGs(deckJSON) {
document.querySelector('#deckName').value = deckJSON.name || "";
document.querySelector('#deckType').value = deckJSON.type || "";
let deck = document.querySelector('#deck');
deck.innerHTML = "";
setDeckTemplate(deckJSON.type, () => {
Object.entries(template.cardTypes).forEach(cardType => {
getSVGTemplate(deckJSON.type + "/" + cardType[0], templateSVG => {
deck.style.width = Math.ceil(Math.sqrt(deckJSON.deck.length)) *
parseInt(templateSVG.getAttribute("width")) + "pt";
deck.style.height = Math.ceil(Math.sqrt(deckJSON.deck.length)) *
parseInt(templateSVG.getAttribute("height")) + "pt";
let template = await fetch(`/template/${deckJSON.type}/input.json`)
.then(data => data.json());
// build card SVGs
deckJSON[cardType[0]].forEach(
card => makeCardSVG(deck, cardType[1], templateSVG, card));
});
let cardCount = Object.entries(template.cardTypes)
.map(ct => deckJSON[ct[0]].length * (ct[1].back ? 2 : 1))
.reduce((sum, current) => sum + current, 0);
// note: needs to be a for loop because it needs to be synchronous
// and also have await
// Although I suppose I could prefetch the SVGs and then do the rest...
for (let cardType of Object.entries(template.cardTypes)) {
let backSVG;
if (cardType[1].back) {
let backTemplate = cardType[1].back.template || (cardType[0] + "-back");
backSVG = await getSVGTemplate(deckJSON.type + "/" + backTemplate);
}
let templateSVG = await getSVGTemplate(deckJSON.type + "/" + cardType[0]);
console.log(templateSVG);
// build card SVGs
deckJSON[cardType[0]].forEach(card => {
makeCardSVG(deck, cardType[1], templateSVG, card);
// if there is a back, build it too
if (cardType[1].back) {
makeCardSVG(deck, cardType[1].back, backSVG, card, back=true);
}
});
});
}
function setDeckTemplate(type, callback) {
getJSON("/template/" + type + "/input.json", json => {
template = json;
callback();
});
// set div width/height based on number of cards
deck.style.width = Math.ceil(Math.sqrt(cardCount)) *
parseInt(templateSVG.getAttribute("width")) + "pt";
deck.style.height = Math.ceil(Math.sqrt(cardCount)) *
parseInt(templateSVG.getAttribute("height")) + "pt";
};
}
function setForm(cardTemplate, card) {
@ -170,21 +172,20 @@ function setForm(cardTemplate, card) {
});
}
function makeCardSVG(deck, cardInputTemplate, templateSVG, card) {
function makeCardSVG(deck, cardInputTemplate, templateSVG, card, back=false) {
let propSource = (back && card.back) ? card.back : card;
let cardSVG = deck.appendChild(templateSVG.cloneNode(true));
cardSVG.addEventListener('click', () => {
selected = {svg: cardSVG, json: card};
setForm(cardInputTemplate, card);
}, true);
Object.keys(cardInputTemplate.inputs).forEach(
prop => wrapSVGText(cardSVG.querySelector('#' + prop), String(card[prop] || "")));
Object.entries(cardInputTemplate.hide).forEach(hidable => {
if (hidable[1] in card) {
cardSVG.querySelector('#' + hidable[0]).setAttribute('display', '');
}
else {
cardSVG.querySelector('#' + hidable[0]).setAttribute('display', 'none');
}
Object.keys(cardInputTemplate.inputs).forEach(prop => {
let inputProp = propSource[prop] || card[prop] || "";
wrapSVGText(cardSVG.querySelector('#' + prop), String(inputProp));
});
Object.entries(cardInputTemplate.hide || []).forEach(hidable => {
cardSVG.querySelector('#' + hidable[0])
.setAttribute('display', hidable[1] in propSource ? '' : 'none');
});
}
@ -193,10 +194,11 @@ function upload() {
// POST the generated SVGs to the server
let data = (new XMLSerializer()).serializeToString(deck);
let xhr = new XMLHttpRequest();
xhr.open('POST', "upload");
xhr.setRequestHeader("Content-Type", "application/json");
xhr.send(JSON.stringify({body: data, json: deckJSON}));
fetch('upload', {
method: 'post',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({body: data, json: deckJSON})
});
}
function wrapSVGText(e, string) {

View File

@ -50,12 +50,14 @@ const server = http.createServer((req, res) => {
case "card.json":
case "deck.json":
case "environment/input.json":
case "hero/input.json":
case "villain/input.json":
sendFile(res, "template/" + item, 'application/json');
break;
case "environment/deck.svg":
case "hero/deck.svg":
case "hero/character-back.svg":
case "hero/character-front.svg":
case "hero/character.svg":
case "villain/deck.svg":
case "villain/character.svg":
case "villain/instructions.svg":

View File

@ -1,6 +1,7 @@
{
"cardTypes": {
"deck": {
"meta": ["count"],
"inputs": {
"name": "text",
"keywords": "text",

View File

@ -4,7 +4,7 @@
<path d="m9.498 243.5h162v-234h-162z" fill="#fff" stroke="#000" stroke-miterlimit="10"/>
<path d="m9.5 243.5h162v-234h-162z" fill="none" stroke="#000" stroke-miterlimit="10" stroke-width="2"/>
<path d="m168.08 239.87h-154.91v-52.371h154.91z" fill="#cfd1d4" stroke="#000" stroke-linejoin="round" stroke-miterlimit="10"/>
<text id="incapacitatedPowers" x="15.184497" y="195.27484" fill="#000000" font-family="'RedStateBlueState BB'" font-size="8px">
<text id="incapacitated" x="15.184497" y="195.27484" fill="#000000" font-family="'RedStateBlueState BB'" font-size="8px">
<tspan x="15.184497" y="195.27484">{ Text Here</tspan>
<tspan x="15.184497" y="214.02484">{ Text Here</tspan>
<tspan x="15.184497" y="232.77484">{ Text Here</tspan>

Before

Width:  |  Height:  |  Size: 851 B

After

Width:  |  Height:  |  Size: 845 B

View File

Before

Width:  |  Height:  |  Size: 74 KiB

After

Width:  |  Height:  |  Size: 74 KiB

View File

@ -1,5 +1,17 @@
{
"cardTypes": {
"character": {
"back": {
"inputs": {
"incapacitated": "text"
}
},
"inputs": {
"hp": "number",
"power": "text",
"powerText": "textarea"
}
},
"deck": {
"inputs": {
"name": "text",

View File

@ -1,5 +1,40 @@
{
"cardTypes": {
"character": {
"back": {
"template": "character",
"inputs": {
"name": "text",
"hp": "number",
"title": "text"
}
},
"inputs": {
"name": "text",
"hp": "number",
"title": "text"
}
},
"instructions": {
"back": {
"template": "instructions",
"inputs": {
"name": "text",
"title": "text",
"gameplay": "textarea"
},
"hide": {
"setupBox": "setup"
}
},
"inputs": {
"name": "text",
"title": "text",
"setup": "textarea",
"gameplay": "textarea",
"advanced": "textarea"
}
},
"deck": {
"inputs": {
"name": "text",

View File

@ -9,16 +9,18 @@
<path d="m99.873 223.92h-81.75v-8.5h81.75z" fill="#ebe130" stroke="#000" stroke-linejoin="round" stroke-miterlimit="10"/>
<path d="m99.873 128.92h-81.75v-8.5h81.75z" fill="#ebe130" stroke="#000" stroke-linejoin="round" stroke-miterlimit="10"/>
<text id="name" transform="scale(.97038 1.0305)" x="14.445003" y="44.008652" fill="#ffffff" font-family="'Armor Piercing'" font-size="29.11px" stroke="#000000" stroke-width=".72998px" xml:space="preserve">Villain Name</text>
<text id="title" x="65.489799" y="52.567066" font-family="'RedStateBlueState BB'" font-size="11px" font-style="italic">Villain Title</text>
<text id="artist" x="168.59041" y="247.55547" fill="#ffffff" font-family="'RedStateBlueState BB'" font-size="5.3px" text-align="end" text-anchor="end">Art By</text>
<text x="16.214796" y="134.85202" font-family="'RedStateBlueState BB'" font-size="8.7997px" stroke-width=".75" style="line-height:5.25px" xml:space="preserve"><tspan x="16.214796" y="134.85202" stroke-width=".75"><tspan x="16.214796" y="134.85202" stroke-width=".75">Text Here</tspan></tspan></text>
<g id="setupBox">
<path d="m167.79 117.46h-154.58v-50.539h154.58z" fill="#cfd1d4" stroke="#000" stroke-linejoin="round" stroke-miterlimit="10"/>
<path d="m99.873 68.92h-81.75v-8.5h81.75z" fill="#ebe130" stroke="#000" stroke-linejoin="round" stroke-miterlimit="10"/>
<text x="16.214796" y="74.85202" font-family="'RedStateBlueState BB'" font-size="8.7997px" letter-spacing="0px" stroke-width=".75" word-spacing="0px" style="line-height:5.25px" xml:space="preserve"><tspan x="16.214796" y="74.85202" stroke-width=".75"><tspan x="16.214796" y="74.85202" stroke-width=".75">Text Here</tspan></tspan></text>
<text x="47.487053" y="67.280876" font-family="'RedStateBlueState BB'" font-size="10px" font-style="italic">SetUp</text>
<g font-family="'RedStateBlueState BB'">
<text id="title" x="65.489799" y="52.567066" font-size="11px" font-style="italic">Villain Title</text>
<g id="setupBox">
<path d="m167.79 117.46h-154.58v-50.539h154.58z" fill="#cfd1d4" stroke="#000" stroke-linejoin="round" stroke-miterlimit="10"/>
<path d="m99.873 68.92h-81.75v-8.5h81.75z" fill="#ebe130" stroke="#000" stroke-linejoin="round" stroke-miterlimit="10"/>
<text id="setup" x="16.214796" y="74.85202" width="152" height="50" font-size="9px">Text Here</text>
<text x="47.487053" y="67.280876" font-size="10px" font-style="italic">SetUp</text>
</g>
<text x="37.503998" y="127.27358" font-size="10px" font-style="italic">Game Play</text>
<text id="gameplay" x="16.214796" y="134.85202" width="150" height="80" font-size="9px">Text Here</text>
<text x="39.672401" y="222.4875" font-size="10px" font-style="italic">Advanced</text>
<text id="advanced" x="16.214796" y="229.85202" width="152" height="10" font-size="9px">Text Here</text>
<text id="artist" x="168.59041" y="247.55547" fill="#ffffff" font-size="5.3px" text-align="end" text-anchor="end">Art By</text>
</g>
<text x="37.503998" y="127.27358" font-family="'RedStateBlueState BB'" font-size="10px" font-style="italic">Game Play</text>
<text x="39.672401" y="222.4875" font-family="'RedStateBlueState BB'" font-size="10px" font-style="italic">Advanced</text>
<text x="16.214796" y="229.85202" font-family="'RedStateBlueState BB'" font-size="8.7997px" stroke-width=".75" style="line-height:5.25px" xml:space="preserve"><tspan x="16.214796" y="229.85202" stroke-width=".75"><tspan x="16.214796" y="229.85202" stroke-width=".75">Text Here</tspan></tspan></text>
</svg>

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB