Skip to content

Commit f30ffd8

Browse files
AzgaarCopilot
andauthored
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>
1 parent 9e0eb03 commit f30ffd8

File tree

9 files changed

+149
-63
lines changed

9 files changed

+149
-63
lines changed

.github/pull_request_template.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
# Type of change
66

7-
<!-- Please put X into brackers of required option OR delete options that are not relevant -->
7+
<!-- Please put X into brackets of required option OR delete options that are not relevant -->
88

99
- [ ] Bug fix
1010
- [ ] New feature

package-lock.json

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "fantasy-map-generator",
3-
"version": "1.109.5",
3+
"version": "1.110.0",
44
"description": "Azgaar's _Fantasy Map Generator_ is a free web application that helps fantasy writers, game masters, and cartographers create and edit fantasy maps.",
55
"homepage": "https://github.com/Azgaar/Fantasy-Map-Generator#readme",
66
"bugs": {

public/modules/ui/burgs-overview.js

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ function overviewBurgs(settings = {stateId: null, cultureId: null}) {
2828
byId("burgsChart").addEventListener("click", showBurgsChart);
2929
byId("burgsFilterState").addEventListener("change", burgsOverviewAddLines);
3030
byId("burgsFilterCulture").addEventListener("change", burgsOverviewAddLines);
31+
byId("burgsSearch").addEventListener("input", burgsOverviewAddLines);
3132
byId("regenerateBurgNames").addEventListener("click", regenerateNames);
3233
byId("addNewBurg").addEventListener("click", enterAddBurgMode);
3334
byId("burgsExport").addEventListener("click", downloadBurgsData);
@@ -63,9 +64,30 @@ function overviewBurgs(settings = {stateId: null, cultureId: null}) {
6364

6465
// add line for each burg
6566
function burgsOverviewAddLines() {
67+
const searchText = byId("burgsSearch").value.toLowerCase().trim();
6668
const selectedStateId = +byId("burgsFilterState").value;
6769
const selectedCultureId = +byId("burgsFilterCulture").value;
68-
let filtered = pack.burgs.filter(b => b.i && !b.removed); // all valid burgs
70+
71+
const validBurgs = pack.burgs.filter(b => b.i && !b.removed);
72+
let filtered = validBurgs;
73+
74+
if (searchText) {
75+
// filter by search text
76+
filtered = filtered.filter(b => {
77+
const name = b.name.toLowerCase();
78+
const state = (pack.states[b.state]?.name || "").toLowerCase();
79+
const prov = pack.cells.province[b.cell];
80+
const province = prov ? pack.provinces[prov]?.name.toLowerCase() : "";
81+
const culture = (pack.cultures[b.culture]?.name || "").toLowerCase();
82+
return (
83+
name.includes(searchText) ||
84+
state.includes(searchText) ||
85+
province.includes(searchText) ||
86+
culture.includes(searchText) ||
87+
b.group.toLowerCase().includes(searchText)
88+
);
89+
});
90+
}
6991
if (selectedStateId !== -1) filtered = filtered.filter(b => b.state === selectedStateId); // filtered by state
7092
if (selectedCultureId !== -1) filtered = filtered.filter(b => b.culture === selectedCultureId); // filtered by culture
7193

@@ -119,7 +141,7 @@ function overviewBurgs(settings = {stateId: null, cultureId: null}) {
119141
body.insertAdjacentHTML("beforeend", lines);
120142

121143
// update footer
122-
burgsFooterBurgs.innerHTML = filtered.length;
144+
burgsFooterBurgs.innerHTML = `${filtered.length} of ${validBurgs.length}`;
123145
burgsFooterPopulation.innerHTML = filtered.length ? si(totalPopulation / filtered.length) : 0;
124146

125147
// add listeners

public/modules/ui/markers-overview.js

Lines changed: 32 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,19 @@ function overviewMarkers() {
44
closeDialogs("#markersOverview, .stable");
55
if (!layerIsOn("toggleMarkers")) toggleMarkers();
66

7-
const markerGroup = document.getElementById("markers");
8-
const body = document.getElementById("markersBody");
9-
const markersInverPin = document.getElementById("markersInverPin");
10-
const markersInverLock = document.getElementById("markersInverLock");
11-
const markersFooterNumber = document.getElementById("markersFooterNumber");
12-
const markersOverviewRefresh = document.getElementById("markersOverviewRefresh");
13-
const markersAddFromOverview = document.getElementById("markersAddFromOverview");
14-
const markersGenerationConfig = document.getElementById("markersGenerationConfig");
15-
const markersRemoveAll = document.getElementById("markersRemoveAll");
16-
const markersExport = document.getElementById("markersExport");
17-
const markerTypeInput = document.getElementById("addedMarkerType");
18-
const markerTypeSelector = document.getElementById("markerTypeSelector");
7+
const markerGroup = byId("markers");
8+
const body = byId("markersBody");
9+
const markersInverPin = byId("markersInverPin");
10+
const markersInverLock = byId("markersInverLock");
11+
const markersFooterNumber = byId("markersFooterNumber");
12+
const markersOverviewRefresh = byId("markersOverviewRefresh");
13+
const markersAddFromOverview = byId("markersAddFromOverview");
14+
const markersGenerationConfig = byId("markersGenerationConfig");
15+
const markersRemoveAll = byId("markersRemoveAll");
16+
const markersExport = byId("markersExport");
17+
const markerTypeInput = byId("addedMarkerType");
18+
const markerTypeSelector = byId("markerTypeSelector");
19+
const markersSearch = byId("markersSearch");
1920

2021
addLines();
2122

@@ -36,7 +37,8 @@ function overviewMarkers() {
3637
listen(markersGenerationConfig, "click", configMarkersGeneration),
3738
listen(markersRemoveAll, "click", triggerRemoveAll),
3839
listen(markersExport, "click", exportMarkers),
39-
listen(markerTypeSelector, "click", toggleMarkerTypeMenu)
40+
listen(markerTypeSelector, "click", toggleMarkerTypeMenu),
41+
listen(markersSearch, "input", addLines)
4042
];
4143

4244
const types = [{type: "empty", icon: "❓"}, ...Markers.getConfig()];
@@ -67,7 +69,17 @@ function overviewMarkers() {
6769
}
6870

6971
function addLines() {
70-
const lines = pack.markers
72+
let markers = pack.markers;
73+
74+
const searchText = byId("markersSearch").value.toLowerCase().trim();
75+
if (searchText) {
76+
markers = markers.filter(marker => {
77+
const type = (marker.type || "").toLowerCase();
78+
return type.includes(searchText);
79+
});
80+
}
81+
82+
const lines = markers
7183
.map(({i, type, icon, pinned, lock}) => {
7284
return /* html */ `
7385
<div class="states" data-i=${i} data-type="${type}">
@@ -91,7 +103,8 @@ function overviewMarkers() {
91103
.join("");
92104

93105
body.innerHTML = lines;
94-
markersFooterNumber.innerText = pack.markers.length;
106+
markersFooterNumber.innerText = markers.length;
107+
markersFooterTotal.innerText = pack.markers.length;
95108

96109
applySorting(markersHeader);
97110
}
@@ -127,7 +140,7 @@ function overviewMarkers() {
127140
}
128141

129142
function focusOnMarker(i) {
130-
highlightElement(document.getElementById(`marker${i}`), 2);
143+
highlightElement(byId(`marker${i}`), 2);
131144
}
132145

133146
function pinMarker(el, i) {
@@ -165,7 +178,7 @@ function overviewMarkers() {
165178
}
166179

167180
function toggleMarkerTypeMenu() {
168-
document.getElementById("markerTypeSelectMenu").classList.toggle("visible");
181+
byId("markerTypeSelectMenu").classList.toggle("visible");
169182
}
170183

171184
function toggleAddMarker() {
@@ -182,7 +195,7 @@ function overviewMarkers() {
182195
function removeMarker(i) {
183196
notes = notes.filter(note => note.id !== `marker${i}`);
184197
pack.markers = pack.markers.filter(marker => marker.i !== i);
185-
document.getElementById(`marker${i}`)?.remove();
198+
byId(`marker${i}`)?.remove();
186199
addLines();
187200
}
188201

@@ -200,7 +213,7 @@ function overviewMarkers() {
200213
if (lock) return true;
201214

202215
const id = `marker${i}`;
203-
document.getElementById(id)?.remove();
216+
byId(id)?.remove();
204217
notes = notes.filter(note => note.id !== id);
205218
return false;
206219
});

public/modules/ui/rivers-overview.js

Lines changed: 34 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ function overviewRivers() {
55
closeDialogs("#riversOverview, .stable");
66
if (!layerIsOn("toggleRivers")) toggleRivers();
77

8-
const body = document.getElementById("riversBody");
8+
const body = byId("riversBody");
99
riversOverviewAddLines();
1010
$("#riversOverview").dialog();
1111

@@ -20,24 +20,40 @@ function overviewRivers() {
2020
});
2121

2222
// add listeners
23-
document.getElementById("riversOverviewRefresh").addEventListener("click", riversOverviewAddLines);
24-
document.getElementById("addNewRiver").addEventListener("click", toggleAddRiver);
25-
document.getElementById("riverCreateNew").addEventListener("click", createRiver);
26-
document.getElementById("riversBasinHighlight").addEventListener("click", toggleBasinsHightlight);
27-
document.getElementById("riversExport").addEventListener("click", downloadRiversData);
28-
document.getElementById("riversRemoveAll").addEventListener("click", triggerAllRiversRemove);
23+
byId("riversOverviewRefresh").on("click", riversOverviewAddLines);
24+
byId("addNewRiver").on("click", toggleAddRiver);
25+
byId("riverCreateNew").on("click", createRiver);
26+
byId("riversBasinHighlight").on("click", toggleBasinsHightlight);
27+
byId("riversExport").on("click", downloadRiversData);
28+
byId("riversRemoveAll").on("click", triggerAllRiversRemove);
29+
byId("riversSearch").on("input", riversOverviewAddLines);
2930

3031
// add line for each river
3132
function riversOverviewAddLines() {
3233
body.innerHTML = "";
3334
let lines = "";
3435
const unit = distanceUnitInput.value;
3536

36-
for (const r of pack.rivers) {
37+
// Precompute a lookup map from river id to river for efficient basin lookup
38+
const riversById = new Map(pack.rivers.map(river => [river.i, river]));
39+
40+
let filteredRivers = pack.rivers;
41+
const searchText = byId("riversSearch").value.toLowerCase().trim();
42+
if (searchText) {
43+
filteredRivers = filteredRivers.filter(r => {
44+
const name = (r.name || "").toLowerCase();
45+
const type = (r.type || "").toLowerCase();
46+
const basin = riversById.get(r.basin);
47+
const basinName = basin ? (basin.name || "").toLowerCase() : "";
48+
return name.includes(searchText) || type.includes(searchText) || basinName.includes(searchText);
49+
});
50+
}
51+
52+
for (const r of filteredRivers) {
3753
const discharge = r.discharge + " m³/s";
3854
const length = rn(r.length * distanceScale) + " " + unit;
3955
const width = rn(r.width * distanceScale, 3) + " " + unit;
40-
const basin = pack.rivers.find(river => river.i === r.basin)?.name;
56+
const basin = riversById.get(r.basin)?.name;
4157

4258
lines += /* html */ `<div
4359
class="states"
@@ -63,22 +79,20 @@ function overviewRivers() {
6379
body.insertAdjacentHTML("beforeend", lines);
6480

6581
// update footer
66-
riversFooterNumber.innerHTML = pack.rivers.length;
67-
const averageDischarge = rn(d3.mean(pack.rivers.map(r => r.discharge)));
82+
riversFooterNumber.innerHTML = `${filteredRivers.length} of ${pack.rivers.length}`;
83+
const averageDischarge = rn(d3.mean(filteredRivers.map(r => r.discharge))) || 0;
6884
riversFooterDischarge.innerHTML = averageDischarge + " m³/s";
69-
const averageLength = rn(d3.mean(pack.rivers.map(r => r.length)));
85+
const averageLength = rn(d3.mean(filteredRivers.map(r => r.length))) || 0;
7086
riversFooterLength.innerHTML = averageLength * distanceScale + " " + unit;
71-
const averageWidth = rn(d3.mean(pack.rivers.map(r => r.width)), 3);
87+
const averageWidth = rn(d3.mean(filteredRivers.map(r => r.width)), 3) || 0;
7288
riversFooterWidth.innerHTML = rn(averageWidth * distanceScale, 3) + " " + unit;
7389

7490
// add listeners
75-
body.querySelectorAll("div.states").forEach(el => el.addEventListener("mouseenter", ev => riverHighlightOn(ev)));
76-
body.querySelectorAll("div.states").forEach(el => el.addEventListener("mouseleave", ev => riverHighlightOff(ev)));
77-
body.querySelectorAll("div > span.icon-dot-circled").forEach(el => el.addEventListener("click", zoomToRiver));
78-
body.querySelectorAll("div > span.icon-pencil").forEach(el => el.addEventListener("click", openRiverEditor));
79-
body
80-
.querySelectorAll("div > span.icon-trash-empty")
81-
.forEach(el => el.addEventListener("click", triggerRiverRemove));
91+
body.querySelectorAll("div.states").forEach(el => el.on("mouseenter", ev => riverHighlightOn(ev)));
92+
body.querySelectorAll("div.states").forEach(el => el.on("mouseleave", ev => riverHighlightOff(ev)));
93+
body.querySelectorAll("div > span.icon-dot-circled").forEach(el => el.on("click", zoomToRiver));
94+
body.querySelectorAll("div > span.icon-pencil").forEach(el => el.on("click", openRiverEditor));
95+
body.querySelectorAll("div > span.icon-trash-empty").forEach(el => el.on("click", triggerRiverRemove));
8296

8397
applySorting(riversHeader);
8498
}

public/modules/ui/routes-overview.js

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,25 @@ function overviewRoutes() {
2525
byId("routesExport").on("click", downloadRoutesData);
2626
byId("routesLockAll").on("click", toggleLockAll);
2727
byId("routesRemoveAll").on("click", triggerAllRoutesRemove);
28+
byId("routesSearch").on("input", routesOverviewAddLines);
2829

2930
// add line for each route
3031
function routesOverviewAddLines() {
3132
body.innerHTML = "";
3233
let lines = "";
3334

34-
for (const route of pack.routes) {
35+
let filteredRoutes = pack.routes;
36+
37+
const searchText = byId("routesSearch").value.toLowerCase().trim();
38+
if (searchText) {
39+
filteredRoutes = filteredRoutes.filter(route => {
40+
const name = (route.name || "").toLowerCase();
41+
const group = (route.group || "").toLowerCase();
42+
return name.includes(searchText) || group.includes(searchText);
43+
});
44+
}
45+
46+
for (const route of filteredRoutes) {
3547
if (!route.points || route.points.length < 2) continue;
3648
route.name = route.name || Routes.generateName(route);
3749
route.length = route.length || Routes.getLength(route.i);
@@ -58,16 +70,16 @@ function overviewRoutes() {
5870
body.insertAdjacentHTML("beforeend", lines);
5971

6072
// update footer
61-
routesFooterNumber.innerHTML = pack.routes.length;
62-
const averageLength = rn(d3.mean(pack.routes.map(r => r.length)) || 0);
73+
routesFooterNumber.innerHTML = `${filteredRoutes.length} of ${pack.routes.length}`;
74+
const averageLength = rn(d3.mean(filteredRoutes.map(r => r.length)) || 0) || 0;
6375
routesFooterLength.innerHTML = averageLength * distanceScale + " " + distanceUnitInput.value;
6476

6577
// add listeners
6678
body.querySelectorAll("div.states").forEach(el => el.on("mouseenter", routeHighlightOn));
6779
body.querySelectorAll("div.states").forEach(el => el.on("mouseleave", routeHighlightOff));
6880
body.querySelectorAll("div > span.icon-dot-circled").forEach(el => el.on("click", zoomToRoute));
6981
body.querySelectorAll("div > span.icon-pencil").forEach(el => el.on("click", openRouteEditor));
70-
body.querySelectorAll("div > span.locks").forEach(el => el.addEventListener("click", toggleLockStatus));
82+
body.querySelectorAll("div > span.locks").forEach(el => el.on("click", toggleLockStatus));
7183
body.querySelectorAll("div > span.icon-trash-empty").forEach(el => el.on("click", triggerRouteRemove));
7284

7385
applySorting(routesHeader);

public/versioning.js

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ if (parseMapVersion(VERSION) !== VERSION) alert("versioning.js: Invalid format o
3737
3838
<ul>
3939
<strong>Latest changes:</strong>
40+
<li>Search input in Overview dialogs</li>
4041
<li>Custom burg grouping and icon selection</li>
4142
<li>Ability to set custom image as Marker or Regiment icon</li>
4243
<li>Submap and Transform tools rework</li>
@@ -48,8 +49,6 @@ if (parseMapVersion(VERSION) !== VERSION) alert("versioning.js: Invalid format o
4849
<li>New routes generation algorithm</li>
4950
<li>Routes overview tool</li>
5051
<li>Configurable longitude</li>
51-
<li>Preview villages map</li>
52-
<li>Ability to render ocean heightmap</li>
5352
</ul>
5453
5554
<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>

0 commit comments

Comments
 (0)