From c565ac0637bc720cc0ada3eaffb3e6b285367e5a Mon Sep 17 00:00:00 2001 From: linkong Date: Tue, 31 Mar 2026 14:07:56 +0800 Subject: [PATCH] fix: tighten earth hud interactions --- VERSION | 2 +- docs/CHANGELOG.md | 20 +++ frontend/package.json | 2 +- frontend/public/earth/css/base.css | 8 ++ frontend/public/earth/css/info-panel.css | 3 + frontend/public/earth/js/controls.js | 157 ++++++++++++++++------- pyproject.toml | 2 +- 7 files changed, 145 insertions(+), 49 deletions(-) diff --git a/VERSION b/VERSION index 21574090..a723ece7 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.22.0 +0.22.1 diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index e717f771..5e49d0ec 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -300,6 +300,26 @@ Released: 2026-03-25 - Fixed several collected-data field mapping issues. - Fixed frontend table layout inconsistencies across multiple admin pages. - Fixed TOP500 parsing and related metadata alignment issues. +## 0.22.1 + +Released: 2026-03-31 + +### Highlights + +- Refined the Earth HUD interaction model so panels and floating menus no longer leak pointer state into the globe runtime. +- Normalized `Escape` handling across hover menus, click-open menus, and locked scene selections. + +### Improved + +- Improved floating toolbar behavior so `Escape` now closes visible secondary menus before clearing locked scene objects. +- Improved toolbar hover recovery after forced close, avoiding double-hover reopening glitches. +- Improved control-module maintainability by deduplicating tooltip, menu, and hide-selection helpers. + +### Fixed + +- Fixed HUD overlap cases where hovering panels could still leave stale scene hover state active. +- Fixed info card pointer blocking so panel regions consistently shield underlying globe interactions. + ## 0.22.0 Released: 2026-03-31 diff --git a/frontend/package.json b/frontend/package.json index e367f6e3..166c1df8 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,6 +1,6 @@ { "name": "planet-frontend", - "version": "0.22.0", + "version": "0.22.1", "private": true, "dependencies": { "@ant-design/icons": "^5.2.6", diff --git a/frontend/public/earth/css/base.css b/frontend/public/earth/css/base.css index aca47c74..d7160d58 100644 --- a/frontend/public/earth/css/base.css +++ b/frontend/public/earth/css/base.css @@ -635,6 +635,14 @@ input[type="range"]::-webkit-slider-thumb { transform: translate(-50%, 0); } +#zoom-control-group.force-closed #zoom-toolbar, +#info-control-group.force-closed #info-toolbar { + opacity: 0; + visibility: hidden; + pointer-events: none; + transform: translate(-50%, 8px); +} + #zoom-control-group #zoom-toolbar .zoom-percent { min-width: 0; width: 42px; diff --git a/frontend/public/earth/css/info-panel.css b/frontend/public/earth/css/info-panel.css index 7184fff9..203e53ab 100644 --- a/frontend/public/earth/css/info-panel.css +++ b/frontend/public/earth/css/info-panel.css @@ -184,6 +184,7 @@ 0 10px 24px rgba(0, 0, 0, 0.16); padding: 0; overflow: hidden; + pointer-events: auto; } .info-card.no-border { @@ -216,6 +217,7 @@ overflow-y: auto; scrollbar-width: thin; scrollbar-color: rgba(160, 220, 255, 0.45) transparent; + pointer-events: auto; } #info-card-content::-webkit-scrollbar { @@ -240,6 +242,7 @@ justify-content: space-between; padding: 6px; border-bottom: 1px solid rgba(255, 255, 255, 0.05); + pointer-events: auto; } .info-card-property:last-child { diff --git a/frontend/public/earth/js/controls.js b/frontend/public/earth/js/controls.js index f5eee2f6..ea07bf13 100644 --- a/frontend/public/earth/js/controls.js +++ b/frontend/public/earth/js/controls.js @@ -6,6 +6,7 @@ import { toggleTerrain } from "./earth.js"; import { reloadData, clearLockedObject, + clearLockedObjectAndInfo, setCablesEnabled, setSatellitesEnabled, } from "./main.js"; @@ -20,13 +21,85 @@ import { toggleBGP, getShowBGP, getBGPCount } from "./bgp.js"; export let autoRotate = true; export let zoomLevel = 1.0; export let showTerrain = false; -export let isDragging = false; export let layoutExpanded = false; let earthObj = null; let listeners = []; let cleanupFns = []; +function getFloatingGroups() { + return [ + document.getElementById("zoom-control-group"), + document.getElementById("info-control-group"), + ].filter(Boolean); +} + +function isFloatingMenuVisible() { + return getFloatingGroups().some((group) => { + return ( + group.classList.contains("open") || + group.matches(":hover") || + group.matches(":focus-within") + ); + }); +} + +function closeFloatingMenus() { + getFloatingGroups().forEach((group) => { + group.classList.remove("open"); + group.classList.add("force-closed"); + }); + + if (document.activeElement instanceof HTMLElement) { + document.activeElement.blur(); + } +} + +function clearForcedFloatingClose() { + getFloatingGroups().forEach((group) => { + group.classList.remove("force-closed"); + }); +} + +function clearForcedFloatingCloseIfPointerOutside() { + getFloatingGroups().forEach((group) => { + if (!group.matches(":hover")) { + group.classList.remove("force-closed"); + } + }); +} + +function setFloatingMenuOpen(group, shouldOpen) { + closeFloatingMenus(); + clearForcedFloatingClose(); + group?.classList.toggle("open", shouldOpen); +} + +function setButtonTooltip(button, text) { + const tooltip = button?.querySelector(".tooltip"); + if (tooltip) { + tooltip.textContent = text; + } +} + +function clearSelectionIfHiding(shouldHide) { + if (shouldHide) { + clearLockedObject(); + } +} + +function bindFloatingMenu(trigger, group) { + bindListener(trigger, "click", (event) => { + event.stopPropagation(); + const shouldOpen = !group?.classList.contains("open"); + setFloatingMenuOpen(group, shouldOpen); + }); + + bindListener(group, "click", (event) => { + event.stopPropagation(); + }); +} + function bindListener(element, eventName, handler, options) { if (!element) return; element.addEventListener(eventName, handler, options); @@ -50,6 +123,7 @@ export function setupControls(camera, renderer, scene, earth) { setupRotateControls(camera, earth); setupTerrainControls(); setupLiquidGlassInteractions(); + setupKeyboardControls(); } function setupZoomControls(camera) { @@ -308,8 +382,7 @@ function setupTerrainControls() { if (trailsBtn) { trailsBtn.classList.add("active"); - const tooltip = trailsBtn.querySelector(".tooltip"); - if (tooltip) tooltip.textContent = "隐藏轨迹"; + setButtonTooltip(trailsBtn, "隐藏轨迹"); } bindListener(searchBtn, "click", () => { @@ -320,8 +393,7 @@ function setupTerrainControls() { showTerrain = !showTerrain; toggleTerrain(showTerrain); this.classList.toggle("active", showTerrain); - const tooltip = this.querySelector(".tooltip"); - if (tooltip) tooltip.textContent = showTerrain ? "隐藏地形" : "显示地形"; + setButtonTooltip(this, showTerrain ? "隐藏地形" : "显示地形"); const terrainStatus = document.getElementById("terrain-status"); if (terrainStatus) terrainStatus.textContent = showTerrain ? "开启" : "关闭"; @@ -330,9 +402,7 @@ function setupTerrainControls() { bindListener(satellitesBtn, "click", async function () { const showSats = !getShowSatellites(); - if (!showSats) { - clearLockedObject(); - } + clearSelectionIfHiding(!showSats); try { await setSatellitesEnabled(showSats); if (!showSats) { @@ -350,13 +420,10 @@ function setupTerrainControls() { bindListener(bgpBtn, "click", function () { const showNextBGP = !getShowBGP(); - if (!showNextBGP) { - clearLockedObject(); - } + clearSelectionIfHiding(!showNextBGP); toggleBGP(showNextBGP); this.classList.toggle("active", showNextBGP); - const tooltip = this.querySelector(".tooltip"); - if (tooltip) tooltip.textContent = showNextBGP ? "隐藏BGP观测" : "显示BGP观测"; + setButtonTooltip(this, showNextBGP ? "隐藏BGP观测" : "显示BGP观测"); const bgpCountEl = document.getElementById("bgp-anomaly-count"); if (bgpCountEl) { bgpCountEl.textContent = `${getBGPCount()} 条`; @@ -369,16 +436,13 @@ function setupTerrainControls() { const nextShowTrails = !isActive; toggleTrails(nextShowTrails); this.classList.toggle("active", nextShowTrails); - const tooltip = this.querySelector(".tooltip"); - if (tooltip) tooltip.textContent = nextShowTrails ? "隐藏轨迹" : "显示轨迹"; + setButtonTooltip(this, nextShowTrails ? "隐藏轨迹" : "显示轨迹"); showStatusMessage(nextShowTrails ? "轨迹已显示" : "轨迹已隐藏", "info"); }); bindListener(cablesBtn, "click", async function () { const showNextCables = !getShowCables(); - if (!showNextCables) { - clearLockedObject(); - } + clearSelectionIfHiding(!showNextCables); try { await setCablesEnabled(showNextCables); } catch (error) { @@ -390,39 +454,27 @@ function setupTerrainControls() { await reloadData(); }); - bindListener(zoomTrigger, "click", (event) => { - event.stopPropagation(); - infoGroup?.classList.remove("open"); - zoomGroup?.classList.toggle("open"); - }); - - bindListener(zoomGroup, "click", (event) => { - event.stopPropagation(); - }); - - bindListener(infoTrigger, "click", (event) => { - event.stopPropagation(); - zoomGroup?.classList.remove("open"); - infoGroup?.classList.toggle("open"); - }); - - bindListener(infoGroup, "click", (event) => { - event.stopPropagation(); - }); + bindFloatingMenu(zoomTrigger, zoomGroup); + bindFloatingMenu(infoTrigger, infoGroup); bindListener(document, "click", (event) => { - if (zoomGroup?.classList.contains("open")) { - if (!zoomGroup.contains(event.target)) { - zoomGroup.classList.remove("open"); - } - } - if (infoGroup?.classList.contains("open")) { - if (!infoGroup.contains(event.target)) { - infoGroup.classList.remove("open"); - } + const openGroups = [zoomGroup, infoGroup].filter((group) => + group?.classList.contains("open"), + ); + if (openGroups.length === 0) return; + + const clickedInsideOpenGroup = openGroups.some((group) => + group.contains(event.target), + ); + if (!clickedInsideOpenGroup) { + closeFloatingMenus(); } }); + bindListener(document, "mousemove", () => { + clearForcedFloatingCloseIfPointerOutside(); + }); + bindListener(layoutBtn, "click", () => { const expanded = toggleLayoutExpanded(container); showStatusMessage(expanded ? "布局已最大化" : "布局已恢复", "info"); @@ -431,6 +483,19 @@ function setupTerrainControls() { updateLayoutUI(container); } +function setupKeyboardControls() { + bindListener(document, "keydown", (event) => { + if (event.key !== "Escape") return; + + if (isFloatingMenuVisible()) { + closeFloatingMenus(); + return; + } + + clearLockedObjectAndInfo(); + }); +} + function setupLiquidGlassInteractions() { const surfaces = document.querySelectorAll(".liquid-glass-surface"); diff --git a/pyproject.toml b/pyproject.toml index 32404991..f58857e3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "planet" -version = "0.22.0" +version = "0.22.1" description = "智能星球计划 - 态势感知系统" requires-python = ">=3.14" dependencies = [