From e85a9fc6141071d07b4fb0a68890c9ce620447f3 Mon Sep 17 00:00:00 2001 From: linkong Date: Fri, 10 Apr 2026 16:37:09 +0800 Subject: [PATCH] fix: refine earth settings modal and hud interactions --- frontend/public/earth/css/hud.css | 268 +++++++++++++++++++++++++++ frontend/public/earth/index.html | 84 ++++++++- frontend/public/earth/js/controls.js | 177 ++++++++++++++++++ frontend/public/earth/js/legend.js | 9 +- 4 files changed, 528 insertions(+), 10 deletions(-) diff --git a/frontend/public/earth/css/hud.css b/frontend/public/earth/css/hud.css index 23f23071..42664966 100644 --- a/frontend/public/earth/css/hud.css +++ b/frontend/public/earth/css/hud.css @@ -62,6 +62,64 @@ font-size: var(--hud-title-size); } +.hud-panel-header { + display: flex; + align-items: center; + justify-content: space-between; + gap: var(--hud-gap-sm); + margin-bottom: var(--hud-gap-sm); +} + +.hud-panel-header .hud-panel-title { + margin-bottom: 0; +} + +.hud-panel-drag-handle { + cursor: grab; + user-select: none; +} + +.hud-panel-drag-handle:active { + cursor: grabbing; +} + +.hud-panel-close { + width: 28px; + height: 28px; + min-width: 28px; + border: 0; + border-radius: 999px; + background: rgba(255, 255, 255, 0.08); + color: #d9efff; + display: inline-flex; + align-items: center; + justify-content: center; + cursor: pointer; + transition: background 0.18s ease, transform 0.18s ease, opacity 0.18s ease; +} + +.hud-panel-close .material-symbols-rounded { + font-size: 18px; +} + +.hud-panel-close:hover { + background: rgba(255, 255, 255, 0.14); + transform: scale(1.04); +} + +.hud-panel.is-dragging { + transition: none !important; + box-shadow: + inset 0 1px 0 rgba(255, 255, 255, 0.14), + inset 0 -1px 0 rgba(255, 255, 255, 0.04), + 0 24px 52px rgba(0, 0, 0, 0.28), + 0 0 36px rgba(120, 200, 255, 0.16); +} + +.hud-panel-hidden { + display: none !important; +} + .hud-panel-row { display: flex; justify-content: space-between; @@ -145,6 +203,216 @@ user-select: none; } +.earth-settings-modal { + position: fixed; + inset: 0; + z-index: 260; + display: none; +} + +.earth-settings-modal.is-open { + display: block; +} + +.earth-settings-backdrop { + position: fixed; + inset: 0; + background: rgba(2, 8, 20, 0.38); + backdrop-filter: blur(10px); + -webkit-backdrop-filter: blur(10px); +} + +.earth-settings-sheet { + position: fixed; + top: 15vh; + right: 20vw; + bottom: 15vh; + left: 20vw; + width: auto; + max-width: none; + transform: none; + border-radius: calc(28px * var(--hud-scale)); + padding: calc(20px * var(--hud-scale)); + display: flex; + flex-direction: column; + gap: var(--hud-gap-md); + overflow: hidden; + animation: none; +} + +.earth-settings-sheet.liquid-glass-surface:hover, +.earth-settings-sheet.liquid-glass-surface:active, +.earth-settings-sheet.liquid-glass-surface.is-pressed { + --btn-scale: 1; + --press-offset: 0px; + --glow-opacity: 0.24; + transform: none; +} + +.earth-settings-header, +.earth-settings-content { + position: relative; + z-index: 1; +} + +.earth-settings-header { + display: flex; + align-items: flex-start; + justify-content: space-between; + gap: var(--hud-gap-md); + padding-bottom: var(--hud-gap-sm); + border-bottom: 1px solid rgba(255, 255, 255, 0.08); +} + +.earth-settings-kicker { + color: #9eb7d0; + font-size: 0.78rem; + letter-spacing: 0.08em; + text-transform: uppercase; +} + +.earth-settings-title { + margin-top: 4px; + color: #eef7ff; + font-size: calc(1.45rem * var(--hud-scale)); + font-weight: 600; +} + +.earth-settings-close { + width: 34px; + height: 34px; + min-width: 34px; + border: 0; + border-radius: 999px; + background: rgba(255, 255, 255, 0.08); + color: #d9efff; + display: inline-flex; + align-items: center; + justify-content: center; + cursor: pointer; +} + +.earth-settings-content { + overflow-y: auto; + padding-right: 4px; + scrollbar-width: thin; + scrollbar-color: rgba(160, 220, 255, 0.45) transparent; +} + +.earth-settings-content::-webkit-scrollbar { + width: 6px; +} + +.earth-settings-content::-webkit-scrollbar-track { + background: transparent; +} + +.earth-settings-content::-webkit-scrollbar-thumb { + background: linear-gradient(180deg, rgba(210, 237, 255, 0.28), rgba(110, 176, 255, 0.34)); + border-radius: 999px; +} + +.earth-settings-section { + padding: 12px 0 20px; +} + +.earth-settings-section-title { + margin-bottom: 12px; + color: #9eb7d0; + font-size: 0.8rem; + letter-spacing: 0.08em; + text-transform: uppercase; +} + +.earth-settings-list { + display: flex; + flex-direction: column; + gap: 10px; +} + +.earth-settings-item { + display: flex; + align-items: center; + justify-content: space-between; + gap: 16px; + padding: 14px 16px; + border-radius: 18px; + background: linear-gradient(180deg, rgba(255, 255, 255, 0.06), rgba(92, 151, 218, 0.03)); + border: 1px solid rgba(255, 255, 255, 0.08); + cursor: pointer; +} + +.earth-settings-copy { + display: flex; + flex-direction: column; + gap: 4px; +} + +.earth-settings-item-title { + color: #eef7ff; + font-size: 1rem; + font-weight: 500; +} + +.earth-settings-item-subtitle { + color: #90a6bb; + font-size: 0.82rem; + line-height: 1.4; +} + +.earth-settings-switch { + position: relative; + display: inline-flex; + align-items: center; +} + +.earth-settings-switch input { + position: absolute; + opacity: 0; + pointer-events: none; +} + +.earth-settings-switch-track { + width: 48px; + height: 30px; + border-radius: 999px; + background: rgba(255, 255, 255, 0.14); + border: 1px solid rgba(255, 255, 255, 0.12); + position: relative; + transition: background 0.18s ease, border-color 0.18s ease; +} + +.earth-settings-switch-track::after { + content: ""; + position: absolute; + top: 3px; + left: 3px; + width: 22px; + height: 22px; + border-radius: 50%; + background: #f5fbff; + box-shadow: 0 3px 10px rgba(0, 0, 0, 0.22); + transition: transform 0.18s ease; +} + +.earth-settings-switch input:checked + .earth-settings-switch-track { + background: linear-gradient(180deg, rgba(69, 171, 255, 0.92), rgba(34, 123, 255, 0.84)); + border-color: rgba(220, 243, 255, 0.36); +} + +.earth-settings-switch input:checked + .earth-settings-switch-track::after { + transform: translateX(18px); +} + +@media (max-width: 960px) { + .earth-settings-sheet { + top: 12vh; + right: 10vw; + bottom: 12vh; + left: 10vw; + } +} + .earth-app.layout-expanded .hud-panel-info { top: var(--hud-offset); left: var(--hud-offset); diff --git a/frontend/public/earth/index.html b/frontend/public/earth/index.html index 39af22e9..bd9af3a3 100644 --- a/frontend/public/earth/index.html +++ b/frontend/public/earth/index.html @@ -128,6 +128,12 @@ + +
经度: 0.00° @@ -161,8 +172,13 @@
鼠标位置: 无
-
-

线缆图例

+
+
+

线缆图例

+ +
@@ -183,8 +199,13 @@
-
-

地球信息

+
+
+

地球信息

+ +
电缆系统: 0个 @@ -234,6 +255,57 @@
+
diff --git a/frontend/public/earth/js/controls.js b/frontend/public/earth/js/controls.js index 22b58736..c0f012f1 100644 --- a/frontend/public/earth/js/controls.js +++ b/frontend/public/earth/js/controls.js @@ -26,6 +26,11 @@ export let layoutExpanded = false; let earthObj = null; let listeners = []; let cleanupFns = []; +const HUD_PANEL_IDS = [ + "legend", + "coordinates-display", + "earth-stats", +]; function getFloatingGroups() { return [ @@ -44,6 +49,12 @@ function isFloatingMenuVisible() { }); } +function isSettingsModalOpen() { + return document + .getElementById("settings-modal") + ?.classList.contains("is-open"); +} + function closeFloatingMenus() { getFloatingGroups().forEach((group) => { group.classList.remove("open"); @@ -55,6 +66,164 @@ function closeFloatingMenus() { } } +function openSettingsModal() { + const modal = document.getElementById("settings-modal"); + if (!modal) return; + closeFloatingMenus(); + modal.classList.add("is-open"); + modal.setAttribute("aria-hidden", "false"); +} + +function closeSettingsModal() { + const modal = document.getElementById("settings-modal"); + if (!modal) return; + modal.classList.remove("is-open"); + modal.setAttribute("aria-hidden", "true"); +} + +function setHudPanelVisibility(panelId, visible) { + const panel = document.getElementById(panelId); + if (!panel) return; + panel.classList.toggle("hud-panel-hidden", !visible); + syncSettingsToggle(panelId, visible); +} + +function syncSettingsToggle(panelId, visible) { + const input = document.querySelector( + `[data-settings-panel="${panelId}"]`, + ); + if (input instanceof HTMLInputElement) { + input.checked = visible; + } +} + +function syncAllHudPanelToggles() { + HUD_PANEL_IDS.forEach((panelId) => { + const panel = document.getElementById(panelId); + syncSettingsToggle(panelId, !panel?.classList.contains("hud-panel-hidden")); + }); +} + +function setupSettingsControls() { + const settingsTrigger = document.getElementById("settings-trigger"); + const settingsClose = document.getElementById("settings-close"); + const settingsBackdrop = document.getElementById("settings-backdrop"); + const settingsModal = document.getElementById("settings-modal"); + + bindListener(settingsTrigger, "click", () => { + openSettingsModal(); + }); + + bindListener(settingsClose, "click", () => { + closeSettingsModal(); + }); + + bindListener(settingsBackdrop, "click", () => { + closeSettingsModal(); + }); + + bindListener(settingsModal, "click", (event) => { + const sheet = event.target.closest(".earth-settings-sheet"); + if (!sheet) { + closeSettingsModal(); + } + }); + + const toggleInputs = document.querySelectorAll("[data-settings-panel]"); + toggleInputs.forEach((input) => { + bindListener(input, "change", (event) => { + const target = event.currentTarget; + if (!(target instanceof HTMLInputElement)) return; + const panelId = target.dataset.settingsPanel; + if (!panelId) return; + setHudPanelVisibility(panelId, target.checked); + }); + }); + + syncAllHudPanelToggles(); +} + +function setupHudPanelControls() { + const closeButtons = document.querySelectorAll("[data-close-panel]"); + closeButtons.forEach((button) => { + bindListener(button, "click", (event) => { + event.stopPropagation(); + const target = event.currentTarget; + if (!(target instanceof HTMLElement)) return; + const panelId = target.dataset.closePanel; + if (!panelId) return; + setHudPanelVisibility(panelId, false); + }); + }); +} + +function setupDraggableHudPanels() { + const app = document.getElementById("container"); + const draggablePanels = document.querySelectorAll(".hud-panel-draggable"); + if (!app || draggablePanels.length === 0) return; + + draggablePanels.forEach((panel) => { + const handle = panel.querySelector(".hud-panel-drag-handle"); + if (!handle) return; + + let isDragging = false; + let startPointerX = 0; + let startPointerY = 0; + let startLeft = 0; + let startTop = 0; + + const stopDragging = () => { + isDragging = false; + panel.classList.remove("is-dragging"); + document.body.style.userSelect = ""; + }; + + const onMove = (event) => { + if (!isDragging) return; + const appRect = app.getBoundingClientRect(); + const panelRect = panel.getBoundingClientRect(); + const nextLeft = Math.min( + Math.max(startLeft + (event.clientX - startPointerX), 0), + appRect.width - panelRect.width, + ); + const nextTop = Math.min( + Math.max(startTop + (event.clientY - startPointerY), 0), + appRect.height - panelRect.height, + ); + + panel.style.left = `${nextLeft}px`; + panel.style.top = `${nextTop}px`; + panel.style.right = "auto"; + panel.style.bottom = "auto"; + panel.style.transform = "none"; + }; + + bindListener(handle, "pointerdown", (event) => { + if (event.target.closest(".hud-panel-close")) return; + isDragging = true; + startPointerX = event.clientX; + startPointerY = event.clientY; + const appRect = app.getBoundingClientRect(); + const panelRect = panel.getBoundingClientRect(); + startLeft = panelRect.left - appRect.left; + startTop = panelRect.top - appRect.top; + panel.style.left = `${startLeft}px`; + panel.style.top = `${startTop}px`; + panel.style.right = "auto"; + panel.style.bottom = "auto"; + panel.style.transform = "none"; + panel.classList.add("is-dragging"); + document.body.style.userSelect = "none"; + handle.setPointerCapture?.(event.pointerId); + }); + + bindListener(handle, "pointermove", onMove); + bindListener(handle, "pointerup", stopDragging); + bindListener(handle, "pointercancel", stopDragging); + bindListener(handle, "lostpointercapture", stopDragging); + }); +} + function clearForcedFloatingClose() { getFloatingGroups().forEach((group) => { group.classList.remove("force-closed"); @@ -379,6 +548,9 @@ function setupTerrainControls() { const reloadBtn = document.getElementById("reload-data"); const zoomGroup = document.getElementById("zoom-control-group"); const zoomTrigger = document.getElementById("zoom-trigger"); + setupSettingsControls(); + setupHudPanelControls(); + setupDraggableHudPanels(); if (trailsBtn) { trailsBtn.classList.add("active"); @@ -487,6 +659,11 @@ function setupKeyboardControls() { bindListener(document, "keydown", (event) => { if (event.key !== "Escape") return; + if (isSettingsModalOpen()) { + closeSettingsModal(); + return; + } + if (isFloatingMenuVisible()) { closeFloatingMenus(); return; diff --git a/frontend/public/earth/js/legend.js b/frontend/public/earth/js/legend.js index 45f9fc35..cb932e29 100644 --- a/frontend/public/earth/js/legend.js +++ b/frontend/public/earth/js/legend.js @@ -49,6 +49,9 @@ function renderLegend(mode) { const config = LEGEND_MODES[mode] || LEGEND_MODES.cables; const items = legendItemsByMode[mode] || []; + const titleEl = legend.querySelector(".legend-title"); + const listEl = legend.querySelector(".legend-list"); + if (!titleEl || !listEl) return; const itemsHtml = items .map( (item) => ` @@ -60,8 +63,6 @@ function renderLegend(mode) { ) .join(""); - legend.innerHTML = ` -

${config.title}

-
${itemsHtml}
- `; + titleEl.textContent = config.title; + listEl.innerHTML = itemsHtml; }