Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
b1151a0
add more testing in preparation of refactors
ndelangen May 6, 2026
80ad549
adding the journey feature v0
ndelangen May 6, 2026
b28e63c
improve visual
ndelangen May 6, 2026
ce10849
fix(load): harden map parsing and presets for older saves
ndelangen May 6, 2026
809db11
add edit feature
ndelangen May 6, 2026
6a0796e
also allow markers and burgs to be points
ndelangen May 6, 2026
c6bc8af
simplify to existing "points" only
ndelangen May 6, 2026
6bf720e
add new campaign layer preset
ndelangen May 6, 2026
0151a42
allow styling of journey
ndelangen May 6, 2026
36de63a
fix initial campaign layer preset not rendering journey
ndelangen May 6, 2026
12e9b9b
cleanup
ndelangen May 7, 2026
1263053
cleanup
ndelangen May 7, 2026
ec8a6b4
Enhance readJourneyStyleConfig to preserve zero values for data attri…
ndelangen May 7, 2026
bedc404
Refactor journey resolution logic to introduce journeyResolvedStopEnt…
ndelangen May 7, 2026
728c6a4
Update journey insertion logic to position journeys below routes inst…
ndelangen May 7, 2026
a9ea014
Implement buildJourneyResolutionContext to enhance journey resolution…
ndelangen May 7, 2026
239541d
Refactor journey handling by introducing ensurePackJourneyNormalized …
ndelangen May 7, 2026
411627b
Refactor journey resolution context usage in JourneyDrawModule and jo…
ndelangen May 7, 2026
cf6d207
Update package versions and dependencies; refactor Playwright tests t…
ndelangen May 7, 2026
38ff8ea
Discard changes to package.json
ndelangen May 7, 2026
7090aad
Discard changes to tests/e2e/burgs.spec.ts
ndelangen May 7, 2026
9728b6a
Discard changes to tests/e2e/layers.spec.ts
ndelangen May 7, 2026
2e52940
Discard changes to tests/e2e/lakes-layer.spec.ts
ndelangen May 7, 2026
eb15e8a
Discard changes to tests/e2e/load-map.spec.ts
ndelangen May 7, 2026
45d5f4b
Discard changes to tests/e2e/states.spec.ts
ndelangen May 7, 2026
10cb526
Discard changes to tests/e2e/zones-export.spec.ts
ndelangen May 7, 2026
e2e51db
Discard changes to package-lock.json
ndelangen May 7, 2026
f9259d3
Refactor journey normalization logic to prioritize `stops[]` over leg…
ndelangen May 8, 2026
17c0809
Merge remote-tracking branch 'upstream/master' into norbert/campaign-…
ndelangen May 8, 2026
9242292
Update package-lock.json and package.json; refactor journey model to …
ndelangen May 8, 2026
fbbbe38
Discard changes to package.json
ndelangen May 8, 2026
f2dcf5a
Discard changes to package-lock.json
ndelangen May 8, 2026
5ead5b5
Refactor journey model tests to remove legacy handling of `stopIds` a…
ndelangen May 8, 2026
94e442f
Refactor journey-draw and journey-model modules to simplify type defi…
ndelangen May 8, 2026
b46d32b
simplify
ndelangen May 8, 2026
3594878
Refactor namesbase-editor and journey modules to utilize escapeHtml f…
ndelangen May 8, 2026
64ba9e6
Add editJourney method to global interface and import ensureEl utilit…
ndelangen May 8, 2026
112d4d5
Enhance type safety in journey module by explicitly defining the type…
ndelangen May 8, 2026
30c115c
allow for many Journeys
ndelangen May 8, 2026
8f59e57
linting & formatting
ndelangen May 8, 2026
86e7a09
delete normalization
ndelangen May 8, 2026
ad13398
linting & formatting
ndelangen May 8, 2026
22d49eb
Merge pull request #1 from ndelangen/norbert/multi-journey
ndelangen May 8, 2026
1be3ceb
improve gradient editing for journeys
ndelangen May 8, 2026
c2cf4da
Merge pull request #2 from ndelangen/norbert/improve-color-picking-jo…
ndelangen May 8, 2026
2ef737f
improve colors gradient display
ndelangen May 8, 2026
b6a6929
reduce commenting of code to adhere to repo standards
ndelangen May 8, 2026
1a21d24
reduce commenting of code to adhere to repo standards
ndelangen May 8, 2026
95d800e
improve arrow positioning
ndelangen May 8, 2026
1a682b5
ensure journey-legs never overlap, even when from different journeys
ndelangen May 8, 2026
58218e4
make naming more consistent
ndelangen May 8, 2026
fec0a79
Merge branch 'master' into norbert/campaign-journey
ndelangen May 8, 2026
5a8ee41
Merge branch 'norbert/campaign-journey' of github.com:ndelangen/Fanta…
ndelangen May 8, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions public/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ let zones = viewbox.append("g").attr("id", "zones");
let borders = viewbox.append("g").attr("id", "borders");
let stateBorders = borders.append("g").attr("id", "stateBorders");
let provinceBorders = borders.append("g").attr("id", "provinceBorders");
let journeys = viewbox.append("g").attr("id", "journeys").style("display", "none");
let routes = viewbox.append("g").attr("id", "routes");
let roads = routes.append("g").attr("id", "roads");
let trails = routes.append("g").attr("id", "trails");
Expand Down Expand Up @@ -585,6 +586,9 @@ function invokeActiveZooming() {
const size = rn((10 / scale ** 0.3) * 2, 2);
ruler.selectAll("text").attr("font-size", size);
}

