Overview dialogs search (#1260)

* feat: add search functionality to overview components

* feat: enhance search functionality

* chore: correct typo in pull request template

* chore: update version to 1.110.0 and add peer dependencies in package-lock.json; enhance versioning.js with new features

* Fix null safety and performance in overview dialogs search (#1272)

* Initial plan

* fix: add optional chaining and optimize performance in overview dialogs

Co-authored-by: Azgaar <26469650+Azgaar@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: Azgaar <26469650+Azgaar@users.noreply.github.com>

---------

Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com>
Co-authored-by: Azgaar <26469650+Azgaar@users.noreply.github.com>
This commit is contained in:
Azgaar 2026-01-22 13:06:13 +01:00 committed by GitHub
parent 9e0eb03618
commit f30ffd812e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 149 additions and 63 deletions

View file

@ -4,7 +4,7 @@
# Type of change # Type of change
<!-- Please put X into brackers of required option OR delete options that are not relevant --> <!-- Please put X into brackets of required option OR delete options that are not relevant -->
- [ ] Bug fix - [ ] Bug fix
- [ ] New feature - [ ] New feature

2
package-lock.json generated
View file

@ -1461,6 +1461,7 @@
"resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz",
"integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==",
"license": "ISC", "license": "ISC",
"peer": true,
"engines": { "engines": {
"node": ">=12" "node": ">=12"
} }
@ -1682,6 +1683,7 @@
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"engines": { "engines": {
"node": ">=12" "node": ">=12"
}, },

View file

@ -1,6 +1,6 @@
{ {
"name": "fantasy-map-generator", "name": "fantasy-map-generator",
"version": "1.109.5", "version": "1.110.0",
"description": "Azgaar's _Fantasy Map Generator_ is a free web application that helps fantasy writers, game masters, and cartographers create and edit fantasy maps.", "description": "Azgaar's _Fantasy Map Generator_ is a free web application that helps fantasy writers, game masters, and cartographers create and edit fantasy maps.",
"homepage": "https://github.com/Azgaar/Fantasy-Map-Generator#readme", "homepage": "https://github.com/Azgaar/Fantasy-Map-Generator#readme",
"bugs": { "bugs": {

View file

@ -28,6 +28,7 @@ function overviewBurgs(settings = {stateId: null, cultureId: null}) {
byId("burgsChart").addEventListener("click", showBurgsChart); byId("burgsChart").addEventListener("click", showBurgsChart);
byId("burgsFilterState").addEventListener("change", burgsOverviewAddLines); byId("burgsFilterState").addEventListener("change", burgsOverviewAddLines);
byId("burgsFilterCulture").addEventListener("change", burgsOverviewAddLines); byId("burgsFilterCulture").addEventListener("change", burgsOverviewAddLines);
byId("burgsSearch").addEventListener("input", burgsOverviewAddLines);
byId("regenerateBurgNames").addEventListener("click", regenerateNames); byId("regenerateBurgNames").addEventListener("click", regenerateNames);
byId("addNewBurg").addEventListener("click", enterAddBurgMode); byId("addNewBurg").addEventListener("click", enterAddBurgMode);
byId("burgsExport").addEventListener("click", downloadBurgsData); byId("burgsExport").addEventListener("click", downloadBurgsData);
@ -63,9 +64,30 @@ function overviewBurgs(settings = {stateId: null, cultureId: null}) {
// add line for each burg // add line for each burg
function burgsOverviewAddLines() { function burgsOverviewAddLines() {
const searchText = byId("burgsSearch").value.toLowerCase().trim();
const selectedStateId = +byId("burgsFilterState").value; const selectedStateId = +byId("burgsFilterState").value;
const selectedCultureId = +byId("burgsFilterCulture").value; const selectedCultureId = +byId("burgsFilterCulture").value;
let filtered = pack.burgs.filter(b => b.i && !b.removed); // all valid burgs
const validBurgs = pack.burgs.filter(b => b.i && !b.removed);
let filtered = validBurgs;
if (searchText) {
// filter by search text
filtered = filtered.filter(b => {
const name = b.name.toLowerCase();
const state = (pack.states[b.state]?.name || "").toLowerCase();
const prov = pack.cells.province[b.cell];
const province = prov ? pack.provinces[prov]?.name.toLowerCase() : "";
const culture = (pack.cultures[b.culture]?.name || "").toLowerCase();
return (
name.includes(searchText) ||
state.includes(searchText) ||
province.includes(searchText) ||
culture.includes(searchText) ||
b.group.toLowerCase().includes(searchText)
);
});
}
if (selectedStateId !== -1) filtered = filtered.filter(b => b.state === selectedStateId); // filtered by state if (selectedStateId !== -1) filtered = filtered.filter(b => b.state === selectedStateId); // filtered by state
if (selectedCultureId !== -1) filtered = filtered.filter(b => b.culture === selectedCultureId); // filtered by culture if (selectedCultureId !== -1) filtered = filtered.filter(b => b.culture === selectedCultureId); // filtered by culture
@ -119,7 +141,7 @@ function overviewBurgs(settings = {stateId: null, cultureId: null}) {
body.insertAdjacentHTML("beforeend", lines); body.insertAdjacentHTML("beforeend", lines);
// update footer // update footer
burgsFooterBurgs.innerHTML = filtered.length; burgsFooterBurgs.innerHTML = `${filtered.length} of ${validBurgs.length}`;
burgsFooterPopulation.innerHTML = filtered.length ? si(totalPopulation / filtered.length) : 0; burgsFooterPopulation.innerHTML = filtered.length ? si(totalPopulation / filtered.length) : 0;
// add listeners // add listeners

View file

@ -4,18 +4,19 @@ function overviewMarkers() {
closeDialogs("#markersOverview, .stable"); closeDialogs("#markersOverview, .stable");
if (!layerIsOn("toggleMarkers")) toggleMarkers(); if (!layerIsOn("toggleMarkers")) toggleMarkers();
const markerGroup = document.getElementById("markers"); const markerGroup = byId("markers");
const body = document.getElementById("markersBody"); const body = byId("markersBody");
const markersInverPin = document.getElementById("markersInverPin"); const markersInverPin = byId("markersInverPin");
const markersInverLock = document.getElementById("markersInverLock"); const markersInverLock = byId("markersInverLock");
const markersFooterNumber = document.getElementById("markersFooterNumber"); const markersFooterNumber = byId("markersFooterNumber");
const markersOverviewRefresh = document.getElementById("markersOverviewRefresh"); const markersOverviewRefresh = byId("markersOverviewRefresh");
const markersAddFromOverview = document.getElementById("markersAddFromOverview"); const markersAddFromOverview = byId("markersAddFromOverview");
const markersGenerationConfig = document.getElementById("markersGenerationConfig"); const markersGenerationConfig = byId("markersGenerationConfig");
const markersRemoveAll = document.getElementById("markersRemoveAll"); const markersRemoveAll = byId("markersRemoveAll");
const markersExport = document.getElementById("markersExport"); const markersExport = byId("markersExport");
const markerTypeInput = document.getElementById("addedMarkerType"); const markerTypeInput = byId("addedMarkerType");
const markerTypeSelector = document.getElementById("markerTypeSelector"); const markerTypeSelector = byId("markerTypeSelector");
const markersSearch = byId("markersSearch");
addLines(); addLines();
@ -36,7 +37,8 @@ function overviewMarkers() {
listen(markersGenerationConfig, "click", configMarkersGeneration), listen(markersGenerationConfig, "click", configMarkersGeneration),
listen(markersRemoveAll, "click", triggerRemoveAll), listen(markersRemoveAll, "click", triggerRemoveAll),
listen(markersExport, "click", exportMarkers), listen(markersExport, "click", exportMarkers),
listen(markerTypeSelector, "click", toggleMarkerTypeMenu) listen(markerTypeSelector, "click", toggleMarkerTypeMenu),
listen(markersSearch, "input", addLines)
]; ];
const types = [{type: "empty", icon: "❓"}, ...Markers.getConfig()]; const types = [{type: "empty", icon: "❓"}, ...Markers.getConfig()];
@ -67,7 +69,17 @@ function overviewMarkers() {
} }
function addLines() { function addLines() {
const lines = pack.markers let markers = pack.markers;
const searchText = byId("markersSearch").value.toLowerCase().trim();
if (searchText) {
markers = markers.filter(marker => {
const type = (marker.type || "").toLowerCase();
return type.includes(searchText);
});
}
const lines = markers
.map(({i, type, icon, pinned, lock}) => { .map(({i, type, icon, pinned, lock}) => {
return /* html */ ` return /* html */ `
<div class="states" data-i=${i} data-type="${type}"> <div class="states" data-i=${i} data-type="${type}">
@ -91,7 +103,8 @@ function overviewMarkers() {
.join(""); .join("");
body.innerHTML = lines; body.innerHTML = lines;
markersFooterNumber.innerText = pack.markers.length; markersFooterNumber.innerText = markers.length;
markersFooterTotal.innerText = pack.markers.length;
applySorting(markersHeader); applySorting(markersHeader);
} }
@ -127,7 +140,7 @@ function overviewMarkers() {
} }
function focusOnMarker(i) { function focusOnMarker(i) {
highlightElement(document.getElementById(`marker${i}`), 2); highlightElement(byId(`marker${i}`), 2);
} }
function pinMarker(el, i) { function pinMarker(el, i) {
@ -165,7 +178,7 @@ function overviewMarkers() {
} }
function toggleMarkerTypeMenu() { function toggleMarkerTypeMenu() {
document.getElementById("markerTypeSelectMenu").classList.toggle("visible"); byId("markerTypeSelectMenu").classList.toggle("visible");
} }
function toggleAddMarker() { function toggleAddMarker() {
@ -182,7 +195,7 @@ function overviewMarkers() {
function removeMarker(i) { function removeMarker(i) {
notes = notes.filter(note => note.id !== `marker${i}`); notes = notes.filter(note => note.id !== `marker${i}`);
pack.markers = pack.markers.filter(marker => marker.i !== i); pack.markers = pack.markers.filter(marker => marker.i !== i);
document.getElementById(`marker${i}`)?.remove(); byId(`marker${i}`)?.remove();
addLines(); addLines();
} }
@ -200,7 +213,7 @@ function overviewMarkers() {
if (lock) return true; if (lock) return true;
const id = `marker${i}`; const id = `marker${i}`;
document.getElementById(id)?.remove(); byId(id)?.remove();
notes = notes.filter(note => note.id !== id); notes = notes.filter(note => note.id !== id);
return false; return false;
}); });

View file

@ -5,7 +5,7 @@ function overviewRivers() {
closeDialogs("#riversOverview, .stable"); closeDialogs("#riversOverview, .stable");
if (!layerIsOn("toggleRivers")) toggleRivers(); if (!layerIsOn("toggleRivers")) toggleRivers();
const body = document.getElementById("riversBody"); const body = byId("riversBody");
riversOverviewAddLines(); riversOverviewAddLines();
$("#riversOverview").dialog(); $("#riversOverview").dialog();
@ -20,12 +20,13 @@ function overviewRivers() {
}); });
// add listeners // add listeners
document.getElementById("riversOverviewRefresh").addEventListener("click", riversOverviewAddLines); byId("riversOverviewRefresh").on("click", riversOverviewAddLines);
document.getElementById("addNewRiver").addEventListener("click", toggleAddRiver); byId("addNewRiver").on("click", toggleAddRiver);
document.getElementById("riverCreateNew").addEventListener("click", createRiver); byId("riverCreateNew").on("click", createRiver);
document.getElementById("riversBasinHighlight").addEventListener("click", toggleBasinsHightlight); byId("riversBasinHighlight").on("click", toggleBasinsHightlight);
document.getElementById("riversExport").addEventListener("click", downloadRiversData); byId("riversExport").on("click", downloadRiversData);
document.getElementById("riversRemoveAll").addEventListener("click", triggerAllRiversRemove); byId("riversRemoveAll").on("click", triggerAllRiversRemove);
byId("riversSearch").on("input", riversOverviewAddLines);
// add line for each river // add line for each river
function riversOverviewAddLines() { function riversOverviewAddLines() {
@ -33,11 +34,26 @@ function overviewRivers() {
let lines = ""; let lines = "";
const unit = distanceUnitInput.value; const unit = distanceUnitInput.value;
for (const r of pack.rivers) { // Precompute a lookup map from river id to river for efficient basin lookup
const riversById = new Map(pack.rivers.map(river => [river.i, river]));
let filteredRivers = pack.rivers;
const searchText = byId("riversSearch").value.toLowerCase().trim();
if (searchText) {
filteredRivers = filteredRivers.filter(r => {
const name = (r.name || "").toLowerCase();
const type = (r.type || "").toLowerCase();
const basin = riversById.get(r.basin);
const basinName = basin ? (basin.name || "").toLowerCase() : "";
return name.includes(searchText) || type.includes(searchText) || basinName.includes(searchText);
});
}
for (const r of filteredRivers) {
const discharge = r.discharge + " m³/s"; const discharge = r.discharge + " m³/s";
const length = rn(r.length * distanceScale) + " " + unit; const length = rn(r.length * distanceScale) + " " + unit;
const width = rn(r.width * distanceScale, 3) + " " + unit; const width = rn(r.width * distanceScale, 3) + " " + unit;
const basin = pack.rivers.find(river => river.i === r.basin)?.name; const basin = riversById.get(r.basin)?.name;
lines += /* html */ `<div lines += /* html */ `<div
class="states" class="states"
@ -63,22 +79,20 @@ function overviewRivers() {
body.insertAdjacentHTML("beforeend", lines); body.insertAdjacentHTML("beforeend", lines);
// update footer // update footer
riversFooterNumber.innerHTML = pack.rivers.length; riversFooterNumber.innerHTML = `${filteredRivers.length} of ${pack.rivers.length}`;
const averageDischarge = rn(d3.mean(pack.rivers.map(r => r.discharge))); const averageDischarge = rn(d3.mean(filteredRivers.map(r => r.discharge))) || 0;
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(filteredRivers.map(r => r.length))) || 0;
riversFooterLength.innerHTML = averageLength * distanceScale + " " + unit; riversFooterLength.innerHTML = averageLength * distanceScale + " " + unit;
const averageWidth = rn(d3.mean(pack.rivers.map(r => r.width)), 3); const averageWidth = rn(d3.mean(filteredRivers.map(r => r.width)), 3) || 0;
riversFooterWidth.innerHTML = rn(averageWidth * distanceScale, 3) + " " + unit; riversFooterWidth.innerHTML = rn(averageWidth * distanceScale, 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.on("mouseenter", ev => riverHighlightOn(ev)));
body.querySelectorAll("div.states").forEach(el => el.addEventListener("mouseleave", ev => riverHighlightOff(ev))); body.querySelectorAll("div.states").forEach(el => el.on("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.on("click", zoomToRiver));
body.querySelectorAll("div > span.icon-pencil").forEach(el => el.addEventListener("click", openRiverEditor)); body.querySelectorAll("div > span.icon-pencil").forEach(el => el.on("click", openRiverEditor));
body body.querySelectorAll("div > span.icon-trash-empty").forEach(el => el.on("click", triggerRiverRemove));
.querySelectorAll("div > span.icon-trash-empty")
.forEach(el => el.addEventListener("click", triggerRiverRemove));
applySorting(riversHeader); applySorting(riversHeader);
} }

View file

@ -25,13 +25,25 @@ function overviewRoutes() {
byId("routesExport").on("click", downloadRoutesData); byId("routesExport").on("click", downloadRoutesData);
byId("routesLockAll").on("click", toggleLockAll); byId("routesLockAll").on("click", toggleLockAll);
byId("routesRemoveAll").on("click", triggerAllRoutesRemove); byId("routesRemoveAll").on("click", triggerAllRoutesRemove);
byId("routesSearch").on("input", routesOverviewAddLines);
// add line for each route // add line for each route
function routesOverviewAddLines() { function routesOverviewAddLines() {
body.innerHTML = ""; body.innerHTML = "";
let lines = ""; let lines = "";
for (const route of pack.routes) { let filteredRoutes = pack.routes;
const searchText = byId("routesSearch").value.toLowerCase().trim();
if (searchText) {
filteredRoutes = filteredRoutes.filter(route => {
const name = (route.name || "").toLowerCase();
const group = (route.group || "").toLowerCase();
return name.includes(searchText) || group.includes(searchText);
});
}
for (const route of filteredRoutes) {
if (!route.points || route.points.length < 2) continue; if (!route.points || route.points.length < 2) continue;
route.name = route.name || Routes.generateName(route); route.name = route.name || Routes.generateName(route);
route.length = route.length || Routes.getLength(route.i); route.length = route.length || Routes.getLength(route.i);
@ -58,8 +70,8 @@ function overviewRoutes() {
body.insertAdjacentHTML("beforeend", lines); body.insertAdjacentHTML("beforeend", lines);
// update footer // update footer
routesFooterNumber.innerHTML = pack.routes.length; routesFooterNumber.innerHTML = `${filteredRoutes.length} of ${pack.routes.length}`;
const averageLength = rn(d3.mean(pack.routes.map(r => r.length)) || 0); const averageLength = rn(d3.mean(filteredRoutes.map(r => r.length)) || 0) || 0;
routesFooterLength.innerHTML = averageLength * distanceScale + " " + distanceUnitInput.value; routesFooterLength.innerHTML = averageLength * distanceScale + " " + distanceUnitInput.value;
// add listeners // add listeners
@ -67,7 +79,7 @@ function overviewRoutes() {
body.querySelectorAll("div.states").forEach(el => el.on("mouseleave", routeHighlightOff)); body.querySelectorAll("div.states").forEach(el => el.on("mouseleave", routeHighlightOff));
body.querySelectorAll("div > span.icon-dot-circled").forEach(el => el.on("click", zoomToRoute)); body.querySelectorAll("div > span.icon-dot-circled").forEach(el => el.on("click", zoomToRoute));
body.querySelectorAll("div > span.icon-pencil").forEach(el => el.on("click", openRouteEditor)); body.querySelectorAll("div > span.icon-pencil").forEach(el => el.on("click", openRouteEditor));
body.querySelectorAll("div > span.locks").forEach(el => el.addEventListener("click", toggleLockStatus)); body.querySelectorAll("div > span.locks").forEach(el => el.on("click", toggleLockStatus));
body.querySelectorAll("div > span.icon-trash-empty").forEach(el => el.on("click", triggerRouteRemove)); body.querySelectorAll("div > span.icon-trash-empty").forEach(el => el.on("click", triggerRouteRemove));
applySorting(routesHeader); applySorting(routesHeader);

View file

@ -37,6 +37,7 @@ if (parseMapVersion(VERSION) !== VERSION) alert("versioning.js: Invalid format o
<ul> <ul>
<strong>Latest changes:</strong> <strong>Latest changes:</strong>
<li>Search input in Overview dialogs</li>
<li>Custom burg grouping and icon selection</li> <li>Custom burg grouping and icon selection</li>
<li>Ability to set custom image as Marker or Regiment icon</li> <li>Ability to set custom image as Marker or Regiment icon</li>
<li>Submap and Transform tools rework</li> <li>Submap and Transform tools rework</li>
@ -48,8 +49,6 @@ if (parseMapVersion(VERSION) !== VERSION) alert("versioning.js: Invalid format o
<li>New routes generation algorithm</li> <li>New routes generation algorithm</li>
<li>Routes overview tool</li> <li>Routes overview tool</li>
<li>Configurable longitude</li> <li>Configurable longitude</li>
<li>Preview villages map</li>
<li>Ability to render ocean heightmap</li>
</ul> </ul>
<p>Join our <a href="${discord}" target="_blank">Discord server</a> and <a href="${reddit}" target="_blank">Reddit community</a> to ask questions, share maps, discuss the Generator and Worlbuilding, report bugs and propose new features.</p> <p>Join our <a href="${discord}" target="_blank">Discord server</a> and <a href="${reddit}" target="_blank">Reddit community</a> to ask questions, share maps, discuss the Generator and Worlbuilding, report bugs and propose new features.</p>

View file

@ -5369,17 +5369,29 @@
<div id="burgsBody" class="table"></div> <div id="burgsBody" class="table"></div>
<div id="burgsFilters" data-tip="Apply a filter" style="padding-block: 0.1em"> <div
<label for="burgsFilterState">State:</label> id="burgsFilters"
<select id="burgsFilterState" style="width: 28%"></select> data-tip="Apply a filter"
style="padding-block: 0.1em; display: flex; gap: 0.5em; width: 100%"
>
<label for="burgsSearch" data-tip="Filter by name, province, state, culture, or group"
>Search: <input id="burgsSearch" type="search"
/></label>
<label for="burgsFilterCulture">Culture:</label> <label for="burgsFilterState"
<select id="burgsFilterCulture" style="width: 28%"></select> >State:
<select id="burgsFilterState"></select
></label>
<label for="burgsFilterCulture"
>Culture:
<select id="burgsFilterCulture"></select
></label>
</div> </div>
<div id="burgsFooter" class="totalLine"> <div id="burgsFooter" class="totalLine">
<div data-tip="Burgs displayed" style="margin-left: 4px"> <div data-tip="Burgs displayed" style="margin-left: 4px">
Burgs:&nbsp;<span id="burgsFooterBurgs">0</span> Burgs:&nbsp;<span id="burgsFooterBurgs">0 of 0</span>
</div> </div>
<div data-tip="Average population" style="margin-left: 14px"> <div data-tip="Average population" style="margin-left: 14px">
@ -5455,7 +5467,7 @@
<div id="routesFooter" class="totalLine"> <div id="routesFooter" class="totalLine">
<div data-tip="Routes number" style="margin-left: 4px"> <div data-tip="Routes number" style="margin-left: 4px">
Total routes:&nbsp;<span id="routesFooterNumber">0</span> Routes:&nbsp;<span id="routesFooterNumber">0</span>
</div> </div>
<div data-tip="Average length" style="margin-left: 12px"> <div data-tip="Average length" style="margin-left: 12px">
Average length:&nbsp;<span id="routesFooterLength">0</span> Average length:&nbsp;<span id="routesFooterLength">0</span>
@ -5476,11 +5488,14 @@
></button> ></button>
<button id="routesLockAll" data-tip="Lock or unlock all routes" class="icon-lock"></button> <button id="routesLockAll" data-tip="Lock or unlock all routes" class="icon-lock"></button>
<button id="routesRemoveAll" data-tip="Remove all routes" class="icon-trash"></button> <button id="routesRemoveAll" data-tip="Remove all routes" class="icon-trash"></button>
<label for="routesSearch" data-tip="Filter by name or group" style="margin-left: 0.2em"
>Search: <input id="routesSearch" type="search"
/></label>
</div> </div>
</div> </div>
<div id="riversOverview" class="dialog stable" style="display: none"> <div id="riversOverview" class="dialog stable" style="display: none">
<div id="riversHeader" class="header" style="grid-template-columns: 9em 4em 6em 6em 5em 9em"> <div id="riversHeader" class="header" style="grid-template-columns: 9em 4em 7em 5em 5em 9em">
<div data-tip="Click to sort by river name" class="sortable alphabetically" data-sortby="name"> <div data-tip="Click to sort by river name" class="sortable alphabetically" data-sortby="name">
River&nbsp; River&nbsp;
</div> </div>
@ -5533,6 +5548,9 @@
class="icon-download" class="icon-download"
></button> ></button>
<button id="riversRemoveAll" data-tip="Remove all rivers" class="icon-trash"></button> <button id="riversRemoveAll" data-tip="Remove all rivers" class="icon-trash"></button>
<label for="riversSearch" data-tip="Filter by name, type or basin" style="margin-left: 0.2em"
>Search: <input id="riversSearch" type="search"
/></label>
</div> </div>
</div> </div>
@ -5702,9 +5720,15 @@
<div id="markersBody" class="table"></div> <div id="markersBody" class="table"></div>
<div>
<label for="markersSearch" data-tip="Filter by type"
>Search: <input id="markersSearch" type="search"
/></label>
</div>
<div id="markersFooter" class="totalLine"> <div id="markersFooter" class="totalLine">
<div data-tip="Markers number" style="margin-left: 4px"> <div data-tip="Markers number">
Total:&nbsp;<span id="markersFooterNumber">0</span>&nbsp;markers Markers: <span id="markersFooterNumber">0</span> of <span id="markersFooterTotal">0</span>
</div> </div>
</div> </div>
@ -8525,12 +8549,12 @@
<script defer src="modules/ui/ai-generator.js?v=1.108.8"></script> <script defer src="modules/ui/ai-generator.js?v=1.108.8"></script>
<script defer src="modules/ui/diplomacy-editor.js?v=1.99.00"></script> <script defer src="modules/ui/diplomacy-editor.js?v=1.99.00"></script>
<script defer src="modules/ui/zones-editor.js?v=1.105.20"></script> <script defer src="modules/ui/zones-editor.js?v=1.105.20"></script>
<script defer src="modules/ui/burgs-overview.js?v=1.105.15"></script> <script defer src="modules/ui/burgs-overview.js?v=1.110.0"></script>
<script defer src="modules/ui/routes-overview.js?v=1.104.3"></script> <script defer src="modules/ui/routes-overview.js?v=1.110.0"></script>
<script defer src="modules/ui/rivers-overview.js?v=1.99.00"></script> <script defer src="modules/ui/rivers-overview.js?v=1.110.0"></script>
<script defer src="modules/ui/military-overview.js?v=1.108.5"></script> <script defer src="modules/ui/military-overview.js?v=1.108.5"></script>
<script defer src="modules/ui/regiments-overview.js?v=1.108.5"></script> <script defer src="modules/ui/regiments-overview.js?v=1.108.5"></script>
<script defer src="modules/ui/markers-overview.js?v=1.108.10"></script> <script defer src="modules/ui/markers-overview.js?v=1.110.0"></script>
<script defer src="modules/ui/regiment-editor.js?v=1.108.5"></script> <script defer src="modules/ui/regiment-editor.js?v=1.108.5"></script>
<script defer src="modules/ui/battle-screen.js?v=1.108.5"></script> <script defer src="modules/ui/battle-screen.js?v=1.108.5"></script>
<script defer src="modules/ui/emblems-editor.js?v=1.99.00"></script> <script defer src="modules/ui/emblems-editor.js?v=1.99.00"></script>