release: bump version to 0.28.0
This commit is contained in:
@@ -8,6 +8,23 @@ This project follows the repository versioning rule:
|
|||||||
- `improvement` -> `+0.0.1`(bugfix + 小功能混合)
|
- `improvement` -> `+0.0.1`(bugfix + 小功能混合)
|
||||||
- `bugfix` -> `+0.0.1`
|
- `bugfix` -> `+0.0.1`
|
||||||
|
|
||||||
|
## [0.28.0] — 2026-04-20
|
||||||
|
|
||||||
|
### ✨ Features
|
||||||
|
- 将 Earth 的“新闻直播”和“全球态势聚合”合并为统一的“媒体情报”面板,支持底部 tab 切换与共享标题栏操作区
|
||||||
|
- 聚合新闻不再单独占据一个 HUD 面板,而是作为媒体情报面板内的第二视图与直播协同呈现
|
||||||
|
|
||||||
|
### 🔧 Improvements
|
||||||
|
- TV / News 面板切换加入底边锚定的 reform 动画,并继续保留拖拽、缩放和共享 HUD 行为收口
|
||||||
|
- 聚合新闻视图新增默认高度约束与内部滚动填充逻辑,避免初始高度过度膨胀
|
||||||
|
- `tv.js`、`news.js` 进一步清理共享 HUDPanel 迁移后的残留逻辑,收紧 tab / resize / reform 相关局部 helper
|
||||||
|
|
||||||
|
### 🐛 Fixes
|
||||||
|
- 修复媒体情报面板右下角缩放时高度异常抬升、标题栏被顶出视口的问题
|
||||||
|
- 修复直播/聚合 tab 切换时按钮高亮、内容切换和底边基准表现不一致的问题
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## [0.27.7] — 2026-04-16
|
## [0.27.7] — 2026-04-16
|
||||||
|
|
||||||
## [0.27.8] — 2026-04-20
|
## [0.27.8] — 2026-04-20
|
||||||
|
|||||||
@@ -16,12 +16,13 @@
|
|||||||
## Current Version
|
## Current Version
|
||||||
|
|
||||||
- `main` 当前主线历史推导到:`0.16.5`
|
- `main` 当前主线历史推导到:`0.16.5`
|
||||||
- `dev` 当前开发分支历史推导到:`0.27.8`
|
- `dev` 当前开发分支历史推导到:`0.28.0`
|
||||||
|
|
||||||
## Timeline
|
## Timeline
|
||||||
|
|
||||||
| Version | Type | Branch | Commit | Summary |
|
| Version | Type | Branch | Commit | Summary |
|
||||||
| --- | --- | --- | --- | --- |
|
| --- | --- | --- | --- | --- |
|
||||||
|
| `0.28.0` | feature | `dev` | `pending` | 合并 Earth 媒体情报面板,整合新闻直播与态势聚合 tab,并稳定 TV/news 的 reform、resize 与共享 HUD 行为 |
|
||||||
| `0.27.8` | bugfix | `dev` | `pending` | 统一 Earth HUD 默认折叠逻辑,修复图例与图层面板箭头和底边阈值行为 |
|
| `0.27.8` | bugfix | `dev` | `pending` | 统一 Earth HUD 默认折叠逻辑,修复图例与图层面板箭头和底边阈值行为 |
|
||||||
| `0.27.7` | bugfix | `dev` | `pending` | 修复电视直播源编辑持久化问题,清理表格空白占位列并统一可折叠操作列 |
|
| `0.27.7` | bugfix | `dev` | `pending` | 修复电视直播源编辑持久化问题,清理表格空白占位列并统一可折叠操作列 |
|
||||||
| `0.27.6` | improvement | `dev` | `pending` | BGP/用户表格滚动条修复,Playground 响应式按钮与输入框收起优化 |
|
| `0.27.6` | improvement | `dev` | `pending` | BGP/用户表格滚动条修复,Playground 响应式按钮与输入框收起优化 |
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "planet-frontend",
|
"name": "planet-frontend",
|
||||||
"version": "0.27.8",
|
"version": "0.28.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"packageManager": "bun@1",
|
"packageManager": "bun@1",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
@@ -1,39 +1,18 @@
|
|||||||
/* news-panel.css */
|
/* news-panel.css */
|
||||||
|
|
||||||
.hud-panel-news {
|
|
||||||
--hud-body-max-height: calc(580px * var(--hud-scale));
|
|
||||||
top: calc(214px * var(--hud-scale));
|
|
||||||
right: var(--hud-offset);
|
|
||||||
width: calc(420px * var(--hud-scale));
|
|
||||||
min-width: calc(320px * var(--hud-scale));
|
|
||||||
max-width: calc(100vw - 32px);
|
|
||||||
padding: calc(12px * var(--hud-scale));
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: var(--hud-gap-sm);
|
|
||||||
z-index: 17;
|
|
||||||
}
|
|
||||||
|
|
||||||
.news-panel-header {
|
|
||||||
align-items: center;
|
|
||||||
gap: var(--hud-gap-xs);
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.news-panel-header-copy {
|
|
||||||
flex: 1 1 auto;
|
|
||||||
min-width: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.news-panel-title-row {
|
.news-panel-title-row {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: calc(8px * var(--hud-scale));
|
gap: calc(8px * var(--hud-scale));
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
|
flex: 1 1 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.news-panel-title {
|
.news-panel-title {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
color: var(--hud-text);
|
||||||
|
font-size: calc(0.92rem * var(--hud-scale));
|
||||||
|
font-weight: 600;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
@@ -58,9 +37,11 @@
|
|||||||
|
|
||||||
.news-panel-body {
|
.news-panel-body {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex: 1 1 auto;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: var(--hud-gap-sm);
|
gap: var(--hud-gap-sm);
|
||||||
--hud-body-collapse-gap: var(--hud-gap-sm);
|
min-height: 0;
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.news-panel-focus {
|
.news-panel-focus {
|
||||||
@@ -104,15 +85,20 @@
|
|||||||
|
|
||||||
.news-board {
|
.news-board {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex: 1 1 auto;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: var(--hud-gap-sm);
|
gap: var(--hud-gap-sm);
|
||||||
|
min-height: 0;
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.news-board-list {
|
.news-board-list {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex: 1 1 auto;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: calc(8px * var(--hud-scale));
|
gap: calc(8px * var(--hud-scale));
|
||||||
max-height: calc(420px * var(--hud-scale));
|
min-height: 0;
|
||||||
|
max-height: none;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
padding-right: calc(4px * var(--hud-scale));
|
padding-right: calc(4px * var(--hud-scale));
|
||||||
scrollbar-width: thin;
|
scrollbar-width: thin;
|
||||||
@@ -205,7 +191,3 @@
|
|||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
padding: calc(16px * var(--hud-scale)) calc(4px * var(--hud-scale));
|
padding: calc(16px * var(--hud-scale)) calc(4px * var(--hud-scale));
|
||||||
}
|
}
|
||||||
|
|
||||||
.earth-app.layout-expanded .hud-panel-news:not([data-dragged="true"]) {
|
|
||||||
transform: translate(calc(100% - var(--hud-offset)), calc(-100% + var(--hud-offset)));
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
right: var(--hud-offset);
|
right: var(--hud-offset);
|
||||||
width: calc(420px * var(--hud-scale));
|
width: calc(420px * var(--hud-scale));
|
||||||
max-width: calc(100vw - 32px);
|
max-width: calc(100vw - 32px);
|
||||||
|
max-height: calc(100vh - (2 * var(--hud-offset)));
|
||||||
min-width: calc(300px * var(--hud-scale));
|
min-width: calc(300px * var(--hud-scale));
|
||||||
padding: calc(10px * var(--hud-scale));
|
padding: calc(10px * var(--hud-scale));
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -13,7 +14,20 @@
|
|||||||
z-index: 18;
|
z-index: 18;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* header 内嵌 select + actions */
|
.hud-panel-tv[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 {
|
||||||
|
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-tv .hud-panel__header {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: var(--hud-gap-xs);
|
gap: var(--hud-gap-xs);
|
||||||
@@ -25,14 +39,53 @@
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* select / action 在 drag-handle 内,恢复正常指针 */
|
|
||||||
.hud-panel-tv .hud-panel__header .tv-panel-select,
|
|
||||||
.hud-panel-tv .hud-panel__header .hud-panel__action,
|
.hud-panel-tv .hud-panel__header .hud-panel__action,
|
||||||
.hud-panel-tv .hud-panel__header .hud-panel-close {
|
.hud-panel-tv .hud-panel__header .hud-panel-close {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
user-select: auto;
|
user-select: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.hud-panel-tv .hud-panel__header .tv-panel-select,
|
||||||
|
.hud-panel-tv .hud-panel__header .tv-panel-tab {
|
||||||
|
cursor: pointer;
|
||||||
|
user-select: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tv-panel-header-controls {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--hud-gap-xs);
|
||||||
|
flex: 1 1 auto;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tv-panel-header-controls--news {
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tv-panel-content {
|
||||||
|
display: flex;
|
||||||
|
flex: 1 1 auto;
|
||||||
|
min-height: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tv-tab-pane {
|
||||||
|
display: flex;
|
||||||
|
flex: 1 1 auto;
|
||||||
|
min-height: 0;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--hud-gap-sm);
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tv-panel-toolbar-actions {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--hud-gap-xs);
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.tv-panel-status {
|
.tv-panel-status {
|
||||||
color: var(--hud-text-soft);
|
color: var(--hud-text-soft);
|
||||||
font-size: calc(0.68rem * var(--hud-scale));
|
font-size: calc(0.68rem * var(--hud-scale));
|
||||||
@@ -141,6 +194,41 @@
|
|||||||
background: #050a14;
|
background: #050a14;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tv-panel-tabs {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
gap: calc(8px * var(--hud-scale));
|
||||||
|
}
|
||||||
|
|
||||||
|
.tv-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);
|
||||||
|
color: var(--hud-text-muted);
|
||||||
|
padding: calc(9px * var(--hud-scale)) calc(12px * var(--hud-scale));
|
||||||
|
font-size: calc(0.78rem * var(--hud-scale));
|
||||||
|
font-weight: 600;
|
||||||
|
letter-spacing: 0.04em;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background 0.18s ease, border-color 0.18s ease, color 0.18s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tv-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 {
|
||||||
|
color: var(--hud-accent-strong);
|
||||||
|
border-color: rgba(120, 180, 255, 0.24);
|
||||||
|
background: rgba(120, 180, 255, 0.12);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tv-tab-pane[hidden] {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
/* ── Multi-edge resize handles ───────────────────────────────── */
|
/* ── Multi-edge resize handles ───────────────────────────────── */
|
||||||
|
|
||||||
.tv-panel-edge {
|
.tv-panel-edge {
|
||||||
|
|||||||
@@ -297,10 +297,11 @@
|
|||||||
<div id="tv-panel" class="hud-panel hud-panel-tv hud-panel-draggable" data-panel-key="tv-panel" data-drag-self="true">
|
<div id="tv-panel" class="hud-panel hud-panel-tv hud-panel-draggable" data-panel-key="tv-panel" data-drag-self="true">
|
||||||
<div class="hud-panel__header hud-panel-drag-handle">
|
<div class="hud-panel__header hud-panel-drag-handle">
|
||||||
<div class="hud-panel__title-group">
|
<div class="hud-panel__title-group">
|
||||||
<span class="hud-panel-title hud-panel__title tv-panel-header-title">新闻直播</span>
|
<span class="hud-panel-title hud-panel__title tv-panel-header-title">媒体情报</span>
|
||||||
</div>
|
</div>
|
||||||
|
<div id="tv-header-controls-live" class="tv-panel-header-controls tv-panel-header-controls--live">
|
||||||
<select id="tv-source-select" class="tv-panel-select" aria-label="选择新闻直播源"></select>
|
<select id="tv-source-select" class="tv-panel-select" aria-label="选择新闻直播源"></select>
|
||||||
<div class="hud-panel__actions">
|
<div class="tv-panel-toolbar-actions">
|
||||||
<button id="tv-refresh" class="hud-panel__action hud-panel__action--refresh" type="button" title="刷新直播源" aria-label="刷新直播源">
|
<button id="tv-refresh" class="hud-panel__action hud-panel__action--refresh" type="button" title="刷新直播源" aria-label="刷新直播源">
|
||||||
<span class="material-symbols-rounded">refresh</span>
|
<span class="material-symbols-rounded">refresh</span>
|
||||||
</button>
|
</button>
|
||||||
@@ -310,11 +311,31 @@
|
|||||||
<button id="tv-meta-toggle" class="hud-panel__action hud-panel__action--collapse tv-panel-meta-toggle" type="button" title="折叠新闻直播内容" aria-label="折叠新闻直播内容">
|
<button id="tv-meta-toggle" class="hud-panel__action hud-panel__action--collapse tv-panel-meta-toggle" type="button" title="折叠新闻直播内容" aria-label="折叠新闻直播内容">
|
||||||
<span class="material-symbols-rounded">expand_less</span>
|
<span class="material-symbols-rounded">expand_less</span>
|
||||||
</button>
|
</button>
|
||||||
<button class="hud-panel-close hud-panel__action hud-panel__action--close" type="button" data-close-panel="tv-panel" aria-label="关闭电视直播">
|
</div>
|
||||||
|
</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">
|
||||||
|
<button id="news-refresh" class="hud-panel__action hud-panel__action--refresh" type="button" title="刷新新闻源" aria-label="刷新新闻源">
|
||||||
|
<span class="material-symbols-rounded">refresh</span>
|
||||||
|
</button>
|
||||||
|
<button id="news-open-external" class="hud-panel__action hud-panel__action--external" type="button" title="打开源站" aria-label="打开源站">
|
||||||
|
<span class="material-symbols-rounded">open_in_new</span>
|
||||||
|
</button>
|
||||||
|
</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="关闭媒体情报面板">
|
||||||
<span class="material-symbols-rounded">close</span>
|
<span class="material-symbols-rounded">close</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</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">
|
||||||
<div class="tv-panel-meta-wrap" id="tv-meta-wrap">
|
<div class="tv-panel-meta-wrap" id="tv-meta-wrap">
|
||||||
<div class="tv-panel-meta" id="tv-panel-meta">
|
<div class="tv-panel-meta" id="tv-panel-meta">
|
||||||
<span id="tv-source-status" class="tv-panel-status">等待加载直播源</span>
|
<span id="tv-source-status" class="tv-panel-status">等待加载直播源</span>
|
||||||
@@ -324,6 +345,7 @@
|
|||||||
<div id="tv-source-notes" class="tv-panel-notes">支持后台配置默认源与采集器补充源。</div>
|
<div id="tv-source-notes" class="tv-panel-notes">支持后台配置默认源与采集器补充源。</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="tv-panel-player">
|
<div class="tv-panel-player">
|
||||||
<div id="tv-empty-state" class="tv-panel-empty">暂无可播放直播源,请先在系统配置中添加频道。</div>
|
<div id="tv-empty-state" class="tv-panel-empty">暂无可播放直播源,请先在系统配置中添加频道。</div>
|
||||||
<iframe
|
<iframe
|
||||||
@@ -336,38 +358,10 @@
|
|||||||
></iframe>
|
></iframe>
|
||||||
<video id="tv-video" class="tv-panel-video" hidden controls autoplay muted playsinline></video>
|
<video id="tv-video" class="tv-panel-video" hidden controls autoplay muted playsinline></video>
|
||||||
</div>
|
</div>
|
||||||
<div class="tv-panel-edge" data-edge="r"></div>
|
</section>
|
||||||
<div class="tv-panel-edge" data-edge="b"></div>
|
|
||||||
<div class="tv-panel-edge" data-edge="l"></div>
|
|
||||||
<div class="tv-panel-edge" data-edge="br"></div>
|
|
||||||
<div class="tv-panel-edge" data-edge="bl"></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="news-panel" class="hud-panel hud-panel-news hud-panel-draggable hud-panel--expand-down" data-panel-key="news-panel">
|
<section id="tv-tab-news-pane" class="tv-tab-pane tv-tab-pane--news" aria-labelledby="tv-tab-news" hidden>
|
||||||
<div class="hud-panel__header hud-panel-drag-handle">
|
<div id="news-panel-body" class="news-panel-body">
|
||||||
<div class="news-panel-header-copy hud-panel__title-group">
|
|
||||||
<div class="news-panel-title-row">
|
|
||||||
<span class="hud-panel-title hud-panel__title news-panel-title">全球态势聚合</span>
|
|
||||||
<span id="news-region-chip" class="news-region-chip hud-panel__chip">global</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="hud-panel__actions">
|
|
||||||
<button id="news-refresh" class="hud-panel__action hud-panel__action--refresh" type="button" title="刷新新闻源" aria-label="刷新新闻源">
|
|
||||||
<span class="material-symbols-rounded">refresh</span>
|
|
||||||
</button>
|
|
||||||
<button id="news-open-external" class="hud-panel__action hud-panel__action--external" type="button" title="打开源站" aria-label="打开源站">
|
|
||||||
<span class="material-symbols-rounded">open_in_new</span>
|
|
||||||
</button>
|
|
||||||
<button id="news-collapse" class="hud-panel__action hud-panel__action--collapse news-panel-collapse" type="button" title="折叠新闻面板" aria-label="折叠新闻面板">
|
|
||||||
<span class="material-symbols-rounded">expand_less</span>
|
|
||||||
</button>
|
|
||||||
<button class="hud-panel__action hud-panel__action--close hud-panel-close" type="button" data-close-panel="news-panel" aria-label="关闭态势新闻">
|
|
||||||
<span class="material-symbols-rounded">close</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="news-panel-body" class="news-panel-body hud-panel__body hud-panel__body--collapsible">
|
|
||||||
<div class="news-panel-subtitle">跟随地球正面视角自动切换区域新闻</div>
|
<div class="news-panel-subtitle">跟随地球正面视角自动切换区域新闻</div>
|
||||||
|
|
||||||
<div class="news-panel-focus">
|
<div class="news-panel-focus">
|
||||||
@@ -386,6 +380,18 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<a id="news-feed-anchor" hidden rel="noreferrer noopener" target="_blank"></a>
|
<a id="news-feed-anchor" hidden rel="noreferrer noopener" target="_blank"></a>
|
||||||
|
</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>
|
||||||
|
<div class="tv-panel-edge" data-edge="r"></div>
|
||||||
|
<div class="tv-panel-edge" data-edge="b"></div>
|
||||||
|
<div class="tv-panel-edge" data-edge="l"></div>
|
||||||
|
<div class="tv-panel-edge" data-edge="br"></div>
|
||||||
|
<div class="tv-panel-edge" data-edge="bl"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="loading" class="earth-loading">
|
<div id="loading" class="earth-loading">
|
||||||
@@ -451,16 +457,6 @@
|
|||||||
<span class="earth-settings-switch-track"></span>
|
<span class="earth-settings-switch-track"></span>
|
||||||
</span>
|
</span>
|
||||||
</label>
|
</label>
|
||||||
<label class="earth-settings-item" for="toggle-view-news">
|
|
||||||
<div class="earth-settings-copy">
|
|
||||||
<span class="earth-settings-item-title">态势新闻</span>
|
|
||||||
<span class="earth-settings-item-subtitle">控制跟随视角切换的全球态势 RSS 看板显示</span>
|
|
||||||
</div>
|
|
||||||
<span class="earth-settings-switch">
|
|
||||||
<input id="toggle-view-news" type="checkbox" data-settings-panel="news-panel" checked>
|
|
||||||
<span class="earth-settings-switch-track"></span>
|
|
||||||
</span>
|
|
||||||
</label>
|
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
21
frontend/public/earth/js/controls.js
vendored
21
frontend/public/earth/js/controls.js
vendored
@@ -21,7 +21,6 @@ import { ensureTVPanelReady } from "./tv.js";
|
|||||||
import { createHUDPanel } from "./hud-panels.js";
|
import { createHUDPanel } from "./hud-panels.js";
|
||||||
import {
|
import {
|
||||||
ensureNewsPanelReady,
|
ensureNewsPanelReady,
|
||||||
setNewsPanelVisible,
|
|
||||||
updateNewsToggleUI,
|
updateNewsToggleUI,
|
||||||
} from "./news.js";
|
} from "./news.js";
|
||||||
|
|
||||||
@@ -37,7 +36,6 @@ const HUD_PANEL_IDS = [
|
|||||||
"legend",
|
"legend",
|
||||||
"earth-stats",
|
"earth-stats",
|
||||||
"tv-panel",
|
"tv-panel",
|
||||||
"news-panel",
|
|
||||||
"layer-toggles",
|
"layer-toggles",
|
||||||
];
|
];
|
||||||
const DRAGGABLE_PANEL_SELECTOR = ".hud-panel-draggable";
|
const DRAGGABLE_PANEL_SELECTOR = ".hud-panel-draggable";
|
||||||
@@ -98,20 +96,13 @@ function setHudPanelVisibility(panelId, visible) {
|
|||||||
syncSettingsToggle(panelId, visible);
|
syncSettingsToggle(panelId, visible);
|
||||||
if (panelId === "tv-panel") {
|
if (panelId === "tv-panel") {
|
||||||
updateTVToggleUI(visible);
|
updateTVToggleUI(visible);
|
||||||
|
updateNewsToggleUI(visible);
|
||||||
if (visible) {
|
if (visible) {
|
||||||
ensureTVPanelReady().catch((error) => {
|
ensureTVPanelReady().catch((error) => {
|
||||||
console.error("初始化电视直播面板失败:", error);
|
console.error("初始化电视直播面板失败:", error);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (panelId === "news-panel") {
|
|
||||||
updateNewsToggleUI(visible);
|
|
||||||
if (visible) {
|
|
||||||
ensureNewsPanelReady().catch((error) => {
|
|
||||||
console.error("初始化态势新闻面板失败:", error);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function syncSettingsToggle(panelId, visible) {
|
function syncSettingsToggle(panelId, visible) {
|
||||||
@@ -245,7 +236,7 @@ function setupDraggableHudPanels() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
bindListener(handle, "pointerdown", (event) => {
|
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-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, .tv-panel-tab, .tv-panel-player, .tv-panel-edge, .legend-bar-btn, .news-story-card")) return;
|
||||||
isDragging = true;
|
isDragging = true;
|
||||||
startPointerX = event.clientX;
|
startPointerX = event.clientX;
|
||||||
startPointerY = event.clientY;
|
startPointerY = event.clientY;
|
||||||
@@ -823,14 +814,10 @@ function setupTerrainControls() {
|
|||||||
console.error("初始化电视直播面板失败:", error);
|
console.error("初始化电视直播面板失败:", error);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
const newsVisible = !document.getElementById("news-panel")?.classList.contains("hud-panel-hidden");
|
updateNewsToggleUI(!document.getElementById("tv-panel")?.classList.contains("hud-panel-hidden"));
|
||||||
updateNewsToggleUI(newsVisible);
|
|
||||||
setNewsPanelVisible(newsVisible);
|
|
||||||
if (newsVisible) {
|
|
||||||
ensureNewsPanelReady().catch((error) => {
|
ensureNewsPanelReady().catch((error) => {
|
||||||
console.error("初始化态势新闻面板失败:", error);
|
console.error("初始化态势新闻内容失败:", error);
|
||||||
});
|
});
|
||||||
}
|
|
||||||
updateLayoutUI(container);
|
updateLayoutUI(container);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { showStatusMessage } from "./ui.js";
|
import { showStatusMessage } from "./ui.js";
|
||||||
import { createHUDPanel } from "./hud-panels.js";
|
import { getActiveTVTab, openTVPanelTab, isTVPanelVisible, setTVPanelVisible } from "./tv.js";
|
||||||
|
|
||||||
const EARTH_NEWS_API = "/api/v1/news/earth-feed";
|
const EARTH_NEWS_API = "/api/v1/news/earth-feed";
|
||||||
const FOCUS_UPDATE_INTERVAL_MS = 4000;
|
const FOCUS_UPDATE_INTERVAL_MS = 4000;
|
||||||
@@ -13,15 +13,11 @@ let payload = null;
|
|||||||
let lastFocus = null;
|
let lastFocus = null;
|
||||||
let lastFetchAt = 0;
|
let lastFetchAt = 0;
|
||||||
let lastRegionSwitchAt = 0;
|
let lastRegionSwitchAt = 0;
|
||||||
let newsPanel = null;
|
|
||||||
|
|
||||||
function getElements() {
|
function getElements() {
|
||||||
return {
|
return {
|
||||||
panel: document.getElementById("news-panel"),
|
|
||||||
toggleBtn: document.getElementById("toggle-news"),
|
toggleBtn: document.getElementById("toggle-news"),
|
||||||
refreshBtn: document.getElementById("news-refresh"),
|
refreshBtn: document.getElementById("news-refresh"),
|
||||||
openBtn: document.getElementById("news-open-external"),
|
openBtn: document.getElementById("news-open-external"),
|
||||||
collapseBtn: document.getElementById("news-collapse"),
|
|
||||||
status: document.getElementById("news-board-status"),
|
status: document.getElementById("news-board-status"),
|
||||||
focusLabel: document.getElementById("news-focus-label"),
|
focusLabel: document.getElementById("news-focus-label"),
|
||||||
focusCoords: document.getElementById("news-focus-coords"),
|
focusCoords: document.getElementById("news-focus-coords"),
|
||||||
@@ -55,28 +51,14 @@ function formatRelativeTime(raw) {
|
|||||||
export function updateNewsToggleUI(visible) {
|
export function updateNewsToggleUI(visible) {
|
||||||
const { toggleBtn } = getElements();
|
const { toggleBtn } = getElements();
|
||||||
if (!toggleBtn) return;
|
if (!toggleBtn) return;
|
||||||
toggleBtn.classList.toggle("active", visible);
|
const active = visible && getActiveTVTab() === "news";
|
||||||
|
toggleBtn.classList.toggle("active", active);
|
||||||
const tooltip = toggleBtn.querySelector(".earth-toolbar-tooltip");
|
const tooltip = toggleBtn.querySelector(".earth-toolbar-tooltip");
|
||||||
if (tooltip) {
|
if (tooltip) {
|
||||||
tooltip.textContent = visible ? "关闭态势新闻" : "打开态势新闻";
|
tooltip.textContent = active ? "关闭态势新闻" : "打开态势新闻";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function syncSettingsToggle(visible) {
|
|
||||||
const input = document.querySelector('[data-settings-panel="news-panel"]');
|
|
||||||
if (input instanceof HTMLInputElement) {
|
|
||||||
input.checked = visible;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function setNewsPanelVisible(visible) {
|
|
||||||
const { panel } = getElements();
|
|
||||||
if (!panel) return;
|
|
||||||
newsPanel?.setVisible(visible);
|
|
||||||
updateNewsToggleUI(visible);
|
|
||||||
syncSettingsToggle(visible);
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderEmptyState(message) {
|
function renderEmptyState(message) {
|
||||||
const { board, empty, status, openBtn } = getElements();
|
const { board, empty, status, openBtn } = getElements();
|
||||||
if (board) board.innerHTML = "";
|
if (board) board.innerHTML = "";
|
||||||
@@ -264,10 +246,6 @@ function openCurrentSourceHomepage() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function setCollapsed(collapsed) {
|
|
||||||
newsPanel?.setCollapsed(collapsed);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function updateNewsViewFocus(coords) {
|
export function updateNewsViewFocus(coords) {
|
||||||
if (!initialized) return;
|
if (!initialized) return;
|
||||||
if (!coords || typeof coords.lat !== "number" || typeof coords.lon !== "number") return;
|
if (!coords || typeof coords.lat !== "number" || typeof coords.lon !== "number") return;
|
||||||
@@ -301,34 +279,44 @@ export function initNewsPanel() {
|
|||||||
if (initialized) return;
|
if (initialized) return;
|
||||||
initialized = true;
|
initialized = true;
|
||||||
|
|
||||||
const { panel, toggleBtn, refreshBtn, openBtn, collapseBtn } = getElements();
|
const { toggleBtn, refreshBtn, openBtn } = getElements();
|
||||||
|
|
||||||
if (panel && collapseBtn) {
|
updateNewsToggleUI(isTVPanelVisible());
|
||||||
newsPanel = createHUDPanel({
|
|
||||||
panel,
|
|
||||||
header: ".hud-panel__header",
|
|
||||||
body: "#news-panel-body",
|
|
||||||
collapseBtn,
|
|
||||||
preferredDirection: "down",
|
|
||||||
expandLabel: "展开新闻面板",
|
|
||||||
collapseLabel: "折叠新闻面板",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
updateNewsToggleUI(true);
|
|
||||||
syncSettingsToggle(true);
|
|
||||||
renderEmptyState("正在准备全球态势新闻聚合源...");
|
renderEmptyState("正在准备全球态势新闻聚合源...");
|
||||||
|
|
||||||
toggleBtn?.addEventListener("click", async () => {
|
const openNewsTab = async () => {
|
||||||
const nextVisible = !(newsPanel?.isVisible() ?? false);
|
openTVPanelTab("news");
|
||||||
setNewsPanelVisible(nextVisible);
|
updateNewsToggleUI(true);
|
||||||
if (nextVisible) {
|
|
||||||
try {
|
try {
|
||||||
await ensureNewsPanelReady();
|
await ensureNewsPanelReady();
|
||||||
} catch {
|
} catch {
|
||||||
// surface already handled
|
// surface already handled
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
toggleBtn?.addEventListener("click", async () => {
|
||||||
|
const visible = isTVPanelVisible();
|
||||||
|
const active = visible && getActiveTVTab() === "news";
|
||||||
|
|
||||||
|
if (!visible) {
|
||||||
|
await openNewsTab();
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!active) {
|
||||||
|
await openNewsTab();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setTVPanelVisible(false);
|
||||||
|
updateNewsToggleUI(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
window.addEventListener("earth:tv-tab-change", () => {
|
||||||
|
updateNewsToggleUI(isTVPanelVisible());
|
||||||
|
});
|
||||||
|
window.addEventListener("earth:tv-visibility-change", (event) => {
|
||||||
|
updateNewsToggleUI(Boolean(event.detail?.visible));
|
||||||
});
|
});
|
||||||
|
|
||||||
refreshBtn?.addEventListener("click", async () => {
|
refreshBtn?.addEventListener("click", async () => {
|
||||||
@@ -341,10 +329,6 @@ export function initNewsPanel() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
openBtn?.addEventListener("click", openCurrentSourceHomepage);
|
openBtn?.addEventListener("click", openCurrentSourceHomepage);
|
||||||
collapseBtn?.addEventListener("click", (event) => {
|
|
||||||
event.stopPropagation();
|
|
||||||
setCollapsed(!newsPanel?.isCollapsed());
|
|
||||||
});
|
|
||||||
|
|
||||||
refreshNews(undefined, undefined, { silent: true }).catch(() => {});
|
refreshNews(undefined, undefined, { silent: true }).catch(() => {});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,11 +23,20 @@ let hlsPlayer = null;
|
|||||||
let hlsRecoveryAttempts = 0;
|
let hlsRecoveryAttempts = 0;
|
||||||
let metaAutoCollapseTimer = null;
|
let metaAutoCollapseTimer = null;
|
||||||
let tvPanel = null;
|
let tvPanel = null;
|
||||||
|
let activeTab = "live";
|
||||||
const failedSourceIds = new Set();
|
const failedSourceIds = new Set();
|
||||||
let probeTimer = null;
|
let probeTimer = null;
|
||||||
|
let reformCleanupTimer = null;
|
||||||
|
|
||||||
const META_AUTO_COLLAPSE_DELAY = 2500;
|
const META_AUTO_COLLAPSE_DELAY = 2500;
|
||||||
const PROBE_INTERVAL_MS = 2 * 60 * 1000;
|
const PROBE_INTERVAL_MS = 2 * 60 * 1000;
|
||||||
|
const DEFAULT_HUD_OFFSET_PX = 20;
|
||||||
|
const MIN_NEWS_TAB_HEIGHT_PX = 280;
|
||||||
|
const PANEL_RESIZE_MARGIN_PX = 12;
|
||||||
|
const TV_PANEL_MIN_WIDTH_PX = 360;
|
||||||
|
const TV_PANEL_MIN_HEIGHT_PX = 340;
|
||||||
|
const REFORM_CLEANUP_MS = 280;
|
||||||
|
const REFORM_RESTORE_ANCHOR_DATA_KEY = "reformRestoreAnchor";
|
||||||
|
|
||||||
const HLS_MAX_RECOVERY_ATTEMPTS = 3;
|
const HLS_MAX_RECOVERY_ATTEMPTS = 3;
|
||||||
const HLS_RETRY_CONFIG = {
|
const HLS_RETRY_CONFIG = {
|
||||||
@@ -41,7 +50,6 @@ function getElements() {
|
|||||||
return {
|
return {
|
||||||
panel: document.getElementById("tv-panel"),
|
panel: document.getElementById("tv-panel"),
|
||||||
toggleBtn: document.getElementById("toggle-tv"),
|
toggleBtn: document.getElementById("toggle-tv"),
|
||||||
resizeHandle: null,
|
|
||||||
select: document.getElementById("tv-source-select"),
|
select: document.getElementById("tv-source-select"),
|
||||||
title: document.getElementById("tv-source-title"),
|
title: document.getElementById("tv-source-title"),
|
||||||
meta: document.getElementById("tv-source-meta"),
|
meta: document.getElementById("tv-source-meta"),
|
||||||
@@ -53,9 +61,14 @@ function getElements() {
|
|||||||
empty: document.getElementById("tv-empty-state"),
|
empty: document.getElementById("tv-empty-state"),
|
||||||
refreshBtn: document.getElementById("tv-refresh"),
|
refreshBtn: document.getElementById("tv-refresh"),
|
||||||
openBtn: document.getElementById("tv-open-external"),
|
openBtn: document.getElementById("tv-open-external"),
|
||||||
metaPanel: document.getElementById("tv-panel-meta"),
|
|
||||||
metaWrap: document.getElementById("tv-meta-wrap"),
|
metaWrap: document.getElementById("tv-meta-wrap"),
|
||||||
metaToggle: document.getElementById("tv-meta-toggle"),
|
metaToggle: document.getElementById("tv-meta-toggle"),
|
||||||
|
liveHeaderControls: document.getElementById("tv-header-controls-live"),
|
||||||
|
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"),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -63,6 +76,37 @@ function setMetaCollapsed(collapsed) {
|
|||||||
tvPanel?.setCollapsed(collapsed);
|
tvPanel?.setCollapsed(collapsed);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function syncPanelActiveTab(tab = activeTab) {
|
||||||
|
const { panel } = getElements();
|
||||||
|
if (panel instanceof HTMLElement) {
|
||||||
|
panel.dataset.activeTab = tab;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function syncNewsDefaultMaxHeight() {
|
||||||
|
const { panel } = getElements();
|
||||||
|
if (!(panel instanceof HTMLElement)) return;
|
||||||
|
|
||||||
|
const earthStats = document.getElementById("earth-stats");
|
||||||
|
const hudOffset = Number.parseFloat(
|
||||||
|
getComputedStyle(document.documentElement).getPropertyValue("--hud-offset"),
|
||||||
|
);
|
||||||
|
const resolvedOffset = Number.isFinite(hudOffset) ? hudOffset : DEFAULT_HUD_OFFSET_PX;
|
||||||
|
|
||||||
|
if (!(earthStats instanceof HTMLElement)) {
|
||||||
|
panel.style.removeProperty("--tv-news-default-max-height");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const statsRect = earthStats.getBoundingClientRect();
|
||||||
|
const availableHeight = Math.max(
|
||||||
|
Math.round(MIN_NEWS_TAB_HEIGHT_PX * getHudScale()),
|
||||||
|
Math.floor(window.innerHeight - resolvedOffset - statsRect.bottom),
|
||||||
|
);
|
||||||
|
|
||||||
|
panel.style.setProperty("--tv-news-default-max-height", `${availableHeight}px`);
|
||||||
|
}
|
||||||
|
|
||||||
function autoExpandMeta() {
|
function autoExpandMeta() {
|
||||||
clearTimeout(metaAutoCollapseTimer);
|
clearTimeout(metaAutoCollapseTimer);
|
||||||
setMetaCollapsed(false);
|
setMetaCollapsed(false);
|
||||||
@@ -92,12 +136,14 @@ function setupResizeHandle() {
|
|||||||
|
|
||||||
let resizing = false;
|
let resizing = false;
|
||||||
let activeEdge = "";
|
let activeEdge = "";
|
||||||
let startX = 0;
|
const resizeStart = {
|
||||||
let startY = 0;
|
pointerX: 0,
|
||||||
let startWidth = 0;
|
pointerY: 0,
|
||||||
let startHeight = 0;
|
width: 0,
|
||||||
let startLeft = 0;
|
height: 0,
|
||||||
let startTop = 0;
|
left: 0,
|
||||||
|
top: 0,
|
||||||
|
};
|
||||||
|
|
||||||
const stopResize = () => {
|
const stopResize = () => {
|
||||||
resizing = false;
|
resizing = false;
|
||||||
@@ -110,23 +156,23 @@ function setupResizeHandle() {
|
|||||||
if (!resizing) return;
|
if (!resizing) return;
|
||||||
const containerRect = container.getBoundingClientRect();
|
const containerRect = container.getBoundingClientRect();
|
||||||
const hudScale = getHudScale();
|
const hudScale = getHudScale();
|
||||||
const minWidth = Math.max(320, Math.round(360 * hudScale));
|
const minWidth = Math.max(320, Math.round(TV_PANEL_MIN_WIDTH_PX * hudScale));
|
||||||
const minHeight = Math.max(260, Math.round(340 * hudScale));
|
const minHeight = Math.max(260, Math.round(TV_PANEL_MIN_HEIGHT_PX * hudScale));
|
||||||
const dx = event.clientX - startX;
|
const dx = event.clientX - resizeStart.pointerX;
|
||||||
const dy = event.clientY - startY;
|
const dy = event.clientY - resizeStart.pointerY;
|
||||||
|
|
||||||
if (activeEdge.includes("r")) {
|
if (activeEdge.includes("r")) {
|
||||||
const maxW = containerRect.width - startLeft - 12;
|
const maxW = containerRect.width - resizeStart.left - PANEL_RESIZE_MARGIN_PX;
|
||||||
panel.style.width = `${Math.min(maxW, Math.max(minWidth, startWidth + dx))}px`;
|
panel.style.width = `${Math.min(maxW, Math.max(minWidth, resizeStart.width + dx))}px`;
|
||||||
}
|
}
|
||||||
if (activeEdge.includes("l")) {
|
if (activeEdge.includes("l")) {
|
||||||
const newW = Math.max(minWidth, startWidth - dx);
|
const newW = Math.max(minWidth, resizeStart.width - dx);
|
||||||
panel.style.width = `${newW}px`;
|
panel.style.width = `${newW}px`;
|
||||||
panel.style.left = `${Math.max(0, startLeft + startWidth - newW)}px`;
|
panel.style.left = `${Math.max(0, resizeStart.left + resizeStart.width - newW)}px`;
|
||||||
}
|
}
|
||||||
if (activeEdge.includes("b")) {
|
if (activeEdge.includes("b")) {
|
||||||
const maxH = containerRect.height - startTop - 12;
|
const maxH = containerRect.height - resizeStart.top - PANEL_RESIZE_MARGIN_PX;
|
||||||
panel.style.minHeight = `${Math.min(maxH, Math.max(minHeight, startHeight + dy))}px`;
|
panel.style.height = `${Math.min(maxH, Math.max(minHeight, resizeStart.height + dy))}px`;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -138,17 +184,21 @@ function setupResizeHandle() {
|
|||||||
|
|
||||||
activeEdge = edgeEl.dataset.edge ?? "";
|
activeEdge = edgeEl.dataset.edge ?? "";
|
||||||
resizing = true;
|
resizing = true;
|
||||||
startX = event.clientX;
|
resizeStart.pointerX = event.clientX;
|
||||||
startY = event.clientY;
|
resizeStart.pointerY = event.clientY;
|
||||||
|
|
||||||
clearPanelPositioningForResize(panel);
|
clearPanelPositioningForResize(panel);
|
||||||
|
panel.dataset.resized = "true";
|
||||||
|
|
||||||
const rect = panel.getBoundingClientRect();
|
const rect = panel.getBoundingClientRect();
|
||||||
const cRect = container.getBoundingClientRect();
|
const cRect = container.getBoundingClientRect();
|
||||||
startWidth = rect.width;
|
resizeStart.width = rect.width;
|
||||||
startHeight = rect.height;
|
resizeStart.height = rect.height;
|
||||||
startLeft = rect.left - cRect.left;
|
resizeStart.left = rect.left - cRect.left;
|
||||||
startTop = rect.top - cRect.top;
|
resizeStart.top = rect.top - cRect.top;
|
||||||
|
panel.style.width = `${resizeStart.width}px`;
|
||||||
|
panel.style.height = `${resizeStart.height}px`;
|
||||||
|
panel.style.minHeight = "";
|
||||||
|
|
||||||
panel.classList.add("is-resizing");
|
panel.classList.add("is-resizing");
|
||||||
document.body.style.userSelect = "none";
|
document.body.style.userSelect = "none";
|
||||||
@@ -165,10 +215,11 @@ function setupResizeHandle() {
|
|||||||
function updateToggleButton(visible) {
|
function updateToggleButton(visible) {
|
||||||
const { toggleBtn } = getElements();
|
const { toggleBtn } = getElements();
|
||||||
if (!toggleBtn) return;
|
if (!toggleBtn) return;
|
||||||
toggleBtn.classList.toggle("active", visible);
|
const active = visible && activeTab === "live";
|
||||||
|
toggleBtn.classList.toggle("active", active);
|
||||||
const tooltip = toggleBtn.querySelector(".earth-toolbar-tooltip");
|
const tooltip = toggleBtn.querySelector(".earth-toolbar-tooltip");
|
||||||
if (tooltip) {
|
if (tooltip) {
|
||||||
tooltip.textContent = visible ? "关闭新闻直播" : "打开新闻直播";
|
tooltip.textContent = active ? "关闭新闻直播" : "打开新闻直播";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -185,6 +236,151 @@ function setPanelVisible(visible) {
|
|||||||
tvPanel?.setVisible(visible);
|
tvPanel?.setVisible(visible);
|
||||||
updateToggleButton(visible);
|
updateToggleButton(visible);
|
||||||
syncSettingsToggle(visible);
|
syncSettingsToggle(visible);
|
||||||
|
window.dispatchEvent(new CustomEvent("earth:tv-visibility-change", {
|
||||||
|
detail: { visible },
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setTVPanelVisible(visible) {
|
||||||
|
setPanelVisible(visible);
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearReformState() {
|
||||||
|
const { panel } = getElements();
|
||||||
|
if (!(panel instanceof HTMLElement)) return;
|
||||||
|
panel.classList.remove("is-reforming");
|
||||||
|
panel.style.height = "";
|
||||||
|
if (panel.dataset[REFORM_RESTORE_ANCHOR_DATA_KEY] === "true") {
|
||||||
|
panel.style.top = "";
|
||||||
|
panel.style.bottom = "";
|
||||||
|
delete panel.dataset[REFORM_RESTORE_ANCHOR_DATA_KEY];
|
||||||
|
}
|
||||||
|
if (reformCleanupTimer) {
|
||||||
|
clearTimeout(reformCleanupTimer);
|
||||||
|
reformCleanupTimer = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function animateTabReform(applyChange) {
|
||||||
|
const { panel } = getElements();
|
||||||
|
const container = document.getElementById("container");
|
||||||
|
if (!(panel instanceof HTMLElement)) {
|
||||||
|
applyChange();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!(container instanceof HTMLElement)) {
|
||||||
|
applyChange();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (panel.classList.contains("is-dragging") || panel.classList.contains("is-resizing")) {
|
||||||
|
applyChange();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
clearReformState();
|
||||||
|
|
||||||
|
const reformStartRect = panel.getBoundingClientRect();
|
||||||
|
const containerRect = container.getBoundingClientRect();
|
||||||
|
const reformSnapshot = {
|
||||||
|
height: reformStartRect.height,
|
||||||
|
anchoredBottom: reformStartRect.bottom - containerRect.top,
|
||||||
|
shouldRestoreDefaultAnchoring: panel.dataset.dragged !== "true",
|
||||||
|
};
|
||||||
|
|
||||||
|
if (reformSnapshot.shouldRestoreDefaultAnchoring) {
|
||||||
|
panel.dataset[REFORM_RESTORE_ANCHOR_DATA_KEY] = "true";
|
||||||
|
panel.style.bottom = "auto";
|
||||||
|
}
|
||||||
|
|
||||||
|
panel.style.top = `${reformSnapshot.anchoredBottom - reformSnapshot.height}px`;
|
||||||
|
panel.style.height = `${reformSnapshot.height}px`;
|
||||||
|
panel.classList.add("is-reforming");
|
||||||
|
void panel.offsetHeight;
|
||||||
|
|
||||||
|
applyChange();
|
||||||
|
|
||||||
|
panel.style.height = "auto";
|
||||||
|
const targetHeight = panel.getBoundingClientRect().height;
|
||||||
|
panel.style.height = `${reformSnapshot.height}px`;
|
||||||
|
void panel.offsetHeight;
|
||||||
|
const targetTop = reformSnapshot.anchoredBottom - targetHeight;
|
||||||
|
|
||||||
|
const finalizeReform = () => {
|
||||||
|
panel.removeEventListener("transitionend", handleReformTransitionEnd);
|
||||||
|
clearReformState();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleReformTransitionEnd = (event) => {
|
||||||
|
if (event.target === panel && event.propertyName === "height") {
|
||||||
|
finalizeReform();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
panel.addEventListener("transitionend", handleReformTransitionEnd);
|
||||||
|
reformCleanupTimer = window.setTimeout(finalizeReform, REFORM_CLEANUP_MS);
|
||||||
|
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
panel.style.top = `${targetTop}px`;
|
||||||
|
panel.style.height = `${targetHeight}px`;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateTabState(target, isActive, activeClassName = "") {
|
||||||
|
if (!(target instanceof HTMLElement)) return;
|
||||||
|
target.hidden = !isActive;
|
||||||
|
if (activeClassName) {
|
||||||
|
target.classList.toggle(activeClassName, isActive);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateTabButtonState(button, isActive) {
|
||||||
|
if (!(button instanceof HTMLButtonElement)) return;
|
||||||
|
button.classList.toggle("tv-panel-tab--active", isActive);
|
||||||
|
button.setAttribute("aria-selected", isActive ? "true" : "false");
|
||||||
|
}
|
||||||
|
|
||||||
|
function setActiveTab(tab) {
|
||||||
|
const nextTab = tab === "news" ? "news" : "live";
|
||||||
|
if (activeTab === nextTab) return;
|
||||||
|
|
||||||
|
animateTabReform(() => {
|
||||||
|
activeTab = nextTab;
|
||||||
|
syncPanelActiveTab(nextTab);
|
||||||
|
syncNewsDefaultMaxHeight();
|
||||||
|
const {
|
||||||
|
liveTabBtn,
|
||||||
|
newsTabBtn,
|
||||||
|
liveHeaderControls,
|
||||||
|
newsHeaderControls,
|
||||||
|
livePane,
|
||||||
|
newsPane,
|
||||||
|
} = getElements();
|
||||||
|
|
||||||
|
updateTabButtonState(liveTabBtn, nextTab === "live");
|
||||||
|
updateTabButtonState(newsTabBtn, nextTab === "news");
|
||||||
|
updateTabState(liveHeaderControls, nextTab === "live");
|
||||||
|
updateTabState(newsHeaderControls, nextTab === "news");
|
||||||
|
updateTabState(livePane, nextTab === "live", "tv-tab-pane--active");
|
||||||
|
updateTabState(newsPane, nextTab === "news", "tv-tab-pane--active");
|
||||||
|
window.dispatchEvent(new CustomEvent("earth:tv-tab-change", {
|
||||||
|
detail: { tab: nextTab },
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function openTVPanelTab(tab = "live") {
|
||||||
|
setPanelVisible(true);
|
||||||
|
if (activeTab === tab) return;
|
||||||
|
setActiveTab(tab);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isTVPanelVisible() {
|
||||||
|
return tvPanel?.isVisible() ?? !getElements().panel?.classList.contains("hud-panel-hidden");
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getActiveTVTab() {
|
||||||
|
return activeTab;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getEmbeddedUrl(source) {
|
function getEmbeddedUrl(source) {
|
||||||
@@ -652,7 +848,17 @@ export function initTVPanel() {
|
|||||||
if (initialized) return;
|
if (initialized) return;
|
||||||
initialized = true;
|
initialized = true;
|
||||||
|
|
||||||
const { select, refreshBtn, iframe, video, toggleBtn, panel, metaToggle } = getElements();
|
const {
|
||||||
|
select,
|
||||||
|
refreshBtn,
|
||||||
|
iframe,
|
||||||
|
video,
|
||||||
|
toggleBtn,
|
||||||
|
panel,
|
||||||
|
metaToggle,
|
||||||
|
liveTabBtn,
|
||||||
|
newsTabBtn,
|
||||||
|
} = getElements();
|
||||||
|
|
||||||
if (panel && metaToggle) {
|
if (panel && metaToggle) {
|
||||||
tvPanel = createHUDPanel({
|
tvPanel = createHUDPanel({
|
||||||
@@ -673,14 +879,23 @@ export function initTVPanel() {
|
|||||||
toggleBtn?.addEventListener("click", async (event) => {
|
toggleBtn?.addEventListener("click", async (event) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
const nextVisible = !(tvPanel?.isVisible() ?? false);
|
const currentlyVisible = tvPanel?.isVisible() ?? false;
|
||||||
setPanelVisible(nextVisible);
|
if (!currentlyVisible) {
|
||||||
if (nextVisible) {
|
setPanelVisible(true);
|
||||||
|
setActiveTab("live");
|
||||||
await ensureTVPanelReady();
|
await ensureTVPanelReady();
|
||||||
showStatusMessage("新闻直播窗口已打开", "info");
|
showStatusMessage("新闻直播窗口已打开", "info");
|
||||||
} else {
|
return;
|
||||||
showStatusMessage("新闻直播窗口已关闭", "info");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (activeTab !== "live") {
|
||||||
|
setActiveTab("live");
|
||||||
|
showStatusMessage("已切换到新闻直播", "info");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setPanelVisible(false);
|
||||||
|
showStatusMessage("新闻直播窗口已关闭", "info");
|
||||||
});
|
});
|
||||||
|
|
||||||
select?.addEventListener("change", (event) => {
|
select?.addEventListener("change", (event) => {
|
||||||
@@ -700,6 +915,13 @@ export function initTVPanel() {
|
|||||||
refreshTVPanel();
|
refreshTVPanel();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
liveTabBtn?.addEventListener("click", () => {
|
||||||
|
setActiveTab("live");
|
||||||
|
});
|
||||||
|
newsTabBtn?.addEventListener("click", () => {
|
||||||
|
setActiveTab("news");
|
||||||
|
});
|
||||||
|
|
||||||
iframe?.addEventListener("load", () => {
|
iframe?.addEventListener("load", () => {
|
||||||
if (iframe.hidden) return;
|
if (iframe.hidden) return;
|
||||||
clearSourceFailed(currentSourceId);
|
clearSourceFailed(currentSourceId);
|
||||||
@@ -720,4 +942,9 @@ export function initTVPanel() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
setupResizeHandle();
|
setupResizeHandle();
|
||||||
|
syncPanelActiveTab("live");
|
||||||
|
syncNewsDefaultMaxHeight();
|
||||||
|
setActiveTab("live");
|
||||||
|
|
||||||
|
window.addEventListener("resize", syncNewsDefaultMaxHeight);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[project]
|
[project]
|
||||||
name = "planet"
|
name = "planet"
|
||||||
version = "0.27.8"
|
version = "0.28.0"
|
||||||
description = "智能星球计划 - 态势感知系统"
|
description = "智能星球计划 - 态势感知系统"
|
||||||
requires-python = ">=3.14"
|
requires-python = ">=3.14"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
|||||||
Reference in New Issue
Block a user