@@ -7,11 +7,23 @@
const display = app . dataset . display === '1' ;
const seenAlerts = new Set ( ) ;
const esc = s => String ( s ? ? '' ) . replace ( /[&<>"']/g , c => ( { '&' : '&' , '<' : '<' , '>' : '>' , '"' : '"' , "'" : ''' } [ c ] ) ) ;
const name = t => t ? esc ( t . name ) : '<span class="tbd">TBD</span>' ;
// every team name on the board links to that team's page (schedule, record, next match)
const teamHref = t => ` /t/ ${ state . slug } /teams/ ${ encodeURIComponent ( t . id ) } ` ;
const name = t => t ? ` <a class="tl" href=" ${ teamHref ( t ) } "> ${ esc ( t . name ) } </a> ` : '<span class="tbd">TBD</span>' ;
const clock = ms => new Date ( ms ) . toLocaleTimeString ( [ ] , { hour : 'numeric' , minute : '2-digit' } ) ;
const isMine = m => myTeam && ( m . a ? . id === myTeam || m . b ? . id === myTeam ) ;
const phaseLabel = { checkin : 'Registration open' , closed : 'Registration closed' , live : 'Live' , final : 'Final' } ;
const score = m => m . status === 'final' ? m . sets . map ( s => ` ${ s [ 0 ] } – ${ s [ 1 ] } ` ) . join ( ', ' )
: m . status === 'forfeit' ? 'forfeit' : m . live ? ` ${ m . live [ 0 ] } – ${ m . live [ 1 ] } ` : m . status === 'live' ? '0– 0' : '' ;
// score from one team's point of view (their points first)
const scoreFor = ( m , teamId ) => {
const flip = m . b ? . id === teamId ;
const pair = ( [ a , b ] ) => flip ? ` ${ b } – ${ a } ` : ` ${ a } – ${ b } ` ;
if ( m . status === 'final' ) return m . sets . map ( pair ) . join ( ', ' ) ;
if ( m . status === 'forfeit' ) return 'forfeit' ;
if ( m . live ) return pair ( m . live ) ;
return m . status === 'live' ? '0– 0' : '' ;
} ;
const rulesLine = r => ` ${ r . teamSize } s · to ${ r . pointsTo } , win by ${ r . winBy } ${ r . cap ? ` , cap ${ r . cap } ` : '' } ${ r . bestOf > 1 ? ` , best of ${ r . bestOf } ` : '' } ` ;
function render ( ) {
@@ -49,7 +61,7 @@
else if ( s . phase === 'live' ) now = ` <p>No match scheduled yet. Waiting on results from other courts.</p> ` ;
else now = ` <p>Waiting for play to start.</p> ` ;
return ` <section class="card mine"><div class="eyebrow">Your team</div><h2> ${ esc ( t . name ) } </h2> ${ now } <p class="muted"> ${ played . length ? ` Record ${ w } – ${ played . length - w } ` : 'No results yet' } ${ t . poolId ? ` · Pool ${ esc ( t . poolId ) } ` : '' } </p>
${ my . length ? ` <ul class="plain sched"> ${ my . map ( m => ` <li class=" ${ m . status } "><span> ${ m . stage === 'pool' ? ` Pool ${ m . round } ` : ( m . label ? ? ` Bracket R ${ m . round } ` ) } </span><span>vs ${ name ( m . a ? . id === t . id ? m . b : m . a ) } </span><span class="mono"> ${ score ( m ) || ( m . court ? ` Court ${ m . court } ` : '' ) } ${ m . status === 'final' || m . status === 'forfeit' ? ( m . winner === t . id ? ' W' : ' L' ) : '' } </span></li> ` ) . join ( '' ) } </ul> ` : '' }
${ my . length ? ` <ul class="plain sched"> ${ my . map ( m => ` <li class=" ${ m . status } "><span> ${ m . stage === 'pool' ? ` Pool ${ m . round } ` : ( m . label ? ? ` Bracket R ${ m . round } ` ) } </span><span>vs ${ name ( m . a ? . id === t . id ? m . b : m . a ) } </span><span class="mono"> ${ scoreFor ( m , t . id ) || ( m . court ? ` Court ${ m . court } ` : '' ) } ${ m . status === 'final' || m . status === 'forfeit' ? ( m . winner === t . id ? ' W' : ' L' ) : '' } </span></li> ` ) . join ( '' ) } </ul> ` : '' }
${ ! display && 'Notification' in window && Notification . permission === 'default' ? ` <button type="button" class="button" id="notify">Alert me when we're up</button> ` : '' }
</section> ` ;
}
@@ -59,20 +71,34 @@
const m = c . match ;
if ( c . status === 'paused' ) return ` <div class="court paused"><div class="lbl">Court ${ c . court } </div><div class="teams"><div class="t">Paused</div></div></div> ` ;
if ( ! m ) return ` <div class="court idle"><div class="lbl">Court ${ c . court } </div><div class="teams"><div class="t muted">Open</div></div></div> ` ;
const avg = state . avgMatchMin ;
const elapsed = m . startedAt ? Math . max ( 0 , Math . round ( ( Date . now ( ) - m . startedAt ) / 60000 ) ) : null ;
const left = avg != null && elapsed != null ? Math . max ( 0 , avg - elapsed ) : null ;
const timing = avg != null ? ` avg game ${ avg } min ${ elapsed != null ? ` · ${ elapsed } min played ${ left ? ` , ~ ${ left } left ` : '' } ` : '' } ` : '' ;
return ` <div class="court ${ isMine ( m ) ? 'mine' : '' } "><div class="lbl">Court ${ c . court } · ${ m . stage === 'pool' ? ` Pool ${ esc ( m . a && state . teams . find ( t => t . id === m . a . id ) ? . poolId || '' ) } R ${ m . round } ` : ( m . label ? ? ` Bracket R ${ m . round } ` ) } </div>
<div class="teams"><div class="t"> ${ name ( m . a ) } </div><div class="s mono"> ${ score ( m ) || '0– 0' } </div><div class="t"> ${ name ( m . b ) } </div></div></div> ` ;
<div class="teams"><div class="t"> ${ name ( m . a ) } </div><div class="s mono"> ${ score ( m ) || '0– 0' } </div><div class="t"> ${ name ( m . b ) } </div></div>
${ timing ? ` <div class="timing"> ${ timing } </div> ` : '' }
${ nextForCourt ( c . court ) } </div> ` ;
} ).join('')}</section> ` ;
}
// the first queued match predicted for this court, with the clock time it should be ready
function nextForCourt ( courtNo ) {
const u = state . upNext . find ( x => x . court === courtNo ) ;
if ( ! u ) return '' ;
const ready = clock ( state . generatedAt + u . etaMin * 60000 ) ;
return ` <div class="nextup ${ isMine ( u ) ? 'mine' : '' } "><span class="k">Next up</span> ${ name ( u . a ) } vs ${ name ( u . b ) } <span class="eta mono">ready ~ ${ ready } </span></div> ` ;
}
function renderUpNext ( ) {
if ( ! state . upNext . length ) return '' ;
return ` <section class="queue"><div class="eyebrow">Up next</div> ${ state . upNext . map ( ( u , i ) => ` <div class="qrow ${ isMine ( u ) ? 'mine' : '' } "><span class="k"> ${ i === 0 ? 'On deck' : 'Then' } </span><span> ${ name ( u . a ) } vs ${ name ( u . b ) } </span><span class="eta mono">Court ${ u . court } · ~ ${ u . etaMin } min</span></div> ` ) . join ( '' ) } </section> ` ;
return ` <section class="queue"><div class="eyebrow">Up next</div> ${ state . upNext . map ( ( u , i ) => ` <div class="qrow ${ isMine ( u ) ? 'mine' : '' } "><span class="k"> ${ i === 0 ? 'On deck' : 'Then' } </span><span> ${ name ( u . a ) } vs ${ name ( u . b ) } </span><span class="eta mono">Court ${ u . court } · ~ ${ clock ( state . generatedAt + u . etaMin * 60000 ) } ( ${ u . etaMin } min) </span></div> ` ) . join ( '' ) } </section> ` ;
}
function renderPools ( ) {
return ` <section class="pools"> ${ state . pools . map ( p => ` <div class="card pool"><h2>Pool ${ esc ( p . id ) } </h2>
<table><thead><tr><th>Team</th><th class="num">W</th><th class="num">L</th><th class="num">Pts</th><th class="num">+/− </th></tr></thead><tbody>
${ p . standings . map ( ( r , i ) => ` <tr class=" ${ r . status === 'withdrawn' ? 'withdrawn' : '' } ${ r . teamId === myTeam ? 'mine' : '' } "><td> ${ i + 1 } . ${ esc ( r . name ) } ${ r . decidedBy ? ` <span class="tb" title="Tie broken by ${ r . decidedBy } ">*</span> ` : '' } </td><td class="num"> ${ r . w } </td><td class="num"> ${ r . l } </td><td class="num"> ${ r . pf } – ${ r . pa } </td><td class="num"> ${ r . pointDiff > 0 ? '+' : '' } ${ r . pointDiff } </td></tr> ` ) . join ( '' ) }
${ p . standings . map ( ( r , i ) => ` <tr class=" ${ r . status === 'withdrawn' ? 'withdrawn' : '' } ${ r . teamId === myTeam ? 'mine' : '' } "><td> ${ i + 1 } . ${ name ( { id : r . teamId , name : r . name } )} ${ r . decidedBy ? ` <span class="tb" title="Tie broken by ${ r . decidedBy } ">*</span> ` : '' } </td><td class="num"> ${ r . w } </td><td class="num"> ${ r . l } </td><td class="num"> ${ r . pf } – ${ r . pa } </td><td class="num"> ${ r . pointDiff > 0 ? '+' : '' } ${ r . pointDiff } </td></tr> ` ) . join ( '' ) }
</tbody></table>
<details ${ display ? '' : '' } ><summary>Matches</summary><ul class="plain matches"> ${ p . matches . map ( m => ` <li class=" ${ m . status } ${ isMine ( m ) ? 'mine' : '' } "><span>R ${ m . round } </span><span class=" ${ m . winner && m . winner === m . a ? . id ? 'won' : '' } "> ${ name ( m . a ) } </span><span class="mono"> ${ score ( m ) || ( m . court ? ` C ${ m . court } ` : '·' ) } </span><span class=" ${ m . winner && m . winner === m . b ? . id ? 'won' : '' } "> ${ name ( m . b ) } </span></li> ` ) . join ( '' ) } </ul></details>
</div> ` ) . join ( '' ) } </section> ` ;
@@ -134,7 +160,7 @@
const row = ( t , side ) => {
const won = m . winner && t && m . winner === t . id ;
const pts = m . sets [ 0 ] ? m . sets . map ( s => s [ side ] ) . join ( ' ' ) : ( m . live && m . status === 'live' ? m . live [ side ] : '' ) ;
return ` <div class="brow ${ won ? 'won' : '' } ${ t && isMineId ( t . id ) ? 'mine' : '' } "><span class="bn"> ${ t ? esc ( t . name ) : ` <span class="tbd"> ${ feedLabel ( m , side ) } </span> ` } </span><span class="bs mono"> ${ pts } </span></div> ` ;
return ` <div class="brow ${ won ? 'won' : '' } ${ t && isMineId ( t . id ) ? 'mine' : '' } "><span class="bn"> ${ t ? name ( t ) : ` <span class="tbd"> ${ feedLabel ( m , side ) } </span> ` } </span><span class="bs mono"> ${ pts } </span></div> ` ;
} ;
const tag = m . status === 'live' ? ` <span class="btag live">Court ${ m . court } </span> ` : m . status === 'on_deck' ? ` <span class="btag">On deck ${ m . court ? ` · C ${ m . court } ` : '' } </span> ` : m . status === 'forfeit' ? '<span class="btag">Forfeit</span>' : m . label ? ` <span class="btag"> ${ esc ( m . label ) } </span> ` : '' ;
return ` <div class="bm ${ m . status } ${ isMine ( m ) ? 'mine' : '' } " style="left: ${ p . x } px;top: ${ p . y } px;width: ${ BOX _W } px;height: ${ BOX _H } px"> ${ row ( m . a , 0 ) } ${ row ( m . b , 1 ) } ${ tag } </div> ` ;
@@ -153,7 +179,7 @@
function bracketList ( stage , ms ) {
const rounds = [ ... new Set ( ms . map ( m => m . round ) ) ] . sort ( ( a , b ) => a - b ) ;
return rounds . map ( r => ` <div class="blist"><div class="rlbl"> ${ roundName ( stage , r , rounds . length , ms ) } </div> ${ ms . filter ( m => m . round === r ) . map ( m => ` <div class="bm list ${ m . status } ${ isMine ( m ) ? 'mine' : '' } "> ${ [ 'a' , 'b' ] . map ( ( k , i ) => { const t = m [ k ] ; const won = m . winner && t && m . winner === t . id ; return ` <div class="brow ${ won ? 'won' : '' } ${ t && isMineId ( t . id ) ? 'mine' : '' } "><span class="bn"> ${ t ? esc ( t . name ) : ` <span class="tbd"> ${ feedLabel ( m , i ) } </span> ` } </span><span class="bs mono"> ${ m . sets [ 0 ] ? m . sets . map ( s => s [ i ] ) . join ( ' ' ) : ( m . live && m . status === 'live' ? m . live [ i ] : '' ) } </span></div> ` ; } ).join('')} ${ m . status === 'live' ? ` <span class="btag live">Court ${ m . court } </span> ` : m . status === 'on_deck' ? ` <span class="btag">On deck ${ m . court ? ` · C ${ m . court } ` : '' } </span> ` : m . status === 'forfeit' ? '<span class="btag">Forfeit</span>' : m . label ? ` <span class="btag"> ${ esc ( m . label ) } </span> ` : '' } </div> ` ) . join ( '' ) } </div> ` ) . join ( '' ) ;
return rounds . map ( r => ` <div class="blist"><div class="rlbl"> ${ roundName ( stage , r , rounds . length , ms ) } </div> ${ ms . filter ( m => m . round === r ) . map ( m => ` <div class="bm list ${ m . status } ${ isMine ( m ) ? 'mine' : '' } "> ${ [ 'a' , 'b' ] . map ( ( k , i ) => { const t = m [ k ] ; const won = m . winner && t && m . winner === t . id ; return ` <div class="brow ${ won ? 'won' : '' } ${ t && isMineId ( t . id ) ? 'mine' : '' } "><span class="bn"> ${ t ? name ( t ) : ` <span class="tbd"> ${ feedLabel ( m , i ) } </span> ` } </span><span class="bs mono"> ${ m . sets [ 0 ] ? m . sets . map ( s => s [ i ] ) . join ( ' ' ) : ( m . live && m . status === 'live' ? m . live [ i ] : '' ) } </span></div> ` ; } ).join('')} ${ m . status === 'live' ? ` <span class="btag live">Court ${ m . court } </span> ` : m . status === 'on_deck' ? ` <span class="btag">On deck ${ m . court ? ` · C ${ m . court } ` : '' } </span> ` : m . status === 'forfeit' ? '<span class="btag">Forfeit</span>' : m . label ? ` <span class="btag"> ${ esc ( m . label ) } </span> ` : '' } </div> ` ) . join ( '' ) } </div> ` ) . join ( '' ) ;
}
const isMineId = id => myTeam && id === myTeam ;