generic confirmationDialog for all files

This commit is contained in:
Azgaar 2021-05-16 18:51:34 +03:00
parent 5464e0a34b
commit 3359df0e02
25 changed files with 6727 additions and 5454 deletions

File diff suppressed because it is too large Load diff

View file

@ -1,200 +1,246 @@
"use strict"; 'use strict';
function editBurg(id) { function editBurg(id) {
if (customization) return; if (customization) return;
closeDialogs(".stable"); closeDialogs('.stable');
if (!layerIsOn("toggleIcons")) toggleIcons(); if (!layerIsOn('toggleIcons')) toggleIcons();
if (!layerIsOn("toggleLabels")) toggleLabels(); if (!layerIsOn('toggleLabels')) toggleLabels();
const burg = id || d3.event.target.dataset.id; const burg = id || d3.event.target.dataset.id;
elSelected = burgLabels.select("[data-id='" + burg + "']"); elSelected = burgLabels.select("[data-id='" + burg + "']");
burgLabels.selectAll("text").call(d3.drag().on("start", dragBurgLabel)).classed("draggable", true); burgLabels.selectAll('text').call(d3.drag().on('start', dragBurgLabel)).classed('draggable', true);
updateBurgValues(); updateBurgValues();
const my = id || d3.event.target.tagName === "text" ? "center bottom-20" : "center top+20"; const my = id || d3.event.target.tagName === 'text' ? 'center bottom-20' : 'center top+20';
const at = id ? "center" : d3.event.target.tagName === "text" ? "top" : "bottom"; const at = id ? 'center' : d3.event.target.tagName === 'text' ? 'top' : 'bottom';
const of = id ? "svg" : d3.event.target; const of = id ? 'svg' : d3.event.target;
$("#burgEditor").dialog({ $('#burgEditor').dialog({
title: "Edit Burg", resizable: false, close: closeBurgEditor, title: 'Edit Burg',
position: {my, at, of, collision: "fit"} resizable: false,
close: closeBurgEditor,
position: {my, at, of, collision: 'fit'}
}); });
if (modules.editBurg) return; if (modules.editBurg) return;
modules.editBurg = true; modules.editBurg = true;
// add listeners // add listeners
document.getElementById("burgGroupShow").addEventListener("click", showGroupSection); document.getElementById('burgGroupShow').addEventListener('click', showGroupSection);
document.getElementById("burgGroupHide").addEventListener("click", hideGroupSection); document.getElementById('burgGroupHide').addEventListener('click', hideGroupSection);
document.getElementById("burgSelectGroup").addEventListener("change", changeGroup); document.getElementById('burgSelectGroup').addEventListener('change', changeGroup);
document.getElementById("burgInputGroup").addEventListener("change", createNewGroup); document.getElementById('burgInputGroup').addEventListener('change', createNewGroup);
document.getElementById("burgAddGroup").addEventListener("click", toggleNewGroupInput); document.getElementById('burgAddGroup').addEventListener('click', toggleNewGroupInput);
document.getElementById("burgRemoveGroup").addEventListener("click", removeBurgsGroup); document.getElementById('burgRemoveGroup').addEventListener('click', removeBurgsGroup);
document.getElementById("burgName").addEventListener("input", changeName); document.getElementById('burgName').addEventListener('input', changeName);
document.getElementById("burgNameReRandom").addEventListener("click", generateNameRandom); document.getElementById('burgNameReRandom').addEventListener('click', generateNameRandom);
document.getElementById("burgType").addEventListener("input", changeType); document.getElementById('burgType').addEventListener('input', changeType);
document.getElementById("burgCulture").addEventListener("input", changeCulture); document.getElementById('burgCulture').addEventListener('input', changeCulture);
document.getElementById("burgNameReCulture").addEventListener("click", generateNameCulture); document.getElementById('burgNameReCulture').addEventListener('click', generateNameCulture);
document.getElementById("burgPopulation").addEventListener("change", changePopulation); document.getElementById('burgPopulation').addEventListener('change', changePopulation);
burgBody.querySelectorAll(".burgFeature").forEach(el => el.addEventListener("click", toggleFeature)); burgBody.querySelectorAll('.burgFeature').forEach((el) => el.addEventListener('click', toggleFeature));
document.getElementById("burgStyleShow").addEventListener("click", showStyleSection); document.getElementById('burgStyleShow').addEventListener('click', showStyleSection);
document.getElementById("burgStyleHide").addEventListener("click", hideStyleSection); document.getElementById('burgStyleHide').addEventListener('click', hideStyleSection);
document.getElementById("burgEditLabelStyle").addEventListener("click", editGroupLabelStyle); document.getElementById('burgEditLabelStyle').addEventListener('click', editGroupLabelStyle);
document.getElementById("burgEditIconStyle").addEventListener("click", editGroupIconStyle); document.getElementById('burgEditIconStyle').addEventListener('click', editGroupIconStyle);
document.getElementById("burgEditAnchorStyle").addEventListener("click", editGroupAnchorStyle); document.getElementById('burgEditAnchorStyle').addEventListener('click', editGroupAnchorStyle);
document.getElementById("burgSeeInMFCG").addEventListener("click", openInMFCG); document.getElementById('burgSeeInMFCG').addEventListener('click', openInMFCG);
document.getElementById("burgEditEmblem").addEventListener("click", openEmblemEdit); document.getElementById('burgEditEmblem').addEventListener('click', openEmblemEdit);
document.getElementById("burgRelocate").addEventListener("click", toggleRelocateBurg); document.getElementById('burgRelocate').addEventListener('click', toggleRelocateBurg);
document.getElementById("burglLegend").addEventListener("click", editBurgLegend); document.getElementById('burglLegend').addEventListener('click', editBurgLegend);
document.getElementById("burgLock").addEventListener("click", toggleBurgLockButton); document.getElementById('burgLock').addEventListener('click', toggleBurgLockButton);
document.getElementById("burgLock").addEventListener("mouseover", showBurgELockTip); document.getElementById('burgLock').addEventListener('mouseover', showBurgELockTip);
document.getElementById("burgRemove").addEventListener("click", removeSelectedBurg); document.getElementById('burgRemove').addEventListener('click', removeSelectedBurg);
function updateBurgValues() { function updateBurgValues() {
const id = +elSelected.attr("data-id"); const id = +elSelected.attr('data-id');
const b = pack.burgs[id]; const b = pack.burgs[id];
const province = pack.cells.province[b.cell]; const province = pack.cells.province[b.cell];
const provinceName = province ? pack.provinces[province].fullName + ", " : ""; const provinceName = province ? pack.provinces[province].fullName + ', ' : '';
const stateName = pack.states[b.state].fullName || pack.states[b.state].name; const stateName = pack.states[b.state].fullName || pack.states[b.state].name;
document.getElementById("burgProvinceAndState").innerHTML = provinceName + stateName; document.getElementById('burgProvinceAndState').innerHTML = provinceName + stateName;
document.getElementById("burgName").value = b.name; document.getElementById('burgName').value = b.name;
document.getElementById("burgType").value = b.type || "Generic"; document.getElementById('burgType').value = b.type || 'Generic';
document.getElementById("burgPopulation").value = rn(b.population * populationRate.value * urbanization.value); document.getElementById('burgPopulation').value = rn(b.population * populationRate.value * urbanization.value);
document.getElementById("burgEditAnchorStyle").style.display = +b.port ? "inline-block" : "none"; document.getElementById('burgEditAnchorStyle').style.display = +b.port ? 'inline-block' : 'none';
// update list and select culture // update list and select culture
const cultureSelect = document.getElementById("burgCulture"); const cultureSelect = document.getElementById('burgCulture');
cultureSelect.options.length = 0; cultureSelect.options.length = 0;
const cultures = pack.cultures.filter(c => !c.removed); const cultures = pack.cultures.filter((c) => !c.removed);
cultures.forEach(c => cultureSelect.options.add(new Option(c.name, c.i, false, c.i === b.culture))); cultures.forEach((c) => cultureSelect.options.add(new Option(c.name, c.i, false, c.i === b.culture)));
const temperature = grid.cells.temp[pack.cells.g[b.cell]]; const temperature = grid.cells.temp[pack.cells.g[b.cell]];
document.getElementById("burgTemperature").innerHTML = convertTemperature(temperature); document.getElementById('burgTemperature').innerHTML = convertTemperature(temperature);
document.getElementById("burgTemperatureLikeIn").innerHTML = getTemperatureLikeness(temperature); document.getElementById('burgTemperatureLikeIn').innerHTML = getTemperatureLikeness(temperature);
document.getElementById("burgElevation").innerHTML = getHeight(pack.cells.h[b.cell]); document.getElementById('burgElevation').innerHTML = getHeight(pack.cells.h[b.cell]);
// toggle features // toggle features
if (b.capital) document.getElementById("burgCapital").classList.remove("inactive"); if (b.capital) document.getElementById('burgCapital').classList.remove('inactive');
else document.getElementById("burgCapital").classList.add("inactive"); else document.getElementById('burgCapital').classList.add('inactive');
if (b.port) document.getElementById("burgPort").classList.remove("inactive"); if (b.port) document.getElementById('burgPort').classList.remove('inactive');
else document.getElementById("burgPort").classList.add("inactive"); else document.getElementById('burgPort').classList.add('inactive');
if (b.citadel) document.getElementById("burgCitadel").classList.remove("inactive"); if (b.citadel) document.getElementById('burgCitadel').classList.remove('inactive');
else document.getElementById("burgCitadel").classList.add("inactive"); else document.getElementById('burgCitadel').classList.add('inactive');
if (b.walls) document.getElementById("burgWalls").classList.remove("inactive"); if (b.walls) document.getElementById('burgWalls').classList.remove('inactive');
else document.getElementById("burgWalls").classList.add("inactive"); else document.getElementById('burgWalls').classList.add('inactive');
if (b.plaza) document.getElementById("burgPlaza").classList.remove("inactive"); if (b.plaza) document.getElementById('burgPlaza').classList.remove('inactive');
else document.getElementById("burgPlaza").classList.add("inactive"); else document.getElementById('burgPlaza').classList.add('inactive');
if (b.temple) document.getElementById("burgTemple").classList.remove("inactive"); if (b.temple) document.getElementById('burgTemple').classList.remove('inactive');
else document.getElementById("burgTemple").classList.add("inactive"); else document.getElementById('burgTemple').classList.add('inactive');
if (b.shanty) document.getElementById("burgShanty").classList.remove("inactive"); if (b.shanty) document.getElementById('burgShanty').classList.remove('inactive');
else document.getElementById("burgShanty").classList.add("inactive"); else document.getElementById('burgShanty').classList.add('inactive');
//toggle lock //toggle lock
updateBurgLockIcon(); updateBurgLockIcon();
// select group // select group
const group = elSelected.node().parentNode.id; const group = elSelected.node().parentNode.id;
const select = document.getElementById("burgSelectGroup"); const select = document.getElementById('burgSelectGroup');
select.options.length = 0; // remove all options select.options.length = 0; // remove all options
burgLabels.selectAll("g").each(function() { burgLabels.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));
}); });
// set emlem image // set emlem image
const coaID = "burgCOA"+id; const coaID = 'burgCOA' + id;
COArenderer.trigger(coaID, b.coa); COArenderer.trigger(coaID, b.coa);
document.getElementById("burgEmblem").setAttribute("href", "#" + coaID); document.getElementById('burgEmblem').setAttribute('href', '#' + coaID);
} }
// in °C, array from -1 °C; source: https://en.wikipedia.org/wiki/List_of_cities_by_average_temperature // in °C, array from -1 °C; source: https://en.wikipedia.org/wiki/List_of_cities_by_average_temperature
function getTemperatureLikeness(temperature) { function getTemperatureLikeness(temperature) {
if (temperature < -5) return "Yakutsk"; if (temperature < -5) return 'Yakutsk';
const cities = [ const cities = [
"Snag (Yukon)", "Yellowknife (Canada)", "Okhotsk (Russia)", "Fairbanks (Alaska)", "Nuuk (Greenland)", "Murmansk", // -5 - 0 'Snag (Yukon)',
"Arkhangelsk", "Anchorage", "Tromsø", "Reykjavik", "Riga", "Stockholm", "Halifax", "Prague", "Copenhagen", "London", // 1 - 10 'Yellowknife (Canada)',
"Antwerp", "Paris", "Milan", "Batumi", "Rome", "Dubrovnik", "Lisbon", "Barcelona", "Marrakesh", "Alexandria", // 11 - 20 'Okhotsk (Russia)',
"Tegucigalpa", "Guangzhou", "Rio de Janeiro", "Dakar", "Miami", "Jakarta", "Mogadishu", "Bangkok", "Aden", "Khartoum"]; // 21 - 30 'Fairbanks (Alaska)',
if (temperature > 30) return "Mecca"; 'Nuuk (Greenland)',
return cities[temperature+5] || null; 'Murmansk', // -5 - 0
'Arkhangelsk',
'Anchorage',
'Tromsø',
'Reykjavik',
'Riga',
'Stockholm',
'Halifax',
'Prague',
'Copenhagen',
'London', // 1 - 10
'Antwerp',
'Paris',
'Milan',
'Batumi',
'Rome',
'Dubrovnik',
'Lisbon',
'Barcelona',
'Marrakesh',
'Alexandria', // 11 - 20
'Tegucigalpa',
'Guangzhou',
'Rio de Janeiro',
'Dakar',
'Miami',
'Jakarta',
'Mogadishu',
'Bangkok',
'Aden',
'Khartoum'
]; // 21 - 30
if (temperature > 30) return 'Mecca';
return cities[temperature + 5] || null;
} }
function dragBurgLabel() { function dragBurgLabel() {
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;
tip('Use dragging for fine-tuning only, to actually move burg use "Relocate" button', false, "warning"); this.setAttribute('transform', `translate(${dx + x},${dy + y})`);
tip('Use dragging for fine-tuning only, to actually move burg use "Relocate" button', false, 'warning');
}); });
} }
function showGroupSection() { function showGroupSection() {
document.querySelectorAll("#burgBottom > button").forEach(el => el.style.display = "none"); document.querySelectorAll('#burgBottom > button').forEach((el) => (el.style.display = 'none'));
document.getElementById("burgGroupSection").style.display = "inline-block"; document.getElementById('burgGroupSection').style.display = 'inline-block';
} }
function hideGroupSection() { function hideGroupSection() {
document.querySelectorAll("#burgBottom > button").forEach(el => el.style.display = "inline-block"); document.querySelectorAll('#burgBottom > button').forEach((el) => (el.style.display = 'inline-block'));
document.getElementById("burgGroupSection").style.display = "none"; document.getElementById('burgGroupSection').style.display = 'none';
document.getElementById("burgInputGroup").style.display = "none"; document.getElementById('burgInputGroup').style.display = 'none';
document.getElementById("burgInputGroup").value = ""; document.getElementById('burgInputGroup').value = '';
document.getElementById("burgSelectGroup").style.display = "inline-block"; document.getElementById('burgSelectGroup').style.display = 'inline-block';
} }
function changeGroup() { function changeGroup() {
const id = +elSelected.attr("data-id"); const id = +elSelected.attr('data-id');
moveBurgToGroup(id, this.value); moveBurgToGroup(id, this.value);
} }
function toggleNewGroupInput() { function toggleNewGroupInput() {
if (burgInputGroup.style.display === "none") { if (burgInputGroup.style.display === 'none') {
burgInputGroup.style.display = "inline-block"; burgInputGroup.style.display = 'inline-block';
burgInputGroup.focus(); burgInputGroup.focus();
burgSelectGroup.style.display = "none"; burgSelectGroup.style.display = 'none';
} else { } else {
burgInputGroup.style.display = "none"; burgInputGroup.style.display = 'none';
burgSelectGroup.style.display = "inline-block"; burgSelectGroup.style.display = 'inline-block';
} }
} }
function createNewGroup() { function createNewGroup() {
if (!this.value) {tip("Please provide a valid group name", false, "error"); return;} if (!this.value) {
const group = this.value.toLowerCase().replace(/ /g, "_").replace(/[^\w\s]/gi, ""); tip('Please provide a valid group name', false, 'error');
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');
return; return;
} }
if (Number.isFinite(+group.charAt(0))) { if (Number.isFinite(+group.charAt(0))) {
tip("Group name should start with a letter", false, "error"); tip('Group name should start with a letter', false, 'error');
return; return;
} }
const id = +elSelected.attr("data-id"); const id = +elSelected.attr('data-id');
const oldGroup = elSelected.node().parentNode.id; const oldGroup = elSelected.node().parentNode.id;
const label = document.querySelector("#burgLabels [data-id='" + id + "']"); const label = document.querySelector("#burgLabels [data-id='" + id + "']");
const icon = document.querySelector("#burgIcons [data-id='" + id + "']"); const icon = document.querySelector("#burgIcons [data-id='" + id + "']");
const anchor = document.querySelector("#anchors [data-id='" + id + "']"); const anchor = document.querySelector("#anchors [data-id='" + id + "']");
if (!label || !icon) {ERROR && console.error("Cannot find label or icon elements"); return;} if (!label || !icon) {
ERROR && console.error('Cannot find label or icon elements');
return;
}
const labelG = document.querySelector("#burgLabels > #"+oldGroup); const labelG = document.querySelector('#burgLabels > #' + oldGroup);
const iconG = document.querySelector("#burgIcons > #"+oldGroup); const iconG = document.querySelector('#burgIcons > #' + oldGroup);
const anchorG = document.querySelector("#anchors > #"+oldGroup); const anchorG = document.querySelector('#anchors > #' + oldGroup);
// just rename if only 1 element left // just rename if only 1 element left
const count = elSelected.node().parentNode.childElementCount; const count = elSelected.node().parentNode.childElementCount;
if (oldGroup !== "cities" && oldGroup !== "towns" && count === 1) { if (oldGroup !== 'cities' && oldGroup !== 'towns' && count === 1) {
document.getElementById("burgSelectGroup").selectedOptions[0].remove(); document.getElementById('burgSelectGroup').selectedOptions[0].remove();
document.getElementById("burgSelectGroup").options.add(new Option(group, group, false, true)); document.getElementById('burgSelectGroup').options.add(new Option(group, group, false, true));
toggleNewGroupInput(); toggleNewGroupInput();
document.getElementById("burgInputGroup").value = ""; document.getElementById('burgInputGroup').value = '';
labelG.id = group; labelG.id = group;
iconG.id = group; iconG.id = group;
if (anchor) anchorG.id = group; if (anchor) anchorG.id = group;
@ -202,16 +248,16 @@ function editBurg(id) {
} }
// create new groups // create new groups
document.getElementById("burgSelectGroup").options.add(new Option(group, group, false, true)); document.getElementById('burgSelectGroup').options.add(new Option(group, group, false, true));
toggleNewGroupInput(); toggleNewGroupInput();
document.getElementById("burgInputGroup").value = ""; document.getElementById('burgInputGroup').value = '';
const newLabelG = document.querySelector("#burgLabels").appendChild(labelG.cloneNode(false)); const newLabelG = document.querySelector('#burgLabels').appendChild(labelG.cloneNode(false));
newLabelG.id = group; newLabelG.id = group;
const newIconG = document.querySelector("#burgIcons").appendChild(iconG.cloneNode(false)); const newIconG = document.querySelector('#burgIcons').appendChild(iconG.cloneNode(false));
newIconG.id = group; newIconG.id = group;
if (anchor) { if (anchor) {
const newAnchorG = document.querySelector("#anchors").appendChild(anchorG.cloneNode(false)); const newAnchorG = document.querySelector('#anchors').appendChild(anchorG.cloneNode(false));
newAnchorG.id = group; newAnchorG.id = group;
} }
moveBurgToGroup(id, group); moveBurgToGroup(id, group);
@ -219,151 +265,158 @@ function editBurg(id) {
function removeBurgsGroup() { function removeBurgsGroup() {
const group = elSelected.node().parentNode; const group = elSelected.node().parentNode;
const basic = group.id === "cities" || group.id === "towns"; const basic = group.id === 'cities' || group.id === 'towns';
const burgsInGroup = []; const burgsInGroup = [];
for (let i=0; i < group.children.length; i++) { for (let i = 0; i < group.children.length; i++) {
burgsInGroup.push(+group.children[i].dataset.id); burgsInGroup.push(+group.children[i].dataset.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 const message = `Are you sure you want to remove
${basic || capital ? "all unlocked elements in the group" : "the entire burg group"}? ${basic || capital ? 'all unlocked elements in the group' : 'the entire burg group'}?
<br>Please note that capital or locked burgs will not be deleted. <br>Please note that capital or locked burgs will not be deleted.
<br><br>Burgs to be removed: ${burgsToRemove.length}`; <br><br>Burgs to be removed: ${burgsToRemove.length}`;
$("#alert").dialog({resizable: false, title: "Remove route group", confirmationDialog({title: 'Remove burg group', message, confirm: 'Remove', onConfirm: removeGroup});
buttons: {
Remove: function() {
$(this).dialog("close");
$("#burgEditor").dialog("close");
hideGroupSection();
burgsToRemove.forEach(b => removeBurg(b));
if (!basic && !capital) { function removeGroup() {
// entirely remove group $(this).dialog('close');
const labelG = document.querySelector("#burgLabels > #"+group.id); $('#burgEditor').dialog('close');
const iconG = document.querySelector("#burgIcons > #"+group.id); hideGroupSection();
const anchorG = document.querySelector("#anchors > #"+group.id); burgsToRemove.forEach((b) => removeBurg(b));
if (labelG) labelG.remove();
if (iconG) iconG.remove(); if (!basic && !capital) {
if (anchorG) anchorG.remove(); // entirely remove group
} const labelG = document.querySelector('#burgLabels > #' + group.id);
}, const iconG = document.querySelector('#burgIcons > #' + group.id);
Cancel: function() {$(this).dialog("close");} const anchorG = document.querySelector('#anchors > #' + group.id);
if (labelG) labelG.remove();
if (iconG) iconG.remove();
if (anchorG) anchorG.remove();
} }
}); }
} }
function changeName() { function changeName() {
const id = +elSelected.attr("data-id"); const id = +elSelected.attr('data-id');
pack.burgs[id].name = burgName.value; pack.burgs[id].name = burgName.value;
elSelected.text(burgName.value); elSelected.text(burgName.value);
} }
function generateNameRandom() { function generateNameRandom() {
const base = rand(nameBases.length-1); const base = rand(nameBases.length - 1);
burgName.value = Names.getBase(base); burgName.value = Names.getBase(base);
changeName(); changeName();
} }
function changeType() { function changeType() {
const id = +elSelected.attr("data-id"); const id = +elSelected.attr('data-id');
pack.burgs[id].type = this.value; pack.burgs[id].type = this.value;
} }
function changeCulture() { function changeCulture() {
const id = +elSelected.attr("data-id"); const id = +elSelected.attr('data-id');
pack.burgs[id].culture = +this.value; pack.burgs[id].culture = +this.value;
} }
function generateNameCulture() { function generateNameCulture() {
const id = +elSelected.attr("data-id"); const id = +elSelected.attr('data-id');
const culture = pack.burgs[id].culture; const culture = pack.burgs[id].culture;
burgName.value = Names.getCulture(culture); burgName.value = Names.getCulture(culture);
changeName(); changeName();
} }
function changePopulation() { function changePopulation() {
const id = +elSelected.attr("data-id"); const id = +elSelected.attr('data-id');
pack.burgs[id].population = rn(burgPopulation.value / populationRate.value / urbanization.value, 4); pack.burgs[id].population = rn(burgPopulation.value / populationRate.value / urbanization.value, 4);
} }
function toggleFeature() { function toggleFeature() {
const id = +elSelected.attr("data-id"); const id = +elSelected.attr('data-id');
const b = pack.burgs[id]; const b = pack.burgs[id];
const feature = this.dataset.feature; const feature = this.dataset.feature;
const turnOn = this.classList.contains("inactive"); const turnOn = this.classList.contains('inactive');
if (feature === "port") togglePort(id); if (feature === 'port') togglePort(id);
else if(feature === "capital") toggleCapital(id); else if (feature === 'capital') toggleCapital(id);
else b[feature] = +turnOn; else b[feature] = +turnOn;
if (b[feature]) this.classList.remove("inactive"); if (b[feature]) this.classList.remove('inactive');
else if (!b[feature]) this.classList.add("inactive"); else if (!b[feature]) this.classList.add('inactive');
if (b.port) document.getElementById("burgEditAnchorStyle").style.display = "inline-block"; if (b.port) document.getElementById('burgEditAnchorStyle').style.display = 'inline-block';
else document.getElementById("burgEditAnchorStyle").style.display = "none"; else document.getElementById('burgEditAnchorStyle').style.display = 'none';
} }
function toggleBurgLockButton() { function toggleBurgLockButton() {
const id = +elSelected.attr("data-id"); const id = +elSelected.attr('data-id');
toggleBurgLock(id); toggleBurgLock(id);
updateBurgLockIcon(); updateBurgLockIcon();
} }
function updateBurgLockIcon() { function updateBurgLockIcon() {
const id = +elSelected.attr("data-id"); const id = +elSelected.attr('data-id');
const b = pack.burgs[id]; const b = pack.burgs[id];
if (b.lock) {document.getElementById("burgLock").classList.remove("icon-lock-open"); document.getElementById("burgLock").classList.add("icon-lock");} if (b.lock) {
else {document.getElementById("burgLock").classList.remove("icon-lock"); document.getElementById("burgLock").classList.add("icon-lock-open");} document.getElementById('burgLock').classList.remove('icon-lock-open');
document.getElementById('burgLock').classList.add('icon-lock');
} else {
document.getElementById('burgLock').classList.remove('icon-lock');
document.getElementById('burgLock').classList.add('icon-lock-open');
}
} }
function showBurgELockTip() { function showBurgELockTip() {
const id = +elSelected.attr("data-id"); const id = +elSelected.attr('data-id');
showBurgLockTip(id); showBurgLockTip(id);
} }
function showStyleSection() { function showStyleSection() {
document.querySelectorAll("#burgBottom > button").forEach(el => el.style.display = "none"); document.querySelectorAll('#burgBottom > button').forEach((el) => (el.style.display = 'none'));
document.getElementById("burgStyleSection").style.display = "inline-block"; document.getElementById('burgStyleSection').style.display = 'inline-block';
} }
function hideStyleSection() { function hideStyleSection() {
document.querySelectorAll("#burgBottom > button").forEach(el => el.style.display = "inline-block"); document.querySelectorAll('#burgBottom > button').forEach((el) => (el.style.display = 'inline-block'));
document.getElementById("burgStyleSection").style.display = "none"; document.getElementById('burgStyleSection').style.display = 'none';
} }
function editGroupLabelStyle() { function editGroupLabelStyle() {
const g = elSelected.node().parentNode.id; const g = elSelected.node().parentNode.id;
editStyle("labels", g); editStyle('labels', g);
} }
function editGroupIconStyle() { function editGroupIconStyle() {
const g = elSelected.node().parentNode.id; const g = elSelected.node().parentNode.id;
editStyle("burgIcons", g); editStyle('burgIcons', g);
} }
function editGroupAnchorStyle() { function editGroupAnchorStyle() {
const g = elSelected.node().parentNode.id; const g = elSelected.node().parentNode.id;
editStyle("anchors", g); editStyle('anchors', g);
} }
function openInMFCG(event) { function openInMFCG(event) {
const id = elSelected.attr("data-id"); const id = elSelected.attr('data-id');
const burg = pack.burgs[id]; const burg = pack.burgs[id];
const defSeed = +(seed + id.padStart(4, 0)); const defSeed = +(seed + id.padStart(4, 0));
if (isCtrlClick(event)) { if (isCtrlClick(event)) {
prompt(`Please provide a Medieval Fantasy City Generator seed. prompt(
`Please provide a Medieval Fantasy City Generator seed.
Seed should be a number. Default seed is FMG map seed + burg id padded to 4 chars with zeros (${defSeed}). Seed should be a number. Default seed is FMG map seed + burg id padded to 4 chars with zeros (${defSeed}).
Please note that if seed is custom, "Overworld" button from MFCG will open a different map`, Please note that if seed is custom, "Overworld" button from MFCG will open a different map`,
{default:burg.MFCG||defSeed, step:1, min:1, max:1e13-1}, v => { {default: burg.MFCG || defSeed, step: 1, min: 1, max: 1e13 - 1},
burg.MFCG = v; (v) => {
openMFCG(v); burg.MFCG = v;
}); openMFCG(v);
}
);
} else openMFCG(); } else openMFCG();
function openMFCG(seed) { function openMFCG(seed) {
if (!seed && burg.MFCGlink) {openURL(burg.MFCGlink); return;} if (!seed && burg.MFCGlink) {
openURL(burg.MFCGlink);
return;
}
const cells = pack.cells; const cells = pack.cells;
const name = elSelected.text(); const name = elSelected.text();
const size = Math.max(Math.min(rn(burg.population), 100), 6); // to be removed once change on MFDC is done const size = Math.max(Math.min(rn(burg.population), 100), 6); // to be removed once change on MFDC is done
@ -381,38 +434,45 @@ function editBurg(id) {
const temple = +burg.temple; const temple = +burg.temple;
const shanty = +burg.shanty; const shanty = +burg.shanty;
const sea = coast && cells.haven[burg.cell] ? getSeaDirections(burg.cell) : ""; const sea = coast && cells.haven[burg.cell] ? getSeaDirections(burg.cell) : '';
function getSeaDirections(i) { function getSeaDirections(i) {
const p1 = cells.p[i]; const p1 = cells.p[i];
const p2 = cells.p[cells.haven[i]]; const p2 = cells.p[cells.haven[i]];
let deg = Math.atan2(p2[1] - p1[1], p2[0] - p1[0]) * 180 / Math.PI - 90; let deg = (Math.atan2(p2[1] - p1[1], p2[0] - p1[0]) * 180) / Math.PI - 90;
if (deg < 0) deg += 360; if (deg < 0) deg += 360;
const norm = rn(normalize(deg, 0, 360) * 2, 2); // 0 = south, 0.5 = west, 1 = north, 1.5 = east const norm = rn(normalize(deg, 0, 360) * 2, 2); // 0 = south, 0.5 = west, 1 = north, 1.5 = east
return "&sea="+norm; return '&sea=' + norm;
} }
const site = "http://fantasycities.watabou.ru/?random=0&continuous=0"; const site = 'http://fantasycities.watabou.ru/?random=0&continuous=0';
const url = `${site}&name=${name}&population=${population}&size=${size}&seed=${s}&hub=${hub}&river=${river}&coast=${coast}&citadel=${citadel}&plaza=${plaza}&temple=${temple}&walls=${walls}&shantytown=${shanty}${sea}`; const url = `${site}&name=${name}&population=${population}&size=${size}&seed=${s}&hub=${hub}&river=${river}&coast=${coast}&citadel=${citadel}&plaza=${plaza}&temple=${temple}&walls=${walls}&shantytown=${shanty}${sea}`;
openURL(url); openURL(url);
} }
} }
function openEmblemEdit() { function openEmblemEdit() {
const id = +elSelected.attr("data-id"), burg = pack.burgs[id]; const id = +elSelected.attr('data-id'),
editEmblem("burg", "burgCOA"+id, burg); burg = pack.burgs[id];
editEmblem('burg', 'burgCOA' + id, burg);
} }
function toggleRelocateBurg() { function toggleRelocateBurg() {
const toggler = document.getElementById("toggleCells"); const toggler = document.getElementById('toggleCells');
document.getElementById("burgRelocate").classList.toggle("pressed"); document.getElementById('burgRelocate').classList.toggle('pressed');
if (document.getElementById("burgRelocate").classList.contains("pressed")) { if (document.getElementById('burgRelocate').classList.contains('pressed')) {
viewbox.style("cursor", "crosshair").on("click", relocateBurgOnClick); viewbox.style('cursor', 'crosshair').on('click', relocateBurgOnClick);
tip("Click on map to relocate burg. Hold Shift for continuous move", true); tip('Click on map to relocate burg. Hold Shift for continuous move', true);
if (!layerIsOn("toggleCells")) {toggleCells(); toggler.dataset.forced = true;} if (!layerIsOn('toggleCells')) {
toggleCells();
toggler.dataset.forced = true;
}
} else { } else {
clearMainTip(); clearMainTip();
viewbox.on("click", clicked).style("cursor", "default"); viewbox.on('click', clicked).style('cursor', 'default');
if (layerIsOn("toggleCells") && toggler.dataset.forced) {toggleCells(); toggler.dataset.forced = false;} if (layerIsOn('toggleCells') && toggler.dataset.forced) {
toggleCells();
toggler.dataset.forced = false;
}
} }
} }
@ -420,16 +480,16 @@ function editBurg(id) {
const cells = pack.cells; const cells = pack.cells;
const point = d3.mouse(this); const point = d3.mouse(this);
const cell = findCell(point[0], point[1]); const cell = findCell(point[0], point[1]);
const id = +elSelected.attr("data-id"); const id = +elSelected.attr('data-id');
const burg = pack.burgs[id]; const burg = pack.burgs[id];
if (cells.h[cell] < 20) { if (cells.h[cell] < 20) {
tip("Cannot place burg into the water! Select a land cell", false, "error"); tip('Cannot place burg into the water! Select a land cell', false, 'error');
return; return;
} }
if (cells.burg[cell] && cells.burg[cell] !== id) { if (cells.burg[cell] && cells.burg[cell] !== id) {
tip("There is already a burg in this cell. Please select a free cell", false, "error"); tip('There is already a burg in this cell. Please select a free cell', false, 'error');
return; return;
} }
@ -437,20 +497,29 @@ function editBurg(id) {
const oldState = burg.state; const oldState = burg.state;
if (newState !== oldState && burg.capital) { if (newState !== oldState && burg.capital) {
tip("Capital cannot be relocated into another state!", false, "error"); tip('Capital cannot be relocated into another state!', false, 'error');
return; return;
} }
// change UI // change UI
const x = rn(point[0], 2), y = rn(point[1], 2); const x = rn(point[0], 2),
burgIcons.select("[data-id='" + id + "']").attr("transform", null).attr("cx", x).attr("cy", y); y = rn(point[1], 2);
burgLabels.select("text[data-id='" + id + "']").attr("transform", null).attr("x", x).attr("y", y); burgIcons
const anchor = anchors.select("use[data-id='" + id+ "']"); .select("[data-id='" + id + "']")
.attr('transform', null)
.attr('cx', x)
.attr('cy', y);
burgLabels
.select("text[data-id='" + id + "']")
.attr('transform', null)
.attr('x', x)
.attr('y', y);
const anchor = anchors.select("use[data-id='" + id + "']");
if (anchor.size()) { if (anchor.size()) {
const size = anchor.attr("width"); const size = anchor.attr('width');
const xa = rn(x - size * 0.47, 2); const xa = rn(x - size * 0.47, 2);
const ya = rn(y - size * 0.47, 2); const ya = rn(y - size * 0.47, 2);
anchor.attr("transform", null).attr("x", xa).attr("y", ya); anchor.attr('transform', null).attr('x', xa).attr('y', ya);
} }
// change data // change data
@ -466,38 +535,38 @@ function editBurg(id) {
} }
function editBurgLegend() { function editBurgLegend() {
const id = elSelected.attr("data-id"); const id = elSelected.attr('data-id');
const name = elSelected.text(); const name = elSelected.text();
editNotes("burg"+id, name); editNotes('burg' + id, name);
} }
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 = `You cannot remove the burg as it is a state capital.<br><br>
You can change the capital using Burgs Editor (shift + T)`; Please change state capital first. You can do it using Burgs Editor (shift + T)`;
$("#alert").dialog({resizable: false, title: "Remove burg", $('#alert').dialog({
buttons: {Ok: function() {$(this).dialog("close");}} resizable: false,
}); title: 'Remove burg',
} else {
alertMessage.innerHTML = "Are you sure you want to remove the burg?";
$("#alert").dialog({resizable: false, title: "Remove burg",
buttons: { buttons: {
Remove: function() { Ok: function () {
$(this).dialog("close"); $(this).dialog('close');
removeBurg(id); // see Editors module }
$("#burgEditor").dialog("close");
},
Cancel: function() {$(this).dialog("close");}
} }
}); });
} else {
const message = 'Are you sure you want to remove the burg? <br>This action cannot be reverted';
const onConfirm = () => {
removeBurg(id);
$('#burgEditor').dialog('close');
};
confirmationDialog({title: 'Remove burg', message, confirm: 'Remove', onConfirm});
} }
} }
function closeBurgEditor() { function closeBurgEditor() {
document.getElementById("burgRelocate").classList.remove("pressed"); document.getElementById('burgRelocate').classList.remove('pressed');
burgLabels.selectAll("text").call(d3.drag().on("drag", null)).classed("draggable", false); burgLabels.selectAll('text').call(d3.drag().on('drag', null)).classed('draggable', false);
unselect(); unselect();
} }
} }

View file

@ -1,34 +1,39 @@
"use strict"; 'use strict';
function overviewBurgs() { function overviewBurgs() {
if (customization) return; if (customization) return;
closeDialogs("#burgsOverview, .stable"); closeDialogs('#burgsOverview, .stable');
if (!layerIsOn("toggleIcons")) toggleIcons(); if (!layerIsOn('toggleIcons')) toggleIcons();
if (!layerIsOn("toggleLabels")) toggleLabels(); if (!layerIsOn('toggleLabels')) toggleLabels();
const body = document.getElementById("burgsBody"); const body = document.getElementById('burgsBody');
updateFilter(); updateFilter();
burgsOverviewAddLines(); burgsOverviewAddLines();
$("#burgsOverview").dialog(); $('#burgsOverview').dialog();
if (modules.overviewBurgs) return; if (modules.overviewBurgs) return;
modules.overviewBurgs = true; modules.overviewBurgs = true;
$("#burgsOverview").dialog({ $('#burgsOverview').dialog({
title: "Burgs Overview", resizable: false, width: fitContent(), close: exitAddBurgMode, title: 'Burgs Overview',
position: {my: "right top", at: "right-10 top+10", of: "svg", collision: "fit"} resizable: false,
width: fitContent(),
close: exitAddBurgMode,
position: {my: 'right top', at: 'right-10 top+10', of: 'svg', collision: 'fit'}
}); });
// add listeners // add listeners
document.getElementById("burgsOverviewRefresh").addEventListener("click", refreshBurgsEditor); document.getElementById('burgsOverviewRefresh').addEventListener('click', refreshBurgsEditor);
document.getElementById("burgsChart").addEventListener("click", showBurgsChart); document.getElementById('burgsChart').addEventListener('click', showBurgsChart);
document.getElementById("burgsFilterState").addEventListener("change", burgsOverviewAddLines); document.getElementById('burgsFilterState').addEventListener('change', burgsOverviewAddLines);
document.getElementById("burgsFilterCulture").addEventListener("change", burgsOverviewAddLines); document.getElementById('burgsFilterCulture').addEventListener('change', burgsOverviewAddLines);
document.getElementById("regenerateBurgNames").addEventListener("click", regenerateNames); document.getElementById('regenerateBurgNames').addEventListener('click', regenerateNames);
document.getElementById("addNewBurg").addEventListener("click", enterAddBurgMode); document.getElementById('addNewBurg').addEventListener('click', enterAddBurgMode);
document.getElementById("burgsExport").addEventListener("click", downloadBurgsData); document.getElementById('burgsExport').addEventListener('click', downloadBurgsData);
document.getElementById("burgNamesImport").addEventListener("click", renameBurgsInBulk); document.getElementById('burgNamesImport').addEventListener('click', renameBurgsInBulk);
document.getElementById("burgsListToLoad").addEventListener("change", function() {uploadFile(this, importBurgNames)}); document.getElementById('burgsListToLoad').addEventListener('change', function () {
document.getElementById("burgsRemoveAll").addEventListener("click", triggerAllBurgsRemove); uploadFile(this, importBurgNames);
});
document.getElementById('burgsRemoveAll').addEventListener('click', triggerAllBurgsRemove);
function refreshBurgsEditor() { function refreshBurgsEditor() {
updateFilter(); updateFilter();
@ -36,41 +41,42 @@ function overviewBurgs() {
} }
function updateFilter() { function updateFilter() {
const stateFilter = document.getElementById("burgsFilterState"); const stateFilter = document.getElementById('burgsFilterState');
const selectedState = stateFilter.value || 1; const selectedState = stateFilter.value || 1;
stateFilter.options.length = 0; // remove all options stateFilter.options.length = 0; // remove all options
stateFilter.options.add(new Option(`all`, -1, false, selectedState == -1)); stateFilter.options.add(new Option(`all`, -1, false, selectedState == -1));
stateFilter.options.add(new Option(pack.states[0].name, 0, false, !selectedState)); stateFilter.options.add(new Option(pack.states[0].name, 0, false, !selectedState));
const statesSorted = pack.states.filter(s => s.i && !s.removed).sort((a, b) => (a.name > b.name) ? 1 : -1); const statesSorted = pack.states.filter((s) => s.i && !s.removed).sort((a, b) => (a.name > b.name ? 1 : -1));
statesSorted.forEach(s => stateFilter.options.add(new Option(s.name, s.i, false, s.i == selectedState))); statesSorted.forEach((s) => stateFilter.options.add(new Option(s.name, s.i, false, s.i == selectedState)));
const cultureFilter = document.getElementById("burgsFilterCulture"); const cultureFilter = document.getElementById('burgsFilterCulture');
const selectedCulture = cultureFilter.value || -1; const selectedCulture = cultureFilter.value || -1;
cultureFilter.options.length = 0; // remove all options cultureFilter.options.length = 0; // remove all options
cultureFilter.options.add(new Option(`all`, -1, false, selectedCulture == -1)); cultureFilter.options.add(new Option(`all`, -1, false, selectedCulture == -1));
cultureFilter.options.add(new Option(pack.cultures[0].name, 0, false, !selectedCulture)); cultureFilter.options.add(new Option(pack.cultures[0].name, 0, false, !selectedCulture));
const culturesSorted = pack.cultures.filter(c => c.i && !c.removed).sort((a, b) => (a.name > b.name) ? 1 : -1); const culturesSorted = pack.cultures.filter((c) => c.i && !c.removed).sort((a, b) => (a.name > b.name ? 1 : -1));
culturesSorted.forEach(c => cultureFilter.options.add(new Option(c.name, c.i, false, c.i == selectedCulture))); culturesSorted.forEach((c) => cultureFilter.options.add(new Option(c.name, c.i, false, c.i == selectedCulture)));
} }
// add line for each burg // add line for each burg
function burgsOverviewAddLines() { function burgsOverviewAddLines() {
const selectedState = +document.getElementById("burgsFilterState").value; const selectedState = +document.getElementById('burgsFilterState').value;
const selectedCulture = +document.getElementById("burgsFilterCulture").value; const selectedCulture = +document.getElementById('burgsFilterCulture').value;
let filtered = pack.burgs.filter(b => b.i && !b.removed); // all valid burgs let filtered = pack.burgs.filter((b) => b.i && !b.removed); // all valid burgs
if (selectedState != -1) filtered = filtered.filter(b => b.state === selectedState); // filtered by state if (selectedState != -1) filtered = filtered.filter((b) => b.state === selectedState); // filtered by state
if (selectedCulture != -1) filtered = filtered.filter(b => b.culture === selectedCulture); // filtered by culture if (selectedCulture != -1) filtered = filtered.filter((b) => b.culture === selectedCulture); // filtered by culture
body.innerHTML = ""; body.innerHTML = '';
let lines = "", totalPopulation = 0; let lines = '',
totalPopulation = 0;
for (const b of filtered) { for (const b of filtered) {
const population = b.population * populationRate.value * urbanization.value; const population = b.population * populationRate.value * urbanization.value;
totalPopulation += population; totalPopulation += population;
const type = b.capital && b.port ? "a-capital-port" : b.capital ? "c-capital" : b.port ? "p-port" : "z-burg"; const type = b.capital && b.port ? 'a-capital-port' : b.capital ? 'c-capital' : b.port ? 'p-port' : 'z-burg';
const state = pack.states[b.state].name; const state = pack.states[b.state].name;
const prov = pack.cells.province[b.cell]; const prov = pack.cells.province[b.cell];
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="${b.name}" data-state="${state}" data-province="${province}" data-culture="${culture}" data-population=${population} data-type="${type}"> lines += `<div class="states" data-id=${b.i} data-name="${b.name}" data-state="${state}" data-province="${province}" data-culture="${culture}" data-population=${population} data-type="${type}">
@ -90,47 +96,47 @@ function overviewBurgs() {
<span data-tip="Remove burg" class="icon-trash-empty"></span> <span data-tip="Remove burg" class="icon-trash-empty"></span>
</div>`; </div>`;
} }
body.insertAdjacentHTML("beforeend", lines); body.insertAdjacentHTML('beforeend', lines);
// update footer // update footer
burgsFooterBurgs.innerHTML = filtered.length; burgsFooterBurgs.innerHTML = filtered.length;
burgsFooterPopulation.innerHTML = filtered.length ? si(totalPopulation / filtered.length) : 0; burgsFooterPopulation.innerHTML = filtered.length ? si(totalPopulation / filtered.length) : 0;
// add listeners // add listeners
body.querySelectorAll("div.states").forEach(el => el.addEventListener("mouseenter", ev => burgHighlightOn(ev))); body.querySelectorAll('div.states').forEach((el) => el.addEventListener('mouseenter', (ev) => burgHighlightOn(ev)));
body.querySelectorAll("div.states").forEach(el => el.addEventListener("mouseleave", ev => burgHighlightOff(ev))); body.querySelectorAll('div.states').forEach((el) => el.addEventListener('mouseleave', (ev) => burgHighlightOff(ev)));
body.querySelectorAll("div > input.burgName").forEach(el => el.addEventListener("input", changeBurgName)); body.querySelectorAll('div > input.burgName').forEach((el) => el.addEventListener('input', changeBurgName));
body.querySelectorAll("div > span.icon-dot-circled").forEach(el => el.addEventListener("click", zoomIntoBurg)); body.querySelectorAll('div > span.icon-dot-circled').forEach((el) => el.addEventListener('click', zoomIntoBurg));
body.querySelectorAll("div > select.stateCulture").forEach(el => el.addEventListener("change", changeBurgCulture)); body.querySelectorAll('div > select.stateCulture').forEach((el) => el.addEventListener('change', changeBurgCulture));
body.querySelectorAll("div > input.burgPopulation").forEach(el => el.addEventListener("change", changeBurgPopulation)); body.querySelectorAll('div > input.burgPopulation').forEach((el) => el.addEventListener('change', changeBurgPopulation));
body.querySelectorAll("div > span.icon-star-empty").forEach(el => el.addEventListener("click", toggleCapitalStatus)); body.querySelectorAll('div > span.icon-star-empty').forEach((el) => el.addEventListener('click', toggleCapitalStatus));
body.querySelectorAll("div > span.icon-anchor").forEach(el => el.addEventListener("click", togglePortStatus)); body.querySelectorAll('div > span.icon-anchor').forEach((el) => el.addEventListener('click', togglePortStatus));
body.querySelectorAll("div > span.locks").forEach(el => el.addEventListener("click", toggleBurgLockStatus)); body.querySelectorAll('div > span.locks').forEach((el) => el.addEventListener('click', toggleBurgLockStatus));
body.querySelectorAll("div > span.locks").forEach(el => el.addEventListener("mouseover", showBurgOLockTip)); body.querySelectorAll('div > span.locks').forEach((el) => el.addEventListener('mouseover', showBurgOLockTip));
body.querySelectorAll("div > span.icon-pencil").forEach(el => el.addEventListener("click", openBurgEditor)); body.querySelectorAll('div > span.icon-pencil').forEach((el) => el.addEventListener('click', openBurgEditor));
body.querySelectorAll("div > span.icon-trash-empty").forEach(el => el.addEventListener("click", triggerBurgRemove)); body.querySelectorAll('div > span.icon-trash-empty').forEach((el) => el.addEventListener('click', triggerBurgRemove));
applySorting(burgsHeader); applySorting(burgsHeader);
} }
function getCultureOptions(culture) { function getCultureOptions(culture) {
let options = ""; let options = '';
pack.cultures.filter(c => !c.removed).forEach(c => options += `<option ${c.i === culture ? "selected" : ""} value="${c.i}">${c.name}</option>`); pack.cultures.filter((c) => !c.removed).forEach((c) => (options += `<option ${c.i === culture ? 'selected' : ''} value="${c.i}">${c.name}</option>`));
return options; return options;
} }
function burgHighlightOn(event) { function burgHighlightOn(event) {
if (!layerIsOn("toggleLabels")) toggleLabels(); if (!layerIsOn('toggleLabels')) toggleLabels();
const burg = +event.target.dataset.id; const burg = +event.target.dataset.id;
burgLabels.select("[data-id='" + burg + "']").classed("drag", true); burgLabels.select("[data-id='" + burg + "']").classed('drag', true);
} }
function burgHighlightOff() { function burgHighlightOff() {
burgLabels.selectAll("text.drag").classed("drag", false); burgLabels.selectAll('text.drag').classed('drag', false);
} }
function changeBurgName() { function changeBurgName() {
if (this.value == "")tip("Please provide a name", false, "error"); if (this.value == '') tip('Please provide a name', false, 'error');
const burg = +this.parentNode.dataset.id; const burg = +this.parentNode.dataset.id;
pack.burgs[burg].name = this.value; pack.burgs[burg].name = this.value;
this.parentNode.dataset.name = this.value; this.parentNode.dataset.name = this.value;
@ -141,7 +147,8 @@ function overviewBurgs() {
function zoomIntoBurg() { function zoomIntoBurg() {
const burg = +this.parentNode.dataset.id; const burg = +this.parentNode.dataset.id;
const label = document.querySelector("#burgLabels [data-id='" + burg + "']"); const label = document.querySelector("#burgLabels [data-id='" + burg + "']");
const x = +label.getAttribute("x"), y = +label.getAttribute("y"); const x = +label.getAttribute('x'),
y = +label.getAttribute('y');
zoomTo(x, y, 8, 2000); zoomTo(x, y, 8, 2000);
} }
@ -154,8 +161,8 @@ function overviewBurgs() {
function changeBurgPopulation() { function changeBurgPopulation() {
const burg = +this.parentNode.dataset.id; const burg = +this.parentNode.dataset.id;
if (this.value == "" || isNaN(+this.value)) { if (this.value == '' || isNaN(+this.value)) {
tip("Please provide an integer number (like 10000, not 10K)", false, "error"); tip('Please provide an integer number (like 10000, not 10K)', false, 'error');
this.value = si(pack.burgs[burg].population * populationRate.value * urbanization.value); this.value = si(pack.burgs[burg].population * populationRate.value * urbanization.value);
return; return;
} }
@ -164,7 +171,7 @@ function overviewBurgs() {
this.value = si(this.value); this.value = si(this.value);
const population = []; const population = [];
body.querySelectorAll(":scope > div").forEach(el => population.push(+getInteger(el.dataset.population))); body.querySelectorAll(':scope > div').forEach((el) => population.push(+getInteger(el.dataset.population)));
burgsFooterPopulation.innerHTML = si(d3.mean(population)); burgsFooterPopulation.innerHTML = si(d3.mean(population));
} }
@ -177,15 +184,22 @@ function overviewBurgs() {
function togglePortStatus() { function togglePortStatus() {
const burg = +this.parentNode.parentNode.dataset.id; const burg = +this.parentNode.parentNode.dataset.id;
togglePort(burg); togglePort(burg);
if (this.classList.contains("inactive")) this.classList.remove("inactive"); if (this.classList.contains('inactive')) this.classList.remove('inactive');
else this.classList.add("inactive"); else this.classList.add('inactive');
} }
function toggleBurgLockStatus() { function toggleBurgLockStatus() {
const burg = +this.parentNode.dataset.id; const burg = +this.parentNode.dataset.id;
toggleBurgLock(burg); toggleBurgLock(burg);
if (this.classList.contains("icon-lock")) {this.classList.remove("icon-lock"); this.classList.add("icon-lock-open"); this.classList.add("inactive");} if (this.classList.contains('icon-lock')) {
else {this.classList.remove("icon-lock-open"); this.classList.add("icon-lock"); this.classList.remove("inactive");} this.classList.remove('icon-lock');
this.classList.add('icon-lock-open');
this.classList.add('inactive');
} else {
this.classList.remove('icon-lock-open');
this.classList.add('icon-lock');
this.classList.remove('inactive');
}
} }
function showBurgOLockTip() { function showBurgOLockTip() {
@ -200,29 +214,27 @@ function overviewBurgs() {
function triggerBurgRemove() { function triggerBurgRemove() {
const burg = +this.parentNode.dataset.id; const burg = +this.parentNode.dataset.id;
if (pack.burgs[burg].capital) {tip("You cannot remove the capital. Please change the capital first", false, "error"); return;} if (pack.burgs[burg].capital) {
tip('You cannot remove the capital. Please change the capital first', false, 'error');
return;
}
alertMessage.innerHTML = "Are you sure you want to remove the burg?"; const message = 'Are you sure you want to remove the burg? <br>This action cannot be reverted';
$("#alert").dialog({resizable: false, title: "Remove burg", const onConfirm = () => {
buttons: { removeBurg(burg);
Remove: function() { burgsOverviewAddLines();
$(this).dialog("close"); };
removeBurg(burg); confirmationDialog({title: 'Remove burg', message, confirm: 'Remove', onConfirm});
burgsOverviewAddLines();
},
Cancel: function() {$(this).dialog("close");}
}
});
} }
function regenerateNames() { function regenerateNames() {
body.querySelectorAll(":scope > div").forEach(function(el) { body.querySelectorAll(':scope > div').forEach(function (el) {
const burg = +el.dataset.id; const burg = +el.dataset.id;
//if (pack.burgs[burg].lock) return; //if (pack.burgs[burg].lock) return;
const culture = pack.burgs[burg].culture; const culture = pack.burgs[burg].culture;
const name = Names.getCulture(culture); const name = Names.getCulture(culture);
if (!pack.burgs[burg].lock) { if (!pack.burgs[burg].lock) {
el.querySelector(".burgName").value = name; el.querySelector('.burgName').value = name;
pack.burgs[burg].name = el.dataset.name = name; pack.burgs[burg].name = el.dataset.name = name;
burgLabels.select("[data-id='" + burg + "']").text(name); burgLabels.select("[data-id='" + burg + "']").text(name);
} }
@ -230,18 +242,27 @@ function overviewBurgs() {
} }
function enterAddBurgMode() { function enterAddBurgMode() {
if (this.classList.contains("pressed")) {exitAddBurgMode(); return;}; if (this.classList.contains('pressed')) {
exitAddBurgMode();
return;
}
customization = 3; customization = 3;
this.classList.add("pressed"); this.classList.add('pressed');
tip("Click on the map to create a new burg. Hold Shift to add multiple", true, "warn"); tip('Click on the map to create a new burg. Hold Shift to add multiple', true, 'warn');
viewbox.style("cursor", "crosshair").on("click", addBurgOnClick); viewbox.style('cursor', 'crosshair').on('click', addBurgOnClick);
} }
function addBurgOnClick() { function addBurgOnClick() {
const point = d3.mouse(this); const point = d3.mouse(this);
const cell = findCell(point[0], point[1]); const cell = findCell(point[0], point[1]);
if (pack.cells.h[cell] < 20) {tip("You cannot place state into the water. Please click on a land cell", false, "error"); return;} if (pack.cells.h[cell] < 20) {
if (pack.cells.burg[cell]) {tip("There is already a burg in this cell. Please select a free cell", false, "error"); return;} tip('You cannot place state into the water. Please click on a land cell', false, 'error');
return;
}
if (pack.cells.burg[cell]) {
tip('There is already a burg in this cell. Please select a free cell', false, 'error');
return;
}
addBurg(point); // add new burg addBurg(point); // add new burg
if (d3.event.shiftKey === false) { if (d3.event.shiftKey === false) {
@ -254,31 +275,37 @@ function overviewBurgs() {
customization = 0; customization = 0;
restoreDefaultEvents(); restoreDefaultEvents();
clearMainTip(); clearMainTip();
if (addBurgTool.classList.contains("pressed")) addBurgTool.classList.remove("pressed"); if (addBurgTool.classList.contains('pressed')) addBurgTool.classList.remove('pressed');
if (addNewBurg.classList.contains("pressed")) addNewBurg.classList.remove("pressed"); if (addNewBurg.classList.contains('pressed')) addNewBurg.classList.remove('pressed');
} }
function showBurgsChart() { function showBurgsChart() {
// build hierarchy tree // build hierarchy tree
const states = pack.states.map(s => { const states = pack.states.map((s) => {
const color = s.color ? s.color : "#ccc"; const color = s.color ? s.color : '#ccc';
const name = s.fullName ? s.fullName : s.name; const name = s.fullName ? s.fullName : s.name;
return {id:s.i, state: s.i ? 0 : null, color, name} return {id: s.i, state: s.i ? 0 : null, color, name};
});
const burgs = pack.burgs.filter(b => b.i && !b.removed).map(b => {
const id = b.i+states.length-1;
const population = b.population;
const capital = b.capital;
const province = pack.cells.province[b.cell];
const parent = province ? province + states.length-1 : b.state;
return {id, i:b.i, state:b.state, culture:b.culture, province, parent, name:b.name, population, capital, x:b.x, y:b.y}
}); });
const burgs = pack.burgs
.filter((b) => b.i && !b.removed)
.map((b) => {
const id = b.i + states.length - 1;
const population = b.population;
const capital = b.capital;
const province = pack.cells.province[b.cell];
const parent = province ? province + states.length - 1 : b.state;
return {id, i: b.i, state: b.state, culture: b.culture, province, parent, name: b.name, population, capital, x: b.x, y: b.y};
});
const data = states.concat(burgs); const data = states.concat(burgs);
const root = d3.stratify().parentId(d => d.state)(data) const root = d3
.sum(d => d.population).sort((a, b) => b.value - a.value); .stratify()
.parentId((d) => d.state)(data)
.sum((d) => d.population)
.sort((a, b) => b.value - a.value);
const width = 150 + 200 * uiSizeOutput.value, height = 150 + 200 * uiSizeOutput.value; const width = 150 + 200 * uiSizeOutput.value,
height = 150 + 200 * uiSizeOutput.value;
const margin = {top: 0, right: -50, bottom: -10, left: -50}; const margin = {top: 0, right: -50, bottom: -10, left: -50};
const w = width - margin.left - margin.right; const w = width - margin.left - margin.right;
const h = height - margin.top - margin.bottom; const h = height - margin.top - margin.bottom;
@ -291,130 +318,155 @@ function overviewBurgs() {
<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.select("#alertMessage").insert("svg", "#burgsInfo").attr("id", "burgsTree") const svg = d3
.attr("width", width).attr("height", height-10).attr("stroke-width", 2); .select('#alertMessage')
const graph = svg.append("g").attr("transform", `translate(-50, -10)`); .insert('svg', '#burgsInfo')
document.getElementById("burgsTreeType").addEventListener("change", updateChart); .attr('id', 'burgsTree')
.attr('width', width)
.attr('height', height - 10)
.attr('stroke-width', 2);
const graph = svg.append('g').attr('transform', `translate(-50, -10)`);
document.getElementById('burgsTreeType').addEventListener('change', updateChart);
treeLayout(root); treeLayout(root);
const node = graph.selectAll("circle").data(root.leaves()) const node = graph
.join("circle").attr("data-id", d => d.data.i) .selectAll('circle')
.attr("r", d => d.r).attr("fill", d => d.parent.data.color) .data(root.leaves())
.attr("cx", d => d.x).attr("cy", d => d.y) .join('circle')
.on("mouseenter", d => showInfo(event, d)) .attr('data-id', (d) => d.data.i)
.on("mouseleave", d => hideInfo(event, d)) .attr('r', (d) => d.r)
.on("click", d => zoomTo(d.data.x, d.data.y, 8, 2000)); .attr('fill', (d) => d.parent.data.color)
.attr('cx', (d) => d.x)
.attr('cy', (d) => d.y)
.on('mouseenter', (d) => showInfo(event, d))
.on('mouseleave', (d) => hideInfo(event, d))
.on('click', (d) => zoomTo(d.data.x, d.data.y, 8, 2000));
function showInfo(ev, d) { function showInfo(ev, d) {
d3.select(ev.target).transition().duration(1500).attr("stroke", "#c13119"); d3.select(ev.target).transition().duration(1500).attr('stroke', '#c13119');
const name = d.data.name; const name = d.data.name;
const parent = d.parent.data.name; const parent = d.parent.data.name;
const population = si(d.value * populationRate.value * urbanization.value); const population = si(d.value * populationRate.value * urbanization.value);
burgsInfo.innerHTML = `${name}. ${parent}. Population: ${population}`; burgsInfo.innerHTML = `${name}. ${parent}. Population: ${population}`;
burgHighlightOn(ev); burgHighlightOn(ev);
tip("Click to zoom into view"); tip('Click to zoom into view');
} }
function hideInfo(ev) { function hideInfo(ev) {
burgHighlightOff(ev); burgHighlightOff(ev);
if (!document.getElementById("burgsInfo")) return; if (!document.getElementById('burgsInfo')) return;
burgsInfo.innerHTML = "&#8205;"; burgsInfo.innerHTML = '&#8205;';
d3.select(ev.target).transition().attr("stroke", null); d3.select(ev.target).transition().attr('stroke', null);
tip(""); tip('');
} }
function updateChart() { function updateChart() {
const getStatesData = () => pack.states.map(s => { const getStatesData = () =>
const color = s.color ? s.color : "#ccc"; pack.states.map((s) => {
const name = s.fullName ? s.fullName : s.name; const color = s.color ? s.color : '#ccc';
return {id:s.i, state: s.i ? 0 : null, color, name} const name = s.fullName ? s.fullName : s.name;
}); return {id: s.i, state: s.i ? 0 : null, color, name};
});
const getCulturesData = () => pack.cultures.map(c => { const getCulturesData = () =>
const color = c.color ? c.color : "#ccc"; pack.cultures.map((c) => {
return {id:c.i, culture: c.i ? 0 : null, color, name:c.name} const color = c.color ? c.color : '#ccc';
}); return {id: c.i, culture: c.i ? 0 : null, color, name: c.name};
});
const getParentData = () => { const getParentData = () => {
const states = pack.states.map(s => { const states = pack.states.map((s) => {
const color = s.color ? s.color : "#ccc"; const color = s.color ? s.color : '#ccc';
const name = s.fullName ? s.fullName : s.name; const name = s.fullName ? s.fullName : s.name;
return {id:s.i, parent: s.i ? 0 : null, color, name} return {id: s.i, parent: s.i ? 0 : null, color, name};
});
const provinces = pack.provinces.filter(p => p.i && !p.removed).map(p => {
return {id:p.i + states.length-1, parent: p.state, color:p.color, name:p.fullName}
}); });
const provinces = pack.provinces
.filter((p) => p.i && !p.removed)
.map((p) => {
return {id: p.i + states.length - 1, parent: p.state, color: p.color, name: p.fullName};
});
return states.concat(provinces); return states.concat(provinces);
} };
const getProvincesData = () => pack.provinces.map(p => { const getProvincesData = () =>
const color = p.color ? p.color : "#ccc"; pack.provinces.map((p) => {
const name = p.fullName ? p.fullName : p.name; const color = p.color ? p.color : '#ccc';
return {id:p.i ? p.i : 0, province: p.i ? 0 : null, color, name} const name = p.fullName ? p.fullName : p.name;
}); return {id: p.i ? p.i : 0, province: p.i ? 0 : null, color, name};
});
const value = d => { const value = (d) => {
if (this.value === "states") return d.state; if (this.value === 'states') return d.state;
if (this.value === "cultures") return d.culture; if (this.value === 'cultures') return d.culture;
if (this.value === "parent") return d.parent; if (this.value === 'parent') return d.parent;
if (this.value === "provinces") return d.province; if (this.value === 'provinces') return d.province;
} };
const base = this.value === "states" ? getStatesData() const base = this.value === 'states' ? getStatesData() : this.value === 'cultures' ? getCulturesData() : this.value === 'parent' ? getParentData() : getProvincesData();
: this.value === "cultures" ? getCulturesData() burgs.forEach((b) => (b.id = b.i + base.length - 1));
: this.value === "parent" ? getParentData() : getProvincesData();
burgs.forEach(b => b.id = b.i+base.length-1);
const data = base.concat(burgs); const data = base.concat(burgs);
const root = d3.stratify().parentId(d => value(d))(data) const root = d3
.sum(d => d.population).sort((a, b) => b.value - a.value); .stratify()
.parentId((d) => value(d))(data)
.sum((d) => d.population)
.sort((a, b) => b.value - a.value);
node.data(treeLayout(root).leaves()).transition().duration(2000) node
.attr("data-id", d => d.data.i).attr("fill", d => d.parent.data.color) .data(treeLayout(root).leaves())
.attr("cx", d => d.x).attr("cy", d => d.y).attr("r", d => d.r); .transition()
.duration(2000)
.attr('data-id', (d) => d.data.i)
.attr('fill', (d) => d.parent.data.color)
.attr('cx', (d) => d.x)
.attr('cy', (d) => d.y)
.attr('r', (d) => d.r);
} }
$("#alert").dialog({ $('#alert').dialog({
title: "Burgs bubble chart", width: fitContent(), title: 'Burgs bubble chart',
position: {my: "left bottom", at: "left+10 bottom-10", of: "svg"}, buttons: {}, width: fitContent(),
close: () => {alertMessage.innerHTML = "";} position: {my: 'left bottom', at: 'left+10 bottom-10', of: 'svg'},
buttons: {},
close: () => {
alertMessage.innerHTML = '';
}
}); });
} }
function downloadBurgsData() { function downloadBurgsData() {
let data = "Id,Burg,Province,State,Culture,Religion,Population,Longitude,Latitude,Elevation ("+heightUnit.value+"),Capital,Port,Citadel,Walls,Plaza,Temple,Shanty Town\n"; // headers let data = 'Id,Burg,Province,State,Culture,Religion,Population,Longitude,Latitude,Elevation (' + heightUnit.value + '),Capital,Port,Citadel,Walls,Plaza,Temple,Shanty Town\n'; // headers
const valid = pack.burgs.filter(b => b.i && !b.removed); // all valid burgs const valid = pack.burgs.filter((b) => b.i && !b.removed); // all valid burgs
valid.forEach(b => { valid.forEach((b) => {
data += b.i + ","; data += b.i + ',';
data += b.name + ","; data += b.name + ',';
const province = pack.cells.province[b.cell]; const province = pack.cells.province[b.cell];
data += province ? pack.provinces[province].fullName + "," : ","; data += province ? pack.provinces[province].fullName + ',' : ',';
data += b.state ? pack.states[b.state].fullName +"," : pack.states[b.state].name + ","; data += b.state ? pack.states[b.state].fullName + ',' : pack.states[b.state].name + ',';
data += pack.cultures[b.culture].name + ","; data += pack.cultures[b.culture].name + ',';
data += pack.religions[pack.cells.religion[b.cell]].name + ","; data += pack.religions[pack.cells.religion[b.cell]].name + ',';
data += rn(b.population * populationRate.value * urbanization.value) + ","; data += rn(b.population * populationRate.value * urbanization.value) + ',';
// add geography data // add geography data
data += mapCoordinates.lonW + (b.x / graphWidth) * mapCoordinates.lonT + ","; data += mapCoordinates.lonW + (b.x / graphWidth) * mapCoordinates.lonT + ',';
data += mapCoordinates.latN - (b.y / graphHeight) * mapCoordinates.latT + ","; // this is inverted in QGIS otherwise data += mapCoordinates.latN - (b.y / graphHeight) * mapCoordinates.latT + ','; // this is inverted in QGIS otherwise
data += parseInt(getHeight(pack.cells.h[b.cell])) + ","; data += parseInt(getHeight(pack.cells.h[b.cell])) + ',';
// add status data // add status data
data += b.capital ? "capital," : ","; data += b.capital ? 'capital,' : ',';
data += b.port ? "port," : ","; data += b.port ? 'port,' : ',';
data += b.citadel ? "citadel," : ","; data += b.citadel ? 'citadel,' : ',';
data += b.walls ? "walls," : ","; data += b.walls ? 'walls,' : ',';
data += b.plaza ? "plaza," : ","; data += b.plaza ? 'plaza,' : ',';
data += b.temple ? "temple," : ","; data += b.temple ? 'temple,' : ',';
data += b.shanty ? "shanty town\n" : "\n"; data += b.shanty ? 'shanty town\n' : '\n';
}); });
const name = getFileName("Burgs") + ".csv"; const name = getFileName('Burgs') + '.csv';
downloadFile(data, name); downloadFile(data, name);
} }
@ -423,49 +475,67 @@ function overviewBurgs() {
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`;
alertMessage.innerHTML = message; alertMessage.innerHTML = message;
$("#alert").dialog({title: "Burgs bulk renaming", width:"22em", $('#alert').dialog({
position: {my: "center", at: "center", of: "svg"}, title: 'Burgs bulk renaming',
width: '22em',
position: {my: 'center', at: 'center', of: 'svg'},
buttons: { buttons: {
Download: function() { Download: function () {
const data = pack.burgs.filter(b => b.i && !b.removed).map(b => b.name).join("\r\n"); const data = pack.burgs
const name = getFileName("Burg names") + ".txt"; .filter((b) => b.i && !b.removed)
.map((b) => b.name)
.join('\r\n');
const name = getFileName('Burg names') + '.txt';
downloadFile(data, name); downloadFile(data, name);
}, },
Upload: () => burgsListToLoad.click(), Upload: () => burgsListToLoad.click(),
Cancel: function() {$(this).dialog("close");} Cancel: function () {
$(this).dialog('close');
}
} }
}); });
} }
function importBurgNames(dataLoaded) { function importBurgNames(dataLoaded) {
if (!dataLoaded) {tip("Cannot load the file, please check the format", false, "error"); return;} if (!dataLoaded) {
const data = dataLoaded.split("\r\n"); tip('Cannot load the file, please check the format', false, 'error');
if (!data.length) {tip("Cannot parse the list, please check the file format", false, "error"); return;} return;
}
const data = dataLoaded.split('\r\n');
if (!data.length) {
tip('Cannot parse the list, please check the file format', false, 'error');
return;
}
let change = [], message = `Burgs will be renamed as below. Please confirm`; let change = [],
message = `Burgs will be renamed as below. Please confirm`;
message += `<table class="overflow-table"><tr><th>Id</th><th>Current name</th><th>New Name</th></tr>`; message += `<table class="overflow-table"><tr><th>Id</th><th>Current name</th><th>New Name</th></tr>`;
const burgs = pack.burgs.filter(b => b.i && !b.removed); const burgs = pack.burgs.filter((b) => b.i && !b.removed);
for (let i=0; i < data.length && i <= burgs.length; i++) { for (let i = 0; i < data.length && i <= burgs.length; i++) {
const v = data[i]; const v = data[i];
if (!v || !burgs[i] || v == burgs[i].name) continue; if (!v || !burgs[i] || v == burgs[i].name) continue;
change.push({id:burgs[i].i, name: v}); change.push({id: burgs[i].i, name: v});
message += `<tr><td style="width:20%">${burgs[i].i}</td><td style="width:40%">${burgs[i].name}</td><td style="width:40%">${v}</td></tr>`; message += `<tr><td style="width:20%">${burgs[i].i}</td><td style="width:40%">${burgs[i].name}</td><td style="width:40%">${v}</td></tr>`;
} }
message += `</tr></table>`; message += `</tr></table>`;
if (!change.length) message = "No changes found in the file. Please change some names to get a result" if (!change.length) message = 'No changes found in the file. Please change some names to get a result';
alertMessage.innerHTML = message; alertMessage.innerHTML = message;
$("#alert").dialog({title: "Burgs bulk renaming", width:"22em", $('#alert').dialog({
position: {my: "center", at: "center", of: "svg"}, title: 'Burgs bulk renaming',
width: '22em',
position: {my: 'center', at: 'center', of: 'svg'},
buttons: { buttons: {
Cancel: function() {$(this).dialog("close");}, Cancel: function () {
Confirm: function() { $(this).dialog('close');
for (let i=0; i < change.length; i++) { },
Confirm: function () {
for (let i = 0; i < change.length; i++) {
const id = change[i].id; const id = change[i].id;
pack.burgs[id].name = change[i].name; pack.burgs[id].name = change[i].name;
burgLabels.select("[data-id='" + id + "']").text(change[i].name); burgLabels.select("[data-id='" + id + "']").text(change[i].name);
} }
$(this).dialog("close"); $(this).dialog('close');
burgsOverviewAddLines(); burgsOverviewAddLines();
} }
} }
@ -473,21 +543,12 @@ function overviewBurgs() {
} }
function triggerAllBurgsRemove() { function triggerAllBurgsRemove() {
alertMessage.innerHTML = `Are you sure you want to remove all unlocked burgs except for capitals? const message = 'Are you sure you want to remove all unlocked burgs except for capitals?<br><i>To remove a capital you have to remove the state first</i>';
<br><i>To remove a capital you have to remove a state first</i>`; confirmationDialog({title: 'Remove all burgs', message, confirm: 'Remove', onConfirm: removeAllBurgs});
$("#alert").dialog({resizable: false, title: "Remove all burgs",
buttons: {
Remove: function() {
$(this).dialog("close");
removeAllBurgs();
},
Cancel: function() {$(this).dialog("close");}
}
});
} }
function removeAllBurgs() { function removeAllBurgs() {
pack.burgs.filter(b => b.i && !(b.capital || b.lock)).forEach(b => removeBurg(b.i)); pack.burgs.filter((b) => b.i && !(b.capital || b.lock)).forEach((b) => removeBurg(b.i));
burgsOverviewAddLines(); burgsOverviewAddLines();
} }
} }

View file

@ -1,96 +1,118 @@
"use strict"; 'use strict';
function editCoastline(node = d3.event.target) { function editCoastline(node = d3.event.target) {
if (customization) return; if (customization) return;
closeDialogs(".stable"); closeDialogs('.stable');
if (layerIsOn("toggleCells")) toggleCells(); if (layerIsOn('toggleCells')) toggleCells();
$("#coastlineEditor").dialog({ $('#coastlineEditor').dialog({
title: "Edit Coastline", resizable: false, title: 'Edit Coastline',
position: {my: "center top+20", at: "top", of: d3.event, collision: "fit"}, resizable: false,
position: {my: 'center top+20', at: 'top', of: d3.event, collision: 'fit'},
close: closeCoastlineEditor close: closeCoastlineEditor
}); });
debug.append("g").attr("id", "vertices"); debug.append('g').attr('id', 'vertices');
elSelected = d3.select(node); elSelected = d3.select(node);
selectCoastlineGroup(node); selectCoastlineGroup(node);
drawCoastlineVertices(); drawCoastlineVertices();
viewbox.on("touchmove mousemove", null); viewbox.on('touchmove mousemove', null);
if (modules.editCoastline) return; if (modules.editCoastline) return;
modules.editCoastline = true; modules.editCoastline = true;
// add listeners // add listeners
document.getElementById("coastlineGroupsShow").addEventListener("click", showGroupSection); document.getElementById('coastlineGroupsShow').addEventListener('click', showGroupSection);
document.getElementById("coastlineGroup").addEventListener("change", changeCoastlineGroup); document.getElementById('coastlineGroup').addEventListener('change', changeCoastlineGroup);
document.getElementById("coastlineGroupAdd").addEventListener("click", toggleNewGroupInput); document.getElementById('coastlineGroupAdd').addEventListener('click', toggleNewGroupInput);
document.getElementById("coastlineGroupName").addEventListener("change", createNewGroup); document.getElementById('coastlineGroupName').addEventListener('change', createNewGroup);
document.getElementById("coastlineGroupRemove").addEventListener("click", removeCoastlineGroup); document.getElementById('coastlineGroupRemove').addEventListener('click', removeCoastlineGroup);
document.getElementById("coastlineGroupsHide").addEventListener("click", hideGroupSection); document.getElementById('coastlineGroupsHide').addEventListener('click', hideGroupSection);
document.getElementById("coastlineEditStyle").addEventListener("click", editGroupStyle); document.getElementById('coastlineEditStyle').addEventListener('click', editGroupStyle);
function drawCoastlineVertices() { function drawCoastlineVertices() {
const f = +elSelected.attr("data-f"); // feature id const f = +elSelected.attr('data-f'); // feature id
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')
.on("mousemove", () => tip("Drag to move the vertex, please use for fine-tuning only. Edit heightmap to change actual cell heights")); .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'));
const unit = areaUnit.value === "square" ? " " + distanceUnitInput.value + "²" : " " + areaUnit.value; const unit = areaUnit.value === 'square' ? ' ' + distanceUnitInput.value + '²' : ' ' + areaUnit.value;
const area = pack.features[f].area; const area = pack.features[f].area;
coastlineArea.innerHTML = si(area * distanceScaleInput.value ** 2) + unit; coastlineArea.innerHTML = si(area * distanceScaleInput.value ** 2) + unit;
} }
function dragVertex() { function dragVertex() {
const x = rn(d3.event.x, 2), y = rn(d3.event.y, 2); const x = rn(d3.event.x, 2),
this.setAttribute("cx", x); y = rn(d3.event.y, 2);
this.setAttribute("cy", y); this.setAttribute('cx', x);
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();
} }
function redrawCoastline() { function redrawCoastline() {
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));
coastlineArea.innerHTML = si(area * distanceScaleInput.value ** 2) + unit; coastlineArea.innerHTML = si(area * distanceScaleInput.value ** 2) + unit;
} }
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 = '';
document.getElementById("coastlineGroup").style.display = "inline-block"; document.getElementById('coastlineGroup').style.display = 'inline-block';
} }
function selectCoastlineGroup(node) { function selectCoastlineGroup(node) {
const group = node.parentNode.id; const group = node.parentNode.id;
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));
}); });
} }
@ -100,88 +122,89 @@ function editCoastline(node = d3.event.target) {
} }
function toggleNewGroupInput() { function toggleNewGroupInput() {
if (coastlineGroupName.style.display === "none") { if (coastlineGroupName.style.display === 'none') {
coastlineGroupName.style.display = "inline-block"; coastlineGroupName.style.display = 'inline-block';
coastlineGroupName.focus(); coastlineGroupName.focus();
coastlineGroup.style.display = "none"; coastlineGroup.style.display = 'none';
} else { } else {
coastlineGroupName.style.display = "none"; coastlineGroupName.style.display = 'none';
coastlineGroup.style.display = "inline-block"; coastlineGroup.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');
return; return;
} }
if (Number.isFinite(+group.charAt(0))) { if (Number.isFinite(+group.charAt(0))) {
tip("Group name should start with a letter", false, "error"); tip('Group name should start with a letter', false, 'error');
return; return;
} }
// just rename if only 1 element left // just rename if only 1 element left
const oldGroup = elSelected.node().parentNode; const oldGroup = elSelected.node().parentNode;
const basic = ["sea_island", "lake_island"].includes(oldGroup.id); const basic = ['sea_island', 'lake_island'].includes(oldGroup.id);
if (!basic && oldGroup.childElementCount === 1) { if (!basic && oldGroup.childElementCount === 1) {
document.getElementById("coastlineGroup").selectedOptions[0].remove(); document.getElementById('coastlineGroup').selectedOptions[0].remove();
document.getElementById("coastlineGroup").options.add(new Option(group, group, false, true)); document.getElementById('coastlineGroup').options.add(new Option(group, group, false, true));
oldGroup.id = group; oldGroup.id = group;
toggleNewGroupInput(); toggleNewGroupInput();
document.getElementById("coastlineGroupName").value = ""; document.getElementById('coastlineGroupName').value = '';
return; return;
} }
// create a new group // create a new group
const newGroup = elSelected.node().parentNode.cloneNode(false); const newGroup = elSelected.node().parentNode.cloneNode(false);
document.getElementById("coastline").appendChild(newGroup); document.getElementById('coastline').appendChild(newGroup);
newGroup.id = group; newGroup.id = group;
document.getElementById("coastlineGroup").options.add(new Option(group, group, false, true)); document.getElementById('coastlineGroup').options.add(new Option(group, group, false, true));
document.getElementById(group).appendChild(elSelected.node()); document.getElementById(group).appendChild(elSelected.node());
toggleNewGroupInput(); toggleNewGroupInput();
document.getElementById("coastlineGroupName").value = ""; document.getElementById('coastlineGroupName').value = '';
} }
function removeCoastlineGroup() { function removeCoastlineGroup() {
const group = elSelected.node().parentNode.id; const group = elSelected.node().parentNode.id;
if (["sea_island", "lake_island"].includes(group)) { if (['sea_island', 'lake_island'].includes(group)) {
tip("This is one of the default groups, it cannot be removed", false, "error"); tip('This is one of the default groups, it cannot be removed', false, 'error');
return; return;
} }
const count = elSelected.node().parentNode.childElementCount; const count = elSelected.node().parentNode.childElementCount;
alertMessage.innerHTML = `Are you sure you want to remove the group?
All coastline elements of the group (${count}) will be moved under <i>sea_island</i> group`; const message = `Are you sure you want to remove the group? <br>All coastline elements of the group (${count}) will be moved under <i>sea_island</i> group`;
$("#alert").dialog({resizable: false, title: "Remove coastline group", width:"26em", const onConfirm = () => {
buttons: { const sea = document.getElementById('sea_island');
Remove: function() { const groupEl = document.getElementById(group);
$(this).dialog("close"); while (groupEl.childNodes.length) {
const sea = document.getElementById("sea_island"); sea.appendChild(groupEl.childNodes[0]);
const groupEl = document.getElementById(group);
while (groupEl.childNodes.length) {
sea.appendChild(groupEl.childNodes[0]);
}
groupEl.remove();
document.getElementById("coastlineGroup").selectedOptions[0].remove();
document.getElementById("coastlineGroup").value = "sea_island";
},
Cancel: function() {$(this).dialog("close");}
} }
}); groupEl.remove();
document.getElementById('coastlineGroup').selectedOptions[0].remove();
document.getElementById('coastlineGroup').value = 'sea_island';
};
confirmationDialog({title: 'Remove coastline group', message, confirm: 'Remove', onConfirm});
} }
function editGroupStyle() { function editGroupStyle() {
const g = elSelected.node().parentNode.id; const g = elSelected.node().parentNode.id;
editStyle("coastline", g); editStyle('coastline', g);
} }
function closeCoastlineEditor() { function closeCoastlineEditor() {
debug.select("#vertices").remove(); debug.select('#vertices').remove();
unselect(); unselect();
} }
} }

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -1,19 +1,20 @@
"use strict"; 'use strict';
function editIce() { function editIce() {
if (customization) return; if (customization) return;
closeDialogs(".stable"); closeDialogs('.stable');
if (!layerIsOn("toggleIce")) toggleIce(); if (!layerIsOn('toggleIce')) toggleIce();
elSelected = d3.select(d3.event.target); elSelected = d3.select(d3.event.target);
const type = elSelected.attr("type") ? "Glacier" : "Iceberg"; const type = elSelected.attr('type') ? 'Glacier' : 'Iceberg';
document.getElementById("iceRandomize").style.display = type === "Glacier" ? "none" : "inline-block"; document.getElementById('iceRandomize').style.display = type === 'Glacier' ? 'none' : 'inline-block';
document.getElementById("iceSize").style.display = type === "Glacier" ? "none" : "inline-block"; document.getElementById('iceSize').style.display = type === 'Glacier' ? 'none' : 'inline-block';
if (type === "Iceberg") document.getElementById("iceSize").value = +elSelected.attr("size"); if (type === 'Iceberg') document.getElementById('iceSize').value = +elSelected.attr('size');
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,
position: {my: "center top+60", at: "top", of: d3.event, collision: "fit"}, resizable: false,
position: {my: 'center top+60', at: 'top', of: d3.event, collision: 'fit'},
close: closeEditor close: closeEditor
}); });
@ -21,41 +22,45 @@ function editIce() {
modules.editIce = true; modules.editIce = true;
// add listeners // add listeners
document.getElementById("iceEditStyle").addEventListener("click", () => editStyle("ice")); document.getElementById('iceEditStyle').addEventListener('click', () => editStyle('ice'));
document.getElementById("iceRandomize").addEventListener("click", randomizeShape); document.getElementById('iceRandomize').addEventListener('click', randomizeShape);
document.getElementById("iceSize").addEventListener("input", changeSize); document.getElementById('iceSize').addEventListener('input', changeSize);
document.getElementById("iceNew").addEventListener("click", toggleAdd); document.getElementById('iceNew').addEventListener('click', toggleAdd);
document.getElementById("iceRemove").addEventListener("click", removeIce); document.getElementById('iceRemove').addEventListener('click', removeIce);
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 points = poly.map(p => [rn(c[0] + p[0] * s, 2), rn(c[1] + p[1] * s, 2)]); const poly = getGridPolygon(i).map((p) => [p[0] - cn[0], p[1] - cn[1]]);
elSelected.attr("points", points); const points = poly.map((p) => [rn(c[0] + p[0] * s, 2), rn(c[1] + p[1] * s, 2)]);
elSelected.attr('points', points);
} }
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);
} }
function toggleAdd() { function toggleAdd() {
document.getElementById("iceNew").classList.toggle("pressed"); document.getElementById('iceNew').classList.toggle('pressed');
if (document.getElementById("iceNew").classList.contains("pressed")) { if (document.getElementById('iceNew').classList.contains('pressed')) {
viewbox.style("cursor", "crosshair").on("click", addIcebergOnClick); viewbox.style('cursor', 'crosshair').on('click', addIcebergOnClick);
tip("Click on map to create an iceberg. Hold Shift to add multiple", true); tip('Click on map to create an iceberg. Hold Shift to add multiple', true);
} else { } else {
clearMainTip(); clearMainTip();
viewbox.on("click", clicked).style("cursor", "default"); viewbox.on('click', clicked).style('cursor', 'default');
} }
} }
@ -63,43 +68,41 @@ function editIce() {
const point = d3.mouse(this); const point = d3.mouse(this);
const i = findGridCell(point[0], point[1]); const i = findGridCell(point[0], point[1]);
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();
} }
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}?`;
$("#alert").dialog({resizable: false, title: "Remove "+type, const message = `Are you sure you want to remove the ${type}? <br>This action cannot be reverted`;
buttons: { const onConfirm = () => {
Remove: function() { elSelected.remove();
$(this).dialog("close"); $('#iceEditor').dialog('close');
elSelected.remove(); };
$("#iceEditor").dialog("close"); confirmationDialog({title: 'Remove ' + type, message, confirm: 'Remove', onConfirm});
},
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})`);
}); });
} }
function closeEditor() { function closeEditor() {
ice.selectAll("*").classed("draggable", false).call(d3.drag().on("drag", null)); ice.selectAll('*').classed('draggable', false).call(d3.drag().on('drag', null));
clearMainTip(); clearMainTip();
iceNew.classList.remove("pressed"); iceNew.classList.remove('pressed');
unselect(); unselect();
} }
} }

View file

@ -1,18 +1,20 @@
"use strict"; 'use strict';
function editLabel() { function editLabel() {
if (customization) return; if (customization) return;
closeDialogs(); closeDialogs();
if (!layerIsOn("toggleLabels")) toggleLabels(); if (!layerIsOn('toggleLabels')) toggleLabels();
const tspan = d3.event.target; const tspan = d3.event.target;
const textPath = tspan.parentNode; const textPath = tspan.parentNode;
const text = textPath.parentNode; const text = textPath.parentNode;
elSelected = d3.select(text).call(d3.drag().on("start", dragLabel)).classed("draggable", true); elSelected = d3.select(text).call(d3.drag().on('start', dragLabel)).classed('draggable', true);
viewbox.on("touchmove mousemove", showEditorTips); viewbox.on('touchmove mousemove', showEditorTips);
$("#labelEditor").dialog({ $('#labelEditor').dialog({
title: "Edit Label", resizable: false, width: fitContent(), title: 'Edit Label',
position: {my: "center top+10", at: "bottom", of: text, collision: "fit"}, resizable: false,
width: fitContent(),
position: {my: 'center top+10', at: 'bottom', of: text, collision: 'fit'},
close: closeLabelEditor close: closeLabelEditor
}); });
@ -24,89 +26,99 @@ function editLabel() {
modules.editLabel = true; modules.editLabel = true;
// add listeners // add listeners
document.getElementById("labelGroupShow").addEventListener("click", showGroupSection); document.getElementById('labelGroupShow').addEventListener('click', showGroupSection);
document.getElementById("labelGroupHide").addEventListener("click", hideGroupSection); document.getElementById('labelGroupHide').addEventListener('click', hideGroupSection);
document.getElementById("labelGroupSelect").addEventListener("click", changeGroup); document.getElementById('labelGroupSelect').addEventListener('click', changeGroup);
document.getElementById("labelGroupInput").addEventListener("change", createNewGroup); document.getElementById('labelGroupInput').addEventListener('change', createNewGroup);
document.getElementById("labelGroupNew").addEventListener("click", toggleNewGroupInput); document.getElementById('labelGroupNew').addEventListener('click', toggleNewGroupInput);
document.getElementById("labelGroupRemove").addEventListener("click", removeLabelsGroup); document.getElementById('labelGroupRemove').addEventListener('click', removeLabelsGroup);
document.getElementById("labelTextShow").addEventListener("click", showTextSection); document.getElementById('labelTextShow').addEventListener('click', showTextSection);
document.getElementById("labelTextHide").addEventListener("click", hideTextSection); document.getElementById('labelTextHide').addEventListener('click', hideTextSection);
document.getElementById("labelText").addEventListener("input", changeText); document.getElementById('labelText').addEventListener('input', changeText);
document.getElementById("labelTextRandom").addEventListener("click", generateRandomName); document.getElementById('labelTextRandom').addEventListener('click', generateRandomName);
document.getElementById("labelEditStyle").addEventListener("click", editGroupStyle); document.getElementById('labelEditStyle').addEventListener('click', editGroupStyle);
document.getElementById("labelSizeShow").addEventListener("click", showSizeSection); document.getElementById('labelSizeShow').addEventListener('click', showSizeSection);
document.getElementById("labelSizeHide").addEventListener("click", hideSizeSection); document.getElementById('labelSizeHide').addEventListener('click', hideSizeSection);
document.getElementById("labelStartOffset").addEventListener("input", changeStartOffset); document.getElementById('labelStartOffset').addEventListener('input', changeStartOffset);
document.getElementById("labelRelativeSize").addEventListener("input", changeRelativeSize); document.getElementById('labelRelativeSize').addEventListener('input', changeRelativeSize);
document.getElementById("labelAlign").addEventListener("click", editLabelAlign); document.getElementById('labelAlign').addEventListener('click', editLabelAlign);
document.getElementById("labelLegend").addEventListener("click", editLabelLegend); document.getElementById('labelLegend').addEventListener('click', editLabelLegend);
document.getElementById("labelRemoveSingle").addEventListener("click", removeLabel); document.getElementById('labelRemoveSingle').addEventListener('click', removeLabel);
function showEditorTips() { function showEditorTips() {
showMainTip(); showMainTip();
if (d3.event.target.parentNode.parentNode.id === elSelected.attr("id")) tip("Drag to shift the label"); else if (d3.event.target.parentNode.parentNode.id === elSelected.attr('id')) tip('Drag to shift the label');
if (d3.event.target.parentNode.id === "controlPoints") { else if (d3.event.target.parentNode.id === 'controlPoints') {
if (d3.event.target.tagName === "circle") tip("Drag to move, click to delete the control point"); if (d3.event.target.tagName === 'circle') tip('Drag to move, click to delete the control point');
if (d3.event.target.tagName === "path") tip("Click to add a control point"); if (d3.event.target.tagName === 'path') tip('Click to add a control point');
} }
} }
function selectLabelGroup(text) { function selectLabelGroup(text) {
const group = text.parentNode.id; const group = text.parentNode.id;
const select = document.getElementById("labelGroupSelect"); const select = document.getElementById('labelGroupSelect');
select.options.length = 0; // remove all options select.options.length = 0; // remove all options
labels.selectAll(":scope > g").each(function() { labels.selectAll(':scope > g').each(function () {
if (this.id === "burgLabels") return; if (this.id === 'burgLabels') return;
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 updateValues(textPath) { function updateValues(textPath) {
document.getElementById("labelText").value = [...textPath.querySelectorAll("tspan")].map(tspan => tspan.textContent).join("|"); document.getElementById('labelText').value = [...textPath.querySelectorAll('tspan')].map((tspan) => tspan.textContent).join('|');
document.getElementById("labelStartOffset").value = parseFloat(textPath.getAttribute("startOffset")); document.getElementById('labelStartOffset').value = parseFloat(textPath.getAttribute('startOffset'));
document.getElementById("labelRelativeSize").value = parseFloat(textPath.getAttribute("font-size")); document.getElementById('labelRelativeSize').value = parseFloat(textPath.getAttribute('font-size'));
} }
function drawControlPointsAndLine() { function drawControlPointsAndLine() {
debug.select("#controlPoints").remove(); debug.select('#controlPoints').remove();
debug.append("g").attr("id", "controlPoints").attr("transform", elSelected.attr("transform")); debug.append('g').attr('id', 'controlPoints').attr('transform', elSelected.attr('transform'));
const path = document.getElementById("textPath_" + elSelected.attr("id")); const path = document.getElementById('textPath_' + elSelected.attr('id'));
debug.select("#controlPoints").append("path").attr("d", path.getAttribute("d")).on("click", addInterimControlPoint); debug.select('#controlPoints').append('path').attr('d', path.getAttribute('d')).on('click', addInterimControlPoint);
const l = path.getTotalLength(); const l = path.getTotalLength();
if (!l) return; if (!l) return;
const increment = l / Math.max(Math.ceil(l / 200), 2); const increment = l / Math.max(Math.ceil(l / 200), 2);
for (let i=0; i <= l; i += increment) {addControlPoint(path.getPointAtLength(i));} for (let i = 0; i <= l; i += increment) {
addControlPoint(path.getPointAtLength(i));
}
} }
function addControlPoint(point) { function addControlPoint(point) {
debug.select("#controlPoints").append("circle") debug
.attr("cx", point.x).attr("cy", point.y).attr("r", 2.5).attr("stroke-width", .8) .select('#controlPoints')
.call(d3.drag().on("drag", dragControlPoint)) .append('circle')
.on("click", clickControlPoint); .attr('cx', point.x)
.attr('cy', point.y)
.attr('r', 2.5)
.attr('stroke-width', 0.8)
.call(d3.drag().on('drag', dragControlPoint))
.on('click', clickControlPoint);
} }
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);
redrawLabelPath(); redrawLabelPath();
} }
function redrawLabelPath() { function redrawLabelPath() {
const path = document.getElementById("textPath_" + elSelected.attr("id")); const path = document.getElementById('textPath_' + elSelected.attr('id'));
lineGen.curve(d3.curveBundle.beta(1)); lineGen.curve(d3.curveBundle.beta(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')]);
});
const d = round(lineGen(points)); const d = round(lineGen(points));
path.setAttribute("d", d); path.setAttribute('d', d);
debug.select("#controlPoints > path").attr("d", d); debug.select('#controlPoints > path').attr('d', d);
} }
function clickControlPoint() { function clickControlPoint() {
@ -118,52 +130,63 @@ function editLabel() {
const point = d3.mouse(this); const point = d3.mouse(this);
const dists = []; const dists = [];
debug.select("#controlPoints").selectAll("circle").each(function() { debug
const x = +this.getAttribute("cx"); .select('#controlPoints')
const y = +this.getAttribute("cy"); .selectAll('circle')
dists.push((point[0] - x) ** 2 + (point[1] - y) ** 2); .each(function () {
}); const x = +this.getAttribute('cx');
const y = +this.getAttribute('cy');
dists.push((point[0] - x) ** 2 + (point[1] - y) ** 2);
});
let index = dists.length; let index = dists.length;
if (dists.length > 1) { if (dists.length > 1) {
const sorted = dists.slice(0).sort((a, b) => a-b); const sorted = dists.slice(0).sort((a, b) => a - b);
const closest = dists.indexOf(sorted[0]); const closest = dists.indexOf(sorted[0]);
const next = dists.indexOf(sorted[1]); const next = dists.indexOf(sorted[1]);
if (closest <= next) index = closest+1; else index = next+1; if (closest <= next) index = closest + 1;
else index = next + 1;
} }
const before = ":nth-child(" + (index + 2) + ")"; const before = ':nth-child(' + (index + 2) + ')';
debug.select("#controlPoints").insert("circle", before) debug
.attr("cx", point[0]).attr("cy", point[1]).attr("r", 2.5).attr("stroke-width", .8) .select('#controlPoints')
.call(d3.drag().on("drag", dragControlPoint)) .insert('circle', before)
.on("click", clickControlPoint); .attr('cx', point[0])
.attr('cy', point[1])
.attr('r', 2.5)
.attr('stroke-width', 0.8)
.call(d3.drag().on('drag', dragControlPoint))
.on('click', clickControlPoint);
redrawLabelPath(); redrawLabelPath();
} }
function dragLabel() { function dragLabel() {
const tr = parseTransform(elSelected.attr("transform")); const tr = parseTransform(elSelected.attr('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,
const transform = `translate(${(dx+x)},${(dy+y)})`; y = d3.event.y;
elSelected.attr("transform", transform); const transform = `translate(${dx + x},${dy + y})`;
debug.select("#controlPoints").attr("transform", transform); elSelected.attr('transform', transform);
debug.select('#controlPoints').attr('transform', transform);
}); });
} }
function showGroupSection() { function showGroupSection() {
document.querySelectorAll("#labelEditor > button").forEach(el => el.style.display = "none"); document.querySelectorAll('#labelEditor > button').forEach((el) => (el.style.display = 'none'));
document.getElementById("labelGroupSection").style.display = "inline-block"; document.getElementById('labelGroupSection').style.display = 'inline-block';
} }
function hideGroupSection() { function hideGroupSection() {
document.querySelectorAll("#labelEditor > button").forEach(el => el.style.display = "inline-block"); document.querySelectorAll('#labelEditor > button').forEach((el) => (el.style.display = 'inline-block'));
document.getElementById("labelGroupSection").style.display = "none"; document.getElementById('labelGroupSection').style.display = 'none';
document.getElementById("labelGroupInput").style.display = "none"; document.getElementById('labelGroupInput').style.display = 'none';
document.getElementById("labelGroupInput").value = ""; document.getElementById('labelGroupInput').value = '';
document.getElementById("labelGroupSelect").style.display = "inline-block"; document.getElementById('labelGroupSelect').style.display = 'inline-block';
} }
function changeGroup() { function changeGroup() {
@ -171,179 +194,177 @@ function editLabel() {
} }
function toggleNewGroupInput() { function toggleNewGroupInput() {
if (labelGroupInput.style.display === "none") { if (labelGroupInput.style.display === 'none') {
labelGroupInput.style.display = "inline-block"; labelGroupInput.style.display = 'inline-block';
labelGroupInput.focus(); labelGroupInput.focus();
labelGroupSelect.style.display = "none"; labelGroupSelect.style.display = 'none';
} else { } else {
labelGroupInput.style.display = "none"; labelGroupInput.style.display = 'none';
labelGroupSelect.style.display = "inline-block"; labelGroupSelect.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');
return; return;
} }
if (Number.isFinite(+group.charAt(0))) { if (Number.isFinite(+group.charAt(0))) {
tip("Group name should start with a letter", false, "error"); tip('Group name should start with a letter', false, 'error');
return; return;
} }
// just rename if only 1 element left // just rename if only 1 element left
const oldGroup = elSelected.node().parentNode; const oldGroup = elSelected.node().parentNode;
if (oldGroup !== "states" && oldGroup !== "addedLabels" && oldGroup.childElementCount === 1) { if (oldGroup !== 'states' && oldGroup !== 'addedLabels' && oldGroup.childElementCount === 1) {
document.getElementById("labelGroupSelect").selectedOptions[0].remove(); document.getElementById('labelGroupSelect').selectedOptions[0].remove();
document.getElementById("labelGroupSelect").options.add(new Option(group, group, false, true)); document.getElementById('labelGroupSelect').options.add(new Option(group, group, false, true));
oldGroup.id = group; oldGroup.id = group;
toggleNewGroupInput(); toggleNewGroupInput();
document.getElementById("labelGroupInput").value = ""; document.getElementById('labelGroupInput').value = '';
return; return;
} }
const newGroup = elSelected.node().parentNode.cloneNode(false); const newGroup = elSelected.node().parentNode.cloneNode(false);
document.getElementById("labels").appendChild(newGroup); document.getElementById('labels').appendChild(newGroup);
newGroup.id = group; newGroup.id = group;
document.getElementById("labelGroupSelect").options.add(new Option(group, group, false, true)); document.getElementById('labelGroupSelect').options.add(new Option(group, group, false, true));
document.getElementById(group).appendChild(elSelected.node()); document.getElementById(group).appendChild(elSelected.node());
toggleNewGroupInput(); toggleNewGroupInput();
document.getElementById("labelGroupInput").value = ""; document.getElementById('labelGroupInput').value = '';
} }
function removeLabelsGroup() { function removeLabelsGroup() {
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
${basic ? "all elements in the group" : "the entire label group"}? const message = `Are you sure you want to remove ${basic ? 'all elements in the group' : 'the entire label group'}?<br><br>Labels to be removed: ${count}`;
<br><br>Labels to be removed: ${count}`; const onConfirm = () => {
$("#alert").dialog({resizable: false, title: "Remove route group", $('#labelEditor').dialog('close');
buttons: { hideGroupSection();
Remove: function() { labels
$(this).dialog("close"); .select('#' + group)
$("#labelEditor").dialog("close"); .selectAll('text')
hideGroupSection(); .each(function () {
labels.select("#"+group).selectAll("text").each(function() { document.getElementById('textPath_' + this.id).remove();
document.getElementById("textPath_" + this.id).remove(); this.remove();
this.remove(); });
}); if (!basic) labels.select('#' + group).remove();
if (!basic) labels.select("#"+group).remove(); };
}, confirmationDialog({title: 'Remove label group', message, confirm: 'Remove', onConfirm});
Cancel: function() {$(this).dialog("close");}
}
});
} }
function showTextSection() { function showTextSection() {
document.querySelectorAll("#labelEditor > button").forEach(el => el.style.display = "none"); document.querySelectorAll('#labelEditor > button').forEach((el) => (el.style.display = 'none'));
document.getElementById("labelTextSection").style.display = "inline-block"; document.getElementById('labelTextSection').style.display = 'inline-block';
} }
function hideTextSection() { function hideTextSection() {
document.querySelectorAll("#labelEditor > button").forEach(el => el.style.display = "inline-block"); document.querySelectorAll('#labelEditor > button').forEach((el) => (el.style.display = 'inline-block'));
document.getElementById("labelTextSection").style.display = "none"; document.getElementById('labelTextSection').style.display = 'none';
} }
function changeText() { function changeText() {
const input = document.getElementById("labelText").value; const input = document.getElementById('labelText').value;
const el = elSelected.select("textPath").node(); const el = elSelected.select('textPath').node();
const example = d3.select(elSelected.node().parentNode) const example = d3.select(elSelected.node().parentNode).append('text').attr('x', 0).attr('x', 0).attr('font-size', el.getAttribute('font-size')).node();
.append("text").attr("x", 0).attr("x", 0)
.attr("font-size", el.getAttribute("font-size")).node();
const lines = input.split("|"); const lines = input.split('|');
const top = (lines.length - 1) / -2; // y offset const top = (lines.length - 1) / -2; // y offset
const inner = lines.map((l, d) => { const inner = lines
example.innerHTML = l; .map((l, d) => {
const left = example.getBBox().width / -2; // x offset example.innerHTML = l;
return `<tspan x="${left}px" dy="${d?1:top}em">${l}</tspan>`; const left = example.getBBox().width / -2; // x offset
}).join(""); return `<tspan x="${left}px" dy="${d ? 1 : top}em">${l}</tspan>`;
})
.join('');
el.innerHTML = inner; el.innerHTML = inner;
example.remove(); example.remove();
if (elSelected.attr("id").slice(0,10) === "stateLabel") tip("Use States Editor to change an actual state name, not just a label", false, "warning"); if (elSelected.attr('id').slice(0, 10) === 'stateLabel') tip('Use States Editor to change an actual state name, not just a label', false, 'warning');
} }
function generateRandomName() { function generateRandomName() {
let name = ""; let name = '';
if (elSelected.attr("id").slice(0,10) === "stateLabel") { if (elSelected.attr('id').slice(0, 10) === 'stateLabel') {
const id = +elSelected.attr("id").slice(10); const id = +elSelected.attr('id').slice(10);
const culture = pack.states[id].culture; const culture = pack.states[id].culture;
name = Names.getState(Names.getCulture(culture, 4, 7, ""), culture); name = Names.getState(Names.getCulture(culture, 4, 7, ''), culture);
} else { } else {
const box = elSelected.node().getBBox(); const box = elSelected.node().getBBox();
const cell = findCell((box.x + box.width) / 2, (box.y + box.height) / 2); const cell = findCell((box.x + box.width) / 2, (box.y + box.height) / 2);
const culture = pack.cells.culture[cell]; const culture = pack.cells.culture[cell];
name = Names.getCulture(culture); name = Names.getCulture(culture);
} }
document.getElementById("labelText").value = name; document.getElementById('labelText').value = name;
changeText(); changeText();
} }
function editGroupStyle() { function editGroupStyle() {
const g = elSelected.node().parentNode.id; const g = elSelected.node().parentNode.id;
editStyle("labels", g); editStyle('labels', g);
} }
function showSizeSection() { function showSizeSection() {
document.querySelectorAll("#labelEditor > button").forEach(el => el.style.display = "none"); document.querySelectorAll('#labelEditor > button').forEach((el) => (el.style.display = 'none'));
document.getElementById("labelSizeSection").style.display = "inline-block"; document.getElementById('labelSizeSection').style.display = 'inline-block';
} }
function hideSizeSection() { function hideSizeSection() {
document.querySelectorAll("#labelEditor > button").forEach(el => el.style.display = "inline-block"); document.querySelectorAll('#labelEditor > button').forEach((el) => (el.style.display = 'inline-block'));
document.getElementById("labelSizeSection").style.display = "none"; document.getElementById('labelSizeSection').style.display = 'none';
} }
function changeStartOffset() { function changeStartOffset() {
elSelected.select("textPath").attr("startOffset", this.value + "%"); elSelected.select('textPath').attr('startOffset', this.value + '%');
tip("Label offset: " + this.value + "%"); tip('Label offset: ' + this.value + '%');
} }
function changeRelativeSize() { function changeRelativeSize() {
elSelected.select("textPath").attr("font-size", this.value + "%"); elSelected.select('textPath').attr('font-size', this.value + '%');
tip("Label relative size: " + this.value + "%"); tip('Label relative size: ' + this.value + '%');
changeText(); changeText();
} }
function editLabelAlign() { function editLabelAlign() {
const bbox = elSelected.node().getBBox(); const bbox = elSelected.node().getBBox();
const c = [bbox.x + bbox.width / 2, bbox.y + bbox.height / 2]; const c = [bbox.x + bbox.width / 2, bbox.y + bbox.height / 2];
const path = defs.select("#textPath_" + elSelected.attr("id")); const path = defs.select('#textPath_' + elSelected.attr('id'));
path.attr("d", `M${c[0]-bbox.width},${c[1]}h${bbox.width*2}`); path.attr('d', `M${c[0] - bbox.width},${c[1]}h${bbox.width * 2}`);
drawControlPointsAndLine(); drawControlPointsAndLine();
} }
function editLabelLegend() { function editLabelLegend() {
const id = elSelected.attr("id"); const id = elSelected.attr('id');
const name = elSelected.text(); const name = elSelected.text();
editNotes(id, name); editNotes(id, name);
} }
function removeLabel() { function removeLabel() {
alertMessage.innerHTML = "Are you sure you want to remove the label?"; const message = 'Are you sure you want to remove the label? <br>This action cannot be reverted';
$("#alert").dialog({resizable: false, title: "Remove label", const onConfirm = () => {
buttons: { defs.select('#textPath_' + elSelected.attr('id')).remove();
Remove: function() { elSelected.remove();
$(this).dialog("close"); $('#labelEditor').dialog('close');
defs.select("#textPath_" + elSelected.attr("id")).remove(); };
elSelected.remove(); confirmationDialog({title: 'Remove label', message, confirm: 'Remove', onConfirm});
$("#labelEditor").dialog("close");
},
Cancel: function() {$(this).dialog("close");}
}
});
} }
function closeLabelEditor() { function closeLabelEditor() {
debug.select("#controlPoints").remove(); debug.select('#controlPoints').remove();
unselect(); unselect();
} }
} }

View file

@ -1,107 +1,126 @@
"use strict"; 'use strict';
function editLake() { function editLake() {
if (customization) return; if (customization) return;
closeDialogs(".stable"); closeDialogs('.stable');
if (layerIsOn("toggleCells")) toggleCells(); if (layerIsOn('toggleCells')) toggleCells();
$("#lakeEditor").dialog({ $('#lakeEditor').dialog({
title: "Edit Lake", resizable: false, title: 'Edit Lake',
position: {my: "center top+20", at: "top", of: d3.event, collision: "fit"}, resizable: false,
position: {my: 'center top+20', at: 'top', of: d3.event, collision: 'fit'},
close: closeLakesEditor close: closeLakesEditor
}); });
const node = d3.event.target; const node = d3.event.target;
debug.append("g").attr("id", "vertices"); debug.append('g').attr('id', 'vertices');
elSelected = d3.select(node); elSelected = d3.select(node);
updateLakeValues(); updateLakeValues();
selectLakeGroup(node); selectLakeGroup(node);
drawLakeVertices(); drawLakeVertices();
viewbox.on("touchmove mousemove", null); viewbox.on('touchmove mousemove', null);
if (modules.editLake) return; if (modules.editLake) return;
modules.editLake = true; modules.editLake = true;
// add listeners // add listeners
document.getElementById("lakeName").addEventListener("input", changeName); document.getElementById('lakeName').addEventListener('input', changeName);
document.getElementById("lakeNameCulture").addEventListener("click", generateNameCulture); document.getElementById('lakeNameCulture').addEventListener('click', generateNameCulture);
document.getElementById("lakeNameRandom").addEventListener("click", generateNameRandom); document.getElementById('lakeNameRandom').addEventListener('click', generateNameRandom);
document.getElementById("lakeGroup").addEventListener("change", changeLakeGroup); document.getElementById('lakeGroup').addEventListener('change', changeLakeGroup);
document.getElementById("lakeGroupAdd").addEventListener("click", toggleNewGroupInput); document.getElementById('lakeGroupAdd').addEventListener('click', toggleNewGroupInput);
document.getElementById("lakeGroupName").addEventListener("change", createNewGroup); document.getElementById('lakeGroupName').addEventListener('change', createNewGroup);
document.getElementById("lakeGroupRemove").addEventListener("click", removeLakeGroup); document.getElementById('lakeGroupRemove').addEventListener('click', removeLakeGroup);
document.getElementById("lakeEditStyle").addEventListener("click", editGroupStyle); document.getElementById('lakeEditStyle').addEventListener('click', editGroupStyle);
document.getElementById("lakeLegend").addEventListener("click", editLakeLegend); document.getElementById('lakeLegend').addEventListener('click', editLakeLegend);
function getLake() { function getLake() {
const lakeId = +elSelected.attr("data-f"); const lakeId = +elSelected.attr('data-f');
return pack.features.find(feature => feature.i === lakeId); return pack.features.find((feature) => feature.i === lakeId);
} }
function updateLakeValues() { function updateLakeValues() {
const cells = pack.cells; const cells = pack.cells;
const l = getLake(); const l = getLake();
document.getElementById("lakeName").value = l.name; document.getElementById('lakeName').value = l.name;
const unit = areaUnit.value === "square" ? " " + distanceUnitInput.value + "²" : " " + areaUnit.value; const unit = areaUnit.value === 'square' ? ' ' + distanceUnitInput.value + '²' : ' ' + areaUnit.value;
document.getElementById("lakeArea").value = si(l.area * distanceScaleInput.value ** 2) + unit; document.getElementById('lakeArea').value = si(l.area * distanceScaleInput.value ** 2) + unit;
const length = d3.polygonLength(l.vertices.map(v => pack.vertices.p[v])); const length = d3.polygonLength(l.vertices.map((v) => pack.vertices.p[v]));
document.getElementById("lakeShoreLength").value = si(length * distanceScaleInput.value) + " " + distanceUnitInput.value; document.getElementById('lakeShoreLength').value = si(length * distanceScaleInput.value) + ' ' + distanceUnitInput.value;
const lakeCells = Array.from(cells.i.filter(i => cells.f[i] === l.i)); const lakeCells = Array.from(cells.i.filter((i) => cells.f[i] === l.i));
const heights = lakeCells.map(i => cells.h[i]); const heights = lakeCells.map((i) => cells.h[i]);
document.getElementById("lakeElevation").value = getHeight(l.height); document.getElementById('lakeElevation').value = getHeight(l.height);
document.getElementById("lakeAvarageDepth").value = getHeight(d3.mean(heights), "abs"); document.getElementById('lakeAvarageDepth').value = getHeight(d3.mean(heights), 'abs');
document.getElementById("lakeMaxDepth").value = getHeight(d3.min(heights), "abs"); document.getElementById('lakeMaxDepth').value = getHeight(d3.min(heights), 'abs');
document.getElementById("lakeFlux").value = l.flux; document.getElementById('lakeFlux').value = l.flux;
document.getElementById("lakeEvaporation").value = l.evaporation; document.getElementById('lakeEvaporation').value = l.evaporation;
const inlets = l.inlets && l.inlets.map(inlet => pack.rivers.find(river => river.i === inlet)?.name); const inlets = l.inlets && l.inlets.map((inlet) => pack.rivers.find((river) => river.i === inlet)?.name);
const outlet = l.outlet ? pack.rivers.find(river => river.i === l.outlet)?.name : "no"; const outlet = l.outlet ? pack.rivers.find((river) => river.i === l.outlet)?.name : 'no';
document.getElementById("lakeInlets").value = inlets ? inlets.length : "no"; document.getElementById('lakeInlets').value = inlets ? inlets.length : 'no';
document.getElementById("lakeInlets").title = inlets ? inlets.join(", ") : ""; document.getElementById('lakeInlets').title = inlets ? inlets.join(', ') : '';
document.getElementById("lakeOutlet").value = outlet; document.getElementById('lakeOutlet').value = outlet;
} }
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')
.on("mousemove", () => tip("Drag to move the vertex, please use for fine-tuning only. Edit heightmap to change actual cell heights")); .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'));
} }
function dragVertex() { function dragVertex() {
const x = rn(d3.event.x, 2), y = rn(d3.event.y, 2); const x = rn(d3.event.x, 2),
this.setAttribute("cx", x); y = rn(d3.event.y, 2);
this.setAttribute("cy", y); this.setAttribute('cx', x);
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();
} }
function redrawLake() { function redrawLake() {
lineGen.curve(d3.curveBasisClosed); lineGen.curve(d3.curveBasisClosed);
const feature = getLake(); const feature = getLake();
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));
document.getElementById("lakeArea").value = si(feature.area * distanceScaleInput.value ** 2) + unit; document.getElementById('lakeArea').value = si(feature.area * distanceScaleInput.value ** 2) + unit;
} }
function changeName() { function changeName() {
@ -115,15 +134,15 @@ 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) {
const group = node.parentNode.id; const group = node.parentNode.id;
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));
}); });
} }
@ -134,93 +153,93 @@ function editLake() {
} }
function toggleNewGroupInput() { function toggleNewGroupInput() {
if (lakeGroupName.style.display === "none") { if (lakeGroupName.style.display === 'none') {
lakeGroupName.style.display = "inline-block"; lakeGroupName.style.display = 'inline-block';
lakeGroupName.focus(); lakeGroupName.focus();
lakeGroup.style.display = "none"; lakeGroup.style.display = 'none';
} 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');
return; return;
} }
if (Number.isFinite(+group.charAt(0))) { if (Number.isFinite(+group.charAt(0))) {
tip("Group name should start with a letter", false, "error"); tip('Group name should start with a letter', false, 'error');
return; return;
} }
// just rename if only 1 element left // just rename if only 1 element left
const oldGroup = elSelected.node().parentNode; const oldGroup = elSelected.node().parentNode;
const basic = ["freshwater", "salt", "sinkhole", "frozen", "lava", "dry"].includes(oldGroup.id); const basic = ['freshwater', 'salt', 'sinkhole', 'frozen', 'lava', 'dry'].includes(oldGroup.id);
if (!basic && oldGroup.childElementCount === 1) { if (!basic && oldGroup.childElementCount === 1) {
document.getElementById("lakeGroup").selectedOptions[0].remove(); document.getElementById('lakeGroup').selectedOptions[0].remove();
document.getElementById("lakeGroup").options.add(new Option(group, group, false, true)); document.getElementById('lakeGroup').options.add(new Option(group, group, false, true));
oldGroup.id = group; oldGroup.id = group;
toggleNewGroupInput(); toggleNewGroupInput();
document.getElementById("lakeGroupName").value = ""; document.getElementById('lakeGroupName').value = '';
return; return;
} }
// create a new group // create a new group
const newGroup = elSelected.node().parentNode.cloneNode(false); const newGroup = elSelected.node().parentNode.cloneNode(false);
document.getElementById("lakes").appendChild(newGroup); document.getElementById('lakes').appendChild(newGroup);
newGroup.id = group; newGroup.id = group;
document.getElementById("lakeGroup").options.add(new Option(group, group, false, true)); document.getElementById('lakeGroup').options.add(new Option(group, group, false, true));
document.getElementById(group).appendChild(elSelected.node()); document.getElementById(group).appendChild(elSelected.node());
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)) {
tip("This is one of the default groups, it cannot be removed", false, "error"); tip('This is one of the default groups, it cannot be removed', false, 'error');
return; return;
} }
const count = elSelected.node().parentNode.childElementCount; const count = elSelected.node().parentNode.childElementCount;
alertMessage.innerHTML = `Are you sure you want to remove the group? const message = `Are you sure you want to remove the group? <br>All lakes of the group (${count}) will be turned into <i>freshwater</i>`;
All lakes of the group (${count}) will be turned into Freshwater`; const onConfirm = () => {
$("#alert").dialog({resizable: false, title: "Remove lake group", width:"26em", const freshwater = document.getElementById('freshwater');
buttons: { const groupEl = document.getElementById(group);
Remove: function() { while (groupEl.childNodes.length) {
$(this).dialog("close"); freshwater.appendChild(groupEl.childNodes[0]);
const freshwater = document.getElementById("freshwater");
const groupEl = document.getElementById(group);
while (groupEl.childNodes.length) {
freshwater.appendChild(groupEl.childNodes[0]);
}
groupEl.remove();
document.getElementById("lakeGroup").selectedOptions[0].remove();
document.getElementById("lakeGroup").value = "freshwater";
},
Cancel: function() {$(this).dialog("close");}
} }
}); groupEl.remove();
document.getElementById('lakeGroup').selectedOptions[0].remove();
document.getElementById('lakeGroup').value = 'freshwater';
};
confirmationDialog({title: 'Remove lake group', message, confirm: 'Remove', onConfirm});
} }
function editGroupStyle() { function editGroupStyle() {
const g = elSelected.node().parentNode.id; const g = elSelected.node().parentNode.id;
editStyle("lakes", g); editStyle('lakes', g);
} }
function editLakeLegend() { function editLakeLegend() {
const id = elSelected.attr("id"); const id = elSelected.attr('id');
editNotes(id, getLake().name + " " + lakeGroup.value + " lake"); editNotes(id, getLake().name + ' ' + lakeGroup.value + ' lake');
} }
function closeLakesEditor() { function closeLakesEditor() {
debug.select("#vertices").remove(); debug.select('#vertices').remove();
unselect(); unselect();
} }
} }

View file

@ -1,130 +1,137 @@
"use strict"; 'use strict';
function editMarker() { function editMarker() {
if (customization) return; if (customization) return;
closeDialogs("#markerEditor, .stable"); closeDialogs('#markerEditor, .stable');
$("#markerEditor").dialog(); $('#markerEditor').dialog();
elSelected = d3.select(d3.event.target).call(d3.drag().on("start", dragMarker)).classed("draggable", true); elSelected = d3.select(d3.event.target).call(d3.drag().on('start', dragMarker)).classed('draggable', true);
updateInputs(); updateInputs();
if (modules.editMarker) return; if (modules.editMarker) return;
modules.editMarker = true; modules.editMarker = true;
$("#markerEditor").dialog({ $('#markerEditor').dialog({
title: "Edit Marker", resizable: false, title: 'Edit Marker',
position: {my: "center top+30", at: "bottom", of: d3.event, collision: "fit"}, resizable: false,
position: {my: 'center top+30', at: 'bottom', of: d3.event, collision: 'fit'},
close: closeMarkerEditor close: closeMarkerEditor
}); });
// add listeners // add listeners
document.getElementById("markerGroup").addEventListener("click", toggleGroupSection); document.getElementById('markerGroup').addEventListener('click', toggleGroupSection);
document.getElementById("markerAddGroup").addEventListener("click", toggleGroupInput); document.getElementById('markerAddGroup').addEventListener('click', toggleGroupInput);
document.getElementById("markerSelectGroup").addEventListener("change", changeGroup); document.getElementById('markerSelectGroup').addEventListener('change', changeGroup);
document.getElementById("markerInputGroup").addEventListener("change", createGroup); document.getElementById('markerInputGroup').addEventListener('change', createGroup);
document.getElementById("markerRemoveGroup").addEventListener("click", removeGroup); document.getElementById('markerRemoveGroup').addEventListener('click', removeGroup);
document.getElementById("markerIcon").addEventListener("click", toggleIconSection); document.getElementById('markerIcon').addEventListener('click', toggleIconSection);
document.getElementById("markerIconSize").addEventListener("input", changeIconSize); document.getElementById('markerIconSize').addEventListener('input', changeIconSize);
document.getElementById("markerIconShiftX").addEventListener("input", changeIconShiftX); document.getElementById('markerIconShiftX').addEventListener('input', changeIconShiftX);
document.getElementById("markerIconShiftY").addEventListener("input", changeIconShiftY); document.getElementById('markerIconShiftY').addEventListener('input', changeIconShiftY);
document.getElementById("markerIconSelect").addEventListener("click", selectMarkerIcon); document.getElementById('markerIconSelect').addEventListener('click', selectMarkerIcon);
document.getElementById("markerStyle").addEventListener("click", toggleStyleSection); document.getElementById('markerStyle').addEventListener('click', toggleStyleSection);
document.getElementById("markerSize").addEventListener("input", changeMarkerSize); document.getElementById('markerSize').addEventListener('input', changeMarkerSize);
document.getElementById("markerBaseStroke").addEventListener("input", changePinStroke); document.getElementById('markerBaseStroke').addEventListener('input', changePinStroke);
document.getElementById("markerBaseFill").addEventListener("input", changePinFill); document.getElementById('markerBaseFill').addEventListener('input', changePinFill);
document.getElementById("markerIconStrokeWidth").addEventListener("input", changeIconStrokeWidth); document.getElementById('markerIconStrokeWidth').addEventListener('input', changeIconStrokeWidth);
document.getElementById("markerIconStroke").addEventListener("input", changeIconStroke); document.getElementById('markerIconStroke').addEventListener('input', changeIconStroke);
document.getElementById("markerIconFill").addEventListener("input", changeIconFill); document.getElementById('markerIconFill').addEventListener('input', changeIconFill);
document.getElementById("markerToggleBubble").addEventListener("click", togglePinVisibility); document.getElementById('markerToggleBubble').addEventListener('click', togglePinVisibility);
document.getElementById("markerLegendButton").addEventListener("click", editMarkerLegend); document.getElementById('markerLegendButton').addEventListener('click', editMarkerLegend);
document.getElementById("markerAdd").addEventListener("click", toggleAddMarker); document.getElementById('markerAdd').addEventListener('click', toggleAddMarker);
document.getElementById("markerRemove").addEventListener("click", removeMarker); document.getElementById('markerRemove').addEventListener('click', removeMarker);
updateGroupOptions(); updateGroupOptions();
function dragMarker() { function dragMarker() {
const tr = parseTransform(this.getAttribute("transform")); const tr = parseTransform(this.getAttribute('transform'));
const x = +tr[0] - d3.event.x, y = +tr[1] - d3.event.y; const x = +tr[0] - d3.event.x,
y = +tr[1] - d3.event.y;
d3.event.on("drag", function() { d3.event.on('drag', function () {
const transform = `translate(${(x + d3.event.x)},${(y + d3.event.y)})`; const transform = `translate(${x + d3.event.x},${y + d3.event.y})`;
this.setAttribute("transform", transform); this.setAttribute('transform', transform);
}); });
} }
function updateInputs() { function updateInputs() {
const id = elSelected.attr("data-id"); const id = elSelected.attr('data-id');
const symbol = d3.select("#defs-markers").select(id); const symbol = d3.select('#defs-markers').select(id);
const icon = symbol.select("text"); const icon = symbol.select('text');
markerSelectGroup.value = id.slice(1); markerSelectGroup.value = id.slice(1);
markerIconSize.value = parseFloat(icon.attr("font-size")); markerIconSize.value = parseFloat(icon.attr('font-size'));
markerIconShiftX.value = parseFloat(icon.attr("x")); markerIconShiftX.value = parseFloat(icon.attr('x'));
markerIconShiftY.value = parseFloat(icon.attr("y")); markerIconShiftY.value = parseFloat(icon.attr('y'));
markerSize.value = elSelected.attr("data-size"); markerSize.value = elSelected.attr('data-size');
markerBaseStroke.value = symbol.select("path").attr("fill"); markerBaseStroke.value = symbol.select('path').attr('fill');
markerBaseFill.value = symbol.select("circle").attr("fill"); markerBaseFill.value = symbol.select('circle').attr('fill');
markerIconStrokeWidth.value = icon.attr("stroke-width"); markerIconStrokeWidth.value = icon.attr('stroke-width');
markerIconStroke.value = icon.attr("stroke"); markerIconStroke.value = icon.attr('stroke');
markerIconFill.value = icon.attr("fill"); markerIconFill.value = icon.attr('fill');
markerToggleBubble.className = symbol.select("circle").attr("opacity") === "0" ? "icon-info" : "icon-info-circled"; markerToggleBubble.className = symbol.select('circle').attr('opacity') === '0' ? 'icon-info' : 'icon-info-circled';
markerIconSelect.innerHTML = icon.text(); markerIconSelect.innerHTML = icon.text();
} }
function toggleGroupSection() { function toggleGroupSection() {
if (markerGroupSection.style.display === "inline-block") { if (markerGroupSection.style.display === 'inline-block') {
markerEditor.querySelectorAll("button:not(#markerGroup)").forEach(b => b.style.display = "inline-block"); markerEditor.querySelectorAll('button:not(#markerGroup)').forEach((b) => (b.style.display = 'inline-block'));
markerGroupSection.style.display = "none"; markerGroupSection.style.display = 'none';
} else { } else {
markerEditor.querySelectorAll("button:not(#markerGroup)").forEach(b => b.style.display = "none"); markerEditor.querySelectorAll('button:not(#markerGroup)').forEach((b) => (b.style.display = 'none'));
markerGroupSection.style.display = "inline-block"; markerGroupSection.style.display = 'inline-block';
} }
} }
function updateGroupOptions() { function updateGroupOptions() {
markerSelectGroup.innerHTML = ""; markerSelectGroup.innerHTML = '';
d3.select("#defs-markers").selectAll("symbol").each(function() { d3.select('#defs-markers')
markerSelectGroup.options.add(new Option(this.id, this.id)); .selectAll('symbol')
}); .each(function () {
markerSelectGroup.value = elSelected.attr("data-id").slice(1); markerSelectGroup.options.add(new Option(this.id, this.id));
});
markerSelectGroup.value = elSelected.attr('data-id').slice(1);
} }
function toggleGroupInput() { function toggleGroupInput() {
if (markerInputGroup.style.display === "inline-block") { if (markerInputGroup.style.display === 'inline-block') {
markerSelectGroup.style.display = "inline-block"; markerSelectGroup.style.display = 'inline-block';
markerInputGroup.style.display = "none"; markerInputGroup.style.display = 'none';
} else { } else {
markerSelectGroup.style.display = "none"; markerSelectGroup.style.display = 'none';
markerInputGroup.style.display = "inline-block"; markerInputGroup.style.display = 'inline-block';
markerInputGroup.focus(); markerInputGroup.focus();
} }
} }
function changeGroup() { function changeGroup() {
elSelected.attr("xlink:href", "#"+this.value); elSelected.attr('xlink:href', '#' + this.value);
elSelected.attr("data-id", "#"+this.value); elSelected.attr('data-id', '#' + this.value);
} }
function createGroup() { function createGroup() {
let newGroup = this.value.toLowerCase().replace(/ /g, "_").replace(/[^\w\s]/gi, ""); let newGroup = this.value
if (Number.isFinite(+newGroup.charAt(0))) newGroup = "m" + newGroup; .toLowerCase()
.replace(/ /g, '_')
.replace(/[^\w\s]/gi, '');
if (Number.isFinite(+newGroup.charAt(0))) newGroup = 'm' + newGroup;
if (document.getElementById(newGroup)) { if (document.getElementById(newGroup)) {
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');
return; return;
} }
markerInputGroup.value = ""; markerInputGroup.value = '';
// clone old group assigning new id // clone old group assigning new id
const id = elSelected.attr("data-id"); const id = elSelected.attr('data-id');
const clone = d3.select("#defs-markers").select(id).node().cloneNode(true); const clone = d3.select('#defs-markers').select(id).node().cloneNode(true);
clone.id = newGroup; clone.id = newGroup;
document.getElementById("defs-markers").insertBefore(clone, null); document.getElementById('defs-markers').insertBefore(clone, null);
elSelected.attr("xlink:href", "#"+newGroup).attr("data-id", "#"+newGroup); elSelected.attr('xlink:href', '#' + newGroup).attr('data-id', '#' + newGroup);
// select new group // select new group
markerSelectGroup.options.add(new Option(newGroup, newGroup, false, true)); markerSelectGroup.options.add(new Option(newGroup, newGroup, false, true));
@ -132,156 +139,157 @@ function editMarker() {
} }
function removeGroup() { function removeGroup() {
const id = elSelected.attr("data-id"); const id = elSelected.attr('data-id');
const used = document.querySelectorAll("use[data-id='"+id+"']"); const used = document.querySelectorAll("use[data-id='" + id + "']");
const count = used.length === 1 ? "1 element" : used.length + " elements";
alertMessage.innerHTML = "Are you sure you want to remove all markers of that type (" + count + ")?";
$("#alert").dialog({resizable: false, title: "Remove marker type", const count = used.length === 1 ? '1 element' : used.length + ' elements';
buttons: { const message = `Are you sure you want to remove all markers of that type (${count})? <br>This action cannot be reverted`;
Remove: function() { const onConfirm = () => {
$(this).dialog("close"); if (id !== '#marker0') d3.select('#defs-markers').select(id).remove();
if (id !== "#marker0") d3.select("#defs-markers").select(id).remove(); used.forEach((e) => {
used.forEach(e => { const index = notes.findIndex((n) => n.id === e.id);
const index = notes.findIndex(n => n.id === e.id); if (index != -1) notes.splice(index, 1);
if (index != -1) notes.splice(index, 1); e.remove();
e.remove(); });
}); updateGroupOptions();
updateGroupOptions(); updateGroupOptions();
updateGroupOptions(); $('#markerEditor').dialog('close');
$("#markerEditor").dialog("close"); };
}, confirmationDialog({title: 'Remove marker type', message, confirm: 'Remove', onConfirm});
Cancel: function() {$(this).dialog("close");}
}
});
} }
function toggleIconSection() { function toggleIconSection() {
if (markerIconSection.style.display === "inline-block") { if (markerIconSection.style.display === 'inline-block') {
markerEditor.querySelectorAll("button:not(#markerIcon)").forEach(b => b.style.display = "inline-block"); markerEditor.querySelectorAll('button:not(#markerIcon)').forEach((b) => (b.style.display = 'inline-block'));
markerIconSection.style.display = "none"; markerIconSection.style.display = 'none';
markerIconSelect.style.display = "none"; markerIconSelect.style.display = 'none';
} else { } else {
markerEditor.querySelectorAll("button:not(#markerIcon)").forEach(b => b.style.display = "none"); markerEditor.querySelectorAll('button:not(#markerIcon)').forEach((b) => (b.style.display = 'none'));
markerIconSection.style.display = "inline-block"; markerIconSection.style.display = 'inline-block';
markerIconSelect.style.display = "inline-block"; markerIconSelect.style.display = 'inline-block';
} }
} }
function selectMarkerIcon() { function selectMarkerIcon() {
selectIcon(this.innerHTML, v => { selectIcon(this.innerHTML, (v) => {
this.innerHTML = v; this.innerHTML = v;
const id = elSelected.attr("data-id"); const id = elSelected.attr('data-id');
d3.select("#defs-markers").select(id).select("text").text(v); d3.select('#defs-markers').select(id).select('text').text(v);
}); });
} }
function changeIconSize() { function changeIconSize() {
const id = elSelected.attr("data-id"); const id = elSelected.attr('data-id');
d3.select("#defs-markers").select(id).select("text").attr("font-size", this.value + "px"); d3.select('#defs-markers')
.select(id)
.select('text')
.attr('font-size', this.value + 'px');
} }
function changeIconShiftX() { function changeIconShiftX() {
const id = elSelected.attr("data-id"); const id = elSelected.attr('data-id');
d3.select("#defs-markers").select(id).select("text").attr("x", this.value + "%"); d3.select('#defs-markers')
.select(id)
.select('text')
.attr('x', this.value + '%');
} }
function changeIconShiftY() { function changeIconShiftY() {
const id = elSelected.attr("data-id"); const id = elSelected.attr('data-id');
d3.select("#defs-markers").select(id).select("text").attr("y", this.value + "%"); d3.select('#defs-markers')
.select(id)
.select('text')
.attr('y', this.value + '%');
} }
function toggleStyleSection() { function toggleStyleSection() {
if (markerStyleSection.style.display === "inline-block") { if (markerStyleSection.style.display === 'inline-block') {
markerEditor.querySelectorAll("button:not(#markerStyle)").forEach(b => b.style.display = "inline-block"); markerEditor.querySelectorAll('button:not(#markerStyle)').forEach((b) => (b.style.display = 'inline-block'));
markerStyleSection.style.display = "none"; markerStyleSection.style.display = 'none';
} else { } else {
markerEditor.querySelectorAll("button:not(#markerStyle)").forEach(b => b.style.display = "none"); markerEditor.querySelectorAll('button:not(#markerStyle)').forEach((b) => (b.style.display = 'none'));
markerStyleSection.style.display = "inline-block"; markerStyleSection.style.display = 'inline-block';
} }
} }
function changeMarkerSize() { function changeMarkerSize() {
const id = elSelected.attr("data-id"); const id = elSelected.attr('data-id');
document.querySelectorAll("use[data-id='"+id+"']").forEach(e => { document.querySelectorAll("use[data-id='" + id + "']").forEach((e) => {
const x = +e.dataset.x, y = +e.dataset.y; const x = +e.dataset.x,
const desired = e.dataset.size = +markerSize.value; y = +e.dataset.y;
const desired = (e.dataset.size = +markerSize.value);
const size = Math.max(desired * 5 + 25 / scale, 1); const size = Math.max(desired * 5 + 25 / scale, 1);
e.setAttribute("x", x - size / 2); e.setAttribute('x', x - size / 2);
e.setAttribute("y", y - size / 2); e.setAttribute('y', y - size / 2);
e.setAttribute("width", size); e.setAttribute('width', size);
e.setAttribute("height", size); e.setAttribute('height', size);
}); });
invokeActiveZooming(); invokeActiveZooming();
} }
function changePinStroke() { function changePinStroke() {
const id = elSelected.attr("data-id"); const id = elSelected.attr('data-id');
d3.select(id).select("path").attr("fill", this.value); d3.select(id).select('path').attr('fill', this.value);
d3.select(id).select("circle").attr("stroke", this.value); d3.select(id).select('circle').attr('stroke', this.value);
} }
function changePinFill() { function changePinFill() {
const id = elSelected.attr("data-id"); const id = elSelected.attr('data-id');
d3.select(id).select("circle").attr("fill", this.value); d3.select(id).select('circle').attr('fill', this.value);
} }
function changeIconStrokeWidth() { function changeIconStrokeWidth() {
const id = elSelected.attr("data-id"); const id = elSelected.attr('data-id');
d3.select("#defs-markers").select(id).select("text").attr("stroke-width", this.value); d3.select('#defs-markers').select(id).select('text').attr('stroke-width', this.value);
} }
function changeIconStroke() { function changeIconStroke() {
const id = elSelected.attr("data-id"); const id = elSelected.attr('data-id');
d3.select("#defs-markers").select(id).select("text").attr("stroke", this.value); d3.select('#defs-markers').select(id).select('text').attr('stroke', this.value);
} }
function changeIconFill() { function changeIconFill() {
const id = elSelected.attr("data-id"); const id = elSelected.attr('data-id');
d3.select("#defs-markers").select(id).select("text").attr("fill", this.value); d3.select('#defs-markers').select(id).select('text').attr('fill', this.value);
} }
function togglePinVisibility() { function togglePinVisibility() {
const id = elSelected.attr("data-id"); const id = elSelected.attr('data-id');
let show = 1; let show = 1;
if (this.className === "icon-info-circled") {this.className = "icon-info"; show = 0; } if (this.className === 'icon-info-circled') {
else this.className = "icon-info-circled"; this.className = 'icon-info';
d3.select(id).select("circle").attr("opacity", show); show = 0;
d3.select(id).select("path").attr("opacity", show); } else this.className = 'icon-info-circled';
d3.select(id).select('circle').attr('opacity', show);
d3.select(id).select('path').attr('opacity', show);
} }
function editMarkerLegend() { function editMarkerLegend() {
const id = elSelected.attr("id"); const id = elSelected.attr('id');
editNotes(id, id); editNotes(id, id);
} }
function toggleAddMarker() { function toggleAddMarker() {
document.getElementById("addMarker").click(); document.getElementById('addMarker').click();
} }
function removeMarker() { function removeMarker() {
alertMessage.innerHTML = "Are you sure you want to remove the marker?"; const message = 'Are you sure you want to remove the marker? <br>This action cannot be reverted';
$("#alert").dialog({resizable: false, title: "Remove marker", const onConfirm = () => {
buttons: { const index = notes.findIndex((n) => n.id === elSelected.attr('id'));
Remove: function() { if (index != -1) notes.splice(index, 1);
$(this).dialog("close"); elSelected.remove();
const index = notes.findIndex(n => n.id === elSelected.attr("id")); $('#markerEditor').dialog('close');
if (index != -1) notes.splice(index, 1); };
elSelected.remove(); confirmationDialog({title: 'Remove marker', message, confirm: 'Remove', onConfirm});
$("#markerEditor").dialog("close");
},
Cancel: function() {$(this).dialog("close");}
}
});
} }
function closeMarkerEditor() { function closeMarkerEditor() {
unselect(); unselect();
if (addMarker.classList.contains("pressed")) addMarker.classList.remove("pressed"); if (addMarker.classList.contains('pressed')) addMarker.classList.remove('pressed');
if (markerAdd.classList.contains("pressed")) markerAdd.classList.remove("pressed"); if (markerAdd.classList.contains('pressed')) markerAdd.classList.remove('pressed');
restoreDefaultEvents(); restoreDefaultEvents();
clearMainTip(); clearMainTip();
} }
} }

View file

@ -1,71 +1,79 @@
"use strict"; 'use strict';
function overviewMilitary() { function overviewMilitary() {
if (customization) return; if (customization) return;
closeDialogs("#militaryOverview, .stable"); closeDialogs('#militaryOverview, .stable');
if (!layerIsOn("toggleStates")) toggleStates(); if (!layerIsOn('toggleStates')) toggleStates();
if (!layerIsOn("toggleBorders")) toggleBorders(); if (!layerIsOn('toggleBorders')) toggleBorders();
if (!layerIsOn("toggleMilitary")) toggleMilitary(); if (!layerIsOn('toggleMilitary')) toggleMilitary();
const body = document.getElementById("militaryBody"); const body = document.getElementById('militaryBody');
addLines(); addLines();
$("#militaryOverview").dialog(); $('#militaryOverview').dialog();
if (modules.overviewMilitary) return; if (modules.overviewMilitary) return;
modules.overviewMilitary = true; modules.overviewMilitary = true;
updateHeaders(); updateHeaders();
$("#militaryOverview").dialog({ $('#militaryOverview').dialog({
title: "Military Overview", resizable: false, width: fitContent(), title: 'Military Overview',
position: {my: "right top", at: "right-10 top+10", of: "svg", collision: "fit"} resizable: false,
width: fitContent(),
position: {my: 'right top', at: 'right-10 top+10', of: 'svg', collision: 'fit'}
}); });
// add listeners // add listeners
document.getElementById("militaryOverviewRefresh").addEventListener("click", addLines); document.getElementById('militaryOverviewRefresh').addEventListener('click', addLines);
document.getElementById("militaryPercentage").addEventListener("click", togglePercentageMode); document.getElementById('militaryPercentage').addEventListener('click', togglePercentageMode);
document.getElementById("militaryOptionsButton").addEventListener("click", militaryCustomize); document.getElementById('militaryOptionsButton').addEventListener('click', militaryCustomize);
document.getElementById("militaryRegimentsList").addEventListener("click", () => overviewRegiments(-1)); document.getElementById('militaryRegimentsList').addEventListener('click', () => overviewRegiments(-1));
document.getElementById("militaryOverviewRecalculate").addEventListener("click", militaryRecalculate); document.getElementById('militaryOverviewRecalculate').addEventListener('click', militaryRecalculate);
document.getElementById("militaryExport").addEventListener("click", downloadMilitaryData); document.getElementById('militaryExport').addEventListener('click', downloadMilitaryData);
document.getElementById("militaryWiki").addEventListener("click", () => wiki("Military-Forces")); document.getElementById('militaryWiki').addEventListener('click', () => wiki('Military-Forces'));
body.addEventListener("change", function(ev) { body.addEventListener('change', function (ev) {
const el = ev.target, line = el.parentNode, state = +line.dataset.id; const el = ev.target,
line = el.parentNode,
state = +line.dataset.id;
changeAlert(state, line, +el.value); changeAlert(state, line, +el.value);
}); });
body.addEventListener("click", function(ev) { body.addEventListener('click', function (ev) {
const el = ev.target, line = el.parentNode, state = +line.dataset.id; const el = ev.target,
if (el.tagName === "SPAN") overviewRegiments(state); line = el.parentNode,
state = +line.dataset.id;
if (el.tagName === 'SPAN') overviewRegiments(state);
}); });
// update military types in header and tooltips // update military types in header and tooltips
function updateHeaders() { function updateHeaders() {
const header = document.getElementById("militaryHeader"); const header = document.getElementById('militaryHeader');
header.querySelectorAll(".removable").forEach(el => el.remove()); header.querySelectorAll('.removable').forEach((el) => el.remove());
const insert = html => document.getElementById("militaryTotal").insertAdjacentHTML("beforebegin", html); const insert = (html) => document.getElementById('militaryTotal').insertAdjacentHTML('beforebegin', html);
for (const u of options.military) { for (const u of options.military) {
const label = capitalize(u.name.replace(/_/g, ' ')); const label = capitalize(u.name.replace(/_/g, ' '));
insert(`<div data-tip="State ${u.name} units number. Click to sort" class="sortable removable" data-sortby="${u.name}">${label}&nbsp;</div>`); insert(`<div data-tip="State ${u.name} units number. Click to sort" class="sortable removable" data-sortby="${u.name}">${label}&nbsp;</div>`);
} }
header.querySelectorAll(".removable").forEach(function(e) { header.querySelectorAll('.removable').forEach(function (e) {
e.addEventListener("click", function() {sortLines(this);}); e.addEventListener('click', function () {
sortLines(this);
});
}); });
} }
// add line for each state // add line for each state
function addLines() { function addLines() {
body.innerHTML = ""; body.innerHTML = '';
let lines = ""; let lines = '';
const states = pack.states.filter(s => s.i && !s.removed); const states = pack.states.filter((s) => s.i && !s.removed);
for (const s of states) { for (const s of states) {
const population = rn((s.rural + s.urban * urbanization.value) * populationRate.value); const population = rn((s.rural + s.urban * urbanization.value) * populationRate.value);
const getForces = u => s.military.reduce((s, r) => s+(r.u[u.name]||0), 0); const getForces = (u) => s.military.reduce((s, r) => s + (r.u[u.name] || 0), 0);
const total = options.military.reduce((s, u) => s + getForces(u) * u.crew, 0); const total = options.military.reduce((s, u) => s + getForces(u) * u.crew, 0);
const rate = total / population * 100; const rate = (total / population) * 100;
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}" data-population="${population}" data-rate="${rate}" data-alert="${s.alert}"> lines += `<div class="states" data-id=${s.i} data-state="${s.name}" ${sortData} data-total="${total}" data-population="${population}" data-rate="${rate}" data-alert="${s.alert}">
<svg data-tip="${s.fullName}" width=".9em" height=".9em" style="margin-bottom:-1px"><rect x="0" y="0" width="100%" height="100%" fill="${s.color}" class="fillRect"></svg> <svg data-tip="${s.fullName}" width=".9em" height=".9em" style="margin-bottom:-1px"><rect x="0" y="0" width="100%" height="100%" fill="${s.color}" class="fillRect"></svg>
@ -78,14 +86,17 @@ function overviewMilitary() {
<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>`;
} }
body.insertAdjacentHTML("beforeend", lines); body.insertAdjacentHTML('beforeend', lines);
updateFooter(); updateFooter();
// add listeners // add listeners
body.querySelectorAll("div.states").forEach(el => el.addEventListener("mouseenter", ev => stateHighlightOn(ev))); body.querySelectorAll('div.states').forEach((el) => el.addEventListener('mouseenter', (ev) => stateHighlightOn(ev)));
body.querySelectorAll("div.states").forEach(el => el.addEventListener("mouseleave", ev => stateHighlightOff(ev))); body.querySelectorAll('div.states').forEach((el) => el.addEventListener('mouseleave', (ev) => stateHighlightOff(ev)));
if (body.dataset.type === "percentage") {body.dataset.type = "absolute"; togglePercentageMode();} if (body.dataset.type === 'percentage') {
body.dataset.type = 'absolute';
togglePercentageMode();
}
applySorting(militaryHeader); applySorting(militaryHeader);
} }
@ -94,191 +105,206 @@ function overviewMilitary() {
const dif = s.alert || alert ? alert / s.alert : 0; // modifier const dif = s.alert || alert ? alert / s.alert : 0; // modifier
s.alert = line.dataset.alert = alert; s.alert = line.dataset.alert = alert;
s.military.forEach(r => { s.military.forEach((r) => {
Object.keys(r.u).forEach(u => r.u[u] = rn(r.u[u] * dif)); // change units value Object.keys(r.u).forEach((u) => (r.u[u] = rn(r.u[u] * dif))); // change units value
r.a = d3.sum(Object.values(r.u)); // change total r.a = d3.sum(Object.values(r.u)); // change total
armies.select(`g>g#regiment${s.i}-${r.i}>text`).text(Military.getTotal(r)); // change icon text armies.select(`g>g#regiment${s.i}-${r.i}>text`).text(Military.getTotal(r)); // change icon text
}); });
const getForces = u => s.military.reduce((s, r) => s+(r.u[u.name]||0), 0); const getForces = (u) => s.military.reduce((s, r) => s + (r.u[u.name] || 0), 0);
options.military.forEach(u => line.dataset[u.name] = line.querySelector(`div[data-type='${u.name}']`).innerHTML = getForces(u)); options.military.forEach((u) => (line.dataset[u.name] = line.querySelector(`div[data-type='${u.name}']`).innerHTML = getForces(u)));
const population = rn((s.rural + s.urban * urbanization.value) * populationRate.value); const population = rn((s.rural + s.urban * urbanization.value) * populationRate.value);
const total = line.dataset.total = options.military.reduce((s, u) => s + getForces(u) * u.crew, 0); const total = (line.dataset.total = options.military.reduce((s, u) => s + getForces(u) * u.crew, 0));
const rate = line.dataset.rate = total / population * 100; const rate = (line.dataset.rate = (total / population) * 100);
line.querySelector("div[data-type='total']").innerHTML = si(total); line.querySelector("div[data-type='total']").innerHTML = si(total);
line.querySelector("div[data-type='rate']").innerHTML = rn(rate, 2) + "%"; line.querySelector("div[data-type='rate']").innerHTML = rn(rate, 2) + '%';
updateFooter(); updateFooter();
} }
function updateFooter() { function updateFooter() {
const lines = Array.from(body.querySelectorAll(":scope > div")); const lines = Array.from(body.querySelectorAll(':scope > div'));
const statesNumber = militaryFooterStates.innerHTML = pack.states.filter(s => s.i && !s.removed).length; const statesNumber = (militaryFooterStates.innerHTML = pack.states.filter((s) => s.i && !s.removed).length);
const total = d3.sum(lines.map(el => el.dataset.total)); const total = d3.sum(lines.map((el) => el.dataset.total));
militaryFooterForcesTotal.innerHTML = si(total); militaryFooterForcesTotal.innerHTML = si(total);
militaryFooterForces.innerHTML = si(total / statesNumber); militaryFooterForces.innerHTML = si(total / statesNumber);
militaryFooterRate.innerHTML = rn(d3.sum(lines.map(el => el.dataset.rate)) / statesNumber, 2) + "%"; militaryFooterRate.innerHTML = rn(d3.sum(lines.map((el) => el.dataset.rate)) / statesNumber, 2) + '%';
militaryFooterAlert.innerHTML = rn(d3.sum(lines.map(el => el.dataset.alert)) / statesNumber, 2); militaryFooterAlert.innerHTML = rn(d3.sum(lines.map((el) => el.dataset.alert)) / statesNumber, 2);
} }
function stateHighlightOn(event) { function stateHighlightOn(event) {
const state = +event.target.dataset.id; const state = +event.target.dataset.id;
if (customization || !state) return; if (customization || !state) return;
armies.select("#army"+state).transition().duration(2000).style("fill", "#ff0000"); armies
.select('#army' + state)
.transition()
.duration(2000)
.style('fill', '#ff0000');
if (!layerIsOn("toggleStates")) return; if (!layerIsOn('toggleStates')) return;
const d = regions.select("#state"+state).attr("d"); const d = regions.select('#state' + state).attr('d');
const path = debug.append("path").attr("class", "highlight").attr("d", d) const path = debug.append('path').attr('class', 'highlight').attr('d', d).attr('fill', 'none').attr('stroke', 'red').attr('stroke-width', 1).attr('opacity', 1).attr('filter', 'url(#blur1)');
.attr("fill", "none").attr("stroke", "red").attr("stroke-width", 1).attr("opacity", 1)
.attr("filter", "url(#blur1)");
const l = path.node().getTotalLength(), dur = (l + 5000) / 2; const l = path.node().getTotalLength(),
const i = d3.interpolateString("0," + l, l + "," + l); dur = (l + 5000) / 2;
path.transition().duration(dur).attrTween("stroke-dasharray", function() {return t => i(t)}); const i = d3.interpolateString('0,' + l, l + ',' + l);
path
.transition()
.duration(dur)
.attrTween('stroke-dasharray', function () {
return (t) => i(t);
});
} }
function stateHighlightOff(event) { function stateHighlightOff(event) {
debug.selectAll(".highlight").each(function() { debug.selectAll('.highlight').each(function () {
d3.select(this).transition().duration(1000).attr("opacity", 0).remove(); d3.select(this).transition().duration(1000).attr('opacity', 0).remove();
}); });
const state = +event.target.dataset.id; const state = +event.target.dataset.id;
armies.select("#army"+state).transition().duration(1000).style("fill", null); armies
.select('#army' + state)
.transition()
.duration(1000)
.style('fill', null);
} }
function togglePercentageMode() { function togglePercentageMode() {
if (body.dataset.type === "absolute") { if (body.dataset.type === 'absolute') {
body.dataset.type = "percentage"; body.dataset.type = 'percentage';
const lines = body.querySelectorAll(":scope > div"); const lines = body.querySelectorAll(':scope > div');
const array = Array.from(lines), cache = []; const array = Array.from(lines),
cache = [];
const total = function(type) { const total = function (type) {
if (cache[type]) cache[type]; if (cache[type]) cache[type];
cache[type] = d3.sum(array.map(el => +el.dataset[type])); cache[type] = d3.sum(array.map((el) => +el.dataset[type]));
return cache[type]; return cache[type];
} };
lines.forEach(function(el) { lines.forEach(function (el) {
el.querySelectorAll("div").forEach(function(div) { el.querySelectorAll('div').forEach(function (div) {
const type = div.dataset.type; const type = div.dataset.type;
if (type === "rate") return; if (type === 'rate') return;
div.textContent = total(type) ? rn(+el.dataset[type] / total(type) * 100) + "%" : "0%"; div.textContent = total(type) ? rn((+el.dataset[type] / total(type)) * 100) + '%' : '0%';
}); });
}); });
} else { } else {
body.dataset.type = "absolute"; body.dataset.type = 'absolute';
addLines(); addLines();
} }
} }
function militaryCustomize() { function militaryCustomize() {
const types = ["melee", "ranged", "mounted", "machinery", "naval", "armored", "aviation", "magical"]; const types = ['melee', 'ranged', 'mounted', 'machinery', 'naval', 'armored', 'aviation', 'magical'];
const table = document.getElementById("militaryOptions").querySelector("tbody"); const table = document.getElementById('militaryOptions').querySelector('tbody');
removeUnitLines(); removeUnitLines();
options.military.map(u => addUnitLine(u)); options.military.map((u) => addUnitLine(u));
$("#militaryOptions").dialog({ $('#militaryOptions').dialog({
title: "Edit Military Units", resizable: false, width: fitContent(), title: 'Edit Military Units',
position: {my: "center", at: "center", of: "svg"}, resizable: false,
width: fitContent(),
position: {my: 'center', at: 'center', of: 'svg'},
buttons: { buttons: {
Apply: applyMilitaryOptions, Apply: applyMilitaryOptions,
Add: () => addUnitLine({icon: "🛡️", name: "custom"+militaryOptionsTable.rows.length, rural: .2, urban: .5, crew: 1, power: 1, type: "melee"}), Add: () => addUnitLine({icon: '🛡️', name: 'custom' + militaryOptionsTable.rows.length, rural: 0.2, urban: 0.5, crew: 1, power: 1, type: 'melee'}),
Restore: restoreDefaultUnits, Restore: restoreDefaultUnits,
Cancel: function() {$(this).dialog("close");} Cancel: function () {
}, open: function() { $(this).dialog('close');
const buttons = $(this).dialog("widget").find(".ui-dialog-buttonset > button"); }
buttons[0].addEventListener("mousemove", () => tip("Apply military units settings. <span style='color:#cb5858'>All forces will be recalculated!</span>")); },
buttons[1].addEventListener("mousemove", () => tip("Add new military unit to the table")); open: function () {
buttons[2].addEventListener("mousemove", () => tip("Restore default military units and settings")); const buttons = $(this).dialog('widget').find('.ui-dialog-buttonset > button');
buttons[3].addEventListener("mousemove", () => tip("Close the window without saving the changes")); buttons[0].addEventListener('mousemove', () => tip("Apply military units settings. <span style='color:#cb5858'>All forces will be recalculated!</span>"));
buttons[1].addEventListener('mousemove', () => tip('Add new military unit to the table'));
buttons[2].addEventListener('mousemove', () => tip('Restore default military units and settings'));
buttons[3].addEventListener('mousemove', () => tip('Close the window without saving the changes'));
} }
}); });
function removeUnitLines() { function removeUnitLines() {
table.querySelectorAll("tr").forEach(el => el.remove()); table.querySelectorAll('tr').forEach((el) => el.remove());
} }
function addUnitLine(u) { function addUnitLine(u) {
const row = document.createElement("tr"); const row = document.createElement('tr');
row.innerHTML = `<td><button type="button" data-tip="Click to select unit icon">${u.icon||" "}</button></td> row.innerHTML = `<td><button type="button" data-tip="Click to select unit icon">${u.icon || ' '}</button></td>
<td><input data-tip="Type unit name. If name is changed for existing unit, old unit will be replaced" value="${u.name}"></td> <td><input data-tip="Type unit name. If name is changed for existing unit, old unit will be replaced" value="${u.name}"></td>
<td><input data-tip="Enter conscription percentage for rural population" type="number" min=0 max=100 step=.01 value="${u.rural}"></td> <td><input data-tip="Enter conscription percentage for rural population" type="number" min=0 max=100 step=.01 value="${u.rural}"></td>
<td><input data-tip="Enter conscription percentage for urban population" type="number" min=0 max=100 step=.01 value="${u.urban}"></td> <td><input data-tip="Enter conscription percentage for urban population" type="number" min=0 max=100 step=.01 value="${u.urban}"></td>
<td><input data-tip="Enter average number of people in crew (used for total personnel calculation)" type="number" min=1 step=1 value="${u.crew}"></td> <td><input data-tip="Enter average number of people in crew (used for total personnel calculation)" type="number" min=1 step=1 value="${u.crew}"></td>
<td><input data-tip="Enter military power (used for battle simulation)" type="number" min=0 step=.1 value="${u.power}"></td> <td><input data-tip="Enter military power (used for battle simulation)" type="number" min=0 step=.1 value="${u.power}"></td>
<td><select data-tip="Select unit type to apply special rules on forces recalculation">${types.map(t => `<option ${u.type === t ? "selected" : ""} value="${t}">${t}</option>`).join(" ")}</select></td> <td><select data-tip="Select unit type to apply special rules on forces recalculation">${types
.map((t) => `<option ${u.type === t ? 'selected' : ''} value="${t}">${t}</option>`)
.join(' ')}</select></td>
<td data-tip="Check if unit is separate and can be stacked only with units of the same type"> <td data-tip="Check if unit is separate and can be stacked only with units of the same type">
<input id="${u.name}Separate" type="checkbox" class="checkbox" ${u.separate ? "checked" : ""}> <input id="${u.name}Separate" type="checkbox" class="checkbox" ${u.separate ? 'checked' : ''}>
<label for="${u.name}Separate" class="checkbox-label"></label></td> <label for="${u.name}Separate" class="checkbox-label"></label></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>`; <td data-tip="Remove the unit"><span data-tip="Remove unit type" class="icon-trash-empty pointer" onclick="this.parentElement.parentElement.remove();"></span></td>`;
row.querySelector("button").addEventListener("click", function(e) {selectIcon(this.innerHTML, v => this.innerHTML = v)}); row.querySelector('button').addEventListener('click', function (e) {
selectIcon(this.innerHTML, (v) => (this.innerHTML = v));
});
table.appendChild(row); table.appendChild(row);
} }
function restoreDefaultUnits() { function restoreDefaultUnits() {
removeUnitLines(); removeUnitLines();
Military.getDefaultOptions().map(u => addUnitLine(u)); Military.getDefaultOptions().map((u) => addUnitLine(u));
} }
function applyMilitaryOptions() { function applyMilitaryOptions() {
const unitLines = Array.from(table.querySelectorAll("tr")); const unitLines = Array.from(table.querySelectorAll('tr'));
const names = unitLines.map(r => r.querySelector("input").value.replace(/[&\/\\#, +()$~%.'":*?<>{}]/g, '_')); const names = unitLines.map((r) => r.querySelector('input').value.replace(/[&\/\\#, +()$~%.'":*?<>{}]/g, '_'));
if (new Set(names).size !== names.length) { if (new Set(names).size !== names.length) {
tip("All units should have unique names", false, "error"); tip('All units should have unique names', false, 'error');
return; return;
} }
$("#militaryOptions").dialog("close"); $('#militaryOptions').dialog('close');
options.military = unitLines.map((r, i) => { options.military = unitLines.map((r, i) => {
const [icon, name, rural, urban, crew, power, type, separate] = Array.from(r.querySelectorAll("input, select, button")).map(d => { const [icon, name, rural, urban, crew, power, type, separate] = Array.from(r.querySelectorAll('input, select, button')).map((d) => {
let value = d.value; let value = d.value;
if (d.type === "number") value = +d.value || 0; if (d.type === 'number') value = +d.value || 0;
if (d.type === "checkbox") value = +d.checked || 0; if (d.type === 'checkbox') value = +d.checked || 0;
if (d.type === "button") value = d.innerHTML || ""; if (d.type === 'button') value = d.innerHTML || '';
return value; return value;
}); });
return {icon, name:names[i], rural, urban, crew, power, type, separate}; return {icon, name: names[i], rural, urban, crew, power, type, separate};
}); });
localStorage.setItem("military", JSON.stringify(options.military)); localStorage.setItem('military', JSON.stringify(options.military));
Military.generate(); Military.generate();
updateHeaders(); updateHeaders();
addLines(); addLines();
} }
} }
function militaryRecalculate() { function militaryRecalculate() {
alertMessage.innerHTML = "Are you sure you want to recalculate military forces for all states?<br>Regiments for all states will be regenerated"; const message = 'Are you sure you want to recalculate military forces for all states?<br>Regiments for all states will be regenerated';
$("#alert").dialog({resizable: false, title: "Remove regiment", const onConfirm = () => {
buttons: { Military.generate();
Recalculate: function() { addLines();
$(this).dialog("close"); };
Military.generate(); confirmationDialog({title: 'Remove regiment', message, confirm: 'Remove', onConfirm});
addLines();
},
Cancel: function() {$(this).dialog("close");}
}
});
} }
function downloadMilitaryData() { function downloadMilitaryData() {
const units = options.military.map(u => u.name); const units = options.military.map((u) => u.name);
let data = "Id,State,"+units.map(u => capitalize(u)).join(",")+",Total,Population,Rate,War Alert\n"; // headers let data = 'Id,State,' + units.map((u) => capitalize(u)).join(',') + ',Total,Population,Rate,War Alert\n'; // headers
body.querySelectorAll(":scope > div").forEach(function(el) { body.querySelectorAll(':scope > div').forEach(function (el) {
data += el.dataset.id + ","; data += el.dataset.id + ',';
data += el.dataset.state + ","; data += el.dataset.state + ',';
data += units.map(u => el.dataset[u]).join(",") + ","; data += units.map((u) => el.dataset[u]).join(',') + ',';
data += el.dataset.total + ","; data += el.dataset.total + ',';
data += el.dataset.population + ","; data += el.dataset.population + ',';
data += rn(el.dataset.rate,2) + "%,"; data += rn(el.dataset.rate, 2) + '%,';
data += el.dataset.alert + "\n"; data += el.dataset.alert + '\n';
}); });
const name = getFileName("Military") + ".csv"; const name = getFileName('Military') + '.csv';
downloadFile(data, name); downloadFile(data, name);
} }
} }

View file

@ -1,74 +1,80 @@
"use strict"; 'use strict';
function editNamesbase() { function editNamesbase() {
if (customization) return; if (customization) return;
closeDialogs("#namesbaseEditor, .stable"); closeDialogs('#namesbaseEditor, .stable');
$("#namesbaseEditor").dialog(); $('#namesbaseEditor').dialog();
if (modules.editNamesbase) return; if (modules.editNamesbase) return;
modules.editNamesbase = true; modules.editNamesbase = true;
// add listeners // add listeners
document.getElementById("namesbaseSelect").addEventListener("change", updateInputs); document.getElementById('namesbaseSelect').addEventListener('change', updateInputs);
document.getElementById("namesbaseTextarea").addEventListener("change", updateNamesData); document.getElementById('namesbaseTextarea').addEventListener('change', updateNamesData);
document.getElementById("namesbaseUpdateExamples").addEventListener("click", updateExamples); document.getElementById('namesbaseUpdateExamples').addEventListener('click', updateExamples);
document.getElementById("namesbaseExamples").addEventListener("click", updateExamples); document.getElementById('namesbaseExamples').addEventListener('click', updateExamples);
document.getElementById("namesbaseName").addEventListener("input", updateBaseName); document.getElementById('namesbaseName').addEventListener('input', updateBaseName);
document.getElementById("namesbaseMin").addEventListener("input", updateBaseMin); document.getElementById('namesbaseMin').addEventListener('input', updateBaseMin);
document.getElementById("namesbaseMax").addEventListener("input", updateBaseMax); document.getElementById('namesbaseMax').addEventListener('input', updateBaseMax);
document.getElementById("namesbaseDouble").addEventListener("input", updateBaseDublication); document.getElementById('namesbaseDouble').addEventListener('input', updateBaseDublication);
document.getElementById("namesbaseAdd").addEventListener("click", namesbaseAdd); document.getElementById('namesbaseAdd').addEventListener('click', namesbaseAdd);
document.getElementById("namesbaseAnalize").addEventListener("click", analizeNamesbase); document.getElementById('namesbaseAnalize').addEventListener('click', analizeNamesbase);
document.getElementById("namesbaseDefault").addEventListener("click", namesbaseRestoreDefault); document.getElementById('namesbaseDefault').addEventListener('click', namesbaseRestoreDefault);
document.getElementById("namesbaseDownload").addEventListener("click", namesbaseDownload); document.getElementById('namesbaseDownload').addEventListener('click', namesbaseDownload);
document.getElementById("namesbaseUpload").addEventListener("click", () => namesbaseToLoad.click()); document.getElementById('namesbaseUpload').addEventListener('click', () => namesbaseToLoad.click());
document.getElementById("namesbaseToLoad").addEventListener("change", function() {uploadFile(this, namesbaseUpload)}); document.getElementById('namesbaseToLoad').addEventListener('change', function () {
document.getElementById("namesbaseSpeak").addEventListener("click", () => speak(namesbaseExamples.textContent)); uploadFile(this, namesbaseUpload);
});
document.getElementById('namesbaseSpeak').addEventListener('click', () => speak(namesbaseExamples.textContent));
createBasesList(); createBasesList();
updateInputs(); updateInputs();
$("#namesbaseEditor").dialog({ $('#namesbaseEditor').dialog({
title: "Namesbase Editor", width: "42.5em", title: 'Namesbase Editor',
position: {my: "center", at: "center", of: "svg"} width: '42.5em',
position: {my: 'center', at: 'center', of: 'svg'}
}); });
function createBasesList() { function createBasesList() {
const select = document.getElementById("namesbaseSelect"); const select = document.getElementById('namesbaseSelect');
select.innerHTML = ""; select.innerHTML = '';
nameBases.forEach((b, i) => select.options.add(new Option(b.name, i))); nameBases.forEach((b, i) => select.options.add(new Option(b.name, i)));
} }
function updateInputs() { function updateInputs() {
const base = +document.getElementById("namesbaseSelect").value; const base = +document.getElementById('namesbaseSelect').value;
if (!nameBases[base]) {tip(`Namesbase ${base} is not defined`, false, "error"); return;} if (!nameBases[base]) {
document.getElementById("namesbaseTextarea").value = nameBases[base].b; tip(`Namesbase ${base} is not defined`, false, 'error');
document.getElementById("namesbaseName").value = nameBases[base].name; return;
document.getElementById("namesbaseMin").value = nameBases[base].min; }
document.getElementById("namesbaseMax").value = nameBases[base].max; document.getElementById('namesbaseTextarea').value = nameBases[base].b;
document.getElementById("namesbaseDouble").value = nameBases[base].d; document.getElementById('namesbaseName').value = nameBases[base].name;
document.getElementById('namesbaseMin').value = nameBases[base].min;
document.getElementById('namesbaseMax').value = nameBases[base].max;
document.getElementById('namesbaseDouble').value = nameBases[base].d;
updateExamples(); updateExamples();
} }
function updateExamples() { function updateExamples() {
const base = +document.getElementById("namesbaseSelect").value; const base = +document.getElementById('namesbaseSelect').value;
let examples = ""; let examples = '';
for (let i=0; i < 10; i++) { for (let i = 0; i < 10; i++) {
const example = Names.getBase(base); const example = Names.getBase(base);
if (example === undefined) { if (example === undefined) {
examples = "Cannot generate examples. Please verify the data"; examples = 'Cannot generate examples. Please verify the data';
break; break;
} }
if (i) examples += ", "; if (i) examples += ', ';
examples += example; examples += example;
} }
document.getElementById("namesbaseExamples").innerHTML = examples; document.getElementById('namesbaseExamples').innerHTML = examples;
} }
function updateNamesData() { function updateNamesData() {
const base = +document.getElementById("namesbaseSelect").value; const base = +document.getElementById('namesbaseSelect').value;
const b = document.getElementById("namesbaseTextarea").value; const b = document.getElementById('namesbaseTextarea').value;
if (b.split(",").length < 3) { if (b.split(',').length < 3) {
tip("The names data provided is too short of incorrect", false, "error"); tip('The names data provided is too short of incorrect', false, 'error');
return; return;
} }
nameBases[base].b = b; nameBases[base].b = b;
@ -76,68 +82,91 @@ function editNamesbase() {
} }
function updateBaseName() { function updateBaseName() {
const base = +document.getElementById("namesbaseSelect").value; const base = +document.getElementById('namesbaseSelect').value;
const select = document.getElementById("namesbaseSelect"); const select = document.getElementById('namesbaseSelect');
select.options[namesbaseSelect.selectedIndex].innerHTML = this.value; select.options[namesbaseSelect.selectedIndex].innerHTML = this.value;
nameBases[base].name = this.value; nameBases[base].name = this.value;
} }
function updateBaseMin() { function updateBaseMin() {
const base = +document.getElementById("namesbaseSelect").value; const base = +document.getElementById('namesbaseSelect').value;
if (+this.value > nameBases[base].max) {tip("Minimal length cannot be greater than maximal", false, "error"); return;} if (+this.value > nameBases[base].max) {
tip('Minimal length cannot be greater than maximal', false, 'error');
return;
}
nameBases[base].min = +this.value; nameBases[base].min = +this.value;
} }
function updateBaseMax() { function updateBaseMax() {
const base = +document.getElementById("namesbaseSelect").value; const base = +document.getElementById('namesbaseSelect').value;
if (+this.value < nameBases[base].min) {tip("Maximal length should be greater than minimal", false, "error"); return;} if (+this.value < nameBases[base].min) {
tip('Maximal length should be greater than minimal', false, 'error');
return;
}
nameBases[base].max = +this.value; nameBases[base].max = +this.value;
} }
function updateBaseDublication() { function updateBaseDublication() {
const base = +document.getElementById("namesbaseSelect").value; const base = +document.getElementById('namesbaseSelect').value;
nameBases[base].d = this.value; nameBases[base].d = this.value;
} }
function analizeNamesbase() { function analizeNamesbase() {
const string = document.getElementById("namesbaseTextarea").value; const string = document.getElementById('namesbaseTextarea').value;
if (!string) {tip("Names data field should not be empty", false, "error"); return;} if (!string) {
tip('Names data field should not be empty', false, 'error');
return;
}
const base = string.toLowerCase(); const base = string.toLowerCase();
const array = base.split(","); const array = base.split(',');
const l = array.length; const l = array.length;
if (!l) {tip("Names data should not be empty", false, "error"); return;} if (!l) {
tip('Names data should not be empty', false, 'error');
return;
}
const wordsLength = array.map(n => n.length); const wordsLength = array.map((n) => n.length);
const multi = rn(d3.mean(array.map(n => (n.match(/ /i)||[]).length)) * 100, 2); const multi = rn(d3.mean(array.map((n) => (n.match(/ /i) || []).length)) * 100, 2);
const geminate = array.map(name => name.match(/[^\w\s]|(.)(?=\1)/g)||[]).flat(); const geminate = array.map((name) => name.match(/[^\w\s]|(.)(?=\1)/g) || []).flat();
const doubled = ([...new Set(geminate)].filter(l => geminate.filter(d => d === l).length > 3)||["none"]).join(""); const doubled = ([...new Set(geminate)].filter((l) => geminate.filter((d) => d === l).length > 3) || ['none']).join('');
const chain = Names.calculateChain(string); const chain = Names.calculateChain(string);
const depth = rn(d3.mean(Object.keys(chain).map(key => chain[key].filter(c => c !== " ").length))); const depth = rn(d3.mean(Object.keys(chain).map((key) => chain[key].filter((c) => c !== ' ').length)));
const nonLatin = (string.match(/[^\u0000-\u007f]/g)||["none"]).join(""); const nonLatin = (string.match(/[^\u0000-\u007f]/g) || ['none']).join('');
const lengthStat = const lengthStat =
l < 30 ? "<span style='color:red'>[not enough]</span>" : l < 30
l < 150 ? "<span style='color:darkred'>[low]</span>" : ? "<span style='color:red'>[not enough]</span>"
l < 150 ? "<span style='color:orange'>[low]</span>" : : l < 150
l < 400 ? "<span style='color:green'>[good]</span>" : ? "<span style='color:darkred'>[low]</span>"
l < 600 ? "<span style='color:orange'>[overmuch]</span>" : : l < 150
"<span style='color:darkred'>[overmuch]</span>"; ? "<span style='color:orange'>[low]</span>"
: l < 400
? "<span style='color:green'>[good]</span>"
: l < 600
? "<span style='color:orange'>[overmuch]</span>"
: "<span style='color:darkred'>[overmuch]</span>";
const rangeStat = const rangeStat =
l < 10 ? "<span style='color:red'>[low]</span>" : l < 10
l < 15 ? "<span style='color:darkred'>[low]</span>" : ? "<span style='color:red'>[low]</span>"
l < 20 ? "<span style='color:orange'>[low]</span>" : : l < 15
"<span style='color:green'>[good]</span>"; ? "<span style='color:darkred'>[low]</span>"
: l < 20
? "<span style='color:orange'>[low]</span>"
: "<span style='color:green'>[good]</span>";
const depthStat = const depthStat =
l < 15 ? "<span style='color:red'>[low]</span>" : l < 15
l < 20 ? "<span style='color:darkred'>[low]</span>" : ? "<span style='color:red'>[low]</span>"
l < 25 ? "<span style='color:orange'>[low]</span>" : : l < 20
"<span style='color:green'>[good]</span>"; ? "<span style='color:darkred'>[low]</span>"
: l < 25
? "<span style='color:orange'>[low]</span>"
: "<span style='color:green'>[good]</span>";
alertMessage.innerHTML = `<div style="line-height: 1.6em; max-width: 20em"> alertMessage.innerHTML = `<div style="line-height: 1.6em; max-width: 20em">
<div>Namesbase length: ${l} ${lengthStat}</div> <div>Namesbase length: ${l} ${lengthStat}</div>
<div>Namesbase range: ${Object.keys(chain).length-1} ${rangeStat}</div> <div>Namesbase range: ${Object.keys(chain).length - 1} ${rangeStat}</div>
<div>Namesbase depth: ${depth} ${depthStat}</div> <div>Namesbase depth: ${depth} ${depthStat}</div>
<div>Non-basic chars: ${nonLatin}</div> <div>Non-basic chars: ${nonLatin}</div>
<hr> <hr>
@ -148,58 +177,61 @@ function editNamesbase() {
<div>Doubled chars: ${doubled}</div> <div>Doubled chars: ${doubled}</div>
<div>Multi-word names: ${multi}%</div> <div>Multi-word names: ${multi}%</div>
</div>`; </div>`;
$("#alert").dialog({ $('#alert').dialog({
resizable: false, title: "Data Analysis", resizable: false,
position: {my: "left top-30", at: "right+10 top", of: "#namesbaseEditor"}, title: 'Data Analysis',
buttons: {OK: function() {$(this).dialog("close");}} position: {my: 'left top-30', at: 'right+10 top', of: '#namesbaseEditor'},
buttons: {
OK: function () {
$(this).dialog('close');
}
}
}); });
} }
function namesbaseAdd() { function namesbaseAdd() {
const base = nameBases.length; const base = nameBases.length;
const b = "This,is,an,example,of,name,base,showing,correct,format,It,should,have,at,least,one,hundred,names,separated,with,comma"; const b = 'This,is,an,example,of,name,base,showing,correct,format,It,should,have,at,least,one,hundred,names,separated,with,comma';
nameBases.push({name: "Base" + base, min: 5, max: 12, d: "", m: 0, b}); nameBases.push({name: 'Base' + base, min: 5, max: 12, d: '', m: 0, b});
document.getElementById("namesbaseSelect").add(new Option("Base" + base, base)); document.getElementById('namesbaseSelect').add(new Option('Base' + base, base));
document.getElementById("namesbaseSelect").value = base; document.getElementById('namesbaseSelect').value = base;
document.getElementById("namesbaseTextarea").value = b; document.getElementById('namesbaseTextarea').value = b;
document.getElementById("namesbaseName").value = "Base" + base; document.getElementById('namesbaseName').value = 'Base' + base;
document.getElementById("namesbaseMin").value = 5; document.getElementById('namesbaseMin').value = 5;
document.getElementById("namesbaseMax").value = 12; document.getElementById('namesbaseMax').value = 12;
document.getElementById("namesbaseDouble").value = ""; document.getElementById('namesbaseDouble').value = '';
document.getElementById("namesbaseExamples").innerHTML = "Please provide names data"; document.getElementById('namesbaseExamples').innerHTML = 'Please provide names data';
} }
function namesbaseRestoreDefault() { function namesbaseRestoreDefault() {
alertMessage.innerHTML = `Are you sure you want to restore default namesbase?`; const message = 'Are you sure you want to restore default namesbase? <br>This action cannot be reverted';
$("#alert").dialog({resizable: false, title: "Restore default data", const onConfirm = () => {
buttons: { Names.clearChains();
Restore: function() { nameBases = Names.getNameBases();
$(this).dialog("close"); createBasesList();
Names.clearChains(); updateInputs();
nameBases = Names.getNameBases(); };
createBasesList(); confirmationDialog({title: 'Restore default data', message, confirm: 'Restore', onConfirm});
updateInputs();
},
Cancel: function() {$(this).dialog("close");}
}
});
} }
function namesbaseDownload() { function namesbaseDownload() {
const data = nameBases.map((b,i) => `${b.name}|${b.min}|${b.max}|${b.d}|${b.m}|${b.b}`).join("\r\n"); const data = nameBases.map((b, i) => `${b.name}|${b.min}|${b.max}|${b.d}|${b.m}|${b.b}`).join('\r\n');
const name = getFileName("Namesbase") + ".txt"; const name = getFileName('Namesbase') + '.txt';
downloadFile(data, name); downloadFile(data, name);
} }
function namesbaseUpload(dataLoaded) { function namesbaseUpload(dataLoaded) {
const data = dataLoaded.split("\r\n"); const data = dataLoaded.split('\r\n');
if (!data || !data[0]) {tip("Cannot load a namesbase. Please check the data format", false, "error"); return;} if (!data || !data[0]) {
tip('Cannot load a namesbase. Please check the data format', false, 'error');
return;
}
Names.clearChains(); Names.clearChains();
nameBases = []; nameBases = [];
data.forEach(d => { data.forEach((d) => {
const e = d.split("|"); const e = d.split('|');
nameBases.push({name:e[0], min:e[1], max:e[2], d:e[3], m:e[4], b:e[5]}); nameBases.push({name: e[0], min: e[1], max: e[2], d: e[3], m: e[4], b: e[5]});
}); });
createBasesList(); createBasesList();

View file

@ -1,16 +1,18 @@
"use strict"; 'use strict';
function editNotes(id, name) { function editNotes(id, name) {
// update list of objects // update list of objects
const select = document.getElementById("notesSelect"); const select = document.getElementById('notesSelect');
select.options.length = 0; select.options.length = 0;
for (const note of notes) {select.options.add(new Option(note.id, note.id));} for (const note of notes) {
select.options.add(new Option(note.id, note.id));
}
// initiate pell (html editor) // initiate pell (html editor)
const editor = Pell.init({ const editor = Pell.init({
element: document.getElementById("notesText"), element: document.getElementById('notesText'),
onChange: html => { onChange: (html) => {
const id = document.getElementById("notesSelect").value; const id = document.getElementById('notesSelect').value;
const note = notes.find(note => note.id === id); const note = notes.find((note) => note.id === id);
if (!note) return; if (!note) return;
note.legend = html; note.legend = html;
showNote(note); showNote(note);
@ -20,10 +22,10 @@ function editNotes(id, name) {
// select an object // select an object
if (notes.length || id) { if (notes.length || id) {
if (!id) id = notes[0].id; if (!id) id = notes[0].id;
let note = notes.find(note => note.id === id); let note = notes.find((note) => note.id === id);
if (note === undefined) { if (note === undefined) {
if (!name) name = id; if (!name) name = id;
note = {id, name, legend: ""}; note = {id, name, legend: ''};
notes.push(note); notes.push(note);
select.options.add(new Option(id, id)); select.options.add(new Option(id, id));
} }
@ -32,65 +34,63 @@ function editNotes(id, name) {
editor.content.innerHTML = note.legend; editor.content.innerHTML = note.legend;
showNote(note); showNote(note);
} else { } else {
editor.content.innerHTML = "There are no added notes. Click on element (e.g. label) and add a free text note"; editor.content.innerHTML = 'There are no added notes. Click on element (e.g. label) and add a free text note';
document.getElementById("notesName").value = ""; document.getElementById('notesName').value = '';
} }
// open a dialog // open a dialog
$("#notesEditor").dialog({ $('#notesEditor').dialog({
title: "Notes Editor", minWidth: "40em", width: "50vw", title: 'Notes Editor',
position: {my: "center", at: "center", of: "svg"}, minWidth: '40em',
close: () => notesText.innerHTML = "" width: '50vw',
position: {my: 'center', at: 'center', of: 'svg'},
close: () => (notesText.innerHTML = '')
}); });
if (modules.editNotes) return; if (modules.editNotes) return;
modules.editNotes = true; modules.editNotes = true;
// add listeners // add listeners
document.getElementById("notesSelect").addEventListener("change", changeObject); document.getElementById('notesSelect').addEventListener('change', changeObject);
document.getElementById("notesName").addEventListener("input", changeName); document.getElementById('notesName').addEventListener('input', changeName);
document.getElementById("notesPin").addEventListener("click", () => options.pinNotes = !options.pinNotes); document.getElementById('notesPin').addEventListener('click', () => (options.pinNotes = !options.pinNotes));
document.getElementById("notesSpeak").addEventListener("click", () => speak(editor.content.innerHTML)); document.getElementById('notesSpeak').addEventListener('click', () => speak(editor.content.innerHTML));
document.getElementById("notesFocus").addEventListener("click", validateHighlightElement); document.getElementById('notesFocus').addEventListener('click', validateHighlightElement);
document.getElementById("notesDownload").addEventListener("click", downloadLegends); document.getElementById('notesDownload').addEventListener('click', downloadLegends);
document.getElementById("notesUpload").addEventListener("click", () => legendsToLoad.click()); document.getElementById('notesUpload').addEventListener('click', () => legendsToLoad.click());
document.getElementById("legendsToLoad").addEventListener("change", function() {uploadFile(this, uploadLegends)}); document.getElementById('legendsToLoad').addEventListener('change', function () {
document.getElementById("notesRemove").addEventListener("click", triggerNotesRemove); uploadFile(this, uploadLegends);
});
document.getElementById('notesRemove').addEventListener('click', triggerNotesRemove);
function showNote(note) { function showNote(note) {
document.getElementById("notes").style.display = "block"; document.getElementById('notes').style.display = 'block';
document.getElementById("notesHeader").innerHTML = note.name; document.getElementById('notesHeader').innerHTML = note.name;
document.getElementById("notesBody").innerHTML = note.legend; document.getElementById('notesBody').innerHTML = note.legend;
} }
function changeObject() { function changeObject() {
const note = notes.find(note => note.id === this.value); const note = notes.find((note) => note.id === this.value);
if (!note) return; if (!note) return;
notesName.value = note.name; notesName.value = note.name;
editor.content.innerHTML = note.legend; editor.content.innerHTML = note.legend;
} }
function changeName() { function changeName() {
const id = document.getElementById("notesSelect").value; const id = document.getElementById('notesSelect').value;
const note = notes.find(note => note.id === id); const note = notes.find((note) => note.id === id);
if (!note) return; if (!note) return;
note.name = this.value; note.name = this.value;
showNote(note); showNote(note);
} }
function validateHighlightElement() { function validateHighlightElement() {
const select = document.getElementById("notesSelect"); const select = document.getElementById('notesSelect');
const element = document.getElementById(select.value); const element = document.getElementById(select.value);
// if element is not found
if (element === null) { if (element === null) {
alertMessage.innerHTML = "Related element is not found. Would you like to remove the note?"; const message = 'Related element is not found. Would you like to remove the note?';
$("#alert").dialog({resizable: false, title: "Element not found", confirmationDialog({title: 'Element not found', message, confirm: 'Remove', onConfirm: removeLegend});
buttons: {
Remove: function() {$(this).dialog("close"); removeLegend();},
Keep: function() {$(this).dialog("close");}
}
});
return; return;
} }
@ -99,35 +99,29 @@ function editNotes(id, name) {
function downloadLegends() { function downloadLegends() {
const data = JSON.stringify(notes); const data = JSON.stringify(notes);
const name = getFileName("Notes") + ".txt"; const name = getFileName('Notes') + '.txt';
downloadFile(data, name); downloadFile(data, name);
} }
function uploadLegends(dataLoaded) { function uploadLegends(dataLoaded) {
if (!dataLoaded) {tip("Cannot load the file. Please check the data format", false, "error"); return;} if (!dataLoaded) return tip('Cannot load the file. Please check the data format', false, 'error');
notes = JSON.parse(dataLoaded); notes = JSON.parse(dataLoaded);
document.getElementById("notesSelect").options.length = 0; document.getElementById('notesSelect').options.length = 0;
editNotes(notes[0].id, notes[0].name); editNotes(notes[0].id, notes[0].name);
} }
function triggerNotesRemove() { function triggerNotesRemove() {
alertMessage.innerHTML = "Are you sure you want to remove the selected note?"; const message = 'Are you sure you want to remove the selected note? <br>This action cannot be reverted';
$("#alert").dialog({resizable: false, title: "Remove note", confirmationDialog({title: 'Remove note', message, confirm: 'Remove', onConfirm: removeLegend});
buttons: {
Remove: function() {$(this).dialog("close"); removeLegend();},
Keep: function() {$(this).dialog("close");}
}
});
} }
function removeLegend() { function removeLegend() {
const select = document.getElementById("notesSelect"); const select = document.getElementById('notesSelect');
const index = notes.findIndex(n => n.id === select.value); const index = notes.findIndex((n) => n.id === select.value);
notes.splice(index, 1); notes.splice(index, 1);
select.options.length = 0; select.options.length = 0;
if (!notes.length) {$("#notesEditor").dialog("close"); return;} if (!notes.length) return $('#notesEditor').dialog('close');
notesText.innerHTML = ""; notesText.innerHTML = '';
editNotes(notes[0].id, notes[0].name); editNotes(notes[0].id, notes[0].name);
} }
} }

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -1,85 +1,95 @@
"use strict"; 'use strict';
function editRegiment(selector) { function editRegiment(selector) {
if (customization) return; if (customization) return;
closeDialogs(".stable"); closeDialogs('.stable');
if (!layerIsOn("toggleMilitary")) toggleMilitary(); if (!layerIsOn('toggleMilitary')) toggleMilitary();
armies.selectAll(":scope > g").classed("draggable", true); armies.selectAll(':scope > g').classed('draggable', true);
armies.selectAll(":scope > g > g").call(d3.drag().on("drag", dragRegiment)); armies.selectAll(':scope > g > g').call(d3.drag().on('drag', dragRegiment));
elSelected = selector ? document.querySelector(selector) : d3.event.target.parentElement; // select g element elSelected = selector ? document.querySelector(selector) : d3.event.target.parentElement; // select g element
if (!pack.states[elSelected.dataset.state]) return; if (!pack.states[elSelected.dataset.state]) return;
if (!regiment()) return; if (!regiment()) return;
updateRegimentData(regiment()); updateRegimentData(regiment());
drawBase(); drawBase();
$("#regimentEditor").dialog({ $('#regimentEditor').dialog({
title: "Edit Regiment", resizable: false, close: closeEditor, title: 'Edit Regiment',
position: {my: "left top", at: "left+10 top+10", of: "#map"} resizable: false,
close: closeEditor,
position: {my: 'left top', at: 'left+10 top+10', of: '#map'}
}); });
if (modules.editRegiment) return; if (modules.editRegiment) return;
modules.editRegiment = true; modules.editRegiment = true;
// add listeners // add listeners
document.getElementById("regimentNameRestore").addEventListener("click", restoreName); document.getElementById('regimentNameRestore').addEventListener('click', restoreName);
document.getElementById("regimentType").addEventListener("click", changeType); document.getElementById('regimentType').addEventListener('click', changeType);
document.getElementById("regimentName").addEventListener("change", changeName); document.getElementById('regimentName').addEventListener('change', changeName);
document.getElementById("regimentEmblem").addEventListener("input", changeEmblem); document.getElementById('regimentEmblem').addEventListener('input', changeEmblem);
document.getElementById("regimentEmblemSelect").addEventListener("click", selectEmblem); document.getElementById('regimentEmblemSelect').addEventListener('click', selectEmblem);
document.getElementById("regimentAttack").addEventListener("click", toggleAttack); document.getElementById('regimentAttack').addEventListener('click', toggleAttack);
document.getElementById("regimentRegenerateLegend").addEventListener("click", regenerateLegend); document.getElementById('regimentRegenerateLegend').addEventListener('click', regenerateLegend);
document.getElementById("regimentLegend").addEventListener("click", editLegend); document.getElementById('regimentLegend').addEventListener('click', editLegend);
document.getElementById("regimentSplit").addEventListener("click", splitRegiment); document.getElementById('regimentSplit').addEventListener('click', splitRegiment);
document.getElementById("regimentAdd").addEventListener("click", toggleAdd); document.getElementById('regimentAdd').addEventListener('click', toggleAdd);
document.getElementById("regimentAttach").addEventListener("click", toggleAttach); document.getElementById('regimentAttach').addEventListener('click', toggleAttach);
document.getElementById("regimentRemove").addEventListener("click", removeRegiment); document.getElementById('regimentRemove').addEventListener('click', removeRegiment);
// get regiment data element // get regiment data element
function regiment() { function regiment() {
return pack.states[elSelected.dataset.state].military.find(r => r.i == elSelected.dataset.id); return pack.states[elSelected.dataset.state].military.find((r) => r.i == elSelected.dataset.id);
} }
function updateRegimentData(regiment) { function updateRegimentData(regiment) {
document.getElementById("regimentType").className = regiment.n ? "icon-anchor" :"icon-users"; document.getElementById('regimentType').className = regiment.n ? 'icon-anchor' : 'icon-users';
document.getElementById("regimentName").value = regiment.name; document.getElementById('regimentName').value = regiment.name;
document.getElementById("regimentEmblem").value = regiment.icon; document.getElementById('regimentEmblem').value = regiment.icon;
const composition = document.getElementById("regimentComposition"); const composition = document.getElementById('regimentComposition');
composition.innerHTML = options.military.map(u => { composition.innerHTML = options.military
return `<div data-tip="${capitalize(u.name)} number. Input to change"> .map((u) => {
return `<div data-tip="${capitalize(u.name)} number. Input to change">
<div class="label">${capitalize(u.name)}:</div> <div class="label">${capitalize(u.name)}:</div>
<input data-u="${u.name}" type="number" min=0 step=1 value="${(regiment.u[u.name]||0)}"> <input data-u="${u.name}" type="number" min=0 step=1 value="${regiment.u[u.name] || 0}">
<i>${u.type}</i></div>` <i>${u.type}</i></div>`;
}).join(""); })
.join('');
composition.querySelectorAll("input").forEach(el => el.addEventListener("change", changeUnit)); composition.querySelectorAll('input').forEach((el) => el.addEventListener('change', changeUnit));
} }
function drawBase() { function drawBase() {
const reg = regiment(); const reg = regiment();
const clr = pack.states[elSelected.dataset.state].color; const clr = pack.states[elSelected.dataset.state].color;
const base = viewbox.insert("g", "g#armies").attr("id", "regimentBase").attr("stroke-width", .3).attr("stroke", "#000").attr("cursor", "move"); const base = viewbox.insert('g', 'g#armies').attr('id', 'regimentBase').attr('stroke-width', 0.3).attr('stroke', '#000').attr('cursor', 'move');
base.on("mouseenter", () => {tip("Regiment base. Drag to re-base the regiment", true);}).on("mouseleave", () => {tip('', true);}); base
.on('mouseenter', () => {
tip('Regiment base. Drag to re-base the regiment', true);
})
.on('mouseleave', () => {
tip('', true);
});
base.append("line").attr("x1", reg.bx).attr("y1", reg.by).attr("x2", reg.x).attr("y2", reg.y).attr("class", "dragLine"); base.append('line').attr('x1', reg.bx).attr('y1', reg.by).attr('x2', reg.x).attr('y2', reg.y).attr('class', 'dragLine');
base.append("circle").attr("cx", reg.bx).attr("cy", reg.by).attr("r", 2).attr("fill", clr).call(d3.drag().on("drag", dragBase)); base.append('circle').attr('cx', reg.bx).attr('cy', reg.by).attr('r', 2).attr('fill', clr).call(d3.drag().on('drag', dragBase));
} }
function changeType() { function changeType() {
const reg = regiment(); const reg = regiment();
reg.n = +!reg.n; reg.n = +!reg.n;
document.getElementById("regimentType").className = reg.n ? "icon-anchor" :"icon-users"; document.getElementById('regimentType').className = reg.n ? 'icon-anchor' : 'icon-users';
const size = +armies.attr("box-size"); const size = +armies.attr('box-size');
const baseRect = elSelected.querySelectorAll("rect")[0]; const baseRect = elSelected.querySelectorAll('rect')[0];
const iconRect = elSelected.querySelectorAll("rect")[1]; const iconRect = elSelected.querySelectorAll('rect')[1];
const icon = elSelected.querySelector(".regimentIcon"); const icon = elSelected.querySelector('.regimentIcon');
const x = reg.n ? reg.x-size*2 : reg.x-size*3; const x = reg.n ? reg.x - size * 2 : reg.x - size * 3;
baseRect.setAttribute("x", x); baseRect.setAttribute('x', x);
baseRect.setAttribute("width", reg.n ? size*4 : size*6); baseRect.setAttribute('width', reg.n ? size * 4 : size * 6);
iconRect.setAttribute("x", x - size*2); iconRect.setAttribute('x', x - size * 2);
icon.setAttribute("x", x - size); icon.setAttribute('x', x - size);
elSelected.querySelector("text").innerHTML = Military.getTotal(reg); elSelected.querySelector('text').innerHTML = Military.getTotal(reg);
} }
function changeName() { function changeName() {
@ -87,49 +97,64 @@ function editRegiment(selector) {
} }
function restoreName() { function restoreName() {
const reg = regiment(), regs = pack.states[elSelected.dataset.state].military; const reg = regiment(),
regs = pack.states[elSelected.dataset.state].military;
const name = Military.getName(reg, regs); const name = Military.getName(reg, regs);
elSelected.dataset.name = reg.name = document.getElementById("regimentName").value = name; elSelected.dataset.name = reg.name = document.getElementById('regimentName').value = name;
} }
function selectEmblem() { function selectEmblem() {
selectIcon(regimentEmblem.value, v => {regimentEmblem.value = v; changeEmblem()}); selectIcon(regimentEmblem.value, (v) => {
regimentEmblem.value = v;
changeEmblem();
});
} }
function changeEmblem() { function changeEmblem() {
const emblem = document.getElementById("regimentEmblem").value; const emblem = document.getElementById('regimentEmblem').value;
regiment().icon = elSelected.querySelector(".regimentIcon").innerHTML = emblem; regiment().icon = elSelected.querySelector('.regimentIcon').innerHTML = emblem;
} }
function changeUnit() { function changeUnit() {
const u = this.dataset.u; const u = this.dataset.u;
const reg = regiment(); const reg = regiment();
reg.u[u] = (+this.value)||0; reg.u[u] = +this.value || 0;
reg.a = d3.sum(Object.values(reg.u)); reg.a = d3.sum(Object.values(reg.u));
elSelected.querySelector("text").innerHTML = Military.getTotal(reg); elSelected.querySelector('text').innerHTML = Military.getTotal(reg);
if (militaryOverviewRefresh.offsetParent) militaryOverviewRefresh.click(); if (militaryOverviewRefresh.offsetParent) militaryOverviewRefresh.click();
if (regimentsOverviewRefresh.offsetParent) regimentsOverviewRefresh.click(); if (regimentsOverviewRefresh.offsetParent) regimentsOverviewRefresh.click();
} }
function splitRegiment() { function splitRegiment() {
const reg = regiment(), u1 = reg.u; const reg = regiment(),
const state = +elSelected.dataset.state, military = pack.states[state].military; u1 = reg.u;
const i = last(military).i + 1, u2 = Object.assign({}, u1); // u clone const state = +elSelected.dataset.state,
military = pack.states[state].military;
const i = last(military).i + 1,
u2 = Object.assign({}, u1); // u clone
Object.keys(u2).forEach(u => u2[u] = Math.floor(u2[u]/2)); // halved new reg Object.keys(u2).forEach((u) => (u2[u] = Math.floor(u2[u] / 2))); // halved new reg
const a = d3.sum(Object.values(u2)); // new reg total const a = d3.sum(Object.values(u2)); // new reg total
if (!a) {tip("Not enough forces to split", false, "error"); return}; // nothing to add if (!a) {
tip('Not enough forces to split', false, 'error');
return;
} // nothing to add
// update old regiment // update old regiment
Object.keys(u1).forEach(u => u1[u] = Math.ceil(u1[u]/2)); // halved old reg Object.keys(u1).forEach((u) => (u1[u] = Math.ceil(u1[u] / 2))); // halved old reg
reg.a = d3.sum(Object.values(u1)); // old reg total reg.a = d3.sum(Object.values(u1)); // old reg total
regimentComposition.querySelectorAll("input").forEach(el => el.value = reg.u[el.dataset.u]||0); regimentComposition.querySelectorAll('input').forEach((el) => (el.value = reg.u[el.dataset.u] || 0));
elSelected.querySelector("text").innerHTML = Military.getTotal(reg); elSelected.querySelector('text').innerHTML = Military.getTotal(reg);
// create new regiment // create new regiment
const shift = +armies.attr("box-size") * 2; const shift = +armies.attr('box-size') * 2;
const y = function(x, y) {do {y+=shift} while (military.find(r => r.x === x && r.y === y)); return y;} const y = function (x, y) {
const newReg = {a, cell:reg.cell, i, n:reg.n, u:u2, x:reg.x, y:y(reg.x, reg.y), bx:reg.bx, by:reg.by, state, icon: reg.icon}; do {
y += shift;
} while (military.find((r) => r.x === x && r.y === y));
return y;
};
const newReg = {a, cell: reg.cell, i, n: reg.n, u: u2, x: reg.x, y: y(reg.x, reg.y), bx: reg.bx, by: reg.by, state, icon: reg.icon};
newReg.name = Military.getName(newReg, military); newReg.name = Military.getName(newReg, military);
military.push(newReg); military.push(newReg);
Military.generateNote(newReg, pack.states[state]); // add legend Military.generateNote(newReg, pack.states[state]); // add legend
@ -139,24 +164,26 @@ function editRegiment(selector) {
} }
function toggleAdd() { function toggleAdd() {
document.getElementById("regimentAdd").classList.toggle("pressed"); document.getElementById('regimentAdd').classList.toggle('pressed');
if (document.getElementById("regimentAdd").classList.contains("pressed")) { if (document.getElementById('regimentAdd').classList.contains('pressed')) {
viewbox.style("cursor", "crosshair").on("click", addRegimentOnClick); viewbox.style('cursor', 'crosshair').on('click', addRegimentOnClick);
tip("Click on map to create new regiment or fleet", true); tip('Click on map to create new regiment or fleet', true);
} else { } else {
clearMainTip(); clearMainTip();
viewbox.on("click", clicked).style("cursor", "default"); viewbox.on('click', clicked).style('cursor', 'default');
} }
} }
function addRegimentOnClick() { function addRegimentOnClick() {
const point = d3.mouse(this); const point = d3.mouse(this);
const cell = findCell(point[0], point[1]); const cell = findCell(point[0], point[1]);
const x = pack.cells.p[cell][0], y = pack.cells.p[cell][1]; const x = pack.cells.p[cell][0],
const state = +elSelected.dataset.state, military = pack.states[state].military; y = pack.cells.p[cell][1];
const state = +elSelected.dataset.state,
military = pack.states[state].military;
const i = military.length ? last(military).i + 1 : 0; const i = military.length ? last(military).i + 1 : 0;
const n = +(pack.cells.h[cell] < 20); // naval or land const n = +(pack.cells.h[cell] < 20); // naval or land
const reg = {a:0, cell, i, n, u:{}, x, y, bx:x, by:y, state, icon:"🛡️"}; const reg = {a: 0, cell, i, n, u: {}, x, y, bx: x, by: y, state, icon: '🛡️'};
reg.name = Military.getName(reg, military); reg.name = Military.getName(reg, military);
military.push(reg); military.push(reg);
Military.generateNote(reg, pack.states[state]); // add legend Military.generateNote(reg, pack.states[state]); // add legend
@ -166,92 +193,124 @@ function editRegiment(selector) {
} }
function toggleAttack() { function toggleAttack() {
document.getElementById("regimentAttack").classList.toggle("pressed"); document.getElementById('regimentAttack').classList.toggle('pressed');
if (document.getElementById("regimentAttack").classList.contains("pressed")) { if (document.getElementById('regimentAttack').classList.contains('pressed')) {
viewbox.style("cursor", "crosshair").on("click", attackRegimentOnClick); viewbox.style('cursor', 'crosshair').on('click', attackRegimentOnClick);
tip("Click on another regiment to initiate battle", true); tip('Click on another regiment to initiate battle', true);
armies.selectAll(":scope > g").classed("draggable", false); armies.selectAll(':scope > g').classed('draggable', false);
} else { } else {
clearMainTip(); clearMainTip();
armies.selectAll(":scope > g").classed("draggable", true); armies.selectAll(':scope > g').classed('draggable', true);
viewbox.on("click", clicked).style("cursor", "default"); viewbox.on('click', clicked).style('cursor', 'default');
} }
} }
function attackRegimentOnClick() { function attackRegimentOnClick() {
const target = d3.event.target, regSelected = target.parentElement, army = regSelected.parentElement; const target = d3.event.target,
const oldState = +elSelected.dataset.state, newState = +regSelected.dataset.state; regSelected = target.parentElement,
army = regSelected.parentElement;
const oldState = +elSelected.dataset.state,
newState = +regSelected.dataset.state;
if (army.parentElement.id !== "armies") {tip("Please click on a regiment to attack", false, "error"); return;} if (army.parentElement.id !== 'armies') {
if (regSelected === elSelected) {tip("Regiment cannot attack itself", false, "error"); return;} tip('Please click on a regiment to attack', false, 'error');
if (oldState === newState) {tip("Cannot attack fraternal regiment", false, "error"); return;} return;
}
if (regSelected === elSelected) {
tip('Regiment cannot attack itself', false, 'error');
return;
}
if (oldState === newState) {
tip('Cannot attack fraternal regiment', false, 'error');
return;
}
const attacker = regiment(); const attacker = regiment();
const defender = pack.states[regSelected.dataset.state].military.find(r => r.i == regSelected.dataset.id); const defender = pack.states[regSelected.dataset.state].military.find((r) => r.i == regSelected.dataset.id);
if (!attacker.a || !defender.a) {tip("Regiment has no troops to battle", false, "error"); return;} if (!attacker.a || !defender.a) {
tip('Regiment has no troops to battle', false, 'error');
return;
}
// save initial position to temp attribute // save initial position to temp attribute
attacker.px = attacker.x, attacker.py = attacker.y; (attacker.px = attacker.x), (attacker.py = attacker.y);
defender.px = defender.x, defender.py = defender.y; (defender.px = defender.x), (defender.py = defender.y);
// move attacker to defender // move attacker to defender
Military.moveRegiment(attacker, defender.x, defender.y-8); Military.moveRegiment(attacker, defender.x, defender.y - 8);
// draw battle icon // draw battle icon
const attack = d3.transition().delay(300).duration(700).ease(d3.easeSinInOut).on("end", () => new Battle(attacker, defender)); const attack = d3
svg.append("text").attr("x", window.innerWidth/2).attr("y", window.innerHeight/2) .transition()
.text("⚔️").attr("font-size", 0).attr("opacity", 1) .delay(300)
.style("dominant-baseline", "central").style("text-anchor", "middle") .duration(700)
.transition(attack).attr("font-size", 1000).attr("opacity", .2).remove(); .ease(d3.easeSinInOut)
.on('end', () => new Battle(attacker, defender));
svg
.append('text')
.attr('x', window.innerWidth / 2)
.attr('y', window.innerHeight / 2)
.text('⚔️')
.attr('font-size', 0)
.attr('opacity', 1)
.style('dominant-baseline', 'central')
.style('text-anchor', 'middle')
.transition(attack)
.attr('font-size', 1000)
.attr('opacity', 0.2)
.remove();
clearMainTip(); clearMainTip();
$("#regimentEditor").dialog("close"); $('#regimentEditor').dialog('close');
} }
function toggleAttach() { function toggleAttach() {
document.getElementById("regimentAttach").classList.toggle("pressed"); document.getElementById('regimentAttach').classList.toggle('pressed');
if (document.getElementById("regimentAttach").classList.contains("pressed")) { if (document.getElementById('regimentAttach').classList.contains('pressed')) {
viewbox.style("cursor", "crosshair").on("click", attachRegimentOnClick); viewbox.style('cursor', 'crosshair').on('click', attachRegimentOnClick);
tip("Click on another regiment to unite both regiments. The current regiment will be removed", true); tip('Click on another regiment to unite both regiments. The current regiment will be removed', true);
armies.selectAll(":scope > g").classed("draggable", false); armies.selectAll(':scope > g').classed('draggable', false);
} else { } else {
clearMainTip(); clearMainTip();
armies.selectAll(":scope > g").classed("draggable", true); armies.selectAll(':scope > g').classed('draggable', true);
viewbox.on("click", clicked).style("cursor", "default"); viewbox.on('click', clicked).style('cursor', 'default');
} }
} }
function attachRegimentOnClick() { function attachRegimentOnClick() {
const target = d3.event.target, regSelected = target.parentElement, army = regSelected.parentElement; const target = d3.event.target,
const oldState = +elSelected.dataset.state, newState = +regSelected.dataset.state; regSelected = target.parentElement,
army = regSelected.parentElement;
const oldState = +elSelected.dataset.state,
newState = +regSelected.dataset.state;
if (army.parentElement.id !== "armies") {tip("Please click on a regiment", false, "error"); return;} if (army.parentElement.id !== 'armies') return tip('Please click on a regiment', false, 'error');
if (regSelected === elSelected) {tip("Cannot attach regiment to itself. Please click on another regiment", false, "error"); return;} if (regSelected === elSelected) return tip('Cannot attach regiment to itself. Please click on another regiment', false, 'error');
const reg = regiment(); // reg to be attached const reg = regiment(); // reg to be attached
const sel = pack.states[newState].military.find(r => r.i == regSelected.dataset.id); // reg to attach to const sel = pack.states[newState].military.find((r) => r.i == regSelected.dataset.id); // reg to attach to
for (const unit of options.military) { for (const unit of options.military) {
const u = unit.name; const u = unit.name;
if (reg.u[u]) sel.u[u] ? sel.u[u] += reg.u[u] : sel.u[u] = reg.u[u]; if (reg.u[u]) sel.u[u] ? (sel.u[u] += reg.u[u]) : (sel.u[u] = reg.u[u]);
} }
sel.a = d3.sum(Object.values(sel.u)); // reg total sel.a = d3.sum(Object.values(sel.u)); // reg total
regSelected.querySelector("text").innerHTML = Military.getTotal(sel); // update selected reg total text regSelected.querySelector('text').innerHTML = Military.getTotal(sel); // update selected reg total text
// remove attached regiment // remove attached regiment
const military = pack.states[oldState].military; const military = pack.states[oldState].military;
military.splice(military.indexOf(reg), 1); military.splice(military.indexOf(reg), 1);
const index = notes.findIndex(n => n.id === elSelected.id); const index = notes.findIndex((n) => n.id === elSelected.id);
if (index != -1) notes.splice(index, 1); if (index != -1) notes.splice(index, 1);
elSelected.remove(); elSelected.remove();
if (regimentsOverviewRefresh.offsetParent) regimentsOverviewRefresh.click(); if (regimentsOverviewRefresh.offsetParent) regimentsOverviewRefresh.click();
$("#regimentEditor").dialog("close"); $('#regimentEditor').dialog('close');
editRegiment("#"+regSelected.id); editRegiment('#' + regSelected.id);
} }
function regenerateLegend() { function regenerateLegend() {
const index = notes.findIndex(n => n.id === elSelected.id); const index = notes.findIndex((n) => n.id === elSelected.id);
if (index != -1) notes.splice(index, 1); if (index != -1) notes.splice(index, 1);
const s = pack.states[elSelected.dataset.state]; const s = pack.states[elSelected.dataset.state];
@ -263,85 +322,83 @@ function editRegiment(selector) {
} }
function removeRegiment() { function removeRegiment() {
alertMessage.innerHTML = "Are you sure you want to remove the regiment?"; const message = 'Are you sure you want to remove the regiment? <br>This action cannot be reverted';
$("#alert").dialog({resizable: false, title: "Remove regiment", const onConfirm = () => {
buttons: { const military = pack.states[elSelected.dataset.state].military;
Remove: function() { const regIndex = military.indexOf(regiment());
$(this).dialog("close"); if (regIndex === -1) return;
const military = pack.states[elSelected.dataset.state].military; military.splice(regIndex, 1);
const regIndex = military.indexOf(regiment());
if (regIndex === -1) return;
military.splice(regIndex, 1);
const index = notes.findIndex(n => n.id === elSelected.id); const index = notes.findIndex((n) => n.id === elSelected.id);
if (index != -1) notes.splice(index, 1); if (index != -1) notes.splice(index, 1);
elSelected.remove(); elSelected.remove();
if (militaryOverviewRefresh.offsetParent) militaryOverviewRefresh.click(); if (militaryOverviewRefresh.offsetParent) militaryOverviewRefresh.click();
if (regimentsOverviewRefresh.offsetParent) regimentsOverviewRefresh.click(); if (regimentsOverviewRefresh.offsetParent) regimentsOverviewRefresh.click();
$("#regimentEditor").dialog("close"); $('#regimentEditor').dialog('close');
}, };
Cancel: function() {$(this).dialog("close");} confirmationDialog({title: 'Remove regiment', message, confirm: 'Remove', onConfirm});
}
});
} }
function dragRegiment() { function dragRegiment() {
d3.select(this).raise(); d3.select(this).raise();
d3.select(this.parentNode).raise(); d3.select(this.parentNode).raise();
const reg = pack.states[this.dataset.state].military.find(r => r.i == this.dataset.id); const reg = pack.states[this.dataset.state].military.find((r) => r.i == this.dataset.id);
const size = +armies.attr("box-size"); const size = +armies.attr('box-size');
const w = reg.n ? size * 4 : size * 6; const w = reg.n ? size * 4 : size * 6;
const h = size * 2; const h = size * 2;
const x1 = x => rn(x - w / 2, 2); const x1 = (x) => rn(x - w / 2, 2);
const y1 = y => rn(y - size, 2); const y1 = (y) => rn(y - size, 2);
const baseRect = this.querySelector("rect"); const baseRect = this.querySelector('rect');
const text = this.querySelector("text"); const text = this.querySelector('text');
const iconRect = this.querySelectorAll("rect")[1]; const iconRect = this.querySelectorAll('rect')[1];
const icon = this.querySelector(".regimentIcon"); const icon = this.querySelector('.regimentIcon');
const self = elSelected === this; const self = elSelected === this;
const baseLine = viewbox.select("g#regimentBase > line"); const baseLine = viewbox.select('g#regimentBase > line');
d3.event.on("drag", function() { d3.event.on('drag', function () {
const x = reg.x = d3.event.x, y = reg.y = d3.event.y; const x = (reg.x = d3.event.x),
y = (reg.y = d3.event.y);
baseRect.setAttribute("x", x1(x)); baseRect.setAttribute('x', x1(x));
baseRect.setAttribute("y", y1(y)); baseRect.setAttribute('y', y1(y));
text.setAttribute("x", x); text.setAttribute('x', x);
text.setAttribute("y", y); text.setAttribute('y', y);
iconRect.setAttribute("x", x1(x)-h); iconRect.setAttribute('x', x1(x) - h);
iconRect.setAttribute("y", y1(y)); iconRect.setAttribute('y', y1(y));
icon.setAttribute("x", x1(x)-size); icon.setAttribute('x', x1(x) - size);
icon.setAttribute("y", y); icon.setAttribute('y', y);
if (self) baseLine.attr("x2", x).attr("y2", y); if (self) baseLine.attr('x2', x).attr('y2', y);
}); });
} }
function dragBase() { function dragBase() {
const baseLine = viewbox.select("g#regimentBase > line"); const baseLine = viewbox.select('g#regimentBase > line');
const reg = regiment(); const reg = regiment();
d3.event.on("drag", function() { d3.event.on('drag', function () {
this.setAttribute("cx", d3.event.x); this.setAttribute('cx', d3.event.x);
this.setAttribute("cy", d3.event.y); this.setAttribute('cy', d3.event.y);
baseLine.attr("x1", d3.event.x).attr("y1", d3.event.y); baseLine.attr('x1', d3.event.x).attr('y1', d3.event.y);
}); });
d3.event.on("end", function() {reg.bx = d3.event.x; reg.by = d3.event.y;}); d3.event.on('end', function () {
reg.bx = d3.event.x;
reg.by = d3.event.y;
});
} }
function closeEditor() { function closeEditor() {
armies.selectAll(":scope > g").classed("draggable", false); armies.selectAll(':scope > g').classed('draggable', false);
armies.selectAll("g>g").call(d3.drag().on("drag", null)); armies.selectAll('g>g').call(d3.drag().on('drag', null));
viewbox.selectAll("g#regimentBase").remove(); viewbox.selectAll('g#regimentBase').remove();
document.getElementById("regimentAdd").classList.remove("pressed"); document.getElementById('regimentAdd').classList.remove('pressed');
document.getElementById("regimentAttack").classList.remove("pressed"); document.getElementById('regimentAttack').classList.remove('pressed');
document.getElementById("regimentAttach").classList.remove("pressed"); document.getElementById('regimentAttach').classList.remove('pressed');
restoreDefaultEvents(); restoreDefaultEvents();
elSelected = null; elSelected = null;
} }
} }

View file

@ -1,19 +1,21 @@
"use strict"; 'use strict';
function editReliefIcon() { function editReliefIcon() {
if (customization) return; if (customization) return;
closeDialogs(".stable"); closeDialogs('.stable');
if (!layerIsOn("toggleRelief")) toggleRelief(); if (!layerIsOn('toggleRelief')) toggleRelief();
terrain.selectAll("use").call(d3.drag().on("drag", dragReliefIcon)).classed("draggable", true); terrain.selectAll('use').call(d3.drag().on('drag', dragReliefIcon)).classed('draggable', true);
elSelected = d3.select(d3.event.target); elSelected = d3.select(d3.event.target);
restoreEditMode(); restoreEditMode();
updateReliefIconSelected(); updateReliefIconSelected();
updateReliefSizeInput(); updateReliefSizeInput();
$("#reliefEditor").dialog({ $('#reliefEditor').dialog({
title: "Edit Relief Icons", resizable: false, width: "27em", title: 'Edit Relief Icons',
position: {my: "left top", at: "left+10 top+10", of: "#map"}, resizable: false,
width: '27em',
position: {my: 'left top', at: 'left+10 top+10', of: '#map'},
close: closeReliefEditor close: closeReliefEditor
}); });
@ -21,62 +23,63 @@ function editReliefIcon() {
modules.editReliefIcon = true; modules.editReliefIcon = true;
// add listeners // add listeners
document.getElementById("reliefIndividual").addEventListener("click", enterIndividualMode); document.getElementById('reliefIndividual').addEventListener('click', enterIndividualMode);
document.getElementById("reliefBulkAdd").addEventListener("click", enterBulkAddMode); document.getElementById('reliefBulkAdd').addEventListener('click', enterBulkAddMode);
document.getElementById("reliefBulkRemove").addEventListener("click", enterBulkRemoveMode); document.getElementById('reliefBulkRemove').addEventListener('click', enterBulkRemoveMode);
document.getElementById("reliefSize").addEventListener("input", changeIconSize); document.getElementById('reliefSize').addEventListener('input', changeIconSize);
document.getElementById("reliefSizeNumber").addEventListener("input", changeIconSize); document.getElementById('reliefSizeNumber').addEventListener('input', changeIconSize);
document.getElementById("reliefEditorSet").addEventListener("change", changeIconsSet); document.getElementById('reliefEditorSet').addEventListener('change', changeIconsSet);
reliefIconsDiv.querySelectorAll("svg").forEach(el => el.addEventListener("click", changeIcon)); reliefIconsDiv.querySelectorAll('svg').forEach((el) => el.addEventListener('click', changeIcon));
document.getElementById("reliefEditStyle").addEventListener("click", () => editStyle("terrain")); document.getElementById('reliefEditStyle').addEventListener('click', () => editStyle('terrain'));
document.getElementById("reliefCopy").addEventListener("click", copyIcon); document.getElementById('reliefCopy').addEventListener('click', copyIcon);
document.getElementById("reliefMoveFront").addEventListener("click", () => elSelected.raise()); document.getElementById('reliefMoveFront').addEventListener('click', () => elSelected.raise());
document.getElementById("reliefMoveBack").addEventListener("click", () => elSelected.lower()); document.getElementById('reliefMoveBack').addEventListener('click', () => elSelected.lower());
document.getElementById("reliefRemove").addEventListener("click", removeIcon); document.getElementById('reliefRemove').addEventListener('click', removeIcon);
function dragReliefIcon() { function dragReliefIcon() {
const dx = +this.getAttribute("x") - d3.event.x; const dx = +this.getAttribute('x') - d3.event.x;
const dy = +this.getAttribute("y") - d3.event.y; const dy = +this.getAttribute('y') - 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("x", dx+x); y = d3.event.y;
this.setAttribute("y", dy+y); this.setAttribute('x', dx + x);
this.setAttribute('y', dy + y);
}); });
} }
function restoreEditMode() { function restoreEditMode() {
if (!reliefTools.querySelector("button.pressed")) enterIndividualMode(); else if (!reliefTools.querySelector('button.pressed')) enterIndividualMode();
if (reliefBulkAdd.classList.contains("pressed")) enterBulkAddMode(); else else if (reliefBulkAdd.classList.contains('pressed')) enterBulkAddMode();
if (reliefBulkRemove.classList.contains("pressed")) enterBulkRemoveMode(); else if (reliefBulkRemove.classList.contains('pressed')) enterBulkRemoveMode();
} }
function updateReliefIconSelected() { function updateReliefIconSelected() {
const type = elSelected.attr("href") || elSelected.attr("data-type"); const type = elSelected.attr('href') || elSelected.attr('data-type');
const button = reliefIconsDiv.querySelector("svg[data-type='"+type+"']"); const button = reliefIconsDiv.querySelector("svg[data-type='" + type + "']");
reliefIconsDiv.querySelectorAll("svg.pressed").forEach(b => b.classList.remove("pressed")); reliefIconsDiv.querySelectorAll('svg.pressed').forEach((b) => b.classList.remove('pressed'));
button.classList.add("pressed"); button.classList.add('pressed');
reliefIconsDiv.querySelectorAll("div").forEach(b => b.style.display = "none"); reliefIconsDiv.querySelectorAll('div').forEach((b) => (b.style.display = 'none'));
button.parentNode.style.display = "block"; button.parentNode.style.display = 'block';
reliefEditorSet.value = button.parentNode.dataset.type; reliefEditorSet.value = button.parentNode.dataset.type;
} }
function updateReliefSizeInput() { function updateReliefSizeInput() {
const size = +elSelected.attr("width"); const size = +elSelected.attr('width');
reliefSize.value = reliefSizeNumber.value = rn(size); reliefSize.value = reliefSizeNumber.value = rn(size);
} }
function enterIndividualMode() { function enterIndividualMode() {
reliefTools.querySelectorAll("button.pressed").forEach(b => b.classList.remove("pressed")); reliefTools.querySelectorAll('button.pressed').forEach((b) => b.classList.remove('pressed'));
reliefIndividual.classList.add("pressed"); reliefIndividual.classList.add('pressed');
reliefSizeDiv.style.display = "block"; reliefSizeDiv.style.display = 'block';
reliefRadiusDiv.style.display = "none"; reliefRadiusDiv.style.display = 'none';
reliefSpacingDiv.style.display = "none"; reliefSpacingDiv.style.display = 'none';
reliefIconsSeletionAny.style.display = "none"; reliefIconsSeletionAny.style.display = 'none';
updateReliefSizeInput(); updateReliefSizeInput();
restoreDefaultEvents(); restoreDefaultEvents();
@ -84,22 +87,23 @@ function editReliefIcon() {
} }
function enterBulkAddMode() { function enterBulkAddMode() {
reliefTools.querySelectorAll("button.pressed").forEach(b => b.classList.remove("pressed")); reliefTools.querySelectorAll('button.pressed').forEach((b) => b.classList.remove('pressed'));
reliefBulkAdd.classList.add("pressed"); reliefBulkAdd.classList.add('pressed');
reliefSizeDiv.style.display = "block"; reliefSizeDiv.style.display = 'block';
reliefRadiusDiv.style.display = "block"; reliefRadiusDiv.style.display = 'block';
reliefSpacingDiv.style.display = "block"; reliefSpacingDiv.style.display = 'block';
reliefIconsSeletionAny.style.display = "none"; reliefIconsSeletionAny.style.display = 'none';
const pressedType = reliefIconsDiv.querySelector("svg.pressed"); const pressedType = reliefIconsDiv.querySelector('svg.pressed');
if (pressedType.id === "reliefIconsSeletionAny") { // in "any" is pressed, select first type if (pressedType.id === 'reliefIconsSeletionAny') {
reliefIconsSeletionAny.classList.remove("pressed"); // in "any" is pressed, select first type
reliefIconsDiv.querySelector("svg").classList.add("pressed"); reliefIconsSeletionAny.classList.remove('pressed');
reliefIconsDiv.querySelector('svg').classList.add('pressed');
} }
viewbox.style("cursor", "crosshair").call(d3.drag().on("start", dragToAdd)).on("touchmove mousemove", moveBrush); viewbox.style('cursor', 'crosshair').call(d3.drag().on('start', dragToAdd)).on('touchmove mousemove', moveBrush);
tip("Drag to place relief icons within radius", true); tip('Drag to place relief icons within radius', true);
} }
function moveBrush() { function moveBrush() {
@ -110,8 +114,11 @@ function editReliefIcon() {
} }
function dragToAdd() { function dragToAdd() {
const pressed = reliefIconsDiv.querySelector("svg.pressed"); const pressed = reliefIconsDiv.querySelector('svg.pressed');
if (!pressed) {tip("Please select an icon", false, error); return;} if (!pressed) {
tip('Please select an icon', false, error);
return;
}
const type = pressed.dataset.type; const type = pressed.dataset.type;
const r = +reliefRadiusNumber.value; const r = +reliefRadiusNumber.value;
@ -121,19 +128,19 @@ function editReliefIcon() {
// build a quadtree // build a quadtree
const tree = d3.quadtree(); const tree = d3.quadtree();
const positions = []; const positions = [];
terrain.selectAll("use").each(function() { terrain.selectAll('use').each(function () {
const x = +this.getAttribute("x") + this.getAttribute("width") / 2; const x = +this.getAttribute('x') + this.getAttribute('width') / 2;
const y = +this.getAttribute("y") + this.getAttribute("height") / 2; const y = +this.getAttribute('y') + this.getAttribute('height') / 2;
tree.add([x, y, x]); tree.add([x, y, x]);
const box = this.getBBox(); const box = this.getBBox();
positions.push(box.y + box.height); positions.push(box.y + box.height);
}); });
d3.event.on("drag", function() { d3.event.on('drag', function () {
const p = d3.mouse(this); const p = d3.mouse(this);
moveCircle(p[0], p[1], r); moveCircle(p[0], p[1], r);
d3.range(Math.ceil(r/10)).forEach(function() { d3.range(Math.ceil(r / 10)).forEach(function () {
const a = Math.PI * 2 * Math.random(); const a = Math.PI * 2 * Math.random();
const rad = r * Math.random(); const rad = r * Math.random();
const cx = p[0] + rad * Math.cos(a); const cx = p[0] + rad * Math.cos(a);
@ -142,83 +149,93 @@ function editReliefIcon() {
if (tree.find(cx, cy, spacing)) return; // too close to existing icon if (tree.find(cx, cy, spacing)) return; // too close to existing icon
if (pack.cells.h[findCell(cx, cy)] < 20) return; // on water cell if (pack.cells.h[findCell(cx, cy)] < 20) return; // on water cell
const h = rn(size / 2 * (Math.random() * .4 + .8), 2); const h = rn((size / 2) * (Math.random() * 0.4 + 0.8), 2);
const x = rn(cx-h, 2); const x = rn(cx - h, 2);
const y = rn(cy-h, 2); const y = rn(cy - h, 2);
const z = y + h * 2; const z = y + h * 2;
const s = rn(h*2, 2); const s = rn(h * 2, 2);
let nth = 1; let nth = 1;
while (positions[nth] && z > positions[nth]) {nth++;} while (positions[nth] && z > positions[nth]) {
nth++;
}
tree.add([cx, cy]); tree.add([cx, cy]);
positions.push(z); positions.push(z);
terrain.insert("use", ":nth-child("+nth+")").attr("href", type) terrain
.attr("x", x).attr("y", y).attr("width", s).attr("height", s); .insert('use', ':nth-child(' + nth + ')')
.attr('href', type)
.attr('x', x)
.attr('y', y)
.attr('width', s)
.attr('height', s);
}); });
}); });
} }
function enterBulkRemoveMode() { function enterBulkRemoveMode() {
reliefTools.querySelectorAll("button.pressed").forEach(b => b.classList.remove("pressed")); reliefTools.querySelectorAll('button.pressed').forEach((b) => b.classList.remove('pressed'));
reliefBulkRemove.classList.add("pressed"); reliefBulkRemove.classList.add('pressed');
reliefSizeDiv.style.display = "none"; reliefSizeDiv.style.display = 'none';
reliefRadiusDiv.style.display = "block"; reliefRadiusDiv.style.display = 'block';
reliefSpacingDiv.style.display = "none"; reliefSpacingDiv.style.display = 'none';
reliefIconsSeletionAny.style.display = "inline-block"; reliefIconsSeletionAny.style.display = 'inline-block';
viewbox.style("cursor", "crosshair").call(d3.drag().on("start", dragToRemove)).on("touchmove mousemove", moveBrush);; viewbox.style('cursor', 'crosshair').call(d3.drag().on('start', dragToRemove)).on('touchmove mousemove', moveBrush);
tip("Drag to remove relief icons in radius", true); tip('Drag to remove relief icons in radius', true);
} }
function dragToRemove() { function dragToRemove() {
const pressed = reliefIconsDiv.querySelector("svg.pressed"); const pressed = reliefIconsDiv.querySelector('svg.pressed');
if (!pressed) {tip("Please select an icon", false, error); return;} if (!pressed) {
tip('Please select an icon', false, error);
return;
}
const r = +reliefRadiusNumber.value; const r = +reliefRadiusNumber.value;
const type = pressed.dataset.type; const type = pressed.dataset.type;
const icons = type ? terrain.selectAll("use[href='"+type+"']") : terrain.selectAll("use"); const icons = type ? terrain.selectAll("use[href='" + type + "']") : terrain.selectAll('use');
const tree = d3.quadtree(); const tree = d3.quadtree();
icons.each(function() { icons.each(function () {
const x = +this.getAttribute("x") + this.getAttribute("width") / 2; const x = +this.getAttribute('x') + this.getAttribute('width') / 2;
const y = +this.getAttribute("y") + this.getAttribute("height") / 2; const y = +this.getAttribute('y') + this.getAttribute('height') / 2;
tree.add([x, y, this]); tree.add([x, y, this]);
}); });
d3.event.on("drag", function() { d3.event.on('drag', function () {
const p = d3.mouse(this); const p = d3.mouse(this);
moveCircle(p[0], p[1], r); moveCircle(p[0], p[1], r);
tree.findAll(p[0], p[1], r).forEach(f => f[2].remove()); tree.findAll(p[0], p[1], r).forEach((f) => f[2].remove());
}); });
} }
function changeIconSize() { function changeIconSize() {
const size = +reliefSizeNumber.value; const size = +reliefSizeNumber.value;
if (!reliefIndividual.classList.contains("pressed")) return; if (!reliefIndividual.classList.contains('pressed')) return;
const shift = (size - +elSelected.attr("width")) / 2; const shift = (size - +elSelected.attr('width')) / 2;
elSelected.attr("width", size).attr("height", size); elSelected.attr('width', size).attr('height', size);
const x = +elSelected.attr("x"), y = +elSelected.attr("y"); const x = +elSelected.attr('x'),
elSelected.attr("x", x-shift).attr("y", y-shift); y = +elSelected.attr('y');
elSelected.attr('x', x - shift).attr('y', y - shift);
} }
function changeIconsSet() { function changeIconsSet() {
const set = reliefEditorSet.value; const set = reliefEditorSet.value;
reliefIconsDiv.querySelectorAll("div").forEach(b => b.style.display = "none"); reliefIconsDiv.querySelectorAll('div').forEach((b) => (b.style.display = 'none'));
reliefIconsDiv.querySelector("div[data-type='" + set + "']").style.display = "block"; reliefIconsDiv.querySelector("div[data-type='" + set + "']").style.display = 'block';
} }
function changeIcon() { function changeIcon() {
if (this.classList.contains("pressed")) return; if (this.classList.contains('pressed')) return;
reliefIconsDiv.querySelectorAll("svg.pressed").forEach(b => b.classList.remove("pressed")) reliefIconsDiv.querySelectorAll('svg.pressed').forEach((b) => b.classList.remove('pressed'));
this.classList.add("pressed"); this.classList.add('pressed');
if (reliefIndividual.classList.contains("pressed")) { if (reliefIndividual.classList.contains('pressed')) {
const type = this.dataset.type; const type = this.dataset.type;
elSelected.attr("href", type); elSelected.attr('href', type);
} }
} }
@ -226,35 +243,31 @@ function editReliefIcon() {
const parent = elSelected.node().parentNode; const parent = elSelected.node().parentNode;
const copy = elSelected.node().cloneNode(true); const copy = elSelected.node().cloneNode(true);
let x = +elSelected.attr("x") - 3, y = +elSelected.attr("y") - 3; let x = +elSelected.attr('x') - 3,
while (parent.querySelector("[x='"+x+"']","[x='"+y+"']")) { y = +elSelected.attr('y') - 3;
x -= 3; y -= 3; while (parent.querySelector("[x='" + x + "']", "[x='" + y + "']")) {
x -= 3;
y -= 3;
} }
copy.setAttribute("x", x); copy.setAttribute('x', x);
copy.setAttribute("y", y); copy.setAttribute('y', y);
parent.insertBefore(copy, null); parent.insertBefore(copy, null);
} }
function removeIcon() { function removeIcon() {
alertMessage.innerHTML = `Are you sure you want to remove the icon?`; const message = 'Are you sure you want to remove the relief icon? <br>This action cannot be reverted';
$("#alert").dialog({resizable: false, title: "Remove relief icon", const onConfirm = () => {
buttons: { elSelected.remove();
Remove: function() { $('#reliefEditor').dialog('close');
$(this).dialog("close"); };
elSelected.remove(); confirmationDialog({title: 'Remove relief icon', message, confirm: 'Remove', onConfirm});
$("#reliefEditor").dialog("close");
},
Cancel: function() {$(this).dialog("close");}
}
});
} }
function closeReliefEditor() { function closeReliefEditor() {
terrain.selectAll("use").call(d3.drag().on("drag", null)).classed("draggable", false); terrain.selectAll('use').call(d3.drag().on('drag', null)).classed('draggable', false);
removeCircle(); removeCircle();
unselect(); unselect();
clearMainTip(); clearMainTip();
} }
} }

View file

@ -1,14 +1,14 @@
"use strict"; 'use strict';
function editReligions() { function editReligions() {
if (customization) return; if (customization) return;
closeDialogs("#religionsEditor, .stable"); closeDialogs('#religionsEditor, .stable');
if (!layerIsOn("toggleReligions")) toggleReligions(); if (!layerIsOn('toggleReligions')) toggleReligions();
if (layerIsOn("toggleCultures")) toggleCultures(); if (layerIsOn('toggleCultures')) toggleCultures();
if (layerIsOn("toggleStates")) toggleStates(); if (layerIsOn('toggleStates')) toggleStates();
if (layerIsOn("toggleBiomes")) toggleBiomes(); if (layerIsOn('toggleBiomes')) toggleBiomes();
if (layerIsOn("toggleProvinces")) toggleProvinces(); if (layerIsOn('toggleProvinces')) toggleProvinces();
const body = document.getElementById("religionsBody"); const body = document.getElementById('religionsBody');
const animate = d3.transition().duration(1500).ease(d3.easeSinIn); const animate = d3.transition().duration(1500).ease(d3.easeSinIn);
refreshReligionsEditor(); refreshReligionsEditor();
drawReligionCenters(); drawReligionCenters();
@ -16,23 +16,26 @@ function editReligions() {
if (modules.editReligions) return; if (modules.editReligions) return;
modules.editReligions = true; modules.editReligions = true;
$("#religionsEditor").dialog({ $('#religionsEditor').dialog({
title: "Religions Editor", resizable: false, width: fitContent(), close: closeReligionsEditor, title: 'Religions Editor',
position: {my: "right top", at: "right-10 top+10", of: "svg"} resizable: false,
width: fitContent(),
close: closeReligionsEditor,
position: {my: 'right top', at: 'right-10 top+10', of: 'svg'}
}); });
// add listeners // add listeners
document.getElementById("religionsEditorRefresh").addEventListener("click", refreshReligionsEditor); document.getElementById('religionsEditorRefresh').addEventListener('click', refreshReligionsEditor);
document.getElementById("religionsEditStyle").addEventListener("click", () => editStyle("relig")); document.getElementById('religionsEditStyle').addEventListener('click', () => editStyle('relig'));
document.getElementById("religionsLegend").addEventListener("click", toggleLegend); document.getElementById('religionsLegend').addEventListener('click', toggleLegend);
document.getElementById("religionsPercentage").addEventListener("click", togglePercentageMode); document.getElementById('religionsPercentage').addEventListener('click', togglePercentageMode);
document.getElementById("religionsHeirarchy").addEventListener("click", showHierarchy); document.getElementById('religionsHeirarchy').addEventListener('click', showHierarchy);
document.getElementById("religionsExtinct").addEventListener("click", toggleExtinct); document.getElementById('religionsExtinct').addEventListener('click', toggleExtinct);
document.getElementById("religionsManually").addEventListener("click", enterReligionsManualAssignent); document.getElementById('religionsManually').addEventListener('click', enterReligionsManualAssignent);
document.getElementById("religionsManuallyApply").addEventListener("click", applyReligionsManualAssignent); document.getElementById('religionsManuallyApply').addEventListener('click', applyReligionsManualAssignent);
document.getElementById("religionsManuallyCancel").addEventListener("click", () => exitReligionsManualAssignment()); document.getElementById('religionsManuallyCancel').addEventListener('click', () => exitReligionsManualAssignment());
document.getElementById("religionsAdd").addEventListener("click", enterAddReligionMode); document.getElementById('religionsAdd').addEventListener('click', enterAddReligionMode);
document.getElementById("religionsExport").addEventListener("click", downloadReligionsData); document.getElementById('religionsExport').addEventListener('click', downloadReligionsData);
function refreshReligionsEditor() { function refreshReligionsEditor() {
religionsCollectStatistics(); religionsCollectStatistics();
@ -40,8 +43,9 @@ function editReligions() {
} }
function religionsCollectStatistics() { function religionsCollectStatistics() {
const cells = pack.cells, religions = pack.religions; const cells = pack.cells,
religions.forEach(r => r.cells = r.area = r.rural = r.urban = 0); religions = pack.religions;
religions.forEach((r) => (r.cells = r.area = r.rural = r.urban = 0));
for (const i of cells.i) { for (const i of cells.i) {
if (cells.h[i] < 20) continue; if (cells.h[i] < 20) continue;
@ -55,30 +59,34 @@ function editReligions() {
// add line for each religion // add line for each religion
function religionsEditorAddLines() { function religionsEditorAddLines() {
const unit = areaUnit.value === "square" ? " " + distanceUnitInput.value + "²" : " " + areaUnit.value; const unit = areaUnit.value === 'square' ? ' ' + distanceUnitInput.value + '²' : ' ' + areaUnit.value;
let lines = "", totalArea = 0, totalPopulation = 0; let lines = '',
totalArea = 0,
totalPopulation = 0;
for (const r of pack.religions) { for (const r of pack.religions) {
if (r.removed) continue; if (r.removed) continue;
const area = r.area * (distanceScaleInput.value ** 2); const area = r.area * distanceScaleInput.value ** 2;
const rural = r.rural * populationRate.value; const rural = r.rural * populationRate.value;
const urban = r.urban * populationRate.value * urbanization.value; const urban = r.urban * populationRate.value * urbanization.value;
const population = rn(rural + urban); const population = rn(rural + urban);
if (r.i && !r.cells && body.dataset.extinct !== "show") continue; // hide extinct religions if (r.i && !r.cells && body.dataset.extinct !== 'show') continue; // hide extinct religions
const populationTip = `Believers: ${si(population)}; Rural areas: ${si(rural)}; Urban areas: ${si(urban)}. Click to change`; const populationTip = `Believers: ${si(population)}; Rural areas: ${si(rural)}; Urban areas: ${si(urban)}. Click to change`;
totalArea += area; totalArea += area;
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 += `<div 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}> data-population=${population} data-type=${r.type} data-form=${r.form} data-deity="${r.deity ? r.deity : ''}" data-expansionism=${r.expansionism}>
<svg data-tip="Religion fill style. Click to change" width=".9em" height=".9em" style="margin-bottom:-1px"><rect x="0" y="0" width="100%" height="100%" fill="${r.color}" class="fillRect pointer"></svg> <svg data-tip="Religion fill style. Click to change" width=".9em" height=".9em" style="margin-bottom:-1px"><rect x="0" y="0" width="100%" height="100%" fill="${
r.color
}" class="fillRect pointer"></svg>
<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">${getTypeOptions(r.type)}</select>
<input data-tip="Religion form" class="religionForm hide" value="${r.form}" autocorrect="off" spellcheck="false"> <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>
@ -104,94 +112,119 @@ function editReligions() {
body.innerHTML = lines; body.innerHTML = lines;
// update footer // update footer
const valid = pack.religions.filter(r => r.i && !r.removed); const valid = pack.religions.filter((r) => r.i && !r.removed);
religionsOrganized.innerHTML = valid.filter(r => r.type === "Organized").length; religionsOrganized.innerHTML = valid.filter((r) => r.type === 'Organized').length;
religionsHeresies.innerHTML = valid.filter(r => r.type === "Heresy").length; religionsHeresies.innerHTML = valid.filter((r) => r.type === 'Heresy').length;
religionsCults.innerHTML = valid.filter(r => r.type === "Cult").length; religionsCults.innerHTML = valid.filter((r) => r.type === 'Cult').length;
religionsFolk.innerHTML = valid.filter(r => r.type === "Folk").length; religionsFolk.innerHTML = valid.filter((r) => r.type === 'Folk').length;
religionsFooterArea.innerHTML = si(totalArea) + unit; religionsFooterArea.innerHTML = si(totalArea) + unit;
religionsFooterPopulation.innerHTML = si(totalPopulation); religionsFooterPopulation.innerHTML = si(totalPopulation);
religionsFooterArea.dataset.area = totalArea; religionsFooterArea.dataset.area = totalArea;
religionsFooterPopulation.dataset.population = totalPopulation; religionsFooterPopulation.dataset.population = totalPopulation;
// add listeners // add listeners
body.querySelectorAll("div.religions").forEach(el => el.addEventListener("mouseenter", ev => religionHighlightOn(ev))); body.querySelectorAll('div.religions').forEach((el) => el.addEventListener('mouseenter', (ev) => religionHighlightOn(ev)));
body.querySelectorAll("div.religions").forEach(el => el.addEventListener("mouseleave", ev => religionHighlightOff(ev))); body.querySelectorAll('div.religions').forEach((el) => el.addEventListener('mouseleave', (ev) => religionHighlightOff(ev)));
body.querySelectorAll("div.states").forEach(el => el.addEventListener("click", selectReligionOnLineClick)); body.querySelectorAll('div.states').forEach((el) => el.addEventListener('click', selectReligionOnLineClick));
body.querySelectorAll("rect.fillRect").forEach(el => el.addEventListener("click", religionChangeColor)); body.querySelectorAll('rect.fillRect').forEach((el) => el.addEventListener('click', religionChangeColor));
body.querySelectorAll("div > input.religionName").forEach(el => el.addEventListener("input", religionChangeName)); body.querySelectorAll('div > input.religionName').forEach((el) => el.addEventListener('input', religionChangeName));
body.querySelectorAll("div > select.religionType").forEach(el => el.addEventListener("change", religionChangeType)); body.querySelectorAll('div > select.religionType').forEach((el) => el.addEventListener('change', religionChangeType));
body.querySelectorAll("div > input.religionForm").forEach(el => el.addEventListener("input", religionChangeForm)); body.querySelectorAll('div > input.religionForm').forEach((el) => el.addEventListener('input', religionChangeForm));
body.querySelectorAll("div > input.religionDeity").forEach(el => el.addEventListener("input", religionChangeDeity)); body.querySelectorAll('div > input.religionDeity').forEach((el) => el.addEventListener('input', religionChangeDeity));
body.querySelectorAll("div > span.icon-arrows-cw").forEach(el => el.addEventListener("click", regenerateDeity)); body.querySelectorAll('div > span.icon-arrows-cw').forEach((el) => el.addEventListener('click', regenerateDeity));
body.querySelectorAll("div > div.culturePopulation").forEach(el => el.addEventListener("click", changePopulation)); body.querySelectorAll('div > div.culturePopulation').forEach((el) => el.addEventListener('click', changePopulation));
body.querySelectorAll("div > span.icon-trash-empty").forEach(el => el.addEventListener("click", religionRemove)); body.querySelectorAll('div > span.icon-trash-empty').forEach((el) => el.addEventListener('click', religionRemove));
if (body.dataset.type === "percentage") {body.dataset.type = "absolute"; togglePercentageMode();} if (body.dataset.type === 'percentage') {
body.dataset.type = 'absolute';
togglePercentageMode();
}
applySorting(religionsHeader); applySorting(religionsHeader);
$("#religionsEditor").dialog({width: fitContent()}); $('#religionsEditor').dialog({width: fitContent()});
} }
function getTypeOptions(type) { function getTypeOptions(type) {
let options = ""; let options = '';
const types = ["Folk", "Organized", "Cult", "Heresy"]; const types = ['Folk', 'Organized', 'Cult', 'Heresy'];
types.forEach(t => options += `<option ${type === t ? "selected" : ""} value="${t}">${t}</option>`); types.forEach((t) => (options += `<option ${type === t ? 'selected' : ''} value="${t}">${t}</option>`));
return options; return options;
} }
function religionHighlightOn(event) { function religionHighlightOn(event) {
const religion = +event.target.dataset.id; const religion = +event.target.dataset.id;
const info = document.getElementById("religionInfo"); const info = document.getElementById('religionInfo');
if (info) { if (info) {
d3.select("#hierarchy").select("g[data-id='"+religion+"'] > path").classed("selected", 1); d3.select('#hierarchy')
.select("g[data-id='" + religion + "'] > path")
.classed('selected', 1);
const r = pack.religions[religion]; const r = pack.religions[religion];
const type = r.name.includes(r.type) ? "" const type = r.name.includes(r.type) ? '' : r.type === 'Folk' || r.type === 'Organized' ? '. ' + r.type + ' religion' : '. ' + r.type;
: r.type === "Folk" || r.type === "Organized" const form = r.form === r.type || r.name.includes(r.form) ? '' : '. ' + r.form;
? ". " + r.type + " religion" : ". " + r.type;
const form = r.form === r.type || r.name.includes(r.form) ? "" : ". " + r.form;
const rural = r.rural * populationRate.value; const rural = r.rural * populationRate.value;
const urban = r.urban * populationRate.value * urbanization.value; const urban = r.urban * populationRate.value * urbanization.value;
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 = `${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');
} }
const el = body.querySelector(`div[data-id='${religion}']`); const el = body.querySelector(`div[data-id='${religion}']`);
if (el) el.classList.add("active"); if (el) el.classList.add('active');
if (!layerIsOn("toggleReligions")) return; if (!layerIsOn('toggleReligions')) return;
if (customization) return; if (customization) return;
relig.select("#religion"+religion).raise().transition(animate).attr("stroke-width", 2.5).attr("stroke", "#c13119"); relig
debug.select("#religionsCenter"+religion).raise().transition(animate).attr("r", 8).attr("stroke-width", 2).attr("stroke", "#c13119"); .select('#religion' + religion)
.raise()
.transition(animate)
.attr('stroke-width', 2.5)
.attr('stroke', '#c13119');
debug
.select('#religionsCenter' + religion)
.raise()
.transition(animate)
.attr('r', 8)
.attr('stroke-width', 2)
.attr('stroke', '#c13119');
} }
function religionHighlightOff(event) { function religionHighlightOff(event) {
const religion = +event.target.dataset.id; const religion = +event.target.dataset.id;
const info = document.getElementById("religionInfo"); const info = document.getElementById('religionInfo');
if (info) { if (info) {
d3.select("#hierarchy").select("g[data-id='"+religion+"'] > path").classed("selected", 0); d3.select('#hierarchy')
info.innerHTML = "&#8205;"; .select("g[data-id='" + religion + "'] > path")
tip(""); .classed('selected', 0);
info.innerHTML = '&#8205;';
tip('');
} }
const el = body.querySelector(`div[data-id='${religion}']`) const el = body.querySelector(`div[data-id='${religion}']`);
if (el) el.classList.remove("active"); if (el) el.classList.remove('active');
relig.select("#religion"+religion).transition().attr("stroke-width", null).attr("stroke", null); relig
debug.select("#religionsCenter"+religion).transition().attr("r", 4).attr("stroke-width", 1.2).attr("stroke", null); .select('#religion' + religion)
.transition()
.attr('stroke-width', null)
.attr('stroke', null);
debug
.select('#religionsCenter' + religion)
.transition()
.attr('r', 4)
.attr('stroke-width', 1.2)
.attr('stroke', null);
} }
function religionChangeColor() { function religionChangeColor() {
const el = this; const el = this;
const currentFill = el.getAttribute("fill"); const currentFill = el.getAttribute('fill');
const religion = +el.parentNode.parentNode.dataset.id; const religion = +el.parentNode.parentNode.dataset.id;
const callback = function(fill) { const callback = function (fill) {
el.setAttribute("fill", fill); el.setAttribute('fill', fill);
pack.religions[religion].color = fill; pack.religions[religion].color = fill;
relig.select("#religion"+religion).attr("fill", fill); relig.select('#religion' + religion).attr('fill', fill);
debug.select("#religionsCenter"+religion).attr("fill", fill); debug.select('#religionsCenter' + religion).attr('fill', fill);
} };
openPicker(currentFill, callback); openPicker(currentFill, callback);
} }
@ -200,7 +233,10 @@ function editReligions() {
const religion = +this.parentNode.dataset.id; const religion = +this.parentNode.dataset.id;
this.parentNode.dataset.name = this.value; this.parentNode.dataset.name = this.value;
pack.religions[religion].name = this.value; pack.religions[religion].name = this.value;
pack.religions[religion].code = abbreviate(this.value, pack.religions.map(c => c.code)); pack.religions[religion].code = abbreviate(
this.value,
pack.religions.map((c) => c.code)
);
} }
function religionChangeType() { function religionChangeType() {
@ -233,113 +269,131 @@ function editReligions() {
function changePopulation() { function changePopulation() {
const religion = +this.parentNode.dataset.id; const religion = +this.parentNode.dataset.id;
const r = pack.religions[religion]; const r = pack.religions[religion];
if (!r.cells) {tip("Religion does not have any cells, cannot change population", false, "error"); return;} if (!r.cells) {
tip('Religion does not have any cells, cannot change population', false, 'error');
return;
}
const rural = rn(r.rural * populationRate.value); const rural = rn(r.rural * populationRate.value);
const urban = rn(r.urban * populationRate.value * urbanization.value); const urban = rn(r.urban * populationRate.value * urbanization.value);
const total = rural + urban; const total = rural + urban;
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 = `<p><i>Please note all population of religion territory is considered
believers of this religion. It means believers number change will directly change population</i></p> believers of this religion. It means believers number change will directly change population</i></p>
Rural: <input type="number" min=0 step=1 id="ruralPop" value=${rural} style="width:6em"> 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"}> 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>`; <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;
if (isNaN(totalNew)) return; if (isNaN(totalNew)) return;
totalPop.innerHTML = l(totalNew); totalPop.innerHTML = l(totalNew);
totalPopPerc.innerHTML = rn(totalNew / total * 100); totalPopPerc.innerHTML = rn((totalNew / total) * 100);
} };
ruralPop.oninput = () => update(); ruralPop.oninput = () => update();
urbanPop.oninput = () => update(); urbanPop.oninput = () => update();
$("#alert").dialog({ $('#alert').dialog({
resizable: false, title: "Change believers number", width: "24em", buttons: { resizable: false,
Apply: function() {applyPopulationChange(); $(this).dialog("close");}, title: 'Change believers number',
Cancel: function() {$(this).dialog("close");} width: '24em',
}, position: {my: "center", at: "center", of: "svg"} buttons: {
Apply: function () {
applyPopulationChange();
$(this).dialog('close');
},
Cancel: function () {
$(this).dialog('close');
}
},
position: {my: 'center', at: 'center', of: 'svg'}
}); });
function applyPopulationChange() { function applyPopulationChange() {
const ruralChange = ruralPop.value / rural; const ruralChange = ruralPop.value / rural;
if (isFinite(ruralChange) && ruralChange !== 1) { if (isFinite(ruralChange) && ruralChange !== 1) {
const cells = pack.cells.i.filter(i => pack.cells.religion[i] === religion); const cells = pack.cells.i.filter((i) => pack.cells.religion[i] === religion);
cells.forEach(i => pack.cells.pop[i] *= ruralChange); cells.forEach((i) => (pack.cells.pop[i] *= ruralChange));
} }
if (!isFinite(ruralChange) && +ruralPop.value > 0) { if (!isFinite(ruralChange) && +ruralPop.value > 0) {
const points = ruralPop.value / populationRate.value; const points = ruralPop.value / populationRate.value;
const cells = pack.cells.i.filter(i => pack.cells.religion[i] === religion); const cells = pack.cells.i.filter((i) => pack.cells.religion[i] === religion);
const pop = rn(points / cells.length); const pop = rn(points / cells.length);
cells.forEach(i => pack.cells.pop[i] = pop); cells.forEach((i) => (pack.cells.pop[i] = pop));
} }
const urbanChange = urbanPop.value / urban; const urbanChange = urbanPop.value / urban;
if (isFinite(urbanChange) && urbanChange !== 1) { if (isFinite(urbanChange) && urbanChange !== 1) {
burgs.forEach(b => b.population = rn(b.population * urbanChange, 4)); burgs.forEach((b) => (b.population = rn(b.population * urbanChange, 4)));
} }
if (!isFinite(urbanChange) && +urbanPop.value > 0) { if (!isFinite(urbanChange) && +urbanPop.value > 0) {
const points = urbanPop.value / populationRate.value / urbanization.value; const points = urbanPop.value / populationRate.value / urbanization.value;
const population = rn(points / burgs.length, 4); const population = rn(points / burgs.length, 4);
burgs.forEach(b => b.population = population); burgs.forEach((b) => (b.population = population));
} }
refreshReligionsEditor(); refreshReligionsEditor();
} }
} }
function religionRemove() { function religionRemove() {
if (customization) return; if (customization) return;
const religion = +this.parentNode.dataset.id; const religion = +this.parentNode.dataset.id;
alertMessage.innerHTML = "Are you sure you want to remove the religion? <br>This action cannot be reverted"; const message = 'Are you sure you want to remove the religion? <br>This action cannot be reverted';
$("#alert").dialog({resizable: false, title: "Remove religion", const onConfirm = () => {
buttons: { relig.select('#religion' + religion).remove();
Remove: function() { relig.select('#religion-gap' + religion).remove();
relig.select("#religion"+religion).remove(); debug.select('#religionsCenter' + religion).remove();
relig.select("#religion-gap"+religion).remove();
debug.select("#religionsCenter"+religion).remove();
pack.cells.religion.forEach((r, i) => {if(r === religion) pack.cells.religion[i] = 0;}); pack.cells.religion.forEach((r, i) => {
pack.religions[religion].removed = true; if (r === religion) pack.cells.religion[i] = 0;
const origin = pack.religions[religion].origin; });
pack.religions.forEach(r => {if(r.origin === religion) r.origin = origin;}); pack.religions[religion].removed = true;
const origin = pack.religions[religion].origin;
pack.religions.forEach((r) => {
if (r.origin === religion) r.origin = origin;
});
refreshReligionsEditor(); refreshReligionsEditor();
$(this).dialog("close"); };
}, confirmationDialog({title: 'Remove religion', message, confirm: 'Remove', onConfirm});
Cancel: function() {$(this).dialog("close");}
}
});
} }
function drawReligionCenters() { function drawReligionCenters() {
debug.select("#religionCenters").remove(); debug.select('#religionCenters').remove();
const religionCenters = debug.append("g").attr("id", "religionCenters") const religionCenters = debug.append('g').attr('id', 'religionCenters').attr('stroke-width', 1.2).attr('stroke', '#444444').style('cursor', 'move');
.attr("stroke-width", 1.2).attr("stroke", "#444444").style("cursor", "move");
const data = pack.religions.filter(r => r.i && r.center && r.cells && !r.removed); const data = pack.religions.filter((r) => r.i && r.center && r.cells && !r.removed);
religionCenters.selectAll("circle").data(data).enter().append("circle") religionCenters
.attr("id", d => "religionsCenter"+d.i).attr("data-id", d => d.i) .selectAll('circle')
.attr("r", 4).attr("fill", d => d.color) .data(data)
.attr("cx", d => pack.cells.p[d.center][0]).attr("cy", d => pack.cells.p[d.center][1]) .enter()
.on("mouseenter", d => { .append('circle')
tip(d.name+ ". Drag to move the religion center", true); .attr('id', (d) => 'religionsCenter' + d.i)
.attr('data-id', (d) => d.i)
.attr('r', 4)
.attr('fill', (d) => d.color)
.attr('cx', (d) => pack.cells.p[d.center][0])
.attr('cy', (d) => pack.cells.p[d.center][1])
.on('mouseenter', (d) => {
tip(d.name + '. Drag to move the religion center', true);
religionHighlightOn(event); religionHighlightOn(event);
}).on("mouseleave", d => { })
.on('mouseleave', (d) => {
tip('', true); tip('', true);
religionHighlightOff(event); religionHighlightOff(event);
}).call(d3.drag().on("start", religionCenterDrag)); })
.call(d3.drag().on('start', religionCenterDrag));
} }
function religionCenterDrag() { function religionCenterDrag() {
const el = d3.select(this); const el = d3.select(this);
const r = +this.dataset.id; const r = +this.dataset.id;
d3.event.on("drag", () => { d3.event.on('drag', () => {
el.attr("cx", d3.event.x).attr("cy", d3.event.y); el.attr('cx', d3.event.x).attr('cy', d3.event.y);
const cell = findCell(d3.event.x, d3.event.y); const cell = findCell(d3.event.x, d3.event.y);
if (pack.cells.h[cell] < 20) return; // ignore dragging on water if (pack.cells.h[cell] < 20) return; // ignore dragging on water
pack.religions[r].center = cell; pack.religions[r].center = cell;
@ -347,23 +401,29 @@ function editReligions() {
} }
function toggleLegend() { function toggleLegend() {
if (legend.selectAll("*").size()) {clearLegend(); return;}; // hide legend if (legend.selectAll('*').size()) {
const data = pack.religions.filter(r => r.i && !r.removed && r.area).sort((a, b) => b.area - a.area).map(r => [r.i, r.color, r.name]); clearLegend();
drawLegend("Religions", data); return;
} // hide legend
const data = pack.religions
.filter((r) => r.i && !r.removed && r.area)
.sort((a, b) => b.area - a.area)
.map((r) => [r.i, r.color, r.name]);
drawLegend('Religions', data);
} }
function togglePercentageMode() { function togglePercentageMode() {
if (body.dataset.type === "absolute") { if (body.dataset.type === 'absolute') {
body.dataset.type = "percentage"; body.dataset.type = 'percentage';
const totalArea = +religionsFooterArea.dataset.area; const totalArea = +religionsFooterArea.dataset.area;
const totalPopulation = +religionsFooterPopulation.dataset.population; const totalPopulation = +religionsFooterPopulation.dataset.population;
body.querySelectorAll(":scope > div").forEach(function(el) { body.querySelectorAll(':scope > div').forEach(function (el) {
el.querySelector(".biomeArea").innerHTML = rn(+el.dataset.area / totalArea * 100) + "%"; el.querySelector('.biomeArea').innerHTML = rn((+el.dataset.area / totalArea) * 100) + '%';
el.querySelector(".culturePopulation").innerHTML = rn(+el.dataset.population / totalPopulation * 100) + "%"; el.querySelector('.culturePopulation').innerHTML = rn((+el.dataset.population / totalPopulation) * 100) + '%';
}); });
} else { } else {
body.dataset.type = "absolute"; body.dataset.type = 'absolute';
religionsEditorAddLines(); religionsEditorAddLines();
} }
} }
@ -371,12 +431,19 @@ function editReligions() {
function showHierarchy() { function showHierarchy() {
// build hierarchy tree // build hierarchy tree
pack.religions[0].origin = null; pack.religions[0].origin = null;
const religions = pack.religions.filter(r => !r.removed); const religions = pack.religions.filter((r) => !r.removed);
if (religions.length < 3) {tip("Not enough religions to show hierarchy", false, "error"); return;} if (religions.length < 3) {
const root = d3.stratify().id(d => d.i).parentId(d => d.origin)(religions); tip('Not enough religions to show hierarchy', false, 'error');
return;
}
const root = d3
.stratify()
.id((d) => d.i)
.parentId((d) => d.origin)(religions);
const treeWidth = root.leaves().length; const treeWidth = root.leaves().length;
const treeHeight = root.height; const treeHeight = root.height;
const width = treeWidth * 40, height = treeHeight * 60; const width = treeWidth * 40,
height = treeHeight * 60;
const margin = {top: 10, right: 10, bottom: -5, left: 10}; const margin = {top: 10, right: 10, bottom: -5, left: 10};
const w = width - margin.left - margin.right; const w = width - margin.left - margin.right;
@ -385,110 +452,151 @@ function editReligions() {
// prepare svg // prepare svg
alertMessage.innerHTML = "<div id='religionInfo' class='chartInfo'>&#8205;</div>"; alertMessage.innerHTML = "<div id='religionInfo' class='chartInfo'>&#8205;</div>";
const svg = d3.select("#alertMessage").insert("svg", "#religionInfo").attr("id", "hierarchy") const svg = d3.select('#alertMessage').insert('svg', '#religionInfo').attr('id', 'hierarchy').attr('width', width).attr('height', height).style('text-anchor', 'middle');
.attr("width", width).attr("height", height).style("text-anchor", "middle"); const graph = svg.append('g').attr('transform', `translate(10, -45)`);
const graph = svg.append("g").attr("transform", `translate(10, -45)`); const links = graph.append('g').attr('fill', 'none').attr('stroke', '#aaaaaa');
const links = graph.append("g").attr("fill", "none").attr("stroke", "#aaaaaa"); const nodes = graph.append('g');
const nodes = graph.append("g");
renderTree(); renderTree();
function renderTree() { function renderTree() {
treeLayout(root); treeLayout(root);
links.selectAll('path').data(root.links()).enter() links
.append('path').attr("d", d => {return "M" + d.source.x + "," + d.source.y .selectAll('path')
+ "C" + d.source.x + "," + (d.source.y * 3 + d.target.y) / 4 .data(root.links())
+ " " + d.target.x + "," + (d.source.y * 2 + d.target.y) / 3 .enter()
+ " " + d.target.x + "," + d.target.y;}); .append('path')
.attr('d', (d) => {
return (
'M' +
d.source.x +
',' +
d.source.y +
'C' +
d.source.x +
',' +
(d.source.y * 3 + d.target.y) / 4 +
' ' +
d.target.x +
',' +
(d.source.y * 2 + d.target.y) / 3 +
' ' +
d.target.x +
',' +
d.target.y
);
});
const node = nodes.selectAll('g').data(root.descendants()).enter() const node = nodes
.append('g').attr("data-id", d => d.data.i).attr("stroke", "#333333") .selectAll('g')
.attr("transform", d => `translate(${d.x}, ${d.y})`) .data(root.descendants())
.on("mouseenter", () => religionHighlightOn(event)) .enter()
.on("mouseleave", () => religionHighlightOff(event)) .append('g')
.call(d3.drag().on("start", d => dragToReorigin(d))); .attr('data-id', (d) => d.data.i)
.attr('stroke', '#333333')
.attr('transform', (d) => `translate(${d.x}, ${d.y})`)
.on('mouseenter', () => religionHighlightOn(event))
.on('mouseleave', () => religionHighlightOff(event))
.call(d3.drag().on('start', (d) => dragToReorigin(d)));
node.append("path").attr("d", d => { node
if (d.data.type === "Folk") return "M11.3,0A11.3,11.3,0,1,1,-11.3,0A11.3,11.3,0,1,1,11.3,0"; else // circle .append('path')
if (d.data.type === "Heresy") return "M0,-14L14,0L0,14L-14,0Z"; else // diamond .attr('d', (d) => {
if (d.data.type === "Cult") return "M-6.5,-11.26l13,0l6.5,11.26l-6.5,11.26l-13,0l-6.5,-11.26Z"; else // hex if (d.data.type === 'Folk') return 'M11.3,0A11.3,11.3,0,1,1,-11.3,0A11.3,11.3,0,1,1,11.3,0';
if (!d.data.i) return "M5,0A5,5,0,1,1,-5,0A5,5,0,1,1,5,0"; else // small circle // circle
return "M-11,-11h22v22h-22Z"; // square else if (d.data.type === 'Heresy') return 'M0,-14L14,0L0,14L-14,0Z';
}).attr("fill", d => d.data.i ? d.data.color : "#ffffff") // diamond
.attr("stroke-dasharray", d => d.data.cells ? "null" : "1"); else if (d.data.type === 'Cult') return 'M-6.5,-11.26l13,0l6.5,11.26l-6.5,11.26l-13,0l-6.5,-11.26Z';
// hex
else if (!d.data.i) return 'M5,0A5,5,0,1,1,-5,0A5,5,0,1,1,5,0';
// small circle
else return 'M-11,-11h22v22h-22Z'; // square
})
.attr('fill', (d) => (d.data.i ? d.data.color : '#ffffff'))
.attr('stroke-dasharray', (d) => (d.data.cells ? 'null' : '1'));
node.append("text").attr("dy", ".35em").text(d => d.data.i ? d.data.code : ''); node
.append('text')
.attr('dy', '.35em')
.text((d) => (d.data.i ? d.data.code : ''));
} }
$("#alert").dialog({ $('#alert').dialog({
title: "Religions tree", width: fitContent(), resizable: false, title: 'Religions tree',
position: {my: "left center", at: "left+10 center", of: "svg"}, buttons: {}, width: fitContent(),
close: () => {alertMessage.innerHTML = "";} resizable: false,
position: {my: 'left center', at: 'left+10 center', of: 'svg'},
buttons: {},
close: () => {
alertMessage.innerHTML = '';
}
}); });
function dragToReorigin(d) { function dragToReorigin(d) {
if (isCtrlClick(d3.event.sourceEvent)) {changeCode(d); return;} if (isCtrlClick(d3.event.sourceEvent)) {
changeCode(d);
return;
}
const originLine = graph.append("path") const originLine = graph.append('path').attr('class', 'dragLine').attr('d', `M${d.x},${d.y}L${d.x},${d.y}`);
.attr("class", "dragLine").attr("d", `M${d.x},${d.y}L${d.x},${d.y}`);
d3.event.on("drag", () => { d3.event.on('drag', () => {
originLine.attr("d", `M${d.x},${d.y}L${d3.event.x},${d3.event.y}`) originLine.attr('d', `M${d.x},${d.y}L${d3.event.x},${d3.event.y}`);
}); });
d3.event.on("end", () => { d3.event.on('end', () => {
originLine.remove(); originLine.remove();
const selected = graph.select("path.selected"); const selected = graph.select('path.selected');
if (!selected.size()) return; if (!selected.size()) return;
const religion = d.data.i; const religion = d.data.i;
const oldOrigin = d.data.origin; const oldOrigin = d.data.origin;
let newOrigin = selected.datum().data.i; let newOrigin = selected.datum().data.i;
if (newOrigin == oldOrigin) return; // already a child of the selected node if (newOrigin == oldOrigin) return; // already a child of the selected node
if (newOrigin == religion) newOrigin = 0; // move to top if (newOrigin == religion) newOrigin = 0; // move to top
if (newOrigin && d.descendants().some(node => node.id == newOrigin)) return; // cannot be a child of its own child if (newOrigin && d.descendants().some((node) => node.id == newOrigin)) return; // cannot be a child of its own child
pack.religions[religion].origin = d.data.origin = newOrigin; // change data pack.religions[religion].origin = d.data.origin = newOrigin; // change data
showHierarchy() // update hierarchy showHierarchy(); // update hierarchy
}); });
} }
function changeCode(d) { function changeCode(d) {
prompt(`Please provide an abbreviation for ${d.data.name}`, {default:d.data.code}, v => { prompt(`Please provide an abbreviation for ${d.data.name}`, {default: d.data.code}, (v) => {
pack.religions[d.data.i].code = v; pack.religions[d.data.i].code = v;
nodes.select("g[data-id='"+d.data.i+"']").select("text").text(v); nodes
.select("g[data-id='" + d.data.i + "']")
.select('text')
.text(v);
}); });
} }
} }
function toggleExtinct() { function toggleExtinct() {
body.dataset.extinct = body.dataset.extinct !== "show" ? "show" : "hide"; body.dataset.extinct = body.dataset.extinct !== 'show' ? 'show' : 'hide';
religionsEditorAddLines(); religionsEditorAddLines();
} }
function enterReligionsManualAssignent() { function enterReligionsManualAssignent() {
if (!layerIsOn("toggleReligions")) toggleReligions(); if (!layerIsOn('toggleReligions')) toggleReligions();
customization = 7; customization = 7;
relig.append("g").attr("id", "temp"); relig.append('g').attr('id', 'temp');
document.querySelectorAll("#religionsBottom > button").forEach(el => el.style.display = "none"); document.querySelectorAll('#religionsBottom > button').forEach((el) => (el.style.display = 'none'));
document.getElementById("religionsManuallyButtons").style.display = "inline-block"; document.getElementById('religionsManuallyButtons').style.display = 'inline-block';
debug.select("#religionCenters").style("display", "none"); debug.select('#religionCenters').style('display', 'none');
religionsEditor.querySelectorAll(".hide").forEach(el => el.classList.add("hidden")); religionsEditor.querySelectorAll('.hide').forEach((el) => el.classList.add('hidden'));
religionsFooter.style.display = "none"; religionsFooter.style.display = 'none';
body.querySelectorAll("div > input, select, span, svg").forEach(e => e.style.pointerEvents = "none"); body.querySelectorAll('div > input, select, span, svg').forEach((e) => (e.style.pointerEvents = 'none'));
$("#religionsEditor").dialog({position: {my: "right top", at: "right-10 top+10", of: "svg"}}); $('#religionsEditor').dialog({position: {my: 'right top', at: 'right-10 top+10', of: 'svg'}});
tip("Click on religion to select, drag the circle to change religion", true); tip('Click on religion to select, drag the circle to change religion', true);
viewbox.style("cursor", "crosshair") viewbox.style('cursor', 'crosshair').on('click', selectReligionOnMapClick).call(d3.drag().on('start', dragReligionBrush)).on('touchmove mousemove', moveReligionBrush);
.on("click", selectReligionOnMapClick)
.call(d3.drag().on("start", dragReligionBrush))
.on("touchmove mousemove", moveReligionBrush);
body.querySelector("div").classList.add("selected"); body.querySelector('div').classList.add('selected');
} }
function selectReligionOnLineClick(i) { function selectReligionOnLineClick(i) {
if (customization !== 7) return; if (customization !== 7) return;
body.querySelector("div.selected").classList.remove("selected"); body.querySelector('div.selected').classList.remove('selected');
this.classList.add("selected"); this.classList.add('selected');
} }
function selectReligionOnMapClick() { function selectReligionOnMapClick() {
@ -496,17 +604,17 @@ function editReligions() {
const i = findCell(point[0], point[1]); const i = findCell(point[0], point[1]);
if (pack.cells.h[i] < 20) return; if (pack.cells.h[i] < 20) return;
const assigned = relig.select("#temp").select("polygon[data-cell='"+i+"']"); const assigned = relig.select('#temp').select("polygon[data-cell='" + i + "']");
const religion = assigned.size() ? +assigned.attr("data-religion") : pack.cells.religion[i]; const religion = assigned.size() ? +assigned.attr('data-religion') : pack.cells.religion[i];
body.querySelector("div.selected").classList.remove("selected"); body.querySelector('div.selected').classList.remove('selected');
body.querySelector("div[data-id='"+religion+"']").classList.add("selected"); body.querySelector("div[data-id='" + religion + "']").classList.add('selected');
} }
function dragReligionBrush() { function dragReligionBrush() {
const r = +religionsManuallyBrushNumber.value; const r = +religionsManuallyBrushNumber.value;
d3.event.on("drag", () => { d3.event.on('drag', () => {
if (!d3.event.dx && !d3.event.dy) return; if (!d3.event.dx && !d3.event.dy) return;
const p = d3.mouse(this); const p = d3.mouse(this);
moveCircle(p[0], p[1], r); moveCircle(p[0], p[1], r);
@ -519,19 +627,19 @@ function editReligions() {
// change religion within selection // change religion within selection
function changeReligionForSelection(selection) { function changeReligionForSelection(selection) {
const temp = relig.select("#temp"); const temp = relig.select('#temp');
const selected = body.querySelector("div.selected"); const selected = body.querySelector('div.selected');
const r = +selected.dataset.id; // religionNew const r = +selected.dataset.id; // religionNew
const color = pack.religions[r].color || "#ffffff"; const color = pack.religions[r].color || '#ffffff';
selection.forEach(function(i) { selection.forEach(function (i) {
const exists = temp.select("polygon[data-cell='"+i+"']"); const exists = temp.select("polygon[data-cell='" + i + "']");
const religionOld = exists.size() ? +exists.attr("data-religion") : pack.cells.religion[i]; const religionOld = exists.size() ? +exists.attr('data-religion') : pack.cells.religion[i];
if (r === religionOld) return; if (r === religionOld) return;
// change of append new element // change of append new element
if (exists.size()) exists.attr("data-religion", r).attr("fill", color); if (exists.size()) exists.attr('data-religion', r).attr('fill', color);
else temp.append("polygon").attr("data-cell", i).attr("data-religion", r).attr("points", getPackPolygon(i)).attr("fill", color); else temp.append('polygon').attr('data-cell', i).attr('data-religion', r).attr('points', getPackPolygon(i)).attr('fill', color);
}); });
} }
@ -543,8 +651,8 @@ function editReligions() {
} }
function applyReligionsManualAssignent() { function applyReligionsManualAssignent() {
const changed = relig.select("#temp").selectAll("polygon"); const changed = relig.select('#temp').selectAll('polygon');
changed.each(function() { changed.each(function () {
const i = +this.dataset.cell; const i = +this.dataset.cell;
const r = +this.dataset.religion; const r = +this.dataset.religion;
pack.cells.religion[i] = r; pack.cells.religion[i] = r;
@ -560,46 +668,55 @@ function editReligions() {
function exitReligionsManualAssignment(close) { function exitReligionsManualAssignment(close) {
customization = 0; customization = 0;
relig.select("#temp").remove(); relig.select('#temp').remove();
removeCircle(); removeCircle();
document.querySelectorAll("#religionsBottom > button").forEach(el => el.style.display = "inline-block"); document.querySelectorAll('#religionsBottom > button').forEach((el) => (el.style.display = 'inline-block'));
document.getElementById("religionsManuallyButtons").style.display = "none"; document.getElementById('religionsManuallyButtons').style.display = 'none';
religionsEditor.querySelectorAll(".hide").forEach(el => el.classList.remove("hidden")); religionsEditor.querySelectorAll('.hide').forEach((el) => el.classList.remove('hidden'));
religionsFooter.style.display = "block"; religionsFooter.style.display = 'block';
body.querySelectorAll("div > input, select, span, svg").forEach(e => e.style.pointerEvents = "all"); body.querySelectorAll('div > input, select, span, svg').forEach((e) => (e.style.pointerEvents = 'all'));
if(!close) $("#religionsEditor").dialog({position: {my: "right top", at: "right-10 top+10", of: "svg"}}); if (!close) $('#religionsEditor').dialog({position: {my: 'right top', at: 'right-10 top+10', of: 'svg'}});
debug.select("#religionCenters").style("display", null); debug.select('#religionCenters').style('display', null);
restoreDefaultEvents(); restoreDefaultEvents();
clearMainTip(); clearMainTip();
const selected = body.querySelector("div.selected"); const selected = body.querySelector('div.selected');
if (selected) selected.classList.remove("selected"); if (selected) selected.classList.remove('selected');
} }
function enterAddReligionMode() { function enterAddReligionMode() {
if (this.classList.contains("pressed")) {exitAddReligionMode(); return;}; if (this.classList.contains('pressed')) {
exitAddReligionMode();
return;
}
customization = 8; customization = 8;
this.classList.add("pressed"); this.classList.add('pressed');
tip("Click on the map to add a new religion", true); tip('Click on the map to add a new religion', true);
viewbox.style("cursor", "crosshair").on("click", addReligion); viewbox.style('cursor', 'crosshair').on('click', addReligion);
body.querySelectorAll("div > input, select, span, svg").forEach(e => e.style.pointerEvents = "none"); body.querySelectorAll('div > input, select, span, svg').forEach((e) => (e.style.pointerEvents = 'none'));
} }
function exitAddReligionMode() { function exitAddReligionMode() {
customization = 0; customization = 0;
restoreDefaultEvents(); restoreDefaultEvents();
clearMainTip(); clearMainTip();
body.querySelectorAll("div > input, select, span, svg").forEach(e => e.style.pointerEvents = "all"); body.querySelectorAll('div > input, select, span, svg').forEach((e) => (e.style.pointerEvents = 'all'));
if (religionsAdd.classList.contains("pressed")) religionsAdd.classList.remove("pressed"); if (religionsAdd.classList.contains('pressed')) religionsAdd.classList.remove('pressed');
} }
function addReligion() { function addReligion() {
const point = d3.mouse(this); const point = d3.mouse(this);
const center = findCell(point[0], point[1]); const center = findCell(point[0], point[1]);
if (pack.cells.h[center] < 20) {tip("You cannot place religion center into the water. Please click on a land cell", false, "error"); return;} if (pack.cells.h[center] < 20) {
const occupied = pack.religions.some(r => !r.removed && r.center === center); tip('You cannot place religion center into the water. Please click on a land cell', false, 'error');
if (occupied) {tip("This cell is already a religion center. Please select a different cell", false, "error"); return;} return;
}
const occupied = pack.religions.some((r) => !r.removed && r.center === center);
if (occupied) {
tip('This cell is already a religion center. Please select a different cell', false, 'error');
return;
}
if (d3.event.shiftKey === false) exitAddReligionMode(); if (d3.event.shiftKey === false) exitAddReligionMode();
Religions.add(center); Religions.add(center);
@ -610,28 +727,27 @@ function editReligions() {
} }
function downloadReligionsData() { function downloadReligionsData() {
const unit = areaUnit.value === "square" ? distanceUnitInput.value + "2" : areaUnit.value; const unit = areaUnit.value === 'square' ? distanceUnitInput.value + '2' : areaUnit.value;
let data = "Id,Religion,Color,Type,Form,Deity,Area "+unit+",Believers\n"; // headers let data = 'Id,Religion,Color,Type,Form,Deity,Area ' + unit + ',Believers\n'; // headers
body.querySelectorAll(":scope > div").forEach(function(el) { body.querySelectorAll(':scope > div').forEach(function (el) {
data += el.dataset.id + ","; data += el.dataset.id + ',';
data += el.dataset.name + ","; data += el.dataset.name + ',';
data += el.dataset.color + ","; data += el.dataset.color + ',';
data += el.dataset.type + ","; data += el.dataset.type + ',';
data += el.dataset.form + ","; data += el.dataset.form + ',';
data += el.dataset.deity.replace(",", " -") + ","; data += el.dataset.deity.replace(',', ' -') + ',';
data += el.dataset.area + ","; data += el.dataset.area + ',';
data += el.dataset.population + "\n"; data += el.dataset.population + '\n';
}); });
const name = getFileName("Religions") + ".csv"; const name = getFileName('Religions') + '.csv';
downloadFile(data, name); downloadFile(data, name);
} }
function closeReligionsEditor() { function closeReligionsEditor() {
debug.select("#religionCenters").remove(); debug.select('#religionCenters').remove();
exitReligionsManualAssignment("close"); exitReligionsManualAssignment('close');
exitAddReligionMode(); exitAddReligionMode();
} }
} }

View file

@ -321,8 +321,7 @@ function editResources() {
function regenerateCurrentResources() { function regenerateCurrentResources() {
const message = 'Are you sure you want to regenerate resources? <br>This action cannot be reverted'; const message = 'Are you sure you want to regenerate resources? <br>This action cannot be reverted';
const onConfirm = () => regenerateResources(); confirmationDialog({title: 'Regenerate resources', message, confirm: 'Regenerate', onConfirm: regenerateResources});
confirmationDialog({title: 'Regenerate resources', message, confirm: 'Regenerate', onConfirm});
} }
function resourcesRestoreDefaults() { function resourcesRestoreDefaults() {

View file

@ -1,20 +1,21 @@
"use strict"; 'use strict';
function editRiver(id) { function editRiver(id) {
if (customization) return; if (customization) return;
if (elSelected && d3.event && d3.event.target.id === elSelected.attr("id")) return; if (elSelected && d3.event && d3.event.target.id === elSelected.attr('id')) return;
closeDialogs(".stable"); closeDialogs('.stable');
if (!layerIsOn("toggleRivers")) toggleRivers(); if (!layerIsOn('toggleRivers')) toggleRivers();
const node = id ? document.getElementById(id) : d3.event.target; const node = id ? document.getElementById(id) : d3.event.target;
elSelected = d3.select(node).on("click", addInterimControlPoint); elSelected = d3.select(node).on('click', addInterimControlPoint);
viewbox.on("touchmove mousemove", showEditorTips); viewbox.on('touchmove mousemove', showEditorTips);
debug.append("g").attr("id", "controlPoints").attr("transform", elSelected.attr("transform")); debug.append('g').attr('id', 'controlPoints').attr('transform', elSelected.attr('transform'));
updateRiverData(); updateRiverData();
drawControlPoints(node); drawControlPoints(node);
$("#riverEditor").dialog({ $('#riverEditor').dialog({
title: "Edit River", resizable: false, title: 'Edit River',
position: {my: "center top+80", at: "top", of: node, collision: "fit"}, resizable: false,
position: {my: 'center top+80', at: 'top', of: node, collision: 'fit'},
close: closeRiverEditor close: closeRiverEditor
}); });
@ -22,64 +23,64 @@ function editRiver(id) {
modules.editRiver = true; modules.editRiver = true;
// add listeners // add listeners
document.getElementById("riverName").addEventListener("input", changeName); document.getElementById('riverName').addEventListener('input', changeName);
document.getElementById("riverType").addEventListener("input", changeType); document.getElementById('riverType').addEventListener('input', changeType);
document.getElementById("riverNameCulture").addEventListener("click", generateNameCulture); document.getElementById('riverNameCulture').addEventListener('click', generateNameCulture);
document.getElementById("riverNameRandom").addEventListener("click", generateNameRandom); document.getElementById('riverNameRandom').addEventListener('click', generateNameRandom);
document.getElementById("riverMainstem").addEventListener("change", changeParent); document.getElementById('riverMainstem').addEventListener('change', changeParent);
document.getElementById("riverSourceWidth").addEventListener("input", changeSourceWidth); document.getElementById('riverSourceWidth').addEventListener('input', changeSourceWidth);
document.getElementById("riverWidthFactor").addEventListener("input", changeWidthFactor); document.getElementById('riverWidthFactor').addEventListener('input', changeWidthFactor);
document.getElementById("riverNew").addEventListener("click", toggleRiverCreationMode); document.getElementById('riverNew').addEventListener('click', toggleRiverCreationMode);
document.getElementById("riverEditStyle").addEventListener("click", () => editStyle("rivers")); document.getElementById('riverEditStyle').addEventListener('click', () => editStyle('rivers'));
document.getElementById("riverElevationProfile").addEventListener("click", showElevationProfile); document.getElementById('riverElevationProfile').addEventListener('click', showElevationProfile);
document.getElementById("riverLegend").addEventListener("click", editRiverLegend); document.getElementById('riverLegend').addEventListener('click', editRiverLegend);
document.getElementById("riverRemove").addEventListener("click", removeRiver); document.getElementById('riverRemove').addEventListener('click', removeRiver);
function showEditorTips() { function showEditorTips() {
showMainTip(); showMainTip();
if (d3.event.target.parentNode.id === elSelected.attr("id")) tip("Drag to move, click to add a control point"); else if (d3.event.target.parentNode.id === elSelected.attr('id')) tip('Drag to move, 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 getRiver() { function getRiver() {
const riverId = +elSelected.attr("id").slice(5); const riverId = +elSelected.attr('id').slice(5);
const river = pack.rivers.find(r => r.i === riverId); const river = pack.rivers.find((r) => r.i === riverId);
return river; return river;
} }
function updateRiverData() { function updateRiverData() {
const r = getRiver(); const r = getRiver();
document.getElementById("riverName").value = r.name; document.getElementById('riverName').value = r.name;
document.getElementById("riverType").value = r.type; document.getElementById('riverType').value = r.type;
const parentSelect = document.getElementById("riverMainstem"); const parentSelect = document.getElementById('riverMainstem');
parentSelect.options.length = 0; parentSelect.options.length = 0;
const parent = r.parent || r.i; const parent = r.parent || r.i;
const sortedRivers = pack.rivers.slice().sort((a, b) => a.name > b.name ? 1 : -1); const sortedRivers = pack.rivers.slice().sort((a, b) => (a.name > b.name ? 1 : -1));
sortedRivers.forEach(river => { sortedRivers.forEach((river) => {
const opt = new Option(river.name, river.i, false, river.i === parent); const opt = new Option(river.name, river.i, false, river.i === parent);
parentSelect.options.add(opt); parentSelect.options.add(opt);
}); });
document.getElementById("riverBasin").value = pack.rivers.find(river => river.i === r.basin).name; document.getElementById('riverBasin').value = pack.rivers.find((river) => river.i === r.basin).name;
document.getElementById("riverDischarge").value = r.discharge + " m³/s"; document.getElementById('riverDischarge').value = r.discharge + ' m³/s';
r.length = elSelected.node().getTotalLength() / 2; r.length = elSelected.node().getTotalLength() / 2;
const length = rn(r.length * distanceScaleInput.value) + " " + distanceUnitInput.value; const length = rn(r.length * distanceScaleInput.value) + ' ' + distanceUnitInput.value;
document.getElementById("riverLength").value = length; document.getElementById('riverLength').value = length;
const width = rn(r.width * distanceScaleInput.value, 3) + " " + distanceUnitInput.value; const width = rn(r.width * distanceScaleInput.value, 3) + ' ' + distanceUnitInput.value;
document.getElementById("riverWidth").value = width; document.getElementById('riverWidth').value = width;
document.getElementById("riverSourceWidth").value = r.sourceWidth; document.getElementById('riverSourceWidth').value = r.sourceWidth;
document.getElementById("riverWidthFactor").value = r.widthFactor; document.getElementById('riverWidthFactor').value = r.widthFactor;
} }
function drawControlPoints(node) { function drawControlPoints(node) {
const length = getRiver().length; const length = getRiver().length;
const segments = Math.ceil(length / 4); const segments = Math.ceil(length / 4);
const increment = rn(length / segments * 1e5); const increment = rn((length / segments) * 1e5);
for (let i = increment * segments, c = i; i >= 0; i -= increment, c += increment) { for (let i = increment * segments, c = i; i >= 0; i -= increment, c += increment) {
const p1 = node.getPointAtLength(i / 1e5); const p1 = node.getPointAtLength(i / 1e5);
const p2 = node.getPointAtLength(c / 1e5); const p2 = node.getPointAtLength(c / 1e5);
@ -88,37 +89,39 @@ function editRiver(id) {
} }
function addControlPoint(point, before = null) { function addControlPoint(point, before = null) {
debug.select("#controlPoints").insert("circle", before) debug.select('#controlPoints').insert('circle', before).attr('cx', point[0]).attr('cy', point[1]).attr('r', 0.6).call(d3.drag().on('drag', dragControlPoint)).on('click', clickControlPoint);
.attr("cx", point[0]).attr("cy", point[1]).attr("r", .6)
.call(d3.drag().on("drag", dragControlPoint))
.on("click", clickControlPoint);
} }
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);
redrawRiver(); redrawRiver();
} }
function redrawRiver() { function redrawRiver() {
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')]);
});
if (points.length < 2) return; if (points.length < 2) return;
if (points.length === 2) { if (points.length === 2) {
const p0 = points[0], p1 = points[1]; const p0 = points[0],
p1 = points[1];
const angle = Math.atan2(p1[1] - p0[1], p1[0] - p0[0]); const angle = Math.atan2(p1[1] - p0[1], p1[0] - p0[0]);
const sin = Math.sin(angle), cos = Math.cos(angle); const sin = Math.sin(angle),
elSelected.attr("d", `M${p0[0]},${p0[1]} L${p1[0]},${p1[1]} l${-sin/2},${cos/2} Z`); cos = Math.cos(angle);
elSelected.attr('d', `M${p0[0]},${p0[1]} L${p1[0]},${p1[1]} l${-sin / 2},${cos / 2} Z`);
return; return;
} }
const widthFactor = +document.getElementById("riverWidthFactor").value; const widthFactor = +document.getElementById('riverWidthFactor').value;
const sourceWidth = +document.getElementById("riverSourceWidth").value; const sourceWidth = +document.getElementById('riverSourceWidth').value;
const [path, length, offset] = Rivers.getPath(points, widthFactor, sourceWidth); const [path, length, offset] = Rivers.getPath(points, widthFactor, sourceWidth);
elSelected.attr("d", path); elSelected.attr('d', path);
const r = getRiver(); const r = getRiver();
if (r) { if (r) {
@ -137,10 +140,10 @@ function editRiver(id) {
function addInterimControlPoint() { function addInterimControlPoint() {
const point = d3.mouse(this); const point = d3.mouse(this);
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) + ')');
redrawRiver(); redrawRiver();
} }
@ -160,14 +163,14 @@ function editRiver(id) {
function generateNameRandom() { function generateNameRandom() {
const r = getRiver(); const r = getRiver();
if (r) r.name = riverName.value = Names.getBase(rand(nameBases.length-1)); if (r) r.name = riverName.value = Names.getBase(rand(nameBases.length - 1));
} }
function changeParent() { function changeParent() {
const r = getRiver(); const r = getRiver();
r.parent = +this.value; r.parent = +this.value;
r.basin = pack.rivers.find(river => river.i === r.parent).basin; r.basin = pack.rivers.find((river) => river.i === r.parent).basin;
document.getElementById("riverBasin").value = pack.rivers.find(river => river.i === r.basin).name; document.getElementById('riverBasin').value = pack.rivers.find((river) => river.i === r.basin).name;
} }
function changeSourceWidth() { function changeSourceWidth() {
@ -186,26 +189,26 @@ function editRiver(id) {
} }
function editRiverLegend() { function editRiverLegend() {
const id = elSelected.attr("id"); const id = elSelected.attr('id');
const river = getRiver(); const river = getRiver();
editNotes(id, river.name + " " + river.type); editNotes(id, river.name + ' ' + river.type);
} }
function toggleRiverCreationMode() { function toggleRiverCreationMode() {
if (document.getElementById("riverNew").classList.contains("pressed")) exitRiverCreationMode(); if (document.getElementById('riverNew').classList.contains('pressed')) exitRiverCreationMode();
else { else {
document.getElementById("riverNew").classList.add("pressed"); document.getElementById('riverNew').classList.add('pressed');
tip("Click on map to add control points", true, "warn"); tip('Click on map to add control points', true, 'warn');
viewbox.on("click", addPointOnClick).style("cursor", "crosshair"); viewbox.on('click', addPointOnClick).style('cursor', 'crosshair');
elSelected.on("click", null); elSelected.on('click', null);
} }
} }
function addPointOnClick() { function addPointOnClick() {
if (!elSelected.attr("data-new")) { if (!elSelected.attr('data-new')) {
debug.select("#controlPoints").selectAll("circle").remove(); debug.select('#controlPoints').selectAll('circle').remove();
const id = getNextId("river"); const id = getNextId('river');
elSelected = d3.select(elSelected.node().parentNode).append("path").attr("id", id).attr("data-new", 1); elSelected = d3.select(elSelected.node().parentNode).append('path').attr('id', id).attr('data-new', 1);
} }
// add control point // add control point
@ -215,20 +218,22 @@ function editRiver(id) {
} }
function exitRiverCreationMode() { function exitRiverCreationMode() {
riverNew.classList.remove("pressed"); riverNew.classList.remove('pressed');
clearMainTip(); clearMainTip();
viewbox.on("click", clicked).style("cursor", "default"); viewbox.on('click', clicked).style('cursor', 'default');
elSelected.on("click", addInterimControlPoint); elSelected.on('click', addInterimControlPoint);
if (!elSelected.attr("data-new")) return; // no need to create a new river if (!elSelected.attr('data-new')) return; // no need to create a new river
elSelected.attr("data-new", null); elSelected.attr('data-new', null);
// add a river // add a river
const r = +elSelected.attr("id").slice(5); const r = +elSelected.attr('id').slice(5);
const node = elSelected.node(), length = node.getTotalLength() / 2; const node = elSelected.node(),
length = node.getTotalLength() / 2;
const cells = []; const cells = [];
const segments = Math.ceil(length / 4), increment = rn(length / segments * 1e5); const segments = Math.ceil(length / 4),
increment = rn((length / segments) * 1e5);
for (let i = increment * segments, c = i; i >= 0; i -= increment, c += increment) { for (let i = increment * segments, c = i; i >= 0; i -= increment, c += increment) {
const p = node.getPointAtLength(i / 1e5); const p = node.getPointAtLength(i / 1e5);
const cell = findCell(p.x, p.y); const cell = findCell(p.x, p.y);
@ -236,38 +241,34 @@ function editRiver(id) {
cells.push(cell); cells.push(cell);
} }
const source = cells[0], mouth = last(cells); const source = cells[0],
mouth = last(cells);
const name = Rivers.getName(mouth); const name = Rivers.getName(mouth);
const smallLength = pack.rivers.map(r => r.length||0).sort((a,b) => a-b)[Math.ceil(pack.rivers.length * .15)]; const smallLength = pack.rivers.map((r) => r.length || 0).sort((a, b) => a - b)[Math.ceil(pack.rivers.length * 0.15)];
const type = length < smallLength ? rw({"Creek":9, "River":3, "Brook":3, "Stream":1}) : "River"; const type = length < smallLength ? rw({Creek: 9, River: 3, Brook: 3, Stream: 1}) : 'River';
const discharge = rn(cells.length * 20 * Math.random()); const discharge = rn(cells.length * 20 * Math.random());
const widthFactor = +document.getElementById("riverWidthFactor").value; const widthFactor = +document.getElementById('riverWidthFactor').value;
const sourceWidth = +document.getElementById("riverSourceWidth").value; const sourceWidth = +document.getElementById('riverSourceWidth').value;
pack.rivers.push({i:r, source, mouth, discharge, length, width: sourceWidth, widthFactor, sourceWidth, parent:0, name, type, basin:r}); pack.rivers.push({i: r, source, mouth, discharge, length, width: sourceWidth, widthFactor, sourceWidth, parent: 0, name, type, basin: r});
} }
function removeRiver() { function removeRiver() {
alertMessage.innerHTML = "Are you sure you want to remove the river? All tributaries will be auto-removed"; const message = 'Are you sure you want to remove the river? <br>All tributaries will be auto-removed';
$("#alert").dialog({resizable: false, width: "22em", title: "Remove river", const onConfirm = () => {
buttons: { const river = +elSelected.attr('id').slice(5);
Remove: function() { Rivers.remove(river);
$(this).dialog("close"); elSelected.remove(); // if river if missed in pack.rivers
const river = +elSelected.attr("id").slice(5); $('#riverEditor').dialog('close');
Rivers.remove(river); };
elSelected.remove(); // keep if river if missed in pack.rivers confirmationDialog({title: 'Remove river', message, confirm: 'Remove', onConfirm});
$("#riverEditor").dialog("close");
},
Cancel: function() {$(this).dialog("close");}
}
});
} }
function closeRiverEditor() { function closeRiverEditor() {
exitRiverCreationMode(); exitRiverCreationMode();
elSelected.on("click", null); elSelected.on('click', null);
debug.select("#controlPoints").remove(); debug.select('#controlPoints').remove();
unselect(); unselect();
} }
} }

View file

@ -1,39 +1,41 @@
"use strict"; 'use strict';
function overviewRivers() { function overviewRivers() {
if (customization) return; if (customization) return;
closeDialogs("#riversOverview, .stable"); closeDialogs('#riversOverview, .stable');
if (!layerIsOn("toggleRivers")) toggleRivers(); if (!layerIsOn('toggleRivers')) toggleRivers();
const body = document.getElementById("riversBody"); const body = document.getElementById('riversBody');
riversOverviewAddLines(); riversOverviewAddLines();
$("#riversOverview").dialog(); $('#riversOverview').dialog();
if (modules.overviewRivers) return; if (modules.overviewRivers) return;
modules.overviewRivers = true; modules.overviewRivers = true;
$("#riversOverview").dialog({ $('#riversOverview').dialog({
title: "Rivers Overview", resizable: false, width: fitContent(), title: 'Rivers Overview',
position: {my: "right top", at: "right-10 top+10", of: "svg", collision: "fit"} resizable: false,
width: fitContent(),
position: {my: 'right top', at: 'right-10 top+10', of: 'svg', collision: 'fit'}
}); });
// add listeners // add listeners
document.getElementById("riversOverviewRefresh").addEventListener("click", riversOverviewAddLines); document.getElementById('riversOverviewRefresh').addEventListener('click', riversOverviewAddLines);
document.getElementById("addNewRiver").addEventListener("click", toggleAddRiver); document.getElementById('addNewRiver').addEventListener('click', toggleAddRiver);
document.getElementById("riversBasinHighlight").addEventListener("click", toggleBasinsHightlight); document.getElementById('riversBasinHighlight').addEventListener('click', toggleBasinsHightlight);
document.getElementById("riversExport").addEventListener("click", downloadRiversData); document.getElementById('riversExport').addEventListener('click', downloadRiversData);
document.getElementById("riversRemoveAll").addEventListener("click", triggerAllRiversRemove); document.getElementById('riversRemoveAll').addEventListener('click', triggerAllRiversRemove);
// add line for each river // add line for each river
function riversOverviewAddLines() { function riversOverviewAddLines() {
body.innerHTML = ""; body.innerHTML = '';
let lines = ""; let lines = '';
const unit = distanceUnitInput.value; const unit = distanceUnitInput.value;
for (const r of pack.rivers) { for (const r of pack.rivers) {
const discharge = r.discharge + " m³/s"; const discharge = r.discharge + ' m³/s';
const length = rn(r.length * distanceScaleInput.value) + " " + unit; const length = rn(r.length * distanceScaleInput.value) + ' ' + unit;
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 += `<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>
@ -47,115 +49,107 @@ function overviewRivers() {
<span data-tip="Remove river" class="icon-trash-empty"></span> <span data-tip="Remove river" class="icon-trash-empty"></span>
</div>`; </div>`;
} }
body.insertAdjacentHTML("beforeend", lines); body.insertAdjacentHTML('beforeend', lines);
// update footer // update footer
riversFooterNumber.innerHTML = pack.rivers.length; riversFooterNumber.innerHTML = pack.rivers.length;
const averageDischarge = rn(d3.mean(pack.rivers.map(r => r.discharge))); const averageDischarge = rn(d3.mean(pack.rivers.map((r) => r.discharge)));
riversFooterDischarge.innerHTML = averageDischarge + " m³/s"; riversFooterDischarge.innerHTML = averageDischarge + ' m³/s';
const averageLength = rn(d3.mean(pack.rivers.map(r => r.length)) ); const averageLength = rn(d3.mean(pack.rivers.map((r) => r.length)));
riversFooterLength.innerHTML = (averageLength * distanceScaleInput.value) + " " + unit; riversFooterLength.innerHTML = averageLength * distanceScaleInput.value + ' ' + unit;
const averageWidth = rn(d3.mean(pack.rivers.map(r => r.width)), 3); const averageWidth = rn(d3.mean(pack.rivers.map((r) => r.width)), 3);
riversFooterWidth.innerHTML = rn(averageWidth * distanceScaleInput.value, 3) + " " + unit; riversFooterWidth.innerHTML = rn(averageWidth * distanceScaleInput.value, 3) + ' ' + unit;
// add listeners // add listeners
body.querySelectorAll("div.states").forEach(el => el.addEventListener("mouseenter", ev => riverHighlightOn(ev))); body.querySelectorAll('div.states').forEach((el) => el.addEventListener('mouseenter', (ev) => riverHighlightOn(ev)));
body.querySelectorAll("div.states").forEach(el => el.addEventListener("mouseleave", ev => riverHighlightOff(ev))); body.querySelectorAll('div.states').forEach((el) => el.addEventListener('mouseleave', (ev) => riverHighlightOff(ev)));
body.querySelectorAll("div > span.icon-dot-circled").forEach(el => el.addEventListener("click", zoomToRiver)); body.querySelectorAll('div > span.icon-dot-circled').forEach((el) => el.addEventListener('click', zoomToRiver));
body.querySelectorAll("div > span.icon-pencil").forEach(el => el.addEventListener("click", openRiverEditor)); body.querySelectorAll('div > span.icon-pencil').forEach((el) => el.addEventListener('click', openRiverEditor));
body.querySelectorAll("div > span.icon-trash-empty").forEach(el => el.addEventListener("click", triggerRiverRemove)); body.querySelectorAll('div > span.icon-trash-empty').forEach((el) => el.addEventListener('click', triggerRiverRemove));
applySorting(riversHeader); applySorting(riversHeader);
} }
function riverHighlightOn(event) { function riverHighlightOn(event) {
if (!layerIsOn("toggleRivers")) toggleRivers(); if (!layerIsOn('toggleRivers')) toggleRivers();
const r = +event.target.dataset.id; const r = +event.target.dataset.id;
rivers.select("#river"+r).attr("stroke", "red").attr("stroke-width", 1); rivers
.select('#river' + r)
.attr('stroke', 'red')
.attr('stroke-width', 1);
} }
function riverHighlightOff(e) { function riverHighlightOff(e) {
const r = +e.target.dataset.id; const r = +e.target.dataset.id;
rivers.select("#river"+r).attr("stroke", null).attr("stroke-width", null); rivers
.select('#river' + r)
.attr('stroke', null)
.attr('stroke-width', null);
} }
function zoomToRiver() { function zoomToRiver() {
const r = +this.parentNode.dataset.id; const r = +this.parentNode.dataset.id;
const river = rivers.select("#river"+r).node(); const river = rivers.select('#river' + r).node();
highlightElement(river); highlightElement(river);
} }
function toggleBasinsHightlight() { function toggleBasinsHightlight() {
if (rivers.attr("data-basin") === "hightlighted") { if (rivers.attr('data-basin') === 'hightlighted') {
rivers.selectAll("*").attr("fill", null); rivers.selectAll('*').attr('fill', null);
rivers.attr("data-basin", null); rivers.attr('data-basin', null);
} else { } else {
rivers.attr("data-basin", "hightlighted"); rivers.attr('data-basin', 'hightlighted');
const basins = [...new Set(pack.rivers.map(r => r.basin))]; const basins = [...new Set(pack.rivers.map((r) => r.basin))];
const colors = ["#1f77b4","#ff7f0e","#2ca02c","#d62728","#9467bd","#8c564b","#e377c2","#7f7f7f","#bcbd22","#17becf"]; const colors = ['#1f77b4', '#ff7f0e', '#2ca02c', '#d62728', '#9467bd', '#8c564b', '#e377c2', '#7f7f7f', '#bcbd22', '#17becf'];
basins.forEach((b, i) => { basins.forEach((b, i) => {
const color = colors[i % colors.length]; const color = colors[i % colors.length];
pack.rivers.filter(r => r.basin === b).forEach(r => { pack.rivers
rivers.select("#river"+r.i).attr("fill", color); .filter((r) => r.basin === b)
}); .forEach((r) => {
rivers.select('#river' + r.i).attr('fill', color);
});
}); });
} }
} }
function downloadRiversData() { function downloadRiversData() {
let data = "Id,River,Type,Discharge,Length,Width,Basin\n"; // headers let data = 'Id,River,Type,Discharge,Length,Width,Basin\n'; // headers
body.querySelectorAll(":scope > div").forEach(function(el) { body.querySelectorAll(':scope > div').forEach(function (el) {
const d = el.dataset; const d = el.dataset;
const discharge = d.discharge + " m³/s" const discharge = d.discharge + ' m³/s';
const length = rn(d.length * distanceScaleInput.value) + " " + distanceUnitInput.value; const length = rn(d.length * distanceScaleInput.value) + ' ' + distanceUnitInput.value;
const width = rn(d.width * distanceScaleInput.value, 3) + " " + distanceUnitInput.value; const width = rn(d.width * distanceScaleInput.value, 3) + ' ' + distanceUnitInput.value;
data += [d.id, d.name, d.type, discharge, length, width, d.basin].join(",") + "\n"; data += [d.id, d.name, d.type, discharge, length, width, d.basin].join(',') + '\n';
}); });
const name = getFileName("Rivers") + ".csv"; const name = getFileName('Rivers') + '.csv';
downloadFile(data, name); downloadFile(data, name);
} }
function openRiverEditor() { function openRiverEditor() {
editRiver("river"+this.parentNode.dataset.id); editRiver('river' + this.parentNode.dataset.id);
} }
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?
All tributaries will be auto-removed`;
$("#alert").dialog({resizable: false, width: "22em", title: "Remove river", const message = 'Are you sure you want to remove the river? <br>All tributaries will be auto-removed';
buttons: { const onConfirm = () => {
Remove: function() { Rivers.remove(river);
Rivers.remove(river); riversOverviewAddLines();
riversOverviewAddLines(); };
$(this).dialog("close"); confirmationDialog({title: 'Remove river', message, confirm: 'Remove', onConfirm});
},
Cancel: function() {$(this).dialog("close");}
}
});
} }
function triggerAllRiversRemove() { function triggerAllRiversRemove() {
alertMessage.innerHTML = `Are you sure you want to remove all rivers?`; const message = 'Are you sure you want to remove all rivers? <br>This action cannot be reverted';
$("#alert").dialog({resizable: false, title: "Remove all rivers", const onConfirm = () => {
buttons: { pack.rivers = [];
Remove: function() { rivers.selectAll('*').remove();
$(this).dialog("close"); riversOverviewAddLines();
removeAllRivers(); };
}, confirmationDialog({title: 'Remove all rivers', message, confirm: 'Remove', onConfirm});
Cancel: function() {$(this).dialog("close");}
}
});
} }
function removeAllRivers() {
pack.rivers = [];
rivers.selectAll("*").remove();
riversOverviewAddLines();
}
} }

View file

@ -1,93 +1,94 @@
"use strict"; 'use strict';
function editRoute(onClick) { function editRoute(onClick) {
if (customization) return; if (customization) return;
if (!onClick && elSelected && d3.event.target.id === elSelected.attr("id")) return; if (!onClick && elSelected && d3.event.target.id === elSelected.attr('id')) return;
closeDialogs(".stable"); closeDialogs('.stable');
if (!layerIsOn("toggleRoutes")) toggleRoutes(); if (!layerIsOn('toggleRoutes')) toggleRoutes();
$("#routeEditor").dialog({ $('#routeEditor').dialog({
title: "Edit Route", resizable: false, title: 'Edit Route',
position: {my: "center top+60", at: "top", of: d3.event, collision: "fit"}, resizable: false,
position: {my: 'center top+60', at: 'top', of: d3.event, collision: 'fit'},
close: closeRoutesEditor close: closeRoutesEditor
}); });
debug.append("g").attr("id", "controlPoints"); debug.append('g').attr('id', 'controlPoints');
const node = onClick ? elSelected.node() : d3.event.target; const node = onClick ? elSelected.node() : d3.event.target;
elSelected = d3.select(node).on("click", addInterimControlPoint); elSelected = d3.select(node).on('click', addInterimControlPoint);
drawControlPoints(node); drawControlPoints(node);
selectRouteGroup(node); selectRouteGroup(node);
viewbox.on("touchmove mousemove", showEditorTips); viewbox.on('touchmove mousemove', showEditorTips);
if (onClick) toggleRouteCreationMode(); if (onClick) toggleRouteCreationMode();
if (modules.editRoute) return; if (modules.editRoute) return;
modules.editRoute = true; modules.editRoute = true;
// add listeners // add listeners
document.getElementById("routeGroupsShow").addEventListener("click", showGroupSection); document.getElementById('routeGroupsShow').addEventListener('click', showGroupSection);
document.getElementById("routeGroup").addEventListener("change", changeRouteGroup); document.getElementById('routeGroup').addEventListener('change', changeRouteGroup);
document.getElementById("routeGroupAdd").addEventListener("click", toggleNewGroupInput); document.getElementById('routeGroupAdd').addEventListener('click', toggleNewGroupInput);
document.getElementById("routeGroupName").addEventListener("change", createNewGroup); document.getElementById('routeGroupName').addEventListener('change', createNewGroup);
document.getElementById("routeGroupRemove").addEventListener("click", removeRouteGroup); document.getElementById('routeGroupRemove').addEventListener('click', removeRouteGroup);
document.getElementById("routeGroupsHide").addEventListener("click", hideGroupSection); document.getElementById('routeGroupsHide').addEventListener('click', hideGroupSection);
document.getElementById("routeElevationProfile").addEventListener("click", showElevationProfile); document.getElementById('routeElevationProfile').addEventListener('click', showElevationProfile);
document.getElementById("routeEditStyle").addEventListener("click", editGroupStyle); document.getElementById('routeEditStyle').addEventListener('click', editGroupStyle);
document.getElementById("routeSplit").addEventListener("click", toggleRouteSplitMode); document.getElementById('routeSplit').addEventListener('click', toggleRouteSplitMode);
document.getElementById("routeLegend").addEventListener("click", editRouteLegend); document.getElementById('routeLegend').addEventListener('click', editRouteLegend);
document.getElementById("routeNew").addEventListener("click", toggleRouteCreationMode); document.getElementById('routeNew').addEventListener('click', toggleRouteCreationMode);
document.getElementById("routeRemove").addEventListener("click", removeRoute); document.getElementById('routeRemove').addEventListener('click', removeRoute);
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.select('#controlPoints').insert('circle', before).attr('cx', point[0]).attr('cy', point[1]).attr('r', 0.6).call(d3.drag().on('drag', dragControlPoint)).on('click', clickControlPoint);
.attr("cx", point[0]).attr("cy", point[1]).attr("r", .6)
.call(d3.drag().on("drag", dragControlPoint))
.on("click", clickControlPoint);
} }
function addInterimControlPoint() { function addInterimControlPoint() {
const point = d3.mouse(this); const point = d3.mouse(this);
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);
redrawRoute(); redrawRoute();
} }
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();
routeLength.innerHTML = rn(l * distanceScaleInput.value) + " " + distanceUnitInput.value; routeLength.innerHTML = rn(l * distanceScaleInput.value) + ' ' + distanceUnitInput.value;
if (modules.elevation) showEPForRoute(elSelected.node()); if (modules.elevation) showEPForRoute(elSelected.node());
} }
@ -98,24 +99,24 @@ 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) {
const group = node.parentNode.id; const group = node.parentNode.id;
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));
}); });
} }
@ -125,131 +126,142 @@ function editRoute(onClick) {
} }
function toggleNewGroupInput() { function toggleNewGroupInput() {
if (routeGroupName.style.display === "none") { if (routeGroupName.style.display === 'none') {
routeGroupName.style.display = "inline-block"; routeGroupName.style.display = 'inline-block';
routeGroupName.focus(); routeGroupName.focus();
routeGroup.style.display = "none"; routeGroup.style.display = 'none';
} 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');
return; return;
} }
if (Number.isFinite(+group.charAt(0))) { if (Number.isFinite(+group.charAt(0))) {
tip("Group name should start with a letter", false, "error"); tip('Group name should start with a letter', false, 'error');
return; return;
} }
// just rename if only 1 element left // just rename if only 1 element left
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;
} }
const newGroup = elSelected.node().parentNode.cloneNode(false); const newGroup = elSelected.node().parentNode.cloneNode(false);
document.getElementById("routes").appendChild(newGroup); document.getElementById('routes').appendChild(newGroup);
newGroup.id = group; newGroup.id = group;
document.getElementById("routeGroup").options.add(new Option(group, group, false, true)); document.getElementById('routeGroup').options.add(new Option(group, group, false, true));
document.getElementById(group).appendChild(elSelected.node()); document.getElementById(group).appendChild(elSelected.node());
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
${basic ? "all elements in the group" : "the entire route group"}? const message = `Are you sure you want to remove ${basic ? 'all elements in the group' : 'the entire route group'}?<br><br>Routes to be removed: ${count}`;
<br><br>Routes to be removed: ${count}`; const onConfirm = () => {
$("#alert").dialog({resizable: false, title: "Remove route group", $('#routeEditor').dialog('close');
buttons: { hideGroupSection();
Remove: function() { if (basic)
$(this).dialog("close"); routes
$("#routeEditor").dialog("close"); .select('#' + group)
hideGroupSection(); .selectAll('path')
if (basic) routes.select("#"+group).selectAll("path").remove(); .remove();
else routes.select("#"+group).remove(); else routes.select('#' + group).remove();
}, };
Cancel: function() {$(this).dialog("close");} confirmationDialog({title: 'Remove route group', message, confirm: 'Remove', onConfirm});
}
});
} }
function editGroupStyle() { function editGroupStyle() {
const g = elSelected.node().parentNode.id; const g = elSelected.node().parentNode.id;
editStyle("routes", g); editStyle('routes', g);
} }
function toggleRouteSplitMode() { function toggleRouteSplitMode() {
document.getElementById("routeNew").classList.remove("pressed"); document.getElementById('routeNew').classList.remove('pressed');
this.classList.toggle("pressed"); this.classList.toggle('pressed');
} }
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');
group.append("path").attr("id", id).attr("d", lineGen(points2)); group.append('path').attr('id', id).attr('d', lineGen(points2));
debug.select("#controlPoints").selectAll("circle").remove(); debug.select('#controlPoints').selectAll('circle').remove();
drawControlPoints(elSelected.node()); drawControlPoints(elSelected.node());
} }
function toggleRouteCreationMode() { function toggleRouteCreationMode() {
document.getElementById("routeSplit").classList.remove("pressed"); document.getElementById('routeSplit').classList.remove('pressed');
document.getElementById("routeNew").classList.toggle("pressed"); document.getElementById('routeNew').classList.toggle('pressed');
if (document.getElementById("routeNew").classList.contains("pressed")) { if (document.getElementById('routeNew').classList.contains('pressed')) {
tip("Click on map to add control points", true); tip('Click on map to add control points', true);
viewbox.on("click", addPointOnClick).style("cursor", "crosshair"); viewbox.on('click', addPointOnClick).style('cursor', 'crosshair');
elSelected.on("click", null); elSelected.on('click', null);
} else { } else {
clearMainTip(); clearMainTip();
viewbox.on("click", clicked).style("cursor", "default"); viewbox.on('click', clicked).style('cursor', 'default');
elSelected.on("click", addInterimControlPoint).attr("data-new", null); elSelected.on('click', addInterimControlPoint).attr('data-new', null);
} }
} }
function addPointOnClick() { function addPointOnClick() {
// create new route // create new route
if (!elSelected.attr("data-new")) { if (!elSelected.attr('data-new')) {
debug.select("#controlPoints").selectAll("circle").remove(); debug.select('#controlPoints').selectAll('circle').remove();
const parent = elSelected.node().parentNode; const parent = elSelected.node().parentNode;
const id = getNextId("route"); const id = getNextId('route');
elSelected = d3.select(parent).append("path").attr("id", id).attr("data-new", 1); elSelected = d3.select(parent).append('path').attr('id', id).attr('data-new', 1);
} }
addControlPoint(d3.mouse(this)); addControlPoint(d3.mouse(this));
@ -257,30 +269,25 @@ function editRoute(onClick) {
} }
function editRouteLegend() { function editRouteLegend() {
const id = elSelected.attr("id"); const id = elSelected.attr('id');
editNotes(id, id); editNotes(id, id);
} }
function removeRoute() { function removeRoute() {
alertMessage.innerHTML = "Are you sure you want to remove the route?"; const message = 'Are you sure you want to remove the route? <br>This action cannot be reverted';
$("#alert").dialog({resizable: false, title: "Remove route", const onConfirm = () => {
buttons: { elSelected.remove();
Remove: function() { $('#routeEditor').dialog('close');
$(this).dialog("close"); };
elSelected.remove(); confirmationDialog({title: 'Remove route', message, confirm: 'Remove', onConfirm});
$("#routeEditor").dialog("close");
},
Cancel: function() {$(this).dialog("close");}
}
});
} }
function closeRoutesEditor() { function closeRoutesEditor() {
elSelected.attr("data-new", null).on("click", null); elSelected.attr('data-new', null).on('click', null);
clearMainTip(); clearMainTip();
routeSplit.classList.remove("pressed"); routeSplit.classList.remove('pressed');
routeNew.classList.remove("pressed"); routeNew.classList.remove('pressed');
debug.select("#controlPoints").remove(); debug.select('#controlPoints').remove();
unselect(); unselect();
} }
} }

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

View file

@ -1,57 +1,57 @@
"use strict"; 'use strict';
function editUnits() { function editUnits() {
closeDialogs("#unitsEditor, .stable"); closeDialogs('#unitsEditor, .stable');
$("#unitsEditor").dialog(); $('#unitsEditor').dialog();
if (modules.editUnits) return; if (modules.editUnits) return;
modules.editUnits = true; modules.editUnits = true;
$("#unitsEditor").dialog({ $('#unitsEditor').dialog({
title: "Units Editor", title: 'Units Editor',
position: {my: "right top", at: "right-10 top+10", of: "svg", collision: "fit"} position: {my: 'right top', at: 'right-10 top+10', of: 'svg', collision: 'fit'}
}); });
// add listeners // add listeners
document.getElementById("distanceUnitInput").addEventListener("change", changeDistanceUnit); document.getElementById('distanceUnitInput').addEventListener('change', changeDistanceUnit);
document.getElementById("distanceScaleOutput").addEventListener("input", changeDistanceScale); document.getElementById('distanceScaleOutput').addEventListener('input', changeDistanceScale);
document.getElementById("distanceScaleInput").addEventListener("change", changeDistanceScale); document.getElementById('distanceScaleInput').addEventListener('change', changeDistanceScale);
document.getElementById("areaUnit").addEventListener("change", () => lock("areaUnit")); document.getElementById('areaUnit').addEventListener('change', () => lock('areaUnit'));
document.getElementById("heightUnit").addEventListener("change", changeHeightUnit); document.getElementById('heightUnit').addEventListener('change', changeHeightUnit);
document.getElementById("heightExponentInput").addEventListener("input", changeHeightExponent); document.getElementById('heightExponentInput').addEventListener('input', changeHeightExponent);
document.getElementById("heightExponentOutput").addEventListener("input", changeHeightExponent); document.getElementById('heightExponentOutput').addEventListener('input', changeHeightExponent);
document.getElementById("temperatureScale").addEventListener("change", changeTemperatureScale); document.getElementById('temperatureScale').addEventListener('change', changeTemperatureScale);
document.getElementById("barSizeOutput").addEventListener("input", changeScaleBarSize); document.getElementById('barSizeOutput').addEventListener('input', changeScaleBarSize);
document.getElementById("barSize").addEventListener("input", changeScaleBarSize); document.getElementById('barSize').addEventListener('input', changeScaleBarSize);
document.getElementById("barLabel").addEventListener("input", changeScaleBarLabel); document.getElementById('barLabel').addEventListener('input', changeScaleBarLabel);
document.getElementById("barPosX").addEventListener("input", changeScaleBarPosition); document.getElementById('barPosX').addEventListener('input', changeScaleBarPosition);
document.getElementById("barPosY").addEventListener("input", changeScaleBarPosition); document.getElementById('barPosY').addEventListener('input', changeScaleBarPosition);
document.getElementById("barBackOpacity").addEventListener("input", changeScaleBarOpacity); document.getElementById('barBackOpacity').addEventListener('input', changeScaleBarOpacity);
document.getElementById("barBackColor").addEventListener("input", changeScaleBarColor); document.getElementById('barBackColor').addEventListener('input', changeScaleBarColor);
document.getElementById("populationRateOutput").addEventListener("input", changePopulationRate); document.getElementById('populationRateOutput').addEventListener('input', changePopulationRate);
document.getElementById("populationRate").addEventListener("change", changePopulationRate); document.getElementById('populationRate').addEventListener('change', changePopulationRate);
document.getElementById("urbanizationOutput").addEventListener("input", changeUrbanizationRate); document.getElementById('urbanizationOutput').addEventListener('input', changeUrbanizationRate);
document.getElementById("urbanization").addEventListener("change", changeUrbanizationRate); document.getElementById('urbanization').addEventListener('change', changeUrbanizationRate);
document.getElementById("addLinearRuler").addEventListener("click", addRuler); document.getElementById('addLinearRuler').addEventListener('click', addRuler);
document.getElementById("addOpisometer").addEventListener("click", toggleOpisometerMode); document.getElementById('addOpisometer').addEventListener('click', toggleOpisometerMode);
document.getElementById("addRouteOpisometer").addEventListener("click", toggleRouteOpisometerMode); document.getElementById('addRouteOpisometer').addEventListener('click', toggleRouteOpisometerMode);
document.getElementById("addPlanimeter").addEventListener("click", togglePlanimeterMode); document.getElementById('addPlanimeter').addEventListener('click', togglePlanimeterMode);
document.getElementById("removeRulers").addEventListener("click", removeAllRulers); document.getElementById('removeRulers').addEventListener('click', removeAllRulers);
document.getElementById("unitsRestore").addEventListener("click", restoreDefaultUnits); document.getElementById('unitsRestore').addEventListener('click', restoreDefaultUnits);
function changeDistanceUnit() { function changeDistanceUnit() {
if (this.value === "custom_name") { if (this.value === 'custom_name') {
prompt("Provide a custom name for a distance unit", {default:""}, custom => { prompt('Provide a custom name for a distance unit', {default: ''}, (custom) => {
this.options.add(new Option(custom, custom, false, true)); this.options.add(new Option(custom, custom, false, true));
lock("distanceUnit"); lock('distanceUnit');
drawScaleBar(); drawScaleBar();
calculateFriendlyGridSize(); calculateFriendlyGridSize();
}); });
return; return;
} }
lock("distanceUnit"); lock('distanceUnit');
drawScaleBar(); drawScaleBar();
calculateFriendlyGridSize(); calculateFriendlyGridSize();
} }
@ -59,285 +59,281 @@ function editUnits() {
function changeDistanceScale() { function changeDistanceScale() {
const scale = +this.value; const scale = +this.value;
if (!scale || isNaN(scale) || scale < 0) { if (!scale || isNaN(scale) || scale < 0) {
tip("Distance scale should be a positive number", false, "error"); tip('Distance scale should be a positive number', false, 'error');
this.value = document.getElementById("distanceScaleInput").dataset.value; this.value = document.getElementById('distanceScaleInput').dataset.value;
return; return;
} }
document.getElementById("distanceScaleOutput").value = scale; document.getElementById('distanceScaleOutput').value = scale;
document.getElementById("distanceScaleInput").value = scale; document.getElementById('distanceScaleInput').value = scale;
document.getElementById("distanceScaleInput").dataset.value = scale; document.getElementById('distanceScaleInput').dataset.value = scale;
lock("distanceScale"); lock('distanceScale');
drawScaleBar(); drawScaleBar();
calculateFriendlyGridSize(); calculateFriendlyGridSize();
} }
function changeHeightUnit() { function changeHeightUnit() {
if (this.value === "custom_name") { if (this.value === 'custom_name') {
prompt("Provide a custom name for a height unit", {default:""}, custom => { prompt('Provide a custom name for a height unit', {default: ''}, (custom) => {
this.options.add(new Option(custom, custom, false, true)); this.options.add(new Option(custom, custom, false, true));
lock("heightUnit"); lock('heightUnit');
}); });
return; return;
} }
lock("heightUnit"); lock('heightUnit');
} }
function changeHeightExponent() { function changeHeightExponent() {
document.getElementById("heightExponentInput").value = this.value; document.getElementById('heightExponentInput').value = this.value;
document.getElementById("heightExponentOutput").value = this.value; document.getElementById('heightExponentOutput').value = this.value;
calculateTemperatures(); calculateTemperatures();
if (layerIsOn("toggleTemp")) drawTemp(); if (layerIsOn('toggleTemp')) drawTemp();
lock("heightExponent"); lock('heightExponent');
} }
function changeTemperatureScale() { function changeTemperatureScale() {
lock("temperatureScale"); lock('temperatureScale');
if (layerIsOn("toggleTemp")) drawTemp(); if (layerIsOn('toggleTemp')) drawTemp();
} }
function changeScaleBarSize() { function changeScaleBarSize() {
document.getElementById("barSize").value = this.value; document.getElementById('barSize').value = this.value;
document.getElementById("barSizeOutput").value = this.value; document.getElementById('barSizeOutput').value = this.value;
drawScaleBar(); drawScaleBar();
lock("barSize"); lock('barSize');
} }
function changeScaleBarPosition() { function changeScaleBarPosition() {
lock("barPosX"); lock('barPosX');
lock("barPosY"); lock('barPosY');
fitScaleBar(); fitScaleBar();
} }
function changeScaleBarLabel() { function changeScaleBarLabel() {
lock("barLabel"); lock('barLabel');
drawScaleBar(); drawScaleBar();
} }
function changeScaleBarOpacity() { function changeScaleBarOpacity() {
scaleBar.select("rect").attr("opacity", this.value); scaleBar.select('rect').attr('opacity', this.value);
lock("barBackOpacity"); lock('barBackOpacity');
} }
function changeScaleBarColor() { function changeScaleBarColor() {
scaleBar.select("rect").attr("fill", this.value); scaleBar.select('rect').attr('fill', this.value);
lock("barBackColor"); lock('barBackColor');
} }
function changePopulationRate() { function changePopulationRate() {
const rate = +this.value; const rate = +this.value;
if (!rate || isNaN(rate) || rate <= 0) { if (!rate || isNaN(rate) || rate <= 0) {
tip("Population rate should be a positive number", false, "error"); tip('Population rate should be a positive number', false, 'error');
this.value = document.getElementById("populationRate").dataset.value; this.value = document.getElementById('populationRate').dataset.value;
return; return;
} }
document.getElementById("populationRateOutput").value = rate; document.getElementById('populationRateOutput').value = rate;
document.getElementById("populationRate").value = rate; document.getElementById('populationRate').value = rate;
document.getElementById("populationRate").dataset.value = rate; document.getElementById('populationRate').dataset.value = rate;
lock("populationRate"); lock('populationRate');
} }
function changeUrbanizationRate() { function changeUrbanizationRate() {
const rate = +this.value; const rate = +this.value;
if (!rate || isNaN(rate) || rate < 0) { if (!rate || isNaN(rate) || rate < 0) {
tip("Urbanization rate should be a number", false, "error"); tip('Urbanization rate should be a number', false, 'error');
this.value = document.getElementById("urbanization").dataset.value; this.value = document.getElementById('urbanization').dataset.value;
return; return;
} }
document.getElementById("urbanizationOutput").value = rate; document.getElementById('urbanizationOutput').value = rate;
document.getElementById("urbanization").value = rate; document.getElementById('urbanization').value = rate;
document.getElementById("urbanization").dataset.value = rate; document.getElementById('urbanization').dataset.value = rate;
lock("urbanization"); lock('urbanization');
} }
function restoreDefaultUnits() { function restoreDefaultUnits() {
// distanceScale // distanceScale
document.getElementById("distanceScaleOutput").value = 3; document.getElementById('distanceScaleOutput').value = 3;
document.getElementById("distanceScaleInput").value = 3; document.getElementById('distanceScaleInput').value = 3;
document.getElementById("distanceScaleInput").dataset.value = 3; document.getElementById('distanceScaleInput').dataset.value = 3;
unlock("distanceScale"); unlock('distanceScale');
// units // units
const US = navigator.language === "en-US"; const US = navigator.language === 'en-US';
const UK = navigator.language === "en-GB"; const UK = navigator.language === 'en-GB';
distanceUnitInput.value = US || UK ? "mi" : "km"; distanceUnitInput.value = US || UK ? 'mi' : 'km';
heightUnit.value = US || UK ? "ft" : "m"; heightUnit.value = US || UK ? 'ft' : 'm';
temperatureScale.value = US ? "°F" : "°C"; temperatureScale.value = US ? '°F' : '°C';
areaUnit.value = "square"; areaUnit.value = 'square';
localStorage.removeItem("distanceUnit"); localStorage.removeItem('distanceUnit');
localStorage.removeItem("heightUnit"); localStorage.removeItem('heightUnit');
localStorage.removeItem("temperatureScale"); localStorage.removeItem('temperatureScale');
localStorage.removeItem("areaUnit"); localStorage.removeItem('areaUnit');
calculateFriendlyGridSize(); calculateFriendlyGridSize();
// height exponent // height exponent
heightExponentInput.value = heightExponentOutput.value = 1.8; heightExponentInput.value = heightExponentOutput.value = 1.8;
localStorage.removeItem("heightExponent"); localStorage.removeItem('heightExponent');
calculateTemperatures(); calculateTemperatures();
// scale bar // scale bar
barSizeOutput.value = barSize.value = 2; barSizeOutput.value = barSize.value = 2;
barLabel.value = ""; barLabel.value = '';
barBackOpacity.value = .2; barBackOpacity.value = 0.2;
barBackColor.value = "#ffffff"; barBackColor.value = '#ffffff';
barPosX.value = barPosY.value = 99; barPosX.value = barPosY.value = 99;
localStorage.removeItem("barSize"); localStorage.removeItem('barSize');
localStorage.removeItem("barLabel"); localStorage.removeItem('barLabel');
localStorage.removeItem("barBackOpacity"); localStorage.removeItem('barBackOpacity');
localStorage.removeItem("barBackColor"); localStorage.removeItem('barBackColor');
localStorage.removeItem("barPosX"); localStorage.removeItem('barPosX');
localStorage.removeItem("barPosY"); localStorage.removeItem('barPosY');
drawScaleBar(); drawScaleBar();
// population // population
populationRateOutput.value = populationRate.value = 1000; populationRateOutput.value = populationRate.value = 1000;
urbanizationOutput.value = urbanization.value = 1; urbanizationOutput.value = urbanization.value = 1;
localStorage.removeItem("populationRate"); localStorage.removeItem('populationRate');
localStorage.removeItem("urbanization"); localStorage.removeItem('urbanization');
} }
function addRuler() { function addRuler() {
if (!layerIsOn("toggleRulers")) toggleRulers(); if (!layerIsOn('toggleRulers')) toggleRulers();
const pt = document.getElementById('map').createSVGPoint(); const pt = document.getElementById('map').createSVGPoint();
pt.x = graphWidth / 2, pt.y = graphHeight / 4; (pt.x = graphWidth / 2), (pt.y = graphHeight / 4);
const p = pt.matrixTransform(viewbox.node().getScreenCTM().inverse()); const p = pt.matrixTransform(viewbox.node().getScreenCTM().inverse());
const dx = graphWidth / 4 / scale; const dx = graphWidth / 4 / scale;
const dy = (rulers.data.length * 40) % (graphHeight / 2); const dy = (rulers.data.length * 40) % (graphHeight / 2);
const from = [p.x-dx | 0, p.y+dy | 0]; const from = [(p.x - dx) | 0, (p.y + dy) | 0];
const to = [p.x+dx | 0, p.y+dy | 0]; const to = [(p.x + dx) | 0, (p.y + dy) | 0];
rulers.create(Ruler, [from, to]).draw(); rulers.create(Ruler, [from, to]).draw();
} }
function toggleOpisometerMode() { function toggleOpisometerMode() {
if (this.classList.contains("pressed")) { if (this.classList.contains('pressed')) {
restoreDefaultEvents(); restoreDefaultEvents();
clearMainTip(); clearMainTip();
this.classList.remove("pressed"); this.classList.remove('pressed');
} else { } else {
if (!layerIsOn("toggleRulers")) toggleRulers(); if (!layerIsOn('toggleRulers')) toggleRulers();
tip("Draw a curve to measure length. Hold Shift to disallow path optimization", true); tip('Draw a curve to measure length. Hold Shift to disallow path optimization', true);
unitsBottom.querySelectorAll(".pressed").forEach(button => button.classList.remove("pressed")); unitsBottom.querySelectorAll('.pressed').forEach((button) => button.classList.remove('pressed'));
this.classList.add("pressed"); this.classList.add('pressed');
viewbox.style("cursor", "crosshair").call(d3.drag().on("start", function() { viewbox.style('cursor', 'crosshair').call(
const point = d3.mouse(this); d3.drag().on('start', function () {
const opisometer = rulers.create(Opisometer, [point]).draw();
d3.event.on("drag", function() {
const point = d3.mouse(this); const point = d3.mouse(this);
opisometer.addPoint(point); const opisometer = rulers.create(Opisometer, [point]).draw();
});
d3.event.on("end", function() { d3.event.on('drag', function () {
restoreDefaultEvents(); const point = d3.mouse(this);
clearMainTip(); opisometer.addPoint(point);
addOpisometer.classList.remove("pressed"); });
if (opisometer.points.length < 2) rulers.remove(opisometer.id);
if (!d3.event.sourceEvent.shiftKey) opisometer.optimize(); d3.event.on('end', function () {
}); restoreDefaultEvents();
})); clearMainTip();
addOpisometer.classList.remove('pressed');
if (opisometer.points.length < 2) rulers.remove(opisometer.id);
if (!d3.event.sourceEvent.shiftKey) opisometer.optimize();
});
})
);
} }
} }
function toggleRouteOpisometerMode() { function toggleRouteOpisometerMode() {
if (this.classList.contains("pressed")) { if (this.classList.contains('pressed')) {
restoreDefaultEvents(); restoreDefaultEvents();
clearMainTip(); clearMainTip();
this.classList.remove("pressed"); this.classList.remove('pressed');
} else { } else {
if (!layerIsOn("toggleRulers")) toggleRulers(); if (!layerIsOn('toggleRulers')) toggleRulers();
tip("Draw a curve along routes to measure length. Hold Shift to measure away from roads.", true); tip('Draw a curve along routes to measure length. Hold Shift to measure away from roads.', true);
unitsBottom.querySelectorAll(".pressed").forEach(button => button.classList.remove("pressed")); unitsBottom.querySelectorAll('.pressed').forEach((button) => button.classList.remove('pressed'));
this.classList.add("pressed"); this.classList.add('pressed');
viewbox.style("cursor", "crosshair").call(d3.drag().on("start", function() { viewbox.style('cursor', 'crosshair').call(
const cells = pack.cells; d3.drag().on('start', function () {
const burgs = pack.burgs; const cells = pack.cells;
const point = d3.mouse(this); const burgs = pack.burgs;
const c = findCell(point[0], point[1]); const point = d3.mouse(this);
if (cells.road[c] || d3.event.sourceEvent.shiftKey) { const c = findCell(point[0], point[1]);
const b = cells.burg[c]; if (cells.road[c] || d3.event.sourceEvent.shiftKey) {
const x = b ? burgs[b].x : cells.p[c][0]; const b = cells.burg[c];
const y = b ? burgs[b].y : cells.p[c][1]; const x = b ? burgs[b].x : cells.p[c][0];
const routeOpisometer = rulers.create(RouteOpisometer, [[x, y]]).draw(); const y = b ? burgs[b].y : cells.p[c][1];
const routeOpisometer = rulers.create(RouteOpisometer, [[x, y]]).draw();
d3.event.on("drag", function () { d3.event.on('drag', function () {
const point = d3.mouse(this); const point = d3.mouse(this);
const c = findCell(point[0], point[1]); const c = findCell(point[0], point[1]);
if (cells.road[c] || d3.event.sourceEvent.shiftKey) { if (cells.road[c] || d3.event.sourceEvent.shiftKey) {
routeOpisometer.trackCell(c, true); routeOpisometer.trackCell(c, true);
} }
}); });
d3.event.on("end", function () { d3.event.on('end', function () {
restoreDefaultEvents();
clearMainTip();
addRouteOpisometer.classList.remove('pressed');
if (routeOpisometer.points.length < 2) {
rulers.remove(routeOpisometer.id);
}
});
} else {
restoreDefaultEvents(); restoreDefaultEvents();
clearMainTip(); clearMainTip();
addRouteOpisometer.classList.remove("pressed"); addRouteOpisometer.classList.remove('pressed');
if (routeOpisometer.points.length < 2) { tip('Must start in a cell with a route in it', false, 'error');
rulers.remove(routeOpisometer.id); }
} })
}); );
} else {
restoreDefaultEvents();
clearMainTip();
addRouteOpisometer.classList.remove("pressed");
tip("Must start in a cell with a route in it", false, "error");
}
}));
} }
} }
function togglePlanimeterMode() { function togglePlanimeterMode() {
if (this.classList.contains("pressed")) { if (this.classList.contains('pressed')) {
restoreDefaultEvents(); restoreDefaultEvents();
clearMainTip(); clearMainTip();
this.classList.remove("pressed"); this.classList.remove('pressed');
} else { } else {
if (!layerIsOn("toggleRulers")) toggleRulers(); if (!layerIsOn('toggleRulers')) toggleRulers();
tip("Draw a curve to measure its area. Hold Shift to disallow path optimization", true); tip('Draw a curve to measure its area. Hold Shift to disallow path optimization', true);
unitsBottom.querySelectorAll(".pressed").forEach(button => button.classList.remove("pressed")); unitsBottom.querySelectorAll('.pressed').forEach((button) => button.classList.remove('pressed'));
this.classList.add("pressed"); this.classList.add('pressed');
viewbox.style("cursor", "crosshair").call(d3.drag().on("start", function() { viewbox.style('cursor', 'crosshair').call(
const point = d3.mouse(this); d3.drag().on('start', function () {
const planimeter = rulers.create(Planimeter, [point]).draw();
d3.event.on("drag", function() {
const point = d3.mouse(this); const point = d3.mouse(this);
planimeter.addPoint(point); const planimeter = rulers.create(Planimeter, [point]).draw();
});
d3.event.on("end", function() { d3.event.on('drag', function () {
restoreDefaultEvents(); const point = d3.mouse(this);
clearMainTip(); planimeter.addPoint(point);
addPlanimeter.classList.remove("pressed"); });
if (planimeter.points.length < 3) rulers.remove(planimeter.id);
else if (!d3.event.sourceEvent.shiftKey) planimeter.optimize();
});
}));
d3.event.on('end', function () {
restoreDefaultEvents();
clearMainTip();
addPlanimeter.classList.remove('pressed');
if (planimeter.points.length < 3) rulers.remove(planimeter.id);
else if (!d3.event.sourceEvent.shiftKey) planimeter.optimize();
});
})
);
} }
} }
function removeAllRulers() { function removeAllRulers() {
if (!rulers.data.length) return; if (!rulers.data.length) return;
alertMessage.innerHTML = `
Are you sure you want to remove all placed rulers? const message = '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`; const onConfirm = () => {
$("#alert").dialog({resizable: false, title: "Remove all rulers", rulers.undraw();
buttons: { rulers = new Rulers();
Remove: function() { };
$(this).dialog("close"); confirmationDialog({title: 'Remove all rulers', message, confirm: 'Remove', onConfirm});
rulers.undraw();
rulers = new Rulers();
},
Cancel: function() {$(this).dialog("close");}
}
});
} }
} }