diff --git a/docs/wiki/Changelog.md b/docs/wiki/Changelog.md index 1d32b95d23..15ea3ae6b0 100644 --- a/docs/wiki/Changelog.md +++ b/docs/wiki/Changelog.md @@ -14,6 +14,10 @@ Current version of the Fantasy Map Generator is the latest `master` branch. You # Releases +**1.151.2 - 2026-09-05**: + +- Fix burg label tiers hidden at every zoom on maps migrated from pre-1.140 versions by _[barrulus](https://github.com/barrulus)_ [1.151.2] + **1.151.1 - 2026-09-03**: - Brushes stamp by distance travelled: smooth, gap-free painting at any refresh rate by _[barrulus](https://github.com/barrulus)_ [1.151.1] diff --git a/package-lock.json b/package-lock.json index 197453408b..00db3fa511 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "fantasy-map-generator", - "version": "1.151.1-fork.1", + "version": "1.151.2-fork.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "fantasy-map-generator", - "version": "1.151.1-fork.1", + "version": "1.151.2-fork.1", "license": "MIT", "dependencies": { "alea": "^1.0.1", diff --git a/package.json b/package.json index 6aea77ff24..312983e79e 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "fantasy-map-generator", "productName": "Fantasy Map Generator", - "version": "1.151.1-fork.1", + "version": "1.151.2-fork.1", "description": "Azgaar's _Fantasy Map Generator_ is a free web application that helps fantasy writers, game masters, and cartographers create and edit fantasy maps.", "homepage": "https://github.com/Azgaar/Fantasy-Map-Generator#readme", "bugs": { diff --git a/src/generators/styles-legacy.test.ts b/src/generators/styles-legacy.test.ts index 940107ef42..eb056a9a0c 100644 --- a/src/generators/styles-legacy.test.ts +++ b/src/generators/styles-legacy.test.ts @@ -164,6 +164,19 @@ test("labelGroupFromLegacy prefers a numeric data-size over font-size, stringifi expect(group.attrs["font-size"]).toBe("10"); }); +// pre-1.140 zoom auto-visibility hid a burg tier with an inline display: none, and a map saved while +// zoomed out carries it in the group's style attribute; harvested verbatim it hides the tier forever +test("labelGroupFromLegacy drops the zoom auto-visibility display from the style", () => { + const legacy = { style: "text-shadow: white 0px 0px 4px; display: none;", "data-dx": 0, "data-dy": -0.4 }; + expect(labelGroupFromLegacy(legacy).attrs.style).toBe( + "text-shadow: white 0px 0px 4px; transform: translate(0em, -0.4em)" + ); + expect(labelGroupFromLegacy({ style: "display: none;" }).attrs.style).toBeNull(); + expect(labelGroupFromLegacy({ style: "text-shadow: white 0px 0px 4px" }).attrs.style).toBe( + "text-shadow: white 0px 0px 4px" + ); +}); + const presetDir = path.join(__dirname, "../../public/styles"); test("all 12 shipped presets parse as the new format with zero warnings", () => { diff --git a/src/generators/styles-legacy.ts b/src/generators/styles-legacy.ts index b66e59e4e0..e28b7e759c 100644 --- a/src/generators/styles-legacy.ts +++ b/src/generators/styles-legacy.ts @@ -627,13 +627,17 @@ export function labelGroupFromLegacy(legacy: unknown): Styles["labels"]["groups" } function labelStyleFromLegacy(bag: Record): string | null { - const style = strOr(bag.style, null); const dx = toNumber(bag["data-dx"], 0); const dy = toNumber(bag["data-dy"], 0); - const declarations = style?.trim().replace(/;+$/, "") || ""; const transform = dx || dy ? `transform: translate(${dx}em, ${dy}em)` : ""; - const cssText = [declarations, transform].filter(Boolean).join("; "); - return cssText ?? null; + return stripDisplay([strOr(bag.style, null), transform].filter(Boolean).join("; ")); +} + +// pre-1.140 zoom auto-visibility hid a group with an inline display: none, which a map saved while +// zoomed out carries in the style attribute; it is layer state, not style, and must not be persisted +export function stripDisplay(style: string | null): string | null { + const declarations = (style || "").split(";").map(declaration => declaration.trim()); + return declarations.filter(declaration => declaration && !/^display\s*:/.test(declaration)).join("; ") || null; } // legacy wrote stored burg-group bags to the DOM verbatim with no per-key defaults; only @@ -721,6 +725,7 @@ globalThis.stylesLegacy = { harvestAttributes, stylesFromMap, harvestStylesFromSvg, + stripDisplay, migrateStyles, restoreStrippedLayerStyles, stripMigratedAttributes diff --git a/src/services/io/auto-update.test.ts b/src/services/io/auto-update.test.ts index 0ccb0adff4..15e9ba328b 100644 --- a/src/services/io/auto-update.test.ts +++ b/src/services/io/auto-update.test.ts @@ -371,6 +371,51 @@ describe("v1.140 label group migration", () => { }); // the .map file carries the whole #map svg, so its defs are only what the file was saved with +describe("v1.151.2 label group display cleanup", () => { + // v1.140-1.151 harvested the zoom auto-visibility display: none into the persisted label group + // style, and since v1.150 the store is re-applied over the saved svg, so the record has to be cleaned + function stylesRecord(style: string | null) { + const record = structuredClone(Styles.defaults) as { + labels: { groups: Record }; + }; + record.labels.groups.hamlet = { + ...record.labels.groups.city, + attrs: { ...record.labels.groups.city.attrs, style } + }; + return record; + } + + it("strips display from stored label group styles, keeping the rest of the record", () => { + const data: string[] = []; + data[48] = JSON.stringify( + stylesRecord("text-shadow: white 0px 0px 4px; display: none; transform: translate(0em, -0.4em)") + ); + + resolveVersionConflicts("1.151.1", data); + + const expected = stylesRecord("text-shadow: white 0px 0px 4px; transform: translate(0em, -0.4em)"); + expect(JSON.parse(data[48])).toEqual(expected); + }); + + it("stores null when display was the only declaration", () => { + const data: string[] = []; + data[48] = JSON.stringify(stylesRecord("display: none")); + + resolveVersionConflicts("1.151.1", data); + + expect(JSON.parse(data[48]).labels.groups.hamlet.attrs.style).toBeNull(); + }); + + it("leaves current maps alone", () => { + const data: string[] = []; + data[48] = JSON.stringify(stylesRecord("display: none")); + + resolveVersionConflicts(VERSION, data); + + expect(JSON.parse(data[48]).labels.groups.hamlet.attrs.style).toBe("display: none"); + }); +}); + describe("missing svg defs", () => { const getDeftempIds = () => Array.from(document.querySelectorAll("#deftemp > *"), node => node.id); diff --git a/src/services/io/auto-update.ts b/src/services/io/auto-update.ts index 14771c8582..3872e1671a 100644 --- a/src/services/io/auto-update.ts +++ b/src/services/io/auto-update.ts @@ -8,7 +8,12 @@ import type { GraphOverrides } from "@/generators/graph-override"; import { type Label, type LabelNameMode, Labels as LabelsGenerator } from "@/generators/labels-generator"; import type { Measurer, MeasurerType } from "@/generators/measurers-generator"; -import { labelGroupFromLegacy, migrateStyles, restoreStrippedLayerStyles } from "@/generators/styles-legacy"; +import { + labelGroupFromLegacy, + migrateStyles, + restoreStrippedLayerStyles, + stripDisplay +} from "@/generators/styles-legacy"; import type { Point } from "@/generators/voronoi"; import { getGroupStyle } from "@/renderers/labels/label-groups"; import { unfog } from "@/renderers/overlays/fogging"; @@ -1889,4 +1894,13 @@ export async function resolveVersionConflicts(mapVersion: string, data: string[] // v1.150.0 made the styles store the source of truth data[48] = await migrateStyles(data[48]); } + + if (isOlderThan("1.151.2")) { + // v1.140-1.151 harvested the zoom auto-visibility display: none into the persisted label group + // styles, and the store is applied over the saved svg, so the hidden tiers never came back + const record = data[48] ? safeParseJSON(data[48]) : undefined; + const groups: { attrs?: { style?: string | null } }[] = Object.values(record?.labels?.groups || {}); + for (const group of groups) if (group?.attrs) group.attrs.style = stripDisplay(group.attrs.style ?? null); + if (record) data[48] = JSON.stringify(record); + } } diff --git a/src/services/versioning.ts b/src/services/versioning.ts index fba183c162..df4a3e9554 100644 --- a/src/services/versioning.ts +++ b/src/services/versioning.ts @@ -19,7 +19,7 @@ import { dialogState } from "@/components/dialog/state"; import { tip } from "@/components/tooltips"; import { isElectron } from "./platform"; -export const VERSION = "1.151.1"; +export const VERSION = "1.151.2"; // new changes on top const latestPublicChanges = [