feedback3 WE: spectator seats in the browser — 观战 in the lobby, the 观战席 strip in the room (host removes, 入座), watch-only match screen; docs (community report #26)
This commit is contained in:
1 parent
42766172d1
commit
976475efff
21 files changed
+352
-30
No files matched your search
@@ -34,8 +34,9 @@ function ProgressList({ list }) {
|
||||
* client?: null | { progress?: any[]|null, observing?: { name: string }|null, onBack?: () => void,
|
||||
* layers?: Array<{ key: string, label: string, self: boolean, watch: boolean }>, layer?: string, onLayer?: (k: string) => void } }} props
|
||||
*/
|
||||
export function CombatHud({ pub, myId, watching, hud, myDone, onWatch, spectating = false, client = null }) {
|
||||
if (client) return ClientHud({ hud, myDone, spectating, client });
|
||||
export function CombatHud({ pub, myId, watching, hud, myDone, onWatch, spectating = false, spectator = false, client = null }) {
|
||||
// `spectator`: a spectator seat (community report #26) watches like an eliminated player, under its own caption
|
||||
if (client) return ClientHud({ hud, myDone, spectating, spectator, client });
|
||||
const fields = (Array.isArray(pub?.fields) ? pub.fields : []).filter((f) => f && f.live !== false);
|
||||
// the watched field may already have finished (not live): still name it
|
||||
const label = switcherLabel(pub, watching, myId, spectating);
|
||||
@@ -44,7 +45,8 @@ export function CombatHud({ pub, myId, watching, hud, myDone, onWatch, spectatin
|
||||
<${DpCounter} dp=${hud?.dp} />
|
||||
<div class="chud__bottom">
|
||||
${myDone ? html`<div class="chud__msg" role="status"><${Icon} name="hourglass" /><span>作战结束,等待队友完成作战</span></div>` : null}
|
||||
${spectating ? html`<div class="chud__msg chud__msg--dead" role="status"><${Icon} name="close" /><span>你已被淘汰,正在观战</span></div>` : null}
|
||||
${spectating ? (spectator ? html`<div class="chud__msg" role="status"><${GIcon} name="eye" /><span>观战中</span></div>`
|
||||
: html`<div class="chud__msg chud__msg--dead" role="status"><${Icon} name="close" /><span>你已被淘汰,正在观战</span></div>`) : null}
|
||||
${fields.length ? html`<div class=${cx('vswitch', !canCycle && 'is-single')}>
|
||||
<button type="button" class="vswitch__arrow" disabled=${!canCycle} aria-label="上一个战场"
|
||||
onClick=${() => { const n = cycleField(fields, watching, -1); if (n && n !== watching) onWatch(n); }}><${Icon} name="chevronLeft" /></button>
|
||||
@@ -56,7 +58,7 @@ export function CombatHud({ pub, myId, watching, hud, myDone, onWatch, spectatin
|
||||
</div>`;
|
||||
}
|
||||
|
||||
function ClientHud({ hud, myDone, spectating, client }) {
|
||||
function ClientHud({ hud, myDone, spectating, spectator = false, client }) {
|
||||
const layers = Array.isArray(client.layers) ? client.layers : [];
|
||||
const idx = Math.max(0, layers.findIndex((l) => l.key === (client.layer || 'ALL')));
|
||||
const cur = layers[idx] || null;
|
||||
@@ -72,7 +74,8 @@ function ClientHud({ hud, myDone, spectating, client }) {
|
||||
<div class="chud__bottom">
|
||||
${myDone && !observing ? html`<div class="chud__msg chud__wait" role="status"><${Icon} name="hourglass" /><span>作战结束,等待队友完成作战</span></div>
|
||||
<${ProgressList} list=${client.progress} />` : null}
|
||||
${spectating && !observing ? html`<div class="chud__msg chud__msg--dead" role="status"><${Icon} name="close" /><span>你已被淘汰,可点击队友头像前往查看</span></div>` : null}
|
||||
${spectating && !observing ? (spectator ? html`<div class="chud__msg" role="status"><${GIcon} name="eye" /><span>观战中,可点击左侧成员头像前往查看</span></div>`
|
||||
: html`<div class="chud__msg chud__msg--dead" role="status"><${Icon} name="close" /><span>你已被淘汰,可点击队友头像前往查看</span></div>`) : null}
|
||||
${observing && !halves ? html`<div class="vswitch chud__observe is-single" role="status">
|
||||
<span class="vswitch__label"><${GIcon} name="eye" /><span>${observing.name}</span></span>
|
||||
${client.onBack ? html`<button type="button" class="btn btn--secondary btn--sm chud__back" onClick=${() => client.onBack()}>
|
||||
|
||||
@@ -74,6 +74,8 @@ export const ICONS = {
|
||||
collapse: { d: 'M8 3h2v7H3V8h5zm6 0h2v5h5v2h-7zM3 14h7v7H8v-5H3zm11 0h7v2h-5v5h-2z' },
|
||||
rotate: { d: 'M7 2h8a2 2 0 0 1 2 2v7h-2V4H7v16h4v2H7a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2zm8 12h5a2 2 0 0 1 2 2v4a2 2 0 0 1-2 2h-5a2 2 0 0 1-2-2v-4a2 2 0 0 1 2-2zm0 2v4h5v-4z', eo: true },
|
||||
book: { d: 'M2 4h7.5A3.5 3.5 0 0 1 12 5.1 3.5 3.5 0 0 1 14.5 4H22v16h-7.5a1.5 1.5 0 0 0-1.5 1.5h-2A1.5 1.5 0 0 0 9.5 20H2zm2 2v12h5.5c.5 0 1 .1 1.5.3V7.5A1.5 1.5 0 0 0 9.5 6zm10.5 0A1.5 1.5 0 0 0 13 7.5v10.8c.5-.2 1-.3 1.5-.3H20V6z', eo: true },
|
||||
// 观战 (spectator seats; the same eye as gameComponents.js GIcon 'eye')
|
||||
eye: { d: 'M12 5c5 0 9 4.5 10 7-1 2.5-5 7-10 7S3 14.5 2 12c1-2.5 5-7 10-7zm0 3a4 4 0 1 0 0 8 4 4 0 0 0 0-8zm0 2a2 2 0 1 1 0 4 2 2 0 0 1 0-4z', eo: true },
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
+3
-2
@@ -308,7 +308,7 @@ export function PauseButton({ paused, busy = false, onToggle }) {
|
||||
* `left` (a leaker in 联防): its enemies still standing — the capsule's ×N tag
|
||||
*/
|
||||
export function TopBar({ pub, priv, conn, hud, total, drawer, onExit, onDrawer, onReady, readyBusy, readyCount, playerCount, pen = false, penAvail = false, onPen = () => {},
|
||||
config = null, frozenAt = null, pause = null, live = null }) {
|
||||
config = null, frozenAt = null, pause = null, live = null, spectator = false }) {
|
||||
const phase = pub?.phase;
|
||||
const boss = isBossPhase(phase);
|
||||
const lp = boss && Number.isFinite(pub?.teamLp) ? pub.teamLp : Number.isFinite(priv?.lp) ? priv.lp : null;
|
||||
@@ -316,7 +316,8 @@ export function TopBar({ pub, priv, conn, hud, total, drawer, onExit, onDrawer,
|
||||
const pending = !boss && Number.isFinite(lp) && live && live.pending > 0 ? Math.min(lp, live.pending) : 0;
|
||||
const hidden = phase === PHASE.HIDDEN_CORE || (Number.isFinite(pub?.lastRound) && pub.round > pub.lastRound);
|
||||
const roundText = hidden ? '??' : pub?.round > 0 ? String(pub.round) : '--';
|
||||
const showReady = phase === PHASE.PREP && priv?.alive !== false;
|
||||
// a spectator seat (no m.private, community report #26) never readies
|
||||
const showReady = phase === PHASE.PREP && !spectator && priv?.alive !== false;
|
||||
// boss rounds: the overtime warning follows the clock (4 Hz while live; frozen while paused)
|
||||
const otLive = boss && Number(pub?.overtimeAt) > 0;
|
||||
useTicker(otLive && frozenAt == null ? 250 : 0);
|
||||
|
||||
@@ -9,7 +9,7 @@ import { html, Button, Modal, PingPill, DifficultyTag, Countdown, MicroLabel } f
|
||||
import { actions } from './gameActions.js';
|
||||
import { toastError } from './toasts.js';
|
||||
import { net } from '../net.js';
|
||||
import { store, useStore, shallowEqual, emptyMatch, createStore } from '../store.js';
|
||||
import { store, useStore, shallowEqual, emptyMatch, createStore, isSpectating } from '../store.js';
|
||||
import { GIcon } from './gameComponents.js';
|
||||
import { GuideButton } from './guide.js';
|
||||
|
||||
@@ -58,12 +58,21 @@ export async function quitMatch() {
|
||||
*/
|
||||
export function ExitModal({ open, onClose, solo, onAway }) {
|
||||
const [busy, setBusy] = useState(null);
|
||||
// a spectator seat (community report #26) only leaves its seat: nothing of the match is given up
|
||||
const spectator = useStore((s) => isSpectating(s.room, s.me.playerId));
|
||||
const quit = async () => {
|
||||
setBusy('quit');
|
||||
await quitMatch();
|
||||
setBusy(null);
|
||||
onClose();
|
||||
};
|
||||
if (spectator) {
|
||||
return html`<${Modal} open=${open} onClose=${onClose} tone="red" title="离开观战" micro="LEAVE SPECTATING" width="6.8rem"
|
||||
actions=${html`<${Button} variant="secondary" onClick=${onClose}>取消<//>
|
||||
<${Button} variant="danger" icon="exit" loading=${busy === 'quit'} onClick=${quit}>离开观战<//>`}>
|
||||
<div class="exitm"><p>离开观战席并返回大厅,本局模拟不受影响;观战席空着时可以凭同盟密钥再次观战。</p></div>
|
||||
<//>`;
|
||||
}
|
||||
const away = async () => {
|
||||
setBusy('away');
|
||||
const ok = await actions.autoplay(true);
|
||||
|
||||
Reference in new issue
Block a user