burgs overview - icon upload basic

This commit is contained in:
Azgaar 2021-06-01 02:01:06 +03:00
parent 3640320f3f
commit 709f45e03e
5 changed files with 337 additions and 196 deletions

View file

@ -1457,13 +1457,13 @@ div.states > .coaIcon > use {
pointer-events: none; pointer-events: none;
} }
div.states > .icon { div.states > .resourceIcon {
margin: 0; margin: 0;
cursor: pointer; cursor: pointer;
vertical-align: middle; vertical-align: middle;
} }
div.states > .icon > * { div.states > .resourceIcon > * {
pointer-events: none; pointer-events: none;
} }
@ -1548,7 +1548,8 @@ div.states > div.resourceBonus > span.icon-male {
#stateNameEditor div.label, #stateNameEditor div.label,
#provinceNameEditor div.label, #provinceNameEditor div.label,
#regimentBody div.label { #regimentBody div.label,
#resourceIconEditor div.label {
display: inline-block; display: inline-block;
width: 5.5em; width: 5.5em;
} }

View file

@ -208,6 +208,7 @@
<g id="textPaths"></g> <g id="textPaths"></g>
<g id="statePaths"></g> <g id="statePaths"></g>
<g id="defs-emblems"></g> <g id="defs-emblems"></g>
<g id="defs-icons"></g>
</g> </g>
<g id="defs-markers"> <g id="defs-markers">
@ -3031,6 +3032,26 @@
</div> </div>
</div> </div>
<div id="resourceIconEditor" class="dialog" style="display: none">
<div style="padding: .1em">
<div class="label">Select:</div>
<select id="resourceSelectIcon"></select>
</div>
<div style="padding: .1em">
<div class="label">Upload:</div>
<button id="resourceUploadIconRaster" data-tip="Upload custom icon as raster image">raster</button>
<button id="resourceUploadIconVector" data-tip="Upload custom vector (.svg) icon image. You can use Inscape to fix the viewBox">vector</button>
</div>
<div style="padding: .1em">
<a href="https://www.iloveimg.com/compress-image" target="_blank" data-tip="Use external tool to compress/resize raster images before upload">Comperess raster</a>
<span> | </span>
<a href="https://jakearchibald.github.io/svgomg" target="_blank" data-tip="Use external tool to optimize vector images before upload">Optimize vector</a>
</div>
<div style="padding: .1em">
<svg viewBox="0 0 200 200"><use id="resourceIconPreview"></use></svg>
</div>
</div>
<div id="unitsEditor" class="dialog stable" style="display: none"> <div id="unitsEditor" class="dialog stable" style="display: none">
<div id="unitsBody" style="margin-left:1.1em"> <div id="unitsBody" style="margin-left:1.1em">
<div class="unitsHeader" style="margin-top:.4em"> <div class="unitsHeader" style="margin-top:.4em">
@ -3468,15 +3489,14 @@
<div id="mapOverlay" style="display: none">Drop a .map file to open</div> <div id="mapOverlay" style="display: none">Drop a .map file to open</div>
<div id="fileInputs" style="display: none"> <div id="fileInputs" style="display: none">
<input type="file" accept=".map" id="mapToLoad"> <input type="file" accept=".map" id="mapToLoad"/>
<input type="file" accept=".txt,.csv" id="burgsListToLoad"> <input type="file" accept=".txt,.csv" id="burgsListToLoad"/>
<input type="file" accept=".txt" id="legendsToLoad"> <input type="file" accept=".txt" id="legendsToLoad"/>
<input type="file" accept="image/*" id="imageToLoad"> <input type="file" accept="image/*" id="imageToLoad"/>
<input type="file" accept="image/*" id="emblemImageToLoad"> <input type="file" accept=".svg" id="svgToLoad"/>
<input type="file" accept=".svg" id="emblemSVGToLoad"> <input type="file" accept=".txt" id="templateToLoad"/>
<input type="file" accept=".txt" id="templateToLoad"> <input type="file" accept=".txt" id="namesbaseToLoad"/>
<input type="file" accept=".txt" id="namesbaseToLoad"> <input type="file" accept=".json" id="styleToLoad"/>
<input type="file" accept=".json" id="styleToLoad">
</div> </div>
<!-- svg elements not required for map display --> <!-- svg elements not required for map display -->

View file

