release: bump version to 0.27.0

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
rayd1o
2026-04-14 07:37:45 +08:00
parent 2ee4773f4f
commit 7cd29cf9c0
24 changed files with 1565 additions and 630 deletions

View File

@@ -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);
}