From 78bb639a83080dd5bf070c40425e18d17748ff8f Mon Sep 17 00:00:00 2001 From: rayd1o Date: Sat, 21 Mar 2026 02:26:41 +0800 Subject: [PATCH 1/5] feat(earth): toolbar zoom improvements and toggle-cables - Remove zoom slider, implement click/hold zoom behavior (+/- buttons) - Add 10% step on click, 1% continuous on hold - Add box-sizing/padding normalization to toolbar buttons - Add toggle-cables functionality with visibility state - Fix breathing effect: faster pulse (0.008), wider opacity range (0.2-1.0) - Fix slider null error in updateZoomDisplay - Set satellite default to hidden --- frontend/public/earth/css/base.css | 7 +- frontend/public/earth/index.html | 4 +- frontend/public/earth/js/cables.js | 15 ++++ frontend/public/earth/js/constants.js | 4 +- frontend/public/earth/js/controls.js | 111 +++++++++++++++----------- frontend/public/earth/js/main.js | 2 +- frontend/public/earth/js/ui.js | 3 +- 7 files changed, 92 insertions(+), 54 deletions(-) diff --git a/frontend/public/earth/css/base.css b/frontend/public/earth/css/base.css index 62d0e4e5..7448c835 100644 --- a/frontend/public/earth/css/base.css +++ b/frontend/public/earth/css/base.css @@ -43,7 +43,7 @@ body { display: flex; flex-direction: column; align-items: center; - gap: 4px; + gap: 6px; background: rgba(10, 10, 30, 0.9); padding: 8px 4px; border-radius: 24px; @@ -93,7 +93,9 @@ body { justify-content: center; transition: all 0.2s ease; padding: 0; + margin: 0; flex: 0 0 auto; + box-sizing: border-box; } #zoom-toolbar .zoom-btn:hover { @@ -306,6 +308,9 @@ input[type="range"]::-webkit-slider-thumb { align-items: center; justify-content: center; transition: all 0.2s ease; + box-sizing: border-box; + padding: 0; + margin: 0; } .toolbar-btn:hover { diff --git a/frontend/public/earth/index.html b/frontend/public/earth/index.html index 21db28a5..c46ab8bb 100644 --- a/frontend/public/earth/index.html +++ b/frontend/public/earth/index.html @@ -39,7 +39,6 @@
- 100% @@ -48,8 +47,9 @@
+ - +
diff --git a/frontend/public/earth/js/cables.js b/frontend/public/earth/js/cables.js index be42a82c..8bdfa33f 100644 --- a/frontend/public/earth/js/cables.js +++ b/frontend/public/earth/js/cables.js @@ -11,6 +11,7 @@ export let cableLines = []; export let landingPoints = []; export let lockedCable = null; let cableIdMap = new Map(); +let cablesVisible = true; function getCableColor(properties) { if (properties.color) { @@ -406,3 +407,17 @@ export function resetLandingPointVisualState() { lp.scale.setScalar(1.0); }); } + +export function toggleCables(show) { + cablesVisible = show; + cableLines.forEach(cable => { + cable.visible = cablesVisible; + }); + landingPoints.forEach(lp => { + lp.visible = cablesVisible; + }); +} + +export function getShowCables() { + return cablesVisible; +} diff --git a/frontend/public/earth/js/constants.js b/frontend/public/earth/js/constants.js index 79a796b1..d6e846a6 100644 --- a/frontend/public/earth/js/constants.js +++ b/frontend/public/earth/js/constants.js @@ -39,11 +39,11 @@ export const CABLE_COLORS = { }; export const CABLE_CONFIG = { - lockedOpacityMin: 0.6, + lockedOpacityMin: 0.2, lockedOpacityMax: 1.0, otherOpacity: 0.5, otherBrightness: 0.6, - pulseSpeed: 0.003, + pulseSpeed: 0.008, pulseCoefficient: 0.4 }; diff --git a/frontend/public/earth/js/controls.js b/frontend/public/earth/js/controls.js index 45d9cdc0..75d1457d 100644 --- a/frontend/public/earth/js/controls.js +++ b/frontend/public/earth/js/controls.js @@ -5,6 +5,7 @@ import { updateZoomDisplay, showStatusMessage } from './ui.js'; import { toggleTerrain } from './earth.js'; import { reloadData } from './main.js'; import { toggleSatellites, toggleTrails, getShowSatellites, getSatelliteCount } from './satellites.js'; +import { toggleCables, getShowCables } from './cables.js'; export let autoRotate = true; export let zoomLevel = 1.0; @@ -23,33 +24,18 @@ export function setupControls(camera, renderer, scene, earth) { function setupZoomControls(camera) { let zoomInterval = null; - let lastDirection = 0; - let isSnapped = false; + let holdTimeout = null; + let startTime = 0; + const HOLD_THRESHOLD = 150; + const LONG_PRESS_TICK = 50; + const CLICK_STEP = 10; const MIN_PERCENT = CONFIG.minZoom * 100; const MAX_PERCENT = CONFIG.maxZoom * 100; - function adjustZoom(direction) { - const currentPercent = Math.round(zoomLevel * 100); - let newPercent; - - if (direction > 0) { - if (!isSnapped || currentPercent % 10 !== 0) { - newPercent = Math.ceil(currentPercent / 10) * 10; - if (newPercent <= currentPercent) newPercent = currentPercent + 10; - isSnapped = true; - } else { - newPercent = currentPercent + 10; - } - } else { - if (!isSnapped || currentPercent % 10 !== 0) { - newPercent = Math.floor(currentPercent / 10) * 10; - if (newPercent >= currentPercent) newPercent = currentPercent - 10; - isSnapped = true; - } else { - newPercent = currentPercent - 10; - } - } + function doZoomStep(direction) { + let currentPercent = Math.round(zoomLevel * 100); + let newPercent = direction > 0 ? currentPercent + CLICK_STEP : currentPercent - CLICK_STEP; if (newPercent > MAX_PERCENT) newPercent = MAX_PERCENT; if (newPercent < MIN_PERCENT) newPercent = MIN_PERCENT; @@ -58,13 +44,22 @@ function setupZoomControls(camera) { applyZoom(camera); } - function startZoom(direction) { - isSnapped = false; - lastDirection = direction; - adjustZoom(direction); + function doContinuousZoom(direction) { + let currentPercent = Math.round(zoomLevel * 100); + let newPercent = direction > 0 ? currentPercent + 1 : currentPercent - 1; + + if (newPercent > MAX_PERCENT) newPercent = MAX_PERCENT; + if (newPercent < MIN_PERCENT) newPercent = MIN_PERCENT; + + zoomLevel = newPercent / 100; + applyZoom(camera); + } + + function startContinuousZoom(direction) { + doContinuousZoom(direction); zoomInterval = setInterval(() => { - adjustZoom(direction); - }, 150); + doContinuousZoom(direction); + }, LONG_PRESS_TICK); } function stopZoom() { @@ -72,29 +67,49 @@ function setupZoomControls(camera) { clearInterval(zoomInterval); zoomInterval = null; } + if (holdTimeout) { + clearTimeout(holdTimeout); + holdTimeout = null; + } } - document.getElementById('zoom-in').addEventListener('mousedown', () => startZoom(1)); - document.getElementById('zoom-in').addEventListener('mouseup', stopZoom); - document.getElementById('zoom-in').addEventListener('mouseleave', stopZoom); - document.getElementById('zoom-in').addEventListener('touchstart', (e) => { e.preventDefault(); startZoom(1); }); - document.getElementById('zoom-in').addEventListener('touchend', stopZoom); + function handleMouseDown(direction) { + startTime = Date.now(); + stopZoom(); + holdTimeout = setTimeout(() => { + startContinuousZoom(direction); + }, HOLD_THRESHOLD); + } - document.getElementById('zoom-out').addEventListener('mousedown', () => startZoom(-1)); - document.getElementById('zoom-out').addEventListener('mouseup', stopZoom); + function handleMouseUp(direction) { + const heldTime = Date.now() - startTime; + stopZoom(); + if (heldTime < HOLD_THRESHOLD) { + doZoomStep(direction); + } + } + + document.getElementById('zoom-in').addEventListener('mousedown', () => handleMouseDown(1)); + document.getElementById('zoom-in').addEventListener('mouseup', () => handleMouseUp(1)); + document.getElementById('zoom-in').addEventListener('mouseleave', stopZoom); + document.getElementById('zoom-in').addEventListener('touchstart', (e) => { e.preventDefault(); handleMouseDown(1); }); + document.getElementById('zoom-in').addEventListener('touchend', () => handleMouseUp(1)); + + document.getElementById('zoom-out').addEventListener('mousedown', () => handleMouseDown(-1)); + document.getElementById('zoom-out').addEventListener('mouseup', () => handleMouseUp(-1)); document.getElementById('zoom-out').addEventListener('mouseleave', stopZoom); - document.getElementById('zoom-out').addEventListener('touchstart', (e) => { e.preventDefault(); startZoom(-1); }); - document.getElementById('zoom-out').addEventListener('touchend', stopZoom); + document.getElementById('zoom-out').addEventListener('touchstart', (e) => { e.preventDefault(); handleMouseDown(-1); }); + document.getElementById('zoom-out').addEventListener('touchend', () => handleMouseUp(-1)); document.getElementById('zoom-value').addEventListener('click', function() { - const startZoom = zoomLevel; + const startZoomVal = zoomLevel; const targetZoom = 1.0; - const startDistance = CONFIG.defaultCameraZ / startZoom; + const startDistance = CONFIG.defaultCameraZ / startZoomVal; const targetDistance = CONFIG.defaultCameraZ / targetZoom; animateValue(0, 1, 600, (progress) => { const ease = 1 - Math.pow(1 - progress, 3); - zoomLevel = startZoom + (targetZoom - startZoom) * ease; + zoomLevel = startZoomVal + (targetZoom - startZoomVal) * ease; camera.position.z = CONFIG.defaultCameraZ / zoomLevel; const distance = startDistance + (targetDistance - startDistance) * ease; updateZoomDisplay(zoomLevel, distance.toFixed(0)); @@ -103,12 +118,6 @@ function setupZoomControls(camera) { showStatusMessage('缩放已重置到100%', 'info'); }); }); - - const slider = document.getElementById('zoom-slider'); - slider?.addEventListener('input', (e) => { - zoomLevel = parseFloat(e.target.value); - applyZoom(camera); - }); } function setupWheelZoom(camera, renderer) { @@ -235,6 +244,14 @@ function setupTerrainControls() { showStatusMessage(showTrails ? '轨迹已显示' : '轨迹已隐藏', 'info'); }); + document.getElementById('toggle-cables').addEventListener('click', function() { + const showCables = !getShowCables(); + toggleCables(showCables); + this.classList.toggle('active', showCables); + this.querySelector('.tooltip').textContent = showCables ? '隐藏线缆' : '显示线缆'; + showStatusMessage(showCables ? '线缆已显示' : '线缆已隐藏', 'info'); + }); + document.getElementById('reload-data').addEventListener('click', async () => { await reloadData(); showStatusMessage('数据已重新加载', 'success'); diff --git a/frontend/public/earth/js/main.js b/frontend/public/earth/js/main.js index fcda5cb1..109c9039 100644 --- a/frontend/public/earth/js/main.js +++ b/frontend/public/earth/js/main.js @@ -92,7 +92,7 @@ function applyCableVisualState() { switch (state) { case CABLE_STATE.LOCKED: - c.material.opacity = 0.3 + pulse * 0.7; + c.material.opacity = CABLE_CONFIG.lockedOpacityMin + pulse * (CABLE_CONFIG.lockedOpacityMax - CABLE_CONFIG.lockedOpacityMin); c.material.color.setRGB(1, 1, 1); break; case CABLE_STATE.HOVERED: diff --git a/frontend/public/earth/js/ui.js b/frontend/public/earth/js/ui.js index 67b82bb6..da36d359 100644 --- a/frontend/public/earth/js/ui.js +++ b/frontend/public/earth/js/ui.js @@ -25,7 +25,8 @@ export function updateZoomDisplay(zoomLevel, distance) { const percent = Math.round(zoomLevel * 100); document.getElementById('zoom-value').textContent = percent + '%'; document.getElementById('zoom-level').textContent = '缩放: ' + percent + '%'; - document.getElementById('zoom-slider').value = zoomLevel; + const slider = document.getElementById('zoom-slider'); + if (slider) slider.value = zoomLevel; document.getElementById('camera-distance').textContent = distance + ' km'; } From d9a64f7768ec92aaeec600290b33e2d016f3e17e Mon Sep 17 00:00:00 2001 From: rayd1o Date: Sat, 21 Mar 2026 04:10:33 +0800 Subject: [PATCH 2/5] fix(frontend): fix iframe scrollbar issue by using 100% instead of 100vw/vh and setting html/body/root to 100% height --- frontend/src/index.css | 6 ++++++ frontend/src/pages/Earth/Earth.tsx | 5 +++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/frontend/src/index.css b/frontend/src/index.css index e47a2973..fb11001b 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -4,6 +4,12 @@ box-sizing: border-box; } +html, body, #root { + width: 100%; + height: 100%; + overflow: hidden; +} + body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; } diff --git a/frontend/src/pages/Earth/Earth.tsx b/frontend/src/pages/Earth/Earth.tsx index 2697b2d2..da2896d1 100644 --- a/frontend/src/pages/Earth/Earth.tsx +++ b/frontend/src/pages/Earth/Earth.tsx @@ -3,9 +3,10 @@ function Earth() {