feat: route - continue

This commit is contained in:
Azgaar 2024-05-16 22:10:42 +02:00
parent 6936f37f9f
commit 29e7ab0953
7 changed files with 258 additions and 21 deletions

View file

@ -2107,6 +2107,9 @@
<button id="overviewBurgsButton" data-tip="Click to open Burgs Overview" data-shortcut="Shift + T"> <button id="overviewBurgsButton" data-tip="Click to open Burgs Overview" data-shortcut="Shift + T">
Burgs Burgs
</button> </button>
<button id="overviewRoutesButton" data-tip="Click to open Routes Overview" data-shortcut="Shift + U">
Routes
</button>
<button id="overviewRiversButton" data-tip="Click to open Rivers Overview" data-shortcut="Shift + V"> <button id="overviewRiversButton" data-tip="Click to open Rivers Overview" data-shortcut="Shift + V">
Rivers Rivers
</button> </button>
@ -5407,6 +5410,40 @@
</div> </div>
</div> </div>
<div id="routesOverview" class="dialog stable" style="display: none">
<div id="routesHeader" class="header" style="grid-template-columns: 17em 8em 8em">
<div data-tip="Click to sort by route name" class="sortable alphabetically" data-sortby="name">
Route&nbsp;
</div>
<div data-tip="Click to sort by route group" class="sortable alphabetically" data-sortby="group">
Group&nbsp;
</div>
<div data-tip="Click to sort by route length" class="sortable" data-sortby="length">Length&nbsp;</div>
</div>
<div id="routesBody" class="table"></div>
<div id="routesFooter" class="totalLine">
<div data-tip="Routes number" style="margin-left: 4px">
Total routes:&nbsp;<span id="routesFooterNumber">0</span>
</div>
<div data-tip="Average length" style="margin-left: 12px">
Average length:&nbsp;<span id="routesFooterLength">0</span>
</div>
</div>
<div id="routesBottom">
<button id="routesOverviewRefresh" data-tip="Refresh the Editor" class="icon-cw"></button>
<button id="routeCreateNew" data-tip="Create new route selecting route cells" class="icon-map-pin"></button>
<button
id="routesExport"
data-tip="Save routes-related data as a text file (.csv)"
class="icon-download"
></button>
<button id="routesRemoveAll" data-tip="Remove all routes" class="icon-trash"></button>
</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 6em 6em 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">
@ -8092,6 +8129,7 @@
<script defer src="modules/ui/diplomacy-editor.js?v=1.88.04"></script> <script defer src="modules/ui/diplomacy-editor.js?v=1.88.04"></script>
<script defer src="modules/ui/zones-editor.js"></script> <script defer src="modules/ui/zones-editor.js"></script>
<script defer src="modules/ui/burgs-overview.js?v=1.97.00"></script> <script defer src="modules/ui/burgs-overview.js?v=1.97.00"></script>
<script defer src="modules/ui/routes-overview.js"></script>
<script defer src="modules/ui/rivers-overview.js"></script> <script defer src="modules/ui/rivers-overview.js"></script>
<script defer src="modules/ui/military-overview.js?v=1.96.07"></script> <script defer src="modules/ui/military-overview.js?v=1.96.07"></script>
<script defer src="modules/ui/regiments-overview.js?v=1.89.20"></script> <script defer src="modules/ui/regiments-overview.js?v=1.89.20"></script>

View file