@ -137,6 +137,8 @@ async function getMapURL(type, subtype) {
cloneDefs.querySelector('#defs-emblems')?.remove(); cloneDefs.querySelector('#defs-emblems')?.remove();
} }
// add displayed resource icons TODO
// replace ocean pattern href to base64 // replace ocean pattern href to base64
if (PRODUCTION && cloneEl.getElementById('oceanicPattern')) { if (PRODUCTION && cloneEl.getElementById('oceanicPattern')) {
const el = cloneEl.getElementById('oceanicPattern'); const el = cloneEl.getElementById('oceanicPattern');
@ -1310,6 +1312,7 @@ function parseLoadedData(data) {
if (version < 1.7) { if (version < 1.7) {
// v 1.7 added resources layer // v 1.7 added resources layer
goods = viewbox.append('g').attr('id', 'goods'); goods = viewbox.append('g').attr('id', 'goods');
defs.append('g').attr('id', 'defs-icons');
} }
void (function checkDataIntegrity() { void (function checkDataIntegrity() {

View file

@ -1,20 +1,23 @@
"use strict"; 'use strict';
function editEmblem(type, id, el) { function editEmblem(type, id, el) {
if (customization) return; if (customization) return;
if (!id && d3.event) defineEmblemData(d3.event); if (!id && d3.event) defineEmblemData(d3.event);
emblems.selectAll("use").call(d3.drag().on("drag", dragEmblem)).classed("draggable", true); emblems.selectAll('use').call(d3.drag().on('drag', dragEmblem)).classed('draggable', true);
const emblemStates = document.getElementById("emblemStates"); const emblemStates = document.getElementById('emblemStates');
const emblemProvinces = document.getElementById("emblemProvinces"); const emblemProvinces = document.getElementById('emblemProvinces');
const emblemBurgs = document.getElementById("emblemBurgs"); const emblemBurgs = document.getElementById('emblemBurgs');
const emblemShapeSelector = document.getElementById("emblemShapeSelector"); const emblemShapeSelector = document.getElementById('emblemShapeSelector');
updateElementSelectors(type, id, el); updateElementSelectors(type, id, el);
$("#emblemEditor").dialog({ $('#emblemEditor').dialog({
title: "Edit Emblem", resizable: true, width: "18.2em", height: "auto", title: 'Edit Emblem',
position: {my: "left top", at: "left+10 top+10", of: "svg", collision: "fit"}, resizable: true,
width: '18.2em',
height: 'auto',
position: {my: 'left top', at: 'left+10 top+10', of: 'svg', collision: 'fit'},
close: closeEmblemEditor close: closeEmblemEditor
}); });
@ -23,45 +26,45 @@ function editEmblem(type, id, el) {
emblemProvinces.oninput = selectProvince; emblemProvinces.oninput = selectProvince;
emblemBurgs.oninput = selectBurg; emblemBurgs.oninput = selectBurg;
emblemShapeSelector.oninput = changeShape; emblemShapeSelector.oninput = changeShape;
document.getElementById("emblemSizeSlider").oninput = changeSize; document.getElementById('emblemSizeSlider').oninput = changeSize;
document.getElementById("emblemSizeNumber").oninput = changeSize; document.getElementById('emblemSizeNumber').oninput = changeSize;
document.getElementById("emblemsRegenerate").onclick = regenerate; document.getElementById('emblemsRegenerate').onclick = regenerate;
document.getElementById("emblemsArmoria").onclick = openInArmoria; document.getElementById('emblemsArmoria').onclick = openInArmoria;
document.getElementById("emblemsUpload").onclick = toggleUpload; document.getElementById('emblemsUpload').onclick = toggleUpload;
document.getElementById("emblemsUploadImage").onclick = () => emblemImageToLoad.click(); document.getElementById('emblemsUploadImage').onclick = () => imageToLoad.click();
document.getElementById("emblemsUploadSVG").onclick = () => emblemSVGToLoad.click(); document.getElementById('emblemsUploadSVG').onclick = () => svgToLoad.click();
document.getElementById("emblemImageToLoad").onchange = () => upload("image"); document.getElementById('imageToLoad').onchange = () => uploadImage('image');
document.getElementById("emblemSVGToLoad").onchange = () => upload("svg"); document.getElementById('svgToLoad').onchange = () => uploadImage('svg');
document.getElementById("emblemsDownload").onclick = toggleDownload; document.getElementById('emblemsDownload').onclick = toggleDownload;
document.getElementById("emblemsDownloadSVG").onclick = () => download("svg"); document.getElementById('emblemsDownloadSVG').onclick = () => download('svg');
document.getElementById("emblemsDownloadPNG").onclick = () => download("png"); document.getElementById('emblemsDownloadPNG').onclick = () => download('png');
document.getElementById("emblemsDownloadJPG").onclick = () => download("jpeg"); document.getElementById('emblemsDownloadJPG').onclick = () => download('jpeg');
document.getElementById("emblemsGallery").onclick = downloadGallery; document.getElementById('emblemsGallery').onclick = downloadGallery;
document.getElementById("emblemsFocus").onclick = showArea; document.getElementById('emblemsFocus').onclick = showArea;
function defineEmblemData(e) { function defineEmblemData(e) {
const parent = e.target.parentNode; const parent = e.target.parentNode;
const [g, t] = parent.id === "burgEmblems" ? [pack.burgs, "burg"] : const [g, t] = parent.id === 'burgEmblems' ? [pack.burgs, 'burg'] : parent.id === 'provinceEmblems' ? [pack.provinces, 'province'] : [pack.states, 'state'];
parent.id === "provinceEmblems" ? [pack.provinces, "province"] :
[pack.states, "state"];
const i = +e.target.dataset.i; const i = +e.target.dataset.i;
type = t; type = t;
id = type+"COA"+i; id = type + 'COA' + i;
el = g[i]; el = g[i];
} }
function updateElementSelectors(type, id, el) { function updateElementSelectors(type, id, el) {
let state = 0, province = 0, burg = 0; let state = 0,
province = 0,
burg = 0;
// set active type // set active type
emblemStates.parentElement.className = type === "state" ? "active" : ""; emblemStates.parentElement.className = type === 'state' ? 'active' : '';
emblemProvinces.parentElement.className = type === "province" ? "active" : ""; emblemProvinces.parentElement.className = type === 'province' ? 'active' : '';
emblemBurgs.parentElement.className = type === "burg" ? "active" : ""; emblemBurgs.parentElement.className = type === 'burg' ? 'active' : '';
// define selected values // define selected values
if (type === "state") state = el.i; if (type === 'state') state = el.i;
else if (type === "province") { else if (type === 'province') {
province = el.i province = el.i;
state = pack.states[el.state].i; state = pack.states[el.state].i;
} else { } else {
burg = el.i; burg = el.i;
@ -69,24 +72,24 @@ function editEmblem(type, id, el) {
state = el.state; state = el.state;
} }
const validBurgs = pack.burgs.filter(burg => burg.i && !burg.removed && burg.coa); const validBurgs = pack.burgs.filter((burg) => burg.i && !burg.removed && burg.coa);
// update option list and select actual values // update option list and select actual values
emblemStates.options.length = 0; emblemStates.options.length = 0;
const neutralBurgs = validBurgs.filter(burg => !burg.state); const neutralBurgs = validBurgs.filter((burg) => !burg.state);
if (neutralBurgs.length) emblemStates.options.add(new Option(pack.states[0].name, 0, false, !state)); if (neutralBurgs.length) emblemStates.options.add(new Option(pack.states[0].name, 0, false, !state));
const stateList = pack.states.filter(state => state.i && !state.removed); const stateList = pack.states.filter((state) => state.i && !state.removed);
stateList.forEach(s => emblemStates.options.add(new Option(s.name, s.i, false, s.i === state))); stateList.forEach((s) => emblemStates.options.add(new Option(s.name, s.i, false, s.i === state)));
emblemProvinces.options.length = 0; emblemProvinces.options.length = 0;
emblemProvinces.options.add(new Option("", 0, false, !province)); emblemProvinces.options.add(new Option('', 0, false, !province));
const provinceList = pack.provinces.filter(province => !province.removed && province.state === state); const provinceList = pack.provinces.filter((province) => !province.removed && province.state === state);
provinceList.forEach(p => emblemProvinces.options.add(new Option(p.name, p.i, false, p.i === province))); provinceList.forEach((p) => emblemProvinces.options.add(new Option(p.name, p.i, false, p.i === province)));
emblemBurgs.options.length = 0; emblemBurgs.options.length = 0;
emblemBurgs.options.add(new Option("", 0, false, !burg)); emblemBurgs.options.add(new Option('', 0, false, !burg));
const burgList = validBurgs.filter(burg => province ? pack.cells.province[burg.cell] === province : burg.state === state); const burgList = validBurgs.filter((burg) => (province ? pack.cells.province[burg.cell] === province : burg.state === state));
burgList.forEach(b => emblemBurgs.options.add(new Option(b.capital ? "👑 " + b.name : b.name, b.i, false, b.i === burg))); burgList.forEach((b) => emblemBurgs.options.add(new Option(b.capital ? '👑 ' + b.name : b.name, b.i, false, b.i === burg)));
emblemBurgs.options[0].disabled = true; emblemBurgs.options[0].disabled = true;
COArenderer.trigger(id, el.coa); COArenderer.trigger(id, el.coa);
@ -95,35 +98,35 @@ function editEmblem(type, id, el) {
function updateEmblemData(type, id, el) { function updateEmblemData(type, id, el) {
if (!el.coa) return; if (!el.coa) return;
document.getElementById("emblemImage").setAttribute("href", "#" + id); document.getElementById('emblemImage').setAttribute('href', '#' + id);
let name = el.fullName || el.name; let name = el.fullName || el.name;
if (type === "burg") name = "Burg of " + name; if (type === 'burg') name = 'Burg of ' + name;
document.getElementById("emblemArmiger").innerText = name; document.getElementById('emblemArmiger').innerText = name;
if (el.coa === "custom") emblemShapeSelector.disabled = true; if (el.coa === 'custom') emblemShapeSelector.disabled = true;
else { else {
emblemShapeSelector.disabled = false; emblemShapeSelector.disabled = false;
emblemShapeSelector.value = el.coa.shield; emblemShapeSelector.value = el.coa.shield;
} }
const size = el.coaSize || 1; const size = el.coaSize || 1;
document.getElementById("emblemSizeSlider").value = size; document.getElementById('emblemSizeSlider').value = size;
document.getElementById("emblemSizeNumber").value = size; document.getElementById('emblemSizeNumber').value = size;
} }
function selectState() { function selectState() {
const state = +this.value; const state = +this.value;
if (state) { if (state) {
type = "state"; type = 'state';
el = pack.states[state]; el = pack.states[state];
id = "stateCOA"+ state; id = 'stateCOA' + state;
} else { } else {
// select neutral burg if state is changed to Neutrals // select neutral burg if state is changed to Neutrals
const neutralBurgs = pack.burgs.filter(burg => burg.i && !burg.removed && !burg.state); const neutralBurgs = pack.burgs.filter((burg) => burg.i && !burg.removed && !burg.state);
if (!neutralBurgs.length) return; if (!neutralBurgs.length) return;
type = "burg"; type = 'burg';
el = neutralBurgs[0]; el = neutralBurgs[0];
id = "burgCOA"+ neutralBurgs[0].i; id = 'burgCOA' + neutralBurgs[0].i;
} }
updateElementSelectors(type, id, el); updateElementSelectors(type, id, el);
} }
@ -132,15 +135,15 @@ function editEmblem(type, id, el) {
const province = +this.value; const province = +this.value;
if (province) { if (province) {
type = "province"; type = 'province';
el = pack.provinces[province]; el = pack.provinces[province];
id = "provinceCOA"+ province; id = 'provinceCOA' + province;
} else { } else {
// select state if province is changed to null value // select state if province is changed to null value
const state = +emblemStates.value; const state = +emblemStates.value;
type = "state"; type = 'state';
el = pack.states[state]; el = pack.states[state];
id = "stateCOA"+ state; id = 'stateCOA' + state;
} }
updateElementSelectors(type, id, el); updateElementSelectors(type, id, el);
@ -148,9 +151,9 @@ function editEmblem(type, id, el) {
function selectBurg() { function selectBurg() {
const burg = +this.value; const burg = +this.value;
type = "burg"; type = 'burg';
el = pack.burgs[burg]; el = pack.burgs[burg];
id = "burgCOA"+ burg; id = 'burgCOA' + burg;
updateElementSelectors(type, id, el); updateElementSelectors(type, id, el);
} }
@ -166,35 +169,38 @@ function editEmblem(type, id, el) {
} }
function changeSize() { function changeSize() {
const size = el.coaSize = +this.value; const size = (el.coaSize = +this.value);
document.getElementById("emblemSizeSlider").value = size; document.getElementById('emblemSizeSlider').value = size;
document.getElementById("emblemSizeNumber").value = size; document.getElementById('emblemSizeNumber').value = size;
const g = emblems.select("#"+type+"Emblems"); const g = emblems.select('#' + type + 'Emblems');
g.select("[data-i='" + el.i + "']").remove(); g.select("[data-i='" + el.i + "']").remove();
if (!size) return; if (!size) return;
// re-append use element // re-append use element
const categotySize = +g.attr("font-size"); const categotySize = +g.attr('font-size');
const shift = categotySize * size / 2; const shift = (categotySize * size) / 2;
const x = el.x || el.pole[0]; const x = el.x || el.pole[0];
const y = el.y || el.pole[1]; const y = el.y || el.pole[1];
g.append("use").attr("data-i", el.i) g.append('use')
.attr("x", rn(x - shift), 2).attr("y", rn(y - shift), 2) .attr('data-i', el.i)
.attr("width", size+"em").attr("height", size+"em") .attr('x', rn(x - shift), 2)
.attr("href", "#"+id); .attr('y', rn(y - shift), 2)
.attr('width', size + 'em')
.attr('height', size + 'em')
.attr('href', '#' + id);
} }
function regenerate() { function regenerate() {
let parent = null; let parent = null;
if (type === "province") parent = pack.states[el.state]; if (type === 'province') parent = pack.states[el.state];
else if (type === "burg") { else if (type === 'burg') {
const province = pack.cells.province[el.cell]; const province = pack.cells.province[el.cell];
parent = province ? pack.provinces[province] : pack.states[el.state]; parent = province ? pack.provinces[province] : pack.states[el.state];
} }
const shield = el.coa.shield || COA.getShield(el.culture || parent?.culture || 0, el.state); const shield = el.coa.shield || COA.getShield(el.culture || parent?.culture || 0, el.state);
el.coa = COA.generate(parent ? parent.coa : null, .3, .1, null); el.coa = COA.generate(parent ? parent.coa : null, 0.3, 0.1, null);
el.coa.shield = shield; el.coa.shield = shield;
emblemShapeSelector.disabled = false; emblemShapeSelector.disabled = false;
emblemShapeSelector.value = el.coa.shield; emblemShapeSelector.value = el.coa.shield;
@ -205,85 +211,80 @@ function editEmblem(type, id, el) {
} }
function openInArmoria() { function openInArmoria() {
const coa = el.coa && el.coa !== "custom" ? el.coa : {t1: "sable"}; const coa = el.coa && el.coa !== 'custom' ? el.coa : {t1: 'sable'};
const json = JSON.stringify(coa).replaceAll("#", "%23"); const json = JSON.stringify(coa).replaceAll('#', '%23');
const url = `https://azgaar.github.io/Armoria/?coa=${json}&from=FMG`; const url = `https://azgaar.github.io/Armoria/?coa=${json}&from=FMG`;
openURL(url); openURL(url);
} }
function toggleUpload() { function toggleUpload() {
document.getElementById("emblemDownloadControl").classList.add("hidden"); document.getElementById('emblemDownloadControl').classList.add('hidden');
const buttons = document.getElementById("emblemUploadControl"); const buttons = document.getElementById('emblemUploadControl');
buttons.classList.toggle("hidden"); buttons.classList.toggle('hidden');
} }
function upload(type) { function uploadImage(type) {
const input = type === "image" ? document.getElementById("emblemImageToLoad") : document.getElementById("emblemSVGToLoad"); const input = type === 'image' ? document.getElementById('imageToLoad') : document.getElementById('svgToLoad');
const file = input.files[0]; const file = input.files[0];
input.value = ""; input.value = '';
if (file.size > 500000) { if (file.size > 500000) return tip(`File is too big, please optimize file size up to 500kB and re-upload. Recommended size is 200x200 px and up to 100kB`, true, 'error', 5000);
tip(`File is too big, please optimize file size up to 500kB and re-upload. Recommended size is 200x200 px and up to 100kB`, true, "error", 5000);
return;
}
const reader = new FileReader(); const reader = new FileReader();
reader.onload = function (readerEvent) { reader.onload = function (readerEvent) {
const result = readerEvent.target.result; const result = readerEvent.target.result;
const defs = document.getElementById("defs-emblems"); const defs = document.getElementById('defs-emblems');
const coa = document.getElementById(id); // old emblem const coa = document.getElementById(id); // old emblem
if (type === "image") { if (type === 'image') {
const svg = `<svg id="${id}" xmlns="http://www.w3.org/2000/svg" width="200" height="200" viewBox="0 0 200 200"><image x="0" y="0" width="200" height="200" href="${result}"/></svg>`; const svg = `<svg id="${id}" xmlns="http://www.w3.org/2000/svg" width="200" height="200" viewBox="0 0 200 200"><image x="0" y="0" width="200" height="200" href="${result}"/></svg>`;
defs.insertAdjacentHTML("beforeend", svg); defs.insertAdjacentHTML('beforeend', svg);
} else { } else {
const el = document.createElement("html"); const el = document.createElement('html');
el.innerHTML = result; el.innerHTML = result;
// remove sodipodi and inkscape attributes // remove sodipodi and inkscape attributes
el.querySelectorAll("*").forEach(el => { el.querySelectorAll('*').forEach((el) => {
const attributes = el.getAttributeNames(); const attributes = el.getAttributeNames();
attributes.forEach(attr => { attributes.forEach((attr) => {
if (attr.includes("inkscape") || attr.includes("sodipodi")) el.removeAttribute(attr); if (attr.includes('inkscape') || attr.includes('sodipodi')) el.removeAttribute(attr);
}); });
}); });
const svg = el.querySelector("svg"); const svg = el.querySelector('svg');
if (!svg) { if (!svg) return tip('The file should be prepated for load to FMG. Please use Armoria or other relevant tools', false, 'error');
tip("The file should be prepated for load to FMG. Please use Armoria or other relevant tools", false, "error");
return;
}
const newEmblem = defs.appendChild(svg); const newEmblem = defs.appendChild(svg);
newEmblem.id = id; newEmblem.id = id;
newEmblem.setAttribute("width", 200); newEmblem.setAttribute('width', 200);
newEmblem.setAttribute("height", 200); newEmblem.setAttribute('height', 200);
} }
if (coa) coa.remove(); // remove old emblem if (coa) coa.remove(); // remove old emblem
el.coa = "custom"; el.coa = 'custom';
emblemShapeSelector.disabled = true; emblemShapeSelector.disabled = true;
}; };
if (type === "image") reader.readAsDataURL(file); else reader.readAsText(file); if (type === 'image') reader.readAsDataURL(file);
else reader.readAsText(file);
} }
function toggleDownload() { function toggleDownload() {
document.getElementById("emblemUploadControl").classList.add("hidden"); document.getElementById('emblemUploadControl').classList.add('hidden');
const buttons = document.getElementById("emblemDownloadControl"); const buttons = document.getElementById('emblemDownloadControl');
buttons.classList.toggle("hidden"); buttons.classList.toggle('hidden');
} }
async function download(format) { async function download(format) {
const coa = document.getElementById(id); const coa = document.getElementById(id);
const size = +emblemsDownloadSize.value; const size = +emblemsDownloadSize.value;
const url = await getURL(coa, size); const url = await getURL(coa, size);
const link = document.createElement("a"); const link = document.createElement('a');
link.download = getFileName(`Emblem ${el.fullName || el.name}`) + "." + format; link.download = getFileName(`Emblem ${el.fullName || el.name}`) + '.' + format;
if (format === "svg") downloadSVG(url, link); else downloadRaster(format, url, link, size); if (format === 'svg') downloadSVG(url, link);
document.getElementById("emblemDownloadControl").classList.add("hidden"); else downloadRaster(format, url, link, size);
document.getElementById('emblemDownloadControl').classList.add('hidden');
} }
function downloadSVG(url, link) { function downloadSVG(url, link) {
@ -292,24 +293,24 @@ function editEmblem(type, id, el) {
} }
function downloadRaster(format, url, link, size) { function downloadRaster(format, url, link, size) {
const canvas = document.createElement("canvas"); const canvas = document.createElement('canvas');
const ctx = canvas.getContext("2d"); const ctx = canvas.getContext('2d');
canvas.width = size; canvas.width = size;
canvas.height = size; canvas.height = size;
const img = new Image(); const img = new Image();
img.src = url; img.src = url;
img.onload = function () { img.onload = function () {
if (format === "jpeg") { if (format === 'jpeg') {
ctx.fillStyle = "#fff"; ctx.fillStyle = '#fff';
ctx.fillRect(0, 0, canvas.width, canvas.height); ctx.fillRect(0, 0, canvas.width, canvas.height);
} }
ctx.drawImage(img, 0, 0, canvas.width, canvas.height); ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
const dataURL = canvas.toDataURL("image/" + format, .92); const dataURL = canvas.toDataURL('image/' + format, 0.92);
link.href = dataURL; link.href = dataURL;
link.click(); link.click();
window.setTimeout(() => window.URL.revokeObjectURL(dataURL), 6000); window.setTimeout(() => window.URL.revokeObjectURL(dataURL), 6000);
} };
} }
async function getURL(svg, size) { async function getURL(svg, size) {
@ -322,61 +323,85 @@ function editEmblem(type, id, el) {
function getSVG(svg, size) { function getSVG(svg, size) {
const clone = svg.cloneNode(true); const clone = svg.cloneNode(true);
clone.setAttribute("width", size); clone.setAttribute('width', size);
clone.setAttribute("height", size); clone.setAttribute('height', size);
return (new XMLSerializer()).serializeToString(clone); return new XMLSerializer().serializeToString(clone);
} }
async function downloadGallery() { async function downloadGallery() {
const name = getFileName("Emblems Gallery"); const name = getFileName('Emblems Gallery');
const validStates = pack.states.filter(s => s.i && !s.removed && s.coa); const validStates = pack.states.filter((s) => s.i && !s.removed && s.coa);
const validProvinces = pack.provinces.filter(p => p.i && !p.removed && p.coa); const validProvinces = pack.provinces.filter((p) => p.i && !p.removed && p.coa);
const validBurgs = pack.burgs.filter(b => b.i && !b.removed && b.coa); const validBurgs = pack.burgs.filter((b) => b.i && !b.removed && b.coa);
await renderAllEmblems(validStates, validProvinces, validBurgs); await renderAllEmblems(validStates, validProvinces, validBurgs);
runDownload(); runDownload();
function runDownload() { function runDownload() {
const back = `<a href="javascript:history.back()">Go Back</a>`; const back = `<a href="javascript:history.back()">Go Back</a>`;
const stateSection = `<div><h2>States</h2>` + validStates.map(state => { const stateSection =
const el = document.getElementById("stateCOA"+state.i); `<div><h2>States</h2>` +
validStates
.map((state) => {
const el = document.getElementById('stateCOA' + state.i);
return `<figure id="state_${state.i}"><a href="#provinces_${state.i}"><figcaption>${state.fullName}</figcaption>${getSVG(el, 200)}</a></figure>`; return `<figure id="state_${state.i}"><a href="#provinces_${state.i}"><figcaption>${state.fullName}</figcaption>${getSVG(el, 200)}</a></figure>`;
}).join("") + `</div>`; })
.join('') +
`</div>`;
const provinceSections = validStates.map(state => { const provinceSections = validStates
const stateProvinces = validProvinces.filter(p => p.state === state.i); .map((state) => {
const figures = stateProvinces.map(province => { const stateProvinces = validProvinces.filter((p) => p.state === state.i);
const el = document.getElementById("provinceCOA"+province.i); const figures = stateProvinces
.map((province) => {
const el = document.getElementById('provinceCOA' + province.i);
return `<figure id="province_${province.i}"><a href="#burgs_${province.i}"><figcaption>${province.fullName}</figcaption>${getSVG(el, 200)}</a></figure>`; return `<figure id="province_${province.i}"><a href="#burgs_${province.i}"><figcaption>${province.fullName}</figcaption>${getSVG(el, 200)}</a></figure>`;
}).join(""); })
return stateProvinces.length ? `<div id="provinces_${state.i}">${back}<h2>${state.fullName} provinces</h2>${figures}</div>` : ""; .join('');
}).join(""); return stateProvinces.length ? `<div id="provinces_${state.i}">${back}<h2>${state.fullName} provinces</h2>${figures}</div>` : '';
})
.join('');
const burgSections = validStates.map(state => { const burgSections = validStates
const stateBurgs = validBurgs.filter(b => b.state === state.i); .map((state) => {
let stateBurgSections = validProvinces.filter(p => p.state === state.i).map(province => { const stateBurgs = validBurgs.filter((b) => b.state === state.i);
const provinceBurgs = stateBurgs.filter(b => pack.cells.province[b.cell] === province.i); let stateBurgSections = validProvinces
const provinceBurgFigures = provinceBurgs.map(burg => { .filter((p) => p.state === state.i)
const el = document.getElementById("burgCOA"+burg.i); .map((province) => {
const provinceBurgs = stateBurgs.filter((b) => pack.cells.province[b.cell] === province.i);
const provinceBurgFigures = provinceBurgs
.map((burg) => {
const el = document.getElementById('burgCOA' + burg.i);
return `<figure id="burg_${burg.i}"><figcaption>${burg.name}</figcaption>${getSVG(el, 200)}</figure>`; return `<figure id="burg_${burg.i}"><figcaption>${burg.name}</figcaption>${getSVG(el, 200)}</figure>`;
}).join(""); })
return provinceBurgs.length ? `<div id="burgs_${province.i}">${back}<h2>${province.fullName} burgs</h2>${provinceBurgFigures}</div>` : ""; .join('');
}).join(""); return provinceBurgs.length ? `<div id="burgs_${province.i}">${back}<h2>${province.fullName} burgs</h2>${provinceBurgFigures}</div>` : '';
})
.join('');
const stateBurgOutOfProvinces = stateBurgs.filter(b => !pack.cells.province[b.cell]); const stateBurgOutOfProvinces = stateBurgs.filter((b) => !pack.cells.province[b.cell]);
const stateBurgOutOfProvincesFigures = stateBurgOutOfProvinces.map(burg => { const stateBurgOutOfProvincesFigures = stateBurgOutOfProvinces
const el = document.getElementById("burgCOA"+burg.i); .map((burg) => {
const el = document.getElementById('burgCOA' + burg.i);
return `<figure id="burg_${burg.i}"><figcaption>${burg.name}</figcaption>${getSVG(el, 200)}</figure>`; return `<figure id="burg_${burg.i}"><figcaption>${burg.name}</figcaption>${getSVG(el, 200)}</figure>`;
}).join(""); })
.join('');
if (stateBurgOutOfProvincesFigures) stateBurgSections += `<div><h2>${state.fullName} burgs under direct control</h2>${stateBurgOutOfProvincesFigures}</div>`; if (stateBurgOutOfProvincesFigures) stateBurgSections += `<div><h2>${state.fullName} burgs under direct control</h2>${stateBurgOutOfProvincesFigures}</div>`;
return stateBurgSections; return stateBurgSections;
}).join(""); })
.join('');
const neutralBurgs = validBurgs.filter(b => !b.state); const neutralBurgs = validBurgs.filter((b) => !b.state);
const neutralsSection = neutralBurgs.length ? "<div><h2>Independent burgs</h2>" + neutralBurgs.map(burg => { const neutralsSection = neutralBurgs.length
const el = document.getElementById("burgCOA"+burg.i); ? '<div><h2>Independent burgs</h2>' +
neutralBurgs
.map((burg) => {
const el = document.getElementById('burgCOA' + burg.i);
return `<figure id="burg_${burg.i}"><figcaption>${burg.name}</figcaption>${getSVG(el, 200)}</figure>`; return `<figure id="burg_${burg.i}"><figcaption>${burg.name}</figcaption>${getSVG(el, 200)}</figure>`;
}).join("") + "</div>" : ""; })
.join('') +
'</div>'
: '';
const FMG = `<a href="https://azgaar.github.io/Fantasy-Map-Generator" target="_blank">Azgaar's Fantasy Map Generator</a>`; const FMG = `<a href="https://azgaar.github.io/Fantasy-Map-Generator" target="_blank">Azgaar's Fantasy Map Generator</a>`;
const license = `<a target="_blank" href="https://github.com/Azgaar/Armoria#license">the license</a>`; const license = `<a target="_blank" href="https://github.com/Azgaar/Armoria#license">the license</a>`;
@ -402,32 +427,33 @@ function editEmblem(type, id, el) {
${neutralsSection} ${neutralsSection}
<address>Generated by ${FMG}. The tool is free, but images may be copyrighted, see ${license}</address> <address>Generated by ${FMG}. The tool is free, but images may be copyrighted, see ${license}</address>
</body></html>`; </body></html>`;
downloadFile(html, name + ".html", "text/plain"); downloadFile(html, name + '.html', 'text/plain');
} }
} }
async function renderAllEmblems(states, provinces, burgs) { async function renderAllEmblems(states, provinces, burgs) {
tip("Preparing for download...", true, "warn"); tip('Preparing for download...', true, 'warn');
const statePromises = states.map(state => COArenderer.trigger("stateCOA"+state.i, state.coa)); const statePromises = states.map((state) => COArenderer.trigger('stateCOA' + state.i, state.coa));
const provincePromises = provinces.map(province => COArenderer.trigger("provinceCOA"+province.i, province.coa)); const provincePromises = provinces.map((province) => COArenderer.trigger('provinceCOA' + province.i, province.coa));
const burgPromises = burgs.map(burg => COArenderer.trigger("burgCOA"+burg.i, burg.coa)); const burgPromises = burgs.map((burg) => COArenderer.trigger('burgCOA' + burg.i, burg.coa));
const promises = [...statePromises, ...provincePromises, ...burgPromises]; const promises = [...statePromises, ...provincePromises, ...burgPromises];
return Promise.allSettled(promises).then(res => clearMainTip()); return Promise.allSettled(promises).then((res) => clearMainTip());
} }
function dragEmblem() { function dragEmblem() {
const tr = parseTransform(this.getAttribute("transform")); const tr = parseTransform(this.getAttribute('transform'));
const x = +tr[0] - d3.event.x, y = +tr[1] - d3.event.y; const x = +tr[0] - d3.event.x,
y = +tr[1] - d3.event.y;
d3.event.on("drag", function() { d3.event.on('drag', function () {
const transform = `translate(${(x + d3.event.x)},${(y + d3.event.y)})`; const transform = `translate(${x + d3.event.x},${y + d3.event.y})`;
this.setAttribute("transform", transform); this.setAttribute('transform', transform);
}); });
} }
function closeEmblemEditor() { function closeEmblemEditor() {
emblems.selectAll("use").call(d3.drag().on("drag", null)).attr("class", null); emblems.selectAll('use').call(d3.drag().on('drag', null)).attr('class', null);
} }
} }

View file

@ -34,6 +34,7 @@ function editResources() {
cl = el.classList, cl = el.classList,
line = el.parentNode; line = el.parentNode;
const resource = Resources.get(+line.dataset.id); const resource = Resources.get(+line.dataset.id);
if (cl.contains('resourceIcon')) return changeIcon(resource, line, el);
if (cl.contains('resourceCategory')) return changeCategory(resource, line, el); if (cl.contains('resourceCategory')) return changeCategory(resource, line, el);
if (cl.contains('resourceModel')) return changeModel(resource, line, el); if (cl.contains('resourceModel')) return changeModel(resource, line, el);
if (cl.contains('resourceBonus')) return changeBonus(resource, line, el); if (cl.contains('resourceBonus')) return changeBonus(resource, line, el);
@ -82,7 +83,7 @@ function editResources() {
data-id=${r.i} data-name="${r.name}" data-color="${r.color}" data-id=${r.i} data-name="${r.name}" data-color="${r.color}"
data-category="${r.category}" data-chance="${r.chance}" data-bonus="${bonusString}" data-category="${r.category}" data-chance="${r.chance}" data-bonus="${bonusString}"
data-value="${r.value}" data-model="${r.model}" data-cells="${r.cells}"> data-value="${r.value}" data-model="${r.model}" data-cells="${r.cells}">
<svg data-tip="Resource icon. Click to change" width="2em" height="2em" class="icon"> <svg data-tip="Resource icon. Click to change" width="2em" height="2em" class="resourceIcon">
<circle cx="50%" cy="50%" r="42%" fill="${r.color}" stroke="${stroke}"/> <circle cx="50%" cy="50%" r="42%" fill="${r.color}" stroke="${stroke}"/>
<use href="#${r.icon}" x="10%" y="10%" width="80%" height="80%"/> <use href="#${r.icon}" x="10%" y="10%" width="80%" height="80%"/>
</svg> </svg>
@ -105,10 +106,7 @@ function editResources() {
document.getElementById('resourcesNumber').innerHTML = pack.resources.length; document.getElementById('resourcesNumber').innerHTML = pack.resources.length;
// add listeners // add listeners
// body.querySelectorAll("div.resources").forEach(el => el.addEventListener("mouseenter", ev => resourceHighlightOn(ev)));
// body.querySelectorAll("div.resources").forEach(el => el.addEventListener("mouseleave", ev => resourceHighlightOff(ev)));
body.querySelectorAll('div.states').forEach((el) => el.addEventListener('click', selectResourceOnLineClick)); body.querySelectorAll('div.states').forEach((el) => el.addEventListener('click', selectResourceOnLineClick));
body.querySelectorAll('svg.icon').forEach((el) => el.addEventListener('click', resourceChangeColor));
if (body.dataset.type === 'percentage') { if (body.dataset.type === 'percentage') {
body.dataset.type = 'absolute'; body.dataset.type = 'absolute';
@ -307,17 +305,110 @@ function editResources() {
resource.chance = line.dataset.chance = +chance; resource.chance = line.dataset.chance = +chance;
} }
function resourceChangeColor() { function changeIcon(resource, line, el) {
const circle = this.querySelector('circle'); const standardIcons = Array.from(document.getElementById('resource-icons').querySelectorAll('symbol')).map((el) => el.id);
const resource = Resources.get(+this.parentNode.dataset.id); const standardIconsOptions = standardIcons.map((icon) => `<option value=${icon}${resource.icon === icon ? 'selected' : ''}>${icon}</option>`);
const callback = function (fill) { const customIconsEl = document.getElementById('defs-icons');
const customIcons = customIconsEl ? Array.from(document.getElementById('defs-icons').querySelectorAll('svg')).map((el) => el.id) : [];
const customIconsOptions = customIcons.map((icon) => `<option value=${icon}${resource.icon === icon ? 'selected' : ''}>${icon}</option>`);
const select = document.getElementById('resourceSelectIcon');
select.innerHTML = standardIconsOptions + customIconsOptions;
const preview = document.getElementById('resourceIconPreview');
preview.setAttribute('href', '#' + resource.icon);
$('#resourceIconEditor').dialog({
resizable: false,
title: 'Change Icon',
buttons: {
Cancel: function () {
$(this).dialog('close');
},
'Change color': () => changeColor(resource, line, el),
Apply: function () {
$(this).dialog('close');
resource.icon = select.value;
line.querySelector('svg.resourceIcon > use').setAttribute('href', '#' + select.value);
drawResources();
}
},
position: {my: 'center bottom', at: 'center', of: 'svg'}
});
const uploadTo = document.getElementById('defs-icons');
const onUpload = (id) => {
preview.setAttribute('href', '#' + id);
select.innerHTML += `<option value=${id}>${id}</option>`;
select.value = id;
};
// add listeners
select.onchange = () => preview.setAttribute('href', '#' + select.value);
document.getElementById('resourceUploadIconRaster').onclick = () => imageToLoad.click();
document.getElementById('resourceUploadIconVector').onclick = () => svgToLoad.click();
document.getElementById('imageToLoad').onchange = () => uploadImage('image', uploadTo, onUpload);
document.getElementById('svgToLoad').onchange = () => uploadImage('svg', uploadTo, onUpload);
}
function uploadImage(type, uploadTo, callback) {
const input = type === 'image' ? document.getElementById('imageToLoad') : document.getElementById('svgToLoad');
const file = input.files[0];
input.value = '';
if (file.size > 200000) return tip(`File is too big, please optimize file size up to 200kB and re-upload. Recommended size is 48x48 px and up to 10kB`, true, 'error', 5000);
const reader = new FileReader();
reader.onload = function (readerEvent) {
const result = readerEvent.target.result;
const id = 'resource-custom-' + Math.random().toString(36).slice(-6);
if (type === 'image') {
const svg = `<svg id="${id}" xmlns="http://www.w3.org/2000/svg" width="200" height="200" viewBox="0 0 200 200"><image x="0" y="0" width="200" height="200" href="${result}"/></svg>`;
uploadTo.insertAdjacentHTML('beforeend', svg);
} else {
const el = document.createElement('html');
el.innerHTML = result;
// remove sodipodi and inkscape attributes
el.querySelectorAll('*').forEach((el) => {
const attributes = el.getAttributeNames();
attributes.forEach((attr) => {
if (attr.includes('inkscape') || attr.includes('sodipodi')) el.removeAttribute(attr);
});
});
// remove all text if source is Noun project (to make it usable)
if (result.includes('from the Noun Project')) el.querySelectorAll('text').forEach((textEl) => textEl.remove());
const svg = el.querySelector('svg');
if (!svg) return tip("The file should be prepated for load to FMG. If you don't know why it's happening, try to upload the raster image", false, 'error');
const icon = uploadTo.appendChild(svg);
icon.id = id;
icon.setAttribute('width', 200);
icon.setAttribute('height', 200);
}
callback(id);
};
if (type === 'image') reader.readAsDataURL(file);
else reader.readAsText(file);
}
function changeColor(resource, line, el) {
const circle = el.querySelector('circle');
const callback = (fill) => {
const stroke = Resources.getStroke(fill); const stroke = Resources.getStroke(fill);
circle.setAttribute('fill', fill); circle.setAttribute('fill', fill);
circle.setAttribute('stroke', stroke); circle.setAttribute('stroke', stroke);
resource.color = fill; resource.color = fill;
resource.stroke = stroke; resource.stroke = stroke;
goods.selectAll(`circle[data-i='${resource.i}']`).attr('fill', fill).attr('stroke', stroke); goods.selectAll(`circle[data-i='${resource.i}']`).attr('fill', fill).attr('stroke', stroke);
line.dataset.color = fill;
}; };
openPicker(resource.color, callback, {allowHatching: false}); openPicker(resource.color, callback, {allowHatching: false});