release: bump version to 0.38.0
This commit is contained in:
@@ -2,6 +2,7 @@ import * as THREE from "three";
|
||||
|
||||
import {
|
||||
CONFIG,
|
||||
CRUISE_MODULES,
|
||||
HUD_CONFIG,
|
||||
CABLE_CONFIG,
|
||||
CABLE_STATE,
|
||||
@@ -147,6 +148,7 @@ import {
|
||||
import {
|
||||
setupControls,
|
||||
getAutoRotate,
|
||||
getCruiseModules,
|
||||
getRotationMode,
|
||||
getShowTerrain,
|
||||
getStartupLoadLayers,
|
||||
@@ -167,6 +169,7 @@ import {
|
||||
import { CalloutConnector } from "./callout-connector.js";
|
||||
import { CruiseSequencer } from "./cruise-sequencer.js";
|
||||
import { createBGPCruiseAdapter } from "./bgp-cruise-adapter.js";
|
||||
import { createNewsCruiseAdapter } from "./news-cruise-adapter.js";
|
||||
import {
|
||||
initInfoCard,
|
||||
showInfoCard,
|
||||
@@ -182,6 +185,10 @@ import { mountBrand } from "./brand.js";
|
||||
import { initTVPanel } from "./tv.js";
|
||||
import { initNewsPanel, updateNewsViewFocus } from "./news.js";
|
||||
import { initSearchPanel } from "./search.js";
|
||||
import {
|
||||
registerEarthClientErrorHandlers,
|
||||
reportEarthClientLog,
|
||||
} from "./client-logs.js";
|
||||
|
||||
export let scene;
|
||||
export let camera;
|
||||
@@ -224,6 +231,7 @@ let sceneLights = null;
|
||||
let cruisePollTimerId = null;
|
||||
let calloutConnector = null;
|
||||
let cruiseBGPAdapter = null;
|
||||
let cruiseNewsAdapter = null;
|
||||
let cruiseSequencer = null;
|
||||
let activeDragPointerId = null;
|
||||
let activeTouchPoints = new Map();
|
||||
@@ -1256,6 +1264,143 @@ function ensureBGPCruiseAdapter() {
|
||||
return cruiseBGPAdapter;
|
||||
}
|
||||
|
||||
function ensureNewsCruiseAdapter() {
|
||||
if (cruiseNewsAdapter) return cruiseNewsAdapter;
|
||||
|
||||
cruiseNewsAdapter = createNewsCruiseAdapter({
|
||||
camera,
|
||||
earth: () => getEarth(),
|
||||
connector: ensureCalloutConnector(),
|
||||
focusView: (options) => focusEarthView(camera, options),
|
||||
});
|
||||
|
||||
return cruiseNewsAdapter;
|
||||
}
|
||||
|
||||
function getCruiseModuleDefinitions() {
|
||||
return [
|
||||
{
|
||||
id: CRUISE_MODULES.BGP,
|
||||
ensureAdapter: ensureBGPCruiseAdapter,
|
||||
isQueueSourceActive: () => getShowBGP(),
|
||||
getSortedQueueItems: () =>
|
||||
ensureBGPCruiseAdapter()
|
||||
.getSortedMarkers()
|
||||
.map((marker) => {
|
||||
const markerId = marker?.userData?.id;
|
||||
if (!markerId) return null;
|
||||
const parsed = marker?.userData?.created_at_raw || marker?.userData?.created_at;
|
||||
const timestamp = parsed ? new Date(parsed).getTime() : 0;
|
||||
return {
|
||||
id: `bgp:${markerId}`,
|
||||
moduleId: CRUISE_MODULES.BGP,
|
||||
sortTimestamp: Number.isFinite(timestamp) ? timestamp : 0,
|
||||
payload: marker,
|
||||
};
|
||||
})
|
||||
.filter(Boolean),
|
||||
focusQueueItem: (item, options = {}) =>
|
||||
ensureBGPCruiseAdapter().focusMarker(item?.payload, options),
|
||||
presentQueueItem: (item, options = {}) =>
|
||||
ensureBGPCruiseAdapter().presentMarker(item?.payload, options),
|
||||
hidePresentation: (options = {}) =>
|
||||
ensureBGPCruiseAdapter().hidePresentation(options),
|
||||
clearCurrentHighlight: () => ensureBGPCruiseAdapter().clearCurrentHighlight(),
|
||||
resetPresentation: () => ensureBGPCruiseAdapter().resetPresentation(),
|
||||
repositionPresentation: (item) =>
|
||||
ensureBGPCruiseAdapter().repositionConnector(item?.payload),
|
||||
syncKnownEventIds: () => ensureBGPCruiseAdapter().syncKnownEventIds(),
|
||||
pollForNewQueueItemIds: async () => {
|
||||
const nextIds = await ensureBGPCruiseAdapter().pollForNewMarkerIds();
|
||||
return nextIds.map((id) => `bgp:${id}`);
|
||||
},
|
||||
},
|
||||
{
|
||||
id: CRUISE_MODULES.NEWS,
|
||||
ensureAdapter: ensureNewsCruiseAdapter,
|
||||
isQueueSourceActive: () => true,
|
||||
ensureItemsLoaded: () => ensureNewsCruiseAdapter().ensureItemsLoaded(),
|
||||
getSortedQueueItems: () =>
|
||||
ensureNewsCruiseAdapter()
|
||||
.getSortedItems()
|
||||
.map((item) => ({
|
||||
...item,
|
||||
moduleId: CRUISE_MODULES.NEWS,
|
||||
payload: item,
|
||||
})),
|
||||
focusQueueItem: (item, options = {}) =>
|
||||
ensureNewsCruiseAdapter().focusItem(item?.payload, options),
|
||||
presentQueueItem: (item, options = {}) =>
|
||||
ensureNewsCruiseAdapter().presentItem(item?.payload, options),
|
||||
hidePresentation: (options = {}) =>
|
||||
ensureNewsCruiseAdapter().hidePresentation(options),
|
||||
clearCurrentHighlight: () => ensureNewsCruiseAdapter().clearCurrentHighlight(),
|
||||
resetPresentation: () => ensureNewsCruiseAdapter().resetPresentation(),
|
||||
repositionPresentation: (item) =>
|
||||
ensureNewsCruiseAdapter().repositionConnector(item?.payload),
|
||||
syncKnownEventIds: () => ensureNewsCruiseAdapter().syncKnownEventIds(),
|
||||
externalEventName: "earth:news-payload-updated",
|
||||
extractQueuedItemIdsFromEvent: (event) => {
|
||||
const itemIds = Array.isArray(event?.detail?.itemIds) ? event.detail.itemIds : [];
|
||||
if (itemIds.length === 0) return [];
|
||||
const adapter = ensureNewsCruiseAdapter();
|
||||
const newIds = adapter.diffNewEventIds(itemIds);
|
||||
adapter.syncKnownEventIds();
|
||||
return newIds;
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
function getCruiseModuleDefinition(moduleId) {
|
||||
return getCruiseModuleDefinitions().find((module) => module.id === moduleId) || null;
|
||||
}
|
||||
|
||||
function getEnabledCruiseModuleDefinitions() {
|
||||
const enabledModuleIds = new Set(getCruiseModules());
|
||||
return getCruiseModuleDefinitions().filter((module) => enabledModuleIds.has(module.id));
|
||||
}
|
||||
|
||||
function sortCruiseQueueItems(items) {
|
||||
return items.sort((left, right) => {
|
||||
const timestampDiff = (right?.sortTimestamp || 0) - (left?.sortTimestamp || 0);
|
||||
if (timestampDiff !== 0) return timestampDiff;
|
||||
return String(left?.id || "").localeCompare(String(right?.id || ""));
|
||||
});
|
||||
}
|
||||
|
||||
function getCruiseQueueItemsSorted() {
|
||||
const items = [];
|
||||
getEnabledCruiseModuleDefinitions().forEach((module) => {
|
||||
if (!module.isQueueSourceActive?.()) return;
|
||||
items.push(...(module.getSortedQueueItems?.() || []));
|
||||
});
|
||||
return sortCruiseQueueItems(items);
|
||||
}
|
||||
|
||||
function getCruiseModuleForItem(item) {
|
||||
return getCruiseModuleDefinition(item?.moduleId);
|
||||
}
|
||||
|
||||
function getCurrentCruiseBGPMarker() {
|
||||
const item = cruiseSequencer?.getCurrentItem() ?? null;
|
||||
return item?.moduleId === CRUISE_MODULES.BGP ? item.payload || null : null;
|
||||
}
|
||||
|
||||
async function syncCruiseModuleKnownEventIds() {
|
||||
const modules = getEnabledCruiseModuleDefinitions();
|
||||
await Promise.all(
|
||||
modules.map(async (module) => {
|
||||
try {
|
||||
await module.ensureItemsLoaded?.();
|
||||
module.syncKnownEventIds?.();
|
||||
} catch (error) {
|
||||
console.warn(`同步巡航模块失败: ${module.id}`, error);
|
||||
}
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
function isCruisePresentationPinned() {
|
||||
return cruiseSequencer?.isPresentationPinned() === true;
|
||||
}
|
||||
@@ -1265,22 +1410,23 @@ function setCruisePresentationVisible(visible) {
|
||||
cruiseSequencer.setPresentationVisible(visible);
|
||||
}
|
||||
if (!visible) {
|
||||
ensureBGPCruiseAdapter().resetPresentation();
|
||||
getCruiseModuleDefinitions().forEach((module) => {
|
||||
module.resetPresentation?.();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function clearCruiseMarkerHighlight() {
|
||||
ensureBGPCruiseAdapter().clearCurrentHighlight();
|
||||
}
|
||||
|
||||
function getCruiseMarkersSorted() {
|
||||
return ensureBGPCruiseAdapter().getSortedMarkers();
|
||||
function clearCruiseHighlights() {
|
||||
getCruiseModuleDefinitions().forEach((module) => {
|
||||
module.clearCurrentHighlight?.();
|
||||
});
|
||||
}
|
||||
|
||||
function repositionCruiseConnector() {
|
||||
if (!isCruisePresentationPinned()) return;
|
||||
const marker = cruiseSequencer?.getCurrentItem() ?? null;
|
||||
ensureBGPCruiseAdapter().repositionConnector(marker);
|
||||
const item = cruiseSequencer?.getCurrentItem() ?? null;
|
||||
const module = getCruiseModuleForItem(item);
|
||||
module?.repositionPresentation?.(item);
|
||||
}
|
||||
|
||||
function isCruiseModeActive() {
|
||||
@@ -1292,12 +1438,12 @@ function ensureCruiseSequencer() {
|
||||
|
||||
cruiseSequencer = new CruiseSequencer({
|
||||
isActive: () => isCruiseModeActive() && getAutoRotate(),
|
||||
getItems: () => getCruiseMarkersSorted(),
|
||||
getItemId: (marker) => marker?.userData?.id || null,
|
||||
getItems: () => getCruiseQueueItemsSorted(),
|
||||
getItemId: (item) => item?.id || null,
|
||||
dwellMs: CRUISE_CONFIG.dwellMs,
|
||||
transitionGapMs: CRUISE_TRANSITION_GAP_MS,
|
||||
clearCurrent: () => {
|
||||
clearCruiseMarkerHighlight();
|
||||
clearCruiseHighlights();
|
||||
clearLockedObject();
|
||||
hideInfoCard();
|
||||
setCruisePresentationVisible(false);
|
||||
@@ -1308,11 +1454,11 @@ function ensureCruiseSequencer() {
|
||||
hideInfoCard();
|
||||
}
|
||||
},
|
||||
focusItem: async (marker, { interrupt }) =>
|
||||
ensureBGPCruiseAdapter().focusMarker(marker, { interrupt }),
|
||||
presentItem: async (marker, { context }) => {
|
||||
focusItem: async (item, { interrupt }) =>
|
||||
getCruiseModuleForItem(item)?.focusQueueItem?.(item, { interrupt }),
|
||||
presentItem: async (item, { context }) => {
|
||||
setCruisePresentationVisible(true);
|
||||
const presented = await ensureBGPCruiseAdapter().presentMarker(marker, {
|
||||
const presented = await getCruiseModuleForItem(item)?.presentQueueItem?.(item, {
|
||||
context,
|
||||
});
|
||||
if (!presented) {
|
||||
@@ -1320,8 +1466,8 @@ function ensureCruiseSequencer() {
|
||||
}
|
||||
return presented;
|
||||
},
|
||||
hideItem: async (_marker, { context }) => {
|
||||
await ensureBGPCruiseAdapter().hidePresentation({ context });
|
||||
hideItem: async (item, { context }) => {
|
||||
await getCruiseModuleForItem(item)?.hidePresentation?.({ context });
|
||||
setCruisePresentationVisible(false);
|
||||
},
|
||||
});
|
||||
@@ -1347,10 +1493,23 @@ async function advanceCruiseEvent({ interrupt = false } = {}) {
|
||||
}
|
||||
|
||||
async function pollCruiseEventsIfNeeded() {
|
||||
if (!isCruiseModeActive() || !getAutoRotate() || !getShowBGP()) return;
|
||||
if (!isCruiseModeActive() || !getAutoRotate()) return;
|
||||
|
||||
try {
|
||||
const newIds = await ensureBGPCruiseAdapter().pollForNewMarkerIds();
|
||||
const modules = getEnabledCruiseModuleDefinitions().filter(
|
||||
(module) => module.isQueueSourceActive?.() && typeof module.pollForNewQueueItemIds === "function",
|
||||
);
|
||||
const results = await Promise.all(
|
||||
modules.map(async (module) => {
|
||||
try {
|
||||
return await module.pollForNewQueueItemIds();
|
||||
} catch (error) {
|
||||
console.warn(`巡航模式轮询失败: ${module.id}`, error);
|
||||
return [];
|
||||
}
|
||||
}),
|
||||
);
|
||||
const newIds = results.flat();
|
||||
if (newIds.length === 0) return;
|
||||
|
||||
ensureCruiseSequencer().enqueue(newIds);
|
||||
@@ -1358,7 +1517,7 @@ async function pollCruiseEventsIfNeeded() {
|
||||
await advanceCruiseEvent({ interrupt: true });
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn("巡航模式轮询 BGP 事件失败:", error);
|
||||
console.warn("巡航模式轮询失败:", error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1390,16 +1549,63 @@ function handleRotationModeChange(event) {
|
||||
}
|
||||
|
||||
ensureCruisePolling();
|
||||
ensureBGPCruiseAdapter().syncKnownEventIds();
|
||||
|
||||
if (!detailActive) {
|
||||
stopCruiseMode({ preserveCard: true });
|
||||
return;
|
||||
}
|
||||
|
||||
advanceCruiseEvent({ interrupt: true }).catch((error) => {
|
||||
console.warn("启动巡航模式失败:", error);
|
||||
syncCruiseModuleKnownEventIds()
|
||||
.catch((error) => {
|
||||
console.warn("同步巡航模块失败:", error);
|
||||
})
|
||||
.finally(() => {
|
||||
advanceCruiseEvent({ interrupt: true }).catch((error) => {
|
||||
console.warn("启动巡航模式失败:", error);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function handleCruiseModulesChange() {
|
||||
interruptCruisePresentation({ resetLoop: true });
|
||||
clearBGPSelection();
|
||||
|
||||
const syncPromise = syncCruiseModuleKnownEventIds().catch((error) => {
|
||||
console.warn("刷新巡航模块失败:", error);
|
||||
});
|
||||
|
||||
if (!isCruiseModeActive()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!getAutoRotate()) {
|
||||
stopCruiseMode({ preserveCard: true });
|
||||
return;
|
||||
}
|
||||
|
||||
syncPromise.finally(() => {
|
||||
advanceCruiseEvent({ interrupt: true }).catch((error) => {
|
||||
console.warn("切换巡航模块后推进失败:", error);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function handleCruiseModuleItemsUpdated(moduleId, event) {
|
||||
const module = getCruiseModuleDefinition(moduleId);
|
||||
if (!module) return;
|
||||
|
||||
const newIds = module.extractQueuedItemIdsFromEvent?.(event) || [];
|
||||
if (newIds.length === 0) return;
|
||||
|
||||
if (!isCruiseModeActive() || !getAutoRotate() || !getCruiseModules().includes(moduleId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
ensureCruiseSequencer().enqueue(newIds);
|
||||
if (!ensureCruiseSequencer().isBusy()) {
|
||||
advanceCruiseEvent({ interrupt: true }).catch((error) => {
|
||||
console.warn(`推进巡航模块失败: ${moduleId}`, error);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function clearSelectionAndInfo() {
|
||||
@@ -1726,13 +1932,7 @@ function getCurrentViewCenterCoords() {
|
||||
return vector3ToLatLon(scratchViewCenterWorld);
|
||||
}
|
||||
|
||||
window.addEventListener("error", (event) => {
|
||||
console.error("全局错误:", event.error);
|
||||
});
|
||||
|
||||
window.addEventListener("unhandledrejection", (event) => {
|
||||
console.error("未处理的 Promise 错误:", event.reason);
|
||||
});
|
||||
registerEarthClientErrorHandlers();
|
||||
|
||||
export function init() {
|
||||
if (initialized && !destroyed) return;
|
||||
@@ -1984,6 +2184,13 @@ async function loadData() {
|
||||
getSatelliteHydrationToken: () => satelliteHydrationToken,
|
||||
reportError: (label, reason) => {
|
||||
errors.push({ label, reason });
|
||||
void reportEarthClientLog({
|
||||
level: "error",
|
||||
category: "startup-load",
|
||||
module: "layer-startup",
|
||||
message: `${label}加载失败: ${reason?.message || String(reason)}`,
|
||||
detail: reason,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
@@ -2087,6 +2294,13 @@ export async function setCablesEnabled(
|
||||
clearCableData(getEarth());
|
||||
updateCableToggleUi(false);
|
||||
const message = `线缆加载失败: ${error?.message || String(error)}`;
|
||||
void reportEarthClientLog({
|
||||
level: "error",
|
||||
category: "layer-toggle",
|
||||
module: "cables",
|
||||
message,
|
||||
detail: error,
|
||||
});
|
||||
if (!suppressLoadingUi) {
|
||||
showError(message);
|
||||
}
|
||||
@@ -2133,6 +2347,13 @@ export async function setSatellitesEnabled(
|
||||
resetSatelliteState();
|
||||
updateSatelliteToggleUi(false, 0);
|
||||
const message = `卫星加载失败: ${error?.message || String(error)}`;
|
||||
void reportEarthClientLog({
|
||||
level: "error",
|
||||
category: "layer-toggle",
|
||||
module: "satellites",
|
||||
message,
|
||||
detail: error,
|
||||
});
|
||||
if (!suppressLoadingUi) {
|
||||
showError(message);
|
||||
}
|
||||
@@ -2156,13 +2377,21 @@ function setupEventListeners() {
|
||||
const handleClick = (event) => onClick(event);
|
||||
const handlePageHide = () => destroy();
|
||||
const handleRotationMode = (event) => handleRotationModeChange(event);
|
||||
const handleCruiseModules = () => handleCruiseModulesChange();
|
||||
const handleInfoCardDrag = () => repositionCruiseConnector();
|
||||
|
||||
bindListener(window, "resize", handleResize);
|
||||
bindListener(window, "pagehide", handlePageHide);
|
||||
bindListener(window, "beforeunload", handlePageHide);
|
||||
bindListener(window, "earth:rotation-mode-change", handleRotationMode);
|
||||
bindListener(window, "earth:cruise-modules-change", handleCruiseModules);
|
||||
bindListener(window, "earth:info-card-drag", handleInfoCardDrag);
|
||||
getCruiseModuleDefinitions().forEach((module) => {
|
||||
if (!module.externalEventName) return;
|
||||
bindListener(window, module.externalEventName, (event) => {
|
||||
handleCruiseModuleItemsUpdated(module.id, event);
|
||||
});
|
||||
});
|
||||
bindListener(renderer.domElement, "pointerdown", handlePointerDown);
|
||||
bindListener(window, "pointermove", handlePointerMove);
|
||||
bindListener(window, "pointerup", handlePointerUp);
|
||||
@@ -2860,7 +3089,7 @@ function animate() {
|
||||
applyCableVisualState();
|
||||
const activeCruiseMarker =
|
||||
isCruiseModeActive() && isCruisePresentationPinned()
|
||||
? cruiseSequencer?.getCurrentItem() ?? null
|
||||
? getCurrentCruiseBGPMarker()
|
||||
: null;
|
||||
updateBGPVisualState(lockedObjectType, lockedObject, camera, activeCruiseMarker);
|
||||
updateComputeCenterVisualState(lockedObjectType, lockedObject, camera);
|
||||
|
||||
Reference in New Issue
Block a user