release: bump version to 0.32.0

This commit is contained in:
rayd1o
2026-04-22 04:41:39 +08:00
parent 437efc848c
commit 3ae4acdff8
16 changed files with 239 additions and 45 deletions

View File

@@ -44,6 +44,8 @@ export let showTerrain = false;
export let layoutExpanded = false;
export let rotationMode = ROTATION_MODE.ROTATE;
let dayNightEnabled = true;
let defaultEarthZoom = CONFIG.defaultViewZoom;
let activeCamera = null;
let earthObj = null;
let listeners = [];
@@ -73,6 +75,7 @@ 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 DEFAULT_EARTH_ZOOM_STEP = 0.01;
let settingsModalTimer = null;
let settingsSheetAnimation = null;
let terrainToggleToken = 0;
@@ -120,6 +123,26 @@ function shouldIncludeLayerInStartupLoad(definition) {
return Boolean(definition?.getVisible?.());
}
function clampEarthZoomLevel(nextZoom) {
const parsedZoom = Number.parseFloat(nextZoom);
if (!Number.isFinite(parsedZoom)) {
return CONFIG.defaultViewZoom;
}
return Math.min(CONFIG.maxZoom, Math.max(CONFIG.minZoom, parsedZoom));
}
function formatZoomPercent(zoom) {
return `${Math.round(zoom * 100)}%`;
}
function getZoomResetTooltipText(zoom) {
return `重置缩放到${formatZoomPercent(zoom)}`;
}
function getZoomResetStatusMessage(zoom) {
return `缩放已重置到${formatZoomPercent(zoom)}`;
}
function canUseLocalStorage() {
try {
return typeof window !== "undefined" && !!window.localStorage;
@@ -145,6 +168,7 @@ function getCurrentSettingsSnapshot() {
),
terrainOpacity: getTerrainOpacity(),
dayNightEnabled,
defaultEarthZoom,
};
}
@@ -160,6 +184,7 @@ function cloneEarthSettings(settings) {
rotationMode: settings.rotationMode,
terrainOpacity: settings.terrainOpacity,
dayNightEnabled: settings.dayNightEnabled,
defaultEarthZoom: settings.defaultEarthZoom,
panelVisibility: { ...(settings.panelVisibility || {}) },
layerVisibility: { ...(settings.layerVisibility || {}) },
};
@@ -197,6 +222,9 @@ function normalizeEarthSettings(rawSettings, defaults) {
const nextDayNightEnabled = typeof rawSettings?.dayNightEnabled === "boolean"
? rawSettings.dayNightEnabled
: defaults.dayNightEnabled;
const nextDefaultEarthZoom = clampEarthZoomLevel(
rawSettings?.defaultEarthZoom ?? defaults.defaultEarthZoom,
);
return {
rotationMode: nextRotationMode,
@@ -206,6 +234,7 @@ function normalizeEarthSettings(rawSettings, defaults) {
? nextTerrainOpacity
: defaults.terrainOpacity,
dayNightEnabled: nextDayNightEnabled,
defaultEarthZoom: nextDefaultEarthZoom,
};
}
@@ -251,6 +280,46 @@ function persistEarthSettings() {
}
}
function syncDefaultEarthZoomUi(nextZoom) {
const slider = document.getElementById("default-earth-size-slider");
const value = document.getElementById("default-earth-size-value");
const zoomValue = document.getElementById("zoom-value");
const tooltipText = getZoomResetTooltipText(nextZoom);
if (slider instanceof HTMLInputElement) {
slider.min = CONFIG.minZoom.toString();
slider.max = CONFIG.maxZoom.toString();
slider.step = DEFAULT_EARTH_ZOOM_STEP.toString();
slider.value = nextZoom.toFixed(2);
}
if (value) {
value.textContent = formatZoomPercent(nextZoom);
}
if (zoomValue instanceof HTMLElement) {
zoomValue.title = tooltipText;
const tooltip = zoomValue.querySelector(".tooltip");
if (tooltip) {
tooltip.textContent = tooltipText;
}
}
}
function setDefaultEarthZoom(nextZoom, { persist = true, applyToCurrentView = true } = {}) {
defaultEarthZoom = clampEarthZoomLevel(nextZoom);
syncDefaultEarthZoomUi(defaultEarthZoom);
if (applyToCurrentView && activeCamera) {
zoomLevel = defaultEarthZoom;
applyZoom(activeCamera);
}
if (persist) {
persistEarthSettings();
}
return defaultEarthZoom;
}
async function applyEarthSettings(settings) {
if (!settings) return;
@@ -277,6 +346,11 @@ async function applyEarthSettings(settings) {
applyDayNightEnabled(settings.dayNightEnabled, { persist: false });
}
setDefaultEarthZoom(settings.defaultEarthZoom, {
persist: false,
applyToCurrentView: true,
});
await applyLayerVisibilitySettings(settings.layerVisibility, {
persist: false,
silent: true,
@@ -660,7 +734,7 @@ export function applyImmediateView(targetEarthObj, camera, options = {}) {
const {
lat = EARTH_CONFIG.chinaLat,
rotLon = EARTH_CONFIG.chinaRotLon,
zoom = 1.0,
zoom = getDefaultEarthZoomLevel(),
} = options;
const nextRotation = getViewRotation(lat, rotLon);
@@ -956,6 +1030,7 @@ function setupSettingsControls() {
const terrainOpacitySlider = document.getElementById("terrain-opacity-slider");
const terrainOpacityValue = document.getElementById("terrain-opacity-value");
const defaultEarthSizeSlider = document.getElementById("default-earth-size-slider");
const rotationModeButtons = document.querySelectorAll("[data-rotation-mode]");
const syncTerrainOpacityUi = (nextOpacity) => {
const safeOpacity = Math.round(nextOpacity * 100);
@@ -968,6 +1043,7 @@ function setupSettingsControls() {
};
syncTerrainOpacityUi(getTerrainOpacity());
syncDefaultEarthZoomUi(defaultEarthZoom);
if (terrainOpacitySlider instanceof HTMLInputElement) {
bindListener(terrainOpacitySlider, "input", (event) => {
@@ -982,6 +1058,18 @@ function setupSettingsControls() {
});
}
if (defaultEarthSizeSlider instanceof HTMLInputElement) {
bindListener(defaultEarthSizeSlider, "input", (event) => {
const target = event.currentTarget;
if (!(target instanceof HTMLInputElement)) return;
const nextZoom = Number.parseFloat(target.value);
setDefaultEarthZoom(
Number.isFinite(nextZoom) ? nextZoom : defaultEarthZoom,
{ persist: true, applyToCurrentView: true },
);
});
}
rotationModeButtons.forEach((button) => {
bindListener(button, "click", (event) => {
const target = event.currentTarget;
@@ -1303,6 +1391,7 @@ function resetCleanup() {
export function setupControls(camera, renderer, scene, earth) {
resetCleanup();
activeCamera = camera;
earthObj = earth;
setupZoomControls(camera);
setupWheelZoom(camera, renderer);
@@ -1407,7 +1496,7 @@ function setupZoomControls(camera) {
bindListener(zoomValue, "click", () => {
const startZoomVal = zoomLevel;
const targetZoom = 1.0;
const targetZoom = getDefaultEarthZoomLevel();
const startDistance = CONFIG.defaultCameraZ / startZoomVal;
const targetDistance = CONFIG.defaultCameraZ / targetZoom;
@@ -1424,8 +1513,8 @@ function setupZoomControls(camera) {
updateZoomDisplay(zoomLevel, distance.toFixed(0));
},
() => {
zoomLevel = 1.0;
showStatusMessage("缩放已重置到100%", "info");
zoomLevel = targetZoom;
showStatusMessage(getZoomResetStatusMessage(targetZoom), "info");
},
);
});
@@ -1482,6 +1571,7 @@ function animateValue(start, end, duration, onUpdate, onComplete) {
export function resetView(camera) {
if (!earthObj) return;
const defaultZoom = getDefaultEarthZoomLevel();
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(
@@ -1490,7 +1580,7 @@ export function resetView(camera) {
lat: pos.coords.latitude,
lon: pos.coords.longitude,
rotLon: pos.coords.longitude - 270,
zoom: 1.0,
zoom: defaultZoom,
duration: 800,
suppressStatus: false,
}),
@@ -1499,7 +1589,7 @@ export function resetView(camera) {
lat: EARTH_CONFIG.chinaLat,
lon: EARTH_CONFIG.chinaLon,
rotLon: EARTH_CONFIG.chinaRotLon,
zoom: 1.0,
zoom: defaultZoom,
duration: 800,
suppressStatus: false,
}),
@@ -1510,7 +1600,7 @@ export function resetView(camera) {
lat: EARTH_CONFIG.chinaLat,
lon: EARTH_CONFIG.chinaLon,
rotLon: EARTH_CONFIG.chinaRotLon,
zoom: 1.0,
zoom: defaultZoom,
duration: 800,
suppressStatus: false,
});
@@ -1877,6 +1967,8 @@ function setupToolbarHubCluster() {
}
let collapseTimer = null;
let expandedToolbarBounds = null;
let refreshBoundsFrameId = 0;
const layoutToolbarOrbs = () => {
const orbs = Array.from(cluster.querySelectorAll(".earth-toolbar-orb"));
@@ -1942,11 +2034,24 @@ function setupToolbarHubCluster() {
orb.style.setProperty("--orb-x", `${x.toFixed(2)}px`);
orb.style.setProperty("--orb-y", `${y.toFixed(2)}px`);
});
if (cluster.classList.contains("is-expanded")) {
scheduleExpandedToolbarBoundsRefresh();
}
};
const setExpanded = (expanded) => {
cluster.classList.toggle("is-expanded", expanded);
cluster.classList.toggle("is-collapsed", !expanded);
if (!expanded) {
expandedToolbarBounds = null;
if (refreshBoundsFrameId) {
cancelAnimationFrame(refreshBoundsFrameId);
refreshBoundsFrameId = 0;
}
return;
}
scheduleExpandedToolbarBoundsRefresh();
};
const scheduleCollapse = () => {
@@ -1966,6 +2071,10 @@ function setupToolbarHubCluster() {
cleanupFns.push(() => {
if (collapseTimer) clearTimeout(collapseTimer);
if (refreshBoundsFrameId) {
cancelAnimationFrame(refreshBoundsFrameId);
refreshBoundsFrameId = 0;
}
});
// Start collapsed — hub acts as the hover target to reveal the arc
@@ -2034,9 +2143,23 @@ function setupToolbarHubCluster() {
};
};
const scheduleExpandedToolbarBoundsRefresh = () => {
if (refreshBoundsFrameId) return;
refreshBoundsFrameId = window.requestAnimationFrame(() => {
expandedToolbarBounds = collectExpandedToolbarBounds();
refreshBoundsFrameId = 0;
});
};
bindListener(document, "mousemove", (event) => {
if (!cluster.classList.contains("is-expanded")) return;
const activeBounds = collectExpandedToolbarBounds();
if (
event.target instanceof Element &&
event.target.closest("#toolbar-cluster, .earth-stack-toolbar")
) {
scheduleExpandedToolbarBoundsRefresh();
}
const activeBounds = expandedToolbarBounds;
if (!activeBounds) {
scheduleCollapse();
return;
@@ -2052,10 +2175,23 @@ function setupToolbarHubCluster() {
scheduleCollapse();
}
});
bindListener(cluster, "mouseenter", () => {
if (cluster.classList.contains("is-expanded")) {
scheduleExpandedToolbarBoundsRefresh();
}
});
bindListener(cluster, "focusin", () => {
if (cluster.classList.contains("is-expanded")) {
scheduleExpandedToolbarBoundsRefresh();
}
});
}
export function teardownControls() {
resetCleanup();
activeCamera = null;
}
export function getAutoRotate() {
@@ -2143,7 +2279,7 @@ export function focusEarthView(camera, options = {}) {
lat = EARTH_CONFIG.chinaLat,
lon = EARTH_CONFIG.chinaLon,
rotLon = lon - 270,
zoom = 1.0,
zoom = getDefaultEarthZoomLevel(),
duration = 800,
suppressStatus = true,
} = options;
@@ -2181,6 +2317,10 @@ export function getZoomLevel() {
return zoomLevel;
}
export function getDefaultEarthZoomLevel() {
return defaultEarthZoom;
}
export function getShowTerrain() {
return showTerrain;
}