@@ -8092,8 +8100,8 @@
-
-
+
+
@@ -8111,8 +8119,8 @@
-
-
+
+
@@ -8139,13 +8147,13 @@
-
-
-
-
+
+
+
+
-
+
@@ -8154,18 +8162,18 @@
-
+
-
+
-
+
diff --git a/modules/io/load.js b/modules/io/load.js
index 69fc360c..8e05a798 100644
--- a/modules/io/load.js
+++ b/modules/io/load.js
@@ -458,20 +458,7 @@ async function parseLoadedData(data, mapVersion) {
if (isVisible(ruler)) turnOn("toggleRulers");
if (isVisible(scaleBar)) turnOn("toggleScaleBar");
if (isVisibleNode(byId("vignette"))) turnOn("toggleVignette");
-
- window.uniquePictures = [];
- window.iconReload = true;
- $("#armies").each(function(id, armyGroup) {
- $(armyGroup).children().each(function(aId, army){
- $(army).children().each(function(rId, regiment){
- if($(regiment).find(".regimentImage").length !== 0 && $(regiment).find(".regimentImage").attr('href')) {
- let icon = $(regiment).find(".regimentImage").attr('href');
- window.uniquePictures.push(icon);
- }
- });
- });
- });
- window.uniquePictures = [...new Set(uniquePictures)];
+
getCurrentPreset();
}
diff --git a/modules/markers-generator.js b/modules/markers-generator.js
index 93db4368..367cdd5f 100644
--- a/modules/markers-generator.js
+++ b/modules/markers-generator.js
@@ -11,7 +11,7 @@ window.Markers = (function () {
/*
Default markers config:
type - short description (snake-case)
- icon - unicode character, make sure it's supported by most of the browsers. Source: emojipedia.org
+ icon - unicode character or url to image
dx: icon offset in x direction, in pixels
dy: icon offset in y direction, in pixels
min: minimum number of candidates to add at least 1 marker
diff --git a/modules/military-generator.js b/modules/military-generator.js
index 61224d69..5aea87db 100644
--- a/modules/military-generator.js
+++ b/modules/military-generator.js
@@ -380,7 +380,7 @@ window.Military = (function () {
: gauss(options.year - 100, 150, 1, options.year - 6);
const conflict = campaign ? ` during the ${campaign.name}` : "";
const legend = `Regiment was formed in ${year} ${options.era}${conflict}. ${station}${troops}`;
- notes.push({id: `regiment${s.i}-${r.i}`, name: `${r.icon} ${r.name}`, legend});
+ notes.push({id: `regiment${s.i}-${r.i}`, name: r.name, legend});
};
return {
diff --git a/modules/renderers/draw-markers.js b/modules/renderers/draw-markers.js
index 0f45f31f..d9d6b816 100644
--- a/modules/renderers/draw-markers.js
+++ b/modules/renderers/draw-markers.js
@@ -42,9 +42,12 @@ function drawMarker(marker, rescale = 1) {
const viewX = rn(x - zoomSize / 2, 1);
const viewY = rn(y - zoomSize, 1);
+ const isExternal = icon.startsWith("http");
+
return /* html */ `
`;
}
diff --git a/modules/renderers/draw-military.js b/modules/renderers/draw-military.js
index bada7fa0..96ed52e4 100644
--- a/modules/renderers/draw-military.js
+++ b/modules/renderers/draw-military.js
@@ -54,14 +54,14 @@ const drawRegiments = function (regiments, s) {
.attr("class", "regimentIcon")
.attr("x", d => x(d) - size)
.attr("y", d => d.y)
- .text(d => d.icon);
+ .text(d => (d.icon.startsWith("http") ? "" : d.icon));
g.append("image")
.attr("class", "regimentImage")
.attr("x", d => x(d) - h)
.attr("y", d => y(d))
.attr("height", h)
.attr("width", h)
- .text(d => d.image);
+ .attr("href", d => (d.icon.startsWith("http") ? d.icon : ""));
};
const drawRegiment = function (reg, stateId) {
@@ -102,14 +102,14 @@ const drawRegiment = function (reg, stateId) {
.attr("class", "regimentIcon")
.attr("x", x1 - size)
.attr("y", reg.y)
- .text(reg.icon);
+ .text(reg.icon.startsWith("http") ? "" : reg.icon);
g.append("image")
.attr("class", "regimentImage")
.attr("x", x1 - h)
.attr("y", y1)
.attr("height", h)
.attr("width", h)
- .text(reg.image);
+ .attr("href", reg.icon.startsWith("http") ? reg.icon : "");
};
// move one regiment to another
@@ -137,12 +137,12 @@ const moveRegiment = function (reg, x, y) {
.transition(move)
.attr("x", x1(x) - size)
.attr("y", y)
- .attr("height", '6')
- .attr("width", '6');
+ .attr("height", "6")
+ .attr("width", "6");
el.select(".regimentImage")
.transition(move)
.attr("x", x1(x) - h)
.attr("y", y1(y))
- .attr("height", '6')
- .attr("width", '6')
+ .attr("height", "6")
+ .attr("width", "6");
};
diff --git a/modules/ui/editors.js b/modules/ui/editors.js
index ab143395..c11166c6 100644
--- a/modules/ui/editors.js
+++ b/modules/ui/editors.js
@@ -960,30 +960,6 @@ function highlightElement(element, zoom) {
}
}
-$('#addImage').click(function(){
- let icon = $('#imageInput').val();
- addImage(icon);
-});
-
-function addImage(icon) {
- let row = "";
- let lastRowId = $('#iconTable').children('tbody').children('tr').length;
- let lastRow = $('#iconTable').children('tbody').children('tr:last-child');
- let countLastRow = lastRow.children().length;
- if(icon.includes("http")) {
- icon = '

';
- const table = byId("iconTable");
- if(countLastRow > 16) {
- row = table.insertRow(lastRowId);
- } else {
- row = table.rows[lastRowId-1];
- }
-
- const cell = row.insertCell(lastRow % 17);
- cell.innerHTML = icon;
- }
-};
-
function selectIcon(initial, callback) {
if (!callback) return;
$("#iconSelector").dialog();
@@ -991,11 +967,8 @@ function selectIcon(initial, callback) {
const table = byId("iconTable");
const input = byId("iconInput");
input.value = initial;
- if(window.iconReload) {
- table.innerHTML = null;
- window.iconReload = false;
- }
- if (!table.innerHTML.includes('⚔️')) {
+
+ if (!table.innerHTML) {
const icons = [
"⚔️",
"🏹",
@@ -1183,48 +1156,75 @@ function selectIcon(initial, callback) {
"🍻",
"🍺",
"🍲",
- "🍷",
+ "🍷"
];
- if(window.uniquePictures) { //if a load has been load, we might get already installed pictures list
- window.uniquePictures.forEach((element) => icons.push(element));
- }
+
let row = "";
for (let i = 0; i < icons.length; i++) {
- if(icons[i].includes("http")) {
- icons[i] = '

';
- }
if (i % 17 === 0) row = table.insertRow((i / 17) | 0);
const cell = row.insertCell(i % 17);
cell.innerHTML = icons[i];
}
+
+ // find external images used as icons and show them
+ const externalResources = new Set();
+ const isExternal = url => url.startsWith("http");
+
+ options.military.forEach(unit => {
+ if (isExternal(unit.icon)) externalResources.add(unit.icon);
+ });
+
+ pack.states.forEach(state => {
+ state?.military?.forEach(regiment => {
+ if (isExternal(regiment.icon)) externalResources.add(regiment.icon);
+ });
+ });
+
+ externalResources.forEach(addExternalImage);
}
-
+ input.oninput = () => callback(input.value);
+
table.onclick = e => {
- if (e.target.tagName === "TD" && e.target.textContent) {
+ if (e.target.tagName === "TD") {
input.value = e.target.textContent;
+ callback(input.value);
}
- if (e.target.tagName === "IMG" && e.target.src) {
- input.value = e.target.src;
- }
- input.oninput = e => callback(input.value);
- callback(input.value || "⠀");
};
+
table.onmouseover = e => {
- if (e.target.tagName === "TD" && e.target.textContent) {
- tip(`Click to select ${e.target.textContent} icon`);
- }
- if (e.target.tagName === "IMG" && e.target.src) {
- tip(`Click to select ${e.target.src} icon`);
- }
+ if (e.target.tagName === "TD") tip(`Click to select ${e.target.textContent} icon`);
};
+ function addExternalImage(url) {
+ const addedIcons = byId("addedIcons");
+ const image = document.createElement("div");
+ image.style.cssText = `width: 2.2em; height: 2.2em; background-size: cover; background-image: url(${url})`;
+ addedIcons.appendChild(image);
+ image.onclick = () => callback(ulr);
+ }
+
+ byId("addImage").onclick = function () {
+ const input = this.previousElementSibling;
+ const ulr = input.value;
+ if (!ulr) return tip("Enter image URL to add", false, "error", 4000);
+ if (!ulr.match(/^(http|https):\/\//)) return tip("Enter valid URL", false, "error", 4000);
+ addExternalImage(ulr);
+ callback(ulr);
+ input.value = "";
+ };
+
+ byId("addedIcons")
+ .querySelectorAll("div")
+ .forEach(div => {
+ div.onclick = () => callback(div.style.backgroundImage.slice(5, -2));
+ });
+
$("#iconSelector").dialog({
width: fitContent(),
title: "Select Icon",
buttons: {
Apply: function () {
- callback(input.value || "⠀");
$(this).dialog("close");
},
Close: function () {
diff --git a/modules/ui/markers-editor.js b/modules/ui/markers-editor.js
index 26f035fa..2147e09b 100644
--- a/modules/ui/markers-editor.js
+++ b/modules/ui/markers-editor.js
@@ -8,25 +8,24 @@ function editMarker(markerI) {
elSelected = d3.select(element).raise().call(d3.drag().on("start", dragMarker)).classed("draggable", true);
- if (document.getElementById("notesEditor").offsetParent) editNotes(element.id, element.id);
+ if (byId("notesEditor").offsetParent) editNotes(element.id, element.id);
// dom elements
- const markerType = document.getElementById("markerType");
- const markerIcon = document.getElementById("markerIcon");
- const markerIconSelect = document.getElementById("markerIconSelect");
- const markerIconSize = document.getElementById("markerIconSize");
- const markerIconShiftX = document.getElementById("markerIconShiftX");
- const markerIconShiftY = document.getElementById("markerIconShiftY");
- const markerSize = document.getElementById("markerSize");
- const markerPin = document.getElementById("markerPin");
- const markerFill = document.getElementById("markerFill");
- const markerStroke = document.getElementById("markerStroke");
+ const markerType = byId("markerType");
+ const markerIconSelect = byId("markerIconSelect");
+ const markerIconSize = byId("markerIconSize");
+ const markerIconShiftX = byId("markerIconShiftX");
+ const markerIconShiftY = byId("markerIconShiftY");
+ const markerSize = byId("markerSize");
+ const markerPin = byId("markerPin");
+ const markerFill = byId("markerFill");
+ const markerStroke = byId("markerStroke");
- const markerNotes = document.getElementById("markerNotes");
- const markerLock = document.getElementById("markerLock");
- const addMarker = document.getElementById("addMarker");
- const markerAdd = document.getElementById("markerAdd");
- const markerRemove = document.getElementById("markerRemove");
+ const markerNotes = byId("markerNotes");
+ const markerLock = byId("markerLock");
+ const addMarker = byId("addMarker");
+ const markerAdd = byId("markerAdd");
+ const markerRemove = byId("markerRemove");
updateInputs();
@@ -39,8 +38,7 @@ function editMarker(markerI) {
const listeners = [
listen(markerType, "change", changeMarkerType),
- listen(markerIcon, "input", changeMarkerIcon),
- listen(markerIconSelect, "click", selectMarkerIcon),
+ listen(markerIconSelect, "click", changeMarkerIcon),
listen(markerIconSize, "input", changeIconSize),
listen(markerIconShiftX, "input", changeIconShiftX),
listen(markerIconShiftY, "input", changeIconShiftY),
@@ -61,7 +59,7 @@ function editMarker(markerI) {
return [element, marker];
}
- const element = document.getElementById(`marker${markerI}`);
+ const element = byId(`marker${markerI}`);
const marker = pack.markers.find(({i}) => i === markerI);
return [element, marker];
}
@@ -97,19 +95,20 @@ function editMarker(markerI) {
}
function updateInputs() {
- const {icon, type = "", size = 30, dx = 50, dy = 50, px = 12, stroke = "#000000", fill = "#ffffff", pin = "bubble", lock} = marker;
+ byId("markerIcon").innerHTML = marker.icon.startsWith("http")
+ ? `

`
+ : marker.icon;
- markerType.value = type;
- markerIcon.value = icon;
- markerIconSize.value = px;
- markerIconShiftX.value = dx;
- markerIconShiftY.value = dy;
- markerSize.value = size;
- markerPin.value = pin;
- markerFill.value = fill;
- markerStroke.value = stroke;
+ markerType.value = marker.type || "";
+ markerIconSize.value = marker.px || 12;
+ markerIconShiftX.value = marker.dx || 50;
+ markerIconShiftY.value = marker.dy || 50;
+ markerSize.value = marker.size || 30;
+ markerPin.value = marker.pin || "bubble";
+ markerFill.value = marker.fill || "#ffffff";
+ markerStroke.value = marker.stroke || "#000000";
- markerLock.className = lock ? "icon-lock" : "icon-lock-open";
+ markerLock.className = marker.lock ? "icon-lock" : "icon-lock-open";
}
function changeMarkerType() {
@@ -117,18 +116,12 @@ function editMarker(markerI) {
}
function changeMarkerIcon() {
- const icon = this.value;
- getSameTypeMarkers().forEach(marker => {
- marker.icon = icon;
- redrawIcon(marker);
- });
- }
+ selectIcon(marker.icon, value => {
+ const isExternal = value.startsWith("http");
+ byId("markerIcon").innerHTML = isExternal ? `

` : value;
- function selectMarkerIcon() {
- selectIcon(marker.icon, icon => {
- markerIcon.value = icon;
getSameTypeMarkers().forEach(marker => {
- marker.icon = icon;
+ marker.icon = value;
redrawIcon(marker);
});
});
@@ -165,7 +158,7 @@ function editMarker(markerI) {
getSameTypeMarkers().forEach(marker => {
marker.size = size;
const {i, x, y, hidden} = marker;
- const el = !hidden && document.getElementById(`marker${i}`);
+ const el = !hidden && byId(`marker${i}`);
if (!el) return;
const zoomedSize = rescale ? Math.max(rn(size / 5 + 24 / scale, 2), 1) : size;
@@ -201,12 +194,23 @@ function editMarker(markerI) {
}
function redrawIcon({i, hidden, icon, dx = 50, dy = 50, px = 12}) {
- const iconElement = !hidden && document.querySelector(`#marker${i} > text`);
- if (iconElement) {
- iconElement.innerHTML = icon;
- iconElement.setAttribute("x", dx + "%");
- iconElement.setAttribute("y", dy + "%");
- iconElement.setAttribute("font-size", px + "px");
+ const isExternal = icon.startsWith("http");
+
+ const iconText = !hidden && document.querySelector(`#marker${i} > text`);
+ if (iconText) {
+ iconText.innerHTML = isExternal ? "" : icon;
+ iconText.setAttribute("x", dx + "%");
+ iconText.setAttribute("y", dy + "%");
+ iconText.setAttribute("font-size", px + "px");
+ }
+
+ const iconImage = !hidden && document.querySelector(`#marker${i} > image`);
+ if (iconImage) {
+ iconImage.setAttribute("x", dx / 2 + "%");
+ iconImage.setAttribute("y", dy / 2 + "%");
+ iconImage.setAttribute("width", px + "px");
+ iconImage.setAttribute("height", px + "px");
+ iconImage.setAttribute("href", isExternal ? icon : "");
}
}
@@ -241,10 +245,10 @@ function editMarker(markerI) {
}
function deleteMarker() {
- Markers.deleteMarker(marker.i)
+ Markers.deleteMarker(marker.i);
element.remove();
$("#markerEditor").dialog("close");
- if (document.getElementById("markersOverviewRefresh").offsetParent) markersOverviewRefresh.click();
+ if (byId("markersOverviewRefresh").offsetParent) markersOverviewRefresh.click();
}
function closeMarkerEditor() {
diff --git a/modules/ui/markers-overview.js b/modules/ui/markers-overview.js
index af0cc11f..4980dd9b 100644
--- a/modules/ui/markers-overview.js
+++ b/modules/ui/markers-overview.js
@@ -69,18 +69,24 @@ function overviewMarkers() {
function addLines() {
const lines = pack.markers
.map(({i, type, icon, pinned, lock}) => {
- return `
-
${icon} ${type}
-
-
-
-
-
-
`;
+ return /* html */ `
+
+ ${
+ icon.startsWith("http")
+ ? `

`
+ : `
${icon}`
+ }
+
${type}
+
+
+
+
+
+
`;
})
.join("");
diff --git a/modules/ui/military-overview.js b/modules/ui/military-overview.js
index 2382cb27..2533f1c5 100644
--- a/modules/ui/military-overview.js
+++ b/modules/ui/military-overview.js
@@ -284,7 +284,14 @@ function overviewMilitary() {
if (el.tagName !== "BUTTON") return;
const type = el.dataset.type;
- if (type === "icon") return selectIcon(el.textContent, v => (el.textContent = v));
+ if (type === "icon") {
+ return selectIcon(el.textContent, function (value) {
+ el.innerHTML = value.startsWith("http")
+ ? `

`
+ : value;
+ });
+ }
+
if (type === "biomes") {
const {i, name, color} = biomesData;
const biomesArray = Array(i.length).fill(null);
@@ -329,9 +336,15 @@ function overviewMilitary() {
${getLimitText(unit[attr])}
`;
- row.innerHTML = /* html */ `
|
+ row.innerHTML = /* html */ `
+
+ |
|
${getLimitButton("biomes")} |
${getLimitButton("states")} |
@@ -424,7 +437,11 @@ function overviewMilitary() {
const [icon, name, biomes, states, cultures, religions, rural, urban, crew, power, type, separate] =
elements.map(el => {
const {type, value} = el.dataset || {};
- if (type === "icon") return el.textContent || "⠀";
+ if (type === "icon") {
+ const value = el.innerHTML.trim();
+ const isImage = value.startsWith("
![]()
parseInt(v)) : null;
if (el.type === "number") return +el.value || 0;
if (el.type === "checkbox") return +el.checked || 0;
diff --git a/modules/ui/regiment-editor.js b/modules/ui/regiment-editor.js
index fc286eca..6a677923 100644
--- a/modules/ui/regiment-editor.js
+++ b/modules/ui/regiment-editor.js
@@ -24,18 +24,17 @@ function editRegiment(selector) {
modules.editRegiment = true;
// add listeners
- document.getElementById("regimentNameRestore").addEventListener("click", restoreName);
- document.getElementById("regimentType").addEventListener("click", changeType);
- document.getElementById("regimentName").addEventListener("change", changeName);
- document.getElementById("regimentEmblem").addEventListener("input", changeEmblem);
- document.getElementById("regimentEmblemSelect").addEventListener("click", selectEmblem);
- document.getElementById("regimentAttack").addEventListener("click", toggleAttack);
- document.getElementById("regimentRegenerateLegend").addEventListener("click", regenerateLegend);
- document.getElementById("regimentLegend").addEventListener("click", editLegend);
- document.getElementById("regimentSplit").addEventListener("click", splitRegiment);
- document.getElementById("regimentAdd").addEventListener("click", toggleAdd);
- document.getElementById("regimentAttach").addEventListener("click", toggleAttach);
- document.getElementById("regimentRemove").addEventListener("click", removeRegiment);
+ byId("regimentNameRestore").addEventListener("click", restoreName);
+ byId("regimentType").addEventListener("click", changeType);
+ byId("regimentName").addEventListener("change", changeName);
+ byId("regimentEmblemChange").addEventListener("click", changeEmblem);
+ byId("regimentAttack").addEventListener("click", toggleAttack);
+ byId("regimentRegenerateLegend").addEventListener("click", regenerateLegend);
+ byId("regimentLegend").addEventListener("click", editLegend);
+ byId("regimentSplit").addEventListener("click", splitRegiment);
+ byId("regimentAdd").addEventListener("click", toggleAdd);
+ byId("regimentAttach").addEventListener("click", toggleAttach);
+ byId("regimentRemove").addEventListener("click", removeRegiment);
// get regiment data element
function getRegiment() {
@@ -43,15 +42,13 @@ function editRegiment(selector) {
}
function updateRegimentData(regiment) {
- document.getElementById("regimentType").className = regiment.n ? "icon-anchor" : "icon-users";
- document.getElementById("regimentName").value = regiment.name;
- if(regiment.image) {
- document.getElementById("regimentEmblem").value = regiment.image;
- } else {
- document.getElementById("regimentEmblem").value = regiment.icon;
- }
- const composition = document.getElementById("regimentComposition");
+ byId("regimentType").className = regiment.n ? "icon-anchor" : "icon-users";
+ byId("regimentName").value = regiment.name;
+ byId("regimentEmblem").innerHTML = regiment.icon.startsWith("http")
+ ? `

`
+ : regiment.icon;
+ const composition = byId("regimentComposition");
composition.innerHTML = options.military
.map(u => {
return `
@@ -130,7 +127,7 @@ function editRegiment(selector) {
function changeType() {
const reg = getRegiment();
reg.n = +!reg.n;
- document.getElementById("regimentType").className = reg.n ? "icon-anchor" : "icon-users";
+ byId("regimentType").className = reg.n ? "icon-anchor" : "icon-users";
const size = +armies.attr("box-size");
const baseRect = elSelected.querySelectorAll("rect")[0];
@@ -153,25 +150,19 @@ function editRegiment(selector) {
const reg = getRegiment(),
regs = pack.states[elSelected.dataset.state].military;
const name = Military.getName(reg, regs);
- elSelected.dataset.name = reg.name = document.getElementById("regimentName").value = name;
- }
-
- function selectEmblem() {
- selectIcon(regimentEmblem.value, v => {
- regimentEmblem.value = v;
- changeEmblem();
- });
+ elSelected.dataset.name = reg.name = byId("regimentName").value = name;
}
function changeEmblem() {
- const emblem = document.getElementById("regimentEmblem").value;
- if(emblem.includes("http")) {
- elSelected.querySelector(".regimentImage").setAttribute("href", emblem);
- getRegiment().icon = elSelected.querySelector(".regimentIcon").innerHTML = "";
- } else {
- getRegiment().icon = elSelected.querySelector(".regimentIcon").innerHTML = emblem;
- elSelected.querySelector(".regimentImage").setAttribute("href", "");
- }
+ const regiment = getRegiment();
+
+ selectIcon(regiment.icon, value => {
+ regiment.icon = value;
+ const isExternal = value.startsWith("http");
+ byId("regimentEmblem").innerHTML = isExternal ? `

` : value;
+ elSelected.querySelector(".regimentIcon").innerHTML = isExternal ? "" : value;
+ elSelected.querySelector(".regimentImage").setAttribute("href", isExternal ? value : "");
+ });
}
function changeUnit() {
@@ -224,7 +215,7 @@ function editRegiment(selector) {
bx: reg.bx,
by: reg.by,
state,
- icon: reg.icon,
+ icon: reg.icon
};
newReg.name = Military.getName(newReg, military);
military.push(newReg);
@@ -235,8 +226,8 @@ function editRegiment(selector) {
}
function toggleAdd() {
- document.getElementById("regimentAdd").classList.toggle("pressed");
- if (document.getElementById("regimentAdd").classList.contains("pressed")) {
+ byId("regimentAdd").classList.toggle("pressed");
+ if (byId("regimentAdd").classList.contains("pressed")) {
viewbox.style("cursor", "crosshair").on("click", addRegimentOnClick);
tip("Click on map to create new regiment or fleet", true);
} else {
@@ -263,8 +254,8 @@ function editRegiment(selector) {
}
function toggleAttack() {
- document.getElementById("regimentAttack").classList.toggle("pressed");
- if (document.getElementById("regimentAttack").classList.contains("pressed")) {
+ byId("regimentAttack").classList.toggle("pressed");
+ if (byId("regimentAttack").classList.contains("pressed")) {
viewbox.style("cursor", "crosshair").on("click", attackRegimentOnClick);
tip("Click on another regiment to initiate battle", true);
armies.selectAll(":scope > g").classed("draggable", false);
@@ -335,8 +326,8 @@ function editRegiment(selector) {
}
function toggleAttach() {
- document.getElementById("regimentAttach").classList.toggle("pressed");
- if (document.getElementById("regimentAttach").classList.contains("pressed")) {
+ byId("regimentAttach").classList.toggle("pressed");
+ if (byId("regimentAttach").classList.contains("pressed")) {
viewbox.style("cursor", "crosshair").on("click", attachRegimentOnClick);
tip("Click on another regiment to unite both regiments. The current regiment will be removed", true);
armies.selectAll(":scope > g").classed("draggable", false);
@@ -462,7 +453,7 @@ function editRegiment(selector) {
icon.setAttribute("y", y);
image.setAttribute("x", x1 - h);
image.setAttribute("y", y1);
- if (self) {;
+ if (self) {
baseLine.attr("x2", x).attr("y2", y);
rotationControl
.attr("cx", x1 + w)
@@ -493,9 +484,9 @@ function editRegiment(selector) {
viewbox.selectAll("g#regimentBase").remove();
armies.selectAll(":scope > g").classed("draggable", false);
armies.selectAll("g>g").call(d3.drag().on("drag", null));
- document.getElementById("regimentAdd").classList.remove("pressed");
- document.getElementById("regimentAttack").classList.remove("pressed");
- document.getElementById("regimentAttach").classList.remove("pressed");
+ byId("regimentAdd").classList.remove("pressed");
+ byId("regimentAttack").classList.remove("pressed");
+ byId("regimentAttach").classList.remove("pressed");
restoreDefaultEvents();
elSelected = null;
}
diff --git a/modules/ui/regiments-overview.js b/modules/ui/regiments-overview.js
index fee7c480..848458b9 100644
--- a/modules/ui/regiments-overview.js
+++ b/modules/ui/regiments-overview.js
@@ -67,14 +67,24 @@ function overviewRegiments(state) {
)
.join(" ");
- lines += /* html */ `
+ lines += /* html */ `
-
${r.icon}
+ ${
+ r.icon.startsWith("http")
+ ? `

`
+ : `
${r.icon}`
+ }
${lineData}
-
${r.a}
-
+
${
+ r.a
+ }
+
`;
regiments.push(r);
diff --git a/modules/ui/tools.js b/modules/ui/tools.js
index 5c67ca0f..359842ad 100644
--- a/modules/ui/tools.js
+++ b/modules/ui/tools.js
@@ -860,33 +860,47 @@ function configMarkersGeneration() {
drawConfigTable();
function drawConfigTable() {
- const {markers} = pack;
const config = Markers.getConfig();
- const headers = `
+
+ const headers = /* html */ `
| Type |
Icon |
Multiplier |
Number |
`;
- const lines = config.map(({type, icon, multiplier}, index) => {
- const inputId = `markerIconInput${index}`;
- return `
- |
+
+ const lines = config.map(({type, icon, multiplier}) => {
+ const isExternal = icon.startsWith("http");
+
+ return /* html */ `
+ |
-
-
+
+ ${isExternal ? "" : icon}
+
|
- |
- ${markers.filter(marker => marker.type === type).length} |
+ |
+ ${pack.markers.filter(marker => marker.type === type).length} |
`;
});
+
const table = `${headers}${lines.join("")}
`;
alertMessage.innerHTML = table;
- alertMessage.querySelectorAll("i").forEach(selectIconButton => {
+ alertMessage.querySelectorAll("button.changeIcon").forEach(selectIconButton => {
selectIconButton.addEventListener("click", function () {
- const input = this.previousElementSibling;
- selectIcon(input.value, icon => (input.value = icon));
+ const image = this.parentElement.querySelector(".image");
+ const emoji = this.parentElement.querySelector(".emoji");
+ const icon = image.getAttribute("src") || emoji.textContent;
+
+ selectIcon(icon, value => {
+ const isExternal = value.startsWith("http");
+ image.setAttribute("src", isExternal ? value : "");
+ image.hidden = !isExternal;
+ emoji.textContent = isExternal ? "" : value;
+ });
});
});
}
@@ -894,12 +908,14 @@ function configMarkersGeneration() {
const applyChanges = () => {
const rows = alertMessage.querySelectorAll("tbody > tr");
const rowsData = Array.from(rows).map(row => {
- const inputs = row.querySelectorAll("input");
- return {
- type: inputs[0].value,
- icon: inputs[1].value,
- multiplier: parseFloat(inputs[2].value)
- };
+ const type = row.querySelector(".type").value;
+
+ const image = row.querySelector(".image");
+ const emoji = row.querySelector(".emoji");
+ const icon = image.getAttribute("src") || emoji.textContent;
+
+ const multiplier = parseFloat(row.querySelector(".multiplier").value);
+ return {type, icon, multiplier};
});
const config = Markers.getConfig();
diff --git a/versioning.js b/versioning.js
index e12cea06..de886bf6 100644
--- a/versioning.js
+++ b/versioning.js
@@ -13,7 +13,7 @@
* Example: 1.102.2 -> Major version 1, Minor version 102, Patch version 2
*/
-const VERSION = "1.106.7";
+const VERSION = "1.107.0";
if (parseMapVersion(VERSION) !== VERSION) alert("versioning.js: Invalid format or parsing function");
{
From 33a02aeea04976a667eaa31e7e4872b635858865 Mon Sep 17 00:00:00 2001
From: Azgaar
Date: Sat, 8 Feb 2025 14:06:33 +0100
Subject: [PATCH 03/16] chore: cleanup
---
picList.txt | 3 ---
1 file changed, 3 deletions(-)
delete mode 100644 picList.txt
diff --git a/picList.txt b/picList.txt
deleted file mode 100644
index d565f0bb..00000000
--- a/picList.txt
+++ /dev/null
@@ -1,3 +0,0 @@
-https://i.ibb.co/C2QDP8G/artillery.png
-https://i.ibb.co/DVHWLfB/soldier.png
-https://i.ibb.co/yQ713ck/tank.png
\ No newline at end of file
From 0be14790d2dde43f5719f0443213ff79c2ea9c28 Mon Sep 17 00:00:00 2001
From: Azgaar
Date: Sat, 8 Feb 2025 14:15:59 +0100
Subject: [PATCH 04/16] feat: rerender affected layers on auto-update
---
modules/dynamic/auto-update.js | 6 ++++++
modules/io/load.js | 2 +-
versioning.js | 1 +
3 files changed, 8 insertions(+), 1 deletion(-)
diff --git a/modules/dynamic/auto-update.js b/modules/dynamic/auto-update.js
index 10a9a375..afa91fca 100644
--- a/modules/dynamic/auto-update.js
+++ b/modules/dynamic/auto-update.js
@@ -977,4 +977,10 @@ export function resolveVersionConflicts(mapVersion) {
BurgsAndStates.getPoles();
Provinces.getPoles();
}
+
+ if (isOlderThan("1.107.0")) {
+ // v1.107.0 allowed custom images for markers and regiments
+ if (layerIsOn("toggleMarkers")) drawMarkers();
+ if (layerIsOn("toggleMilitary")) drawMilitary();
+ }
}
diff --git a/modules/io/load.js b/modules/io/load.js
index 8e05a798..0ec0b8b4 100644
--- a/modules/io/load.js
+++ b/modules/io/load.js
@@ -471,7 +471,7 @@ async function parseLoadedData(data, mapVersion) {
{
// dynamically import and run auto-update script
- const {resolveVersionConflicts} = await import("../dynamic/auto-update.js?v=1.105.24");
+ const {resolveVersionConflicts} = await import("../dynamic/auto-update.js?v=1.107.0");
resolveVersionConflicts(mapVersion);
}
diff --git a/versioning.js b/versioning.js
index de886bf6..5dcbb6a6 100644
--- a/versioning.js
+++ b/versioning.js
@@ -37,6 +37,7 @@ if (parseMapVersion(VERSION) !== VERSION) alert("versioning.js: Invalid format o
Latest changes:
+ - Ability to set custom image as Marker or Regiment icon
- Submap and Transform tools rework
- Azgaar Bot to answer questions and provide help
- Labels: ability to set letter spacing
From 22636b1b620732799988ec2dcb8ace724c1807cb Mon Sep 17 00:00:00 2001
From: Azgaar
Date: Sat, 8 Feb 2025 15:12:57 +0100
Subject: [PATCH 05/16] fix: data integrity checks - better Stripping issue
detection, v1.107.1
---
index.html | 2 +-
modules/io/load.js | 14 +++++++++-----
modules/renderers/draw-features.js | 5 +++++
utils/commonUtils.js | 5 +++++
versioning.js | 2 +-
5 files changed, 21 insertions(+), 7 deletions(-)
diff --git a/index.html b/index.html
index 9d8c9538..52ab5795 100644
--- a/index.html
+++ b/index.html
@@ -8149,7 +8149,7 @@
-
+
diff --git a/modules/io/load.js b/modules/io/load.js
index 8e05a798..13d2ec9e 100644
--- a/modules/io/load.js
+++ b/modules/io/load.js
@@ -489,12 +489,16 @@ async function parseLoadedData(data, mapVersion) {
if (textureHref) updateTextureSelectValue(textureHref);
}
+ // data integrity checks
{
- const cells = pack.cells;
+ const {cells, vertices} = pack;
- if (pack.cells.i.length !== pack.cells.state.length) {
- const message = "[Data integrity] Striping issue detected. To fix edit the heightmap in ERASE mode";
- ERROR && console.error(message);
+ const cellsMismatch = cells.i.length !== cells.state.length;
+ const featureVerticesMismatch = pack.features.some(f => f?.vertices?.some(vertex => !vertices.p[vertex]));
+
+ if (cellsMismatch || featureVerticesMismatch) {
+ const message = "[Data integrity] Striping issue detected. To fix try to edit the heightmap in ERASE mode";
+ throw new Error(message);
}
const invalidStates = [...new Set(cells.state)].filter(s => !pack.states[s] || pack.states[s].removed);
@@ -745,7 +749,7 @@ async function parseLoadedData(data, mapVersion) {
$("#alert").dialog({
resizable: false,
title: "Loading error",
- maxWidth: "50em",
+ maxWidth: "40em",
buttons: {
"Clear cache": () => cleanupData(),
"Select file": function () {
diff --git a/modules/renderers/draw-features.js b/modules/renderers/draw-features.js
index ac2a395e..74e16c87 100644
--- a/modules/renderers/draw-features.js
+++ b/modules/renderers/draw-features.js
@@ -51,6 +51,11 @@ function drawFeatures() {
function getFeaturePath(feature) {
const points = feature.vertices.map(vertex => pack.vertices.p[vertex]);
+ if (points.some(point => point === undefined)) {
+ ERROR && console.error("Undefined point in getFeaturePath");
+ return "";
+ }
+
const simplifiedPoints = simplify(points, 0.3);
const clippedPoints = clipPoly(simplifiedPoints, 1);
diff --git a/utils/commonUtils.js b/utils/commonUtils.js
index dd4c3d45..2be3e36d 100644
--- a/utils/commonUtils.js
+++ b/utils/commonUtils.js
@@ -4,6 +4,11 @@
// clip polygon by graph bbox
function clipPoly(points, secure = 0) {
if (points.length < 2) return points;
+ if (points.some(point => point === undefined)) {
+ ERROR && console.error("Undefined point in clipPoly", points);
+ return points;
+ }
+
return polygonclip(points, [0, 0, graphWidth, graphHeight], secure);
}
diff --git a/versioning.js b/versioning.js
index e12cea06..461ab1c7 100644
--- a/versioning.js
+++ b/versioning.js
@@ -13,7 +13,7 @@
* Example: 1.102.2 -> Major version 1, Minor version 102, Patch version 2
*/
-const VERSION = "1.106.7";
+const VERSION = "1.107.1";
if (parseMapVersion(VERSION) !== VERSION) alert("versioning.js: Invalid format or parsing function");
{
From 51c47a18d2b9f7554d6a93502e6e94b114803f58 Mon Sep 17 00:00:00 2001
From: Azgaar
Date: Mon, 10 Feb 2025 01:39:21 +0100
Subject: [PATCH 06/16] fix: external icons - battle screen
---
index.html | 2 +-
modules/ui/battle-screen.js | 12 +++++++++---
versioning.js | 2 +-
3 files changed, 11 insertions(+), 5 deletions(-)
diff --git a/index.html b/index.html
index 3f46c748..90f7299c 100644
--- a/index.html
+++ b/index.html
@@ -8151,7 +8151,7 @@
-
+
diff --git a/modules/ui/battle-screen.js b/modules/ui/battle-screen.js
index f2e47452..b12ceea4 100644
--- a/modules/ui/battle-screen.js
+++ b/modules/ui/battle-screen.js
@@ -131,7 +131,9 @@ class Battle {
for (const u of options.military) {
const label = capitalize(u.name.replace(/_/g, " "));
- headers += `${u.icon} | `;
+ const isExternal = u.icon.startsWith("http");
+ const iconHTML = isExternal ? `
` : u.icon;
+ headers += `${iconHTML} | `;
}
headers += "Total |
";
@@ -145,9 +147,13 @@ class Battle {
const state = pack.states[regiment.state];
const distance = (Math.hypot(this.y - regiment.by, this.x - regiment.bx) * distanceScale) | 0; // distance between regiment and its base
const color = state.color[0] === "#" ? state.color : "#999";
+
+ const isExternal = regiment.icon.startsWith("http");
+ const iconHtml = isExternal
+ ? `
`
+ : `
${regiment.icon}`;
const icon = `
`;
+
${iconHtml}`;
const body = `
`;
let initial = `| ${icon} |
Date: Mon, 10 Feb 2025 12:41:14 +0100
Subject: [PATCH 07/16] fix: notes editor size to be relative to canvas size
---
index.html | 2 +-
modules/ui/notes-editor.js | 4 ++--
versioning.js | 2 +-
3 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/index.html b/index.html
index 90f7299c..b9f8274d 100644
--- a/index.html
+++ b/index.html
@@ -8140,7 +8140,7 @@
-
+
diff --git a/modules/ui/notes-editor.js b/modules/ui/notes-editor.js
index 2b5d1c79..10f87e16 100644
--- a/modules/ui/notes-editor.js
+++ b/modules/ui/notes-editor.js
@@ -40,8 +40,8 @@ function editNotes(id, name) {
$("#notesEditor").dialog({
title: "Notes Editor",
- width: window.innerWidth * 0.8,
- height: window.innerHeight * 0.75,
+ width: svgWidth * 0.8,
+ height: svgHeight * 0.75,
position: {my: "center", at: "center", of: "svg"},
close: removeEditor
});
diff --git a/versioning.js b/versioning.js
index 1453308f..fff0a599 100644
--- a/versioning.js
+++ b/versioning.js
@@ -13,7 +13,7 @@
* Example: 1.102.2 -> Major version 1, Minor version 102, Patch version 2
*/
-const VERSION = "1.107.2";
+const VERSION = "1.107.3";
if (parseMapVersion(VERSION) !== VERSION) alert("versioning.js: Invalid format or parsing function");
{
From e39ca793f2346dc24afcd7d30d76b1bb3f18375a Mon Sep 17 00:00:00 2001
From: Azgaar
Date: Thu, 13 Feb 2025 02:54:37 +0100
Subject: [PATCH 08/16] feat: add growthRate to safe file
---
index.html | 4 ++--
modules/io/load.js | 1 +
modules/io/save.js | 3 ++-
versioning.js | 2 +-
4 files changed, 6 insertions(+), 4 deletions(-)
diff --git a/index.html b/index.html
index b9f8274d..b3c429da 100644
--- a/index.html
+++ b/index.html
@@ -8161,8 +8161,8 @@
-
-
+
+
diff --git a/modules/io/load.js b/modules/io/load.js
index 688a6936..6083dc1a 100644
--- a/modules/io/load.js
+++ b/modules/io/load.js
@@ -257,6 +257,7 @@ async function parseLoadedData(data, mapVersion) {
if (settings[23]) rescaleLabels.checked = +settings[23];
if (settings[24]) urbanDensity = urbanDensityInput.value = +settings[24];
if (settings[25]) longitudeInput.value = longitudeOutput.value = minmax(settings[25] || 50, 0, 100);
+ if (settings[26]) growthRate.value = settings[26];
}
{
diff --git a/modules/io/save.js b/modules/io/save.js
index 96d1d981..304fef59 100644
--- a/modules/io/save.js
+++ b/modules/io/save.js
@@ -68,7 +68,8 @@ function prepareMapData() {
stylePreset.value,
+rescaleLabels.checked,
urbanDensity,
- longitudeOutput.value
+ longitudeOutput.value,
+ growthRate.value
].join("|");
const coords = JSON.stringify(mapCoordinates);
const biomes = [biomesData.color, biomesData.habitability, biomesData.name].join("|");
diff --git a/versioning.js b/versioning.js
index fff0a599..20591d39 100644
--- a/versioning.js
+++ b/versioning.js
@@ -13,7 +13,7 @@
* Example: 1.102.2 -> Major version 1, Minor version 102, Patch version 2
*/
-const VERSION = "1.107.3";
+const VERSION = "1.107.4";
if (parseMapVersion(VERSION) !== VERSION) alert("versioning.js: Invalid format or parsing function");
{
From 764993b680ab5827ef26e355f9edf8ab34572320 Mon Sep 17 00:00:00 2001
From: Azgaar
Date: Sat, 15 Feb 2025 13:06:14 +0100
Subject: [PATCH 09/16] fix: remove old feature masks, v1.108.0
---
index.html | 8 ++--
modules/dynamic/auto-update.js | 13 +++++-
modules/io/load.js | 2 +-
modules/renderers/draw-features.js | 66 +++++++++++++++---------------
versioning.js | 2 +-
5 files changed, 49 insertions(+), 42 deletions(-)
diff --git a/index.html b/index.html
index b3c429da..bffa0d29 100644
--- a/index.html
+++ b/index.html
@@ -354,9 +354,7 @@
-
-
-
+
@@ -8162,11 +8160,11 @@
-
+
-
+
diff --git a/modules/dynamic/auto-update.js b/modules/dynamic/auto-update.js
index afa91fca..6622e3b5 100644
--- a/modules/dynamic/auto-update.js
+++ b/modules/dynamic/auto-update.js
@@ -963,7 +963,6 @@ export function resolveVersionConflicts(mapVersion) {
defs.select("#land").selectAll("path, use").remove();
defs.select("#water").selectAll("path, use").remove();
viewbox.select("#coastline").selectAll("path, use").remove();
- drawFeatures();
// v1.104.0 introduced bugs with state borders
regions
@@ -983,4 +982,16 @@ export function resolveVersionConflicts(mapVersion) {
if (layerIsOn("toggleMarkers")) drawMarkers();
if (layerIsOn("toggleMilitary")) drawMilitary();
}
+
+ if (isOlderThan("1.108.0")) {
+ // v1.108.0 changed features rendering method
+ pack.features.forEach(f => {
+ // fix lakes with missing group
+ if (f?.type === "lake" && !f.group) f.group = "freshwater";
+ });
+ drawFeatures();
+
+ // some old maps has incorrect "heights" groups
+ viewbox.selectAll("#heights").remove();
+ }
}
diff --git a/modules/io/load.js b/modules/io/load.js
index 6083dc1a..ccfccecb 100644
--- a/modules/io/load.js
+++ b/modules/io/load.js
@@ -472,7 +472,7 @@ async function parseLoadedData(data, mapVersion) {
{
// dynamically import and run auto-update script
- const {resolveVersionConflicts} = await import("../dynamic/auto-update.js?v=1.107.0");
+ const {resolveVersionConflicts} = await import("../dynamic/auto-update.js?v=1.108.0");
resolveVersionConflicts(mapVersion);
}
diff --git a/modules/renderers/draw-features.js b/modules/renderers/draw-features.js
index 74e16c87..afd26afa 100644
--- a/modules/renderers/draw-features.js
+++ b/modules/renderers/draw-features.js
@@ -2,50 +2,48 @@
function drawFeatures() {
TIME && console.time("drawFeatures");
- const featurePaths = defs.select("#featurePaths");
- const landMask = defs.select("#land");
- const waterMask = defs.select("#water");
+
+ const html = {
+ paths: [],
+ landMask: [],
+ waterMask: [''],
+ coastline: {},
+ lakes: {}
+ };
for (const feature of pack.features) {
if (!feature || feature.type === "ocean") continue;
- featurePaths
- .append("path")
- .attr("d", getFeaturePath(feature))
- .attr("id", "feature_" + feature.i)
- .attr("data-f", feature.i);
+ html.paths.push(``);
if (feature.type === "lake") {
- landMask
- .append("use")
- .attr("href", "#feature_" + feature.i)
- .attr("data-f", feature.i)
- .attr("fill", "black");
- lakes
- .select(`#${feature.group}`)
- .append("use")
- .attr("href", "#feature_" + feature.i)
- .attr("data-f", feature.i);
+ html.landMask.push(``);
+
+ const lakeGroup = feature.group || "freshwater";
+ if (!html.lakes[lakeGroup]) html.lakes[lakeGroup] = [];
+ html.lakes[lakeGroup].push(``);
} else {
- landMask
- .append("use")
- .attr("href", "#feature_" + feature.i)
- .attr("data-f", feature.i)
- .attr("fill", "white");
- waterMask
- .append("use")
- .attr("href", "#feature_" + feature.i)
- .attr("data-f", feature.i)
- .attr("fill", "black");
- const coastlineGroup = feature.group === "lake_island" ? "#lake_island" : "#sea_island";
- coastline
- .select(coastlineGroup)
- .append("use")
- .attr("href", "#feature_" + feature.i)
- .attr("data-f", feature.i);
+ html.landMask.push(``);
+ html.waterMask.push(``);
+
+ const coastlineGroup = feature.group === "lake_island" ? "lake_island" : "sea_island";
+ if (!html.coastline[coastlineGroup]) html.coastline[coastlineGroup] = [];
+ html.coastline[coastlineGroup].push(``);
}
}
+ defs.select("#featurePaths").html(html.paths.join(""));
+ defs.select("#land").html(html.landMask.join(""));
+ defs.select("#water").html(html.waterMask.join(""));
+
+ Object.entries(html.coastline).forEach(([group, paths]) => {
+ coastline.select("#" + group).html(paths.join(""));
+ });
+
+ Object.entries(html.lakes).forEach(([group, paths]) => {
+ lakes.select("#" + group).html(paths.join(""));
+ });
+
TIME && console.timeEnd("drawFeatures");
}
diff --git a/versioning.js b/versioning.js
index 20591d39..8de59ae4 100644
--- a/versioning.js
+++ b/versioning.js
@@ -13,7 +13,7 @@
* Example: 1.102.2 -> Major version 1, Minor version 102, Patch version 2
*/
-const VERSION = "1.107.4";
+const VERSION = "1.108.0";
if (parseMapVersion(VERSION) !== VERSION) alert("versioning.js: Invalid format or parsing function");
{
From d98ef5717e3be98f355ba54eda9256e8969d0651 Mon Sep 17 00:00:00 2001
From: Azgaar
Date: Sat, 15 Feb 2025 14:43:51 +0100
Subject: [PATCH 10/16] perf: set text-rendering to optimizeSpeed, v1.108.1
---
index.html | 24 ++++++++++++------------
main.js | 5 ++++-
modules/dynamic/editors/states-editor.js | 1 +
modules/renderers/draw-burg-labels.js | 2 ++
modules/renderers/draw-military.js | 9 ++++++++-
modules/renderers/draw-scalebar.js | 2 ++
modules/renderers/draw-state-labels.js | 1 +
modules/ui/editors.js | 5 ++++-
modules/ui/layers.js | 1 +
modules/ui/provinces-editor.js | 1 +
modules/ui/regiment-editor.js | 1 +
modules/ui/tools.js | 2 ++
versioning.js | 2 +-
13 files changed, 40 insertions(+), 16 deletions(-)
diff --git a/index.html b/index.html
index bffa0d29..460801b8 100644
--- a/index.html
+++ b/index.html
@@ -11,8 +11,8 @@
name="description"
content="Free web app that helps fantasy writers, game masters, and cartographers create and edit fantasy maps"
/>
-
+
-
+
@@ -8108,20 +8108,20 @@
-
+
-
+
-
-
+
+
-
+
@@ -8148,7 +8148,7 @@
-
+
@@ -8168,12 +8168,12 @@
-
+
-
-
-
+
+
+
|