From 3ab40ada5f0abb40d6343706770e41dad434a0f1 Mon Sep 17 00:00:00 2001 From: StempunkDev Date: Tue, 17 Feb 2026 01:45:48 +0100 Subject: [PATCH] feat: migrate label data structure from SVG to data model and update version to 1.113.0 --- public/modules/dynamic/auto-update.js | 152 ++++++++++++++++++++++++++ public/modules/io/load.js | 3 +- public/modules/io/save.js | 4 +- public/versioning.js | 2 +- 4 files changed, 158 insertions(+), 3 deletions(-) diff --git a/public/modules/dynamic/auto-update.js b/public/modules/dynamic/auto-update.js index 0b1cd227..e255930d 100644 --- a/public/modules/dynamic/auto-update.js +++ b/public/modules/dynamic/auto-update.js @@ -1106,4 +1106,156 @@ export function resolveVersionConflicts(mapVersion) { } } + + if (isOlderThan("1.113.0")) { + // v1.113.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; + + 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({ + i: labelId++, + type: "state", + stateId: stateId, + text: text, + fontSize: fontSize + }); + }); + } + + // 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; + + pack.labels.push({ + i: labelId++, + type: "burg", + burgId: burgId, + group: group, + text: text, + x: x, + y: y, + dx: dx, + dy: dy + }); + }); + }); + } + + // 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("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 (simplified - assumes M and L commands) + const pathPoints = []; + const commands = d.match(/[MLZ][^MLZ]*/g); + 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]]); + } + } + }); + } + + if (pathPoints.length > 0) { + pack.labels.push({ + i: labelId++, + 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(); + } + } + } } diff --git a/public/modules/io/load.js b/public/modules/io/load.js index 9b401733..441e25fe 100644 --- a/public/modules/io/load.js +++ b/public/modules/io/load.js @@ -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("/"); @@ -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); } diff --git a/public/modules/io/save.js b/public/modules/io/save.js index 25cd7493..4d6c40ea 100644 --- a/public/modules/io/save.js +++ b/public/modules/io/save.js @@ -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(); @@ -158,7 +159,8 @@ function prepareMapData() { cellRoutes, routes, zones, - ice + ice, + labels ].join("\r\n"); return mapData; } diff --git a/public/versioning.js b/public/versioning.js index fd2a67a2..07993ff8 100644 --- a/public/versioning.js +++ b/public/versioning.js @@ -13,7 +13,7 @@ * Example: 1.102.2 -> Major version 1, Minor version 102, Patch version 2 */ -const VERSION = "1.112.1"; +const VERSION = "1.113.0"; if (parseMapVersion(VERSION) !== VERSION) alert("versioning.js: Invalid format or parsing function"); {