annotate template strings for syntax hightlight and format

This commit is contained in:
Azgaar 2022-05-08 19:22:15 +03:00
parent 5a35f0d320
commit 1573fad58d
35 changed files with 816 additions and 460 deletions

View file

@ -1,7 +1,5 @@
// fill-box cannot use Shadow DOM as it needs access to svg hatches
// append stylesheet
{ {
const style = ` const style = /* css */ `
fill-box:not([disabled]) { fill-box:not([disabled]) {
cursor: pointer; cursor: pointer;
} }
@ -14,7 +12,8 @@
fill-box > svg > rect { fill-box > svg > rect {
stroke: #666666; stroke: #666666;
stroke-width: 2; stroke-width: 2;
}`; }
`;
const styleElement = document.createElement("style"); const styleElement = document.createElement("style");
styleElement.setAttribute("type", "text/css"); styleElement.setAttribute("type", "text/css");
@ -24,7 +23,7 @@
{ {
const template = document.createElement("template"); const template = document.createElement("template");
template.innerHTML = ` template.innerHTML = /* html */ `
<svg> <svg>
<rect x="0" y="0" width="100%" height="100%"> <rect x="0" y="0" width="100%" height="100%">
</svg> </svg>
@ -70,5 +69,6 @@
} }
} }
// cannot use Shadow DOM here as need an access to svg hatches
customElements.define("fill-box", FillBox); customElements.define("fill-box", FillBox);
} }

14
main.js
View file

