release: bump version to 0.28.1

This commit is contained in:
linkong
2026-04-20 15:14:53 +08:00
parent 4c21973197
commit 75cb214f23
42 changed files with 8556 additions and 89 deletions

View File

@@ -541,4 +541,4 @@
/* .hud-panel-legend layout-expanded rule lives in legend.css */
/* .hud-panel-stats layout-expanded rule lives in earth-stats.css */
/* .hud-panel-layers layout-expanded rule lives in layer-panel.css */
/* .hud-panel-tv layout-expanded rule lives in tv-panel.css */
/* .hud-panel-media layout-expanded rule lives in tv-panel.css */

View File

@@ -3,21 +3,10 @@
.news-panel-title-row {
display: flex;
align-items: center;
gap: calc(8px * var(--hud-scale));
min-width: 0;
flex: 1 1 auto;
}
.news-panel-title {
margin: 0;
color: var(--hud-text);
font-size: calc(0.92rem * var(--hud-scale));
font-weight: 600;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.news-region-chip {
--news-accent: #d6e6ff;
border: 1px solid color-mix(in srgb, var(--news-accent) 46%, transparent);

View File

@@ -1,6 +1,10 @@
/* tv-panel */
/* media-panel
* Outer HUD shell: #media-panel
* Inner live pane: #tv-panel
* Inner news pane: #news-panel
*/
.hud-panel-tv {
.hud-panel-media {
bottom: var(--hud-offset);
right: var(--hud-offset);
width: calc(420px * var(--hud-scale));
@@ -14,39 +18,44 @@
z-index: 18;
}
.hud-panel-tv[data-active-tab="news"]:not([data-resized="true"]) {
.hud-panel-media[data-active-tab="news"]:not([data-resized="true"]) {
max-height: min(
var(--tv-news-default-max-height, calc(100vh - (2 * var(--hud-offset)))),
calc(100vh - (2 * var(--hud-offset)))
);
}
.hud-panel-tv.is-reforming {
.hud-panel-media.is-reforming {
transition:
height 0.24s cubic-bezier(0.22, 1, 0.36, 1),
top 0.24s cubic-bezier(0.22, 1, 0.36, 1);
will-change: height, top;
}
.hud-panel-tv .hud-panel__header {
.hud-panel-media .hud-panel__header {
align-items: center;
gap: var(--hud-gap-xs);
}
.hud-panel-media .hud-panel__title-group {
flex: 0 0 auto;
min-width: auto;
}
.tv-panel-header-title {
flex: 0 0 auto;
white-space: nowrap;
margin: 0;
}
.hud-panel-tv .hud-panel__header .hud-panel__action,
.hud-panel-tv .hud-panel__header .hud-panel-close {
.hud-panel-media .hud-panel__header .hud-panel__action,
.hud-panel-media .hud-panel__header .hud-panel-close {
cursor: pointer;
user-select: auto;
}
.hud-panel-tv .hud-panel__header .tv-panel-select,
.hud-panel-tv .hud-panel__header .tv-panel-tab {
.hud-panel-media .hud-panel__header .tv-panel-select,
.hud-panel-media .hud-panel__header .media-panel-tab {
cursor: pointer;
user-select: auto;
}
@@ -194,13 +203,13 @@
background: #050a14;
}
.tv-panel-tabs {
.media-panel-tabs {
display: grid;
grid-template-columns: 1fr 1fr;
gap: calc(8px * var(--hud-scale));
}
.tv-panel-tab {
.media-panel-tab {
border: 1px solid rgba(201, 225, 247, 0.12);
border-radius: calc(12px * var(--hud-scale));
background: rgba(255, 255, 255, 0.03);
@@ -213,13 +222,13 @@
transition: background 0.18s ease, border-color 0.18s ease, color 0.18s ease;
}
.tv-panel-tab:hover {
.media-panel-tab:hover {
color: var(--hud-text);
border-color: rgba(214, 235, 255, 0.18);
background: rgba(255, 255, 255, 0.06);
}
.tv-panel-tab--active {
.media-panel-tab--active {
color: var(--hud-accent-strong);
border-color: rgba(120, 180, 255, 0.24);
background: rgba(120, 180, 255, 0.12);
@@ -290,35 +299,35 @@
border-color: rgba(244, 249, 255, 0.75);
}
.hud-panel-tv.is-resizing {
.hud-panel-media.is-resizing {
transition: none !important;
user-select: none;
}
/* Whole panel is draggable; player overrides back to default */
.hud-panel-tv:not(.is-resizing) {
.hud-panel-media:not(.is-resizing) {
cursor: grab;
}
.hud-panel-tv.is-dragging {
.hud-panel-media.is-dragging {
cursor: grabbing;
}
.hud-panel-tv .tv-panel-player {
.hud-panel-media .tv-panel-player {
cursor: default;
}
/* Disable iframe/video pointer capture while dragging so mouse events pass through */
.hud-panel-tv.is-dragging .tv-panel-iframe,
.hud-panel-tv.is-dragging .tv-panel-video {
.hud-panel-media.is-dragging .tv-panel-iframe,
.hud-panel-media.is-dragging .tv-panel-video {
pointer-events: none;
}
.earth-app.layout-expanded .hud-panel-tv:not([data-dragged="true"]) {
.earth-app.layout-expanded .hud-panel-media:not([data-dragged="true"]) {
bottom: var(--hud-offset);
right: var(--hud-offset);
transform: translate(calc(100% - var(--hud-offset)), calc(100% - var(--hud-offset)));
}
/* TV panel keeps its fixed width on all screen sizes.
/* Media panel keeps its fixed width on all screen sizes.
Responsive stretching removed — width only changes if user manually resizes. */

View File

@@ -294,7 +294,7 @@
<span id="camera-distance" hidden></span>
</div>
<div id="tv-panel" class="hud-panel hud-panel-tv hud-panel-draggable" data-panel-key="tv-panel" data-drag-self="true">
<div id="media-panel" class="hud-panel hud-panel-media hud-panel-draggable" data-panel-key="media-panel" data-drag-self="true">
<div class="hud-panel__header hud-panel-drag-handle">
<div class="hud-panel__title-group">
<span class="hud-panel-title hud-panel__title tv-panel-header-title">媒体情报</span>
@@ -315,7 +315,6 @@
</div>
<div id="tv-header-controls-news" class="tv-panel-header-controls tv-panel-header-controls--news" hidden>
<div class="news-panel-title-row">
<span class="news-panel-title">全球态势聚合</span>
<span id="news-region-chip" class="news-region-chip hud-panel__chip">global</span>
</div>
<div class="tv-panel-toolbar-actions">
@@ -328,14 +327,14 @@
</div>
</div>
<div class="hud-panel__actions">
<button class="hud-panel-close hud-panel__action hud-panel__action--close" type="button" data-close-panel="tv-panel" aria-label="关闭媒体情报面板">
<button class="hud-panel-close hud-panel__action hud-panel__action--close" type="button" data-close-panel="media-panel" aria-label="关闭媒体情报面板">
<span class="material-symbols-rounded">close</span>
</button>
</div>
</div>
<div class="tv-panel-content">
<section id="tv-tab-live-pane" class="tv-tab-pane tv-tab-pane--active" aria-labelledby="tv-tab-live">
<section id="tv-panel" class="tv-tab-pane tv-tab-pane--active" aria-labelledby="tv-tab-live">
<div class="tv-panel-meta-wrap" id="tv-meta-wrap">
<div class="tv-panel-meta" id="tv-panel-meta">
<span id="tv-source-status" class="tv-panel-status">等待加载直播源</span>
@@ -360,7 +359,7 @@
</div>
</section>
<section id="tv-tab-news-pane" class="tv-tab-pane tv-tab-pane--news" aria-labelledby="tv-tab-news" hidden>
<section id="news-panel" class="tv-tab-pane tv-tab-pane--news" aria-labelledby="tv-tab-news" hidden>
<div id="news-panel-body" class="news-panel-body">
<div class="news-panel-subtitle">跟随地球正面视角自动切换区域新闻</div>
@@ -383,9 +382,9 @@
</section>
</div>
<div class="tv-panel-tabs" role="tablist" aria-label="媒体情报切换">
<button id="tv-tab-live" class="tv-panel-tab tv-panel-tab--active" type="button" role="tab" aria-selected="true" aria-controls="tv-tab-live-pane">直播</button>
<button id="tv-tab-news" class="tv-panel-tab" type="button" role="tab" aria-selected="false" aria-controls="tv-tab-news-pane">聚合</button>
<div class="media-panel-tabs" role="tablist" aria-label="媒体情报切换">
<button id="tv-tab-live" class="media-panel-tab media-panel-tab--active" type="button" role="tab" aria-selected="true" aria-controls="tv-panel">电视直播</button>
<button id="tv-tab-news" class="media-panel-tab" type="button" role="tab" aria-selected="false" aria-controls="news-panel">态势聚合</button>
</div>
<div class="tv-panel-edge" data-edge="r"></div>
<div class="tv-panel-edge" data-edge="b"></div>
@@ -453,7 +452,7 @@
<span class="earth-settings-item-subtitle">控制新闻直播窗口显示</span>
</div>
<span class="earth-settings-switch">
<input id="toggle-view-tv" type="checkbox" data-settings-panel="tv-panel">
<input id="toggle-view-tv" type="checkbox" data-settings-panel="media-panel">
<span class="earth-settings-switch-track"></span>
</span>
</label>

View File

@@ -35,7 +35,7 @@ let cleanupFns = [];
const HUD_PANEL_IDS = [
"legend",
"earth-stats",
"tv-panel",
"media-panel",
"layer-toggles",
];
const DRAGGABLE_PANEL_SELECTOR = ".hud-panel-draggable";
@@ -94,7 +94,7 @@ function setHudPanelVisibility(panelId, visible) {
if (!panel) return;
panel.classList.toggle("hud-panel-hidden", !visible);
syncSettingsToggle(panelId, visible);
if (panelId === "tv-panel") {
if (panelId === "media-panel") {
updateTVToggleUI(visible);
updateNewsToggleUI(visible);
if (visible) {
@@ -236,7 +236,7 @@ function setupDraggableHudPanels() {
};
bindListener(handle, "pointerdown", (event) => {
if (event.target.closest(".hud-panel-close, .hud-panel__action, .layer-panel-btn, .info-card-close, .tv-panel-select, .tv-panel-tab, .tv-panel-player, .tv-panel-edge, .legend-bar-btn, .news-story-card")) return;
if (event.target.closest(".hud-panel-close, .hud-panel__action, .layer-panel-btn, .info-card-close, .tv-panel-select, .media-panel-tab, .tv-panel-player, .tv-panel-edge, .legend-bar-btn, .news-story-card")) return;
isDragging = true;
startPointerX = event.clientX;
startPointerY = event.clientY;
@@ -807,14 +807,14 @@ function setupTerrainControls() {
showStatusMessage(expanded ? "布局已最大化" : "布局已恢复", "info");
});
const tvVisible = !document.getElementById("tv-panel")?.classList.contains("hud-panel-hidden");
updateTVToggleUI(tvVisible);
if (tvVisible) {
const mediaVisible = !document.getElementById("media-panel")?.classList.contains("hud-panel-hidden");
updateTVToggleUI(mediaVisible);
if (mediaVisible) {
ensureTVPanelReady().catch((error) => {
console.error("初始化电视直播面板失败:", error);
});
}
updateNewsToggleUI(!document.getElementById("tv-panel")?.classList.contains("hud-panel-hidden"));
updateNewsToggleUI(mediaVisible);
ensureNewsPanelReady().catch((error) => {
console.error("初始化态势新闻内容失败:", error);
});

View File

@@ -194,10 +194,8 @@ const HUD_INTERACTIVE_SELECTORS = [
"#legend *",
"#earth-stats",
"#earth-stats *",
"#tv-panel",
"#tv-panel *",
"#news-panel",
"#news-panel *",
"#media-panel",
"#media-panel *",
];
function bindListener(target, eventName, handler, options) {

View File

@@ -1,6 +1,10 @@
import { showStatusMessage } from "./ui.js";
import { getActiveTVTab, openTVPanelTab, isTVPanelVisible, setTVPanelVisible } from "./tv.js";
// News aggregation now lives inside the shared media panel:
// - outer shell: #media-panel
// - this module renders into inner pane: #news-panel
const EARTH_NEWS_API = "/api/v1/news/earth-feed";
const FOCUS_UPDATE_INTERVAL_MS = 4000;
const DATA_REFRESH_INTERVAL_MS = 180000;

View File

@@ -2,6 +2,11 @@ import Hls from "hls.js";
import { showStatusMessage } from "./ui.js";
import { createHUDPanel } from "./hud-panels.js";
// Naming convention:
// - #media-panel is the outer HUD shell, responsible for drag/resize/show-hide
// - #tv-panel is the inner live tab pane
// - #news-panel is the inner aggregation-news tab pane
const TV_STREAMS_API = "/api/v1/tv/streams";
const TV_PROXY_API = "/api/v1/tv/proxy";
const TV_STATUS_MESSAGE = {
@@ -22,7 +27,7 @@ let refreshPromise = null;
let hlsPlayer = null;
let hlsRecoveryAttempts = 0;
let metaAutoCollapseTimer = null;
let tvPanel = null;
let mediaPanel = null;
let activeTab = "live";
const failedSourceIds = new Set();
let probeTimer = null;
@@ -48,7 +53,8 @@ const HLS_RETRY_CONFIG = {
function getElements() {
return {
panel: document.getElementById("tv-panel"),
// Outer media shell node.
panel: document.getElementById("media-panel"),
toggleBtn: document.getElementById("toggle-tv"),
select: document.getElementById("tv-source-select"),
title: document.getElementById("tv-source-title"),
@@ -67,13 +73,14 @@ function getElements() {
newsHeaderControls: document.getElementById("tv-header-controls-news"),
liveTabBtn: document.getElementById("tv-tab-live"),
newsTabBtn: document.getElementById("tv-tab-news"),
livePane: document.getElementById("tv-tab-live-pane"),
newsPane: document.getElementById("tv-tab-news-pane"),
// Inner tab panes.
livePane: document.getElementById("tv-panel"),
newsPane: document.getElementById("news-panel"),
};
}
function setMetaCollapsed(collapsed) {
tvPanel?.setCollapsed(collapsed);
mediaPanel?.setCollapsed(collapsed);
}
function syncPanelActiveTab(tab = activeTab) {
@@ -224,7 +231,7 @@ function updateToggleButton(visible) {
}
function syncSettingsToggle(visible) {
const input = document.querySelector('[data-settings-panel="tv-panel"]');
const input = document.querySelector('[data-settings-panel="media-panel"]');
if (input instanceof HTMLInputElement) {
input.checked = visible;
}
@@ -233,7 +240,7 @@ function syncSettingsToggle(visible) {
function setPanelVisible(visible) {
const { panel } = getElements();
if (!panel) return;
tvPanel?.setVisible(visible);
mediaPanel?.setVisible(visible);
updateToggleButton(visible);
syncSettingsToggle(visible);
window.dispatchEvent(new CustomEvent("earth:tv-visibility-change", {
@@ -336,7 +343,7 @@ function updateTabState(target, isActive, activeClassName = "") {
function updateTabButtonState(button, isActive) {
if (!(button instanceof HTMLButtonElement)) return;
button.classList.toggle("tv-panel-tab--active", isActive);
button.classList.toggle("media-panel-tab--active", isActive);
button.setAttribute("aria-selected", isActive ? "true" : "false");
}
@@ -376,7 +383,7 @@ export function openTVPanelTab(tab = "live") {
}
export function isTVPanelVisible() {
return tvPanel?.isVisible() ?? !getElements().panel?.classList.contains("hud-panel-hidden");
return mediaPanel?.isVisible() ?? !getElements().panel?.classList.contains("hud-panel-hidden");
}
export function getActiveTVTab() {
@@ -861,7 +868,7 @@ export function initTVPanel() {
} = getElements();
if (panel && metaToggle) {
tvPanel = createHUDPanel({
mediaPanel = createHUDPanel({
panel,
header: ".hud-panel__header",
body: "#tv-meta-wrap",
@@ -873,13 +880,13 @@ export function initTVPanel() {
});
}
updateToggleButton(tvPanel?.isVisible() ?? !panel?.classList.contains("hud-panel-hidden"));
syncSettingsToggle(tvPanel?.isVisible() ?? !panel?.classList.contains("hud-panel-hidden"));
updateToggleButton(mediaPanel?.isVisible() ?? !panel?.classList.contains("hud-panel-hidden"));
syncSettingsToggle(mediaPanel?.isVisible() ?? !panel?.classList.contains("hud-panel-hidden"));
toggleBtn?.addEventListener("click", async (event) => {
event.preventDefault();
event.stopPropagation();
const currentlyVisible = tvPanel?.isVisible() ?? false;
const currentlyVisible = mediaPanel?.isVisible() ?? false;
if (!currentlyVisible) {
setPanelVisible(true);
setActiveTab("live");
@@ -907,7 +914,7 @@ export function initTVPanel() {
metaToggle?.addEventListener("click", () => {
clearTimeout(metaAutoCollapseTimer);
const isNowCollapsed = !(tvPanel?.isCollapsed() ?? false);
const isNowCollapsed = !(mediaPanel?.isCollapsed() ?? false);
setMetaCollapsed(isNowCollapsed);
});