fix: tighten earth hud interactions
This commit is contained in:
@@ -300,6 +300,26 @@ Released: 2026-03-25
|
|||||||
- Fixed several collected-data field mapping issues.
|
- Fixed several collected-data field mapping issues.
|
||||||
- Fixed frontend table layout inconsistencies across multiple admin pages.
|
- Fixed frontend table layout inconsistencies across multiple admin pages.
|
||||||
- Fixed TOP500 parsing and related metadata alignment issues.
|
- 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
|
## 0.22.0
|
||||||
|
|
||||||
Released: 2026-03-31
|
Released: 2026-03-31
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "planet-frontend",
|
"name": "planet-frontend",
|
||||||
"version": "0.22.0",
|
"version": "0.22.1",
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ant-design/icons": "^5.2.6",
|
"@ant-design/icons": "^5.2.6",
|
||||||
|
|||||||
@@ -635,6 +635,14 @@ input[type="range"]::-webkit-slider-thumb {
|
|||||||
transform: translate(-50%, 0);
|
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 {
|
#zoom-control-group #zoom-toolbar .zoom-percent {
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
width: 42px;
|
width: 42px;
|
||||||
|
|||||||
@@ -184,6 +184,7 @@
|
|||||||
0 10px 24px rgba(0, 0, 0, 0.16);
|
0 10px 24px rgba(0, 0, 0, 0.16);
|
||||||
padding: 0;
|
padding: 0;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
pointer-events: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.info-card.no-border {
|
.info-card.no-border {
|
||||||
@@ -216,6 +217,7 @@
|
|||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
scrollbar-width: thin;
|
scrollbar-width: thin;
|
||||||
scrollbar-color: rgba(160, 220, 255, 0.45) transparent;
|
scrollbar-color: rgba(160, 220, 255, 0.45) transparent;
|
||||||
|
pointer-events: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
#info-card-content::-webkit-scrollbar {
|
#info-card-content::-webkit-scrollbar {
|
||||||
@@ -240,6 +242,7 @@
|
|||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
padding: 6px;
|
padding: 6px;
|
||||||
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
|
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
|
||||||
|
pointer-events: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.info-card-property:last-child {
|
.info-card-property:last-child {
|
||||||
|
|||||||
157
frontend/public/earth/js/controls.js
vendored
157
frontend/public/earth/js/controls.js
vendored
@@ -6,6 +6,7 @@ import { toggleTerrain } from "./earth.js";
|
|||||||
import {
|
import {
|
||||||
reloadData,
|
reloadData,
|
||||||
clearLockedObject,
|
clearLockedObject,
|
||||||
|
clearLockedObjectAndInfo,
|
||||||
setCablesEnabled,
|
setCablesEnabled,
|
||||||
setSatellitesEnabled,
|
setSatellitesEnabled,
|
||||||
} from "./main.js";
|
} from "./main.js";
|
||||||
@@ -20,13 +21,85 @@ import { toggleBGP, getShowBGP, getBGPCount } from "./bgp.js";
|
|||||||
export let autoRotate = true;
|
export let autoRotate = true;
|
||||||
export let zoomLevel = 1.0;
|
export let zoomLevel = 1.0;
|
||||||
export let showTerrain = false;
|
export let showTerrain = false;
|
||||||
export let isDragging = false;
|
|
||||||
export let layoutExpanded = false;
|
export let layoutExpanded = false;
|
||||||
|
|
||||||
let earthObj = null;
|
let earthObj = null;
|
||||||
let listeners = [];
|
let listeners = [];
|
||||||
let cleanupFns = [];
|
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) {
|
function bindListener(element, eventName, handler, options) {
|
||||||
if (!element) return;
|
if (!element) return;
|
||||||
element.addEventListener(eventName, handler, options);
|
element.addEventListener(eventName, handler, options);
|
||||||
@@ -50,6 +123,7 @@ export function setupControls(camera, renderer, scene, earth) {
|
|||||||
setupRotateControls(camera, earth);
|
setupRotateControls(camera, earth);
|
||||||
setupTerrainControls();
|
setupTerrainControls();
|
||||||
setupLiquidGlassInteractions();
|
setupLiquidGlassInteractions();
|
||||||
|
setupKeyboardControls();
|
||||||
}
|
}
|
||||||
|
|
||||||
function setupZoomControls(camera) {
|
function setupZoomControls(camera) {
|
||||||
@@ -308,8 +382,7 @@ function setupTerrainControls() {
|
|||||||
|
|
||||||
if (trailsBtn) {
|
if (trailsBtn) {
|
||||||
trailsBtn.classList.add("active");
|
trailsBtn.classList.add("active");
|
||||||
const tooltip = trailsBtn.querySelector(".tooltip");
|
setButtonTooltip(trailsBtn, "隐藏轨迹");
|
||||||
if (tooltip) tooltip.textContent = "隐藏轨迹";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bindListener(searchBtn, "click", () => {
|
bindListener(searchBtn, "click", () => {
|
||||||
@@ -320,8 +393,7 @@ function setupTerrainControls() {
|
|||||||
showTerrain = !showTerrain;
|
showTerrain = !showTerrain;
|
||||||
toggleTerrain(showTerrain);
|
toggleTerrain(showTerrain);
|
||||||
this.classList.toggle("active", showTerrain);
|
this.classList.toggle("active", showTerrain);
|
||||||
const tooltip = this.querySelector(".tooltip");
|
setButtonTooltip(this, showTerrain ? "隐藏地形" : "显示地形");
|
||||||
if (tooltip) tooltip.textContent = showTerrain ? "隐藏地形" : "显示地形";
|
|
||||||
const terrainStatus = document.getElementById("terrain-status");
|
const terrainStatus = document.getElementById("terrain-status");
|
||||||
if (terrainStatus)
|
if (terrainStatus)
|
||||||
terrainStatus.textContent = showTerrain ? "开启" : "关闭";
|
terrainStatus.textContent = showTerrain ? "开启" : "关闭";
|
||||||
@@ -330,9 +402,7 @@ function setupTerrainControls() {
|
|||||||
|
|
||||||
bindListener(satellitesBtn, "click", async function () {
|
bindListener(satellitesBtn, "click", async function () {
|
||||||
const showSats = !getShowSatellites();
|
const showSats = !getShowSatellites();
|
||||||
if (!showSats) {
|
clearSelectionIfHiding(!showSats);
|
||||||
clearLockedObject();
|
|
||||||
}
|
|
||||||
try {
|
try {
|
||||||
await setSatellitesEnabled(showSats);
|
await setSatellitesEnabled(showSats);
|
||||||
if (!showSats) {
|
if (!showSats) {
|
||||||
@@ -350,13 +420,10 @@ function setupTerrainControls() {
|
|||||||
|
|
||||||
bindListener(bgpBtn, "click", function () {
|
bindListener(bgpBtn, "click", function () {
|
||||||
const showNextBGP = !getShowBGP();
|
const showNextBGP = !getShowBGP();
|
||||||
if (!showNextBGP) {
|
clearSelectionIfHiding(!showNextBGP);
|
||||||
clearLockedObject();
|
|
||||||
}
|
|
||||||
toggleBGP(showNextBGP);
|
toggleBGP(showNextBGP);
|
||||||
this.classList.toggle("active", showNextBGP);
|
this.classList.toggle("active", showNextBGP);
|
||||||
const tooltip = this.querySelector(".tooltip");
|
setButtonTooltip(this, showNextBGP ? "隐藏BGP观测" : "显示BGP观测");
|
||||||
if (tooltip) tooltip.textContent = showNextBGP ? "隐藏BGP观测" : "显示BGP观测";
|
|
||||||
const bgpCountEl = document.getElementById("bgp-anomaly-count");
|
const bgpCountEl = document.getElementById("bgp-anomaly-count");
|
||||||
if (bgpCountEl) {
|
if (bgpCountEl) {
|
||||||
bgpCountEl.textContent = `${getBGPCount()} 条`;
|
bgpCountEl.textContent = `${getBGPCount()} 条`;
|
||||||
@@ -369,16 +436,13 @@ function setupTerrainControls() {
|
|||||||
const nextShowTrails = !isActive;
|
const nextShowTrails = !isActive;
|
||||||
toggleTrails(nextShowTrails);
|
toggleTrails(nextShowTrails);
|
||||||
this.classList.toggle("active", nextShowTrails);
|
this.classList.toggle("active", nextShowTrails);
|
||||||
const tooltip = this.querySelector(".tooltip");
|
setButtonTooltip(this, nextShowTrails ? "隐藏轨迹" : "显示轨迹");
|
||||||
if (tooltip) tooltip.textContent = nextShowTrails ? "隐藏轨迹" : "显示轨迹";
|
|
||||||
showStatusMessage(nextShowTrails ? "轨迹已显示" : "轨迹已隐藏", "info");
|
showStatusMessage(nextShowTrails ? "轨迹已显示" : "轨迹已隐藏", "info");
|
||||||
});
|
});
|
||||||
|
|
||||||
bindListener(cablesBtn, "click", async function () {
|
bindListener(cablesBtn, "click", async function () {
|
||||||
const showNextCables = !getShowCables();
|
const showNextCables = !getShowCables();
|
||||||
if (!showNextCables) {
|
clearSelectionIfHiding(!showNextCables);
|
||||||
clearLockedObject();
|
|
||||||
}
|
|
||||||
try {
|
try {
|
||||||
await setCablesEnabled(showNextCables);
|
await setCablesEnabled(showNextCables);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -390,39 +454,27 @@ function setupTerrainControls() {
|
|||||||
await reloadData();
|
await reloadData();
|
||||||
});
|
});
|
||||||
|
|
||||||
bindListener(zoomTrigger, "click", (event) => {
|
bindFloatingMenu(zoomTrigger, zoomGroup);
|
||||||
event.stopPropagation();
|
bindFloatingMenu(infoTrigger, infoGroup);
|
||||||
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();
|
|
||||||
});
|
|
||||||
|
|
||||||
bindListener(document, "click", (event) => {
|
bindListener(document, "click", (event) => {
|
||||||
if (zoomGroup?.classList.contains("open")) {
|
const openGroups = [zoomGroup, infoGroup].filter((group) =>
|
||||||
if (!zoomGroup.contains(event.target)) {
|
group?.classList.contains("open"),
|
||||||
zoomGroup.classList.remove("open");
|
);
|
||||||
}
|
if (openGroups.length === 0) return;
|
||||||
}
|
|
||||||
if (infoGroup?.classList.contains("open")) {
|
const clickedInsideOpenGroup = openGroups.some((group) =>
|
||||||
if (!infoGroup.contains(event.target)) {
|
group.contains(event.target),
|
||||||
infoGroup.classList.remove("open");
|
);
|
||||||
}
|
if (!clickedInsideOpenGroup) {
|
||||||
|
closeFloatingMenus();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
bindListener(document, "mousemove", () => {
|
||||||
|
clearForcedFloatingCloseIfPointerOutside();
|
||||||
|
});
|
||||||
|
|
||||||
bindListener(layoutBtn, "click", () => {
|
bindListener(layoutBtn, "click", () => {
|
||||||
const expanded = toggleLayoutExpanded(container);
|
const expanded = toggleLayoutExpanded(container);
|
||||||
showStatusMessage(expanded ? "布局已最大化" : "布局已恢复", "info");
|
showStatusMessage(expanded ? "布局已最大化" : "布局已恢复", "info");
|
||||||
@@ -431,6 +483,19 @@ function setupTerrainControls() {
|
|||||||
updateLayoutUI(container);
|
updateLayoutUI(container);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function setupKeyboardControls() {
|
||||||
|
bindListener(document, "keydown", (event) => {
|
||||||
|
if (event.key !== "Escape") return;
|
||||||
|
|
||||||
|
if (isFloatingMenuVisible()) {
|
||||||
|
closeFloatingMenus();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
clearLockedObjectAndInfo();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function setupLiquidGlassInteractions() {
|
function setupLiquidGlassInteractions() {
|
||||||
const surfaces = document.querySelectorAll(".liquid-glass-surface");
|
const surfaces = document.querySelectorAll(".liquid-glass-surface");
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[project]
|
[project]
|
||||||
name = "planet"
|
name = "planet"
|
||||||
version = "0.22.0"
|
version = "0.22.1"
|
||||||
description = "智能星球计划 - 态势感知系统"
|
description = "智能星球计划 - 态势感知系统"
|
||||||
requires-python = ">=3.14"
|
requires-python = ">=3.14"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
|||||||
Reference in New Issue
Block a user