// Connection banner (global chrome, mounted once by main.js): reconnecting / closed / rejected-hello states with // the matching action, and "正在同步同盟状态…" while a resumed session waits for its room/match state. Outside a // match it sits at the bottom centre; in a match (html.sp-in-match, set by the match screen) it moves under the top bar // so it never covers the combat view switcher or the shop bar. While it shows, html.sp-conn moves the toasts below it // (classes instead of CSS :has(), which Firefox ESR / Safari < 15.4 lack). import { html, Button, Icon, useTicker } from './components.js'; import { net, CLIENT_ERR_TEXT } from '../net.js'; import { useStore, shallowEqual } from '../store.js'; import { useDocClass } from './device.js'; /** Whether the banner shows for this connection state (mirrors the early returns below). */ export function bannerVisible(conn, entered, restoring) { if (!entered || !conn) return false; if (conn.status === 'online') return !!restoring; if (!conn.everOnline && (conn.status === 'connecting' || conn.status === 'handshaking' || conn.status === 'idle')) return false; return true; } export function ConnectionBanner() { const conn = useStore((s) => s.connection, shallowEqual); const entered = useStore((s) => s.session.entered); const restoring = useStore((s) => s.ui.restoring); useTicker(conn.status === 'reconnecting' ? 500 : 0); useDocClass('sp-conn', bannerVisible(conn, entered, restoring)); if (!entered) return null; if (conn.status === 'online' && !restoring) return null; if (conn.status === 'online' && restoring) { return html`
<${Icon} name="refresh" />正在同步同盟状态…
`; } if (!conn.everOnline && (conn.status === 'connecting' || conn.status === 'handshaking' || conn.status === 'idle')) return null; const secs = conn.retryAt ? Math.max(0, Math.ceil((conn.retryAt - Date.now()) / 1000)) : 0; const replaced = conn.status === 'closed' && conn.lastError?.code === 'REPLACED'; const rejected = conn.status === 'connected' && !!conn.lastError; // hello refused (version, server full…) const versionMismatch = rejected && conn.lastError.text === CLIENT_ERR_TEXT.VERSION; // Short transitional states (a rename re-sends hello on the live socket) only show if they linger. const transient = conn.status === 'connecting' || conn.status === 'handshaking' || (conn.status === 'connected' && !rejected); const text = conn.status === 'reconnecting' ? '与服务器的连接已中断,正在重连' : replaced ? '该身份已在其他页面登录' : conn.status === 'closed' ? '连接已关闭' : rejected ? conn.lastError.text : '正在连接服务器'; const action = conn.status === 'reconnecting' ? { label: '立即重连', run: () => net.retryNow() } : conn.status === 'closed' ? { label: replaced ? '在此页面继续' : '重新连接', run: () => net.connect() } : versionMismatch ? { label: '刷新页面', run: () => location.reload() } : rejected ? { label: '重试', run: () => net.reconnectNow() } : null; return html``; }