release: bump version to 0.27.3

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
linkong
2026-04-14 12:52:05 +08:00
parent 48eb13b993
commit 93c1c1e550
12 changed files with 346 additions and 143 deletions

View File

@@ -174,7 +174,9 @@ function setupDraggableHudPanels() {
if (!app || draggablePanels.length === 0) return;
draggablePanels.forEach((panel) => {
const handle = panel.querySelector(".hud-panel-drag-handle");
const handle = panel.dataset.dragSelf === "true"
? panel
: panel.querySelector(".hud-panel-drag-handle");
if (!handle) return;
let isDragging = false;
@@ -228,7 +230,7 @@ function setupDraggableHudPanels() {
};
bindListener(handle, "pointerdown", (event) => {
if (event.target.closest(".hud-panel-close, .layer-panel-btn, .info-card-close")) return;
if (event.target.closest(".hud-panel-close, .layer-panel-btn, .info-card-close, .tv-panel-select, .tv-panel-action, .tv-panel-player, .tv-panel-edge, .legend-bar-btn")) return;
isDragging = true;
startPointerX = event.clientX;
startPointerY = event.clientY;
@@ -238,6 +240,8 @@ function setupDraggableHudPanels() {
// 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) {
panel.dataset.originalParentId = panel.parentElement?.id || "";
panel.dataset.originalNextSiblingId = panel.nextElementSibling?.id || "";
const capturedWidth = panelRect.width;
panel.style.position = "absolute";
panel.style.width = `${capturedWidth}px`;
@@ -929,11 +933,28 @@ function updateLayoutUI(container) {
}
function resetPanelInlineLayout(panel) {
const originalParentId = panel.dataset.originalParentId;
if (originalParentId) {
const originalParent = document.getElementById(originalParentId);
if (originalParent) {
const nextId = panel.dataset.originalNextSiblingId;
const nextSibling = nextId ? document.getElementById(nextId) : null;
if (nextSibling) {
originalParent.insertBefore(panel, nextSibling);
} else {
originalParent.appendChild(panel);
}
}
delete panel.dataset.originalParentId;
delete panel.dataset.originalNextSiblingId;
}
panel.style.left = "";
panel.style.top = "";
panel.style.right = "";
panel.style.bottom = "";
panel.style.transform = "";
panel.style.position = "";
panel.style.width = "";
delete panel.dataset.dragged;
}

View File

@@ -20,6 +20,9 @@ let initialized = false;
let refreshPromise = null;
let hlsPlayer = null;
let hlsRecoveryAttempts = 0;
let metaAutoCollapseTimer = null;
const META_AUTO_COLLAPSE_DELAY = 2500;
const HLS_MAX_RECOVERY_ATTEMPTS = 3;
const HLS_RETRY_CONFIG = {
@@ -33,7 +36,7 @@ function getElements() {
return {
panel: document.getElementById("tv-panel"),
toggleBtn: document.getElementById("toggle-tv"),
resizeHandle: document.getElementById("tv-resize-handle"),
resizeHandle: null,
select: document.getElementById("tv-source-select"),
title: document.getElementById("tv-source-title"),
meta: document.getElementById("tv-source-meta"),
@@ -45,9 +48,51 @@ function getElements() {
empty: document.getElementById("tv-empty-state"),
refreshBtn: document.getElementById("tv-refresh"),
openBtn: document.getElementById("tv-open-external"),
metaPanel: document.getElementById("tv-panel-meta"),
metaWrap: document.getElementById("tv-meta-wrap"),
metaToggle: document.getElementById("tv-meta-toggle"),
};
}
function setMetaCollapsed(collapsed) {
const { metaWrap, metaToggle, panel } = getElements();
if (!metaWrap) return;
const isDragged = panel?.dataset.dragged === "true" && panel.style.top;
if (isDragged) {
// Top-anchored panel: toggle instantly and compensate top so the player
// (panel bottom) stays visually fixed.
const bottomBefore = panel.getBoundingClientRect().bottom;
metaWrap.style.transition = "none";
metaWrap.classList.toggle("is-collapsed", collapsed);
metaToggle?.classList.toggle("is-collapsed", collapsed);
// Force synchronous reflow to get updated panel height
void panel.offsetHeight;
const delta = panel.getBoundingClientRect().bottom - bottomBefore;
if (delta !== 0) {
panel.style.top = `${parseFloat(panel.style.top) - delta}px`;
}
// Restore CSS transition after this paint
requestAnimationFrame(() => {
metaWrap.style.transition = "";
});
} else {
metaWrap.classList.toggle("is-collapsed", collapsed);
metaToggle?.classList.toggle("is-collapsed", collapsed);
}
}
function autoExpandMeta() {
clearTimeout(metaAutoCollapseTimer);
setMetaCollapsed(false);
metaAutoCollapseTimer = setTimeout(() => setMetaCollapsed(true), META_AUTO_COLLAPSE_DELAY);
}
function clearPanelPositioningForResize(panel) {
panel.style.left = `${panel.offsetLeft}px`;
panel.style.top = `${panel.offsetTop}px`;
@@ -65,71 +110,80 @@ function getHudScale() {
}
function setupResizeHandle() {
const { panel, resizeHandle } = getElements();
const { panel } = getElements();
const container = document.getElementById("container");
if (!(panel instanceof HTMLElement) || !(resizeHandle instanceof HTMLElement) || !(container instanceof HTMLElement)) {
return;
}
if (!(panel instanceof HTMLElement) || !(container instanceof HTMLElement)) return;
let resizing = false;
let activeEdge = "";
let startX = 0;
let startY = 0;
let startWidth = 0;
let startHeight = 0;
let startLeft = 0;
let startTop = 0;
const stopResize = () => {
resizing = false;
activeEdge = "";
panel.classList.remove("is-resizing");
document.body.style.userSelect = "";
};
resizeHandle.addEventListener("pointerdown", (event) => {
if (document.getElementById("container")?.classList.contains("layout-expanded")) {
return;
}
event.preventDefault();
event.stopPropagation();
resizing = true;
startX = event.clientX;
startY = event.clientY;
clearPanelPositioningForResize(panel);
const rect = panel.getBoundingClientRect();
startWidth = rect.width;
startHeight = rect.height;
panel.classList.add("is-resizing");
document.body.style.userSelect = "none";
resizeHandle.setPointerCapture?.(event.pointerId);
});
resizeHandle.addEventListener("pointermove", (event) => {
const onMove = (event) => {
if (!resizing) return;
const containerRect = container.getBoundingClientRect();
const panelRect = panel.getBoundingClientRect();
const currentLeft = panelRect.left - containerRect.left;
const currentTop = panelRect.top - containerRect.top;
const hudScale = getHudScale();
const minWidth = Math.max(320, Math.round(360 * hudScale));
const minHeight = Math.max(260, Math.round(340 * hudScale));
const maxWidth = Math.max(minWidth, containerRect.width - currentLeft - 12);
const maxHeight = Math.max(minHeight, containerRect.height - currentTop - 12);
const nextWidth = Math.min(
maxWidth,
Math.max(minWidth, startWidth + (event.clientX - startX)),
);
const nextHeight = Math.min(
maxHeight,
Math.max(minHeight, startHeight + (event.clientY - startY)),
);
const dx = event.clientX - startX;
const dy = event.clientY - startY;
panel.style.width = `${nextWidth}px`;
panel.style.minHeight = `${nextHeight}px`;
if (activeEdge.includes("r")) {
const maxW = containerRect.width - startLeft - 12;
panel.style.width = `${Math.min(maxW, Math.max(minWidth, startWidth + dx))}px`;
}
if (activeEdge.includes("l")) {
const newW = Math.max(minWidth, startWidth - dx);
panel.style.width = `${newW}px`;
panel.style.left = `${Math.max(0, startLeft + startWidth - newW)}px`;
}
if (activeEdge.includes("b")) {
const maxH = containerRect.height - startTop - 12;
panel.style.minHeight = `${Math.min(maxH, Math.max(minHeight, startHeight + dy))}px`;
}
};
panel.querySelectorAll(".tv-panel-edge[data-edge]").forEach((edgeEl) => {
edgeEl.addEventListener("pointerdown", (event) => {
if (container.classList.contains("layout-expanded")) return;
event.preventDefault();
event.stopPropagation();
activeEdge = edgeEl.dataset.edge ?? "";
resizing = true;
startX = event.clientX;
startY = event.clientY;
clearPanelPositioningForResize(panel);
const rect = panel.getBoundingClientRect();
const cRect = container.getBoundingClientRect();
startWidth = rect.width;
startHeight = rect.height;
startLeft = rect.left - cRect.left;
startTop = rect.top - cRect.top;
panel.classList.add("is-resizing");
document.body.style.userSelect = "none";
edgeEl.setPointerCapture?.(event.pointerId);
});
edgeEl.addEventListener("pointermove", onMove);
edgeEl.addEventListener("pointerup", stopResize);
edgeEl.addEventListener("pointercancel", stopResize);
edgeEl.addEventListener("lostpointercapture", stopResize);
});
resizeHandle.addEventListener("pointerup", stopResize);
resizeHandle.addEventListener("pointercancel", stopResize);
resizeHandle.addEventListener("lostpointercapture", stopResize);
}
function updateToggleButton(visible) {
@@ -502,6 +556,7 @@ function renderSource(source) {
source.id === tvPayload?.default_source_id ? "当前正在播放默认源" : "当前正在播放已选频道",
);
updateOpenButton(source);
autoExpandMeta();
}
function resolveInitialSourceId() {
@@ -592,6 +647,13 @@ export function initTVPanel() {
renderSource(findSourceById(currentSourceId));
});
const { metaToggle } = getElements();
metaToggle?.addEventListener("click", () => {
clearTimeout(metaAutoCollapseTimer);
const isNowCollapsed = !metaToggle.classList.contains("is-collapsed");
setMetaCollapsed(isNowCollapsed);
});
refreshBtn?.addEventListener("click", () => {
refreshTVPanel();
});