feat: add zone layout selector

This commit is contained in:
2026-05-25 03:17:23 +02:00
parent 558e0dc157
commit f09f110e77
5 changed files with 111 additions and 5 deletions

View File

@@ -455,6 +455,12 @@ def graph_explorer_page() -> str:
<option value="accessZone">Access Zone</option>
</select>
</label>
<label class="map-control-field"><span class="field-label">Zone Layout <button type="button" class="help-tip" aria-label="Zone layout help" data-help-title="Zone Layout" data-help="Zone layout changes how each zone arranges its assigned visible nodes inside the stable zone rectangle. It does not change the underlying Fabric graph.">?</button></span>
<select id="zone-layout-select" title="Choose zone-local layout algorithm">
<option value="compact-grid">Grid</option>
<option value="circle">Circle</option>
</select>
</label>
<label class="map-control-field"><span class="field-label">Labels <button type="button" class="help-tip" aria-label="Labels help" data-help-title="Labels" data-help="Label density changes only text visibility. Auto hides low-priority labels when the map is dense; Key keeps repositories, services, deployments, servers, and issue markers visible.">?</button></span>
<select id="label-select" title="Control node label density">
<option value="auto">Auto</option>
@@ -570,6 +576,7 @@ def graph_explorer_page() -> str:
const labelSelect = document.getElementById("label-select");
const zoneBoundaryToggle = document.getElementById("zone-boundary-toggle");
const zoneGroupSelect = document.getElementById("zone-group-select");
const zoneLayoutSelect = document.getElementById("zone-layout-select");
const nodeTypeFilter = document.getElementById("node-type-filter");
const nodeTypeSummary = document.getElementById("node-type-summary");
const edgeTypeFilter = document.getElementById("edge-type-filter");
@@ -610,6 +617,7 @@ def graph_explorer_page() -> str:
let activeLabelMode = "auto";
let activeZoneGrouping = "deploymentEnvironment";
let activeZoneDefinitionSet = "fabric-default";
let activeZoneLayoutAlgorithm = "compact-grid";
let profilePersistence = "none";
let profiles = [];
let currentProfileId = "";
@@ -1062,6 +1070,11 @@ def graph_explorer_page() -> str:
},
};
const zoneLayoutAlgorithms = new Set(["compact-grid", "circle"]);
const normalizeZoneLayoutAlgorithm = (value) =>
zoneLayoutAlgorithms.has(String(value || "")) ? String(value) : "compact-grid";
const zoneElementData = (element) => {
if (!element) return {};
if (typeof element.data === "function") return element.data();
@@ -1495,7 +1508,7 @@ def graph_explorer_page() -> str:
.slice()
.sort((left, right) => zoneNodeSortKey(left).localeCompare(zoneNodeSortKey(right)));
if (!nodes.length || !container) return;
const algorithm = String(zone.layout?.algorithm || "compact-grid");
const algorithm = normalizeZoneLayoutAlgorithm(activeZoneLayoutAlgorithm || zone.layout?.algorithm);
if (algorithm === "circle") {
layoutZoneNodesInCircle(nodes, container);
return;
@@ -2031,6 +2044,9 @@ def graph_explorer_page() -> str:
const currentZoneViewState = () => {
const visible = zoneBoundaryToggle ? zoneBoundaryToggle.checked : true;
const grouping = zoneGroupSelect ? zoneGroupSelect.value || "deploymentEnvironment" : "deploymentEnvironment";
const layoutAlgorithm = normalizeZoneLayoutAlgorithm(
zoneLayoutSelect ? zoneLayoutSelect.value : activeZoneLayoutAlgorithm
);
const definitionSet = zoneDefinitionSets[activeZoneDefinitionSet]
? activeZoneDefinitionSet
: "fabric-default";
@@ -2038,6 +2054,9 @@ def graph_explorer_page() -> str:
visible,
grouping,
definitionSet,
layout: {
algorithm: layoutAlgorithm,
},
presentation: {
boundaries: visible,
labels: true,
@@ -2061,6 +2080,7 @@ def graph_explorer_page() -> str:
zoneBoundaries: zone.visible,
zoneGrouping: zone.grouping,
zoneDefinitionSet: zone.definitionSet,
zoneLayoutAlgorithm: zone.layout.algorithm,
rules: filterRules.map((rule) => ({...rule})),
manualOverrides: {...manualOverrides},
};
@@ -2093,6 +2113,7 @@ def graph_explorer_page() -> str:
if (params.has("zoneBoundaries")) state.zoneBoundaries = params.get("zoneBoundaries") !== "0";
if (params.has("zoneGrouping")) state.zoneGrouping = params.get("zoneGrouping") || "";
if (params.has("zoneDefinitionSet")) state.zoneDefinitionSet = params.get("zoneDefinitionSet") || "";
if (params.has("zoneLayout")) state.zoneLayoutAlgorithm = params.get("zoneLayout") || "";
if (params.has("profile")) state.profile = params.get("profile") || "";
if (params.has("state")) {
try {
@@ -2118,6 +2139,7 @@ def graph_explorer_page() -> str:
if (state.zoneBoundaries === false) params.set("zoneBoundaries", "0");
if (state.zoneGrouping && state.zoneGrouping !== "deploymentEnvironment") params.set("zoneGrouping", state.zoneGrouping);
if (state.zoneDefinitionSet && state.zoneDefinitionSet !== "fabric-default") params.set("zoneDefinitionSet", state.zoneDefinitionSet);
if (state.zoneLayoutAlgorithm && state.zoneLayoutAlgorithm !== "compact-grid") params.set("zoneLayout", state.zoneLayoutAlgorithm);
if (currentProfileId) params.set("profile", currentProfileId);
if (includeStateBlob || hasManualOverrides() || filterRules.length > 0) {
params.set("state", encodeStateBlob(state));
@@ -2142,6 +2164,7 @@ def graph_explorer_page() -> str:
const containers = nested.containers && typeof nested.containers === "object"
? nested.containers
: state.zoneContainers;
const layout = nested.layout && typeof nested.layout === "object" ? nested.layout : {};
const grouping = nested.grouping || state.zoneGrouping || "deploymentEnvironment";
const definitionSet = zoneDefinitionSets[nested.definitionSet || state.zoneDefinitionSet]
? nested.definitionSet || state.zoneDefinitionSet
@@ -2155,6 +2178,9 @@ def graph_explorer_page() -> str:
visible,
grouping: zoneGroupSelect && optionExists(zoneGroupSelect, grouping) ? grouping : "deploymentEnvironment",
definitionSet,
layout: {
algorithm: normalizeZoneLayoutAlgorithm(layout.algorithm || state.zoneLayoutAlgorithm),
},
presentation: {
boundaries: "boundaries" in presentation ? presentation.boundaries !== false : visible,
labels: "labels" in presentation ? presentation.labels !== false : true,
@@ -2173,11 +2199,13 @@ def graph_explorer_page() -> str:
if ("edgeTypes" in state) setCheckedValues(edgeTypeFilter, (state.edgeTypes || []).filter((value) => allEdgeTypes.includes(value)));
if ("review" in state) reviewFilter.value = optionExists(reviewFilter, state.review) ? state.review : "";
if ("unresolved" in state) unresolvedFilter.value = optionExists(unresolvedFilter, state.unresolved) ? state.unresolved : "";
if ("zone" in state || "zoneBoundaries" in state || "zoneGrouping" in state || "zoneDefinitionSet" in state) {
if ("zone" in state || "zoneBoundaries" in state || "zoneGrouping" in state || "zoneDefinitionSet" in state || "zoneLayoutAlgorithm" in state) {
const zone = normalizeZoneViewState(state);
activeZoneDefinitionSet = zone.definitionSet;
if (zoneBoundaryToggle) zoneBoundaryToggle.checked = zone.visible;
if (zoneGroupSelect) zoneGroupSelect.value = zone.grouping;
activeZoneLayoutAlgorithm = zone.layout.algorithm;
if (zoneLayoutSelect) zoneLayoutSelect.value = activeZoneLayoutAlgorithm;
zoneContainerState = zone.containers;
}
if ("manualOverrides" in state && state.manualOverrides && typeof state.manualOverrides === "object") {
@@ -2192,6 +2220,9 @@ def graph_explorer_page() -> str:
activeMode = modeSelect.value || "full";
activeLabelMode = labelSelect.value || "auto";
activeZoneGrouping = zoneGroupSelect ? zoneGroupSelect.value || "deploymentEnvironment" : "deploymentEnvironment";
activeZoneLayoutAlgorithm = normalizeZoneLayoutAlgorithm(
zoneLayoutSelect ? zoneLayoutSelect.value : activeZoneLayoutAlgorithm
);
selectedZoneId = "";
collapsedZoneSnapshots = new Map();
focusSet = null;
@@ -2233,6 +2264,9 @@ def graph_explorer_page() -> str:
if (zoneGroupSelect && (zoneGroupSelect.value || "deploymentEnvironment") !== "deploymentEnvironment") {
parts.push("access zone boundaries");
}
if (zoneLayoutSelect && (zoneLayoutSelect.value || "compact-grid") !== "compact-grid") {
parts.push(`${zoneLayoutSelect.options[zoneLayoutSelect.selectedIndex]?.textContent || zoneLayoutSelect.value} zone layout`);
}
if (filterRules.length) parts.push(`${filterRules.length} rule${filterRules.length === 1 ? "" : "s"}`);
const overrideCount = Object.keys(manualOverrides).length;
if (overrideCount) parts.push(`${overrideCount} override${overrideCount === 1 ? "" : "s"}`);
@@ -2968,6 +3002,18 @@ def graph_explorer_page() -> str:
updateProfileSummary();
updateUrlState();
});
zoneLayoutSelect.addEventListener("input", () => {
activeZoneLayoutAlgorithm = normalizeZoneLayoutAlgorithm(zoneLayoutSelect.value);
currentProfileId = "";
profileSelect.value = "";
applyZoneContainerLayout();
fitVisibleGraph();
renderZoneOverlay();
if (!selected) renderMapOverview();
updateProfileControls();
updateProfileSummary();
updateUrlState();
});
zoneOverlay.addEventListener("click", (event) => {
if (suppressZoneClick) {
suppressZoneClick = false;
@@ -3089,6 +3135,8 @@ def graph_explorer_page() -> str:
zoneBoundaryToggle.checked = true;
zoneGroupSelect.value = "deploymentEnvironment";
activeZoneGrouping = "deploymentEnvironment";
zoneLayoutSelect.value = "compact-grid";
activeZoneLayoutAlgorithm = "compact-grid";
activeZoneDefinitionSet = "fabric-default";
selectedZoneId = "";
collapsedZoneSnapshots = new Map();