Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
7bf33b6
feat: add labels module and integrate into PackedGraph
StempunkDev Feb 10, 2026
dac231f
refactor: clean up label-related code and introduce raycasting utilities
StempunkDev Feb 10, 2026
c467f87
refactor: change exported functions to internal functions in label-ra…
StempunkDev Feb 10, 2026
94b638f
feat: integrate label generation into main flow and enhance label dat…
StempunkDev Feb 11, 2026
689fef0
feat: enhance label editing functionality and improve data model sync…
StempunkDev Feb 13, 2026
2379770
refactor: clean up code formatting and improve timing logic in label …
StempunkDev Feb 13, 2026
471e865
refactor: update import path for findClosestCell utility
StempunkDev Feb 13, 2026
ca6d01f
feat: synchronize label data model with burg name and position updates
StempunkDev Feb 16, 2026
3ab40ad
feat: migrate label data structure from SVG to data model and update …
StempunkDev Feb 17, 2026
6ab2c03
fix: prevent error on rendering removed or neutral states in stateLab…
StempunkDev Feb 17, 2026
861db87
refactor: encapsulate label generation functions within LabelsModule
StempunkDev Feb 17, 2026
0d56479
refactor: update import path for label-raycast and improve state labe…
StempunkDev Feb 17, 2026
c22e6eb
chore: update script version numbers to 1.113.0 in index.html
StempunkDev Feb 17, 2026
32e7049
refactor: replace currentLabelData with direct calls to getLabelData …
StempunkDev Feb 19, 2026
6d99d82
Fix spelling in label-raycast.ts [Copilot]
StempunkDev Feb 19, 2026
6fa3f78
refactor: improve code formatting and organization in labels and rend…
StempunkDev Feb 19, 2026
e174056
refactor: optimize label rendering by building HTML string for batch …
StempunkDev Feb 21, 2026
ba0ce8e
Merge branch 'master' into feature/split-label-view-data
StempunkDev Feb 24, 2026
fd32007
apply format
StempunkDev Feb 24, 2026
3927a76
chore: update version to 1.114.0 and adjust related script references…
StempunkDev Feb 27, 2026
fab495f
refactor: streamline label management by integrating label removal an…
StempunkDev Mar 4, 2026
25824fe
fix: regenerateStateLabels
StempunkDev Mar 4, 2026
5f35924
refactor: update label indexing to use current length for state and c…
StempunkDev Mar 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
2 changes: 2 additions & 0 deletions public/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -650,6 +650,8 @@ async function generate(options) {
Provinces.generate();
Provinces.getPoles();

Labels.generate();
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably should do the same on heightmap edit as well, at least in Erase mode.


Rivers.specify();
Lakes.defineNames();

Expand Down
157 changes: 157 additions & 0 deletions public/modules/dynamic/auto-update.js
Original file line number Diff line number Diff line change
Expand Up @@ -1106,4 +1106,161 @@ export function resolveVersionConflicts(mapVersion) {
}

}

