release: bump version to 0.29.1
This commit is contained in:
@@ -8,6 +8,23 @@ This project follows the repository versioning rule:
|
||||
- `improvement` -> `+0.0.1`(bugfix + 小功能混合)
|
||||
- `bugfix` -> `+0.0.1`
|
||||
|
||||
## [0.29.1] — 2026-04-20
|
||||
|
||||
### ✨ Highlights
|
||||
- Earth 加载状态条改成单一队列式通知面板,加载阶段不再因为步骤文案变化而回缩,也不会被其他通知打断
|
||||
- 调整 brand panel 的呈现方式与昼夜/选中态可读性,让品牌区更自然、交互高亮在白天和黑夜里都更稳定
|
||||
|
||||
### 🔧 Improvements
|
||||
- 移除旧的地球加载浮层结构,统一由 HUD 状态消息承载三点脉冲加载过程
|
||||
- brand panel 改为无边框品牌层,仅保留轻微氛围光,不再因为非常规尺寸显得像第五块功能面板
|
||||
- 温和收敛地球昼夜材质与主背光强度,保留昼夜辨识度的同时提升白天地表纹理和夜面交互可见性
|
||||
|
||||
### 🐛 Fixes
|
||||
- 修复加载地球时通知条在步骤切换中反复缩短、其他状态消息抢占加载流程的问题
|
||||
- 修复海缆、登陆点和 BGP 选中高亮在黑夜中过暗、在高光中过亮导致难以辨识的问题
|
||||
|
||||
---
|
||||
|
||||
## [0.29.0] — 2026-04-20
|
||||
|
||||
### ✨ Highlights
|
||||
|
||||
@@ -16,12 +16,13 @@
|
||||
## Current Version
|
||||
|
||||
- `main` 当前主线历史推导到:`0.16.5`
|
||||
- `dev` 当前开发分支历史推导到:`0.29.0`
|
||||
- `dev` 当前开发分支历史推导到:`0.29.1`
|
||||
|
||||
## Timeline
|
||||
|
||||
| Version | Type | Branch | Commit | Summary |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| `0.29.1` | bugfix | `dev` | `pending` | Earth 加载通知条改为队列式单面板显示,brand panel 去框并收敛昼夜与选中态可读性 |
|
||||
| `0.29.0` | feature | `dev` | `pending` | Earth 新增天球背景与太阳/月亮位置层,强化昼夜分隔并收口卫星图例与图层面板交互 |
|
||||
| `0.28.2` | bugfix | `dev` | `pending` | 修正媒体情报 tab 尺寸记忆与切换锚点逻辑,并清理 docs 根目录遗留旧路径文档 |
|
||||
| `0.28.1` | bugfix | `dev` | `pending` | 收口 Earth 媒体情报面板命名与 tab 文案,整理 docs 分组并归档已完成/废弃计划文档 |
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "planet-frontend",
|
||||
"version": "0.29.0",
|
||||
"version": "0.29.1",
|
||||
"private": true,
|
||||
"packageManager": "bun@1",
|
||||
"dependencies": {
|
||||
|
||||
@@ -83,59 +83,17 @@ body.earth-page {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.earth-loading {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
z-index: 240;
|
||||
min-width: min(calc(320px * var(--hud-scale)), 78vw);
|
||||
padding: calc(26px * var(--hud-scale));
|
||||
border-radius: calc(18px * var(--hud-scale));
|
||||
border: 1px solid rgba(77, 184, 255, 0.34);
|
||||
background:
|
||||
radial-gradient(circle at 50% 18%, rgba(255, 255, 255, 0.12), transparent 35%),
|
||||
linear-gradient(180deg, rgba(13, 24, 46, 0.95), rgba(7, 14, 28, 0.94));
|
||||
box-shadow:
|
||||
0 0 30px rgba(77, 184, 255, 0.22),
|
||||
0 16px 40px rgba(0, 0, 0, 0.28);
|
||||
text-align: center;
|
||||
color: #4db8ff;
|
||||
}
|
||||
|
||||
.earth-loading-text {
|
||||
color: #4db8ff;
|
||||
}
|
||||
|
||||
.earth-loading-title {
|
||||
font-size: calc(1.15rem * var(--hud-scale));
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.earth-loading-subtitle {
|
||||
margin-top: calc(10px * var(--hud-scale));
|
||||
color: #9ab7d4;
|
||||
font-size: calc(0.84rem * var(--hud-scale));
|
||||
line-height: 1.45;
|
||||
}
|
||||
|
||||
.earth-loading-spinner {
|
||||
width: calc(40px * var(--hud-scale));
|
||||
height: calc(40px * var(--hud-scale));
|
||||
margin: 0 auto calc(15px * var(--hud-scale));
|
||||
border: 4px solid rgba(77, 184, 255, 0.28);
|
||||
border-top: 4px solid #4db8ff;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
@keyframes earthLoadingPulse {
|
||||
0%,
|
||||
80%,
|
||||
100% {
|
||||
opacity: 0.28;
|
||||
transform: scale(0.78);
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
40% {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -243,21 +243,32 @@
|
||||
|
||||
.earth-status-message {
|
||||
position: absolute;
|
||||
top: 20px;
|
||||
top: calc(20px * var(--hud-scale));
|
||||
left: 50%;
|
||||
transform: translate(-50%, -18px);
|
||||
background:
|
||||
linear-gradient(180deg, rgba(18, 31, 52, 0.92), rgba(8, 18, 32, 0.9));
|
||||
border-radius: 14px;
|
||||
padding: 11px 15px;
|
||||
z-index: 210;
|
||||
box-shadow: var(--hud-shadow-soft);
|
||||
border: 1px solid var(--hud-border);
|
||||
font-size: 0.9rem;
|
||||
display: none;
|
||||
backdrop-filter: blur(10px);
|
||||
text-align: center;
|
||||
min-width: 180px;
|
||||
align-items: center;
|
||||
gap: calc(10px * var(--hud-scale));
|
||||
background:
|
||||
linear-gradient(90deg, rgba(145, 186, 255, 0.07) 0%, transparent 44%),
|
||||
linear-gradient(180deg, rgba(22, 36, 58, 0.95), rgba(8, 18, 32, 0.93));
|
||||
border-radius: 999px;
|
||||
border: 1px solid var(--hud-border);
|
||||
border-left-color: rgba(145, 186, 255, 0.32);
|
||||
padding: calc(8px * var(--hud-scale)) calc(20px * var(--hud-scale)) calc(8px * var(--hud-scale)) calc(16px * var(--hud-scale));
|
||||
z-index: 210;
|
||||
box-shadow:
|
||||
var(--hud-shadow-soft),
|
||||
0 0 18px rgba(145, 186, 255, 0.06);
|
||||
font-size: calc(0.84rem * var(--hud-scale));
|
||||
font-weight: 500;
|
||||
letter-spacing: 0.01em;
|
||||
backdrop-filter: blur(12px);
|
||||
-webkit-backdrop-filter: blur(12px);
|
||||
text-align: left;
|
||||
min-width: min(calc(160px * var(--hud-scale)), 58vw);
|
||||
max-width: min(calc(440px * var(--hud-scale)), 74vw);
|
||||
color: var(--hud-text);
|
||||
opacity: 0;
|
||||
transition:
|
||||
transform 0.28s ease,
|
||||
@@ -269,19 +280,110 @@
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* ── Indicator: single dot (transient) or three dots (loading) ── */
|
||||
|
||||
.earth-status-indicator {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: calc(5px * var(--hud-scale));
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.earth-status-dot {
|
||||
width: calc(7px * var(--hud-scale));
|
||||
height: calc(7px * var(--hud-scale));
|
||||
border-radius: 50%;
|
||||
background: rgba(145, 186, 255, 0.95);
|
||||
box-shadow:
|
||||
0 0 8px rgba(145, 186, 255, 0.7),
|
||||
0 0 20px rgba(145, 186, 255, 0.28);
|
||||
animation: statusDotPulse 2.4s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.earth-status-text {
|
||||
flex: 1 1 auto;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
/* Loading: three-dot sequential pulse */
|
||||
.earth-status-message.loading .earth-status-dot {
|
||||
animation: earthLoadingPulse 1.2s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.earth-status-message.loading .earth-status-dot:nth-child(2) {
|
||||
animation-delay: 0.16s;
|
||||
}
|
||||
|
||||
.earth-status-message.loading .earth-status-dot:nth-child(3) {
|
||||
animation-delay: 0.32s;
|
||||
}
|
||||
|
||||
/* ── Status color variants ───────────────────────────────────── */
|
||||
|
||||
.earth-status-message.success {
|
||||
color: #d8f7df;
|
||||
border-left: 3px solid #66d18f;
|
||||
color: #dff8e7;
|
||||
background:
|
||||
linear-gradient(90deg, rgba(102, 209, 143, 0.08) 0%, transparent 44%),
|
||||
linear-gradient(180deg, rgba(22, 36, 58, 0.95), rgba(8, 18, 32, 0.93));
|
||||
border-left-color: rgba(102, 209, 143, 0.38);
|
||||
box-shadow:
|
||||
var(--hud-shadow-soft),
|
||||
0 0 18px rgba(102, 209, 143, 0.06);
|
||||
}
|
||||
|
||||
.earth-status-message.success .earth-status-dot {
|
||||
background: #66d18f;
|
||||
box-shadow:
|
||||
0 0 8px rgba(102, 209, 143, 0.8),
|
||||
0 0 20px rgba(102, 209, 143, 0.3);
|
||||
}
|
||||
|
||||
.earth-status-message.warning {
|
||||
color: #fff2c3;
|
||||
border-left: 3px solid #e4c464;
|
||||
background:
|
||||
linear-gradient(90deg, rgba(228, 196, 100, 0.08) 0%, transparent 44%),
|
||||
linear-gradient(180deg, rgba(22, 36, 58, 0.95), rgba(8, 18, 32, 0.93));
|
||||
border-left-color: rgba(228, 196, 100, 0.38);
|
||||
box-shadow:
|
||||
var(--hud-shadow-soft),
|
||||
0 0 18px rgba(228, 196, 100, 0.06);
|
||||
}
|
||||
|
||||
.earth-status-message.warning .earth-status-dot {
|
||||
background: #e4c464;
|
||||
box-shadow:
|
||||
0 0 8px rgba(228, 196, 100, 0.8),
|
||||
0 0 20px rgba(228, 196, 100, 0.3);
|
||||
}
|
||||
|
||||
.earth-status-message.error {
|
||||
color: #ffd4d7;
|
||||
border-left: 3px solid #ff7b86;
|
||||
background:
|
||||
linear-gradient(90deg, rgba(255, 123, 134, 0.08) 0%, transparent 44%),
|
||||
linear-gradient(180deg, rgba(22, 36, 58, 0.95), rgba(8, 18, 32, 0.93));
|
||||
border-left-color: rgba(255, 123, 134, 0.38);
|
||||
box-shadow:
|
||||
var(--hud-shadow-soft),
|
||||
0 0 18px rgba(255, 123, 134, 0.06);
|
||||
}
|
||||
|
||||
.earth-status-message.error .earth-status-dot {
|
||||
background: #ff7b86;
|
||||
box-shadow:
|
||||
0 0 8px rgba(255, 123, 134, 0.8),
|
||||
0 0 20px rgba(255, 123, 134, 0.34);
|
||||
}
|
||||
|
||||
@keyframes statusDotPulse {
|
||||
0%, 100% {
|
||||
opacity: 0.82;
|
||||
transform: scale(0.9);
|
||||
}
|
||||
|
||||
50% {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
.earth-tooltip {
|
||||
|
||||
@@ -26,13 +26,23 @@
|
||||
.hud-panel-brand {
|
||||
--brand-scale: 0.88;
|
||||
--brand-copy-width: 160px;
|
||||
border-radius: 0;
|
||||
padding: calc(18px * var(--hud-scale)) calc(20px * var(--hud-scale));
|
||||
padding: calc(10px * var(--hud-scale)) calc(4px * var(--hud-scale)) calc(12px * var(--hud-scale)) 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
justify-content: flex-start;
|
||||
/* Reserve full panel height before brand images load */
|
||||
min-height: calc(66px * var(--hud-scale));
|
||||
background: transparent;
|
||||
border: 0;
|
||||
box-shadow: none;
|
||||
backdrop-filter: none;
|
||||
-webkit-backdrop-filter: none;
|
||||
isolation: isolate;
|
||||
}
|
||||
|
||||
.hud-panel-brand::before,
|
||||
.hud-panel-brand::after {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.hud-panel-brand .earth-brand {
|
||||
@@ -40,6 +50,20 @@
|
||||
align-items: center;
|
||||
gap: calc(10px * var(--hud-scale) * var(--brand-scale));
|
||||
min-width: 0;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.hud-panel-brand .earth-brand::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: calc(4px * var(--hud-scale)) calc(-10px * var(--hud-scale)) calc(6px * var(--hud-scale)) calc(-10px * var(--hud-scale));
|
||||
background:
|
||||
radial-gradient(circle at 18% 50%, rgba(145, 186, 255, 0.14), transparent 32%),
|
||||
linear-gradient(90deg, rgba(145, 186, 255, 0.05), transparent 58%);
|
||||
opacity: 0.75;
|
||||
pointer-events: none;
|
||||
filter: blur(14px);
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
.hud-panel-brand .earth-brand__logo {
|
||||
|
||||
@@ -397,12 +397,7 @@
|
||||
<div class="tv-panel-edge" data-edge="bl"></div>
|
||||
</div>
|
||||
|
||||
<div id="loading" class="earth-loading">
|
||||
<div id="loading-spinner" class="earth-loading-spinner"></div>
|
||||
<div id="loading-title" class="earth-loading-title earth-loading-text">正在初始化全球态势数据...</div>
|
||||
<div id="loading-subtitle" class="earth-loading-subtitle">同步卫星、海底光缆、登陆点与BGP态势数据</div>
|
||||
</div>
|
||||
<div id="status-message" class="earth-status-message"></div>
|
||||
<div id="status-message" class="earth-status-message" aria-live="polite" aria-atomic="true"></div>
|
||||
<div id="tooltip" class="earth-tooltip"></div>
|
||||
<div id="settings-modal" class="earth-settings-modal" aria-hidden="true">
|
||||
<div id="settings-backdrop" class="earth-settings-backdrop"></div>
|
||||
|
||||
@@ -1386,21 +1386,17 @@ export function updateBGPVisualState(lockedObjectType, lockedObject, camera) {
|
||||
|
||||
if (isLocked) {
|
||||
scale *= 1.1 + 0.14 * pulse;
|
||||
opacity = BGP_CONFIG.opacity.collectorHover;
|
||||
haloOpacity = 0.022;
|
||||
pulseOpacity = 0.012;
|
||||
coverageOpacity = 0.02;
|
||||
markerColor = blendHexColors(
|
||||
BGP_CONFIG.collectorIcon.lockedNeutralColor,
|
||||
marker.userData.baseColor || BGP_CONFIG.collectorColor,
|
||||
BGP_CONFIG.collectorIcon.lockedBlend,
|
||||
);
|
||||
opacity = 0.96;
|
||||
haloOpacity = 0.05;
|
||||
pulseOpacity = 0.024;
|
||||
coverageOpacity = 0.036;
|
||||
markerColor = 0xcff2ff;
|
||||
} else if (isHovered) {
|
||||
scale *= 1.08;
|
||||
opacity = BGP_CONFIG.opacity.collectorHover;
|
||||
haloOpacity = 0.016;
|
||||
pulseOpacity = 0.008;
|
||||
coverageOpacity = 0.014;
|
||||
opacity = 0.88;
|
||||
haloOpacity = 0.03;
|
||||
pulseOpacity = 0.014;
|
||||
coverageOpacity = 0.02;
|
||||
markerColor = blendHexColors(
|
||||
BGP_CONFIG.collectorIcon.hoverNeutralColor,
|
||||
marker.userData.baseColor || BGP_CONFIG.collectorColor,
|
||||
@@ -1482,12 +1478,13 @@ export function updateBGPVisualState(lockedObjectType, lockedObject, camera) {
|
||||
if (isLocked || isLinkedCollectorLocked) {
|
||||
scale *= 1 + BGP_CONFIG.pulse.lockedAmplitude * pulse;
|
||||
opacity =
|
||||
BGP_CONFIG.opacity.lockedMin +
|
||||
(BGP_CONFIG.opacity.lockedMax - BGP_CONFIG.opacity.lockedMin) * pulse;
|
||||
0.9 +
|
||||
0.1 * pulse;
|
||||
markerColor = 0xfff1a8;
|
||||
ringBaseOpacity *= 1.2;
|
||||
} else if (isHovered) {
|
||||
scale *= BGP_CONFIG.marker.hoverScale;
|
||||
opacity = BGP_CONFIG.opacity.hover;
|
||||
opacity = 0.9;
|
||||
ringBaseOpacity *= 1.05;
|
||||
} else if (isOtherLocked) {
|
||||
scale *= BGP_CONFIG.marker.dimmedScale;
|
||||
|
||||
@@ -558,14 +558,18 @@ export function applyLandingPointVisualState(lockedCableName, dimAll = false, ca
|
||||
lp.userData.cableNames.some((name) => relatedNames.includes(name));
|
||||
|
||||
if (isRelated) {
|
||||
lp.material.color.setHex(CABLE_CONFIG.landingPoint.color);
|
||||
lp.material.emissive.setHex(CABLE_CONFIG.landingPoint.emissive);
|
||||
lp.material.color.setHex(0xffd27a);
|
||||
lp.material.emissive.setHex(0x7a4a00);
|
||||
lp.material.emissiveIntensity =
|
||||
CABLE_CONFIG.landingPointVisual.related.emissiveIntensityBase +
|
||||
pulse * CABLE_CONFIG.landingPointVisual.related.emissiveIntensityPulse;
|
||||
0.2 +
|
||||
pulse * (CABLE_CONFIG.landingPointVisual.related.emissiveIntensityPulse + 0.2);
|
||||
lp.material.opacity =
|
||||
CABLE_CONFIG.landingPointVisual.related.opacityBase +
|
||||
pulse * CABLE_CONFIG.landingPointVisual.related.opacityPulse;
|
||||
Math.max(
|
||||
0.92,
|
||||
CABLE_CONFIG.landingPointVisual.related.opacityBase +
|
||||
pulse * CABLE_CONFIG.landingPointVisual.related.opacityPulse,
|
||||
);
|
||||
const distanceScale = getLandingPointDistanceScale(lp, camera);
|
||||
const baseScale = lp.userData?.baseScale || CABLE_CONFIG.landingPoint.baseScale;
|
||||
lp.scale.setScalar(
|
||||
|
||||
@@ -68,9 +68,9 @@ export const CELESTIAL_CONFIG = {
|
||||
brightStarMaxScale: 6.8,
|
||||
brightStarOpacity: 0.88,
|
||||
sunLightDistance: 460,
|
||||
sunLightIntensity: 1.28,
|
||||
sunLightIntensity: 1.02,
|
||||
sunLightColor: 0xfff4df,
|
||||
backLightIntensity: 0.18,
|
||||
backLightIntensity: 0.3,
|
||||
backLightColor: 0x2b4c78,
|
||||
};
|
||||
|
||||
@@ -300,12 +300,12 @@ export const EARTH_MATERIAL_CONFIG = {
|
||||
dayNight: {
|
||||
enabled: true,
|
||||
sunDirection: { x: 1, y: 0.2, z: 0.4 },
|
||||
nightFloor: 0.18,
|
||||
dayBoost: 1.08,
|
||||
twilightWidth: 0.18,
|
||||
twilightIntensity: 0.22,
|
||||
nightFloor: 0.32,
|
||||
dayBoost: 0.94,
|
||||
twilightWidth: 0.24,
|
||||
twilightIntensity: 0.14,
|
||||
twilightColor: 0x4ea0ff,
|
||||
nightTintColor: 0x0b1830,
|
||||
nightTintIntensity: 0.1,
|
||||
nightTintIntensity: 0.05,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -700,14 +700,17 @@ function applyCableVisualState() {
|
||||
switch (state) {
|
||||
case CABLE_STATE.LOCKED:
|
||||
cable.material.opacity =
|
||||
CABLE_CONFIG.lockedOpacityMin +
|
||||
pulse *
|
||||
(CABLE_CONFIG.lockedOpacityMax - CABLE_CONFIG.lockedOpacityMin);
|
||||
cable.material.color.setRGB(1, 1, 1);
|
||||
Math.max(
|
||||
0.92,
|
||||
CABLE_CONFIG.lockedOpacityMin +
|
||||
pulse *
|
||||
(CABLE_CONFIG.lockedOpacityMax - CABLE_CONFIG.lockedOpacityMin),
|
||||
);
|
||||
cable.material.color.setRGB(0.86, 0.96, 1.0);
|
||||
break;
|
||||
case CABLE_STATE.HOVERED:
|
||||
cable.material.opacity = 1;
|
||||
cable.material.color.setRGB(1, 1, 1);
|
||||
cable.material.color.setRGB(0.92, 0.98, 1.0);
|
||||
break;
|
||||
case CABLE_STATE.NORMAL:
|
||||
default:
|
||||
@@ -1039,7 +1042,7 @@ async function loadData() {
|
||||
clearCableData(earth);
|
||||
clearSatelliteData();
|
||||
|
||||
setLoadingMessage("正在初始化...", "清除旧数据");
|
||||
setLoadingMessage("正在初始化...");
|
||||
setLoading(true);
|
||||
await yieldFrame();
|
||||
if (loadToken !== currentLoadToken) { isDataLoading = false; return; }
|
||||
@@ -1048,7 +1051,7 @@ async function loadData() {
|
||||
|
||||
// Step 1 — Landing points
|
||||
if (cablesEnabled) {
|
||||
setLoadingMessage("正在加载登陆点...", "同步海底光缆登陆站数据");
|
||||
setLoadingMessage("正在加载登陆点...");
|
||||
await yieldFrame(30);
|
||||
try {
|
||||
await loadLandingPoints(scene, earth);
|
||||
@@ -1061,7 +1064,7 @@ async function loadData() {
|
||||
|
||||
// Step 2 — Cables
|
||||
if (cablesEnabled) {
|
||||
setLoadingMessage("正在加载海缆...", "同步海底光缆网络数据");
|
||||
setLoadingMessage("正在加载海缆...");
|
||||
await yieldFrame(30);
|
||||
try {
|
||||
const cableCount = await loadGeoJSONFromPath(scene, earth);
|
||||
@@ -1080,7 +1083,7 @@ async function loadData() {
|
||||
|
||||
// Step 3 — Satellites
|
||||
if (satellitesEnabled) {
|
||||
setLoadingMessage("正在加载卫星...", "同步在轨卫星轨道数据");
|
||||
setLoadingMessage("正在加载卫星...");
|
||||
await yieldFrame(30);
|
||||
try {
|
||||
clearSatelliteData();
|
||||
@@ -1100,7 +1103,7 @@ async function loadData() {
|
||||
}
|
||||
|
||||
// Step 4 — BGP
|
||||
setLoadingMessage("正在加载BGP态势...", "同步全球路由观测数据");
|
||||
setLoadingMessage("正在加载BGP态势...");
|
||||
await yieldFrame(30);
|
||||
try {
|
||||
const bgpResult = await loadBGPAnomalies(scene, earth);
|
||||
@@ -1115,7 +1118,7 @@ async function loadData() {
|
||||
await yieldFrame();
|
||||
|
||||
// Step 5 — Earth texture (loads last so data layers appear on the white sphere first)
|
||||
setLoadingMessage("正在加载地球纹理...", "加载8K卫星地图");
|
||||
setLoadingMessage("正在加载地球纹理...");
|
||||
await yieldFrame(30);
|
||||
try {
|
||||
await loadEarthTexture();
|
||||
@@ -1127,19 +1130,10 @@ async function loadData() {
|
||||
|
||||
// Step 6 — Terrain (if enabled)
|
||||
if (getShowTerrain()) {
|
||||
setLoadingMessage("正在渲染地形...", "生成地表高程数据");
|
||||
setLoadingMessage("正在渲染地形...");
|
||||
await yieldFrame(50);
|
||||
}
|
||||
|
||||
if (errors.length > 0) {
|
||||
const errorMessage = buildLoadErrorMessage(errors);
|
||||
showError(errorMessage);
|
||||
showStatusMessage(errorMessage, "error");
|
||||
} else {
|
||||
hideError();
|
||||
showStatusMessage("数据已加载", "success");
|
||||
}
|
||||
|
||||
updateStatsSummary();
|
||||
updateCableToggleUi(cablesEnabled);
|
||||
updateSatelliteToggleUi(satellitesEnabled);
|
||||
@@ -1149,6 +1143,15 @@ async function loadData() {
|
||||
refreshLegend();
|
||||
setLoading(false);
|
||||
isDataLoading = false;
|
||||
|
||||
if (errors.length > 0) {
|
||||
const errorMessage = buildLoadErrorMessage(errors);
|
||||
showError(errorMessage);
|
||||
showStatusMessage(errorMessage, "error");
|
||||
} else {
|
||||
hideError();
|
||||
showStatusMessage("数据已加载", "success");
|
||||
}
|
||||
}
|
||||
|
||||
const POSITION_UPDATE_FORCE_DELTA = 250;
|
||||
@@ -1170,7 +1173,7 @@ export async function setCablesEnabled(enabled) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
setLoadingMessage("正在加载线缆数据...", "重建海缆与登陆点对象");
|
||||
setLoadingMessage("正在加载线缆数据...");
|
||||
setLoading(true);
|
||||
hideError();
|
||||
|
||||
@@ -1203,7 +1206,7 @@ export async function setSatellitesEnabled(enabled) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
setLoadingMessage("正在加载卫星数据...", "重建卫星点位与轨迹缓存");
|
||||
setLoadingMessage("正在加载卫星数据...");
|
||||
setLoading(true);
|
||||
hideError();
|
||||
|
||||
|
||||
@@ -2,8 +2,13 @@
|
||||
|
||||
let statusTimeoutId = null;
|
||||
let statusHideTimeoutId = null;
|
||||
let statusReplayTimeoutId = null;
|
||||
const STATUS_BASE_CLASS = "earth-status-message";
|
||||
const STATUS_DISPLAY_MS = 3000;
|
||||
const STATUS_FADE_MS = 280;
|
||||
let statusQueue = [];
|
||||
let statusBusy = false;
|
||||
let loadingActive = false;
|
||||
let loadingLockedWidth = 0;
|
||||
|
||||
function getElement(id) {
|
||||
return document.getElementById(id);
|
||||
@@ -19,53 +24,97 @@ function clearStatusTimers() {
|
||||
clearTimeout(statusTimeoutId);
|
||||
statusTimeoutId = null;
|
||||
}
|
||||
|
||||
if (statusHideTimeoutId) {
|
||||
clearTimeout(statusHideTimeoutId);
|
||||
statusHideTimeoutId = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (statusReplayTimeoutId) {
|
||||
clearTimeout(statusReplayTimeoutId);
|
||||
statusReplayTimeoutId = null;
|
||||
function clearLoadingWidthLock(statusEl) {
|
||||
loadingLockedWidth = 0;
|
||||
if (statusEl) {
|
||||
statusEl.style.minWidth = "";
|
||||
}
|
||||
}
|
||||
|
||||
// Show status message
|
||||
export function showStatusMessage(message, type = "info") {
|
||||
function updateLoadingWidthLock(statusEl) {
|
||||
if (!statusEl || !loadingActive) return;
|
||||
const nextWidth = Math.ceil(statusEl.getBoundingClientRect().width || statusEl.scrollWidth || 0);
|
||||
if (nextWidth <= 0) return;
|
||||
loadingLockedWidth = Math.max(loadingLockedWidth, nextWidth);
|
||||
statusEl.style.minWidth = `${loadingLockedWidth}px`;
|
||||
}
|
||||
|
||||
function buildStatusContent(statusEl, message, type) {
|
||||
statusEl.innerHTML = "";
|
||||
|
||||
const indicator = document.createElement("span");
|
||||
indicator.className = "earth-status-indicator";
|
||||
indicator.setAttribute("aria-hidden", "true");
|
||||
|
||||
const dotCount = type === "loading" ? 3 : 1;
|
||||
for (let i = 0; i < dotCount; i++) {
|
||||
const dot = document.createElement("span");
|
||||
dot.className = "earth-status-dot";
|
||||
indicator.appendChild(dot);
|
||||
}
|
||||
|
||||
const text = document.createElement("span");
|
||||
text.className = "earth-status-text";
|
||||
text.textContent = message;
|
||||
|
||||
statusEl.appendChild(indicator);
|
||||
statusEl.appendChild(text);
|
||||
}
|
||||
|
||||
function hideStatusElement(statusEl, onHidden) {
|
||||
statusEl.classList.remove("visible");
|
||||
statusHideTimeoutId = setTimeout(() => {
|
||||
if (!statusEl.classList.contains("visible")) {
|
||||
setElementDisplay(statusEl, false);
|
||||
statusEl.className = STATUS_BASE_CLASS;
|
||||
statusEl.innerHTML = "";
|
||||
}
|
||||
statusHideTimeoutId = null;
|
||||
if (typeof onHidden === "function") {
|
||||
onHidden();
|
||||
}
|
||||
}, STATUS_FADE_MS);
|
||||
}
|
||||
|
||||
function processStatusQueue() {
|
||||
if (loadingActive || statusBusy || statusQueue.length === 0) return;
|
||||
const next = statusQueue.shift();
|
||||
if (!next) return;
|
||||
startTransientStatus(next.message, next.type);
|
||||
}
|
||||
|
||||
function startTransientStatus(message, type = "info") {
|
||||
const statusEl = getElement("status-message");
|
||||
if (!statusEl) return;
|
||||
|
||||
clearStatusTimers();
|
||||
statusBusy = true;
|
||||
|
||||
const startShow = () => {
|
||||
statusEl.textContent = message;
|
||||
statusEl.className = `${STATUS_BASE_CLASS} ${type}`;
|
||||
setElementDisplay(statusEl, true);
|
||||
statusEl.offsetHeight;
|
||||
statusEl.classList.add("visible");
|
||||
buildStatusContent(statusEl, message, type);
|
||||
statusEl.className = `${STATUS_BASE_CLASS} ${type}`;
|
||||
setElementDisplay(statusEl, true, "inline-flex");
|
||||
statusEl.offsetHeight;
|
||||
statusEl.classList.add("visible");
|
||||
|
||||
statusTimeoutId = setTimeout(() => {
|
||||
statusEl.classList.remove("visible");
|
||||
statusHideTimeoutId = setTimeout(() => {
|
||||
setElementDisplay(statusEl, false);
|
||||
statusEl.textContent = "";
|
||||
statusHideTimeoutId = null;
|
||||
}, 280);
|
||||
statusTimeoutId = null;
|
||||
}, 3000);
|
||||
};
|
||||
statusTimeoutId = setTimeout(() => {
|
||||
hideStatusElement(statusEl, () => {
|
||||
statusBusy = false;
|
||||
processStatusQueue();
|
||||
});
|
||||
statusTimeoutId = null;
|
||||
}, STATUS_DISPLAY_MS);
|
||||
}
|
||||
|
||||
if (statusEl.classList.contains("visible")) {
|
||||
statusEl.classList.remove("visible");
|
||||
statusReplayTimeoutId = setTimeout(() => {
|
||||
startShow();
|
||||
statusReplayTimeoutId = null;
|
||||
}, 180);
|
||||
return;
|
||||
}
|
||||
|
||||
startShow();
|
||||
// Show status message
|
||||
export function showStatusMessage(message, type = "info") {
|
||||
statusQueue.push({ message, type });
|
||||
processStatusQueue();
|
||||
}
|
||||
|
||||
// Update coordinates display
|
||||
@@ -120,23 +169,49 @@ export function updateEarthStats(stats) {
|
||||
textureQualityEl.textContent = stats.textureQuality || "8K 卫星图";
|
||||
}
|
||||
|
||||
// Show/hide loading
|
||||
// Show/hide loading via status message
|
||||
export function setLoading(loading) {
|
||||
const loadingEl = getElement("loading");
|
||||
if (!loadingEl) return;
|
||||
setElementDisplay(loadingEl, loading);
|
||||
const statusEl = getElement("status-message");
|
||||
if (!statusEl) return;
|
||||
|
||||
if (loading) {
|
||||
clearStatusTimers();
|
||||
loadingActive = true;
|
||||
statusBusy = false;
|
||||
clearLoadingWidthLock(statusEl);
|
||||
buildStatusContent(statusEl, "正在加载...", "loading");
|
||||
statusEl.className = `${STATUS_BASE_CLASS} loading`;
|
||||
setElementDisplay(statusEl, true, "inline-flex");
|
||||
statusEl.offsetHeight;
|
||||
statusEl.classList.add("visible");
|
||||
requestAnimationFrame(() => {
|
||||
updateLoadingWidthLock(statusEl);
|
||||
});
|
||||
} else {
|
||||
if (!statusEl.classList.contains("loading")) {
|
||||
loadingActive = false;
|
||||
clearLoadingWidthLock(statusEl);
|
||||
processStatusQueue();
|
||||
return;
|
||||
}
|
||||
loadingActive = false;
|
||||
hideStatusElement(statusEl, () => {
|
||||
clearLoadingWidthLock(statusEl);
|
||||
statusBusy = false;
|
||||
processStatusQueue();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export function setLoadingMessage(title, subtitle = "") {
|
||||
const titleEl = getElement("loading-title");
|
||||
const subtitleEl = getElement("loading-subtitle");
|
||||
|
||||
if (titleEl) {
|
||||
titleEl.textContent = title;
|
||||
}
|
||||
|
||||
if (subtitleEl) {
|
||||
subtitleEl.textContent = subtitle;
|
||||
export function setLoadingMessage(title) {
|
||||
const statusEl = getElement("status-message");
|
||||
if (!statusEl || !statusEl.classList.contains("loading")) return;
|
||||
const textEl = statusEl.querySelector(".earth-status-text");
|
||||
if (textEl) {
|
||||
textEl.textContent = title;
|
||||
requestAnimationFrame(() => {
|
||||
updateLoadingWidthLock(statusEl);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -177,12 +252,16 @@ export function hideError() {
|
||||
|
||||
export function clearUiState() {
|
||||
clearStatusTimers();
|
||||
statusQueue = [];
|
||||
statusBusy = false;
|
||||
loadingActive = false;
|
||||
|
||||
const statusEl = getElement("status-message");
|
||||
if (statusEl) {
|
||||
statusEl.className = STATUS_BASE_CLASS;
|
||||
setElementDisplay(statusEl, false);
|
||||
statusEl.textContent = "";
|
||||
statusEl.innerHTML = "";
|
||||
clearLoadingWidthLock(statusEl);
|
||||
}
|
||||
|
||||
hideTooltip();
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[project]
|
||||
name = "planet"
|
||||
version = "0.29.0"
|
||||
version = "0.29.1"
|
||||
description = "智能星球计划 - 态势感知系统"
|
||||
requires-python = ">=3.14"
|
||||
dependencies = [
|
||||
|
||||
Reference in New Issue
Block a user