@ -128,7 +128,21 @@ window.Routes = (function () {
routes.push({i: routes.length, group: "roads", feature, cells}); routes.push({i: routes.length, group: "roads", feature, cells});
} }
for (const {feature, cells} of trails) { // merge routes so that the last cells of one route are the first cells of the next route
for (let i = 0; i < trails.length; i++) {
const {cells, feature, merged} = trails[i];
if (merged) continue;
for (let j = i + 1; j < trails.length; j++) {
const nextTrail = trails[j];
if (nextTrail.merged) continue;
if (nextTrail.cells[0] === cells.at(-1)) {
cells.push(...nextTrail.cells.slice(1));
nextTrail.merged = true;
}
}
routes.push({i: routes.length, group: "trails", feature, cells}); routes.push({i: routes.length, group: "trails", feature, cells});
} }
@ -502,6 +516,8 @@ window.Routes = (function () {
}; };
function generateName({group, cells}) { function generateName({group, cells}) {
if (cells.length < 4) return "Unnamed route segment";
const model = rw(models[group]); const model = rw(models[group]);
const suffix = rw(suffixes[group]); const suffix = rw(suffixes[group]);
@ -521,5 +537,24 @@ window.Routes = (function () {
} }
} }
return {generate, isConnected, areConnected, getRoute, hasRoad, isCrossroad, generateName}; function remove(route) {
const routes = pack.cells.routes;
for (const from of route.cells) {
for (const [to, routeId] of Object.entries(routes[from])) {
if (routeId === route.i) {
delete routes[from][to];
delete routes[to][from];
}
}
}
pack.routes = pack.routes.filter(r => r.i !== route.i);
viewbox
.select("#routes")
.select("#route" + route.i)
.remove();
}
return {generate, isConnected, areConnected, getRoute, hasRoad, isCrossroad, generateName, remove};
})(); })();

View file