if (isOlderThan("1.114.0")) {
// v1.114.0 moved labels data from SVG to data model
// Migrate old SVG labels to pack.labels structure
if (!pack.labels || !pack.labels.length) {
pack.labels = [];
let labelId = 0;

// Migrate state labels
const stateLabelsGroup = document.querySelector("#labels > #states");
if (stateLabelsGroup) {
stateLabelsGroup.querySelectorAll("text").forEach(textElement => {
const id = textElement.getAttribute("id");
if (!id || !id.startsWith("stateLabel")) return;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks redundant as the check is below


const stateIdMatch = id.match(/stateLabel(\d+)/);
if (!stateIdMatch) return;

const stateId = +stateIdMatch[1];
const state = pack.states[stateId];
if (!state || state.removed) return;

const textPath = textElement.querySelector("textPath");
if (!textPath) return;

const text = textPath.textContent.trim();
const fontSizeAttr = textPath.getAttribute("font-size");
const fontSize = fontSizeAttr ? parseFloat(fontSizeAttr) : 100;

pack.labels.push({
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't we need to save the custom path path as well? Currently it's not migrating manually changed labels.

i: pack.labels.length,
type: "state",
stateId: stateId,
text: text,
fontSize: fontSize
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please reduce the notation

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about other params like startOffset and so on?

});
});
}

// Migrate burg labels
const burgLabelsGroup = document.querySelector("#burgLabels");
if (burgLabelsGroup) {
burgLabelsGroup.querySelectorAll("g").forEach(groupElement => {
const group = groupElement.getAttribute("id");
if (!group) return;

const dxAttr = groupElement.getAttribute("data-dx");
const dyAttr = groupElement.getAttribute("data-dy");
const dx = dxAttr ? parseFloat(dxAttr) : 0;
const dy = dyAttr ? parseFloat(dyAttr) : 0;

groupElement.querySelectorAll("text").forEach(textElement => {
const burgId = +textElement.getAttribute("data-id");
if (!burgId) return;

const burg = pack.burgs[burgId];
if (!burg || burg.removed) return;

const text = textElement.textContent.trim();
// Use burg coordinates, not SVG text coordinates
// SVG coordinates may be affected by viewbox transforms
const x = burg.x;
const y = burg.y;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if use has manually changed to label position? We need to parse transform param and update the position.


pack.labels.push({
i: labelId++,
type: "burg",
burgId: burgId,
group: group,
text: text,
x: x,
y: y,
dx: dx,
dy: dy
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here

});
});
});
}

// Migrate custom labels
const customLabelsGroup = document.querySelector("#labels > #addedLabels");
if (customLabelsGroup) {
customLabelsGroup.querySelectorAll("text").forEach(textElement => {
const id = textElement.getAttribute("id");
if (!id) return;

const group = "custom";
const textPath = textElement.querySelector("textPath");
if (!textPath) return;

const text = textPath.textContent.trim();
const fontSizeAttr = textPath.getAttribute("font-size");
const fontSize = fontSizeAttr ? parseFloat(fontSizeAttr) : 100;
const letterSpacingAttr = textPath.getAttribute("letter-spacing");
const letterSpacing = letterSpacingAttr ? parseFloat(letterSpacingAttr) : 0;
const startOffsetAttr = textPath.getAttribute("startOffset");
const startOffset = startOffsetAttr ? parseFloat(startOffsetAttr) : 50;
const transform = textPath.getAttribute("transform");

// Get path points from the referenced path
const href = textPath.getAttribute("xlink:href") || textPath.getAttribute("href");
if (!href) return;

const pathId = href.replace("#", "");
const pathElement = document.getElementById(pathId);
if (!pathElement) return;

const d = pathElement.getAttribute("d");
if (!d) return;

// Parse path data to extract points(M, L and C commands)
const pathPoints = [];
const commands = d.match(/[MLC][^MLC]*/g);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a pretty weak approach, what if coords are lower case m, l, c for some reason? Instead we can use path.getPathData() or getPointAtLength()

if (commands) {
commands.forEach(cmd => {
const type = cmd[0];
if (type === "M" || type === "L") {
const coords = cmd.slice(1).trim().split(/[\s,]+/).map(Number);
if (coords.length >= 2) {
pathPoints.push([coords[0], coords[1]]);
}
} else if (type === "C") {
const coords = cmd.slice(1).trim().split(/[\s,]+/).map(Number);
if (coords.length >= 6) {
pathPoints.push([coords[4], coords[5]]);
}
}
});
}

if (pathPoints.length > 0) {
pack.labels.push({
i: pack.labels.length,
type: "custom",
group: group,
text: text,
pathPoints: pathPoints,
startOffset: startOffset,
fontSize: fontSize,
letterSpacing: letterSpacing,
transform: transform || undefined
});
}
});
}

// Clear old SVG labels and redraw from data
if (stateLabelsGroup) stateLabelsGroup.querySelectorAll("*").forEach(el => el.remove());
if (burgLabelsGroup) burgLabelsGroup.querySelectorAll("text").forEach(el => el.remove());

// Regenerate labels from data
if (layerIsOn("toggleLabels")) {
drawStateLabels();
drawBurgLabels();
}
}
}
}
3 changes: 2 additions & 1 deletion public/modules/io/load.js
Original file line number Diff line number Diff line change
Expand Up @@ -407,6 +407,7 @@ 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]) : [];
pack.labels = data[40] ? JSON.parse(data[40]) : [];

if (data[31]) {
const namesDL = data[31].split("/");
Expand Down Expand Up @@ -473,7 +474,7 @@ async function parseLoadedData(data, mapVersion) {

{
// dynamically import and run auto-update script
const {resolveVersionConflicts} = await import("../dynamic/auto-update.js?v=1.109.4");
const {resolveVersionConflicts} = await import("../dynamic/auto-update.js?v=1.113.0");
resolveVersionConflicts(mapVersion);
}

Expand Down
4 changes: 3 additions & 1 deletion public/modules/io/save.js
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ function prepareMapData() {
const routes = JSON.stringify(pack.routes);
const zones = JSON.stringify(pack.zones);
const ice = JSON.stringify(pack.ice);
const labels = JSON.stringify(pack.labels || []);

// store name array only if not the same as default
const defaultNB = Names.getNameBases();
Expand Down Expand Up @@ -158,7 +159,8 @@ function prepareMapData() {
cellRoutes,
routes,
zones,
ice
ice,
labels
].join("\r\n");
return mapData;
}
Expand Down
7 changes: 7 additions & 0 deletions public/modules/ui/burg-editor.js
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,9 @@ function editBurg(id) {
const id = +elSelected.attr("data-id");
pack.burgs[id].name = burgName.value;
elSelected.text(burgName.value);
// Sync to Labels data model
const labelData = Labels.getBurgLabel(id);
if (labelData) Labels.updateLabel(labelData.i, {text: burgName.value});
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can Labels.updateLabel find the label element itself? Look overcomplicated to find it first and then update.

}

function generateNameRandom() {
Expand Down Expand Up @@ -382,6 +385,10 @@ function editBurg(id) {
burg.y = y;
if (burg.capital) pack.states[newState].center = burg.cell;

// Sync position to Labels data model
const labelData = Labels.getBurgLabel(id);
if (labelData) Labels.updateLabel(labelData.i, {x, y});

if (d3.event.shiftKey === false) toggleRelocateBurg();
}

Expand Down
Loading