release: bump version to 0.27.0
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
132
frontend/public/earth/js/controls.js
vendored
132
frontend/public/earth/js/controls.js
vendored
@@ -29,9 +29,9 @@ let listeners = [];
|
||||
let cleanupFns = [];
|
||||
const HUD_PANEL_IDS = [
|
||||
"legend",
|
||||
"coordinates-display",
|
||||
"earth-stats",
|
||||
"tv-panel",
|
||||
"layer-toggles",
|
||||
];
|
||||
const DRAGGABLE_PANEL_SELECTOR = ".hud-panel-draggable";
|
||||
const PANEL_LAYOUT_ANIMATION_MS = 420;
|
||||
@@ -39,7 +39,6 @@ const PANEL_LAYOUT_ANIMATION_MS = 420;
|
||||
function getFloatingGroups() {
|
||||
return [
|
||||
document.getElementById("zoom-control-group"),
|
||||
document.getElementById("info-control-group"),
|
||||
].filter(Boolean);
|
||||
}
|
||||
|
||||
@@ -212,12 +211,22 @@ function setupDraggableHudPanels() {
|
||||
};
|
||||
|
||||
bindListener(handle, "pointerdown", (event) => {
|
||||
if (event.target.closest(".hud-panel-close")) return;
|
||||
if (event.target.closest(".hud-panel-close, .layer-panel-btn, .info-card-close")) return;
|
||||
isDragging = true;
|
||||
startPointerX = event.clientX;
|
||||
startPointerY = event.clientY;
|
||||
const appRect = app.getBoundingClientRect();
|
||||
const panelRect = panel.getBoundingClientRect();
|
||||
|
||||
// If panel is inside a flow container (not a direct child of app), reparent
|
||||
// it so absolute positioning is relative to the app container.
|
||||
if (panel.parentElement !== app) {
|
||||
const capturedWidth = panelRect.width;
|
||||
panel.style.position = "absolute";
|
||||
panel.style.width = `${capturedWidth}px`;
|
||||
app.appendChild(panel);
|
||||
}
|
||||
|
||||
startLeft = panelRect.left - appRect.left;
|
||||
startTop = panelRect.top - appRect.top;
|
||||
panel.style.left = `${startLeft}px`;
|
||||
@@ -265,6 +274,17 @@ function setButtonTooltip(button, text) {
|
||||
}
|
||||
}
|
||||
|
||||
export function updateLayerButtonState(button, isActive) {
|
||||
if (!button) return;
|
||||
button.classList.toggle("active", isActive);
|
||||
button.setAttribute("aria-checked", isActive ? "true" : "false");
|
||||
// Legacy badge text (kept for compatibility)
|
||||
const state = button.querySelector(".earth-layer-btn__state");
|
||||
if (state) {
|
||||
state.textContent = isActive ? "ON" : "OFF";
|
||||
}
|
||||
}
|
||||
|
||||
function clearSelectionIfHiding(shouldHide) {
|
||||
if (shouldHide) {
|
||||
clearLockedObject();
|
||||
@@ -555,11 +575,91 @@ function setupRotateControls(camera) {
|
||||
});
|
||||
}
|
||||
|
||||
function filterLayerRows(query, emptyStateEl, clearBtn) {
|
||||
const rows = document.querySelectorAll("#layer-panel-list .layer-row");
|
||||
let visibleCount = 0;
|
||||
rows.forEach((row) => {
|
||||
const name = (row.dataset.layerName || "").toLowerCase();
|
||||
const matches = !query || name.includes(query);
|
||||
row.hidden = !matches;
|
||||
if (matches) visibleCount++;
|
||||
});
|
||||
if (emptyStateEl) emptyStateEl.hidden = visibleCount > 0;
|
||||
if (clearBtn) clearBtn.hidden = !query;
|
||||
}
|
||||
|
||||
function setupLayerPanel() {
|
||||
const panel = document.getElementById("layer-toggles");
|
||||
const collapseBtn = document.getElementById("layer-panel-collapse");
|
||||
const searchInput = document.getElementById("layer-search-input");
|
||||
const searchClear = document.getElementById("layer-search-clear");
|
||||
const emptyState = document.getElementById("layer-panel-empty");
|
||||
if (!panel) return;
|
||||
|
||||
bindListener(collapseBtn, "click", (e) => {
|
||||
e.stopPropagation();
|
||||
const isCollapsed = panel.classList.toggle("layer-panel--collapsed");
|
||||
collapseBtn.title = isCollapsed ? "展开" : "折叠";
|
||||
collapseBtn.setAttribute("aria-label", isCollapsed ? "展开图层列表" : "折叠图层列表");
|
||||
const icon = collapseBtn.querySelector(".material-symbols-rounded");
|
||||
if (icon) icon.textContent = isCollapsed ? "expand_less" : "expand_more";
|
||||
});
|
||||
|
||||
if (searchInput) {
|
||||
bindListener(searchInput, "input", () => {
|
||||
const query = searchInput.value.trim().toLowerCase();
|
||||
filterLayerRows(query, emptyState, searchClear);
|
||||
});
|
||||
|
||||
if (searchClear) {
|
||||
bindListener(searchClear, "click", () => {
|
||||
searchInput.value = "";
|
||||
filterLayerRows("", emptyState, searchClear);
|
||||
searchInput.focus();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function createLayerRow({ id, icon, label, meta, defaultActive }) {
|
||||
const row = document.createElement("div");
|
||||
row.className = "layer-row";
|
||||
row.dataset.layerName = `${label} ${meta || ""}`.trim().toLowerCase();
|
||||
row.innerHTML = `
|
||||
<span class="material-symbols-rounded layer-row-icon">${icon}</span>
|
||||
<div class="layer-row-copy">
|
||||
<span class="layer-row-label">${label}</span>
|
||||
${meta ? `<span class="layer-row-meta">${meta}</span>` : ""}
|
||||
</div>
|
||||
<button id="${id}" class="layer-row-toggle${defaultActive ? " active" : ""}" type="button"
|
||||
role="switch" aria-checked="${defaultActive ? "true" : "false"}" title="切换${label}显示">
|
||||
<span class="layer-row-toggle-track"></span>
|
||||
</button>
|
||||
`;
|
||||
return row;
|
||||
}
|
||||
|
||||
export function registerLayer({ id, icon, label, meta = "", defaultActive = false, onToggle }) {
|
||||
const list = document.getElementById("layer-panel-list");
|
||||
if (!list) return;
|
||||
if (document.getElementById(id)) return; // avoid duplicates
|
||||
|
||||
const row = createLayerRow({ id, icon, label, meta, defaultActive });
|
||||
list.appendChild(row);
|
||||
|
||||
const btn = row.querySelector("button");
|
||||
if (btn && typeof onToggle === "function") {
|
||||
btn.addEventListener("click", function () {
|
||||
const isActive = this.classList.toggle("active");
|
||||
this.setAttribute("aria-checked", isActive ? "true" : "false");
|
||||
onToggle(isActive);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function setupTerrainControls() {
|
||||
const container = document.getElementById("container");
|
||||
const searchBtn = document.getElementById("search-action");
|
||||
const infoGroup = document.getElementById("info-control-group");
|
||||
const infoTrigger = document.getElementById("info-trigger");
|
||||
const terrainBtn = document.getElementById("toggle-terrain");
|
||||
const satellitesBtn = document.getElementById("toggle-satellites");
|
||||
const bgpBtn = document.getElementById("toggle-bgp");
|
||||
@@ -572,9 +672,10 @@ function setupTerrainControls() {
|
||||
setupSettingsControls();
|
||||
setupHudPanelControls();
|
||||
setupDraggableHudPanels();
|
||||
setupLayerPanel();
|
||||
|
||||
if (trailsBtn) {
|
||||
trailsBtn.classList.add("active");
|
||||
updateLayerButtonState(trailsBtn, true);
|
||||
setButtonTooltip(trailsBtn, "隐藏轨迹");
|
||||
}
|
||||
|
||||
@@ -585,7 +686,7 @@ function setupTerrainControls() {
|
||||
bindListener(terrainBtn, "click", function () {
|
||||
showTerrain = !showTerrain;
|
||||
toggleTerrain(showTerrain);
|
||||
this.classList.toggle("active", showTerrain);
|
||||
updateLayerButtonState(this, showTerrain);
|
||||
setButtonTooltip(this, showTerrain ? "隐藏地形" : "显示地形");
|
||||
const terrainStatus = document.getElementById("terrain-status");
|
||||
if (terrainStatus)
|
||||
@@ -615,7 +716,7 @@ function setupTerrainControls() {
|
||||
const showNextBGP = !getShowBGP();
|
||||
clearSelectionIfHiding(!showNextBGP);
|
||||
toggleBGP(showNextBGP);
|
||||
this.classList.toggle("active", showNextBGP);
|
||||
updateLayerButtonState(this, showNextBGP);
|
||||
setButtonTooltip(this, showNextBGP ? "隐藏BGP观测" : "显示BGP观测");
|
||||
const bgpCountEl = document.getElementById("bgp-anomaly-count");
|
||||
if (bgpCountEl) {
|
||||
@@ -628,7 +729,7 @@ function setupTerrainControls() {
|
||||
const isActive = this.classList.contains("active");
|
||||
const nextShowTrails = !isActive;
|
||||
toggleTrails(nextShowTrails);
|
||||
this.classList.toggle("active", nextShowTrails);
|
||||
updateLayerButtonState(this, nextShowTrails);
|
||||
setButtonTooltip(this, nextShowTrails ? "隐藏轨迹" : "显示轨迹");
|
||||
showStatusMessage(nextShowTrails ? "轨迹已显示" : "轨迹已隐藏", "info");
|
||||
});
|
||||
@@ -648,10 +749,9 @@ function setupTerrainControls() {
|
||||
});
|
||||
|
||||
bindFloatingMenu(zoomTrigger, zoomGroup);
|
||||
bindFloatingMenu(infoTrigger, infoGroup);
|
||||
|
||||
bindListener(document, "click", (event) => {
|
||||
const openGroups = [zoomGroup, infoGroup].filter((group) =>
|
||||
const openGroups = [zoomGroup].filter((group) =>
|
||||
group?.classList.contains("open"),
|
||||
);
|
||||
if (openGroups.length === 0) return;
|
||||
@@ -673,9 +773,13 @@ function setupTerrainControls() {
|
||||
showStatusMessage(expanded ? "布局已最大化" : "布局已恢复", "info");
|
||||
});
|
||||
|
||||
updateTVToggleUI(
|
||||
!document.getElementById("tv-panel")?.classList.contains("hud-panel-hidden"),
|
||||
);
|
||||
const tvVisible = !document.getElementById("tv-panel")?.classList.contains("hud-panel-hidden");
|
||||
updateTVToggleUI(tvVisible);
|
||||
if (tvVisible) {
|
||||
ensureTVPanelReady().catch((error) => {
|
||||
console.error("初始化电视直播面板失败:", error);
|
||||
});
|
||||
}
|
||||
updateLayoutUI(container);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user