Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 4 additions & 0 deletions docs/wiki/Changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -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": {
Expand Down
13 changes: 13 additions & 0 deletions src/generators/styles-legacy.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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", () => {
Expand Down
13 changes: 9 additions & 4 deletions src/generators/styles-legacy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -627,13 +627,17 @@ export function labelGroupFromLegacy(legacy: unknown): Styles["labels"]["groups"
}

function labelStyleFromLegacy(bag: Record<string, unknown>): 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
Expand Down Expand Up @@ -721,6 +725,7 @@ globalThis.stylesLegacy = {
harvestAttributes,
stylesFromMap,
harvestStylesFromSvg,
stripDisplay,
migrateStyles,
restoreStrippedLayerStyles,
stripMigratedAttributes
Expand Down
45 changes: 45 additions & 0 deletions src/services/io/auto-update.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, { attrs: { style: string | null } }> };
};
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);

Expand Down
16 changes: 15 additions & 1 deletion src/services/io/auto-update.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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);
}
}
2 changes: 1 addition & 1 deletion src/services/versioning.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 = [
Expand Down
Loading