release: bump version to 0.37.0
This commit is contained in:
242
frontend/public/earth/js/controls.js
vendored
242
frontend/public/earth/js/controls.js
vendored
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user