swiss WIP
parent
1efd45a3a1
commit
efd3bdf72f
|
@ -125,14 +125,24 @@ final class Swiss(
|
|||
}
|
||||
}
|
||||
|
||||
def standing(id: String, page: Int) = Open { implicit ctx =>
|
||||
WithSwiss(id) { swiss =>
|
||||
JsonOk {
|
||||
env.swiss.standingApi(swiss, page)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private def WithSwiss(id: String)(f: SwissModel => Fu[Result])(implicit ctx: Context): Fu[Result] =
|
||||
env.swiss.api.byId(SwissId(id)) flatMap { _ ?? f }
|
||||
|
||||
private def WithEditableSwiss(id: String, me: lila.user.User)(
|
||||
f: SwissModel => Fu[Result]
|
||||
)(implicit ctx: Context): Fu[Result] =
|
||||
env.swiss.api byId SwissId(id) flatMap {
|
||||
case Some(t) if (t.createdBy == me.id && !t.isFinished) || isGranted(_.ManageTournament) =>
|
||||
f(t)
|
||||
case Some(t) => Redirect(routes.Swiss.show(t.id.value)).fuccess
|
||||
case _ => notFound
|
||||
WithSwiss(id) { swiss =>
|
||||
if (swiss.createdBy == me.id && !swiss.isFinished) f(swiss)
|
||||
else if (isGranted(_.ManageTournament)) f(swiss)
|
||||
else Redirect(routes.Swiss.show(swiss.id.value)).fuccess
|
||||
}
|
||||
|
||||
private def canHaveChat(swiss: SwissModel)(implicit ctx: Context): Fu[Boolean] =
|
||||
|
|
|
@ -134,8 +134,8 @@ final class Tournament(
|
|||
|
||||
def standing(id: String, page: Int) = Open { implicit ctx =>
|
||||
OptionFuResult(repo byId id) { tour =>
|
||||
env.tournament.standingApi(tour, page) map { data =>
|
||||
Ok(data) as JSON
|
||||
JsonOk {
|
||||
env.tournament.standingApi(tour, page)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -252,6 +252,7 @@ POST /swiss/$id<\w{8}>/join controllers.Swiss.join(id: String)
|
|||
GET /swiss/$id<\w{8}>/edit controllers.Swiss.edit(id: String)
|
||||
POST /swiss/$id<\w{8}>/edit controllers.Swiss.update(id: String)
|
||||
POST /swiss/$id<\w{8}>/terminate controllers.Swiss.terminate(id: String)
|
||||
GET /swiss/$id<\w{8}>/standing/:page controllers.Swiss.standing(id: String, page: Int)
|
||||
|
||||
# Simul
|
||||
GET /simul controllers.Simul.home
|
||||
|
|
|
@ -37,6 +37,7 @@ final class Env(
|
|||
setupEnv: lila.setup.Env,
|
||||
simulEnv: lila.simul.Env,
|
||||
tourEnv: lila.tournament.Env,
|
||||
swissEnv: lila.swiss.Env,
|
||||
onlineApiUsers: lila.bot.OnlineApiUsers,
|
||||
challengeEnv: lila.challenge.Env,
|
||||
msgEnv: lila.msg.Env,
|
||||
|
|
|
@ -12,6 +12,7 @@ import lila.round.JsonView.WithFlags
|
|||
import lila.round.{ Forecast, JsonView }
|
||||
import lila.security.Granter
|
||||
import lila.simul.Simul
|
||||
import lila.swiss.Swiss
|
||||
import lila.tournament.{ GameView => TourView }
|
||||
import lila.tree.Node.partitionTreeJsonWriter
|
||||
import lila.user.User
|
||||
|
@ -23,6 +24,7 @@ final private[api] class RoundApi(
|
|||
bookmarkApi: lila.bookmark.BookmarkApi,
|
||||
gameRepo: lila.game.GameRepo,
|
||||
tourApi: lila.tournament.TournamentApi,
|
||||
swissApi: lila.swiss.SwissApi,
|
||||
simulApi: lila.simul.SimulApi,
|
||||
getTeamName: lila.team.GetTeamName,
|
||||
getLightUser: lila.common.LightUser.GetterSync
|
||||
|
@ -45,13 +47,15 @@ final private[api] class RoundApi(
|
|||
nvui = ctx.blind
|
||||
) zip
|
||||
(pov.game.simulId ?? simulApi.find) zip
|
||||
fetchSwiss(pov) zip
|
||||
(ctx.me.ifTrue(ctx.isMobileApi) ?? (me => noteApi.get(pov.gameId, me.id))) zip
|
||||
forecastApi.loadForDisplay(pov) zip
|
||||
bookmarkApi.exists(pov.game, ctx.me) map {
|
||||
case json ~ simulOption ~ note ~ forecast ~ bookmarked =>
|
||||
case json ~ simul ~ swiss ~ note ~ forecast ~ bookmarked =>
|
||||
(
|
||||
withTournament(pov, tour) _ compose
|
||||
withSimul(simulOption) _ compose
|
||||
withSwiss(swiss) _ compose
|
||||
withSimul(simul) _ compose
|
||||
withSteps(pov, initialFen) _ compose
|
||||
withNote(note) _ compose
|
||||
withBookmark(bookmarked) _ compose
|
||||
|
@ -82,12 +86,14 @@ final private[api] class RoundApi(
|
|||
withFlags = WithFlags(blurs = ctx.me ?? Granter(_.ViewBlurs))
|
||||
) zip
|
||||
(pov.game.simulId ?? simulApi.find) zip
|
||||
fetchSwiss(pov) zip
|
||||
(ctx.me.ifTrue(ctx.isMobileApi) ?? (me => noteApi.get(pov.gameId, me.id))) zip
|
||||
bookmarkApi.exists(pov.game, ctx.me) map {
|
||||
case json ~ simulOption ~ note ~ bookmarked =>
|
||||
case json ~ simul ~ swiss ~ note ~ bookmarked =>
|
||||
(
|
||||
withTournament(pov, tour) _ compose
|
||||
withSimul(simulOption) _ compose
|
||||
withSwiss(swiss) _ compose
|
||||
withSimul(simul) _ compose
|
||||
withNote(note) _ compose
|
||||
withBookmark(bookmarked) _ compose
|
||||
withSteps(pov, initialFen) _
|
||||
|
@ -119,12 +125,15 @@ final private[api] class RoundApi(
|
|||
) zip
|
||||
tourApi.gameView.analysis(pov.game) zip
|
||||
(pov.game.simulId ?? simulApi.find) zip
|
||||
(ctx.me.ifTrue(ctx.isMobileApi) ?? (me => noteApi.get(pov.gameId, me.id))) zip
|
||||
fetchSwiss(pov) zip
|
||||
ctx.userId.ifTrue(ctx.isMobileApi).?? { noteApi.get(pov.gameId, _) } zip
|
||||
bookmarkApi.exists(pov.game, ctx.me) map {
|
||||
case json ~ tour ~ simulOption ~ note ~ bookmarked =>
|
||||
case json ~ tour ~ simul ~ swiss ~ note ~ bookmarked =>
|
||||
(
|
||||
withTournament(pov, tour) _ compose
|
||||
withSimul(simulOption) _ compose
|
||||
withSwiss(swiss) _ compose
|
||||
withSimul(simul) _ compose
|
||||
withSwiss(swiss) _ compose
|
||||
withNote(note) _ compose
|
||||
withBookmark(bookmarked) _ compose
|
||||
withTree(pov, analysis, initialFen, withFlags) _ compose
|
||||
|
@ -267,6 +276,15 @@ final private[api] class RoundApi(
|
|||
})
|
||||
})
|
||||
|
||||
def withSwiss(swiss: Option[Swiss])(json: JsObject) =
|
||||
json.add("swiss" -> swiss.map { s =>
|
||||
Json
|
||||
.obj(
|
||||
"id" -> s.id.value,
|
||||
"running" -> s.isStarted
|
||||
)
|
||||
})
|
||||
|
||||
private def withSimul(simulOption: Option[Simul])(json: JsObject) =
|
||||
json.add("simul", simulOption.map { simul =>
|
||||
Json.obj(
|
||||
|
@ -276,4 +294,6 @@ final private[api] class RoundApi(
|
|||
"nbPlaying" -> simul.playingPairings.size
|
||||
)
|
||||
})
|
||||
|
||||
private def fetchSwiss(pov: Pov) = pov.game.swissId.map(Swiss.Id.apply) ?? swissApi.byId
|
||||
}
|
||||
|
|
|
@ -82,9 +82,11 @@ case class Game(
|
|||
|
||||
def tournamentId = metadata.tournamentId
|
||||
def simulId = metadata.simulId
|
||||
def swissId = metadata.swissId
|
||||
|
||||
def isTournament = tournamentId.isDefined
|
||||
def isSimul = simulId.isDefined
|
||||
def isSwiss = swissId.isDefined
|
||||
def isMandatory = isTournament || isSimul
|
||||
def isClassical = perfType contains Classical
|
||||
def nonMandatory = !isMandatory
|
||||
|
|
|
@ -21,7 +21,7 @@ final class TrouperMap[T <: Trouper](
|
|||
|
||||
def tellIfPresent(id: String, msg: => Any): Unit = getIfPresent(id) foreach (_ ! msg)
|
||||
|
||||
def tellAll(msg: Any) = troupers.asMap().asScala.foreach(_._2 ! msg)
|
||||
def tellAll(msg: Any) = troupers.asMap.asScala.foreach(_._2 ! msg)
|
||||
|
||||
def tellIds(ids: Seq[String], msg: Any): Unit = ids foreach { tell(_, msg) }
|
||||
|
||||
|
@ -60,4 +60,6 @@ final class TrouperMap[T <: Trouper](
|
|||
})
|
||||
|
||||
def monitor(name: String) = lila.mon.caffeineStats(troupers, name)
|
||||
|
||||
def keys: Set[String] = troupers.asMap.asScala.keySet.toSet
|
||||
}
|
||||
|
|
|
@ -50,7 +50,7 @@ private object BsonHandlers {
|
|||
implicit val playerHandler = new BSON[SwissPlayer] {
|
||||
import SwissPlayer.Fields._
|
||||
def reads(r: BSON.Reader) = SwissPlayer(
|
||||
_id = r.get[SwissPlayer.Id](id),
|
||||
id = r.get[SwissPlayer.Id](id),
|
||||
swissId = r.get[Swiss.Id](swissId),
|
||||
number = r.get[SwissPlayer.Number](number),
|
||||
userId = r str userId,
|
||||
|
@ -60,7 +60,7 @@ private object BsonHandlers {
|
|||
score = r.get[Swiss.Score](score)
|
||||
)
|
||||
def writes(w: BSON.Writer, o: SwissPlayer) = $doc(
|
||||
id -> o._id,
|
||||
id -> o.id,
|
||||
swissId -> o.swissId,
|
||||
number -> o.number,
|
||||
userId -> o.userId,
|
||||
|
@ -88,7 +88,7 @@ private object BsonHandlers {
|
|||
r.get[List[SwissPlayer.Number]](players) match {
|
||||
case List(w, b) =>
|
||||
SwissPairing(
|
||||
_id = r str id,
|
||||
id = r str id,
|
||||
swissId = r.get[Swiss.Id](swissId),
|
||||
round = r.get[SwissRound.Number](round),
|
||||
white = w,
|
||||
|
@ -98,7 +98,7 @@ private object BsonHandlers {
|
|||
case _ => sys error "Invalid swiss pairing users"
|
||||
}
|
||||
def writes(w: BSON.Writer, o: SwissPairing) = $doc(
|
||||
id -> o._id,
|
||||
id -> o.id,
|
||||
swissId -> o.swissId,
|
||||
round -> o.round,
|
||||
gameId -> o.gameId,
|
||||
|
|
|
@ -42,7 +42,7 @@ final class Env(
|
|||
def version(swissId: Swiss.Id): Fu[SocketVersion] =
|
||||
socket.rooms.ask[SocketVersion](swissId.value)(GetVersion)
|
||||
|
||||
private lazy val standingApi = wire[SwissStandingApi]
|
||||
lazy val standingApi = wire[SwissStandingApi]
|
||||
|
||||
private lazy val rankingApi = wire[SwissRankingApi]
|
||||
|
||||
|
|
|
@ -85,7 +85,7 @@ final private class PairingSystem(executable: String) {
|
|||
* suffix must be at least 3 characters long, otherwise this function throws an IllegalArgumentException.
|
||||
*/
|
||||
def withTempFile[A](contents: String)(f: File => A): A = {
|
||||
val file = File.createTempFile("lila-", "-swiss").pp
|
||||
val file = File.createTempFile("lila-", "-swiss")
|
||||
val p = new PrintWriter(file, "UTF-8")
|
||||
try {
|
||||
p.write(contents)
|
||||
|
@ -94,7 +94,7 @@ final private class PairingSystem(executable: String) {
|
|||
res
|
||||
} finally {
|
||||
p.close()
|
||||
// file.delete()
|
||||
file.delete()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -125,7 +125,7 @@ final class SwissApi(
|
|||
.flatMap { ids =>
|
||||
lila.common.Future.applySequentially(ids) { id =>
|
||||
Sequencing(id)(notFinishedById) { swiss =>
|
||||
director.startRound(swiss).flatMap { scoring.recompute _ } >>- socket.reload(swiss.id)
|
||||
director.startRound(swiss).flatMap(scoring.recompute) >>- socket.reload(swiss.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,9 +28,9 @@ final private class SwissDirector(
|
|||
_ <- pendings.isEmpty ?? fufail[Unit](s"BBPairing empty for ${from.id}")
|
||||
pairings <- pendings.collect {
|
||||
case Right(SwissPairing.Pending(w, b)) =>
|
||||
idGenerator.game map { id =>
|
||||
idGenerator.game dmap { id =>
|
||||
SwissPairing(
|
||||
_id = id,
|
||||
id = id,
|
||||
swissId = swiss.id,
|
||||
round = swiss.round,
|
||||
white = w,
|
||||
|
|
|
@ -3,14 +3,14 @@ package lila.swiss
|
|||
import lila.game.Game
|
||||
|
||||
case class SwissPairing(
|
||||
_id: Game.ID,
|
||||
id: Game.ID,
|
||||
swissId: Swiss.Id,
|
||||
round: SwissRound.Number,
|
||||
white: SwissPlayer.Number,
|
||||
black: SwissPlayer.Number,
|
||||
status: SwissPairing.Status
|
||||
) {
|
||||
def gameId = _id
|
||||
def gameId = id
|
||||
def players = List(white, black)
|
||||
def has(number: SwissPlayer.Number) = white == number || black == number
|
||||
def colorOf(number: SwissPlayer.Number) = chess.Color(white == number)
|
||||
|
|
|
@ -4,7 +4,7 @@ import lila.rating.Perf
|
|||
import lila.user.{ Perfs, User }
|
||||
|
||||
case class SwissPlayer(
|
||||
_id: SwissPlayer.Id, // random
|
||||
id: SwissPlayer.Id, // random
|
||||
swissId: Swiss.Id,
|
||||
number: SwissPlayer.Number,
|
||||
userId: User.ID,
|
||||
|
@ -13,7 +13,6 @@ case class SwissPlayer(
|
|||
points: Swiss.Points,
|
||||
score: Swiss.Score
|
||||
) {
|
||||
def id = _id
|
||||
def is(uid: User.ID): Boolean = uid == userId
|
||||
def is(user: User): Boolean = is(user.id)
|
||||
def is(other: SwissPlayer): Boolean = is(other.userId)
|
||||
|
@ -31,7 +30,7 @@ object SwissPlayer {
|
|||
user: User,
|
||||
perfLens: Perfs => Perf
|
||||
): SwissPlayer = new SwissPlayer(
|
||||
_id = makeId(swissId, user.id),
|
||||
id = makeId(swissId, user.id),
|
||||
swissId = swissId,
|
||||
number = number,
|
||||
userId = user.id,
|
||||
|
|
|
@ -8,63 +8,50 @@ final class SwissScoring(
|
|||
|
||||
import BsonHandlers._
|
||||
|
||||
def recompute(swiss: Swiss): Funit =
|
||||
SwissPlayer
|
||||
.fields { p =>
|
||||
for {
|
||||
prevPlayers <- fetchPlayers(swiss)
|
||||
pairings <- fetchPairings(swiss)
|
||||
pairingMap = SwissPairing.toMap(pairings)
|
||||
playersWithPoints = prevPlayers.map { p =>
|
||||
val playerPairings = ~pairingMap.get(p.number)
|
||||
p.copy(
|
||||
points = swiss.allRounds.pp.foldLeft(Swiss.Points(0)) {
|
||||
case (points, round) =>
|
||||
points + playerPairings
|
||||
.get(round)
|
||||
.fold(Swiss.Points(1)) { pairing =>
|
||||
pairing.winner.map(p.number.==) match {
|
||||
case Some(true) => Swiss.Points(2)
|
||||
case None => Swiss.Points(1)
|
||||
case _ => Swiss.Points(0)
|
||||
}
|
||||
}
|
||||
.pp
|
||||
def recompute(swiss: Swiss): Funit = {
|
||||
for {
|
||||
prevPlayers <- fetchPlayers(swiss)
|
||||
pairings <- fetchPairings(swiss)
|
||||
pairingMap = SwissPairing.toMap(pairings)
|
||||
playersWithPoints = prevPlayers.map { player =>
|
||||
val playerPairings = ~pairingMap.get(player.number)
|
||||
player.copy(
|
||||
points = swiss.allRounds.foldLeft(Swiss.Points(0)) {
|
||||
case (points, round) =>
|
||||
points + playerPairings.get(round).fold(Swiss.Points(1)) { pairing =>
|
||||
pairing.status match {
|
||||
case Right(Some(winner)) if winner == player.number => Swiss.Points(2)
|
||||
case Right(None) => Swiss.Points(1)
|
||||
case _ => Swiss.Points(0)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
playerMap = SwissPlayer.toMap(playersWithPoints)
|
||||
players = playersWithPoints.map { p =>
|
||||
p.copy(score = Swiss.Score {
|
||||
(~pairingMap.get(p.number)).values.foldLeft(0d) {
|
||||
case (score, pairing) =>
|
||||
score + {
|
||||
def opponentPoints = playerMap.get(pairing opponentOf p.number).??(_.points.value)
|
||||
pairing.winner.map(p.number.==) match {
|
||||
case Some(true) => opponentPoints
|
||||
case None => opponentPoints / 2
|
||||
case _ => 0
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
_ <- SwissPlayer.fields { f =>
|
||||
prevPlayers
|
||||
.zip(players.pp)
|
||||
.map {
|
||||
case (prev, player) =>
|
||||
val upd = (prev.points != player.points).?? { $doc(f.points -> player.points) } ++
|
||||
(prev.score != player.score).?? { $doc(f.score -> player.score) }
|
||||
println(lila.db.BSON.debug(upd))
|
||||
!upd.isEmpty ?? colls.player.update.one($id(p.id), $set(upd)).void
|
||||
}
|
||||
.sequenceFu
|
||||
.void
|
||||
}
|
||||
} yield {}
|
||||
)
|
||||
}
|
||||
.monSuccess(_.swiss.tiebreakRecompute)
|
||||
playerMap = SwissPlayer.toMap(playersWithPoints)
|
||||
players = playersWithPoints.map { p =>
|
||||
p.copy(score = Swiss.Score {
|
||||
(~pairingMap.get(p.number)).values.foldLeft(0d) {
|
||||
case (score, pairing) =>
|
||||
def opponentPoints = playerMap.get(pairing opponentOf p.number).??(_.points.value)
|
||||
score + pairing.winner.map(p.number.==).fold(opponentPoints / 2) { _ ?? opponentPoints }
|
||||
}
|
||||
})
|
||||
}
|
||||
_ <- SwissPlayer.fields { f =>
|
||||
prevPlayers
|
||||
.zip(players)
|
||||
.map {
|
||||
case (prev, player) =>
|
||||
val upd = (prev.points != player.points).?? { $doc(f.points -> player.points) } ++
|
||||
(prev.score != player.score).?? { $doc(f.score -> player.score) }
|
||||
(!upd.isEmpty) ?? colls.player.update.one($id(player.id), $set(upd)).void
|
||||
}
|
||||
.sequenceFu
|
||||
.void
|
||||
}
|
||||
} yield {}
|
||||
}.monSuccess(_.swiss.tiebreakRecompute)
|
||||
|
||||
private def fetchPlayers(swiss: Swiss) = SwissPlayer.fields { f =>
|
||||
colls.player.ext
|
||||
|
|
|
@ -5,6 +5,7 @@ export interface GameData {
|
|||
spectator?: boolean;
|
||||
tournament?: Tournament;
|
||||
simul?: Simul;
|
||||
swiss?: Swiss;
|
||||
takebackable: boolean;
|
||||
moretimeable: boolean;
|
||||
clock?: Clock;
|
||||
|
@ -99,6 +100,11 @@ export interface Simul {
|
|||
nbPlaying: number;
|
||||
}
|
||||
|
||||
export interface Swiss {
|
||||
id: string;
|
||||
running?: boolean;
|
||||
}
|
||||
|
||||
export interface Clock {
|
||||
running: boolean;
|
||||
initial: number;
|
||||
|
|
|
@ -48,7 +48,7 @@ export default function(opts: RoundOpts): void {
|
|||
});
|
||||
|
||||
function startTournamentClock() {
|
||||
if (opts.data.tournament) $('.game__tournament .clock').each(function(this: HTMLElement) {
|
||||
if (data.tournament) $('.game__tournament .clock').each(function(this: HTMLElement) {
|
||||
$(this).clock({
|
||||
time: parseFloat($(this).data('time'))
|
||||
});
|
||||
|
@ -62,18 +62,18 @@ export default function(opts: RoundOpts): void {
|
|||
};
|
||||
opts.element = element;
|
||||
opts.socketSend = li.socket.send;
|
||||
if (!opts.data.tournament && !data.simul) opts.onChange = (d: RoundData) => {
|
||||
if (!data.tournament && !data.simul && !data.swiss) opts.onChange = (d: RoundData) => {
|
||||
if (chat) chat.preset.setGroup(getPresetGroup(d));
|
||||
};
|
||||
|
||||
round = (window['LichessRound'] as RoundMain).app(opts);
|
||||
const chatOpts = opts.chat;
|
||||
if (chatOpts) {
|
||||
if (opts.data.tournament?.top) {
|
||||
chatOpts.plugin = tourStandingCtrl(opts.data.tournament.top, opts.data.tournament.team, opts.i18n.standing);
|
||||
if (data.tournament?.top) {
|
||||
chatOpts.plugin = tourStandingCtrl(data.tournament.top, data.tournament.team, opts.i18n.standing);
|
||||
chatOpts.alwaysEnabled = true;
|
||||
} else if (!data.simul) {
|
||||
chatOpts.preset = getPresetGroup(opts.data);
|
||||
chatOpts.preset = getPresetGroup(data);
|
||||
chatOpts.parseMoves = true;
|
||||
}
|
||||
if (chatOpts.noteId && (chatOpts.noteAge || 0) < 10) chatOpts.noteText = '';
|
||||
|
|
|
@ -213,7 +213,7 @@ export function submitMove(ctrl: RoundController): VNode | undefined {
|
|||
|
||||
export function backToTournament(ctrl: RoundController): VNode | undefined {
|
||||
const d = ctrl.data;
|
||||
return (d.tournament && d.tournament.running) ? h('div.follow-up', [
|
||||
return d.tournament?.running ? h('div.follow-up', [
|
||||
h('a.text.fbt.strong.glowing', {
|
||||
attrs: {
|
||||
'data-icon': 'G',
|
||||
|
@ -233,6 +233,20 @@ export function backToTournament(ctrl: RoundController): VNode | undefined {
|
|||
]) : undefined;
|
||||
}
|
||||
|
||||
export function backToSwiss(ctrl: RoundController): VNode | undefined {
|
||||
const d = ctrl.data;
|
||||
return d.swiss?.running ? h('div.follow-up', [
|
||||
h('a.text.fbt.strong.glowing', {
|
||||
attrs: {
|
||||
'data-icon': 'G',
|
||||
href: '/swiss/' + d.swiss.id
|
||||
},
|
||||
hook: util.bind('click', ctrl.setRedirecting)
|
||||
}, ctrl.noarg('backToTournament')),
|
||||
analysisButton(ctrl)
|
||||
]) : undefined;
|
||||
}
|
||||
|
||||
export function moretime(ctrl: RoundController) {
|
||||
return game.moretimeable(ctrl.data) ? h('a.moretime', {
|
||||
attrs: {
|
||||
|
@ -246,7 +260,7 @@ export function moretime(ctrl: RoundController) {
|
|||
|
||||
export function followUp(ctrl: RoundController): VNode {
|
||||
const d = ctrl.data,
|
||||
rematchable = !d.game.rematch && (status.finished(d) || status.aborted(d)) && !d.tournament && !d.simul && !d.game.boosted,
|
||||
rematchable = !d.game.rematch && (status.finished(d) || status.aborted(d)) && !d.tournament && !d.simul && !d.swiss && !d.game.boosted,
|
||||
newable = (status.finished(d) || status.aborted(d)) && (
|
||||
d.game.source === 'lobby' ||
|
||||
d.game.source === 'pool'),
|
||||
|
@ -260,6 +274,9 @@ export function followUp(ctrl: RoundController): VNode {
|
|||
d.tournament ? h('a.fbt', {
|
||||
attrs: {href: '/tournament/' + d.tournament.id}
|
||||
}, ctrl.noarg('viewTournament')) : null,
|
||||
d.swiss ? h('a.fbt', {
|
||||
attrs: {href: '/swiss/' + d.swiss.id}
|
||||
}, ctrl.noarg('viewTournament')) : null,
|
||||
newable ? h('a.fbt', {
|
||||
attrs: { href: d.game.source === 'pool' ? poolUrl(d.clock!, d.opponent.user) : '/?hook_like=' + d.game.id },
|
||||
}, ctrl.noarg('newOpponent')) : null,
|
||||
|
@ -279,6 +296,9 @@ export function watcherFollowUp(ctrl: RoundController): VNode | null {
|
|||
d.tournament ? h('a.fbt', {
|
||||
attrs: {href: '/tournament/' + d.tournament.id}
|
||||
}, ctrl.noarg('viewTournament')) : null,
|
||||
d.swiss ? h('a.fbt', {
|
||||
attrs: {href: '/swiss/' + d.swiss.id}
|
||||
}, ctrl.noarg('viewTournament')) : null,
|
||||
analysisButton(ctrl)
|
||||
];
|
||||
return content.find(x => !!x) ? h('div.follow-up', content) : null;
|
||||
|
|
|
@ -36,7 +36,9 @@ function renderTableWith(ctrl: RoundController, buttons: MaybeVNodes) {
|
|||
|
||||
export function renderTableEnd(ctrl: RoundController) {
|
||||
return renderTableWith(ctrl, [
|
||||
isLoading(ctrl) ? loader() : (button.backToTournament(ctrl) || button.followUp(ctrl))
|
||||
isLoading(ctrl) ? loader() : (
|
||||
button.backToTournament(ctrl) || button.backToSwiss(ctrl) || button.followUp(ctrl)
|
||||
)
|
||||
]);
|
||||
}
|
||||
|
||||
|
|
|
@ -51,7 +51,8 @@
|
|||
font-size: .8em;
|
||||
}
|
||||
}
|
||||
.sheet {
|
||||
.pairings {
|
||||
width: 100%;
|
||||
text-align: right;
|
||||
padding-right: 0;
|
||||
padding-left: 0;
|
||||
|
@ -63,25 +64,12 @@
|
|||
opacity: 0.7;
|
||||
}
|
||||
}
|
||||
tr.long .sheet {
|
||||
font-size: .9rem;
|
||||
letter-spacing: .06em;
|
||||
}
|
||||
tr.xlong .sheet {
|
||||
font-size: .85rem;
|
||||
letter-spacing: .04em;
|
||||
}
|
||||
double {
|
||||
color: $c-brag;
|
||||
/* font-weight: bold; */
|
||||
}
|
||||
streak {
|
||||
color: $c-secondary;
|
||||
/* font-weight: bold; */
|
||||
}
|
||||
.total {
|
||||
.points {
|
||||
text-align: right;
|
||||
font-weight: bold;
|
||||
}
|
||||
.score {
|
||||
text-align: right;
|
||||
padding-right: $block-gap;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -82,7 +82,7 @@ export default class SwissCtrl {
|
|||
};
|
||||
|
||||
loadPage = (data: Standing) => {
|
||||
if (!this.pages[data.page]) this.pages[data.page] = data.players;
|
||||
if (!data.failed || !this.pages[data.page]) this.pages[data.page] = data.players;
|
||||
}
|
||||
|
||||
setPage = (page: number) => {
|
||||
|
|
|
@ -63,6 +63,7 @@ export interface Pairing {
|
|||
export interface Standing {
|
||||
page: number;
|
||||
players: Player[];
|
||||
failed?: boolean;
|
||||
}
|
||||
|
||||
export interface Player {
|
||||
|
|
|
@ -9,7 +9,7 @@ export default function(send: SocketSend, ctrl: SwissCtrl) {
|
|||
|
||||
const handlers = {
|
||||
reload() {
|
||||
setTimeout(ctrl.askReload, Math.floor(Math.random() * 4000))
|
||||
setTimeout(ctrl.askReload, Math.floor(Math.random() * 3000))
|
||||
},
|
||||
redirect(fullId) {
|
||||
ctrl.redirectFirst(fullId.slice(0, 8), true);
|
||||
|
|
|
@ -3,38 +3,29 @@ import { json, form } from 'common/xhr';
|
|||
import SwissCtrl from './ctrl';
|
||||
import { SwissData } from './interfaces';
|
||||
|
||||
const headers = {
|
||||
'Accept': 'application/vnd.lichess.v5+json'
|
||||
};
|
||||
|
||||
// when the tournament no longer exists
|
||||
function onFail(err) {
|
||||
// throw err;
|
||||
window.lichess.reload();
|
||||
throw err;
|
||||
// window.lichess.reload();
|
||||
}
|
||||
|
||||
const join = (ctrl: SwissCtrl) =>
|
||||
json(`/swiss/${ctrl.data.id}/join`, { method: 'post' }).catch(onFail);
|
||||
|
||||
const loadPage = (ctrl: SwissCtrl, p: number) =>
|
||||
thenReloadOrFail(ctrl,
|
||||
json(`/swiss/${ctrl.data.id}/standing/${p}`)
|
||||
);
|
||||
json(`/swiss/${ctrl.data.id}/standing/${p}`).then(data => {
|
||||
ctrl.loadPage(data);
|
||||
ctrl.redraw();
|
||||
});
|
||||
|
||||
const loadPageOf = (ctrl: SwissCtrl, userId: string): Promise<any> =>
|
||||
json(`/swiss/${ctrl.data.id}/page-of/${userId}`);
|
||||
|
||||
const reload = (ctrl: SwissCtrl) =>
|
||||
thenReloadOrFail(ctrl,
|
||||
json(`/swiss/${ctrl.data.id}?page=${ctrl.focusOnMe ? 0 : ctrl.page}`)
|
||||
);
|
||||
|
||||
const thenReloadOrFail = (ctrl: SwissCtrl, res: Promise<SwissData>) =>
|
||||
res.then(data => {
|
||||
json(`/swiss/${ctrl.data.id}?page=${ctrl.focusOnMe ? 0 : ctrl.page}`).then(data => {
|
||||
ctrl.reload(data);
|
||||
ctrl.redraw();
|
||||
})
|
||||
.catch(onFail);
|
||||
}).catch(onFail);
|
||||
|
||||
// function playerInfo(ctrl: SwissCtrl, userId: string) {
|
||||
// return $.ajax({
|
||||
|
|
Loading…
Reference in New Issue