feat(map): refine sector inspector styling

This commit is contained in:
Codex
2026-08-06 12:10:26 +03:00
parent d4827009d4
commit e8729401fd
11 changed files with 437 additions and 26 deletions
+27
View File
@@ -538,6 +538,8 @@ const GRID_LOD_PROFILE_KEYS = new Set([
"crossesColor", "crossesOpacity", "graticuleStepDegrees", "graticuleLineWidthPx", "graticuleColor",
"graticuleOpacity", "majorLinesEnabled", "majorLabelsEnabled", "majorLineWidthMultiplier",
"volumeEnabled", "volumeMinimumHeightMeters", "volumeMaximumHeightMeters", "volumeBandHeightMeters",
"selectionFillColor", "selectionFillOpacityPercent", "selectionOutlineColor", "selectionOutlineWidthPx",
"selectionOutlineOpacityPercent",
]);
const GRID_LOD_PROFILE_INPUT_KEYS = new Set([...GRID_LOD_PROFILE_KEYS, "lineWidthPx"]);
@@ -564,6 +566,8 @@ const DEFAULT_GRID_LOD_PROFILES = Object.freeze([
graticuleStepDegrees: 0.25, graticuleLineWidthPx: 1, graticuleColor: "#f5f5f5", graticuleOpacity: 12,
majorLinesEnabled: true, majorLabelsEnabled: true, majorLineWidthMultiplier: 2.5,
volumeEnabled: true, volumeMinimumHeightMeters: 0, volumeMaximumHeightMeters: 300, volumeBandHeightMeters: 300,
selectionFillColor: "#35cfff", selectionFillOpacityPercent: 10,
selectionOutlineColor: "#35cfff", selectionOutlineWidthPx: 6.25, selectionOutlineOpacityPercent: 95,
}),
Object.freeze({
maxHeightKm: 50, stepKm: 5, mode: "3d", heightMeters: 500, max3dViewAngleDegrees: 30,
@@ -573,6 +577,8 @@ const DEFAULT_GRID_LOD_PROFILES = Object.freeze([
graticuleStepDegrees: 0.5, graticuleLineWidthPx: 1, graticuleColor: "#f8fbfc", graticuleOpacity: 12,
majorLinesEnabled: true, majorLabelsEnabled: true, majorLineWidthMultiplier: 2.5,
volumeEnabled: true, volumeMinimumHeightMeters: 0, volumeMaximumHeightMeters: 500, volumeBandHeightMeters: 500,
selectionFillColor: "#35cfff", selectionFillOpacityPercent: 10,
selectionOutlineColor: "#35cfff", selectionOutlineWidthPx: 6.25, selectionOutlineOpacityPercent: 95,
}),
Object.freeze({
maxHeightKm: 200, stepKm: 25, mode: "3d", heightMeters: 500, max3dViewAngleDegrees: 30,
@@ -582,6 +588,8 @@ const DEFAULT_GRID_LOD_PROFILES = Object.freeze([
graticuleStepDegrees: 1, graticuleLineWidthPx: 1, graticuleColor: "#fafafa", graticuleOpacity: 12,
majorLinesEnabled: true, majorLabelsEnabled: false, majorLineWidthMultiplier: 2,
volumeEnabled: false, volumeMinimumHeightMeters: 0, volumeMaximumHeightMeters: 500, volumeBandHeightMeters: 500,
selectionFillColor: "#35cfff", selectionFillOpacityPercent: 16,
selectionOutlineColor: "#35cfff", selectionOutlineWidthPx: 5, selectionOutlineOpacityPercent: 95,
}),
Object.freeze({
maxHeightKm: 800, stepKm: 50, mode: "graticule", heightMeters: 500, max3dViewAngleDegrees: 30,
@@ -591,6 +599,8 @@ const DEFAULT_GRID_LOD_PROFILES = Object.freeze([
graticuleStepDegrees: 2, graticuleLineWidthPx: 1, graticuleColor: "#fcfdfd", graticuleOpacity: 12,
majorLinesEnabled: true, majorLabelsEnabled: false, majorLineWidthMultiplier: 2,
volumeEnabled: false, volumeMinimumHeightMeters: 0, volumeMaximumHeightMeters: 500, volumeBandHeightMeters: 500,
selectionFillColor: "#35cfff", selectionFillOpacityPercent: 12,
selectionOutlineColor: "#35cfff", selectionOutlineWidthPx: 3, selectionOutlineOpacityPercent: 95,
}),
Object.freeze({
maxHeightKm: 3_000, stepKm: 50, mode: "graticule", heightMeters: 500, max3dViewAngleDegrees: 30,
@@ -600,6 +610,8 @@ const DEFAULT_GRID_LOD_PROFILES = Object.freeze([
graticuleStepDegrees: 2, graticuleLineWidthPx: 1, graticuleColor: "#9c9c9c", graticuleOpacity: 8,
majorLinesEnabled: true, majorLabelsEnabled: false, majorLineWidthMultiplier: 2,
volumeEnabled: false, volumeMinimumHeightMeters: 0, volumeMaximumHeightMeters: 500, volumeBandHeightMeters: 500,
selectionFillColor: "#35cfff", selectionFillOpacityPercent: 12,
selectionOutlineColor: "#35cfff", selectionOutlineWidthPx: 3, selectionOutlineOpacityPercent: 95,
}),
]);
@@ -618,6 +630,11 @@ function promoteLegacyGridLodProfiles(settings) {
volumeMinimumHeightMeters: _volumeMinimumHeightMeters,
volumeMaximumHeightMeters: _volumeMaximumHeightMeters,
volumeBandHeightMeters: _volumeBandHeightMeters,
selectionFillColor: _selectionFillColor,
selectionFillOpacityPercent: _selectionFillOpacityPercent,
selectionOutlineColor: _selectionOutlineColor,
selectionOutlineWidthPx: _selectionOutlineWidthPx,
selectionOutlineOpacityPercent: _selectionOutlineOpacityPercent,
...legacyFallback
} = fallback;
return {
@@ -679,6 +696,11 @@ function validateGridLodProfiles(value) {
volumeMinimumHeightMeters: profile.volumeMinimumHeightMeters ?? DEFAULT_GRID_LOD_PROFILES[index].volumeMinimumHeightMeters,
volumeMaximumHeightMeters: profile.volumeMaximumHeightMeters ?? DEFAULT_GRID_LOD_PROFILES[index].volumeMaximumHeightMeters,
volumeBandHeightMeters: profile.volumeBandHeightMeters ?? DEFAULT_GRID_LOD_PROFILES[index].volumeBandHeightMeters,
selectionFillColor: profile.selectionFillColor ?? DEFAULT_GRID_LOD_PROFILES[index].selectionFillColor,
selectionFillOpacityPercent: profile.selectionFillOpacityPercent ?? DEFAULT_GRID_LOD_PROFILES[index].selectionFillOpacityPercent,
selectionOutlineColor: profile.selectionOutlineColor ?? DEFAULT_GRID_LOD_PROFILES[index].selectionOutlineColor,
selectionOutlineWidthPx: profile.selectionOutlineWidthPx ?? DEFAULT_GRID_LOD_PROFILES[index].selectionOutlineWidthPx,
selectionOutlineOpacityPercent: profile.selectionOutlineOpacityPercent ?? DEFAULT_GRID_LOD_PROFILES[index].selectionOutlineOpacityPercent,
};
delete normalized.lineWidthPx;
if (GRID_LOD_PROFILE_KEYS.size !== Object.keys(normalized).length
@@ -771,6 +793,11 @@ function validateGridLodProfiles(value) {
volumeMinimumHeightMeters,
volumeMaximumHeightMeters,
volumeBandHeightMeters,
selectionFillColor: requireHex(normalized.selectionFillColor, code("selectionFillColor")),
selectionFillOpacityPercent: requireNumber(normalized.selectionFillOpacityPercent, 0, 100, code("selectionFillOpacityPercent")),
selectionOutlineColor: requireHex(normalized.selectionOutlineColor, code("selectionOutlineColor")),
selectionOutlineWidthPx: requireNumber(normalized.selectionOutlineWidthPx, 1, 12, code("selectionOutlineWidthPx")),
selectionOutlineOpacityPercent: requireNumber(normalized.selectionOutlineOpacityPercent, 0, 100, code("selectionOutlineOpacityPercent")),
};
});
}
+5
View File
@@ -492,6 +492,11 @@ const mapPageSettingsPatchInputSchema = {
volumeMinimumHeightMeters: { type: "number", minimum: -1000, maximum: 10000 },
volumeMaximumHeightMeters: { type: "number", minimum: -1000, maximum: 10000 },
volumeBandHeightMeters: { type: "number", minimum: 1, maximum: 10000 },
selectionFillColor: { type: "string", pattern: "^#[0-9A-Fa-f]{6}$" },
selectionFillOpacityPercent: { type: "number", minimum: 0, maximum: 100 },
selectionOutlineColor: { type: "string", pattern: "^#[0-9A-Fa-f]{6}$" },
selectionOutlineWidthPx: { type: "number", minimum: 1, maximum: 12 },
selectionOutlineOpacityPercent: { type: "number", minimum: 0, maximum: 100 },
},
},
},
+42
View File
@@ -15,6 +15,11 @@ const extensionKeys = [
"volumeMinimumHeightMeters",
"volumeMaximumHeightMeters",
"volumeBandHeightMeters",
"selectionFillColor",
"selectionFillOpacityPercent",
"selectionOutlineColor",
"selectionOutlineWidthPx",
"selectionOutlineOpacityPercent",
];
function applicationError(code, statusCode = 400) {
@@ -96,3 +101,40 @@ test("flat layouts migrate mode-aware extensions while explicit invalid hierarch
test("Foundry MCP exposes every persisted sector-v2 profile field", () => {
for (const key of extensionKeys) assert.match(mcpSource, new RegExp(`\\b${key}: \\{`));
});
test("server persists selected-sector visuals and accepts a 1,000 m address band inside its range", () => {
const profiles = structuredClone(DEFAULT_GRID_LOD_PROFILES);
Object.assign(profiles[0], {
volumeMaximumHeightMeters: 1_000,
volumeBandHeightMeters: 1_000,
selectionFillColor: "#A0B1C2",
selectionFillOpacityPercent: 34,
selectionOutlineColor: "#C3D4E5",
selectionOutlineWidthPx: 7.5,
selectionOutlineOpacityPercent: 81,
});
const validated = structuredClone(serverGrid.validateGridLodProfiles(profiles));
assert.equal(validated[0].volumeBandHeightMeters, 1_000);
assert.equal(validated[0].selectionFillColor, "#a0b1c2");
assert.equal(validated[0].selectionFillOpacityPercent, 34);
assert.equal(validated[0].selectionOutlineColor, "#c3d4e5");
assert.equal(validated[0].selectionOutlineWidthPx, 7.5);
assert.equal(validated[0].selectionOutlineOpacityPercent, 81);
});
test("server rejects selected-sector visual values outside the canonical bounds", () => {
for (const [field, value] of [
["selectionFillColor", "cyan"],
["selectionFillOpacityPercent", 101],
["selectionOutlineColor", "#ffff"],
["selectionOutlineWidthPx", 12.01],
["selectionOutlineOpacityPercent", -1],
]) {
const profiles = structuredClone(DEFAULT_GRID_LOD_PROFILES);
profiles[0][field] = value;
assert.throws(
() => serverGrid.validateGridLodProfiles(profiles),
new RegExp(`invalid_map_grid_lod_profile_1_${field}`),
);
}
});