@ -50,6 +50,7 @@ function handleKeyup(event) {
else if (shift && code === "KeyO") editNotes(); else if (shift && code === "KeyO") editNotes();
else if (shift && code === "KeyA") overviewCharts(); else if (shift && code === "KeyA") overviewCharts();
else if (shift && code === "KeyT") overviewBurgs(); else if (shift && code === "KeyT") overviewBurgs();
else if (shift && code === "KeyU") overviewRoutes();
else if (shift && code === "KeyV") overviewRivers(); else if (shift && code === "KeyV") overviewRivers();
else if (shift && code === "KeyM") overviewMilitary(); else if (shift && code === "KeyM") overviewMilitary();
else if (shift && code === "KeyK") overviewMarkers(); else if (shift && code === "KeyK") overviewMarkers();

View file

@ -1,4 +1,5 @@
"use strict"; "use strict";
function overviewRivers() { function overviewRivers() {
if (customization) return; if (customization) return;
closeDialogs("#riversOverview, .stable"); closeDialogs("#riversOverview, .stable");
@ -49,7 +50,7 @@ function overviewRivers() {
data-basin="${basin}" data-basin="${basin}"
> >
<span data-tip="Click to focus on river" class="icon-dot-circled pointer"></span> <span data-tip="Click to focus on river" class="icon-dot-circled pointer"></span>
<div data-tip="River name" class="riverName">${r.name}</div> <div data-tip="River name" style="margin-left: 0.4em;" class="riverName">${r.name}</div>
<div data-tip="River type name" class="riverType">${r.type}</div> <div data-tip="River type name" class="riverType">${r.type}</div>
<div data-tip="River discharge (flux power)" class="biomeArea">${discharge}</div> <div data-tip="River discharge (flux power)" class="biomeArea">${discharge}</div>
<div data-tip="River length from source to mouth" class="biomeArea">${length}</div> <div data-tip="River length from source to mouth" class="biomeArea">${length}</div>
@ -75,7 +76,9 @@ function overviewRivers() {
body.querySelectorAll("div.states").forEach(el => el.addEventListener("mouseleave", ev => riverHighlightOff(ev))); body.querySelectorAll("div.states").forEach(el => el.addEventListener("mouseleave", ev => riverHighlightOff(ev)));
body.querySelectorAll("div > span.icon-dot-circled").forEach(el => el.addEventListener("click", zoomToRiver)); body.querySelectorAll("div > span.icon-dot-circled").forEach(el => el.addEventListener("click", zoomToRiver));
body.querySelectorAll("div > span.icon-pencil").forEach(el => el.addEventListener("click", openRiverEditor)); body.querySelectorAll("div > span.icon-pencil").forEach(el => el.addEventListener("click", openRiverEditor));
body.querySelectorAll("div > span.icon-trash-empty").forEach(el => el.addEventListener("click", triggerRiverRemove)); body
.querySelectorAll("div > span.icon-trash-empty")
.forEach(el => el.addEventListener("click", triggerRiverRemove));
applySorting(riversHeader); applySorting(riversHeader);
} }
@ -110,7 +113,18 @@ function overviewRivers() {
} else { } else {
rivers.attr("data-basin", "hightlighted"); rivers.attr("data-basin", "hightlighted");
const basins = [...new Set(pack.rivers.map(r => r.basin))]; const basins = [...new Set(pack.rivers.map(r => r.basin))];
const colors = ["#1f77b4", "#ff7f0e", "#2ca02c", "#d62728", "#9467bd", "#8c564b", "#e377c2", "#7f7f7f", "#bcbd22", "#17becf"]; const colors = [
"#1f77b4",
"#ff7f0e",
"#2ca02c",
"#d62728",
"#9467bd",
"#8c564b",
"#e377c2",
"#7f7f7f",
"#bcbd22",
"#17becf"
];
basins.forEach((b, i) => { basins.forEach((b, i) => {
const color = colors[i % colors.length]; const color = colors[i % colors.length];

View file

@ -69,7 +69,7 @@ function editRoute(id) {
function updateRouteLength(route) { function updateRouteLength(route) {
route.length = rn(elSelected.node().getTotalLength() / 2, 2); route.length = rn(elSelected.node().getTotalLength() / 2, 2);
const lengthUI = `${rn(route.length * distanceScaleInput.value)} ${distanceUnitInput.value}`; const lengthUI = `${rn(route.length * distanceScale)} ${distanceUnitInput.value}`;
byId("routeLength").value = lengthUI; byId("routeLength").value = lengthUI;
} }
@ -255,22 +255,8 @@ function editRoute(id) {
title: "Remove route", title: "Remove route",
buttons: { buttons: {
Remove: function () { Remove: function () {
const route = getRoute(); Routes.remove(getRoute());
const routes = pack.cells.routes;
for (const from of route.cells) {
for (const [to, routeId] of Object.entries(routes[from])) {
if (routeId === route.i) {
delete routes[from][to];
delete routes[to][from];
}
}
}
pack.routes = pack.routes.filter(r => r.i !== route.i);
$(this).dialog("close"); $(this).dialog("close");
elSelected.remove();
$("#routeEditor").dialog("close"); $("#routeEditor").dialog("close");
}, },
Cancel: function () { Cancel: function () {

View file

@ -0,0 +1,162 @@
"use strict";
function overviewRoutes() {
if (customization) return;
closeDialogs("#routesOverview, .stable");
if (!layerIsOn("toggleRoutes")) toggleRoutes();
const body = byId("routesBody");
routesOverviewAddLines();
$("#routesOverview").dialog();
if (modules.overviewRoutes) return;
modules.overviewRoutes = true;
$("#routesOverview").dialog({
title: "Routes Overview",
resizable: false,
width: fitContent(),
position: {my: "right top", at: "right-10 top+10", of: "svg", collision: "fit"}
});
// add listeners
byId("routesOverviewRefresh").on("click", routesOverviewAddLines);
// byId("routeCreateNew").on("click", createRoute);
byId("routesExport").on("click", downloadRoutesData);
byId("routesRemoveAll").on("click", triggerAllRoutesRemove);
// add line for each route
function routesOverviewAddLines() {
body.innerHTML = "";
let lines = "";
const unit = distanceUnitInput.value;
for (const route of pack.routes) {
route.name = route.name || Routes.generateName(route);
route.length = route.length || getRouteLength(route.i);
const length = rn(route.length * distanceScale) + " " + unit;
lines += /* html */ `<div
class="states"
data-id="${route.i}"
data-name="${route.name}"
data-group="${route.group}"
data-length="${route.length}"
>
<span data-tip="Click to focus on route" class="icon-dot-circled pointer"></span>
<div data-tip="Route name" style="width: 15em; margin-left: 0.4em;">${route.name}</div>
<div data-tip="Route group" style="width: 8em;">${route.group}</div>
<div data-tip="Route length" style="width: 6em;">${length}</div>
<span data-tip="Edit route" class="icon-pencil"></span>
<span data-tip="Remove route" class="icon-trash-empty"></span>
</div>`;
}
body.insertAdjacentHTML("beforeend", lines);
// update footer
routesFooterNumber.innerHTML = pack.routes.length;
const averageLength = rn(d3.mean(pack.routes.map(r => r.length)));
routesFooterLength.innerHTML = averageLength * distanceScale + " " + unit;
// add listeners
body.querySelectorAll("div.states").forEach(el => el.on("mouseenter", routeHighlightOn));
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-pencil").forEach(el => el.on("click", openRouteEditor));
body.querySelectorAll("div > span.icon-trash-empty").forEach(el => el.on("click", triggerRouteRemove));
applySorting(routesHeader);
}
function getRouteLength(routeId) {
const path = routes.select("#route" + routeId).node();
return rn(path.getTotalLength() / 2, 2);
}
function routeHighlightOn(event) {
if (!layerIsOn("toggleRoutes")) toggleRoutes();
const routeId = +event.target.dataset.id;
routes
.select("#route" + routeId)
.attr("stroke", "red")
.attr("stroke-width", 3)
.attr("stroke-dasharray", "none");
}
function routeHighlightOff(e) {
const routeId = +e.target.dataset.id;
routes
.select("#route" + routeId)
.attr("stroke", null)
.attr("stroke-width", null)
.attr("stroke-dasharray", null);
}
function zoomToRoute() {
const r = +this.parentNode.dataset.id;
const route = routes.select("#route" + r).node();
highlightElement(route, 3);
}
function downloadRoutesData() {
let data = "Id,Route,Group,Length\n"; // headers
body.querySelectorAll(":scope > div").forEach(function (el) {
const d = el.dataset;
const length = rn(d.length * distanceScale) + " " + distanceUnitInput.value;
data += [d.id, d.name, d.group, length].join(",") + "\n";
});
const name = getFileName("Routes") + ".csv";
downloadFile(data, name);
}
function openRouteEditor() {
const id = "route" + this.parentNode.dataset.id;
editRoute(id);
}
function triggerRouteRemove() {
const routeId = +this.parentNode.dataset.id;
alertMessage.innerHTML = /* html */ `Are you sure you want to remove the route? All tributaries will be auto-removed`;
$("#alert").dialog({
resizable: false,
width: "22em",
title: "Remove route",
buttons: {
Remove: function () {
const route = pack.routes.find(r => r.i === routeId);
Routes.remove(route);
routesOverviewAddLines();
$(this).dialog("close");
},
Cancel: function () {
$(this).dialog("close");
}
}
});
}
function triggerAllRoutesRemove() {
alertMessage.innerHTML = /* html */ `Are you sure you want to remove all routes? This action can't be undone`;
$("#alert").dialog({
resizable: false,
title: "Remove all routes",
buttons: {
Remove: function () {
pack.cells.routes = {};
pack.routes = [];
routes.selectAll("path").remove();
$(this).dialog("close");
$("#routesOverview").dialog("close");
},
Cancel: function () {
$(this).dialog("close");
}
}
});
}
}

View file

@ -22,6 +22,7 @@ toolsContent.addEventListener("click", function (event) {
else if (button === "editZonesButton") editZones(); else if (button === "editZonesButton") editZones();
else if (button === "overviewChartsButton") overviewCharts(); else if (button === "overviewChartsButton") overviewCharts();
else if (button === "overviewBurgsButton") overviewBurgs(); else if (button === "overviewBurgsButton") overviewBurgs();
else if (button === "overviewRoutesButton") overviewRoutes();
else if (button === "overviewRiversButton") overviewRivers(); else if (button === "overviewRiversButton") overviewRivers();
else if (button === "overviewMilitaryButton") overviewMilitary(); else if (button === "overviewMilitaryButton") overviewMilitary();
else if (button === "overviewMarkersButton") overviewMarkers(); else if (button === "overviewMarkersButton") overviewMarkers();