// journey layer: screen-constant sizing + LOD on tier change
if (layerIsOn("toggleJourney")) syncJourneyZoom(scale);
}

// add drag to upload logic, pull request from @evyatron
Expand Down Expand Up @@ -651,6 +655,7 @@ async function generate(options) {
else delete grid.cells.h;
grid.cells.h = await HeightmapGenerator.generate(grid);
pack = {}; // reset pack
pack.journeys = [];

Features.markupGrid();
addLakesInDeepDepressions();
Expand Down
18 changes: 18 additions & 0 deletions public/modules/io/load.js
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,7 @@ async function parseLoadedData(data, mapVersion) {
anchors = icons.select("#anchors");
armies = viewbox.select("#armies");
markers = viewbox.select("#markers");
journeys = viewbox.select("#journeys");
ruler = viewbox.select("#ruler");
fogging = viewbox.select("#fogging");
debug = viewbox.select("#debug");
Expand All @@ -371,6 +372,9 @@ async function parseLoadedData(data, mapVersion) {
if (!emblems.size()) {
emblems = viewbox.insert("g", "#labels").attr("id", "emblems").style("display", "none");
}
if (!journeys.size()) {
journeys = viewbox.insert("g", "#routes").attr("id", "journeys").style("display", "none");
}
}

{
Expand Down Expand Up @@ -413,6 +417,17 @@ async function parseLoadedData(data, mapVersion) {
// data[28] had deprecated cells.crossroad
pack.cells.routes = data[36] ? JSON.parse(data[36]) : {};
pack.ice = data[39] ? JSON.parse(data[39]) : [];
{
let parsedJourney = null;
if (data[40]) {
try {
parsedJourney = JSON.parse(data[40]);
} catch {
parsedJourney = null;
}
}
pack.journeys = Array.isArray(parsedJourney) ? parsedJourney : [];
}

if (data[31]) {
const namesDL = data[31].split("/");
Expand Down Expand Up @@ -464,6 +479,8 @@ async function parseLoadedData(data, mapVersion) {
if (isVisible(icons)) turnOn("toggleBurgIcons");
if (hasChildren(armies) && isVisible(armies)) turnOn("toggleMilitary");
if (hasChild(markers, "svg")) turnOn("toggleMarkers");
if (isVisible(journeys) && (hasChild(journeys, "path") || hasChild(journeys, "circle")))
turnOn("toggleJourney");
if (isVisible(ruler)) turnOn("toggleRulers");
if (isVisible(scaleBar)) turnOn("toggleScaleBar");
if (isVisibleNode(ensureEl("vignette"))) turnOn("toggleVignette");
Expand Down Expand Up @@ -735,6 +752,7 @@ async function parseLoadedData(data, mapVersion) {
// draw data layers (not kept in svg)
if (rulers && layerIsOn("toggleRulers")) rulers.draw();
if (layerIsOn("toggleGrid")) drawGrid();
if (layerIsOn("toggleJourney")) drawJourney();
}

{
Expand Down
6 changes: 5 additions & 1 deletion public/modules/io/save.js
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,9 @@ function prepareMapData() {
const routes = JSON.stringify(pack.routes);
const zones = JSON.stringify(pack.zones);
const ice = JSON.stringify(pack.ice);
const journey = JSON.stringify(
Array.isArray(pack.journeys) ? pack.journeys : [],
);

// store name array only if not the same as default
const defaultNB = Names.getNameBases();
Expand Down Expand Up @@ -157,7 +160,8 @@ function prepareMapData() {
cellRoutes,
routes,
zones,
ice
ice,
journey
].join("\r\n");
return mapData;
}
Expand Down
1 change: 1 addition & 0 deletions public/modules/ui/hotkeys.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ function handleKeyup(event) {
else if ((shift || altShift) && code === "KeyV") overviewRivers();
else if ((shift || altShift) && code === "KeyM") overviewMilitary();
else if ((shift || altShift) && code === "KeyK") overviewMarkers();
else if ((shift || altShift) && code === "KeyJ") editJourney();
else if ((shift || altShift) && code === "KeyE") viewCellDetails();
else if (key === "!") toggleAddBurg();
else if (key === "@") toggleAddLabel();
Expand Down
46 changes: 46 additions & 0 deletions public/modules/ui/layers.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,11 +59,26 @@ function getDefaultPresets() {
"toggleIce",
"toggleLakes",
"toggleMarkers",
"toggleJourney",
"toggleRivers",
"toggleRoutes",
"toggleScaleBar",
"toggleVignette"
],
journeyPath: [
"toggleHeight",
"toggleLakes",
"toggleCells",
"toggleGrid",
"toggleStates",
"toggleZones",
"toggleBorders",
"toggleJourney",
"toggleRoutes",
"toggleBurgIcons",
"toggleLabels",
"toggleMarkers"
],
military: [
"toggleBorders",
"toggleBurgIcons",
Expand Down Expand Up @@ -206,6 +221,7 @@ function drawLayers() {
if (layerIsOn("toggleProvinces")) drawProvinces();
if (layerIsOn("toggleZones")) drawZones();
if (layerIsOn("toggleBorders")) drawBorders();
if (layerIsOn("toggleJourney")) drawJourney();
if (layerIsOn("toggleRoutes")) drawRoutes();
if (layerIsOn("toggleTemperature")) drawTemperature();
if (layerIsOn("togglePopulation")) drawPopulation();
Expand Down Expand Up @@ -853,6 +869,35 @@ function toggleMarkers(event) {
}
}

function drawJourney() {
TIME && console.time("drawJourney");
const zm = Math.max(+ensureEl("zoomExtentMin").value, 0.01);
JourneyDraw.redraw(defs, journeys, scale, zm);
// Presets only flip layer buttons; they never call toggleJourney/fadeIn. Match visible state to layerIsOn.
if (layerIsOn("toggleJourney")) journeys.style("display", null);
TIME && console.timeEnd("drawJourney");
}

function syncJourneyZoom(zoomScale) {
if (!layerIsOn("toggleJourney")) return;
const jn = journeys.node();
if (!jn || getComputedStyle(jn).display === "none") return;
const zm = Math.max(+ensureEl("zoomExtentMin").value, 0.01);
JourneyDraw.syncZoom(defs, journeys, zoomScale, zm);
}

function toggleJourney(event) {
if (!layerIsOn("toggleJourney")) {
turnButtonOn("toggleJourney");
drawJourney();
if (event && isCtrlClick(event)) editStyle("journeys");
} else {
if (event && isCtrlClick(event)) return editStyle("journeys");
$("#journeys").fadeOut();
turnButtonOff("toggleJourney");
}
}

function toggleLabels(event) {
if (!layerIsOn("toggleLabels")) {
turnButtonOn("toggleLabels");
Expand Down Expand Up @@ -1022,5 +1067,6 @@ function getLayer(id) {
if (id === "toggleLabels") return $("#labels");
if (id === "toggleBurgIcons") return $("#icons");
if (id === "toggleMarkers") return $("#markers");
if (id === "toggleJourney") return $("#journeys");
if (id === "toggleRulers") return $("#ruler");
}
13 changes: 13 additions & 0 deletions public/modules/ui/style-presets.js
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,8 @@ function applyStyleWithUiRefresh(style) {

drawScaleBar(scaleBar, scale);
fitScaleBar(scaleBar, svgWidth, svgHeight);

if (layerIsOn("toggleJourney")) drawJourney();
}

function addStylePreset() {
Expand Down Expand Up @@ -253,6 +255,17 @@ function addStylePreset() {
"filter"
],
"#ice": ["opacity", "fill", "stroke", "stroke-width", "filter"],
"#journeys": [
"opacity",
"filter",
"data-line-screen-px",
"data-waypoint-fill",
"data-waypoint-stroke",
"data-waypoint-r-screen-px",
"data-waypoint-ring-screen-px",
"data-outline-color",
"data-outline-screen-px"
],
"#emblems": ["opacity", "stroke-width", "filter"],
"#emblems > #stateEmblems": ["data-size"],
"#emblems > #provinceEmblems": ["data-size"],
Expand Down
103 changes: 102 additions & 1 deletion public/modules/ui/style.js
Original file line number Diff line number Diff line change
Expand Up @@ -77,14 +77,51 @@ function getColor(value, scheme = getColorScheme("bright")) {
// Toggle style sections on element select
styleElementSelect.on("change", selectStyleElement);

function journeyStyleHexForPicker(raw, fallback) {
const fb = fallback || "#000000";
const s = (raw != null && String(raw).trim() !== "" ? String(raw).trim() : fb);
if (/^#[\da-fA-F]{6}$/.test(s)) return s;
if (/^#[\da-fA-F]{3}$/.test(s)) {
const r = s[1],
g = s[2],
b = s[3];
return `#${r}${r}${g}${g}${b}${b}`;
}
return fb;
}

/** Fallbacks if TS bundle not loaded yet; normally mirror `window.Journey.STYLE_DEFAULTS`. */
function journeyUiDefaults() {
const d = window.Journey && window.Journey.STYLE_DEFAULTS;
return {
gradientFromHex: (d && d.gradientFromHex) || "#e81416",
gradientToHex: (d && d.gradientToHex) || "#70389d",
lineScreenPx: (d && d.lineScreenPx) || 6,
waypointRScreenPx: (d && d.waypointRScreenPx) || 9,
waypointRingScreenPx: (d && d.waypointRingScreenPx) || 4.5,
outlineScreenPx: (d && d.outlineScreenPx) || 2,
solidStroke: (d && d.solidStroke) || "#5c5c70",
waypointFill: (d && d.waypointFill) || "#ffffff",
waypointStroke: (d && d.waypointStroke) || "#000000",
outlineColor: (d && d.outlineColor) || "#000000",
};
}

function redrawJourneyIfVisible() {
if (typeof drawJourney === "function" && layerIsOn("toggleJourney")) drawJourney();
}

function selectStyleElement() {
const styleElement = styleElementSelect.value;
let el = d3.select("#" + styleElement);

styleElements.querySelectorAll("tbody").forEach(e => (e.style.display = "none")); // hide all sections

// show alert line if layer is not visible
const isLayerOff = styleElement !== "ocean" && (el.style("display") === "none" || !el.selectAll("*").size());
const isLayerOff =
styleElement !== "ocean" &&
styleElement !== "journeys" &&
(el.style("display") === "none" || !el.selectAll("*").size());
styleIsOff.style.display = isLayerOff ? "block" : "none";

// active group element
Expand Down Expand Up @@ -203,6 +240,32 @@ function selectStyleElement() {
styleRescaleMarkers.checked = +markers.attr("rescale");
}

if (styleElement === "journeys") {
ensureEl("styleJourney").style.display = "table-row-group";
const j = el;
const jd = journeyUiDefaults();
const numAttr = (name, fb) => {
const v = parseFloat(j.attr(name));
return Number.isFinite(v) ? v : fb;
};
ensureEl("styleJourneyLineScreenPx").value = numAttr("data-line-screen-px", jd.lineScreenPx);
const wpf = journeyStyleHexForPicker(j.attr("data-waypoint-fill"), jd.waypointFill);
ensureEl("styleJourneyWaypointFill").value = wpf;
ensureEl("styleJourneyWaypointFillOutput").value = wpf;
const wps = journeyStyleHexForPicker(j.attr("data-waypoint-stroke"), jd.waypointStroke);
ensureEl("styleJourneyWaypointStroke").value = wps;
ensureEl("styleJourneyWaypointStrokeOutput").value = wps;
ensureEl("styleJourneyWaypointRScreenPx").value = numAttr("data-waypoint-r-screen-px", jd.waypointRScreenPx);
ensureEl("styleJourneyWaypointRingScreenPx").value = numAttr(
"data-waypoint-ring-screen-px",
jd.waypointRingScreenPx,
);
const oc = journeyStyleHexForPicker(j.attr("data-outline-color"), jd.outlineColor);
ensureEl("styleJourneyOutlineColor").value = oc;
ensureEl("styleJourneyOutlineColorOutput").value = oc;
ensureEl("styleJourneyOutlineScreenPx").value = numAttr("data-outline-screen-px", jd.outlineScreenPx);
}

if (styleElement === "gridOverlay") {
styleGrid.style.display = "block";
styleGridType.value = el.attr("type");
Expand Down Expand Up @@ -557,6 +620,44 @@ styleRescaleMarkers.on("change", function () {
invokeActiveZooming();
});

d3.select("#styleJourneyLineScreenPx").on("input", function () {
svg.select("#journeys").attr("data-line-screen-px", this.value);
redrawJourneyIfVisible();
});

d3.select("#styleJourneyWaypointFill").on("input", function () {
ensureEl("styleJourneyWaypointFillOutput").value = this.value;
svg.select("#journeys").attr("data-waypoint-fill", this.value);
redrawJourneyIfVisible();
});

d3.select("#styleJourneyWaypointStroke").on("input", function () {
ensureEl("styleJourneyWaypointStrokeOutput").value = this.value;
svg.select("#journeys").attr("data-waypoint-stroke", this.value);
redrawJourneyIfVisible();
});

d3.select("#styleJourneyWaypointRScreenPx").on("input", function () {
svg.select("#journeys").attr("data-waypoint-r-screen-px", this.value);
redrawJourneyIfVisible();
});

d3.select("#styleJourneyWaypointRingScreenPx").on("input", function () {
svg.select("#journeys").attr("data-waypoint-ring-screen-px", this.value);
redrawJourneyIfVisible();
});

d3.select("#styleJourneyOutlineColor").on("input", function () {
ensureEl("styleJourneyOutlineColorOutput").value = this.value;
svg.select("#journeys").attr("data-outline-color", this.value);
redrawJourneyIfVisible();
});

d3.select("#styleJourneyOutlineScreenPx").on("input", function () {
svg.select("#journeys").attr("data-outline-screen-px", this.value);
redrawJourneyIfVisible();
});

styleCoastlineAuto.on("change", function () {
coastline.select("#sea_island").attr("auto-filter", +this.checked);
styleFilter.style.display = this.checked ? "none" : "block";
Expand Down
1 change: 1 addition & 0 deletions public/modules/ui/tools.js
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ toolsContent.addEventListener("click", function (event) {
else if (button === "addRiver") toggleAddRiver();
else if (button === "addRoute") createRoute();
else if (button === "addMarker") toggleAddMarker();
else if (button === "addJourney") editJourney();
// click to create a new map buttons
else if (button === "openSubmapTool") openSubmapTool();
else if (button === "openTransformTool") openTransformTool();
Expand Down
14 changes: 14 additions & 0 deletions public/styles/ancient.json
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,20 @@
"rescale": 1,
"filter": null
},
"#journeys": {
"opacity": null,
"filter": null,
"data-color-mode": "rainbow",
"data-solid-stroke": "#5c5c70",
"data-rainbow-stops": null,
"data-line-screen-px": 6,
"data-waypoint-fill": "#ffffff",
"data-waypoint-stroke": "#000000",
"data-waypoint-r-screen-px": 9,
"data-waypoint-ring-screen-px": 4.5,
"data-outline-color": "#000000",
"data-outline-screen-px": 2
},
"#prec": {
"opacity": null,
"stroke": "#000000",
Expand Down
14 changes: 14 additions & 0 deletions public/styles/atlas.json
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,20 @@
"rescale": 1,
"filter": null
},
"#journeys": {
"opacity": null,
"filter": null,
"data-color-mode": "rainbow",
"data-solid-stroke": "#5c5c70",
"data-rainbow-stops": null,
"data-line-screen-px": 6,
"data-waypoint-fill": "#ffffff",
"data-waypoint-stroke": "#000000",
"data-waypoint-r-screen-px": 9,
"data-waypoint-ring-screen-px": 4.5,
"data-outline-color": "#000000",
"data-outline-screen-px": 2
},
"#prec": {
"opacity": null,
"stroke": "#000000",
Expand Down
Loading