diff --git a/index.css b/index.css
index a5fc9b2b..4b4960f2 100644
--- a/index.css
+++ b/index.css
@@ -19,6 +19,11 @@ font {
pointer-events: none;
}
+form input:invalid {
+ outline: 1px solid #ed4337;
+ outline-offset: 1px;
+}
+
input,
select,
button {
@@ -53,6 +58,7 @@ input:read-only {
input[type="radio"] {
vertical-align: bottom;
cursor: pointer;
+ accent-color: var(--header);
}
textarea {
@@ -1889,7 +1895,8 @@ div.editorLine {
width: 5em;
}
-#militaryOptionsTable button {
+#militaryOptionsTable button,
+#burgGroupsBody button {
width: 100%;
}
@@ -1922,7 +1929,12 @@ ul.share-buttons img {
width: 2em;
}
-input[type="checkbox"] {
+input[type="checkbox"].native {
+ accent-color: var(--header);
+ cursor: pointer;
+}
+
+input[type="checkbox"]:not(.native) {
display: none;
}
diff --git a/index.html b/index.html
index df4904de..8b22cef1 100644
--- a/index.html
+++ b/index.html
@@ -3364,11 +3364,7 @@
-
diff --git a/modules/burgs-generator.js b/modules/burgs-generator.js
index 0b777957..6de59243 100644
--- a/modules/burgs-generator.js
+++ b/modules/burgs-generator.js
@@ -262,26 +262,26 @@ window.Burgs = (() => {
const getDefaultGroups = () => [
{name: "capitals", active: true, features: {capital: true}, preview: "watabou-city-generator"},
- {name: "cities", active: true, percentile: 90, population: [5, Infinity], preview: "watabou-city-generator"},
+ {name: "cities", active: true, percentile: 90, min: 5, preview: "watabou-city-generator"},
{
name: "forts",
active: true,
features: {citadel: true, walls: false, plaza: false, port: false},
- population: [0, 1],
+ max: 1,
preview: null
},
{
name: "monasteries",
active: true,
features: {temple: true, walls: false, plaza: false, port: false},
- population: [0, 0.8],
+ max: 0.8,
preview: null
},
{
name: "caravanserais",
active: true,
features: {port: false, plaza: true},
- population: [0, 0.8],
+ max: 0.8,
biomes: [1, 2, 3],
preview: null
},
@@ -289,14 +289,15 @@ window.Burgs = (() => {
name: "trading_posts",
active: true,
features: {plaza: true},
- population: [0, 0.8],
+ max: 0.8,
biomes: [5, 6, 7, 8, 9, 10, 11, 12],
preview: null
},
{
name: "villages",
active: true,
- population: [0.1, 2],
+ min: 0.1,
+ max: 2,
features: {walls: false},
preview: "watabou-village-generator"
},
@@ -304,7 +305,7 @@ window.Burgs = (() => {
name: "hamlets",
active: true,
features: {walls: false, plaza: false},
- population: [0, 0.1],
+ max: 0.1,
preview: "watabou-village-generator"
},
{name: "towns", active: true, isDefault: true, preview: "watabou-city-generator"}
@@ -314,9 +315,13 @@ window.Burgs = (() => {
for (const group of options.burgs.groups) {
if (!group.active) continue;
- if (group.population) {
- const [min, max] = group.population;
- const isFit = burg.population >= min && burg.population <= max;
+ if (group.min) {
+ const isFit = burg.population >= group.min;
+ if (!isFit) continue;
+ }
+
+ if (group.max) {
+ const isFit = burg.population <= group.max;
if (!isFit) continue;
}
diff --git a/modules/ui/burg-editor.js b/modules/ui/burg-editor.js
index caad2779..342952d2 100644
--- a/modules/ui/burg-editor.js
+++ b/modules/ui/burg-editor.js
@@ -25,7 +25,7 @@ function editBurg(id) {
byId("burgName").on("input", changeName);
byId("burgNameReRandom").on("click", generateNameRandom);
byId("burgGroup").on("change", changeGroup);
- byId("burgGroupEdit").on("change", editBurgGroups);
+ byId("burgGroupConfigure").on("click", editBurgGroups);
byId("burgType").on("change", changeType);
byId("burgCulture").on("change", changeCulture);
byId("burgNameReCulture").on("click", generateNameCulture);
diff --git a/modules/ui/burg-group-editor.js b/modules/ui/burg-group-editor.js
index bcdb5d80..cd97a593 100644
--- a/modules/ui/burg-group-editor.js
+++ b/modules/ui/burg-group-editor.js
@@ -2,42 +2,57 @@
function editBurgGroups() {
if (customization) return;
- if (!layerIsOn("toggleBurgs")) toggleBurgs();
-
addLines();
$("#burgGroupsEditor").dialog({
- title: "Edit Burg groups",
+ title: "Configure Burg groups",
resizable: false,
- position: {my: "left top", at: "left+10 top+140", of: "#map"}
+ position: {my: "center", at: "center", of: "svg"},
+ buttons: {
+ Apply: () => byId("burgGroupsForm").requestSubmit(),
+ Cancel: function () {
+ $(this).dialog("close");
+ }
+ }
});
if (modules.editBurgGroups) return;
modules.editBurgGroups = true;
// add listeners
- byId("burgGroupsEditorAdd").addEventListener("click", addGroup);
- byId("burgGroupsEditorBody").on("click", ev => {
+ byId("burgGroupsForm").on("submit", submitForm);
+ byId("burgGroupsForm").on("change", validateForm);
+ byId("burgGroupsEditorAdd").on("click", addGroup);
+ byId("burgGroupsEditStyle").on("click", () => editStyle("burgIcons"));
+ byId("burgGroupsBody").on("click", ev => {
const group = ev.target.closest(".states")?.dataset.id;
if (ev.target.classList.contains("editStyle")) editStyle("burgs", group);
else if (ev.target.classList.contains("removeGroup")) removeGroup(group);
});
function addLines() {
- byId("burgGroupsEditorBody").innerHTML = "";
+ byId("burgGroupsBody").innerHTML = "";
- const lines = Array.from(burgs.selectAll("g")._groups[0]).map(el => {
- const count = el.children.length;
- return /* html */ `
-
${el.id} (${count})
-
-
-
-
-
`;
+ const lines = options.burgs.groups.map(group => {
+ const count = pack.burgs.filter(burg => !burg.removed && burg.group === group.name).length;
+ // prettier-ignore
+ return /* html */ `
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ ${count} |
+ |
+
`;
});
- byId("burgGroupsEditorBody").innerHTML = lines.join("");
+ byId("burgGroupsBody").innerHTML = lines.join("");
}
const DEFAULT_GROUPS = ["roads", "trails", "seaburgs"];
@@ -81,4 +96,49 @@ function editBurgGroups() {
}
});
}
+
+ function validateForm(event) {
+ const form = event.target.form;
+
+ const names = Array.from(form.name).map(input => input.value);
+ form.name.forEach(nameInput => {
+ const value = nameInput.value;
+ const isUnique = names.filter(n => n === value).length === 1;
+ nameInput.setCustomValidity(isUnique ? "" : "Group name should be unique");
+ nameInput.reportValidity();
+ });
+
+ const active = Array.from(form.active).map(input => input.checked);
+ form.active[0].setCustomValidity(active.includes(true) ? "" : "At least one group should be active");
+ form.active[0].reportValidity();
+ }
+
+ function submitForm(event) {
+ event.preventDefault();
+
+ function parseInput(input) {
+ if (input.name === "name") return sanitizeId(input.value);
+ if (input.type === "radio") return input.checked;
+ if (input.type === "checkbox") return input.checked;
+ if (input.type === "number") {
+ const value = input.valueAsNumber;
+ if (value === 0 || isNaN(value)) return null;
+ return value;
+ }
+ return input.value;
+ }
+
+ const lines = Array.from(byId("burgGroupsBody").children);
+ options.burgs.groups = lines.map(line => {
+ const inputs = line.querySelectorAll("input");
+ const group = Array.from(inputs).reduce((obj, input) => {
+ const value = parseInput(input);
+ if (value !== null) obj[input.name] = value;
+ return obj;
+ }, {});
+ return group;
+ });
+
+ $("#burgGroupsEditor").dialog("close");
+ }
}
diff --git a/modules/ui/military-overview.js b/modules/ui/military-overview.js
index 2382cb27..e300ed86 100644
--- a/modules/ui/military-overview.js
+++ b/modules/ui/military-overview.js
@@ -414,7 +414,7 @@ function overviewMilitary() {
function applyMilitaryOptions() {
const unitLines = Array.from(tableBody.querySelectorAll("tr"));
- const names = unitLines.map(r => r.querySelector("input").value.replace(/[&\/\\#, +()$~%.'":*?<>{}]/g, "_"));
+ const names = unitLines.map(r => sanitizeId(r.querySelector("input").value));
if (new Set(names).size !== names.length) return tip("All units should have unique names", false, "error");
$("#militaryOptions").dialog("close");
diff --git a/utils/stringUtils.js b/utils/stringUtils.js
index 6325d278..e9c4ef7a 100644
--- a/utils/stringUtils.js
+++ b/utils/stringUtils.js
@@ -56,3 +56,18 @@ JSON.isValid = str => {
}
return true;
};
+
+function sanitizeId(string) {
+ if (!string) throw new Error("No string provided");
+
+ let sanitized = string
+ .toLowerCase()
+ .trim()
+ .replace(/[^a-z0-9-_]/g, "") // no invalid characters
+ .replace(/\s+/g, "-"); // replace spaces with hyphens
+
+ // remove leading numbers
+ if (sanitized.match(/^\d/)) sanitized = "_" + sanitized;
+
+ return sanitized;
+}
diff --git a/versioning.js b/versioning.js
index 139d7f0e..a880c42a 100644
--- a/versioning.js
+++ b/versioning.js
@@ -32,7 +32,7 @@ if (parseMapVersion(VERSION) !== VERSION) alert("versioning.js: Invalid format o
const patreon = "https://www.patreon.com/azgaar";
alertMessage.innerHTML = /* html */ `The Fantasy Map Generator is updated up to version
${VERSION}. This version is compatible with
previous versions, loaded save files will be auto-updated.
- ${storedVersion ? "
Click on OK and then reload the page to fetch fresh code." : ""}
+ ${storedVersion ? "
In case of errors reload the page to update the code." : ""}