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