release: bump version to 0.37.0

This commit is contained in:
rayd1o
2026-04-23 07:56:10 +08:00
parent abe04030fb
commit 67f82dc41c
22 changed files with 1417 additions and 226 deletions

View File

@@ -86,7 +86,8 @@ const SETTINGS_MODAL_CLOSE_ANIMATION_MS = 320;
const SETTINGS_SHEET_MIN_SCALE = 0.06;
const SETTINGS_SHEET_MAX_SCALE_X = 0.22;
const SETTINGS_SHEET_MAX_SCALE_Y = 0.18;
const EARTH_SETTINGS_STORAGE_KEY = "planet.earth.settings.v1";
const EARTH_SETTINGS_STORAGE_KEY = "planet.earth.settings.v2";
const LEGACY_EARTH_SETTINGS_STORAGE_KEY = "planet.earth.settings.v1";
const DEFAULT_EARTH_ZOOM_STEP = 0.01;
let settingsModalTimer = null;
let settingsSheetAnimation = null;
@@ -95,6 +96,7 @@ let terrainPrefetchStarted = false;
let terrainPrefetchScheduled = false;
let focusViewAnimationToken = 0;
let earthSettingsDefaults = null;
let earthSettingsState = null;
let layerRegistry = new Map();
let layerPanelInitialized = false;
let layoutMode = "desktop";
@@ -118,6 +120,10 @@ function detectLayoutMode() {
return "desktop";
}
function getSettingsViewportScope(mode = layoutMode) {
return mode === "mobile" ? "mobile" : "desktop";
}
export function getLayoutMode() {
return layoutMode;
}
@@ -225,6 +231,7 @@ function closeTransientMobileOverlays({ except = null } = {}) {
}
function applyResponsiveLayout() {
const previousLayoutMode = layoutMode;
layoutMode = detectLayoutMode();
const isMobile = isMobileLayout();
@@ -233,6 +240,7 @@ function applyResponsiveLayout() {
document.documentElement.classList.toggle("layout-mode-mobile", isMobile);
document.documentElement.classList.toggle("layout-mode-compact", isCompact);
document.documentElement.dataset.earthLayoutMode = layoutMode;
document.body.classList.toggle("layout-mode-mobile", isMobile);
document.body.classList.toggle("layout-mode-compact", isCompact);
container?.classList.toggle("layout-mode-mobile", isMobile);
@@ -243,6 +251,16 @@ function applyResponsiveLayout() {
mobileDrawerOpen = false;
}
if (previousLayoutMode !== layoutMode) {
if (isMobile) {
resetDesktopHudPanelsForMobile();
}
if (earthSettingsState) {
applyCurrentViewportPanelVisibility({ persist: false });
}
}
syncMobileDrawerState();
}
@@ -281,6 +299,10 @@ function setMobileDrawerState({ open = mobileDrawerOpen, card = mobileDrawerCard
ensureTVPanelReady().catch((error) => {
console.error("初始化媒体抽屉失败:", error);
});
} else if (mobileDrawerCard === "news") {
ensureNewsPanelReady().catch((error) => {
console.error("初始化新闻抽屉失败:", error);
});
}
}
@@ -593,18 +615,19 @@ function canUseLocalStorage() {
}
}
function getCurrentSettingsSnapshot() {
const panelVisibility = Object.fromEntries(
function getCurrentPanelVisibilitySnapshot() {
return Object.fromEntries(
HUD_PANEL_IDS.map((panelId) => {
const panel = document.getElementById(panelId);
const visible = !panel?.classList.contains("hud-panel-hidden");
return [panelId, visible];
}),
);
}
function getCurrentSharedSettingsSnapshot() {
return {
rotationMode,
panelVisibility,
layerVisibility: Object.fromEntries(
getPersistedLayers().map((layer) => [layer.id, Boolean(layer.getVisible?.())]),
),
@@ -616,37 +639,87 @@ function getCurrentSettingsSnapshot() {
function captureEarthSettingsDefaults() {
if (!earthSettingsDefaults) {
earthSettingsDefaults = getCurrentSettingsSnapshot();
const panelVisibility = getCurrentPanelVisibilitySnapshot();
const shared = getCurrentSharedSettingsSnapshot();
earthSettingsDefaults = {
version: 2,
shared,
views: {
desktop: {
panelVisibility: { ...panelVisibility },
},
mobile: {
panelVisibility: { ...panelVisibility },
},
},
};
}
return earthSettingsDefaults;
}
function cloneEarthSettings(settings) {
return {
rotationMode: settings.rotationMode,
terrainOpacity: settings.terrainOpacity,
dayNightEnabled: settings.dayNightEnabled,
defaultEarthZoom: settings.defaultEarthZoom,
panelVisibility: { ...(settings.panelVisibility || {}) },
layerVisibility: { ...(settings.layerVisibility || {}) },
version: 2,
shared: {
rotationMode: settings.shared.rotationMode,
terrainOpacity: settings.shared.terrainOpacity,
dayNightEnabled: settings.shared.dayNightEnabled,
defaultEarthZoom: settings.shared.defaultEarthZoom,
layerVisibility: { ...(settings.shared.layerVisibility || {}) },
},
views: {
desktop: {
panelVisibility: {
...(settings.views?.desktop?.panelVisibility || {}),
},
},
mobile: {
panelVisibility: {
...(settings.views?.mobile?.panelVisibility || {}),
},
},
},
};
}
function normalizeEarthSettings(rawSettings, defaults) {
const normalizedPanelVisibility = { ...defaults.panelVisibility };
const normalizedLayerVisibility = { ...defaults.layerVisibility };
const inputPanelVisibility =
rawSettings && typeof rawSettings.panelVisibility === "object"
? rawSettings.panelVisibility
const normalizedDesktopPanelVisibility = {
...defaults.views.desktop.panelVisibility,
};
const normalizedMobilePanelVisibility = {
...defaults.views.mobile.panelVisibility,
};
const normalizedLayerVisibility = {
...defaults.shared.layerVisibility,
};
const sharedSettings =
rawSettings && typeof rawSettings.shared === "object"
? rawSettings.shared
: rawSettings;
const inputDesktopPanelVisibility =
rawSettings?.views?.desktop && typeof rawSettings.views.desktop.panelVisibility === "object"
? rawSettings.views.desktop.panelVisibility
: rawSettings && typeof rawSettings.panelVisibility === "object"
? rawSettings.panelVisibility
: {};
const inputMobilePanelVisibility =
rawSettings?.views?.mobile && typeof rawSettings.views.mobile.panelVisibility === "object"
? rawSettings.views.mobile.panelVisibility
: {};
const inputLayerVisibility =
rawSettings && typeof rawSettings.layerVisibility === "object"
? rawSettings.layerVisibility
sharedSettings && typeof sharedSettings.layerVisibility === "object"
? sharedSettings.layerVisibility
: {};
Object.entries(inputPanelVisibility).forEach(([panelId, visible]) => {
if (panelId in normalizedPanelVisibility) {
normalizedPanelVisibility[panelId] = Boolean(visible);
Object.entries(inputDesktopPanelVisibility).forEach(([panelId, visible]) => {
if (panelId in normalizedDesktopPanelVisibility) {
normalizedDesktopPanelVisibility[panelId] = Boolean(visible);
}
});
Object.entries(inputMobilePanelVisibility).forEach(([panelId, visible]) => {
if (panelId in normalizedMobilePanelVisibility) {
normalizedMobilePanelVisibility[panelId] = Boolean(visible);
}
});
@@ -657,26 +730,36 @@ function normalizeEarthSettings(rawSettings, defaults) {
});
const nextRotationMode =
rawSettings?.rotationMode === ROTATION_MODE.CRUISE
sharedSettings?.rotationMode === ROTATION_MODE.CRUISE
? ROTATION_MODE.CRUISE
: defaults.rotationMode;
const nextTerrainOpacity = Number.parseFloat(rawSettings?.terrainOpacity);
const nextDayNightEnabled = typeof rawSettings?.dayNightEnabled === "boolean"
? rawSettings.dayNightEnabled
: defaults.dayNightEnabled;
: defaults.shared.rotationMode;
const nextTerrainOpacity = Number.parseFloat(sharedSettings?.terrainOpacity);
const nextDayNightEnabled = typeof sharedSettings?.dayNightEnabled === "boolean"
? sharedSettings.dayNightEnabled
: defaults.shared.dayNightEnabled;
const nextDefaultEarthZoom = clampEarthZoomLevel(
rawSettings?.defaultEarthZoom ?? defaults.defaultEarthZoom,
sharedSettings?.defaultEarthZoom ?? defaults.shared.defaultEarthZoom,
);
return {
rotationMode: nextRotationMode,
panelVisibility: normalizedPanelVisibility,
layerVisibility: normalizedLayerVisibility,
terrainOpacity: Number.isFinite(nextTerrainOpacity)
? nextTerrainOpacity
: defaults.terrainOpacity,
dayNightEnabled: nextDayNightEnabled,
defaultEarthZoom: nextDefaultEarthZoom,
version: 2,
shared: {
rotationMode: nextRotationMode,
layerVisibility: normalizedLayerVisibility,
terrainOpacity: Number.isFinite(nextTerrainOpacity)
? nextTerrainOpacity
: defaults.shared.terrainOpacity,
dayNightEnabled: nextDayNightEnabled,
defaultEarthZoom: nextDefaultEarthZoom,
},
views: {
desktop: {
panelVisibility: normalizedDesktopPanelVisibility,
},
mobile: {
panelVisibility: normalizedMobilePanelVisibility,
},
},
};
}
@@ -701,8 +784,10 @@ function loadEarthSettings() {
try {
const rawValue = window.localStorage.getItem(EARTH_SETTINGS_STORAGE_KEY);
if (!rawValue) return defaults;
const parsedValue = JSON.parse(rawValue);
const legacyRawValue = window.localStorage.getItem(LEGACY_EARTH_SETTINGS_STORAGE_KEY);
const sourceValue = rawValue || legacyRawValue;
if (!sourceValue) return defaults;
const parsedValue = JSON.parse(sourceValue);
return normalizeEarthSettings(parsedValue, defaults);
} catch (error) {
console.warn("读取 Earth 设置失败,已回退默认值:", error);
@@ -710,13 +795,33 @@ function loadEarthSettings() {
}
}
function getViewportPanelVisibility(settings, mode = layoutMode) {
const scope = getSettingsViewportScope(mode);
return settings?.views?.[scope]?.panelVisibility || {};
}
function syncEarthSettingsStateFromRuntime() {
const defaults = cloneEarthSettings(captureEarthSettingsDefaults());
const nextSettings = earthSettingsState
? cloneEarthSettings(earthSettingsState)
: defaults;
const scope = getSettingsViewportScope();
nextSettings.shared = getCurrentSharedSettingsSnapshot();
nextSettings.views[scope].panelVisibility = getCurrentPanelVisibilitySnapshot();
earthSettingsState = nextSettings;
return nextSettings;
}
function persistEarthSettings() {
if (!canUseLocalStorage()) return;
try {
const nextSettings = syncEarthSettingsStateFromRuntime();
window.localStorage.setItem(
EARTH_SETTINGS_STORAGE_KEY,
JSON.stringify(getCurrentSettingsSnapshot()),
JSON.stringify(nextSettings),
);
window.localStorage.removeItem(LEGACY_EARTH_SETTINGS_STORAGE_KEY);
} catch (error) {
console.warn("保存 Earth 设置失败:", error);
}
@@ -767,15 +872,11 @@ function setDefaultEarthZoom(nextZoom, { persist = true, applyToCurrentView = tr
async function applyEarthSettings(settings) {
if (!settings) return;
earthSettingsState = cloneEarthSettings(settings);
HUD_PANEL_IDS.forEach((panelId) => {
const visible = settings.panelVisibility?.[panelId];
if (typeof visible === "boolean") {
setHudPanelVisibility(panelId, visible, { persist: false });
}
});
applyCurrentViewportPanelVisibility({ persist: false });
const appliedOpacity = setTerrainOpacity(settings.terrainOpacity);
const appliedOpacity = setTerrainOpacity(settings.shared.terrainOpacity);
document.querySelectorAll("#terrain-opacity-slider, [data-terrain-opacity-slider]").forEach((slider) => {
if (slider instanceof HTMLInputElement) {
slider.value = appliedOpacity.toFixed(2);
@@ -787,18 +888,18 @@ async function applyEarthSettings(settings) {
}
});
setRotationMode(settings.rotationMode, { persist: false, suppressStatus: true });
setRotationMode(settings.shared.rotationMode, { persist: false, suppressStatus: true });
if (typeof settings.dayNightEnabled === "boolean") {
applyDayNightEnabled(settings.dayNightEnabled, { persist: false });
if (typeof settings.shared.dayNightEnabled === "boolean") {
applyDayNightEnabled(settings.shared.dayNightEnabled, { persist: false });
}
setDefaultEarthZoom(settings.defaultEarthZoom, {
setDefaultEarthZoom(settings.shared.defaultEarthZoom, {
persist: false,
applyToCurrentView: true,
});
await applyLayerVisibilitySettings(settings.layerVisibility, {
await applyLayerVisibilitySettings(settings.shared.layerVisibility, {
persist: false,
silent: true,
});
@@ -806,9 +907,11 @@ async function applyEarthSettings(settings) {
function resetEarthSettings() {
const defaults = cloneEarthSettings(captureEarthSettingsDefaults());
earthSettingsState = cloneEarthSettings(defaults);
if (canUseLocalStorage()) {
try {
window.localStorage.removeItem(EARTH_SETTINGS_STORAGE_KEY);
window.localStorage.removeItem(LEGACY_EARTH_SETTINGS_STORAGE_KEY);
} catch (error) {
console.warn("移除 Earth 设置失败:", error);
}
@@ -1443,6 +1546,10 @@ function setHudPanelVisibility(panelId, visible, { persist = true } = {}) {
const panel = document.getElementById(panelId);
if (!panel) return;
panel.classList.toggle("hud-panel-hidden", !visible);
const scope = getSettingsViewportScope();
if (earthSettingsState?.views?.[scope]?.panelVisibility) {
earthSettingsState.views[scope].panelVisibility[panelId] = visible;
}
if (!visible && activeMobileDrawerId === panelId) {
activeMobileDrawerId = null;
syncMobileDrawerState();
@@ -1451,10 +1558,13 @@ function setHudPanelVisibility(panelId, visible, { persist = true } = {}) {
if (panelId === "media-panel") {
updateTVToggleUI(visible);
updateNewsToggleUI(visible);
if (visible) {
if (visible && !isMobileLayout()) {
ensureTVPanelReady().catch((error) => {
console.error("初始化电视直播面板失败:", error);
});
ensureNewsPanelReady().catch((error) => {
console.error("初始化态势新闻内容失败:", error);
});
}
}
if (persist) {
@@ -1462,6 +1572,24 @@ function setHudPanelVisibility(panelId, visible, { persist = true } = {}) {
}
}
function resetDesktopHudPanelsForMobile() {
document.querySelectorAll(DRAGGABLE_PANEL_SELECTOR).forEach((panel) => {
if (!(panel instanceof HTMLElement)) return;
resetPanelInlineLayout(panel);
});
}
function applyCurrentViewportPanelVisibility({ persist = false } = {}) {
const panelVisibility = getViewportPanelVisibility(earthSettingsState, layoutMode);
HUD_PANEL_IDS.forEach((panelId) => {
const visible = panelVisibility?.[panelId];
if (typeof visible === "boolean") {
setHudPanelVisibility(panelId, visible, { persist });
}
});
syncAllHudPanelToggles();
}
function syncSettingsToggle(panelId, visible) {
const input = document.querySelector(
`[data-settings-panel="${panelId}"]`,
@@ -2424,17 +2552,19 @@ function setupTerrainControls() {
showStatusMessage(expanded ? "布局已最大化" : "布局已恢复", "info");
});
const mediaVisible = !document.getElementById("media-panel")?.classList.contains("hud-panel-hidden");
const mediaVisible =
!isMobileLayout() &&
!document.getElementById("media-panel")?.classList.contains("hud-panel-hidden");
updateTVToggleUI(mediaVisible);
if (mediaVisible) {
ensureTVPanelReady().catch((error) => {
console.error("初始化电视直播面板失败:", error);
});
ensureNewsPanelReady().catch((error) => {
console.error("初始化态势新闻内容失败:", error);
});
}
updateNewsToggleUI(mediaVisible);
ensureNewsPanelReady().catch((error) => {
console.error("初始化态势新闻内容失败:", error);
});
applyResponsiveLayout();
updateLayoutUI(container);
}