release: bump version to 0.41.0

This commit is contained in:
linkong
2026-04-27 13:58:29 +08:00
parent f9c1334365
commit 3ea99a9529
25 changed files with 1652 additions and 171 deletions

View File

@@ -29,6 +29,11 @@ import {
clearLockedObject,
clearLockedObjectAndInfo,
setCablesEnabled,
setCountryBoundariesEnabled,
setHighResTextureEnabled,
getHighResTextureEnabled,
setAtmosphereCloudsEnabled,
getAtmosphereCloudsEnabled,
setSatellitesEnabled,
getSatellitesEnabled,
} from "./main.js";
@@ -41,6 +46,7 @@ import {
} from "./satellites.js";
import { getShowCables } from "./cables.js";
import { toggleBGP, getShowBGP, getBGPCount } from "./bgp.js";
import { getShowCountryBoundaries } from "./country-boundaries.js";
import {
toggleComputeCenters,
getShowComputeCenters,
@@ -73,6 +79,7 @@ export let rotationMode = ROTATION_MODE.ROTATE;
let dayNightEnabled = true;
let defaultEarthZoom = CONFIG.defaultViewZoom;
let activeCamera = null;
let settingsApplyPromise = Promise.resolve();
let earthObj = null;
let listeners = [];
@@ -331,9 +338,31 @@ function getMobileLayerButtons(layerId) {
).filter((button) => button instanceof HTMLButtonElement);
}
function getLayerDisabledState(layerId) {
if (layerId === "trails" && !getSatellitesEnabled()) {
return {
disabled: true,
statusText: "不可用",
tooltip: "卫星关闭时不可用",
};
}
if (layerId === "terrain" && !getHighResTextureEnabled()) {
return {
disabled: true,
statusText: "不可用",
tooltip: "高清材质关闭时不可用",
};
}
return {
disabled: false,
statusText: null,
tooltip: null,
};
}
function syncMobileLayerCards() {
const summary = document.getElementById("mobile-layer-summary");
const definitions = getSortedLayerDefinitions();
const definitions = getDisplayLayerDefinitions();
let activeCount = 0;
definitions.forEach((definition) => {
@@ -342,11 +371,19 @@ function syncMobileLayerCards() {
activeCount += 1;
}
getMobileLayerButtons(definition.id).forEach((button) => {
const disabledState = getLayerDisabledState(definition.id);
button.classList.toggle("is-active", visible);
button.classList.toggle("is-disabled", disabledState.disabled);
button.disabled = disabledState.disabled;
button.setAttribute("aria-checked", visible ? "true" : "false");
if (disabledState.tooltip) {
button.title = disabledState.tooltip;
} else {
button.removeAttribute("title");
}
const status = button.querySelector("[data-mobile-layer-status]");
if (status) {
status.textContent = visible ? "开启" : "关闭";
status.textContent = disabledState.statusText || (visible ? "开启" : "关闭");
}
});
});
@@ -360,7 +397,7 @@ function renderMobileLayerCards() {
const list = document.getElementById("mobile-layer-list");
if (!(list instanceof HTMLElement)) return;
const definitions = getSortedLayerDefinitions();
const definitions = getDisplayLayerDefinitions();
list.innerHTML = definitions
.map((definition) => `
<button
@@ -384,6 +421,7 @@ function renderMobileLayerCards() {
bindListener(button, "click", async (event) => {
const target = event.currentTarget;
if (!(target instanceof HTMLButtonElement)) return;
if (target.disabled || target.classList.contains("is-disabled")) return;
const layerId = target.dataset.mobileLayerButton;
const definition = layerId ? getLayerDefinition(layerId) : null;
if (!definition) return;
@@ -594,6 +632,21 @@ function getSortedLayerDefinitions({ includeUnprioritized = true } = {}) {
.sort(compareLayerDefinitionsByStartupPriority);
}
function getDisplayLayerDefinitions() {
return Array.from(layerRegistry.values()).sort((left, right) => {
const leftOrder = Number.isFinite(left?.displayOrder)
? left.displayOrder
: Number.POSITIVE_INFINITY;
const rightOrder = Number.isFinite(right?.displayOrder)
? right.displayOrder
: Number.POSITIVE_INFINITY;
if (leftOrder !== rightOrder) {
return leftOrder - rightOrder;
}
return String(left?.id || "").localeCompare(String(right?.id || ""));
});
}
function shouldIncludeLayerInStartupLoad(definition) {
if (!Number.isFinite(definition?.startupPriority)) {
return false;
@@ -658,13 +711,22 @@ function getCurrentSharedSettingsSnapshot() {
};
}
function getDefaultLayerVisibilitySnapshot() {
return Object.fromEntries(
getPersistedLayers().map((layer) => [layer.id, Boolean(layer.defaultActive)]),
);
}
function captureEarthSettingsDefaults() {
if (!earthSettingsDefaults) {
const panelVisibility = getCurrentPanelVisibilitySnapshot();
const shared = getCurrentSharedSettingsSnapshot();
earthSettingsDefaults = {
version: 2,
shared,
shared: {
...shared,
layerVisibility: getDefaultLayerVisibilitySnapshot(),
},
views: {
desktop: {
panelVisibility: { ...panelVisibility },
@@ -680,7 +742,7 @@ function captureEarthSettingsDefaults() {
function cloneEarthSettings(settings) {
return {
version: 2,
version: 3,
shared: {
rotationMode: settings.shared.rotationMode,
cruiseModules: [...(settings.shared.cruiseModules || DEFAULT_CRUISE_MODULES)],
@@ -753,6 +815,10 @@ function normalizeEarthSettings(rawSettings, defaults) {
}
});
if ((rawSettings?.version || 0) < 3 && inputLayerVisibility.gridLines === true) {
normalizedLayerVisibility.gridLines = defaults.shared.layerVisibility.gridLines;
}
const nextRotationMode =
sharedSettings?.rotationMode === ROTATION_MODE.CRUISE
? ROTATION_MODE.CRUISE
@@ -779,7 +845,7 @@ function normalizeEarthSettings(rawSettings, defaults) {
);
return {
version: 2,
version: 3,
shared: {
rotationMode: nextRotationMode,
cruiseModules: nextCruiseModules.length > 0
@@ -805,7 +871,7 @@ function normalizeEarthSettings(rawSettings, defaults) {
}
function getPersistedLayers() {
return getSortedLayerDefinitions().filter((layer) => layer.persist !== false);
return getDisplayLayerDefinitions().filter((layer) => layer.persist !== false);
}
function getLayerDefinition(layerId) {
@@ -1149,6 +1215,7 @@ async function setSatellitesLayerEnabled(button, enabled, { persist = true, sile
} else if (enabled) {
setEarthStatValue("satellite-count", `${getSatelliteCount()}`);
}
syncTrailsAvailability();
syncMobileLayerCards();
if (persist) persistEarthSettings();
return enabled;
@@ -1179,6 +1246,61 @@ function setGridLinesLayerEnabled(button, enabled, { persist = true, silent = fa
return enabled;
}
async function setCountryBoundariesLayerEnabled(button, enabled, { persist = true, silent = false } = {}) {
try {
if (enabled) {
setLayerButtonState(button, {
active: false,
loading: true,
tooltip: "国界加载中...",
});
}
await setCountryBoundariesEnabled(enabled, { suppressStatus: silent });
setLayerButtonState(button, {
active: enabled,
loading: false,
tooltip: enabled ? "隐藏国界" : "显示国界",
});
syncMobileLayerCards();
if (persist) persistEarthSettings();
return enabled;
} catch (error) {
console.error("切换国界显示失败:", error);
setLayerButtonState(button, {
active: false,
loading: false,
tooltip: "显示国界",
});
syncMobileLayerCards();
if (persist) persistEarthSettings();
return false;
}
}
function setHighResTextureLayerEnabled(button, enabled, { persist = true, silent = false } = {}) {
setHighResTextureEnabled(enabled, { suppressStatus: silent });
setLayerButtonState(button, {
active: enabled,
loading: false,
tooltip: enabled ? "隐藏高清材质" : "显示高清材质",
});
syncMobileLayerCards();
if (persist) persistEarthSettings();
return enabled;
}
function setAtmosphereCloudsLayerEnabled(button, enabled, { persist = true, silent = false } = {}) {
setAtmosphereCloudsEnabled(enabled, { suppressStatus: silent });
setLayerButtonState(button, {
active: enabled,
loading: false,
tooltip: enabled ? "隐藏大气云图" : "显示大气云图",
});
syncMobileLayerCards();
if (persist) persistEarthSettings();
return enabled;
}
function setBGPLayerEnabled(button, enabled, { persist = true, silent = false } = {}) {
clearSelectionIfHiding(!enabled);
toggleBGP(enabled);
@@ -1216,9 +1338,11 @@ function setComputeCentersLayerEnabled(button, enabled, { persist = true, silent
function setTrailsLayerEnabled(button, enabled, { persist = true, silent = false } = {}) {
toggleTrails(enabled);
const disabledState = getLayerDisabledState("trails");
setLayerButtonState(button, {
active: enabled,
tooltip: enabled ? "隐藏轨迹" : "显示轨迹",
disabled: disabledState.disabled,
tooltip: disabledState.tooltip || (enabled ? "隐藏轨迹" : "显示轨迹"),
});
syncMobileLayerCards();
if (persist) persistEarthSettings();
@@ -1228,6 +1352,17 @@ function setTrailsLayerEnabled(button, enabled, { persist = true, silent = false
return enabled;
}
function syncTrailsAvailability() {
const trailsEnabled = getShowTrails();
const disabledState = getLayerDisabledState("trails");
setLayerButtonState(getLayerButton("trails"), {
active: trailsEnabled,
disabled: disabledState.disabled,
tooltip: disabledState.tooltip || (trailsEnabled ? "隐藏轨迹" : "显示轨迹"),
});
syncMobileLayerCards();
}
async function setCablesLayerEnabled(button, enabled, { persist = true, silent = false } = {}) {
clearSelectionIfHiding(!enabled);
try {
@@ -1253,23 +1388,6 @@ async function applyLayerVisibilitySettings(layerVisibility = {}, options = {})
function getBuiltinLayerDefinitions() {
return [
{
id: "terrain",
buttonId: "toggle-terrain",
icon: "landscape",
label: "地形",
meta: "Terrain",
keywords: "地形 terrain",
defaultActive: false,
startupPriority: null,
startupMode: "visible",
startupLabel: "地形",
startupMessage: "正在渲染地形...",
statusTarget: "terrain-status",
getVisible: () => showTerrain,
setVisible: (visible, options = {}) =>
setTerrainEnabled(getLayerButton("terrain"), visible, options),
},
{
id: "gridLines",
buttonId: "toggle-grid-lines",
@@ -1277,8 +1395,9 @@ function getBuiltinLayerDefinitions() {
label: "经纬线",
meta: "Graticule",
keywords: "经纬线 graticule 经纬 latitude longitude",
defaultActive: true,
startupPriority: null,
defaultActive: false,
displayOrder: 100,
startupPriority: 10,
startupMode: "visible",
startupLabel: "经纬线",
startupMessage: "",
@@ -1287,36 +1406,55 @@ function getBuiltinLayerDefinitions() {
setGridLinesLayerEnabled(getLayerButton("gridLines"), visible, options),
},
{
id: "satellites",
buttonId: "toggle-satellites",
icon: "satellite_alt",
label: "卫星",
meta: "Satellites",
keywords: "卫星 satellites",
defaultActive: false,
startupPriority: 30,
startupMode: "visible",
startupLabel: "卫星",
startupMessage: "正在加载卫星...",
getVisible: () => getSatellitesEnabled(),
id: "countryBoundaries",
buttonId: "toggle-country-boundaries",
icon: "public",
label: "国界",
meta: "Country Borders",
keywords: "国界 国家 borders countries boundary",
defaultActive: true,
displayOrder: 90,
startupPriority: 20,
startupMode: "preload",
startupLabel: "海陆基座",
startupMessage: "正在加载海陆基座...",
getVisible: () => getShowCountryBoundaries(),
setVisible: (visible, options = {}) =>
setSatellitesLayerEnabled(getLayerButton("satellites"), visible, options),
setCountryBoundariesLayerEnabled(getLayerButton("countryBoundaries"), visible, options),
},
{
id: "trails",
buttonId: "toggle-trails",
icon: "timeline",
label: "轨迹",
meta: "Trails",
keywords: "轨迹 trails",
id: "earthHighResTexture",
buttonId: "toggle-earth-high-res-texture",
icon: "globe",
label: "高清材质",
meta: "High-Res Texture",
keywords: "高清 材质 纹理 texture hd 地表 earth",
defaultActive: true,
startupPriority: null,
displayOrder: 70,
startupPriority: 30,
startupMode: "visible",
startupLabel: "轨迹",
startupMessage: "",
getVisible: () => getShowTrails(),
startupLabel: "高清材质",
startupMessage: "正在启用高清材质...",
getVisible: () => getHighResTextureEnabled(),
setVisible: (visible, options = {}) =>
setTrailsLayerEnabled(getLayerButton("trails"), visible, options),
setHighResTextureLayerEnabled(getLayerButton("earthHighResTexture"), visible, options),
},
{
id: "atmosphereClouds",
buttonId: "toggle-atmosphere-clouds",
icon: "cloud",
label: "大气云图",
meta: "Cloud Layer",
keywords: "大气 云图 云层 clouds atmosphere",
defaultActive: true,
displayOrder: 80,
startupPriority: 40,
startupMode: "visible",
startupLabel: "大气云图",
startupMessage: "",
getVisible: () => getAtmosphereCloudsEnabled(),
setVisible: (visible, options = {}) =>
setAtmosphereCloudsLayerEnabled(getLayerButton("atmosphereClouds"), visible, options),
},
{
id: "cables",
@@ -1326,7 +1464,8 @@ function getBuiltinLayerDefinitions() {
meta: "Subsea Cables",
keywords: "海缆 subsea cables",
defaultActive: true,
startupPriority: 20,
displayOrder: 10,
startupPriority: 50,
startupMode: "visible",
startupLabel: "海缆",
startupMessage: {
@@ -1345,7 +1484,8 @@ function getBuiltinLayerDefinitions() {
meta: "Compute Centers",
keywords: "算力中心 compute centers gpu 超算",
defaultActive: true,
startupPriority: 35,
displayOrder: 40,
startupPriority: 60,
startupMode: "preload",
startupLabel: "算力中心",
startupMessage: "正在加载算力中心...",
@@ -1361,7 +1501,8 @@ function getBuiltinLayerDefinitions() {
meta: "Routing Signals",
keywords: "bgp观测 routing signals",
defaultActive: true,
startupPriority: 40,
displayOrder: 50,
startupPriority: 70,
startupMode: "preload",
startupLabel: "BGP态势",
startupMessage: "正在加载BGP态势...",
@@ -1369,6 +1510,58 @@ function getBuiltinLayerDefinitions() {
setVisible: (visible, options = {}) =>
setBGPLayerEnabled(getLayerButton("bgp"), visible, options),
},
{
id: "satellites",
buttonId: "toggle-satellites",
icon: "satellite_alt",
label: "卫星",
meta: "Satellites",
keywords: "卫星 satellites",
defaultActive: false,
displayOrder: 30,
startupPriority: 80,
startupMode: "visible",
startupLabel: "卫星",
startupMessage: "正在加载卫星...",
getVisible: () => getSatellitesEnabled(),
setVisible: (visible, options = {}) =>
setSatellitesLayerEnabled(getLayerButton("satellites"), visible, options),
},
{
id: "trails",
buttonId: "toggle-trails",
icon: "timeline",
label: "轨迹",
meta: "Trails",
keywords: "轨迹 trails",
defaultActive: true,
displayOrder: 20,
startupPriority: null,
startupMode: "visible",
startupLabel: "轨迹",
startupMessage: "",
getVisible: () => getShowTrails(),
setVisible: (visible, options = {}) =>
setTrailsLayerEnabled(getLayerButton("trails"), visible, options),
},
{
id: "terrain",
buttonId: "toggle-terrain",
icon: "landscape",
label: "地形",
meta: "Terrain",
keywords: "地形 terrain",
defaultActive: false,
displayOrder: 60,
startupPriority: null,
startupMode: "visible",
startupLabel: "地形",
startupMessage: "正在渲染地形...",
statusTarget: "terrain-status",
getVisible: () => showTerrain,
setVisible: (visible, options = {}) =>
setTerrainEnabled(getLayerButton("terrain"), visible, options),
},
];
}
@@ -1430,6 +1623,7 @@ function syncLayerRowDefinition(definition, { appendIfMissing = false } = {}) {
function registerLayerDefinition(definition, options = {}) {
const normalizedDefinition = {
persist: true,
displayOrder: null,
startupPriority: null,
startupMode: "visible",
startupLabel: "",
@@ -1818,6 +2012,31 @@ function applyDayNightEnabled(enabled, { persist = true } = {}) {
if (persist) persistEarthSettings();
}
export function setDayNightEnabledExternal(enabled, { persist = true } = {}) {
applyDayNightEnabled(enabled, { persist });
}
export function getDayNightEnabled() {
return dayNightEnabled;
}
export function setTerrainLayerInteractable(enabled) {
const button = getLayerButton("terrain");
setLayerButtonState(button, {
disabled: !enabled,
tooltip: enabled ? null : "高清材质关闭时不可用",
});
syncMobileLayerCards();
}
export function setDayNightInteractable(enabled) {
document.querySelectorAll("#toggle-daynight, [data-daynight-toggle]").forEach((input) => {
input.disabled = !enabled;
const label = input.closest("label");
if (label) label.classList.toggle("is-disabled", !enabled);
});
}
function setupSettingsControls() {
const settingsTrigger = document.getElementById("settings-trigger");
const settingsClose = document.getElementById("settings-close");
@@ -1960,7 +2179,7 @@ function setupSettingsControls() {
});
captureEarthSettingsDefaults();
applyEarthSettings(loadEarthSettings());
settingsApplyPromise = applyEarthSettings(loadEarthSettings());
syncAllHudPanelToggles();
syncRotationModeButtons();
syncCruiseModuleControls();
@@ -2283,7 +2502,7 @@ function resetCleanup() {
listeners = [];
}
export function setupControls(camera, renderer, scene, earth) {
export async function setupControls(camera, renderer, scene, earth) {
resetCleanup();
activeCamera = camera;
earthObj = earth;
@@ -2292,6 +2511,8 @@ export function setupControls(camera, renderer, scene, earth) {
setupWheelZoom(camera, renderer);
setupRotateControls(camera, earth);
setupTerrainControls();
await settingsApplyPromise;
syncTrailsAvailability();
setupLiquidGlassInteractions();
setupToolbarHubCluster();
setupKeyboardControls();
@@ -2622,7 +2843,11 @@ function bindLayerButton(row, definition) {
if (button.dataset.layerBound === "true") return;
bindListener(button, "click", async function () {
if (this.classList.contains("is-loading")) {
if (
this.disabled ||
this.classList.contains("is-loading") ||
this.classList.contains("is-disabled")
) {
return;
}
await definition.setVisible(!definition.getVisible());
@@ -2638,6 +2863,7 @@ export function registerLayer({
keywords = "",
defaultActive = false,
persist = true,
displayOrder = null,
startupPriority = null,
startupMode = "visible",
startupLabel = "",
@@ -2658,6 +2884,7 @@ export function registerLayer({
keywords,
defaultActive,
persist,
displayOrder,
startupPriority,
startupMode,
startupLabel,