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:
parent
7b3ca422c0
commit
933c275243
112
js/editor.js
112
js/editor.js
@ -10,10 +10,13 @@ document.title = "Editor|" + deckName;
|
|||||||
|
|
||||||
window.addEventListener("load", () => {
|
window.addEventListener("load", () => {
|
||||||
// load deck input json
|
// load deck input json
|
||||||
getJSON("deck.input.json", json => {
|
fetch("deck.input.json")
|
||||||
deckJSON = json;
|
.then(data => data.json())
|
||||||
makeSVGs(deckJSON);
|
.then(json => {
|
||||||
});
|
deckJSON = json;
|
||||||
|
makeSVGs(deckJSON);
|
||||||
|
})
|
||||||
|
.catch(error => console.error(error));
|
||||||
|
|
||||||
// deck JSON uploader
|
// deck JSON uploader
|
||||||
document.querySelector('#jsonUpload').addEventListener('change', event => {
|
document.querySelector('#jsonUpload').addEventListener('change', event => {
|
||||||
@ -96,55 +99,54 @@ function downloadFile(file, name) {
|
|||||||
document.body.removeChild(dl);
|
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) {
|
function getSVGTemplate(name, callback) {
|
||||||
let xhr = new XMLHttpRequest();
|
return fetch("/template/" + name + ".svg")
|
||||||
xhr.addEventListener("load", () => {
|
.then(response => response.text())
|
||||||
let respSVG = xhr.responseXML.children[0];
|
.then(str => (new window.DOMParser()).parseFromString(str, "text/xml").activeElement);
|
||||||
callback(respSVG);
|
|
||||||
});
|
|
||||||
xhr.open("GET", "/template/" + name + ".svg");
|
|
||||||
xhr.send();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function makeSVGs(deckJSON) {
|
async function makeSVGs(deckJSON) {
|
||||||
document.querySelector('#deckName').value = deckJSON.name || "";
|
document.querySelector('#deckName').value = deckJSON.name || "";
|
||||||
document.querySelector('#deckType').value = deckJSON.type || "";
|
document.querySelector('#deckType').value = deckJSON.type || "";
|
||||||
|
|
||||||
let deck = document.querySelector('#deck');
|
let deck = document.querySelector('#deck');
|
||||||
deck.innerHTML = "";
|
deck.innerHTML = "";
|
||||||
|
|
||||||
setDeckTemplate(deckJSON.type, () => {
|
let template = await fetch(`/template/${deckJSON.type}/input.json`)
|
||||||
Object.entries(template.cardTypes).forEach(cardType => {
|
.then(data => data.json());
|
||||||
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";
|
|
||||||
|
|
||||||
// build card SVGs
|
let cardCount = Object.entries(template.cardTypes)
|
||||||
deckJSON[cardType[0]].forEach(
|
.map(ct => deckJSON[ct[0]].length * (ct[1].back ? 2 : 1))
|
||||||
card => makeCardSVG(deck, cardType[1], templateSVG, card));
|
.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) {
|
// set div width/height based on number of cards
|
||||||
getJSON("/template/" + type + "/input.json", json => {
|
deck.style.width = Math.ceil(Math.sqrt(cardCount)) *
|
||||||
template = json;
|
parseInt(templateSVG.getAttribute("width")) + "pt";
|
||||||
callback();
|
deck.style.height = Math.ceil(Math.sqrt(cardCount)) *
|
||||||
});
|
parseInt(templateSVG.getAttribute("height")) + "pt";
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function setForm(cardTemplate, card) {
|
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));
|
let cardSVG = deck.appendChild(templateSVG.cloneNode(true));
|
||||||
cardSVG.addEventListener('click', () => {
|
cardSVG.addEventListener('click', () => {
|
||||||
selected = {svg: cardSVG, json: card};
|
selected = {svg: cardSVG, json: card};
|
||||||
setForm(cardInputTemplate, card);
|
setForm(cardInputTemplate, card);
|
||||||
}, true);
|
}, true);
|
||||||
Object.keys(cardInputTemplate.inputs).forEach(
|
Object.keys(cardInputTemplate.inputs).forEach(prop => {
|
||||||
prop => wrapSVGText(cardSVG.querySelector('#' + prop), String(card[prop] || "")));
|
let inputProp = propSource[prop] || card[prop] || "";
|
||||||
Object.entries(cardInputTemplate.hide).forEach(hidable => {
|
wrapSVGText(cardSVG.querySelector('#' + prop), String(inputProp));
|
||||||
if (hidable[1] in card) {
|
});
|
||||||
cardSVG.querySelector('#' + hidable[0]).setAttribute('display', '');
|
Object.entries(cardInputTemplate.hide || []).forEach(hidable => {
|
||||||
}
|
cardSVG.querySelector('#' + hidable[0])
|
||||||
else {
|
.setAttribute('display', hidable[1] in propSource ? '' : 'none');
|
||||||
cardSVG.querySelector('#' + hidable[0]).setAttribute('display', 'none');
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -193,10 +194,11 @@ function upload() {
|
|||||||
|
|
||||||
// POST the generated SVGs to the server
|
// POST the generated SVGs to the server
|
||||||
let data = (new XMLSerializer()).serializeToString(deck);
|
let data = (new XMLSerializer()).serializeToString(deck);
|
||||||
let xhr = new XMLHttpRequest();
|
fetch('upload', {
|
||||||
xhr.open('POST', "upload");
|
method: 'post',
|
||||||
xhr.setRequestHeader("Content-Type", "application/json");
|
headers: {'Content-Type': 'application/json'},
|
||||||
xhr.send(JSON.stringify({body: data, json: deckJSON}));
|
body: JSON.stringify({body: data, json: deckJSON})
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function wrapSVGText(e, string) {
|
function wrapSVGText(e, string) {
|
||||||
|
@ -50,12 +50,14 @@ const server = http.createServer((req, res) => {
|
|||||||
case "card.json":
|
case "card.json":
|
||||||
case "deck.json":
|
case "deck.json":
|
||||||
case "environment/input.json":
|
case "environment/input.json":
|
||||||
|
case "hero/input.json":
|
||||||
|
case "villain/input.json":
|
||||||
sendFile(res, "template/" + item, 'application/json');
|
sendFile(res, "template/" + item, 'application/json');
|
||||||
break;
|
break;
|
||||||
case "environment/deck.svg":
|
case "environment/deck.svg":
|
||||||
case "hero/deck.svg":
|
case "hero/deck.svg":
|
||||||
case "hero/character-back.svg":
|
case "hero/character-back.svg":
|
||||||
case "hero/character-front.svg":
|
case "hero/character.svg":
|
||||||
case "villain/deck.svg":
|
case "villain/deck.svg":
|
||||||
case "villain/character.svg":
|
case "villain/character.svg":
|
||||||
case "villain/instructions.svg":
|
case "villain/instructions.svg":
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
{
|
{
|
||||||
"cardTypes": {
|
"cardTypes": {
|
||||||
"deck": {
|
"deck": {
|
||||||
|
"meta": ["count"],
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"name": "text",
|
"name": "text",
|
||||||
"keywords": "text",
|
"keywords": "text",
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
<path d="m9.498 243.5h162v-234h-162z" fill="#fff" stroke="#000" stroke-miterlimit="10"/>
|
<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="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"/>
|
<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="195.27484">{ Text Here</tspan>
|
||||||
<tspan x="15.184497" y="214.02484">{ Text Here</tspan>
|
<tspan x="15.184497" y="214.02484">{ Text Here</tspan>
|
||||||
<tspan x="15.184497" y="232.77484">{ 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 |
Before Width: | Height: | Size: 74 KiB After Width: | Height: | Size: 74 KiB |
@ -1,5 +1,17 @@
|
|||||||
{
|
{
|
||||||
"cardTypes": {
|
"cardTypes": {
|
||||||
|
"character": {
|
||||||
|
"back": {
|
||||||
|
"inputs": {
|
||||||
|
"incapacitated": "text"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"inputs": {
|
||||||
|
"hp": "number",
|
||||||
|
"power": "text",
|
||||||
|
"powerText": "textarea"
|
||||||
|
}
|
||||||
|
},
|
||||||
"deck": {
|
"deck": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"name": "text",
|
"name": "text",
|
||||||
|
@ -1,5 +1,40 @@
|
|||||||
{
|
{
|
||||||
"cardTypes": {
|
"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": {
|
"deck": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"name": "text",
|
"name": "text",
|
||||||
|
@ -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 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"/>
|
<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="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>
|
<g font-family="'RedStateBlueState BB'">
|
||||||
<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 id="title" x="65.489799" y="52.567066" font-size="11px" font-style="italic">Villain Title</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">
|
||||||
<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="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"/>
|
||||||
<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="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-size="10px" font-style="italic">SetUp</text>
|
||||||
<text x="47.487053" y="67.280876" font-family="'RedStateBlueState BB'" 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>
|
</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>
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 2.4 KiB |
Loading…
Reference in New Issue
Block a user