m.private.diyBanned (every bond of the operator off this match) is shown under 本局禁用干员 in the INFO_CHECK briefing, the strategy draft's 本局信息 dialog and the in-game 本局信息 tab: ui/gameLogic/diy.js diyBannedPieces, ui/matchInfo.js matchInfoModel diyBanned + DiyBannedLine (t() with English in public/i18n/en.json). Client only; golden unchanged.
332 lines
21 KiB
JavaScript
332 lines
21 KiB
JavaScript
// Band draft — BAND_DRAFT "2/2 选择策略" (research 06 §4.2, D1): left = draft order (avatar, name, state:
|
||
// … waiting / ⌛ 决策中 / chosen band ✓), current picker highlighted; centre = grid of every band allowed
|
||
// for the mode type (icon, name, LP); a band a teammate already picked carries the picker's avatar and is marked
|
||
// 队友已选 — it cannot be chosen again (research 09 §5, guidebook 策略与轮选; the server refuses it too); right =
|
||
// detail pane (icon, 初始生命值, name, effect name + rich description) with 跳过 (co-op, once) and 确认选择.
|
||
// One countdown (user playtest #4 item 4): every turn has the same clock (Match BAND_TURN_SECONDS, m.public.draft
|
||
// turnSeconds) and the step header counts it down — m.public.deadline IS the turn's end, the same number as the
|
||
// current picker's row. The highlighted band (the detail pane's) is what a turn that runs out takes: every change of it
|
||
// is reported (g.bandFocus) and the server assigns it while it is free, else 「华法琳」, else the first free strategy
|
||
// (timeoutBand). It starts on that default, so the tip under the order list always names what a timeout gives.
|
||
// Solo, and a co-op match with a single human (the server's soloUntimed: draft.untimed): no clock at all.
|
||
// A strategy built around a bond the mode switches off (bands.json bondIds ∩ the mode's inactive bonds — 标准: 潘格尼尼
|
||
// 拉特兰, 克莱门莎 阿戈尔, 玛恩纳 卡西米尔; the bot never picks one) reads 本局禁用 on its card and in the detail pane
|
||
// (BandOffTag / BandOffNote, DESIGN §21.26, §21.7's look); it stays selectable — information only.
|
||
// 本局信息 (GitHub issue #8 item 1, "选策略时没法返回查看禁用的干员和盟约"): 查看禁用盟约与干员 under the order list opens the
|
||
// briefing's bond rows, legend and 本局禁用干员 again, read-only (ui/matchInfo.js MatchInfoDialog — the very blocks of the
|
||
// briefing). The draft runs on underneath: its status line repeats the current turn and the countdown (draftInfoStatus),
|
||
// a turn change (a pick, a skip, a turn that runs out, an AI pick) closes it, the end of the draft unmounts it, and it
|
||
// never touches the highlighted band or the buttons.
|
||
|
||
import { useEffect, useMemo, useRef, useState } from '../../vendor/hooks.module.js';
|
||
import { html, Button, Icon, MicroLabel, useTicker, secondsLeft } from '../ui/components.js';
|
||
import { useGameData, BandIcon, RichText, PlayerAvatar, LpTower, Sprite } from '../ui/gameComponents.js';
|
||
import { StepHeader, ExitModal } from '../ui/matchChrome.js';
|
||
import { MatchInfoDialog, matchInfoModel } from '../ui/matchInfo.js';
|
||
import { actions, act } from '../ui/gameActions.js';
|
||
import { normalizeDraft, sortedPlayers } from '../ui/gameLogic.js';
|
||
import { useStore } from '../store.js';
|
||
import { data } from '../data.js';
|
||
import { audio } from '../audio.js';
|
||
import { modeOffBonds, bandOffBonds, bandOffLine } from '../ui/gameLogic.js';
|
||
|
||
const cx = (...p) => p.flat().filter(Boolean).join(' ');
|
||
|
||
/** 本局禁用 on a strategy card whose bonds `names` the mode switches off (bandOffBonds); nothing otherwise. */
|
||
export function BandOffTag({ names = [] }) {
|
||
return names.length ? html`<span class="dband__off" title=${bandOffLine(names)}>本局禁用</span>` : null;
|
||
}
|
||
|
||
/** The detail pane's note for such a strategy: "本局禁用【拉特兰】盟约,此策略效果可能无法发挥" (the bond names struck through). */
|
||
export function BandOffNote({ names = [] }) {
|
||
if (!names.length) return null;
|
||
return html`<p class="draft-detail__off" role="note" aria-label=${bandOffLine(names)}><${Icon} name="info" /><span>本局禁用${names.map((n, i) => html`<span key=${i}>【<s class="draft-detail__offname">${n}</s>】</span>`)}盟约,此策略效果可能无法发挥</span></p>`;
|
||
}
|
||
|
||
/**
|
||
* Bands selectable in a mode (modeTypeList contains the mode's type), sorted by sortId.
|
||
* @param {any[]} bands
|
||
* @param {string|null} modeType 'SINGLE'|'MULTI'
|
||
*/
|
||
export function allowedBands(bands, modeType) {
|
||
const sid = (b) => (Number.isFinite(b.sortId) ? b.sortId : 99);
|
||
return (Array.isArray(bands) ? bands : [])
|
||
.filter((b) => b && (!modeType || !Array.isArray(b.modeTypeList) || b.modeTypeList.includes(modeType)))
|
||
.sort((a, b) => sid(a) - sid(b) || (a.bandId < b.bandId ? -1 : a.bandId > b.bandId ? 1 : 0));
|
||
}
|
||
|
||
/** The official default strategy of an automatic assignment (data/config.json bandDraft.timeoutBandId). */
|
||
export const DEFAULT_TIMEOUT_BAND = 'band_bldsk';
|
||
|
||
/**
|
||
* The strategy the server assigns me when my turn times out (server/match/match/phases.js defaultBand): the official default
|
||
* 「华法琳」 while no teammate holds it, else the first free strategy in draft order (sortId) — never one a teammate
|
||
* already picked (队友已选).
|
||
* @param {any[]} bands allowedBands(...) (sortId order)
|
||
* @param {Map<string, any>} taken teammateBands(...)
|
||
* @param {string} [defaultId]
|
||
* @returns {string|null}
|
||
*/
|
||
export function timeoutBand(bands, taken, defaultId = DEFAULT_TIMEOUT_BAND) {
|
||
const list = Array.isArray(bands) ? bands : [];
|
||
const has = (id) => !!(taken && typeof taken.has === 'function' && taken.has(id));
|
||
if (defaultId && !has(defaultId) && (!list.length || list.some((b) => b.bandId === defaultId))) return defaultId;
|
||
return list.find((b) => !has(b.bandId))?.bandId || defaultId || null;
|
||
}
|
||
|
||
/**
|
||
* Bands taken by teammates (队友已选): bandId → the picking players (never the viewer).
|
||
* @param {Map<string, string>} picks normalizeDraft(...).picks (playerId → bandId)
|
||
* @param {string} myId
|
||
*/
|
||
export function teammateBands(picks, myId) {
|
||
const out = new Map();
|
||
for (const [pid, bid] of picks instanceof Map ? picks : []) {
|
||
if (pid === myId || typeof bid !== 'string') continue;
|
||
if (!out.has(bid)) out.set(bid, []);
|
||
out.get(bid).push(pid);
|
||
}
|
||
return out;
|
||
}
|
||
|
||
/**
|
||
* The band the detail pane shows (= the highlighted band a turn that runs out takes): the current one, else my pick,
|
||
* else the band a timeout would give me (timeoutBand: 「华法琳」 while free, else the first free one). When it is my
|
||
* turn and the shown band was taken meanwhile (队友已选), that default instead (confirm would be disabled).
|
||
* @param {string|null} sel
|
||
* @param {{ bands: any[], taken: Map<string, any>, myPick: string|null, myTurn: boolean, defaultId?: string }} o
|
||
*/
|
||
export function draftSelection(sel, { bands, taken, myPick, myTurn, defaultId = DEFAULT_TIMEOUT_BAND }) {
|
||
if (!Array.isArray(bands) || !bands.length) return sel;
|
||
const free = timeoutBand(bands, taken, defaultId) || bands[0].bandId;
|
||
if (!sel) return myPick || free;
|
||
if (!myPick && myTurn && taken.has(sel)) return free;
|
||
return sel;
|
||
}
|
||
|
||
/**
|
||
* The strategy a turn that runs out assigns me (server Match.timeoutBand): the highlighted band while it is one of
|
||
* the mode's and no teammate holds it, else timeoutBand. Null after my pick.
|
||
* @param {string|null} sel the highlighted band
|
||
* @param {{ bands: any[], taken: Map<string, any>, myPick?: string|null, defaultId?: string }} o
|
||
*/
|
||
export function autoPickBand(sel, { bands, taken, myPick = null, defaultId = DEFAULT_TIMEOUT_BAND }) {
|
||
if (myPick) return null;
|
||
const list = Array.isArray(bands) ? bands : [];
|
||
const has = (id) => !!(taken && typeof taken.has === 'function' && taken.has(id));
|
||
if (sel && !has(sel) && list.some((b) => b.bandId === sel)) return sel;
|
||
return timeoutBand(list, taken, defaultId);
|
||
}
|
||
|
||
/**
|
||
* The tip under the co-op draft order: the one skip, the turn clock and what a turn that runs out assigns me (the
|
||
* highlighted band while free — autoPickBand). Untimed drafts (a single human) name no clock.
|
||
* @param {{ timed: boolean, turnSeconds?: number|null, autoName?: string|null, selected?: boolean }} o
|
||
* selected: the auto pick is the highlighted band (not the default standing in for a band a teammate holds)
|
||
*/
|
||
export function draftTip({ timed, turnSeconds = null, autoName = null, selected = true }) {
|
||
const skip = '联合模拟在选择策略时可以进行一次跳过';
|
||
if (!timed) return `${skip};本局不限时`;
|
||
const clock = Number(turnSeconds) > 0 ? `每位博士有 ${Math.round(turnSeconds)} 秒` : '每位博士限时决策';
|
||
if (!autoName) return `${skip};${clock}`;
|
||
return `${skip};${clock},超时将自动选择${selected ? '当前选中的' : ''}「${autoName}」`;
|
||
}
|
||
|
||
/**
|
||
* The step header's countdown during the draft: the current turn's (m.public.deadline = draft.turnDeadline) with a
|
||
* turn's length as the gauge total — null when the draft is untimed.
|
||
* @param {any} pub m.public
|
||
* @returns {{ deadline: number, total: number|null } | null}
|
||
*/
|
||
export function draftClock(pub) {
|
||
const d = pub && typeof pub.draft === 'object' ? pub.draft : null;
|
||
if (!d || d.untimed) return null;
|
||
const deadline = Number(pub.deadline) > 0 ? Number(pub.deadline) : Number(d.turnDeadline) || 0;
|
||
if (!(deadline > 0)) return null;
|
||
return { deadline, total: Number(d.turnSeconds) > 0 ? Number(d.turnSeconds) : null };
|
||
}
|
||
|
||
/**
|
||
* The status line of the 本局信息 dialog: what the draft does while the dialog covers it — my pick, else whose turn it is
|
||
* with the turn's seconds left (the step header's number; none when untimed), warning at ≤ 10 s like the countdown.
|
||
* @param {{ myPick?: string|null, pickName?: string|null, myTurn: boolean, turnName?: string|null, secs?: number|null,
|
||
* waiting?: boolean }} o waiting: teammates still have to pick after my pick
|
||
* @returns {{ text: string, secs: number|null, tone: 'mint'|'gold'|'warn'|'dim' }}
|
||
*/
|
||
export function draftInfoStatus({ myPick = null, pickName = null, myTurn, turnName = null, secs = null, waiting = false }) {
|
||
if (myPick) return { text: `已选择「${pickName || ''}」${waiting ? ',等待其他博士' : ''}`, secs: null, tone: 'mint' };
|
||
const s = Number.isFinite(secs) ? Math.max(0, Math.round(secs)) : null;
|
||
const tone = s != null && s <= 10 ? 'warn' : 'gold';
|
||
if (myTurn) return { text: '轮到你决策', secs: s, tone };
|
||
return { text: turnName ? `${turnName} 决策中` : '等待轮到你', secs: s, tone: s != null ? tone : 'dim' };
|
||
}
|
||
|
||
/** BAND_DRAFT screen. */
|
||
export function BandDraftScreen() {
|
||
const pub = useStore((s) => s.match.public);
|
||
const priv = useStore((s) => s.match.private);
|
||
const myId = useStore((s) => s.me.playerId);
|
||
const roomSolo = useStore((s) => s.room?.mode === 'solo');
|
||
const gd = useGameData();
|
||
const [sel, setSel] = useState(null);
|
||
const [busy, setBusy] = useState(null);
|
||
const [exit, setExit] = useState(false);
|
||
const [skipped, setSkipped] = useState(false);
|
||
const [infoOpen, setInfoOpen] = useState(false);
|
||
|
||
const mode = gd.config?.modes?.[pub?.modeId];
|
||
const offBonds = modeOffBonds(mode); // the bonds this mode never activates (标准: 10 of 23)
|
||
const solo = roomSolo || mode?.type === 'SINGLE' || String(pub?.modeId || '').includes('single');
|
||
const bands = useMemo(() => allowedBands(gd.list('bands'), mode?.type || (solo ? 'SINGLE' : 'MULTI')), [gd.ready, mode?.type, solo]);
|
||
const players = sortedPlayers(pub);
|
||
const draft = normalizeDraft(pub?.draft, players);
|
||
const myPick = draft.picks.get(myId) || priv?.bandId || null;
|
||
const myTurn = !myPick && (solo || draft.turnPid === myId);
|
||
const skipsLeft = draft.skipsLeft.has(myId) ? draft.skipsLeft.get(myId) : (skipped ? 0 : 1);
|
||
const canSkip = !solo && myTurn && skipsLeft > 0 && draft.order.length > 1;
|
||
const taken = solo ? new Map() : teammateBands(draft.picks, myId);
|
||
const pickers = new Map(); // bandId → players
|
||
for (const [pid, bid] of draft.picks) {
|
||
const p = players.find((x) => x.playerId === pid);
|
||
if (!pickers.has(bid)) pickers.set(bid, []);
|
||
pickers.get(bid).push(p || { playerId: pid, name: '?' });
|
||
}
|
||
|
||
// default selection: my pick, else what a timeout gives me (华法琳 while free); when my turn comes while the selected
|
||
// band has been taken by a teammate meanwhile (队友已选), move the selection back to that default
|
||
const defaultId = gd.config?.bandDraft?.timeoutBandId || DEFAULT_TIMEOUT_BAND;
|
||
const takenKey = [...taken.keys()].sort().join(',');
|
||
useEffect(() => {
|
||
const next = draftSelection(sel, { bands, taken, myPick, myTurn, defaultId });
|
||
if (next !== sel) setSel(next);
|
||
}, [bands.length, myPick, myTurn, takenKey]);
|
||
// "your turn" cue
|
||
useEffect(() => { if (myTurn && !solo) audio.sfx('yourTurn'); }, [myTurn]);
|
||
// the 本局信息 dialog never outlives the turn it was opened in: a turn change (a pick, a skip, a turn that ran out, an
|
||
// AI pick) or my pick closes it, so whoever's turn begins sees the draft
|
||
const turnKey = `${draft.turnPid || ''}|${myPick || ''}`;
|
||
useEffect(() => { setInfoOpen(false); }, [turnKey]);
|
||
|
||
// one countdown (user playtest #4 item 4): the current turn's — m.public.deadline, the same clock as the picker's row
|
||
const clock = solo ? null : draftClock(pub);
|
||
// the highlighted band is what a turn that runs out takes (Match.timeoutBand): report every change before my pick
|
||
const timed = !solo && !!pub?.draft && !pub.draft.untimed;
|
||
const focusSent = useRef(null);
|
||
useEffect(() => {
|
||
// (a spectator seat — community report #26 — is in no draft order: it never reports)
|
||
if (!timed || myPick || !sel || focusSent.current === sel || !draft.order.includes(myId)) return;
|
||
focusSent.current = sel;
|
||
act('g.bandFocus', { bandId: sel }, { sfx: false, quiet: true });
|
||
}, [sel, timed, myPick]);
|
||
|
||
// what a timeout gives me: the highlighted band while free, else the default (never 队友已选 — match/phases.js timeoutBand)
|
||
const autoId = autoPickBand(sel, { bands, taken, myPick, defaultId });
|
||
const autoName = (autoId && gd.band(autoId)?.name) || gd.band(defaultId)?.name || '华法琳';
|
||
|
||
const band = sel ? gd.band(sel) : null;
|
||
const selTaken = !!band && taken.has(band.bandId);
|
||
const confirm = async () => {
|
||
if (!band || busy || !myTurn || selTaken) return;
|
||
setBusy('pick');
|
||
await actions.band(band.bandId);
|
||
setBusy(null);
|
||
};
|
||
const skip = async () => {
|
||
if (busy || !canSkip) return;
|
||
setBusy('skip');
|
||
if (await actions.bandSkip()) setSkipped(true);
|
||
setBusy(null);
|
||
};
|
||
const turnName = players.find((p) => p.playerId === draft.turnPid)?.name;
|
||
useTicker(clock ? 250 : 0);
|
||
// the picker's row shows the step header's number (both read the one turn deadline)
|
||
const turnSecs = clock ? secondsLeft(clock.deadline) : null;
|
||
const turnLen = Number(pub?.draft?.turnSeconds) > 0 ? Math.round(pub.draft.turnSeconds) : null;
|
||
// 本局信息: the briefing's blocks (built only while the dialog is open) and the draft's state under it
|
||
const info = infoOpen ? matchInfoModel(pub, { bonds: gd.list('bonds'), chess: gd.chess, mode, priv, diyData: { chess: data.get('chess'), backups: data.get('backups') } }) : null;
|
||
const infoStatus = infoOpen ? draftInfoStatus({ myPick, pickName: myPick ? gd.band(myPick)?.name : null, myTurn, turnName, secs: turnSecs,
|
||
waiting: !solo && !draft.done }) : null;
|
||
|
||
return html`<div class="screen draft">
|
||
<div class="brief__bg" aria-hidden="true"></div>
|
||
<${StepHeader} step=${2} of=${2} title="选择策略" micro="STRATEGY // BAND CHECK" pub=${clock ? { ...pub, deadline: clock.deadline } : { ...pub, deadline: 0 }}
|
||
total=${clock ? clock.total : null} onExit=${() => setExit(true)} />
|
||
<main class="draft__main">
|
||
<aside class="draft-order">
|
||
<h3 class="brief-h"><span>${solo ? '独立模拟' : '决策顺序'}</span><${MicroLabel}>${solo ? 'FREE PICK' : 'RANDOM ORDER'}</${MicroLabel}></h3>
|
||
${(solo ? players.filter((p) => p.playerId === myId) : draft.order.map((pid) => players.find((p) => p.playerId === pid)).filter(Boolean)).map((p, i) => {
|
||
const picked = draft.picks.get(p.playerId) || (p.playerId === myId ? myPick : p.bandId) || null;
|
||
const cur = !picked && (solo || draft.turnPid === p.playerId);
|
||
const pband = picked ? gd.band(picked) : null;
|
||
return html`<div key=${p.playerId} class=${cx('dorder', cur && 'is-cur', picked && 'is-done', p.playerId === myId && 'is-self')}>
|
||
${!solo ? html`<span class="dorder__idx num">${i + 1}</span>` : null}
|
||
<${PlayerAvatar} player=${p} self=${p.playerId === myId} />
|
||
<div class="dorder__text">
|
||
<b class="dorder__name">${p.name || '博士'}${p.isBot ? html`<span class="dorder__ai">AI</span>` : null}</b>
|
||
<span class="dorder__state">${picked ? html`<span class="t-mint">${pband?.name || '已选择'}</span>`
|
||
: cur ? html`<span class="t-gold"><${Icon} name="hourglass" />决策中${turnSecs != null ? html`<b class="num dorder__secs">${turnSecs}s</b>` : null}</span>`
|
||
: html`<span class="t-dim"><${Icon} name="dots" />等待中</span>`}</span>
|
||
</div>
|
||
<span class="dorder__box">
|
||
${picked ? html`<${BandIcon} bandId=${picked} size="sm" /><span class="dorder__check"><${Icon} name="check" /></span>`
|
||
: cur && p.playerId === myId ? html`<${Sprite} k="bandChoose/youturn_finger" class="dorder__finger" fallback=${html`<${Icon} name="chevronLeft" />`} />`
|
||
: null}
|
||
</span>
|
||
</div>`;
|
||
})}
|
||
<${Button} variant="secondary" icon="search" block=${true} class="draft-order__info" data-testid="match-info-open"
|
||
aria-haspopup="dialog" onClick=${() => setInfoOpen(true)}>查看禁用盟约与干员<//>
|
||
${!solo ? html`<p class="draft-order__tip" data-testid="draft-tip">${draftTip({ timed, turnSeconds: turnLen, autoName: myPick ? null : autoName, selected: autoId === sel })}</p>` : null}
|
||
</aside>
|
||
|
||
<section class="draft-grid" role="listbox" aria-label="策略">
|
||
${bands.map((b) => {
|
||
const who = pickers.get(b.bandId) || [];
|
||
const isTaken = taken.has(b.bandId);
|
||
const offNames = bandOffBonds(b, offBonds).map((id) => gd.bond(id)?.name || id); // 本局禁用 (still selectable)
|
||
return html`<button key=${b.bandId} type="button" role="option" aria-selected=${sel === b.bandId ? 'true' : 'false'} data-band=${b.bandId}
|
||
aria-disabled=${isTaken ? 'true' : 'false'} title=${isTaken ? '队友已选' : offNames.length ? bandOffLine(offNames) : undefined}
|
||
class=${cx('dband', sel === b.bandId && 'is-sel', myPick === b.bandId && 'is-mine', isTaken && 'is-taken', offNames.length && 'is-off')} onClick=${() => { setSel(b.bandId); audio.sfx('tab', { volume: 0.5 }); }}>
|
||
<${BandIcon} bandId=${b.bandId} size="lg" />
|
||
<span class="dband__name">${b.name}</span>
|
||
<span class="dband__lp num"><i></i>${b.totalHp}</span>
|
||
<${BandOffTag} names=${offNames} />
|
||
${who.length ? html`<span class="dband__who">${who.slice(0, 4).map((p) => html`<${PlayerAvatar} key=${p.playerId} player=${p} size="sm" />`)}</span>` : null}
|
||
${isTaken ? html`<span class="dband__taken">队友已选</span>` : null}
|
||
</button>`;
|
||
})}
|
||
</section>
|
||
|
||
<aside class="draft-detail brackets">
|
||
${band ? html`
|
||
<div class="draft-detail__art">
|
||
<${BandIcon} bandId=${band.bandId} size="xl" />
|
||
</div>
|
||
<div class="draft-detail__hp"><span>初始生命值</span><${LpTower} value=${band.totalHp} size="lg" /></div>
|
||
<h2 class="draft-detail__name">${band.name}</h2>
|
||
<${BandOffNote} names=${bandOffBonds(band, offBonds).map((id) => gd.bond(id)?.name || id)} />
|
||
<div class="draft-detail__eff">
|
||
<${MicroLabel} tone="mint">EFFECT</${MicroLabel}>
|
||
<b>${band.effectName || ''}</b>
|
||
<${RichText} as="p" text=${band.descRaw || band.desc} class="draft-detail__desc" />
|
||
</div>` : html`<p class="t-dim">选择一个策略查看详情</p>`}
|
||
<div class="draft-detail__actions">
|
||
${myPick ? html`<p class="draft-detail__status t-mint"><${Icon} name="check" />已选择「${gd.band(myPick)?.name || ''}」${!solo && !draft.done ? ',等待其他博士' : ''}</p>`
|
||
: selTaken ? html`<p class="draft-detail__status draft-detail__status--taken"><${Icon} name="close" />队友已选,请选择其他策略</p>`
|
||
: !myTurn ? html`<p class="draft-detail__status"><${Icon} name="hourglass" />${turnName ? `${turnName} 正在决策…` : '等待轮到你'}</p>` : null}
|
||
<div class="draft-detail__btns">
|
||
${!solo ? html`<${Button} variant="secondary" size="lg" icon="chevrons" disabled=${!canSkip} loading=${busy === 'skip'} onClick=${skip}
|
||
title=${skipsLeft > 0 ? '跳过本轮,稍后再选' : '跳过次数已用完'}>跳过${skipsLeft > 0 ? '' : '(已用)'}<//>` : null}
|
||
<${Button} variant="primary" size="lg" icon="check" disabled=${!myTurn || !band || selTaken} loading=${busy === 'pick'} onClick=${confirm}>${selTaken ? '队友已选' : '确认选择'}<//>
|
||
</div>
|
||
</div>
|
||
</aside>
|
||
</main>
|
||
<${ExitModal} open=${exit} onClose=${() => setExit(false)} solo=${solo} />
|
||
<${MatchInfoDialog} open=${infoOpen} onClose=${() => setInfoOpen(false)} model=${info}
|
||
status=${infoStatus ? html`<span class=${cx('minfo-dlg__turn', `is-${infoStatus.tone}`)}>
|
||
<${Icon} name=${infoStatus.tone === 'mint' ? 'check' : 'hourglass'} />${infoStatus.text}${infoStatus.secs != null ? html`<b class="num">${infoStatus.secs}s</b>` : null}
|
||
</span>` : null} />
|
||
</div>`;
|
||
}
|