@ -176,8 +176,8 @@ oceanLayers.append("rect").attr("id", "oceanBase").attr("x", 0).attr("y", 0).att
document.addEventListener("DOMContentLoaded", async () => { document.addEventListener("DOMContentLoaded", async () => {
if (!location.hostname) { if (!location.hostname) {
const wiki = "https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Run-FMG-locally"; const wiki = "https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Run-FMG-locally";
alertMessage.innerHTML = `Fantasy Map Generator cannot run serverless. alertMessage.innerHTML = /* html */ `Fantasy Map Generator cannot run serverless. Follow the <a href="${wiki}" target="_blank">instructions</a> on how you can
Follow the <a href="${wiki}" target="_blank">instructions</a> on how you can easily run a local web-server`; easily run a local web-server`;
$("#alert").dialog({ $("#alert").dialog({
resizable: false, resizable: false,
@ -455,9 +455,10 @@ function showWelcomeMessage() {
const discord = link("https://discordapp.com/invite/X7E84HU", "Discord server"); const discord = link("https://discordapp.com/invite/X7E84HU", "Discord server");
const patreon = link("https://www.patreon.com/azgaar", "Patreon"); const patreon = link("https://www.patreon.com/azgaar", "Patreon");
alertMessage.innerHTML = `The Fantasy Map Generator is updated up to version <strong>${version}</strong>. alertMessage.innerHTML = /* html */ `The Fantasy Map Generator is updated up to version <strong>${version}</strong>. This version is compatible with ${changelog},
This version is compatible with ${changelog}, loaded <i>.map</i> files will be auto-updated. loaded <i>.map</i> files will be auto-updated.
<ul><strong>Latest changes:</strong> <ul>
<strong>Latest changes:</strong>
<li>Submap tool by Goteguru</li> <li>Submap tool by Goteguru</li>
<li>Resample tool by Goteguru</li> <li>Resample tool by Goteguru</li>
<li>Pre-defined heightmaps</li> <li>Pre-defined heightmaps</li>
@ -713,8 +714,7 @@ async function generate() {
const parsedError = parseError(error); const parsedError = parseError(error);
clearMainTip(); clearMainTip();
alertMessage.innerHTML = `An error has occurred on map generation. Please retry. alertMessage.innerHTML = /* html */ `An error has occurred on map generation. Please retry. <br />If error is critical, clear the stored data and try again.
<br>If error is critical, clear the stored data and try again.
<p id="errorBox">${parsedError}</p>`; <p id="errorBox">${parsedError}</p>`;
$("#alert").dialog({ $("#alert").dialog({
resizable: false, resizable: false,

View file

@ -41,7 +41,7 @@ window.BurgsAndStates = (function () {
if (sorted.length < count * 10) { if (sorted.length < count * 10) {
count = Math.floor(sorted.length / 10); count = Math.floor(sorted.length / 10);
if (!count) { if (!count) {
WARN && console.warn(`There is no populated cells. Cannot generate states`); WARN && console.warn("There is no populated cells. Cannot generate states");
return burgs; return burgs;
} else { } else {
WARN && console.warn(`Not enough populated cells (${sorted.length}). Will generate only ${count} states`); WARN && console.warn(`Not enough populated cells (${sorted.length}). Will generate only ${count} states`);
@ -883,7 +883,7 @@ window.BurgsAndStates = (function () {
dd.forEach((r, d) => { dd.forEach((r, d) => {
if (r !== "Ally" || states[d].diplomacy.includes("Vassal")) return; if (r !== "Ally" || states[d].diplomacy.includes("Vassal")) return;
if (states[d].diplomacy[attacker] !== "Rival" && ap / dp > 2 * gauss(1.6, 0.8, 0, 10, 2)) { if (states[d].diplomacy[attacker] !== "Rival" && ap / dp > 2 * gauss(1.6, 0.8, 0, 10, 2)) {
const reason = states[d].diplomacy.includes("Enemy") ? `Being already at war,` : `Frightened by ${an},`; const reason = states[d].diplomacy.includes("Enemy") ? "Being already at war," : `Frightened by ${an},`;
war.push(`${reason} ${states[d].name} severed the defense pact with ${dn}`); war.push(`${reason} ${states[d].name} severed the defense pact with ${dn}`);
dd[d] = states[d].diplomacy[defender] = "Suspicion"; dd[d] = states[d].diplomacy[defender] = "Suspicion";
return; return;

View file

@ -15,9 +15,8 @@ window.Cultures = (function () {
if (!count) { if (!count) {
WARN && console.warn(`There are no populated cells. Cannot generate cultures`); WARN && console.warn(`There are no populated cells. Cannot generate cultures`);
pack.cultures = [{name: "Wildlands", i: 0, base: 1, shield: "round"}]; pack.cultures = [{name: "Wildlands", i: 0, base: 1, shield: "round"}];
alertMessage.innerHTML = ` alertMessage.innerHTML = /* html */ ` The climate is harsh and people cannot live in this world.<br />
The climate is harsh and people cannot live in this world.<br> No cultures, states and burgs will be created.<br />
No cultures, states and burgs will be created.<br>
Please consider changing climate settings in the World Configurator`; Please consider changing climate settings in the World Configurator`;
$("#alert").dialog({ $("#alert").dialog({
resizable: false, resizable: false,
@ -31,9 +30,8 @@ window.Cultures = (function () {
return; return;
} else { } else {
WARN && console.warn(`Not enough populated cells (${populated.length}). Will generate only ${count} cultures`); WARN && console.warn(`Not enough populated cells (${populated.length}). Will generate only ${count} cultures`);
alertMessage.innerHTML = ` alertMessage.innerHTML = /* html */ ` There are only ${populated.length} populated cells and it's insufficient livable area.<br />
There are only ${populated.length} populated cells and it's insufficient livable area.<br> Only ${count} out of ${culturesInput.value} requested cultures will be generated.<br />
Only ${count} out of ${culturesInput.value} requested cultures will be generated.<br>
Please consider changing climate settings in the World Configurator`; Please consider changing climate settings in the World Configurator`;
$("#alert").dialog({ $("#alert").dialog({
resizable: false, resizable: false,

View file

@ -69,7 +69,7 @@ export function resolveVersionConflicts(version) {
labels.selectAll("textPath").each(function () { labels.selectAll("textPath").each(function () {
const text = this.textContent; const text = this.textContent;
const shift = this.getComputedTextLength() / -1.5; const shift = this.getComputedTextLength() / -1.5;
this.innerHTML = `<tspan x="${shift}">${text}</tspan>`; this.innerHTML = /* html */ `<tspan x="${shift}">${text}</tspan>`;
}); });
// v1.0 added new biome - Wetland // v1.0 added new biome - Wetland
@ -362,7 +362,7 @@ export function resolveVersionConflicts(version) {
const pattern = document.getElementById("oceanic"); const pattern = document.getElementById("oceanic");
const filter = pattern.firstElementChild.getAttribute("filter"); const filter = pattern.firstElementChild.getAttribute("filter");
const href = filter ? "./images/" + filter.replace("url(#", "").replace(")", "") + ".png" : ""; const href = filter ? "./images/" + filter.replace("url(#", "").replace(")", "") + ".png" : "";
pattern.innerHTML = `<image id="oceanicPattern" href=${href} width="100" height="100" opacity="0.2"></image>`; pattern.innerHTML = /* html */ `<image id="oceanicPattern" href=${href} width="100" height="100" opacity="0.2"></image>`;
} }
if (version < 1.62) { if (version < 1.62) {

View file

@ -50,8 +50,8 @@ function loadMapPrompt(blob) {
return; return;
} }
alertMessage.innerHTML = `Are you sure you want to load saved map?<br> alertMessage.innerHTML = /* html */ `Are you sure you want to load saved map?<br />
All unsaved changes made to the current map will be lost`; All unsaved changes made to the current map will be lost`;
$("#alert").dialog({ $("#alert").dialog({
resizable: false, resizable: false,
title: "Load saved map", title: "Load saved map",
@ -94,9 +94,10 @@ function loadMapFromURL(maplink, random) {
function showUploadErrorMessage(error, URL, random) { function showUploadErrorMessage(error, URL, random) {
ERROR && console.error(error); ERROR && console.error(error);
alertMessage.innerHTML = `Cannot load map from the ${link(URL, "link provided")}. alertMessage.innerHTML = /* html */ `Cannot load map from the ${link(URL, "link provided")}. ${
${random ? `A new random map is generated. ` : ""} random ? `A new random map is generated. ` : ""
Please ensure the linked file is reachable and CORS is allowed on server side`; } Please ensure the
linked file is reachable and CORS is allowed on server side`;
$("#alert").dialog({ $("#alert").dialog({
title: "Loading error", title: "Loading error",
width: "32em", width: "32em",
@ -563,8 +564,7 @@ async function parseLoadedData(data) {
ERROR && console.error(error); ERROR && console.error(error);
clearMainTip(); clearMainTip();
alertMessage.innerHTML = `An error is occured on map loading. Select a different file to load, alertMessage.innerHTML = /* html */ `An error is occured on map loading. Select a different file to load, <br />generate a new random map or cancel the loading
<br>generate a new random map or cancel the loading
<p id="errorBox">${parseError(error)}</p>`; <p id="errorBox">${parseError(error)}</p>`;
$("#alert").dialog({ $("#alert").dialog({

View file

@ -92,23 +92,38 @@ function editBiomes() {
totalArea += area; totalArea += area;
totalPopulation += population; totalPopulation += population;
lines += `<div class="states biomes" data-id="${i}" data-name="${b.name[i]}" data-habitability="${b.habitability[i]}" lines += /* html */ `
data-cells=${b.cells[i]} data-area=${area} data-population=${population} data-color=${b.color[i]}> <div
<fill-box fill="${b.color[i]}"></fill-box> class="states biomes"
<input data-tip="Biome name. Click and type to change" class="biomeName" value="${b.name[i]}" autocorrect="off" spellcheck="false"> data-id="${i}"
<span data-tip="Biome habitability percent" class="hide">%</span> data-name="${b.name[i]}"
<input data-tip="Biome habitability percent. Click and set new value to change" type="number" min=0 max=9999 class="biomeHabitability hide" value=${ data-habitability="${b.habitability[i]}"
b.habitability[i] data-cells=${b.cells[i]}
}> data-area=${area}
<span data-tip="Cells count" class="icon-check-empty hide"></span> data-population=${population}
<div data-tip="Cells count" class="biomeCells hide">${b.cells[i]}</div> data-color=${b.color[i]}
<span data-tip="Biome area" style="padding-right: 4px" class="icon-map-o hide"></span> >
<div data-tip="Biome area" class="biomeArea hide">${si(area) + unit}</div> <fill-box fill="${b.color[i]}"></fill-box>
<span data-tip="${populationTip}" class="icon-male hide"></span> <input data-tip="Biome name. Click and type to change" class="biomeName" value="${b.name[i]}" autocorrect="off" spellcheck="false" />
<div data-tip="${populationTip}" class="biomePopulation hide">${si(population)}</div> <span data-tip="Biome habitability percent" class="hide">%</span>
<span data-tip="Open Wikipedia article about the biome" class="icon-info-circled pointer hide"></span> <input
${i > 12 && !b.cells[i] ? '<span data-tip="Remove the custom biome" class="icon-trash-empty hide"></span>' : ""} data-tip="Biome habitability percent. Click and set new value to change"
</div>`; type="number"
min="0"
max="9999"
class="biomeHabitability hide"
value=${b.habitability[i]}
/>
<span data-tip="Cells count" class="icon-check-empty hide"></span>
<div data-tip="Cells count" class="biomeCells hide">${b.cells[i]}</div>
<span data-tip="Biome area" style="padding-right: 4px" class="icon-map-o hide"></span>
<div data-tip="Biome area" class="biomeArea hide">${si(area) + unit}</div>
<span data-tip="${populationTip}" class="icon-male hide"></span>
<div data-tip="${populationTip}" class="biomePopulation hide">${si(population)}</div>
<span data-tip="Open Wikipedia article about the biome" class="icon-info-circled pointer hide"></span>
${i > 12 && !b.cells[i] ? '<span data-tip="Remove the custom biome" class="icon-trash-empty hide"></span>' : ""}
</div>
`;
} }
body.innerHTML = lines; body.innerHTML = lines;

View file

@ -281,10 +281,10 @@ function editBurg(id) {
const burgsToRemove = burgsInGroup.filter(b => !(pack.burgs[b].capital || pack.burgs[b].lock)); const burgsToRemove = burgsInGroup.filter(b => !(pack.burgs[b].capital || pack.burgs[b].lock));
const capital = burgsToRemove.length < burgsInGroup.length; const capital = burgsToRemove.length < burgsInGroup.length;
alertMessage.innerHTML = `Are you sure you want to remove alertMessage.innerHTML = /* html */ `Are you sure you want to remove ${
${basic || capital ? "all unlocked elements in the burg group" : "the entire burg group"}? basic || capital ? "all unlocked elements in the burg group" : "the entire burg group"
<br>Please note that capital or locked burgs will not be deleted. }?
<br><br>Burgs to be removed: ${burgsToRemove.length}`; <br />Please note that capital or locked burgs will not be deleted. <br /><br />Burgs to be removed: ${burgsToRemove.length}`;
$("#alert").dialog({ $("#alert").dialog({
resizable: false, resizable: false,
title: "Remove burg group", title: "Remove burg group",
@ -545,7 +545,7 @@ function editBurg(id) {
function removeSelectedBurg() { function removeSelectedBurg() {
const id = +elSelected.attr("data-id"); const id = +elSelected.attr("data-id");
if (pack.burgs[id].capital) { if (pack.burgs[id].capital) {
alertMessage.innerHTML = `You cannot remove the burg as it is a state capital.<br><br> alertMessage.innerHTML = /* html */ `You cannot remove the burg as it is a state capital.<br /><br />
You can change the capital using Burgs Editor (shift + T)`; You can change the capital using Burgs Editor (shift + T)`;
$("#alert").dialog({ $("#alert").dialog({
resizable: false, resizable: false,

View file

@ -82,22 +82,30 @@ function overviewBurgs() {
const province = prov ? pack.provinces[prov].name : ""; const province = prov ? pack.provinces[prov].name : "";
const culture = pack.cultures[b.culture].name; const culture = pack.cultures[b.culture].name;
lines += `<div class="states" data-id=${b.i} data-name="${ lines += /* html */ `<div
b.name class="states"
}" data-state="${state}" data-province="${province}" data-culture="${culture}" data-population=${population} data-type="${type}"> data-id=${b.i}
data-name="${b.name}"
data-state="${state}"
data-province="${province}"
data-culture="${culture}"
data-population=${population}
data-type="${type}"
>
<span data-tip="Click to zoom into view" class="icon-dot-circled pointer"></span> <span data-tip="Click to zoom into view" class="icon-dot-circled pointer"></span>
<input data-tip="Burg name. Click and type to change" class="burgName" value="${b.name}" autocorrect="off" spellcheck="false"> <input data-tip="Burg name. Click and type to change" class="burgName" value="${b.name}" autocorrect="off" spellcheck="false" />
<input data-tip="Burg province" class="burgState" value="${province}" disabled> <input data-tip="Burg province" class="burgState" value="${province}" disabled />
<input data-tip="Burg state" class="burgState" value="${state}" disabled> <input data-tip="Burg state" class="burgState" value="${state}" disabled />
<select data-tip="Dominant culture. Click to change burg culture (to change cell culture use Cultures Editor)" class="stateCulture">${getCultureOptions( <select data-tip="Dominant culture. Click to change burg culture (to change cell culture use Cultures Editor)" class="stateCulture">
b.culture ${getCultureOptions(b.culture)}
)}</select> </select>
<span data-tip="Burg population" class="icon-male"></span> <span data-tip="Burg population" class="icon-male"></span>
<input data-tip="Burg population. Type to change" class="burgPopulation" value=${si(population)}> <input data-tip="Burg population. Type to change" class="burgPopulation" value=${si(population)} />
<div class="burgType"> <div class="burgType">
<span data-tip="${b.capital ? " This burg is a state capital" : "Click to assign a capital status"}" class="icon-star-empty${ <span
b.capital ? "" : " inactive pointer" data-tip="${b.capital ? " This burg is a state capital" : "Click to assign a capital status"}"
}"></span> class="icon-star-empty${b.capital ? "" : " inactive pointer"}"
></span>
<span data-tip="Click to toggle port status" class="icon-anchor pointer${b.port ? "" : " inactive"}" style="font-size:.9em"></span> <span data-tip="Click to toggle port status" class="icon-anchor pointer${b.port ? "" : " inactive"}" style="font-size:.9em"></span>
</div> </div>
<span data-tip="Edit burg" class="icon-pencil"></span> <span data-tip="Edit burg" class="icon-pencil"></span>
@ -312,11 +320,12 @@ function overviewBurgs() {
const treeLayout = d3.pack().size([w, h]).padding(3); const treeLayout = d3.pack().size([w, h]).padding(3);
// prepare svg // prepare svg
alertMessage.innerHTML = `<select id="burgsTreeType" style="display:block; margin-left:13px; font-size:11px"> alertMessage.innerHTML = /* html */ `<select id="burgsTreeType" style="display:block; margin-left:13px; font-size:11px">
<option value="states" selected>Group by state</option> <option value="states" selected>Group by state</option>
<option value="cultures">Group by culture</option> <option value="cultures">Group by culture</option>
<option value="parent">Group by province and state</option> <option value="parent">Group by province and state</option>
<option value="provinces">Group by province</option></select>`; <option value="provinces">Group by province</option>
</select>`;
alertMessage.innerHTML += `<div id='burgsInfo' class='chartInfo'>&#8205;</div>`; alertMessage.innerHTML += `<div id='burgsInfo' class='chartInfo'>&#8205;</div>`;
const svg = d3 const svg = d3
.select("#alertMessage") .select("#alertMessage")
@ -349,7 +358,7 @@ function overviewBurgs() {
const parent = d.parent.data.name; const parent = d.parent.data.name;
const population = si(d.value * populationRate * urbanization); const population = si(d.value * populationRate * urbanization);
burgsInfo.innerHTML = `${name}. ${parent}. Population: ${population}`; burgsInfo.innerHTML = /* html */ `${name}. ${parent}. Population: ${population}`;
burgHighlightOn(ev); burgHighlightOn(ev);
tip("Click to zoom into view"); tip("Click to zoom into view");
} }
@ -483,9 +492,8 @@ function overviewBurgs() {
} }
function renameBurgsInBulk() { function renameBurgsInBulk() {
alertMessage.innerHTML = `Download burgs list as a text file, make changes and re-upload the file. alertMessage.innerHTML = /* html */ `Download burgs list as a text file, make changes and re-upload the file. Make sure the file is a plain text document with each
Make sure the file is a plain text document with each name on its own line (the dilimiter is CRLF). name on its own line (the dilimiter is CRLF). If you do not want to change the name, just leave it as is`;
If you do not want to change the name, just leave it as is`;
$("#alert").dialog({ $("#alert").dialog({
title: "Burgs bulk renaming", title: "Burgs bulk renaming",

View file

@ -5,7 +5,8 @@ function editCoastline(node = d3.event.target) {
if (layerIsOn("toggleCells")) toggleCells(); if (layerIsOn("toggleCells")) toggleCells();
$("#coastlineEditor").dialog({ $("#coastlineEditor").dialog({
title: "Edit Coastline", resizable: false, title: "Edit Coastline",
resizable: false,
position: {my: "center top+20", at: "top", of: d3.event, collision: "fit"}, position: {my: "center top+20", at: "top", of: d3.event, collision: "fit"},
close: closeCoastlineEditor close: closeCoastlineEditor
}); });
@ -33,13 +34,27 @@ function editCoastline(node = d3.event.target) {
const v = pack.features[f].vertices; // coastline outer vertices const v = pack.features[f].vertices; // coastline outer vertices
const l = pack.cells.i.length; const l = pack.cells.i.length;
const c = [... new Set(v.map(v => pack.vertices.c[v]).flat())].filter(c => c < l); const c = [...new Set(v.map(v => pack.vertices.c[v]).flat())].filter(c => c < l);
debug.select("#vertices").selectAll("polygon").data(c).enter().append("polygon") debug
.attr("points", d => getPackPolygon(d)).attr("data-c", d => d); .select("#vertices")
.selectAll("polygon")
.data(c)
.enter()
.append("polygon")
.attr("points", d => getPackPolygon(d))
.attr("data-c", d => d);
debug.select("#vertices").selectAll("circle").data(v).enter().append("circle") debug
.attr("cx", d => pack.vertices.p[d][0]).attr("cy", d => pack.vertices.p[d][1]) .select("#vertices")
.attr("r", .4).attr("data-v", d => d).call(d3.drag().on("drag", dragVertex)) .selectAll("circle")
.data(v)
.enter()
.append("circle")
.attr("cx", d => pack.vertices.p[d][0])
.attr("cy", d => pack.vertices.p[d][1])
.attr("r", 0.4)
.attr("data-v", d => d)
.call(d3.drag().on("drag", dragVertex))
.on("mousemove", () => tip("Drag to move the vertex, please use for fine-tuning only. Edit heightmap to change actual cell heights")); .on("mousemove", () => tip("Drag to move the vertex, please use for fine-tuning only. Edit heightmap to change actual cell heights"));
const unit = areaUnit.value === "square" ? " " + distanceUnitInput.value + "²" : " " + areaUnit.value; const unit = areaUnit.value === "square" ? " " + distanceUnitInput.value + "²" : " " + areaUnit.value;
@ -48,12 +63,16 @@ function editCoastline(node = d3.event.target) {
} }
function dragVertex() { function dragVertex() {
const x = rn(d3.event.x, 2), y = rn(d3.event.y, 2); const x = rn(d3.event.x, 2),
y = rn(d3.event.y, 2);
this.setAttribute("cx", x); this.setAttribute("cx", x);
this.setAttribute("cy", y); this.setAttribute("cy", y);
const v = +this.dataset.v; const v = +this.dataset.v;
pack.vertices.p[v] = [x, y]; pack.vertices.p[v] = [x, y];
debug.select("#vertices").selectAll("polygon").attr("points", d => getPackPolygon(d)); debug
.select("#vertices")
.selectAll("polygon")
.attr("points", d => getPackPolygon(d));
redrawCoastline(); redrawCoastline();
} }
@ -61,11 +80,14 @@ function editCoastline(node = d3.event.target) {
lineGen.curve(d3.curveBasisClosed); lineGen.curve(d3.curveBasisClosed);
const f = +elSelected.attr("data-f"); const f = +elSelected.attr("data-f");
const vertices = pack.features[f].vertices; const vertices = pack.features[f].vertices;
const points = clipPoly(vertices.map(v => pack.vertices.p[v]), 1); const points = clipPoly(
vertices.map(v => pack.vertices.p[v]),
1
);
const d = round(lineGen(points)); const d = round(lineGen(points));
elSelected.attr("d", d); elSelected.attr("d", d);
defs.select("mask#land > path#land_"+f).attr("d", d); // update land mask defs.select("mask#land > path#land_" + f).attr("d", d); // update land mask
defs.select("mask#water > path#water_"+f).attr("d", d); // update water mask defs.select("mask#water > path#water_" + f).attr("d", d); // update water mask
const unit = areaUnit.value === "square" ? " " + distanceUnitInput.value + "²" : " " + areaUnit.value; const unit = areaUnit.value === "square" ? " " + distanceUnitInput.value + "²" : " " + areaUnit.value;
const area = Math.abs(d3.polygonArea(points)); const area = Math.abs(d3.polygonArea(points));
@ -73,12 +95,12 @@ function editCoastline(node = d3.event.target) {
} }
function showGroupSection() { function showGroupSection() {
document.querySelectorAll("#coastlineEditor > button").forEach(el => el.style.display = "none"); document.querySelectorAll("#coastlineEditor > button").forEach(el => (el.style.display = "none"));
document.getElementById("coastlineGroupsSelection").style.display = "inline-block"; document.getElementById("coastlineGroupsSelection").style.display = "inline-block";
} }
function hideGroupSection() { function hideGroupSection() {
document.querySelectorAll("#coastlineEditor > button").forEach(el => el.style.display = "inline-block"); document.querySelectorAll("#coastlineEditor > button").forEach(el => (el.style.display = "inline-block"));
document.getElementById("coastlineGroupsSelection").style.display = "none"; document.getElementById("coastlineGroupsSelection").style.display = "none";
document.getElementById("coastlineGroupName").style.display = "none"; document.getElementById("coastlineGroupName").style.display = "none";
document.getElementById("coastlineGroupName").value = ""; document.getElementById("coastlineGroupName").value = "";
@ -90,7 +112,7 @@ function editCoastline(node = d3.event.target) {
const select = document.getElementById("coastlineGroup"); const select = document.getElementById("coastlineGroup");
select.options.length = 0; // remove all options select.options.length = 0; // remove all options
coastline.selectAll("g").each(function() { coastline.selectAll("g").each(function () {
select.options.add(new Option(this.id, this.id, false, this.id === group)); select.options.add(new Option(this.id, this.id, false, this.id === group));
}); });
} }
@ -111,8 +133,14 @@ function editCoastline(node = d3.event.target) {
} }
function createNewGroup() { function createNewGroup() {
if (!this.value) {tip("Please provide a valid group name"); return;} if (!this.value) {
const group = this.value.toLowerCase().replace(/ /g, "_").replace(/[^\w\s]/gi, ""); tip("Please provide a valid group name");
return;
}
const group = this.value
.toLowerCase()
.replace(/ /g, "_")
.replace(/[^\w\s]/gi, "");
if (document.getElementById(group)) { if (document.getElementById(group)) {
tip("Element with this id already exists. Please provide a unique name", false, "error"); tip("Element with this id already exists. Please provide a unique name", false, "error");
@ -155,11 +183,14 @@ function editCoastline(node = d3.event.target) {
} }
const count = elSelected.node().parentNode.childElementCount; const count = elSelected.node().parentNode.childElementCount;
alertMessage.innerHTML = `Are you sure you want to remove the group? alertMessage.innerHTML = /* html */ `Are you sure you want to remove the group? All coastline elements of the group (${count}) will be moved under
All coastline elements of the group (${count}) will be moved under <i>sea_island</i> group`; <i>sea_island</i> group`;
$("#alert").dialog({resizable: false, title: "Remove coastline group", width:"26em", $("#alert").dialog({
resizable: false,
title: "Remove coastline group",
width: "26em",
buttons: { buttons: {
Remove: function() { Remove: function () {
$(this).dialog("close"); $(this).dialog("close");
const sea = document.getElementById("sea_island"); const sea = document.getElementById("sea_island");
const groupEl = document.getElementById(group); const groupEl = document.getElementById(group);
@ -170,7 +201,9 @@ function editCoastline(node = d3.event.target) {
document.getElementById("coastlineGroup").selectedOptions[0].remove(); document.getElementById("coastlineGroup").selectedOptions[0].remove();
document.getElementById("coastlineGroup").value = "sea_island"; document.getElementById("coastlineGroup").value = "sea_island";
}, },
Cancel: function() {$(this).dialog("close");} Cancel: function () {
$(this).dialog("close");
}
} }
}); });
} }
@ -184,4 +217,4 @@ function editCoastline(node = d3.event.target) {
debug.select("#vertices").remove(); debug.select("#vertices").remove();
unselect(); unselect();
} }
} }

View file

@ -84,22 +84,37 @@ function editCultures() {
if (!c.i) { if (!c.i) {
// Uncultured (neutral) line // Uncultured (neutral) line
lines += `<div class="states" data-id=${c.i} data-name="${c.name}" data-color="" data-cells=${c.cells} lines += /* html */ `<div
data-area=${area} data-population=${population} data-base=${c.base} data-type="" data-expansionism="" data-emblems="${c.shield}"> class="states"
data-id=${c.i}
data-name="${c.name}"
data-color=""
data-cells=${c.cells}
data-area=${area}
data-population=${population}
data-base=${c.base}
data-type=""
data-expansionism=""
data-emblems="${c.shield}"
>
<svg width="9" height="9" class="placeholder"></svg> <svg width="9" height="9" class="placeholder"></svg>
<input data-tip="Neutral culture name. Click and type to change" class="cultureName italic" value="${c.name}" autocorrect="off" spellcheck="false"> <input data-tip="Neutral culture name. Click and type to change" class="cultureName italic" value="${c.name}" autocorrect="off" spellcheck="false" />
<span class="icon-cw placeholder"></span> <span class="icon-cw placeholder"></span>
<span data-tip="Cells count" class="icon-check-empty hide"></span> <span data-tip="Cells count" class="icon-check-empty hide"></span>
<div data-tip="Cells count" class="stateCells hide">${c.cells}</div> <div data-tip="Cells count" class="stateCells hide">${c.cells}</div>
<span class="icon-resize-full placeholder hide"></span> <span class="icon-resize-full placeholder hide"></span>
<input class="statePower placeholder hide" type="number"> <input class="statePower placeholder hide" type="number" />
<select class="cultureType placeholder">${getTypeOptions(c.type)}</select> <select class="cultureType placeholder">
${getTypeOptions(c.type)}
</select>
<span data-tip="Culture area" style="padding-right: 4px" class="icon-map-o hide"></span> <span data-tip="Culture area" style="padding-right: 4px" class="icon-map-o hide"></span>
<div data-tip="Culture area" class="biomeArea hide">${si(area) + unit}</div> <div data-tip="Culture area" class="biomeArea hide">${si(area) + unit}</div>
<span data-tip="${populationTip}" class="icon-male hide"></span> <span data-tip="${populationTip}" class="icon-male hide"></span>
<div data-tip="${populationTip}" class="culturePopulation hide">${si(population)}</div> <div data-tip="${populationTip}" class="culturePopulation hide">${si(population)}</div>
<span data-tip="Click to re-generate names for burgs with this culture assigned" class="icon-arrows-cw hide"></span> <span data-tip="Click to re-generate names for burgs with this culture assigned" class="icon-arrows-cw hide"></span>
<select data-tip="Culture namesbase. Click to change. Click on arrows to re-generate names" class="cultureBase">${getBaseOptions(c.base)}</select> <select data-tip="Culture namesbase. Click to change. Click on arrows to re-generate names" class="cultureBase">
${getBaseOptions(c.base)}
</select>
${ ${
selectShape selectShape
? `<select data-tip="Emblem shape associated with culture. Click to change" class="cultureShape hide">${getShapeOptions(c.shield)}</select>` ? `<select data-tip="Emblem shape associated with culture. Click to change" class="cultureShape hide">${getShapeOptions(c.shield)}</select>`
@ -109,24 +124,45 @@ function editCultures() {
continue; continue;
} }
lines += `<div class="states cultures" data-id=${c.i} data-name="${c.name}" data-color="${c.color}" data-cells=${c.cells} lines += /* html */ `<div
data-area=${area} data-population=${population} data-base=${c.base} data-type=${c.type} data-expansionism=${c.expansionism} data-emblems="${c.shield}"> class="states cultures"
data-id=${c.i}
data-name="${c.name}"
data-color="${c.color}"
data-cells=${c.cells}
data-area=${area}
data-population=${population}
data-base=${c.base}
data-type=${c.type}
data-expansionism=${c.expansionism}
data-emblems="${c.shield}"
>
<fill-box fill="${c.color}"></fill-box> <fill-box fill="${c.color}"></fill-box>
<input data-tip="Culture name. Click and type to change" class="cultureName" value="${c.name}" autocorrect="off" spellcheck="false"> <input data-tip="Culture name. Click and type to change" class="cultureName" value="${c.name}" autocorrect="off" spellcheck="false" />
<span data-tip="Regenerate culture name" class="icon-cw hiddenIcon" style="visibility: hidden"></span> <span data-tip="Regenerate culture name" class="icon-cw hiddenIcon" style="visibility: hidden"></span>
<span data-tip="Cells count" class="icon-check-empty hide"></span> <span data-tip="Cells count" class="icon-check-empty hide"></span>
<div data-tip="Cells count" class="stateCells hide">${c.cells}</div> <div data-tip="Cells count" class="stateCells hide">${c.cells}</div>
<span data-tip="Culture expansionism. Defines competitive size" class="icon-resize-full hide"></span> <span data-tip="Culture expansionism. Defines competitive size" class="icon-resize-full hide"></span>
<input data-tip="Culture expansionism. Defines competitive size. Click to change, then click Recalculate to apply change" class="statePower hide" type="number" min=0 max=99 step=.1 value=${ <input
c.expansionism data-tip="Culture expansionism. Defines competitive size. Click to change, then click Recalculate to apply change"
}> class="statePower hide"
<select data-tip="Culture type. Defines growth model. Click to change" class="cultureType">${getTypeOptions(c.type)}</select> type="number"
min="0"
max="99"
step=".1"
value=${c.expansionism}
/>
<select data-tip="Culture type. Defines growth model. Click to change" class="cultureType">
${getTypeOptions(c.type)}
</select>
<span data-tip="Culture area" style="padding-right: 4px" class="icon-map-o hide"></span> <span data-tip="Culture area" style="padding-right: 4px" class="icon-map-o hide"></span>
<div data-tip="Culture area" class="biomeArea hide">${si(area) + unit}</div> <div data-tip="Culture area" class="biomeArea hide">${si(area) + unit}</div>
<span data-tip="${populationTip}" class="icon-male hide"></span> <span data-tip="${populationTip}" class="icon-male hide"></span>
<div data-tip="${populationTip}" class="culturePopulation hide">${si(population)}</div> <div data-tip="${populationTip}" class="culturePopulation hide">${si(population)}</div>
<span data-tip="Click to re-generate names for burgs with this culture assigned" class="icon-arrows-cw hide"></span> <span data-tip="Click to re-generate names for burgs with this culture assigned" class="icon-arrows-cw hide"></span>
<select data-tip="Culture namesbase. Click to change. Click on arrows to re-generate names" class="cultureBase">${getBaseOptions(c.base)}</select> <select data-tip="Culture namesbase. Click to change. Click on arrows to re-generate names" class="cultureBase">
${getBaseOptions(c.base)}
</select>
${ ${
selectShape selectShape
? `<select data-tip="Emblem shape associated with culture. Click to change" class="cultureShape hide">${getShapeOptions(c.shield)}</select>` ? `<select data-tip="Emblem shape associated with culture. Click to change" class="cultureShape hide">${getShapeOptions(c.shield)}</select>`
@ -200,7 +236,7 @@ function editCultures() {
const rural = c.rural * populationRate; const rural = c.rural * populationRate;
const urban = c.urban * populationRate * urbanization; const urban = c.urban * populationRate * urbanization;
const population = rural + urban > 0 ? si(rn(rural + urban)) + " people" : "Extinct"; const population = rural + urban > 0 ? si(rn(rural + urban)) + " people" : "Extinct";
info.innerHTML = `${c.name} culture. ${c.type}. ${population}`; info.innerHTML = /* html */ `${c.name} culture. ${c.type}. ${population}`;
tip("Drag to change parent, drag to itself to move to the top level. Hold CTRL and click to change abbreviation"); tip("Drag to change parent, drag to itself to move to the top level. Hold CTRL and click to change abbreviation");
} }
@ -347,10 +383,9 @@ function editCultures() {
const l = n => Number(n).toLocaleString(); const l = n => Number(n).toLocaleString();
const burgs = pack.burgs.filter(b => !b.removed && b.culture === culture); const burgs = pack.burgs.filter(b => !b.removed && b.culture === culture);
alertMessage.innerHTML = ` alertMessage.innerHTML = /* html */ ` Rural: <input type="number" min="0" step="1" id="ruralPop" value=${rural} style="width:6em" /> Urban:
Rural: <input type="number" min=0 step=1 id="ruralPop" value=${rural} style="width:6em"> <input type="number" min="0" step="1" id="urbanPop" value=${urban} style="width:6em" ${burgs.length ? "" : "disabled"} />
Urban: <input type="number" min=0 step=1 id="urbanPop" value=${urban} style="width:6em" ${burgs.length ? "" : "disabled"}> <p>Total population: ${l(total)} <span id="totalPop">${l(total)}</span> (<span id="totalPopPerc">100</span>%)</p>`;
<p>Total population: ${l(total)} <span id="totalPop">${l(total)}</span> (<span id="totalPopPerc">100</span>%)</p>`;
const update = function () { const update = function () {
const totalNew = ruralPop.valueAsNumber + urbanPop.valueAsNumber; const totalNew = ruralPop.valueAsNumber + urbanPop.valueAsNumber;
@ -898,8 +933,10 @@ function editCultures() {
const shapes = Object.keys(COA.shields.types) const shapes = Object.keys(COA.shields.types)
.map(type => Object.keys(COA.shields[type])) .map(type => Object.keys(COA.shields[type]))
.flat(); .flat();
const populated = pack.cells.pop.map((c, i) => c? i: null).filter(c => c); const populated = pack.cells.pop.map((c, i) => (c ? i : null)).filter(c => c);
cultures.forEach((item) => {if (item.i) item.removed = true}); cultures.forEach(item => {
if (item.i) item.removed = true;
});
for (const c of csv.iterator((a, b) => +a[0] > +b[0])) { for (const c of csv.iterator((a, b) => +a[0] > +b[0])) {
let current; let current;
if (+c.id < cultures.length) { if (+c.id < cultures.length) {
@ -934,7 +971,7 @@ function editCultures() {
current.base = nameBaseIndex === -1 ? 0 : nameBaseIndex; current.base = nameBaseIndex === -1 ? 0 : nameBaseIndex;
} }
cultures.filter(c => c.removed).forEach(c => removeCultureModel(c.i)) cultures.filter(c => c.removed).forEach(c => removeCultureModel(c.i));
drawCultures(); drawCultures();
refreshCulturesEditor(); refreshCulturesEditor();

View file

@ -81,7 +81,7 @@ function editDiplomacy() {
const selectedName = states[selectedId].name; const selectedName = states[selectedId].name;
COArenderer.trigger("stateCOA" + selectedId, states[selectedId].coa); COArenderer.trigger("stateCOA" + selectedId, states[selectedId].coa);
let lines = `<div class="states Self" data-id=${selectedId} data-tip="List below shows relations to ${selectedName}"> let lines = /* html */ `<div class="states Self" data-id=${selectedId} data-tip="List below shows relations to ${selectedName}">
<div style="width: max-content">${states[selectedId].fullName}</div> <div style="width: max-content">${states[selectedId].fullName}</div>
<svg class="coaIcon" viewBox="0 0 200 200"><use href="#stateCOA${selectedId}"></use></svg> <svg class="coaIcon" viewBox="0 0 200 200"><use href="#stateCOA${selectedId}"></use></svg>
</div>`; </div>`;
@ -98,7 +98,7 @@ function editDiplomacy() {
const name = state.fullName.length < 23 ? state.fullName : state.name; const name = state.fullName.length < 23 ? state.fullName : state.name;
COArenderer.trigger("stateCOA" + state.i, state.coa); COArenderer.trigger("stateCOA" + state.i, state.coa);
lines += `<div class="states" data-id=${state.i} data-name="${name}" data-relations="${relation}"> lines += /* html */ `<div class="states" data-id=${state.i} data-name="${name}" data-relations="${relation}">
<svg data-tip="${tipSelect}" class="coaIcon" viewBox="0 0 200 200"><use href="#stateCOA${state.i}"></use></svg> <svg data-tip="${tipSelect}" class="coaIcon" viewBox="0 0 200 200"><use href="#stateCOA${state.i}"></use></svg>
<div data-tip="${tipSelect}" style="width: 12em">${name}</div> <div data-tip="${tipSelect}" style="width: 12em">${name}</div>
<div data-tip="${tipChange}" class="changeRelations" style="width: 6em"> <div data-tip="${tipChange}" class="changeRelations" style="width: 6em">
@ -202,7 +202,7 @@ function editDiplomacy() {
const object = states[objectId]; const object = states[objectId];
const footer = `<div style="margin-top: 0.7em"><svg class="coaIcon" viewBox="0 0 200 200"><use href="#stateCOA${object.i}"></use></svg> <b>${object.fullName}</b></div>`; const footer = `<div style="margin-top: 0.7em"><svg class="coaIcon" viewBox="0 0 200 200"><use href="#stateCOA${object.i}"></use></svg> <b>${object.fullName}</b></div>`;
alertMessage.innerHTML = `<div style="overflow: hidden">${header} ${options} ${footer}</div>`; alertMessage.innerHTML = /* html */ `<div style="overflow: hidden">${header} ${options} ${footer}</div>`;
$("#alert").dialog({ $("#alert").dialog({
width: fitContent(), width: fitContent(),

View file

@ -60,7 +60,7 @@ function closeDialogs(except = "#except") {
function moveCircle(x, y, r = 20) { function moveCircle(x, y, r = 20) {
let circle = document.getElementById("brushCircle"); let circle = document.getElementById("brushCircle");
if (!circle) { if (!circle) {
const html = `<circle id="brushCircle" cx=${x} cy=${y} r=${r}></circle>`; const html = /* html */ `<circle id="brushCircle" cx=${x} cy=${y} r=${r}></circle>`;
document.getElementById("debug").insertAdjacentHTML("afterBegin", html); document.getElementById("debug").insertAdjacentHTML("afterBegin", html);
} else { } else {
circle.setAttribute("cx", x); circle.setAttribute("cx", x);
@ -192,9 +192,9 @@ function moveBurgToGroup(id, g) {
function moveAllBurgsToGroup(fromGroup, toGroup) { function moveAllBurgsToGroup(fromGroup, toGroup) {
const groupToMove = document.querySelector(`#burgIcons #${fromGroup}`); const groupToMove = document.querySelector(`#burgIcons #${fromGroup}`);
const burgsToMove = Array.from(groupToMove.children).map(x=>x.dataset.id); const burgsToMove = Array.from(groupToMove.children).map(x => x.dataset.id);
addBurgsGroup(toGroup) addBurgsGroup(toGroup);
burgsToMove.forEach(x=>moveBurgToGroup(x, toGroup)); burgsToMove.forEach(x => moveBurgToGroup(x, toGroup));
} }
function addBurgsGroup(group) { function addBurgsGroup(group) {
@ -495,18 +495,17 @@ function createPicker() {
.attr("width", 303) .attr("width", 303)
.attr("height", 20) .attr("height", 20)
.on("mousemove", () => tip("Color value in different color spaces. Edit to change")); .on("mousemove", () => tip("Color value in different color spaces. Edit to change"));
const html = ` const html = /* html */ ` <label style="margin-right: 6px"
<label style="margin-right: 6px">HSL: >HSL: <input type="number" id="pickerHSL_H" data-space="hsl" min="0" max="360" value="231" />,
<input type="number" id="pickerHSL_H" data-space="hsl" min=0 max=360 value="231">, <input type="number" id="pickerHSL_S" data-space="hsl" min="0" max="100" value="70" />,
<input type="number" id="pickerHSL_S" data-space="hsl" min=0 max=100 value="70">, <input type="number" id="pickerHSL_L" data-space="hsl" min="0" max="100" value="70" />
<input type="number" id="pickerHSL_L" data-space="hsl" min=0 max=100 value="70"> </label>
</label> <label style="margin-right: 6px"
<label style="margin-right: 6px">RGB: >RGB: <input type="number" id="pickerRGB_R" data-space="rgb" min="0" max="255" value="125" />,
<input type="number" id="pickerRGB_R" data-space="rgb" min=0 max=255 value="125">, <input type="number" id="pickerRGB_G" data-space="rgb" min="0" max="255" value="142" />,
<input type="number" id="pickerRGB_G" data-space="rgb" min=0 max=255 value="142">, <input type="number" id="pickerRGB_B" data-space="rgb" min="0" max="255" value="232" />
<input type="number" id="pickerRGB_B" data-space="rgb" min=0 max=255 value="232"> </label>
</label> <label>HEX: <input type="text" id="pickerHEX" data-space="hex" style="width:42px" autocorrect="off" spellcheck="false" value="#7d8ee8" /></label>`;
<label>HEX: <input type="text" id="pickerHEX" data-space="hex" style="width:42px" autocorrect="off" spellcheck="false" value="#7d8ee8"></label>`;
spaces.node().insertAdjacentHTML("beforeend", html); spaces.node().insertAdjacentHTML("beforeend", html);
spaces.selectAll("input").on("change", changePickerSpace); spaces.selectAll("input").on("change", changePickerSpace);
@ -523,7 +522,7 @@ function createPicker() {
.attr("fill", d) .attr("fill", d)
.attr("class", i ? "" : "selected") .attr("class", i ? "" : "selected")
.attr("x", (i % 14) * 22 + 4) .attr("x", (i % 14) * 22 + 4)
.attr("y", 40 + Math.floor(i / 14)*20) .attr("y", 40 + Math.floor(i / 14) * 20)
.attr("width", 16) .attr("width", 16)
.attr("height", 16); .attr("height", 16);
}); });
@ -534,9 +533,9 @@ function createPicker() {
.attr("id", "picker_" + this.id) .attr("id", "picker_" + this.id)
.attr("fill", "url(#" + this.id + ")") .attr("fill", "url(#" + this.id + ")")
.attr("x", (i % 14) * 22 + 4) .attr("x", (i % 14) * 22 + 4)
.attr("y", Math.floor(i / 14)*20 + 20 + (number * 2)) .attr("y", Math.floor(i / 14) * 20 + 20 + number * 2)
.attr("width", 16) .attr("width", 16)
.attr("height", 16) .attr("height", 16);
}); });
colors colors
@ -546,7 +545,9 @@ function createPicker() {
hatches hatches
.selectAll("rect") .selectAll("rect")
.on("click", pickerFillClicked) .on("click", pickerFillClicked)
.on("mouseover", function() { tip("Click to fill with the hatching " + this.id) }); .on("mouseover", function () {
tip("Click to fill with the hatching " + this.id);
});
// append box // append box
const bbox = picker.node().getBBox(); const bbox = picker.node().getBBox();
@ -562,10 +563,15 @@ function createPicker() {
.attr("fill", "#ffffff") .attr("fill", "#ffffff")
.attr("stroke", "#5d4651") .attr("stroke", "#5d4651")
.on("mousemove", pos); .on("mousemove", pos);
picker.insert("text", ":first-child").attr("x", width-20).attr("y", -10).attr("id", "pickerCloseText").text("✕"); picker
.insert("text", ":first-child")
.attr("x", width - 20)
.attr("y", -10)
.attr("id", "pickerCloseText")
.text("✕");
picker picker
.insert("rect", ":first-child") .insert("rect", ":first-child")
.attr("x", width-23) .attr("x", width - 23)
.attr("y", -21) .attr("y", -21)
.attr("id", "pickerCloseRect") .attr("id", "pickerCloseRect")
.attr("width", 14) .attr("width", 14)

View file

@ -13,7 +13,10 @@ function editEmblem(type, id, el) {
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",
resizable: true,
width: "18.2em",
height: "auto",
position: {my: "left top", at: "left+10 top+10", of: "svg", collision: "fit"}, position: {my: "left top", at: "left+10 top+10", of: "svg", collision: "fit"},
close: closeEmblemEditor close: closeEmblemEditor
}); });
@ -41,17 +44,17 @@ function editEmblem(type, id, el) {
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" : "";
@ -61,7 +64,7 @@ function editEmblem(type, id, el) {
// 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;
@ -85,7 +88,7 @@ function editEmblem(type, id, el) {
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;
@ -116,14 +119,14 @@ function editEmblem(type, id, el) {
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);
} }
@ -134,13 +137,13 @@ function editEmblem(type, id, el) {
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);
@ -150,7 +153,7 @@ function editEmblem(type, id, el) {
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,23 +169,26 @@ 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() {
@ -194,7 +200,7 @@ function editEmblem(type, id, el) {
} }
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;
@ -228,8 +234,8 @@ function editEmblem(type, id, el) {
} }
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
@ -240,7 +246,7 @@ function editEmblem(type, id, el) {
} 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();
@ -266,7 +272,8 @@ function editEmblem(type, id, el) {
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() {
@ -282,7 +289,8 @@ function editEmblem(type, id, el) {
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);
else downloadRaster(format, url, link, size);
document.getElementById("emblemDownloadControl").classList.add("hidden"); document.getElementById("emblemDownloadControl").classList.add("hidden");
} }
@ -299,22 +307,22 @@ function editEmblem(type, id, el) {
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) {
const serialized = getSVG(svg, size); const serialized = getSVG(svg, size);
const blob = new Blob([serialized], {type: 'image/svg+xml;charset=utf-8'}); const blob = new Blob([serialized], {type: "image/svg+xml;charset=utf-8"});
const url = window.URL.createObjectURL(blob); const url = window.URL.createObjectURL(blob);
window.setTimeout(() => window.URL.revokeObjectURL(url), 6000); window.setTimeout(() => window.URL.revokeObjectURL(url), 6000);
return url; return url;
@ -324,7 +332,7 @@ function editEmblem(type, id, el) {
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() {
@ -338,70 +346,135 @@ function editEmblem(type, id, el) {
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>` +
return `<figure id="state_${state.i}"><a href="#provinces_${state.i}"><figcaption>${state.fullName}</figcaption>${getSVG(el, 200)}</a></figure>`; validStates
}).join("") + `</div>`; .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>`;
})
.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
return `<figure id="province_${province.i}"><a href="#burgs_${province.i}"><figcaption>${province.fullName}</figcaption>${getSVG(el, 200)}</a></figure>`; .map(province => {
}).join(""); const el = document.getElementById("provinceCOA" + province.i);
return stateProvinces.length ? `<div id="provinces_${state.i}">${back}<h2>${state.fullName} provinces</h2>${figures}</div>` : ""; return `<figure id="province_${province.i}"><a href="#burgs_${province.i}"><figcaption>${province.fullName}</figcaption>${getSVG(
}).join(""); el,
200
)}</a></figure>`;
})
.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 => {
return `<figure id="burg_${burg.i}"><figcaption>${burg.name}</figcaption>${getSVG(el, 200)}</figure>`; const provinceBurgs = stateBurgs.filter(b => pack.cells.province[b.cell] === province.i);
}).join(""); const provinceBurgFigures = provinceBurgs
return provinceBurgs.length ? `<div id="burgs_${province.i}">${back}<h2>${province.fullName} burgs</h2>${provinceBurgFigures}</div>` : ""; .map(burg => {
}).join(""); const el = document.getElementById("burgCOA" + burg.i);
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("");
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 => {
return `<figure id="burg_${burg.i}"><figcaption>${burg.name}</figcaption>${getSVG(el, 200)}</figure>`; const el = document.getElementById("burgCOA" + burg.i);
}).join(""); return `<figure id="burg_${burg.i}"><figcaption>${burg.name}</figcaption>${getSVG(el, 200)}</figure>`;
if (stateBurgOutOfProvincesFigures) stateBurgSections += `<div><h2>${state.fullName} burgs under direct control</h2>${stateBurgOutOfProvincesFigures}</div>`; })
return stateBurgSections; .join("");
}).join(""); if (stateBurgOutOfProvincesFigures)
stateBurgSections += `<div><h2>${state.fullName} burgs under direct control</h2>${stateBurgOutOfProvincesFigures}</div>`;
return stateBurgSections;
})
.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>" +
return `<figure id="burg_${burg.i}"><figcaption>${burg.name}</figcaption>${getSVG(el, 200)}</figure>`; neutralBurgs
}).join("") + "</div>" : ""; .map(burg => {
const el = document.getElementById("burgCOA" + burg.i);
return `<figure id="burg_${burg.i}"><figcaption>${burg.name}</figcaption>${getSVG(el, 200)}</figure>`;
})
.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>`;
const html = `<!DOCTYPE html><html><head><title>${mapName.value} Emblems Gallery</title></head> const html = /* html */ `<!DOCTYPE html>
<style type="text/css"> <html>
body { margin: 0; padding: 1em; font-family: serif; } <head>
h1, h2 { font-family: 'Forum'; } <title>${mapName.value} Emblems Gallery</title>
div { width: 100%; max-width: 1018px; margin: 0 auto; border-bottom: 1px solid #ddd; } </head>
figure { margin: 0 0 2em; display: inline-block; transition: .2s; } <style type="text/css">
figure:hover { background-color: #f6f6f6; } body {
figcaption { text-align: center; margin: .4em 0; width: 200px; font-family: 'Overlock SC' } margin: 0;
address { width: 100%; max-width: 1018px; margin: 0 auto; } padding: 1em;
a { color: black; } font-family: serif;
figure > a { text-decoration: none; } }
div > a { float: right; font-family: monospace; margin-top: .8em; } h1,
</style> h2 {
<link href="https://fonts.googleapis.com/css2?family=Forum&family=Overlock+SC" rel="stylesheet"> font-family: "Forum";
<body> }
<div><h1>${mapName.value} Emblems Gallery</h1></div> div {
${stateSection} width: 100%;
${provinceSections} max-width: 1018px;
${burgSections} margin: 0 auto;
${neutralsSection} border-bottom: 1px solid #ddd;
<address>Generated by ${FMG}. The tool is free, but images may be copyrighted, see ${license}</address> }
</body></html>`; figure {
margin: 0 0 2em;
display: inline-block;
transition: 0.2s;
}
figure:hover {
background-color: #f6f6f6;
}
figcaption {
text-align: center;
margin: 0.4em 0;
width: 200px;
font-family: "Overlock SC";
}
address {
width: 100%;
max-width: 1018px;
margin: 0 auto;
}
a {
color: black;
}
figure > a {
text-decoration: none;
}
div > a {
float: right;
font-family: monospace;
margin-top: 0.8em;
}
</style>
<link href="https://fonts.googleapis.com/css2?family=Forum&family=Overlock+SC" rel="stylesheet" />
<body>
<div><h1>${mapName.value} Emblems Gallery</h1></div>
${stateSection} ${provinceSections} ${burgSections} ${neutralsSection}
<address>Generated by ${FMG}. The tool is free, but images may be copyrighted, see ${license}</address>
</body>
</html>`;
downloadFile(html, name + ".html", "text/plain"); downloadFile(html, name + ".html", "text/plain");
} }
} }
@ -409,9 +482,9 @@ function editEmblem(type, id, el) {
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());
@ -419,10 +492,11 @@ function editEmblem(type, id, el) {
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);
}); });
} }
@ -430,4 +504,4 @@ function editEmblem(type, id, el) {
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

@ -405,7 +405,7 @@ document.querySelectorAll("[data-locked]").forEach(function (e) {
// lock option // lock option
function lock(id) { function lock(id) {
const input = document.querySelector("[data-stored=\"" + id + "\"]"); const input = document.querySelector('[data-stored="' + id + '"]');
if (input) localStorage.setItem(id, input.value); if (input) localStorage.setItem(id, input.value);
const el = document.getElementById("lock_" + id); const el = document.getElementById("lock_" + id);
if (!el) return; if (!el) return;
@ -467,15 +467,18 @@ function showInfo() {
const QAA = link("https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Q&A", "Q&A page"); const QAA = link("https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Q&A", "Q&A page");
const VideoTutorial = link("https://youtube.com/playlist?list=PLtgiuDC8iVR2gIG8zMTRn7T_L0arl9h1C", "Video tutorial"); const VideoTutorial = link("https://youtube.com/playlist?list=PLtgiuDC8iVR2gIG8zMTRn7T_L0arl9h1C", "Video tutorial");
alertMessage.innerHTML = ` alertMessage.innerHTML = /* html */ `<b>Fantasy Map Generator</b> (FMG) is a free open-source application. It means that you own all created maps and can use them as
<b>Fantasy Map Generator</b> (FMG) is a free open-source application. you wish.
It means that you own all created maps and can use them as you wish.
<p>The development is community-backed, you can donate on ${Patreon}. <p>
You can also help creating overviews, tutorials and spreding the word about the Generator.</p> The development is community-backed, you can donate on ${Patreon}. You can also help creating overviews, tutorials and spreding the word about the
Generator.
</p>
<p>The best way to get help is to contact the community on ${Discord} and ${Reddit}. <p>
Before asking questions, please check out the ${QuickStart}, the ${QAA}, and ${VideoTutorial}.</p> The best way to get help is to contact the community on ${Discord} and ${Reddit}. Before asking questions, please check out the ${QuickStart}, the ${QAA},
and ${VideoTutorial}.
</p>
<p>Check out our another project: ${Armoria} heraldry generator and editor.</p> <p>Check out our another project: ${Armoria} heraldry generator and editor.</p>

View file

@ -2,12 +2,14 @@
function editHeightmap() { function editHeightmap() {
void (function selectEditMode() { void (function selectEditMode() {
alertMessage.innerHTML = `Heightmap is a core element on which all other data (rivers, burgs, states etc) is based. alertMessage.innerHTML = /* html */ `Heightmap is a core element on which all other data (rivers, burgs, states etc) is based. So the best edit approach is to
So the best edit approach is to <i>erase</i> the secondary data and let the system automatically regenerate it on edit completion. <i>erase</i> the secondary data and let the system automatically regenerate it on edit completion.
<p><i>Erase</i> mode also allows you Convert an Image into a heightmap or use Template Editor.</p> <p><i>Erase</i> mode also allows you Convert an Image into a heightmap or use Template Editor.</p>
<p>You can <i>keep</i> the data, but you won't be able to change the coastline.</p> <p>You can <i>keep</i> the data, but you won't be able to change the coastline.</p>
<p>Try <i>risk</i> mode to change the coastline and keep the data. The data will be restored as much as possible, but it can cause unpredictable errors.</p> <p>
<p>Please <span class="pseudoLink" onclick=dowloadMap(); editHeightmap();>save the map</span> before editing the heightmap!</p> Try <i>risk</i> mode to change the coastline and keep the data. The data will be restored as much as possible, but it can cause unpredictable errors.
</p>
<p>Please <span class="pseudoLink" onclick="dowloadMap();" editHeightmap();>save the map</span> before editing the heightmap!</p>
<p style="margin-bottom: 0">Check out ${link("https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Heightmap-customization", "wiki")} for guidance.</p>`; <p style="margin-bottom: 0">Check out ${link("https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Heightmap-customization", "wiki")} for guidance.</p>`;
$("#alert").dialog({ $("#alert").dialog({
@ -107,7 +109,7 @@ function editHeightmap() {
heightmapInfoX.innerHTML = rn(p[0]); heightmapInfoX.innerHTML = rn(p[0]);
heightmapInfoY.innerHTML = rn(p[1]); heightmapInfoY.innerHTML = rn(p[1]);
heightmapInfoCell.innerHTML = cell; heightmapInfoCell.innerHTML = cell;
heightmapInfoHeight.innerHTML = `${grid.cells.h[cell]} (${getHeight(grid.cells.h[cell])})`; heightmapInfoHeight.innerHTML = /* html */ `${grid.cells.h[cell]} (${getHeight(grid.cells.h[cell])})`;
if (tooltip.dataset.main) showMainTip(); if (tooltip.dataset.main) showMainTip();
// move radius circle if drag mode is active // move radius circle if drag mode is active
@ -485,7 +487,7 @@ function editHeightmap() {
function updateStatistics() { function updateStatistics() {
const landCells = grid.cells.h.reduce((s, h) => (h >= 20 ? s + 1 : s)); const landCells = grid.cells.h.reduce((s, h) => (h >= 20 ? s + 1 : s));
landmassCounter.innerHTML = `${landCells} (${rn((landCells / grid.cells.i.length) * 100)}%)`; landmassCounter.innerHTML = /* html */ `${landCells} (${rn((landCells / grid.cells.i.length) * 100)}%)`;
landmassAverage.innerHTML = rn(d3.mean(grid.cells.h)); landmassAverage.innerHTML = rn(d3.mean(grid.cells.h));
} }
@ -1247,11 +1249,8 @@ function editHeightmap() {
function closeImageConverter(event) { function closeImageConverter(event) {
event.preventDefault(); event.preventDefault();
event.stopPropagation(); event.stopPropagation();
alertMessage.innerHTML = ` alertMessage.innerHTML = /* html */ ` Are you sure you want to close the Image Converter? Click "Cancel" to geck back to convertion. Click "Complete" to apply
Are you sure you want to close the Image Converter? the conversion. Click "Close" to exit conversion mode and restore previous heightmap`;
Click "Cancel" to geck back to convertion.
Click "Complete" to apply the conversion.
Click "Close" to exit conversion mode and restore previous heightmap`;
$("#alert").dialog({ $("#alert").dialog({
resizable: false, resizable: false,

View file

@ -12,7 +12,8 @@ function editIce() {
ice.selectAll("*").classed("draggable", true).call(d3.drag().on("drag", dragElement)); ice.selectAll("*").classed("draggable", true).call(d3.drag().on("drag", dragElement));
$("#iceEditor").dialog({ $("#iceEditor").dialog({
title: "Edit "+type, resizable: false, title: "Edit " + type,
resizable: false,
position: {my: "center top+60", at: "top", of: d3.event, collision: "fit"}, position: {my: "center top+60", at: "top", of: d3.event, collision: "fit"},
close: closeEditor close: closeEditor
}); });
@ -30,8 +31,9 @@ function editIce() {
function randomizeShape() { function randomizeShape() {
const c = grid.points[+elSelected.attr("cell")]; const c = grid.points[+elSelected.attr("cell")];
const s = +elSelected.attr("size"); const s = +elSelected.attr("size");
const i = ra(grid.cells.i), cn = grid.points[i]; const i = ra(grid.cells.i),
const poly = getGridPolygon(i).map(p => [p[0]-cn[0], p[1]-cn[1]]); cn = grid.points[i];
const poly = getGridPolygon(i).map(p => [p[0] - cn[0], p[1] - cn[1]]);
const points = poly.map(p => [rn(c[0] + p[0] * s, 2), rn(c[1] + p[1] * s, 2)]); const points = poly.map(p => [rn(c[0] + p[0] * s, 2), rn(c[1] + p[1] * s, 2)]);
elSelected.attr("points", points); elSelected.attr("points", points);
} }
@ -39,10 +41,13 @@ function editIce() {
function changeSize() { function changeSize() {
const c = grid.points[+elSelected.attr("cell")]; const c = grid.points[+elSelected.attr("cell")];
const s = +elSelected.attr("size"); const s = +elSelected.attr("size");
const flat = elSelected.attr("points").split(",").map(el => +el); const flat = elSelected
.attr("points")
.split(",")
.map(el => +el);
const pairs = []; const pairs = [];
while (flat.length) pairs.push(flat.splice(0,2)); while (flat.length) pairs.push(flat.splice(0, 2));
const poly = pairs.map(p => [(p[0]-c[0]) / s, (p[1]-c[1]) / s]); const poly = pairs.map(p => [(p[0] - c[0]) / s, (p[1] - c[1]) / s]);
const size = +this.value; const size = +this.value;
const points = poly.map(p => [rn(c[0] + p[0] * size, 2), rn(c[1] + p[1] * size, 2)]); const points = poly.map(p => [rn(c[0] + p[0] * size, 2), rn(c[1] + p[1] * size, 2)]);
elSelected.attr("points", points).attr("size", size); elSelected.attr("points", points).attr("size", size);
@ -65,7 +70,7 @@ function editIce() {
const c = grid.points[i]; const c = grid.points[i];
const s = +document.getElementById("iceSize").value; const s = +document.getElementById("iceSize").value;
const points = getGridPolygon(i).map(p => [(p[0] + (c[0]-p[0]) / s)|0, (p[1] + (c[1]-p[1]) / s)|0]); const points = getGridPolygon(i).map(p => [(p[0] + (c[0] - p[0]) / s) | 0, (p[1] + (c[1] - p[1]) / s) | 0]);
const iceberg = ice.append("polygon").attr("points", points).attr("cell", i).attr("size", s); const iceberg = ice.append("polygon").attr("points", points).attr("cell", i).attr("size", s);
iceberg.call(d3.drag().on("drag", dragElement)); iceberg.call(d3.drag().on("drag", dragElement));
if (d3.event.shiftKey === false) toggleAdd(); if (d3.event.shiftKey === false) toggleAdd();
@ -73,26 +78,32 @@ function editIce() {
function removeIce() { function removeIce() {
const type = elSelected.attr("type") ? "Glacier" : "Iceberg"; const type = elSelected.attr("type") ? "Glacier" : "Iceberg";
alertMessage.innerHTML = `Are you sure you want to remove the ${type}?`; alertMessage.innerHTML = /* html */ `Are you sure you want to remove the ${type}?`;
$("#alert").dialog({resizable: false, title: "Remove "+type, $("#alert").dialog({
resizable: false,
title: "Remove " + type,
buttons: { buttons: {
Remove: function() { Remove: function () {
$(this).dialog("close"); $(this).dialog("close");
elSelected.remove(); elSelected.remove();
$("#iceEditor").dialog("close"); $("#iceEditor").dialog("close");
}, },
Cancel: function() {$(this).dialog("close");} Cancel: function () {
$(this).dialog("close");
}
} }
}); });
} }
function dragElement() { function dragElement() {
const tr = parseTransform(this.getAttribute("transform")); const tr = parseTransform(this.getAttribute("transform"));
const dx = +tr[0] - d3.event.x, dy = +tr[1] - d3.event.y; const dx = +tr[0] - d3.event.x,
dy = +tr[1] - d3.event.y;
d3.event.on("drag", function() { d3.event.on("drag", function () {
const x = d3.event.x, y = d3.event.y; const x = d3.event.x,
this.setAttribute("transform", `translate(${(dx+x)},${(dy+y)})`); y = d3.event.y;
this.setAttribute("transform", `translate(${dx + x},${dy + y})`);
}); });
} }

View file

@ -257,9 +257,10 @@ function editLabel() {
const group = elSelected.node().parentNode.id; const group = elSelected.node().parentNode.id;
const basic = group === "states" || group === "addedLabels"; const basic = group === "states" || group === "addedLabels";
const count = elSelected.node().parentNode.childElementCount; const count = elSelected.node().parentNode.childElementCount;
alertMessage.innerHTML = `Are you sure you want to remove alertMessage.innerHTML = /* html */ `Are you sure you want to remove ${
${basic ? "all elements in the group" : "the entire label group"}? basic ? "all elements in the group" : "the entire label group"
<br><br>Labels to be removed: ${count}`; }? <br /><br />Labels to be
removed: ${count}`;
$("#alert").dialog({ $("#alert").dialog({
resizable: false, resizable: false,
title: "Remove route group", title: "Remove route group",

View file

@ -5,7 +5,8 @@ function editLake() {
if (layerIsOn("toggleCells")) toggleCells(); if (layerIsOn("toggleCells")) toggleCells();
$("#lakeEditor").dialog({ $("#lakeEditor").dialog({
title: "Edit Lake", resizable: false, title: "Edit Lake",
resizable: false,
position: {my: "center top+20", at: "top", of: d3.event, collision: "fit"}, position: {my: "center top+20", at: "top", of: d3.event, collision: "fit"},
close: closeLakesEditor close: closeLakesEditor
}); });
@ -71,23 +72,41 @@ function editLake() {
function drawLakeVertices() { function drawLakeVertices() {
const v = getLake().vertices; // lake outer vertices const v = getLake().vertices; // lake outer vertices
const c = [... new Set(v.map(v => pack.vertices.c[v]).flat())]; const c = [...new Set(v.map(v => pack.vertices.c[v]).flat())];
debug.select("#vertices").selectAll("polygon").data(c).enter().append("polygon") debug
.attr("points", d => getPackPolygon(d)).attr("data-c", d => d); .select("#vertices")
.selectAll("polygon")
.data(c)
.enter()
.append("polygon")
.attr("points", d => getPackPolygon(d))
.attr("data-c", d => d);
debug.select("#vertices").selectAll("circle").data(v).enter().append("circle") debug
.attr("cx", d => pack.vertices.p[d][0]).attr("cy", d => pack.vertices.p[d][1]) .select("#vertices")
.attr("r", .4).attr("data-v", d => d).call(d3.drag().on("drag", dragVertex)) .selectAll("circle")
.data(v)
.enter()
.append("circle")
.attr("cx", d => pack.vertices.p[d][0])
.attr("cy", d => pack.vertices.p[d][1])
.attr("r", 0.4)
.attr("data-v", d => d)
.call(d3.drag().on("drag", dragVertex))
.on("mousemove", () => tip("Drag to move the vertex, please use for fine-tuning only. Edit heightmap to change actual cell heights")); .on("mousemove", () => tip("Drag to move the vertex, please use for fine-tuning only. Edit heightmap to change actual cell heights"));
} }
function dragVertex() { function dragVertex() {
const x = rn(d3.event.x, 2), y = rn(d3.event.y, 2); const x = rn(d3.event.x, 2),
y = rn(d3.event.y, 2);
this.setAttribute("cx", x); this.setAttribute("cx", x);
this.setAttribute("cy", y); this.setAttribute("cy", y);
const v = +this.dataset.v; const v = +this.dataset.v;
pack.vertices.p[v] = [x, y]; pack.vertices.p[v] = [x, y];
debug.select("#vertices").selectAll("polygon").attr("points", d => getPackPolygon(d)); debug
.select("#vertices")
.selectAll("polygon")
.attr("points", d => getPackPolygon(d));
redrawLake(); redrawLake();
} }
@ -97,7 +116,7 @@ function editLake() {
const points = feature.vertices.map(v => pack.vertices.p[v]); const points = feature.vertices.map(v => pack.vertices.p[v]);
const d = round(lineGen(points)); const d = round(lineGen(points));
elSelected.attr("d", d); elSelected.attr("d", d);
defs.select("mask#land > path#land_"+feature.i).attr("d", d); // update land mask defs.select("mask#land > path#land_" + feature.i).attr("d", d); // update land mask
const unit = areaUnit.value === "square" ? " " + distanceUnitInput.value + "²" : " " + areaUnit.value; const unit = areaUnit.value === "square" ? " " + distanceUnitInput.value + "²" : " " + areaUnit.value;
feature.area = Math.abs(d3.polygonArea(points)); feature.area = Math.abs(d3.polygonArea(points));
@ -115,7 +134,7 @@ function editLake() {
function generateNameRandom() { function generateNameRandom() {
const lake = getLake(); const lake = getLake();
lake.name = lakeName.value = Names.getBase(rand(nameBases.length-1)); lake.name = lakeName.value = Names.getBase(rand(nameBases.length - 1));
} }
function selectLakeGroup(node) { function selectLakeGroup(node) {
@ -123,7 +142,7 @@ function editLake() {
const select = document.getElementById("lakeGroup"); const select = document.getElementById("lakeGroup");
select.options.length = 0; // remove all options select.options.length = 0; // remove all options
lakes.selectAll("g").each(function() { lakes.selectAll("g").each(function () {
select.options.add(new Option(this.id, this.id, false, this.id === group)); select.options.add(new Option(this.id, this.id, false, this.id === group));
}); });
} }
@ -141,12 +160,18 @@ function editLake() {
} else { } else {
lakeGroupName.style.display = "none"; lakeGroupName.style.display = "none";
lakeGroup.style.display = "inline-block"; lakeGroup.style.display = "inline-block";
} }
} }
function createNewGroup() { function createNewGroup() {
if (!this.value) {tip("Please provide a valid group name"); return;} if (!this.value) {
const group = this.value.toLowerCase().replace(/ /g, "_").replace(/[^\w\s]/gi, ""); tip("Please provide a valid group name");
return;
}
const group = this.value
.toLowerCase()
.replace(/ /g, "_")
.replace(/[^\w\s]/gi, "");
if (document.getElementById(group)) { if (document.getElementById(group)) {
tip("Element with this id already exists. Please provide a unique name", false, "error"); tip("Element with this id already exists. Please provide a unique name", false, "error");
@ -180,7 +205,7 @@ function editLake() {
toggleNewGroupInput(); toggleNewGroupInput();
document.getElementById("lakeGroupName").value = ""; document.getElementById("lakeGroupName").value = "";
} }
function removeLakeGroup() { function removeLakeGroup() {
const group = elSelected.node().parentNode.id; const group = elSelected.node().parentNode.id;
if (["freshwater", "salt", "sinkhole", "frozen", "lava", "dry"].includes(group)) { if (["freshwater", "salt", "sinkhole", "frozen", "lava", "dry"].includes(group)) {
@ -189,11 +214,13 @@ function editLake() {
} }
const count = elSelected.node().parentNode.childElementCount; const count = elSelected.node().parentNode.childElementCount;
alertMessage.innerHTML = `Are you sure you want to remove the group? alertMessage.innerHTML = /* html */ `Are you sure you want to remove the group? All lakes of the group (${count}) will be turned into Freshwater`;
All lakes of the group (${count}) will be turned into Freshwater`; $("#alert").dialog({
$("#alert").dialog({resizable: false, title: "Remove lake group", width:"26em", resizable: false,
title: "Remove lake group",
width: "26em",
buttons: { buttons: {
Remove: function() { Remove: function () {
$(this).dialog("close"); $(this).dialog("close");
const freshwater = document.getElementById("freshwater"); const freshwater = document.getElementById("freshwater");
const groupEl = document.getElementById(group); const groupEl = document.getElementById(group);
@ -204,7 +231,9 @@ function editLake() {
document.getElementById("lakeGroup").selectedOptions[0].remove(); document.getElementById("lakeGroup").selectedOptions[0].remove();
document.getElementById("lakeGroup").value = "freshwater"; document.getElementById("lakeGroup").value = "freshwater";
}, },
Cancel: function() {$(this).dialog("close");} Cancel: function () {
$(this).dialog("close");
}
} }
}); });
} }
@ -223,4 +252,4 @@ function editLake() {
debug.select("#vertices").remove(); debug.select("#vertices").remove();
unselect(); unselect();
} }
} }

View file

@ -75,18 +75,30 @@ function overviewMilitary() {
const sortData = options.military.map(u => `data-${u.name}="${getForces(u)}"`).join(" "); const sortData = options.military.map(u => `data-${u.name}="${getForces(u)}"`).join(" ");
const lineData = options.military.map(u => `<div data-type="${u.name}" data-tip="State ${u.name} units number">${getForces(u)}</div>`).join(" "); const lineData = options.military.map(u => `<div data-type="${u.name}" data-tip="State ${u.name} units number">${getForces(u)}</div>`).join(" ");
lines += `<div class="states" data-id=${s.i} data-state="${s.name}" ${sortData} data-total="${total}" lines += /* html */ `<div
data-population="${population}" data-rate="${rate}" data-alert="${s.alert}"> class="states"
data-id=${s.i}
data-state="${s.name}"
${sortData}
data-total="${total}"
data-population="${population}"
data-rate="${rate}"
data-alert="${s.alert}"
>
<fill-box data-tip="${s.fullName}" fill="${s.color}" disabled></fill-box> <fill-box data-tip="${s.fullName}" fill="${s.color}" disabled></fill-box>
<input data-tip="${s.fullName}" style="width:6em" value="${s.name}" readonly> <input data-tip="${s.fullName}" style="width:6em" value="${s.name}" readonly />
${lineData} ${lineData}
<div data-type="total" data-tip="Total state military personnel (considering crew)" style="font-weight: bold">${si(total)}</div> <div data-type="total" data-tip="Total state military personnel (considering crew)" style="font-weight: bold">${si(total)}</div>
<div data-type="population" data-tip="State population">${si(population)}</div> <div data-type="population" data-tip="State population">${si(population)}</div>
<div data-type="rate" data-tip="Military personnel rate (% of state population). Depends on war alert">${rn(rate, 2)}%</div> <div data-type="rate" data-tip="Military personnel rate (% of state population). Depends on war alert">${rn(rate, 2)}%</div>
<input data-tip="War Alert. Editable modifier to military forces number, depends of political situation" style="width:4.1em" type="number" min=0 step=.01 value="${rn( <input
s.alert, data-tip="War Alert. Editable modifier to military forces number, depends of political situation"
2 style="width:4.1em"
)}"> type="number"
min="0"
step=".01"
value="${rn(s.alert, 2)}"
/>
<span data-tip="Show regiments list" class="icon-list-bullet pointer"></span> <span data-tip="Show regiments list" class="icon-list-bullet pointer"></span>
</div>`; </div>`;
} }
@ -290,21 +302,28 @@ function overviewMilitary() {
${getLimitText(unit[attr])} ${getLimitText(unit[attr])}
</button>`; </button>`;
row.innerHTML = `<td><button data-type="icon" data-tip="Click to select unit icon">${icon || " "}</button></td> row.innerHTML = /* html */ `<td><button data-type="icon" data-tip="Click to select unit icon">${icon || " "}</button></td>
<td><input data-tip="Type unit name. If name is changed for existing unit, old unit will be replaced" value="${name}"></td> <td><input data-tip="Type unit name. If name is changed for existing unit, old unit will be replaced" value="${name}" /></td>
<td>${getLimitButton("biomes")}</td> <td>${getLimitButton("biomes")}</td>
<td>${getLimitButton("states")}</td> <td>${getLimitButton("states")}</td>
<td>${getLimitButton("cultures")}</td> <td>${getLimitButton("cultures")}</td>
<td>${getLimitButton("religions")}</td> <td>${getLimitButton("religions")}</td>
<td><input data-tip="Enter conscription percentage for rural population" type="number" min=0 max=100 step=.01 value="${rural}"></td> <td><input data-tip="Enter conscription percentage for rural population" type="number" min="0" max="100" step=".01" value="${rural}" /></td>
<td><input data-tip="Enter conscription percentage for urban population" type="number" min=0 max=100 step=.01 value="${urban}"></td> <td><input data-tip="Enter conscription percentage for urban population" type="number" min="0" max="100" step=".01" value="${urban}" /></td>
<td><input data-tip="Enter average number of people in crew (for total personnel calculation)" type="number" min=1 step=1 value="${crew}"></td> <td><input data-tip="Enter average number of people in crew (for total personnel calculation)" type="number" min="1" step="1" value="${crew}" /></td>
<td><input data-tip="Enter military power (used for battle simulation)" type="number" min=0 step=.1 value="${power}"></td> <td><input data-tip="Enter military power (used for battle simulation)" type="number" min="0" step=".1" value="${power}" /></td>
<td><select data-tip="Select unit type to apply special rules on forces recalculation">${typeOptions}</select></td> <td>
<select data-tip="Select unit type to apply special rules on forces recalculation">
${typeOptions}
</select>
</td>
<td data-tip="Check if unit is <b>separate</b> and can be stacked only with the same units"> <td data-tip="Check if unit is <b>separate</b> and can be stacked only with the same units">
<input id="${name}Separate" type="checkbox" class="checkbox" ${separate ? "checked" : ""}> <input id="${name}Separate" type="checkbox" class="checkbox" ${separate ? "checked" : ""} />
<label for="${name}Separate" class="checkbox-label"></label></td> <label for="${name}Separate" class="checkbox-label"></label>
<td data-tip="Remove the unit"><span data-tip="Remove unit type" class="icon-trash-empty pointer" onclick="this.parentElement.parentElement.remove();"></span></td>`; </td>
<td data-tip="Remove the unit">
<span data-tip="Remove unit type" class="icon-trash-empty pointer" onclick="this.parentElement.parentElement.remove();"></span>
</td>`;
tableBody.appendChild(row); tableBody.appendChild(row);
} }
@ -326,7 +345,12 @@ function overviewMilitary() {
<label for="el${i}" class="checkbox-label">${fullName || name}</label> <label for="el${i}" class="checkbox-label">${fullName || name}</label>
</td></tr>` </td></tr>`
); );
alertMessage.innerHTML = `<b>Limit unit by ${type}:</b><table style="margin-top:.3em"><tbody>${lines.join("")}</tbody></table>`; alertMessage.innerHTML = /* html */ `<b>Limit unit by ${type}:</b>
<table style="margin-top:.3em">
<tbody>
${lines.join("")}
</tbody>
</table>`;
$("#alert").dialog({ $("#alert").dialog({
width: fitContent(), width: fitContent(),

View file

@ -154,15 +154,15 @@ function editNamesbase() {
return "<span data-tip='Namesbase variety is good' style='color:green'>[good]</span>"; return "<span data-tip='Namesbase variety is good' style='color:green'>[good]</span>";
}; };
alertMessage.innerHTML = `<div style="line-height: 1.6em; max-width: 20em"> alertMessage.innerHTML = /* html */ `<div style="line-height: 1.6em; max-width: 20em">
<div data-tip="Number of names provided">Namesbase length: ${length} ${getLengthQuality()}</div> <div data-tip="Number of names provided">Namesbase length: ${length} ${getLengthQuality()}</div>
<div data-tip="Average number of generation variants for each key in the chain">Namesbase variety: ${variety} ${getVarietyLevel()}</div> <div data-tip="Average number of generation variants for each key in the chain">Namesbase variety: ${variety} ${getVarietyLevel()}</div>
<hr> <hr />
<div data-tip="The shortest name length">Min name length: ${d3.min(wordsLength)}</div> <div data-tip="The shortest name length">Min name length: ${d3.min(wordsLength)}</div>
<div data-tip="The longest name length">Max name length: ${d3.max(wordsLength)}</div> <div data-tip="The longest name length">Max name length: ${d3.max(wordsLength)}</div>
<div data-tip="Average name length">Mean name length: ${rn(d3.mean(wordsLength), 1)}</div> <div data-tip="Average name length">Mean name length: ${rn(d3.mean(wordsLength), 1)}</div>
<div data-tip="Common name length">Median name length: ${d3.median(wordsLength)}</div> <div data-tip="Common name length">Median name length: ${d3.median(wordsLength)}</div>
<hr> <hr />
<div data-tip="Characters outside of Basic Latin have bad font support">Non-basic chars: ${nonBasicLatinChars}</div> <div data-tip="Characters outside of Basic Latin have bad font support">Non-basic chars: ${nonBasicLatinChars}</div>
<div data-tip="Characters that are frequently (more than 3 times) doubled">Doubled chars: ${doubled.join("")}</div> <div data-tip="Characters that are frequently (more than 3 times) doubled">Doubled chars: ${doubled.join("")}</div>
<div data-tip="Names used more than one time">Duplicates: ${duplicates}</div> <div data-tip="Names used more than one time">Duplicates: ${duplicates}</div>
@ -196,7 +196,7 @@ function editNamesbase() {
} }
function namesbaseRestoreDefault() { function namesbaseRestoreDefault() {
alertMessage.innerHTML = `Are you sure you want to restore default namesbase?`; alertMessage.innerHTML = /* html */ `Are you sure you want to restore default namesbase?`;
$("#alert").dialog({ $("#alert").dialog({
resizable: false, resizable: false,
title: "Restore default data", title: "Restore default data",

View file

@ -296,7 +296,9 @@ function showSeedHistoryDialog() {
const button = `<i data-tip="Click to generate a map with this seed" onclick="restoreSeed(${i})" class="icon-history optionsSeedRestore"></i>`; const button = `<i data-tip="Click to generate a map with this seed" onclick="restoreSeed(${i})" class="icon-history optionsSeedRestore"></i>`;
return `<li>Seed: ${h.seed} ${button}. Size: ${h.width}x${h.height}. Template: ${h.template}. Created: ${created}</li>`; return `<li>Seed: ${h.seed} ${button}. Size: ${h.width}x${h.height}. Template: ${h.template}. Created: ${created}</li>`;
}); });
alertMessage.innerHTML = `<ol style="margin: 0; padding-left: 1.5em">${lines.join("")}</ol>`; alertMessage.innerHTML = /* html */ `<ol style="margin: 0; padding-left: 1.5em">
${lines.join("")}
</ol>`;
$("#alert").dialog({ $("#alert").dialog({
resizable: false, resizable: false,
@ -677,8 +679,8 @@ function regeneratePrompt(source) {
const workingTime = (Date.now() - last(mapHistory).created) / 60000; // minutes const workingTime = (Date.now() - last(mapHistory).created) / 60000; // minutes
if (workingTime < 5) return regenerateMap(source); if (workingTime < 5) return regenerateMap(source);
alertMessage.innerHTML = `Are you sure you want to generate a new map?<br> alertMessage.innerHTML = /* html */ `Are you sure you want to generate a new map?<br />
All unsaved changes made to the current map will be lost`; All unsaved changes made to the current map will be lost`;
$("#alert").dialog({ $("#alert").dialog({
resizable: false, resizable: false,
title: "Generate new map", title: "Generate new map",
@ -752,13 +754,13 @@ async function showLoadPane() {
document.getElementById("loadFromDropboxSelect").style.display = "block"; document.getElementById("loadFromDropboxSelect").style.display = "block";
const loadFromDropboxButtons = document.getElementById("loadFromDropboxButtons"); const loadFromDropboxButtons = document.getElementById("loadFromDropboxButtons");
const fileSelect = document.getElementById("loadFromDropboxSelect"); const fileSelect = document.getElementById("loadFromDropboxSelect");
fileSelect.innerHTML = `<option value="" disabled selected>Loading...</option>`; fileSelect.innerHTML = /* html */ `<option value="" disabled selected>Loading...</option>`;
const files = await Cloud.providers.dropbox.list(); const files = await Cloud.providers.dropbox.list();
if (!files) { if (!files) {
loadFromDropboxButtons.style.display = "none"; loadFromDropboxButtons.style.display = "none";
fileSelect.innerHTML = `<option value="" disabled selected>Save files to Dropbox first</option>`; fileSelect.innerHTML = /* html */ `<option value="" disabled selected>Save files to Dropbox first</option>`;
return; return;
} }
@ -841,7 +843,7 @@ function openSaveTiles() {
loading = setInterval(() => (status.innerHTML += "."), 1000); loading = setInterval(() => (status.innerHTML += "."), 1000);
saveTiles().then(() => { saveTiles().then(() => {
clearInterval(loading); clearInterval(loading);
status.innerHTML = `Done. Check file in "Downloads" (crtl + J)`; status.innerHTML = /* html */ `Done. Check file in "Downloads" (crtl + J)`;
setTimeout(() => (status.innerHTML = ""), 8000); setTimeout(() => (status.innerHTML = ""), 8000);
}); });
}, },
@ -874,7 +876,7 @@ function updateTilesOptions() {
const sizeY = graphHeight * scale * tilesY; const sizeY = graphHeight * scale * tilesY;
const totalSize = sizeX * sizeY; const totalSize = sizeX * sizeY;
tileSize.innerHTML = `${sizeX} x ${sizeY} px`; tileSize.innerHTML = /* html */ `${sizeX} x ${sizeY} px`;
tileSize.style.color = totalSize > 1e9 ? "#d00b0b" : totalSize > 1e8 ? "#9e6409" : "#1a941a"; tileSize.style.color = totalSize > 1e9 ? "#d00b0b" : totalSize > 1e8 ? "#9e6409" : "#1a941a";
// draw tiles // draw tiles

View file

@ -131,25 +131,37 @@ function editProvinces() {
const separable = p.burg && p.burg !== pack.states[p.state].capital; const separable = p.burg && p.burg !== pack.states[p.state].capital;
const focused = defs.select("#fog #focusProvince" + p.i).size(); const focused = defs.select("#fog #focusProvince" + p.i).size();
COArenderer.trigger("provinceCOA" + p.i, p.coa); COArenderer.trigger("provinceCOA" + p.i, p.coa);
lines += `<div class="states" data-id=${p.i} data-name="${p.name}" data-form="${p.formName}" data-color="${ lines += /* html */ `<div
p.color class="states"
}" data-capital="${capital}" data-state="${stateName}" data-area=${area} data-population=${population}> data-id=${p.i}
data-name="${p.name}"
data-form="${p.formName}"
data-color="${p.color}"
data-capital="${capital}"
data-state="${stateName}"
data-area=${area}
data-population=${population}
>
<fill-box fill="${p.color}"></fill-box> <fill-box fill="${p.color}"></fill-box>
<input data-tip="Province name. Click to change" class="name pointer" value="${p.name}" readonly> <input data-tip="Province name. Click to change" class="name pointer" value="${p.name}" readonly />
<svg data-tip="Click to show and edit province emblem" class="coaIcon pointer hide" viewBox="0 0 200 200"><use href="#provinceCOA${p.i}"></use></svg> <svg data-tip="Click to show and edit province emblem" class="coaIcon pointer hide" viewBox="0 0 200 200"><use href="#provinceCOA${p.i}"></use></svg>
<input data-tip="Province form name. Click to change" class="name pointer hide" value="${p.formName}" readonly> <input data-tip="Province form name. Click to change" class="name pointer hide" value="${p.formName}" readonly />
<span data-tip="Province capital. Click to zoom into view" class="icon-star-empty pointer hide ${p.burg ? "" : "placeholder"}"></span> <span data-tip="Province capital. Click to zoom into view" class="icon-star-empty pointer hide ${p.burg ? "" : "placeholder"}"></span>
<select data-tip="Province capital. Click to select from burgs within the state. No capital means the province is governed from the state capital" class="cultureBase hide ${ <select
p.burgs.length ? "" : "placeholder" data-tip="Province capital. Click to select from burgs within the state. No capital means the province is governed from the state capital"
}">${p.burgs.length ? getCapitalOptions(p.burgs, p.burg) : ""}</select> class="cultureBase hide ${p.burgs.length ? "" : "placeholder"}"
>
${p.burgs.length ? getCapitalOptions(p.burgs, p.burg) : ""}
</select>
<input data-tip="Province owner" class="provinceOwner" value="${stateName}" disabled"> <input data-tip="Province owner" class="provinceOwner" value="${stateName}" disabled">
<span data-tip="Province area" style="padding-right: 4px" class="icon-map-o hide"></span> <span data-tip="Province area" style="padding-right: 4px" class="icon-map-o hide"></span>
<div data-tip="Province area" class="biomeArea hide">${si(area) + unit}</div> <div data-tip="Province area" class="biomeArea hide">${si(area) + unit}</div>
<span data-tip="${populationTip}" class="icon-male hide"></span> <span data-tip="${populationTip}" class="icon-male hide"></span>
<div data-tip="${populationTip}" class="culturePopulation hide">${si(population)}</div> <div data-tip="${populationTip}" class="culturePopulation hide">${si(population)}</div>
<span data-tip="Declare province independence (turn non-capital province with burgs into a new state)" class="icon-flag-empty ${ <span
separable ? "" : "placeholder" data-tip="Declare province independence (turn non-capital province with burgs into a new state)"
} hide"></span> class="icon-flag-empty ${separable ? "" : "placeholder"} hide"
></span>
<span data-tip="Toggle province focus" class="icon-pin ${focused ? "" : " inactive"} hide"></span> <span data-tip="Toggle province focus" class="icon-pin ${focused ? "" : " inactive"} hide"></span>
<span data-tip="Remove the province" class="icon-trash-empty hide"></span> <span data-tip="Remove the province" class="icon-trash-empty hide"></span>
</div>`; </div>`;
@ -360,10 +372,9 @@ function editProvinces() {
const total = rural + urban; const total = rural + urban;
const l = n => Number(n).toLocaleString(); const l = n => Number(n).toLocaleString();
alertMessage.innerHTML = ` alertMessage.innerHTML = /* html */ ` Rural: <input type="number" min="0" step="1" id="ruralPop" value=${rural} style="width:6em" /> Urban:
Rural: <input type="number" min=0 step=1 id="ruralPop" value=${rural} style="width:6em"> <input type="number" min="0" step="1" id="urbanPop" value=${urban} style="width:6em" ${p.burgs.length ? "" : "disabled"} />
Urban: <input type="number" min=0 step=1 id="urbanPop" value=${urban} style="width:6em" ${p.burgs.length ? "" : "disabled"}> <p>Total population: ${l(total)} <span id="totalPop">${l(total)}</span> (<span id="totalPopPerc">100</span>%)</p>`;
<p>Total population: ${l(total)} <span id="totalPop">${l(total)}</span> (<span id="totalPopPerc">100</span>%)</p>`;
const update = function () { const update = function () {
const totalNew = ruralPop.valueAsNumber + urbanPop.valueAsNumber; const totalNew = ruralPop.valueAsNumber + urbanPop.valueAsNumber;
@ -424,7 +435,7 @@ function editProvinces() {
} }
function removeProvince(p) { function removeProvince(p) {
alertMessage.innerHTML = `Are you sure you want to remove the province? <br>This action cannot be reverted`; alertMessage.innerHTML = /* html */ `Are you sure you want to remove the province? <br />This action cannot be reverted`;
$("#alert").dialog({ $("#alert").dialog({
resizable: false, resizable: false,
title: "Remove province", title: "Remove province",
@ -588,7 +599,7 @@ function editProvinces() {
const treeLayout = d3.treemap().size([w, h]).padding(2); const treeLayout = d3.treemap().size([w, h]).padding(2);
// prepare svg // prepare svg
alertMessage.innerHTML = `<select id="provincesTreeType" style="display:block; margin-left:13px; font-size:11px"> alertMessage.innerHTML = /* html */ `<select id="provincesTreeType" style="display:block; margin-left:13px; font-size:11px">
<option value="area" selected>Area</option> <option value="area" selected>Area</option>
<option value="population">Total population</option> <option value="population">Total population</option>
<option value="rural">Rural population</option> <option value="rural">Rural population</option>
@ -635,7 +646,7 @@ function editProvinces() {
? "Urban population: " + si(urban) ? "Urban population: " + si(urban)
: "Population: " + si(rural + urban); : "Population: " + si(rural + urban);
provinceInfo.innerHTML = `${name}. ${state}. ${value}`; provinceInfo.innerHTML = /* html */ `${name}. ${state}. ${value}`;
provinceHighlightOn(ev); provinceHighlightOn(ev);
} }
@ -1027,7 +1038,7 @@ function editProvinces() {
} }
function removeAllProvinces() { function removeAllProvinces() {
alertMessage.innerHTML = `Are you sure you want to remove all provinces? <br>This action cannot be reverted`; alertMessage.innerHTML = /* html */ `Are you sure you want to remove all provinces? <br />This action cannot be reverted`;
$("#alert").dialog({ $("#alert").dialog({
resizable: false, resizable: false,
title: "Remove all provinces", title: "Remove all provinces",

View file

@ -60,11 +60,11 @@ function overviewRegiments(state) {
.map(u => `<div data-type="${u.name}" data-tip="${capitalize(u.name)} units number">${r.u[u.name] || 0}</div>`) .map(u => `<div data-type="${u.name}" data-tip="${capitalize(u.name)} units number">${r.u[u.name] || 0}</div>`)
.join(" "); .join(" ");
lines += `<div class="states" data-id=${r.i} data-s="${s.i}" data-state="${s.name}" data-name="${r.name}" ${sortData} data-total="${r.a}"> lines += /* html */ `<div class="states" data-id=${r.i} data-s="${s.i}" data-state="${s.name}" data-name="${r.name}" ${sortData} data-total="${r.a}">
<fill-box data-tip="${s.fullName}" fill="${s.color}" disabled></fill-box> <fill-box data-tip="${s.fullName}" fill="${s.color}" disabled></fill-box>
<input data-tip="${s.fullName}" style="width:6em" value="${s.name}" readonly> <input data-tip="${s.fullName}" style="width:6em" value="${s.name}" readonly />
<span data-tip="Regiment's emblem" style="width:1em">${r.icon}</span> <span data-tip="Regiment's emblem" style="width:1em">${r.icon}</span>
<input data-tip="Regiment's name" style="width:13em" value="${r.name}" readonly> <input data-tip="Regiment's name" style="width:13em" value="${r.name}" readonly />
${lineData} ${lineData}
<div data-type="total" data-tip="Total military personnel (not considering crew)" style="font-weight: bold">${r.a}</div> <div data-type="total" data-tip="Total military personnel (not considering crew)" style="font-weight: bold">${r.a}</div>
<span data-tip="Edit regiment" onclick="editRegiment('#regiment${s.i}-${r.i}')" class="icon-pencil pointer"></span> <span data-tip="Edit regiment" onclick="editRegiment('#regiment${s.i}-${r.i}')" class="icon-pencil pointer"></span>
@ -74,7 +74,7 @@ function overviewRegiments(state) {
} }
} }
lines += `<div id="regimentsTotalLine" class="totalLine" data-tip="Total of all displayed regiments"> lines += /* html */ `<div id="regimentsTotalLine" class="totalLine" data-tip="Total of all displayed regiments">
<div style="width: 21em; margin-left: 1em">Regiments: ${regiments.length}</div> <div style="width: 21em; margin-left: 1em">Regiments: ${regiments.length}</div>
${options.military.map(u => `<div style="width:5em">${si(d3.sum(regiments.map(r => r.u[u.name] || 0)))}</div>`).join(" ")} ${options.military.map(u => `<div style="width:5em">${si(d3.sum(regiments.map(r => r.u[u.name] || 0)))}</div>`).join(" ")}
<div style="width:5em">${si(d3.sum(regiments.map(r => r.a)))}</div> <div style="width:5em">${si(d3.sum(regiments.map(r => r.a)))}</div>

View file

@ -254,7 +254,7 @@ function editReliefIcon() {
let selection = null; let selection = null;
const pressed = reliefTools.querySelector("button.pressed"); const pressed = reliefTools.querySelector("button.pressed");
if (pressed.id === "reliefIndividual") { if (pressed.id === "reliefIndividual") {
alertMessage.innerHTML = `Are you sure you want to remove the icon?`; alertMessage.innerHTML = "Are you sure you want to remove the icon?";
selection = elSelected; selection = elSelected;
} else { } else {
const type = reliefIconsDiv.querySelector("svg.pressed")?.dataset.type; const type = reliefIconsDiv.querySelector("svg.pressed")?.dataset.type;

View file

@ -77,14 +77,26 @@ function editReligions() {
totalPopulation += population; totalPopulation += population;
if (r.i) { if (r.i) {
lines += `<div class="states religions" data-id=${r.i} data-name="${r.name}" data-color="${r.color}" data-area=${area} lines += /* html */ `<div
data-population=${population} data-type=${r.type} data-form=${r.form} data-deity="${r.deity ? r.deity : ""}" data-expansionism=${r.expansionism}> class="states religions"
data-id=${r.i}
data-name="${r.name}"
data-color="${r.color}"
data-area=${area}
data-population=${population}
data-type=${r.type}
data-form=${r.form}
data-deity="${r.deity ? r.deity : ""}"
data-expansionism=${r.expansionism}
>
<fill-box fill="${r.color}"></fill-box> <fill-box fill="${r.color}"></fill-box>
<input data-tip="Religion name. Click and type to change" class="religionName" value="${r.name}" autocorrect="off" spellcheck="false"> <input data-tip="Religion name. Click and type to change" class="religionName" value="${r.name}" autocorrect="off" spellcheck="false" />
<select data-tip="Religion type" class="religionType">${getTypeOptions(r.type)}</select> <select data-tip="Religion type" class="religionType">
<input data-tip="Religion form" class="religionForm hide" value="${r.form}" autocorrect="off" spellcheck="false"> ${getTypeOptions(r.type)}
</select>
<input data-tip="Religion form" class="religionForm hide" value="${r.form}" autocorrect="off" spellcheck="false" />
<span data-tip="Click to re-generate supreme deity" class="icon-arrows-cw hide"></span> <span data-tip="Click to re-generate supreme deity" class="icon-arrows-cw hide"></span>
<input data-tip="Religion supreme deity" class="religionDeity hide" value="${r.deity ? r.deity : ""}" autocorrect="off" spellcheck="false"> <input data-tip="Religion supreme deity" class="religionDeity hide" value="${r.deity ? r.deity : ""}" autocorrect="off" spellcheck="false" />
<span data-tip="Religion area" style="padding-right: 4px" class="icon-map-o hide"></span> <span data-tip="Religion area" style="padding-right: 4px" class="icon-map-o hide"></span>
<div data-tip="Religion area" class="biomeArea hide">${si(area) + unit}</div> <div data-tip="Religion area" class="biomeArea hide">${si(area) + unit}</div>
<span data-tip="${populationTip}" class="icon-male hide"></span> <span data-tip="${populationTip}" class="icon-male hide"></span>
@ -93,15 +105,26 @@ function editReligions() {
</div>`; </div>`;
} else { } else {
// No religion (neutral) line // No religion (neutral) line
lines += `<div class="states" data-id=${r.i} data-name="${ lines += /* html */ `<div
r.name class="states"
}" data-color="" data-area=${area} data-population=${population} data-type="" data-form="" data-deity="" data-expansionism=""> data-id=${r.i}
data-name="${r.name}"
data-color=""
data-area=${area}
data-population=${population}
data-type=""
data-form=""
data-deity=""
data-expansionism=""
>
<svg width="9" height="9" class="placeholder"></svg> <svg width="9" height="9" class="placeholder"></svg>
<input data-tip="Religion name. Click and type to change" class="religionName italic" value="${r.name}" autocorrect="off" spellcheck="false"> <input data-tip="Religion name. Click and type to change" class="religionName italic" value="${r.name}" autocorrect="off" spellcheck="false" />
<select data-tip="Religion type" class="religionType placeholder">${getTypeOptions(r.type)}</select> <select data-tip="Religion type" class="religionType placeholder">
<input data-tip="Religion form" class="religionForm placeholder hide" value="" autocorrect="off" spellcheck="false"> ${getTypeOptions(r.type)}
</select>
<input data-tip="Religion form" class="religionForm placeholder hide" value="" autocorrect="off" spellcheck="false" />
<span data-tip="Click to re-generate supreme deity" class="icon-arrows-cw placeholder hide"></span> <span data-tip="Click to re-generate supreme deity" class="icon-arrows-cw placeholder hide"></span>
<input data-tip="Religion supreme deity" class="religionDeity placeholder hide" value="" autocorrect="off" spellcheck="false"> <input data-tip="Religion supreme deity" class="religionDeity placeholder hide" value="" autocorrect="off" spellcheck="false" />
<span data-tip="Religion area" style="padding-right: 4px" class="icon-map-o hide"></span> <span data-tip="Religion area" style="padding-right: 4px" class="icon-map-o hide"></span>
<div data-tip="Religion area" class="biomeArea hide">${si(area) + unit}</div> <div data-tip="Religion area" class="biomeArea hide">${si(area) + unit}</div>
<span data-tip="${populationTip}" class="icon-male hide"></span> <span data-tip="${populationTip}" class="icon-male hide"></span>
@ -163,7 +186,7 @@ function editReligions() {
const rural = r.rural * populationRate; const rural = r.rural * populationRate;
const urban = r.urban * populationRate * urbanization; const urban = r.urban * populationRate * urbanization;
const population = rural + urban > 0 ? ". " + si(rn(rural + urban)) + " believers" : ". Extinct"; const population = rural + urban > 0 ? ". " + si(rn(rural + urban)) + " believers" : ". Extinct";
info.innerHTML = `${r.name}${type}${form}${population}`; info.innerHTML = /* html */ `${r.name}${type}${form}${population}`;
tip("Drag to change parent, drag to itself to move to the top level. Hold CTRL and click to change abbreviation"); tip("Drag to change parent, drag to itself to move to the top level. Hold CTRL and click to change abbreviation");
} }
@ -279,11 +302,15 @@ function editReligions() {
const l = n => Number(n).toLocaleString(); const l = n => Number(n).toLocaleString();
const burgs = pack.burgs.filter(b => !b.removed && pack.cells.religion[b.cell] === religion); const burgs = pack.burgs.filter(b => !b.removed && pack.cells.religion[b.cell] === religion);
alertMessage.innerHTML = `<p><i>Please note all population of religion territory is considered alertMessage.innerHTML = /* html */ `<p>
believers of this religion. It means believers number change will directly change population</i></p> <i
Rural: <input type="number" min=0 step=1 id="ruralPop" value=${rural} style="width:6em"> >Please note all population of religion territory is considered believers of this religion. It means believers number change will directly change
Urban: <input type="number" min=0 step=1 id="urbanPop" value=${urban} style="width:6em" ${burgs.length ? "" : "disabled"}> population</i
<p>Total believers: ${l(total)} <span id="totalPop">${l(total)}</span> (<span id="totalPopPerc">100</span>%)</p>`; >
</p>
Rural: <input type="number" min="0" step="1" id="ruralPop" value=${rural} style="width:6em" /> Urban:
<input type="number" min="0" step="1" id="urbanPop" value=${urban} style="width:6em" ${burgs.length ? "" : "disabled"} />
<p>Total believers: ${l(total)} <span id="totalPop">${l(total)}</span> (<span id="totalPopPerc">100</span>%)</p>`;
const update = function () { const update = function () {
const totalNew = ruralPop.valueAsNumber + urbanPop.valueAsNumber; const totalNew = ruralPop.valueAsNumber + urbanPop.valueAsNumber;

View file

@ -38,14 +38,23 @@ function overviewRivers() {
const width = rn(r.width * distanceScaleInput.value, 3) + " " + unit; const width = rn(r.width * distanceScaleInput.value, 3) + " " + unit;
const basin = pack.rivers.find(river => river.i === r.basin)?.name; const basin = pack.rivers.find(river => river.i === r.basin)?.name;
lines += `<div class="states" data-id=${r.i} data-name="${r.name}" data-type="${r.type}" data-discharge="${r.discharge}" data-length="${r.length}" data-width="${r.width}" data-basin="${basin}"> lines += /* html */ `<div
class="states"
data-id=${r.i}
data-name="${r.name}"
data-type="${r.type}"
data-discharge="${r.discharge}"
data-length="${r.length}"
data-width="${r.width}"
data-basin="${basin}"
>
<span data-tip="Click to focus on river" class="icon-dot-circled pointer"></span> <span data-tip="Click to focus on river" class="icon-dot-circled pointer"></span>
<div data-tip="River name" class="riverName">${r.name}</div> <div data-tip="River name" class="riverName">${r.name}</div>
<div data-tip="River type name" class="riverType">${r.type}</div> <div data-tip="River type name" class="riverType">${r.type}</div>
<div data-tip="River discharge (flux power)" class="biomeArea">${discharge}</div> <div data-tip="River discharge (flux power)" class="biomeArea">${discharge}</div>
<div data-tip="River length from source to mouth" class="biomeArea">${length}</div> <div data-tip="River length from source to mouth" class="biomeArea">${length}</div>
<div data-tip="River mouth width" class="biomeArea">${width}</div> <div data-tip="River mouth width" class="biomeArea">${width}</div>
<input data-tip="River basin (name of the main stem)" class="stateName" value="${basin}" disabled> <input data-tip="River basin (name of the main stem)" class="stateName" value="${basin}" disabled />
<span data-tip="Edit river" class="icon-pencil"></span> <span data-tip="Edit river" class="icon-pencil"></span>
<span data-tip="Remove river" class="icon-trash-empty"></span> <span data-tip="Remove river" class="icon-trash-empty"></span>
</div>`; </div>`;
@ -136,8 +145,7 @@ function overviewRivers() {
function triggerRiverRemove() { function triggerRiverRemove() {
const river = +this.parentNode.dataset.id; const river = +this.parentNode.dataset.id;
alertMessage.innerHTML = `Are you sure you want to remove the river? alertMessage.innerHTML = /* html */ `Are you sure you want to remove the river? All tributaries will be auto-removed`;
All tributaries will be auto-removed`;
$("#alert").dialog({ $("#alert").dialog({
resizable: false, resizable: false,
@ -157,7 +165,7 @@ function overviewRivers() {
} }
function triggerAllRiversRemove() { function triggerAllRiversRemove() {
alertMessage.innerHTML = `Are you sure you want to remove all rivers?`; alertMessage.innerHTML = /* html */ `Are you sure you want to remove all rivers?`;
$("#alert").dialog({ $("#alert").dialog({
resizable: false, resizable: false,
title: "Remove all rivers", title: "Remove all rivers",

View file

@ -6,7 +6,8 @@ function editRoute(onClick) {
if (!layerIsOn("toggleRoutes")) toggleRoutes(); if (!layerIsOn("toggleRoutes")) toggleRoutes();
$("#routeEditor").dialog({ $("#routeEditor").dialog({
title: "Edit Route", resizable: false, title: "Edit Route",
resizable: false,
position: {my: "center top+60", at: "top", of: d3.event, collision: "fit"}, position: {my: "center top+60", at: "top", of: d3.event, collision: "fit"},
close: closeRoutesEditor close: closeRoutesEditor
}); });
@ -41,23 +42,27 @@ function editRoute(onClick) {
function showEditorTips() { function showEditorTips() {
showMainTip(); showMainTip();
if (routeNew.classList.contains("pressed")) return; if (routeNew.classList.contains("pressed")) return;
if (d3.event.target.id === elSelected.attr("id")) tip("Click to add a control point"); else if (d3.event.target.id === elSelected.attr("id")) tip("Click to add a control point");
if (d3.event.target.parentNode.id === "controlPoints") tip("Drag to move, click to delete the control point"); else if (d3.event.target.parentNode.id === "controlPoints") tip("Drag to move, click to delete the control point");
} }
function drawControlPoints(node) { function drawControlPoints(node) {
const l = node.getTotalLength(); const l = node.getTotalLength();
const increment = l / Math.ceil(l / 4); const increment = l / Math.ceil(l / 4);
for (let i=0; i <= l; i += increment) { for (let i = 0; i <= l; i += increment) {
const point = node.getPointAtLength(i); const point = node.getPointAtLength(i);
addControlPoint([point.x, point.y]); addControlPoint([point.x, point.y]);
} }
routeLength.innerHTML = rn(l * distanceScaleInput.value) + " " + distanceUnitInput.value; routeLength.innerHTML = rn(l * distanceScaleInput.value) + " " + distanceUnitInput.value;
} }
function addControlPoint(point, before = null) { function addControlPoint(point, before = null) {
debug.select("#controlPoints").insert("circle", before) debug
.attr("cx", point[0]).attr("cy", point[1]).attr("r", .6) .select("#controlPoints")
.insert("circle", before)
.attr("cx", point[0])
.attr("cy", point[1])
.attr("r", 0.6)
.call(d3.drag().on("drag", dragControlPoint)) .call(d3.drag().on("drag", dragControlPoint))
.on("click", clickControlPoint); .on("click", clickControlPoint);
} }
@ -67,11 +72,11 @@ function editRoute(onClick) {
const controls = document.getElementById("controlPoints").querySelectorAll("circle"); const controls = document.getElementById("controlPoints").querySelectorAll("circle");
const points = Array.from(controls).map(circle => [+circle.getAttribute("cx"), +circle.getAttribute("cy")]); const points = Array.from(controls).map(circle => [+circle.getAttribute("cx"), +circle.getAttribute("cy")]);
const index = getSegmentId(points, point, 2); const index = getSegmentId(points, point, 2);
addControlPoint(point, ":nth-child(" + (index+1) + ")"); addControlPoint(point, ":nth-child(" + (index + 1) + ")");
redrawRoute(); redrawRoute();
} }
function dragControlPoint() { function dragControlPoint() {
this.setAttribute("cx", d3.event.x); this.setAttribute("cx", d3.event.x);
this.setAttribute("cy", d3.event.y); this.setAttribute("cy", d3.event.y);
@ -79,11 +84,14 @@ function editRoute(onClick) {
} }
function redrawRoute() { function redrawRoute() {
lineGen.curve(d3.curveCatmullRom.alpha(.1)); lineGen.curve(d3.curveCatmullRom.alpha(0.1));
const points = []; const points = [];
debug.select("#controlPoints").selectAll("circle").each(function() { debug
points.push([this.getAttribute("cx"), this.getAttribute("cy")]); .select("#controlPoints")
}); .selectAll("circle")
.each(function () {
points.push([this.getAttribute("cx"), this.getAttribute("cy")]);
});
elSelected.attr("d", round(lineGen(points))); elSelected.attr("d", round(lineGen(points)));
const l = elSelected.node().getTotalLength(); const l = elSelected.node().getTotalLength();
@ -98,16 +106,16 @@ function editRoute(onClick) {
} }
function showGroupSection() { function showGroupSection() {
document.querySelectorAll("#routeEditor > button").forEach(el => el.style.display = "none"); document.querySelectorAll("#routeEditor > button").forEach(el => (el.style.display = "none"));
document.getElementById("routeGroupsSelection").style.display = "inline-block"; document.getElementById("routeGroupsSelection").style.display = "inline-block";
} }
function hideGroupSection() { function hideGroupSection() {
document.querySelectorAll("#routeEditor > button").forEach(el => el.style.display = "inline-block"); document.querySelectorAll("#routeEditor > button").forEach(el => (el.style.display = "inline-block"));
document.getElementById("routeGroupsSelection").style.display = "none"; document.getElementById("routeGroupsSelection").style.display = "none";
document.getElementById("routeGroupName").style.display = "none"; document.getElementById("routeGroupName").style.display = "none";
document.getElementById("routeGroupName").value = ""; document.getElementById("routeGroupName").value = "";
document.getElementById("routeGroup").style.display = "inline-block"; document.getElementById("routeGroup").style.display = "inline-block";
} }
function selectRouteGroup(node) { function selectRouteGroup(node) {
@ -115,15 +123,15 @@ function editRoute(onClick) {
const select = document.getElementById("routeGroup"); const select = document.getElementById("routeGroup");
select.options.length = 0; // remove all options select.options.length = 0; // remove all options
routes.selectAll("g").each(function() { routes.selectAll("g").each(function () {
select.options.add(new Option(this.id, this.id, false, this.id === group)); select.options.add(new Option(this.id, this.id, false, this.id === group));
}); });
} }
function changeRouteGroup() { function changeRouteGroup() {
document.getElementById(this.value).appendChild(elSelected.node()); document.getElementById(this.value).appendChild(elSelected.node());
} }
function toggleNewGroupInput() { function toggleNewGroupInput() {
if (routeGroupName.style.display === "none") { if (routeGroupName.style.display === "none") {
routeGroupName.style.display = "inline-block"; routeGroupName.style.display = "inline-block";
@ -132,12 +140,18 @@ function editRoute(onClick) {
} else { } else {
routeGroupName.style.display = "none"; routeGroupName.style.display = "none";
routeGroup.style.display = "inline-block"; routeGroup.style.display = "inline-block";
} }
} }
function createNewGroup() { function createNewGroup() {
if (!this.value) {tip("Please provide a valid group name"); return;} if (!this.value) {
const group = this.value.toLowerCase().replace(/ /g, "_").replace(/[^\w\s]/gi, ""); tip("Please provide a valid group name");
return;
}
const group = this.value
.toLowerCase()
.replace(/ /g, "_")
.replace(/[^\w\s]/gi, "");
if (document.getElementById(group)) { if (document.getElementById(group)) {
tip("Element with this id already exists. Please provide a unique name", false, "error"); tip("Element with this id already exists. Please provide a unique name", false, "error");
@ -152,11 +166,11 @@ function editRoute(onClick) {
const oldGroup = elSelected.node().parentNode; const oldGroup = elSelected.node().parentNode;
const basic = ["roads", "trails", "searoutes"].includes(oldGroup.id); const basic = ["roads", "trails", "searoutes"].includes(oldGroup.id);
if (!basic && oldGroup.childElementCount === 1) { if (!basic && oldGroup.childElementCount === 1) {
document.getElementById("routeGroup").selectedOptions[0].remove(); document.getElementById("routeGroup").selectedOptions[0].remove();
document.getElementById("routeGroup").options.add(new Option(group, group, false, true)); document.getElementById("routeGroup").options.add(new Option(group, group, false, true));
oldGroup.id = group; oldGroup.id = group;
toggleNewGroupInput(); toggleNewGroupInput();
document.getElementById("routeGroupName").value = ""; document.getElementById("routeGroupName").value = "";
return; return;
} }
@ -169,24 +183,33 @@ function editRoute(onClick) {
toggleNewGroupInput(); toggleNewGroupInput();
document.getElementById("routeGroupName").value = ""; document.getElementById("routeGroupName").value = "";
} }
function removeRouteGroup() { function removeRouteGroup() {
const group = elSelected.node().parentNode.id; const group = elSelected.node().parentNode.id;
const basic = ["roads", "trails", "searoutes"].includes(group); const basic = ["roads", "trails", "searoutes"].includes(group);
const count = elSelected.node().parentNode.childElementCount; const count = elSelected.node().parentNode.childElementCount;
alertMessage.innerHTML = `Are you sure you want to remove alertMessage.innerHTML = /* html */ `Are you sure you want to remove ${
${basic ? "all elements in the group" : "the entire route group"}? basic ? "all elements in the group" : "the entire route group"
<br><br>Routes to be removed: ${count}`; }? <br /><br />Routes to be
$("#alert").dialog({resizable: false, title: "Remove route group", removed: ${count}`;
$("#alert").dialog({
resizable: false,
title: "Remove route group",
buttons: { buttons: {
Remove: function() { Remove: function () {
$(this).dialog("close"); $(this).dialog("close");
$("#routeEditor").dialog("close"); $("#routeEditor").dialog("close");
hideGroupSection(); hideGroupSection();
if (basic) routes.select("#"+group).selectAll("path").remove(); if (basic)
else routes.select("#"+group).remove(); routes
.select("#" + group)
.selectAll("path")
.remove();
else routes.select("#" + group).remove();
}, },
Cancel: function() {$(this).dialog("close");} Cancel: function () {
$(this).dialog("close");
}
} }
}); });
} }
@ -203,24 +226,31 @@ function editRoute(onClick) {
function clickControlPoint() { function clickControlPoint() {
if (routeSplit.classList.contains("pressed")) splitRoute(this); if (routeSplit.classList.contains("pressed")) splitRoute(this);
else {this.remove(); redrawRoute();} else {
this.remove();
redrawRoute();
}
} }
function splitRoute(clicked) { function splitRoute(clicked) {
lineGen.curve(d3.curveCatmullRom.alpha(.1)); lineGen.curve(d3.curveCatmullRom.alpha(0.1));
const group = d3.select(elSelected.node().parentNode); const group = d3.select(elSelected.node().parentNode);
routeSplit.classList.remove("pressed"); routeSplit.classList.remove("pressed");
const points1 = [], points2 = []; const points1 = [],
points2 = [];
let points = points1; let points = points1;
debug.select("#controlPoints").selectAll("circle").each(function() { debug
points.push([this.getAttribute("cx"), this.getAttribute("cy")]); .select("#controlPoints")
if (this === clicked) { .selectAll("circle")
points = points2; .each(function () {
points.push([this.getAttribute("cx"), this.getAttribute("cy")]); points.push([this.getAttribute("cx"), this.getAttribute("cy")]);
} if (this === clicked) {
this.remove(); points = points2;
}); points.push([this.getAttribute("cx"), this.getAttribute("cy")]);
}
this.remove();
});
elSelected.attr("d", round(lineGen(points1))); elSelected.attr("d", round(lineGen(points1)));
const id = getNextId("route"); const id = getNextId("route");
@ -263,14 +293,18 @@ function editRoute(onClick) {
function removeRoute() { function removeRoute() {
alertMessage.innerHTML = "Are you sure you want to remove the route?"; alertMessage.innerHTML = "Are you sure you want to remove the route?";
$("#alert").dialog({resizable: false, title: "Remove route", $("#alert").dialog({
resizable: false,
title: "Remove route",
buttons: { buttons: {
Remove: function() { Remove: function () {
$(this).dialog("close"); $(this).dialog("close");
elSelected.remove(); elSelected.remove();
$("#routeEditor").dialog("close"); $("#routeEditor").dialog("close");
}, },
Cancel: function() {$(this).dialog("close");} Cancel: function () {
$(this).dialog("close");
}
} }
}); });
} }

View file

@ -100,24 +100,41 @@ function editStates() {
if (!s.i) { if (!s.i) {
// Neutral line // Neutral line
lines += `<div class="states" data-id=${s.i} data-name="${s.name}" data-cells=${s.cells} data-area=${area} lines += /* html */ `<div
data-population=${population} data-burgs=${s.burgs} data-color="" data-form="" data-capital="" data-culture="" data-type="" data-expansionism=""> class="states"
data-id=${s.i}
data-name="${s.name}"
data-cells=${s.cells}
data-area=${area}
data-population=${population}
data-burgs=${s.burgs}
data-color=""
data-form=""
data-capital=""
data-culture=""
data-type=""
data-expansionism=""
>
<svg width="1em" height="1em" class="placeholder"></svg> <svg width="1em" height="1em" class="placeholder"></svg>
<input data-tip="Neutral lands name. Click to change" class="stateName name pointer italic" value="${s.name}" readonly> <input data-tip="Neutral lands name. Click to change" class="stateName name pointer italic" value="${s.name}" readonly />
<svg class="coaIcon placeholder hide"></svg> <svg class="coaIcon placeholder hide"></svg>
<input class="stateForm placeholder" value="none"> <input class="stateForm placeholder" value="none" />
<span class="icon-star-empty placeholder hide"></span> <span class="icon-star-empty placeholder hide"></span>
<input class="stateCapital placeholder hide"> <input class="stateCapital placeholder hide" />
<select class="stateCulture placeholder hide">${getCultureOptions(0)}</select> <select class="stateCulture placeholder hide">
${getCultureOptions(0)}
</select>
<span data-tip="Burgs count" style="padding-right: 1px" class="icon-dot-circled hide"></span> <span data-tip="Burgs count" style="padding-right: 1px" class="icon-dot-circled hide"></span>
<div data-tip="Burgs count" class="stateBurgs hide">${s.burgs}</div> <div data-tip="Burgs count" class="stateBurgs hide">${s.burgs}</div>
<span data-tip="Neutral lands area" style="padding-right: 4px" class="icon-map-o hide"></span> <span data-tip="Neutral lands area" style="padding-right: 4px" class="icon-map-o hide"></span>
<div data-tip="Neutral lands area" class="biomeArea hide">${si(area) + unit}</div> <div data-tip="Neutral lands area" class="biomeArea hide">${si(area) + unit}</div>
<span data-tip="${populationTip}" class="icon-male hide"></span> <span data-tip="${populationTip}" class="icon-male hide"></span>
<div data-tip="${populationTip}" class="culturePopulation hide">${si(population)}</div> <div data-tip="${populationTip}" class="culturePopulation hide">${si(population)}</div>
<select class="cultureType ${hidden} placeholder show hide">${getTypeOptions(0)}</select> <select class="cultureType ${hidden} placeholder show hide">
${getTypeOptions(0)}
</select>
<span class="icon-resize-full ${hidden} placeholder show hide"></span> <span class="icon-resize-full ${hidden} placeholder show hide"></span>
<input class="statePower ${hidden} placeholder show hide" type="number" value=0> <input class="statePower ${hidden} placeholder show hide" type="number" value="0" />
<span data-tip="Cells count" class="icon-check-empty ${hidden} show hide"></span> <span data-tip="Cells count" class="icon-check-empty ${hidden} show hide"></span>
<div data-tip="Cells count" class="stateCells ${hidden} show hide">${s.cells}</div> <div data-tip="Cells count" class="stateCells ${hidden} show hide">${s.cells}</div>
</div>`; </div>`;
@ -126,26 +143,49 @@ function editStates() {
const capital = pack.burgs[s.capital].name; const capital = pack.burgs[s.capital].name;
COArenderer.trigger("stateCOA" + s.i, s.coa); COArenderer.trigger("stateCOA" + s.i, s.coa);
lines += `<div class="states" data-id=${s.i} data-name="${s.name}" data-form="${s.formName}" data-capital="${capital}" lines += /* html */ `<div
data-color="${s.color}" data-cells=${s.cells} data-area=${area} data-population=${population} data-burgs=${s.burgs} class="states"
data-culture=${pack.cultures[s.culture].name} data-type=${s.type} data-expansionism=${s.expansionism}> data-id=${s.i}
data-name="${s.name}"
data-form="${s.formName}"
data-capital="${capital}"
data-color="${s.color}"
data-cells=${s.cells}
data-area=${area}
data-population=${population}
data-burgs=${s.burgs}
data-culture=${pack.cultures[s.culture].name}
data-type=${s.type}
data-expansionism=${s.expansionism}
>
<fill-box fill="${s.color}"></fill-box> <fill-box fill="${s.color}"></fill-box>
<input data-tip="State name. Click to change" class="stateName name pointer" value="${s.name}" readonly> <input data-tip="State name. Click to change" class="stateName name pointer" value="${s.name}" readonly />
<svg data-tip="Click to show and edit state emblem" class="coaIcon pointer hide" viewBox="0 0 200 200"><use href="#stateCOA${s.i}"></use></svg> <svg data-tip="Click to show and edit state emblem" class="coaIcon pointer hide" viewBox="0 0 200 200"><use href="#stateCOA${s.i}"></use></svg>
<input data-tip="State form name. Click to change" class="stateForm name pointer" value="${s.formName}" readonly> <input data-tip="State form name. Click to change" class="stateForm name pointer" value="${s.formName}" readonly />
<span data-tip="State capital. Click to zoom into view" class="icon-star-empty pointer hide"></span> <span data-tip="State capital. Click to zoom into view" class="icon-star-empty pointer hide"></span>
<input data-tip="Capital name. Click and type to rename" class="stateCapital hide" value="${capital}" autocorrect="off" spellcheck="false"/> <input data-tip="Capital name. Click and type to rename" class="stateCapital hide" value="${capital}" autocorrect="off" spellcheck="false" />
<select data-tip="Dominant culture. Click to change" class="stateCulture hide">${getCultureOptions(s.culture)}</select> <select data-tip="Dominant culture. Click to change" class="stateCulture hide">
${getCultureOptions(s.culture)}
</select>
<span data-tip="Burgs count" style="padding-right: 1px" class="icon-dot-circled hide"></span> <span data-tip="Burgs count" style="padding-right: 1px" class="icon-dot-circled hide"></span>
<div data-tip="Burgs count" class="stateBurgs hide">${s.burgs}</div> <div data-tip="Burgs count" class="stateBurgs hide">${s.burgs}</div>
<span data-tip="State area" style="padding-right: 4px" class="icon-map-o hide"></span> <span data-tip="State area" style="padding-right: 4px" class="icon-map-o hide"></span>
<div data-tip="State area" class="biomeArea hide">${si(area) + unit}</div> <div data-tip="State area" class="biomeArea hide">${si(area) + unit}</div>
<span data-tip="${populationTip}" class="icon-male hide"></span> <span data-tip="${populationTip}" class="icon-male hide"></span>
<div data-tip="${populationTip}" class="culturePopulation hide">${si(population)}</div> <div data-tip="${populationTip}" class="culturePopulation hide">${si(population)}</div>
<select data-tip="State type. Defines growth model. Click to change" class="cultureType ${hidden} show hide">${getTypeOptions(s.type)}</select> <select data-tip="State type. Defines growth model. Click to change" class="cultureType ${hidden} show hide">
${getTypeOptions(s.type)}
</select>
<span data-tip="State expansionism" class="icon-resize-full ${hidden} show hide"></span> <span data-tip="State expansionism" class="icon-resize-full ${hidden} show hide"></span>
<input data-tip="Expansionism (defines competitive size). Change to re-calculate states based on new value" <input
class="statePower ${hidden} show hide" type="number" min=0 max=99 step=.1 value=${s.expansionism}> data-tip="Expansionism (defines competitive size). Change to re-calculate states based on new value"
class="statePower ${hidden} show hide"
type="number"
min="0"
max="99"
step=".1"
value=${s.expansionism}
/>
<span data-tip="Cells count" class="icon-check-empty ${hidden} show hide"></span> <span data-tip="Cells count" class="icon-check-empty ${hidden} show hide"></span>
<div data-tip="Cells count" class="stateCells ${hidden} show hide">${s.cells}</div> <div data-tip="Cells count" class="stateCells ${hidden} show hide">${s.cells}</div>
<span data-tip="Toggle state focus" class="icon-pin ${focused ? "" : " inactive"} hide"></span> <span data-tip="Toggle state focus" class="icon-pin ${focused ? "" : " inactive"} hide"></span>
@ -373,10 +413,9 @@ function editStates() {
const total = rural + urban; const total = rural + urban;
const l = n => Number(n).toLocaleString(); const l = n => Number(n).toLocaleString();
alertMessage.innerHTML = ` alertMessage.innerHTML = /* html */ ` Rural: <input type="number" min="0" step="1" id="ruralPop" value=${rural} style="width:6em" /> Urban:
Rural: <input type="number" min=0 step=1 id="ruralPop" value=${rural} style="width:6em"> <input type="number" min="0" step="1" id="urbanPop" value=${urban} style="width:6em" ${s.burgs ? "" : "disabled"} />
Urban: <input type="number" min=0 step=1 id="urbanPop" value=${urban} style="width:6em" ${s.burgs ? "" : "disabled"}> <p>Total population: ${l(total)} <span id="totalPop">${l(total)}</span> (<span id="totalPopPerc">100</span>%)</p>`;
<p>Total population: ${l(total)} <span id="totalPop">${l(total)}</span> (<span id="totalPopPerc">100</span>%)</p>`;
const update = function () { const update = function () {
const totalNew = ruralPop.valueAsNumber + urbanPop.valueAsNumber; const totalNew = ruralPop.valueAsNumber + urbanPop.valueAsNumber;
@ -592,7 +631,7 @@ function editStates() {
const treeLayout = d3.pack().size([w, h]).padding(3); const treeLayout = d3.pack().size([w, h]).padding(3);
// prepare svg // prepare svg
alertMessage.innerHTML = `<select id="statesTreeType" style="display:block; margin-left:13px; font-size:11px"> alertMessage.innerHTML = /* html */ `<select id="statesTreeType" style="display:block; margin-left:13px; font-size:11px">
<option value="area" selected>Area</option> <option value="area" selected>Area</option>
<option value="population">Total population</option> <option value="population">Total population</option>
<option value="rural">Rural population</option> <option value="rural">Rural population</option>
@ -664,7 +703,7 @@ function editStates() {
? "Burgs number: " + d.data.burgs ? "Burgs number: " + d.data.burgs
: "Population: " + si(rural + urban); : "Population: " + si(rural + urban);
statesInfo.innerHTML = `${state}. ${value}`; statesInfo.innerHTML = /* html */ `${state}. ${value}`;
stateHighlightOn(ev); stateHighlightOn(ev);
} }

View file

@ -712,8 +712,8 @@ emblemsBurgSizeInput.addEventListener("change", drawEmblems);
// request a URL to image to be used as a texture // request a URL to image to be used as a texture
function textureProvideURL() { function textureProvideURL() {
alertMessage.innerHTML = `Provide an image URL to be used as a texture: alertMessage.innerHTML = /* html */ `Provide an image URL to be used as a texture:
<input id="textureURL" type="url" style="width: 100%" placeholder="http://www.example.com/image.jpg" oninput="fetchTextureURL(this.value)"> <input id="textureURL" type="url" style="width: 100%" placeholder="http://www.example.com/image.jpg" oninput="fetchTextureURL(this.value)" />
<canvas id="texturePreview" width="256px" height="144px"></canvas>`; <canvas id="texturePreview" width="256px" height="144px"></canvas>`;
$("#alert").dialog({ $("#alert").dialog({
resizable: false, resizable: false,

View file

@ -203,8 +203,7 @@ window.UISubmap = (function () {
ERROR && console.error(error); ERROR && console.error(error);
clearMainTip(); clearMainTip();
alertMessage.innerHTML = `Map resampling failed: alertMessage.innerHTML = /* html */ `Map resampling failed: <br />You may retry after clearing stored data or contact us at discord.
<br>You may retry after clearing stored data or contact us at discord.
<p id="errorBox">${parseError(error)}</p>`; <p id="errorBox">${parseError(error)}</p>`;
$("#alert").dialog({ $("#alert").dialog({
resizable: false, resizable: false,

View file

@ -35,7 +35,7 @@ toolsContent.addEventListener("click", function (event) {
return; return;
} }
alertMessage.innerHTML = `Regeneration will remove all the custom changes for the element.<br><br>Are you sure you want to proceed?`; alertMessage.innerHTML = /* html */ `Regeneration will remove all the custom changes for the element.<br /><br />Are you sure you want to proceed?`;
$("#alert").dialog({ $("#alert").dialog({
resizable: false, resizable: false,
title: "Regenerate element", title: "Regenerate element",

View file

@ -277,9 +277,8 @@ function editUnits() {
function removeAllRulers() { function removeAllRulers() {
if (!rulers.data.length) return; if (!rulers.data.length) return;
alertMessage.innerHTML = ` alertMessage.innerHTML = /* html */ ` Are you sure you want to remove all placed rulers?
Are you sure you want to remove all placed rulers? <br />If you just want to hide rulers, toggle the Rulers layer off in Menu`;
<br>If you just want to hide rulers, toggle the Rulers layer off in Menu`;
$("#alert").dialog({ $("#alert").dialog({
resizable: false, resizable: false,
title: "Remove all rulers", title: "Remove all rulers",

View file

@ -77,12 +77,12 @@ function editWorld() {
const scale = +distanceScaleInput.value, const scale = +distanceScaleInput.value,
unit = distanceUnitInput.value; unit = distanceUnitInput.value;
const meridian = toKilometer(eqD * 2 * scale); const meridian = toKilometer(eqD * 2 * scale);
document.getElementById("mapSize").innerHTML = `${graphWidth}x${graphHeight}`; document.getElementById("mapSize").innerHTML = /* html */ `${graphWidth}x${graphHeight}`;
document.getElementById("mapSizeFriendly").innerHTML = `${rn(graphWidth * scale)}x${rn(graphHeight * scale)} ${unit}`; document.getElementById("mapSizeFriendly").innerHTML = /* html */ `${rn(graphWidth * scale)}x${rn(graphHeight * scale)} ${unit}`;
document.getElementById("meridianLength").innerHTML = rn(eqD * 2); document.getElementById("meridianLength").innerHTML = rn(eqD * 2);
document.getElementById("meridianLengthFriendly").innerHTML = `${rn(eqD * 2 * scale)} ${unit}`; document.getElementById("meridianLengthFriendly").innerHTML = /* html */ `${rn(eqD * 2 * scale)} ${unit}`;
document.getElementById("meridianLengthEarth").innerHTML = meridian ? " = " + rn(meridian / 200) + "%🌏" : ""; document.getElementById("meridianLengthEarth").innerHTML = meridian ? " = " + rn(meridian / 200) + "%🌏" : "";
document.getElementById("mapCoordinates").innerHTML = `${lat(mc.latN)} ${Math.abs(rn(mc.lonW))}°W; ${lat(mc.latS)} ${rn(mc.lonE)}°E`; document.getElementById("mapCoordinates").innerHTML = /* html */ `${lat(mc.latN)} ${Math.abs(rn(mc.lonW))}°W; ${lat(mc.latS)} ${rn(mc.lonE)}°E`;
function toKilometer(v) { function toKilometer(v) {
if (unit === "km") return v; if (unit === "km") return v;

View file

@ -110,7 +110,7 @@ function editZones() {
const totalArea = (zonesFooterArea.dataset.area = graphWidth * graphHeight * distanceScaleInput.value ** 2); const totalArea = (zonesFooterArea.dataset.area = graphWidth * graphHeight * distanceScaleInput.value ** 2);
const totalPop = (d3.sum(pack.cells.pop) + d3.sum(pack.burgs.filter(b => !b.removed).map(b => b.population)) * urbanization) * populationRate; const totalPop = (d3.sum(pack.cells.pop) + d3.sum(pack.burgs.filter(b => !b.removed).map(b => b.population)) * urbanization) * populationRate;
zonesFooterPopulation.dataset.population = totalPop; zonesFooterPopulation.dataset.population = totalPop;
zonesFooterNumber.innerHTML = `${filteredZones.length} of ${zones.length}`; zonesFooterNumber.innerHTML = /* html */ `${filteredZones.length} of ${zones.length}`;
zonesFooterCells.innerHTML = pack.cells.i.length; zonesFooterCells.innerHTML = pack.cells.i.length;
zonesFooterArea.innerHTML = si(totalArea) + unit; zonesFooterArea.innerHTML = si(totalArea) + unit;
zonesFooterPopulation.innerHTML = si(totalPop); zonesFooterPopulation.innerHTML = si(totalPop);
@ -414,10 +414,9 @@ function editZones() {
const total = rural + urban; const total = rural + urban;
const l = n => Number(n).toLocaleString(); const l = n => Number(n).toLocaleString();
alertMessage.innerHTML = ` alertMessage.innerHTML = /* html */ `Rural: <input type="number" min="0" step="1" id="ruralPop" value=${rural} style="width:6em" /> Urban:
Rural: <input type="number" min=0 step=1 id="ruralPop" value=${rural} style="width:6em"> <input type="number" min="0" step="1" id="urbanPop" value=${urban} style="width:6em" ${burgs.length ? "" : "disabled"} />
Urban: <input type="number" min=0 step=1 id="urbanPop" value=${urban} style="width:6em" ${burgs.length ? "" : "disabled"}> <p>Total population: ${l(total)} <span id="totalPop">${l(total)}</span> (<span id="totalPopPerc">100</span>%)</p>`;
<p>Total population: ${l(total)} <span id="totalPop">${l(total)}</span> (<span id="totalPopPerc">100</span>%)</p>`;
const update = function () { const update = function () {
const totalNew = ruralPop.valueAsNumber + urbanPop.valueAsNumber; const totalNew = ruralPop.valueAsNumber + urbanPop.valueAsNumber;