338 lines
11 KiB
Scala
338 lines
11 KiB
Scala
package views.html.user
|
|
|
|
import play.api.i18n.Lang
|
|
|
|
import lila.api.Context
|
|
import lila.app.templating.Environment._
|
|
import lila.app.ui.ScalatagsTemplate._
|
|
import lila.rating.{ Perf, PerfType }
|
|
import lila.perfStat.{ PerfStat, PerfStatData }
|
|
import lila.user.User
|
|
|
|
import controllers.routes
|
|
|
|
object perfStat {
|
|
|
|
import trans.perfStat._
|
|
|
|
def apply(
|
|
data: PerfStatData,
|
|
ratingChart: Option[String]
|
|
)(implicit ctx: Context) = {
|
|
import data._
|
|
import stat.perfType
|
|
views.html.base.layout(
|
|
title = s"${user.username} - ${perfStats.txt(perfType.trans)}",
|
|
robots = false,
|
|
moreJs = frag(
|
|
jsModule("user"),
|
|
ratingChart.map { rc =>
|
|
frag(
|
|
jsTag("chart/ratingHistory.js"),
|
|
embedJsUnsafeLoadThen(
|
|
s"lichess.ratingHistoryChart($rc,{singlePerfName:'${perfType.trans(lila.i18n.defaultLang)}'});"
|
|
)
|
|
)
|
|
}
|
|
),
|
|
moreCss = cssTag("perf-stat")
|
|
) {
|
|
main(cls := s"page-menu")(
|
|
st.aside(cls := "page-menu__menu")(show.side(user, ranks, perfType.some)),
|
|
div(cls := s"page-menu__content box perf-stat ${perfType.key}")(
|
|
div(cls := "box__top")(
|
|
div(cls := "box__top__title")(
|
|
bits.perfTrophies(user, ranks.view.filterKeys(perfType.==).toMap),
|
|
h1(
|
|
a(href := routes.User.show(user.username))(user.username),
|
|
span(perfStats(perfType.trans))
|
|
)
|
|
),
|
|
div(cls := "box__top__actions")(
|
|
user.perfs(perfType).nb > 0 option a(
|
|
cls := "button button-empty text",
|
|
dataIcon := perfType.iconChar,
|
|
href := s"${routes.User.games(user.username, "search")}?perf=${perfType.id}"
|
|
)(viewTheGames())
|
|
)
|
|
),
|
|
ratingChart.isDefined option div(cls := "rating-history")(spinner),
|
|
div(cls := "box__pad perf-stat__content")(
|
|
glicko(user, perfType, user.perfs(perfType), percentile),
|
|
counter(stat.count),
|
|
highlow(stat),
|
|
resultStreak(stat.resultStreak),
|
|
result(stat, user),
|
|
playStreakNb(stat.playStreak),
|
|
playStreakTime(stat.playStreak)
|
|
)
|
|
)
|
|
)
|
|
}
|
|
}
|
|
|
|
private def decimal(v: Double) = lila.common.Maths.roundDownAt(v, 2)
|
|
|
|
private def glicko(u: User, perfType: PerfType, perf: Perf, percentile: Option[Double])(implicit
|
|
ctx: Context
|
|
): Frag =
|
|
st.section(cls := "glicko")(
|
|
h2(
|
|
trans.perfRatingX(
|
|
strong(
|
|
if (perf.glicko.clueless) "?"
|
|
else decimal(perf.glicko.rating).toString
|
|
)
|
|
),
|
|
perf.glicko.provisional option frag(
|
|
" ",
|
|
span(
|
|
title := notEnoughRatedGames.txt(),
|
|
cls := "details"
|
|
)("(", provisional(), ")")
|
|
),
|
|
". ",
|
|
percentile.filter(_ != 0.0 && !perf.glicko.provisional).map { percentile =>
|
|
span(cls := "details")(
|
|
if (ctx is u) {
|
|
trans.youAreBetterThanPercentOfPerfTypePlayers(
|
|
a(href := routes.User.ratingDistribution(perfType.key))(strong(percentile, "%")),
|
|
a(href := routes.User.topNb(200, perfType.key))(perfType.trans)
|
|
)
|
|
} else {
|
|
trans.userIsBetterThanPercentOfPerfTypePlayers(
|
|
a(href := routes.User.show(u.username))(u.username),
|
|
a(href := routes.User.ratingDistribution(perfType.key))(strong(percentile, "%")),
|
|
a(href := routes.User.topNb(200, perfType.key))(perfType.trans)
|
|
)
|
|
}
|
|
)
|
|
}
|
|
),
|
|
p(
|
|
progressOverLastXGames(12),
|
|
" ",
|
|
span(cls := "progress")(
|
|
if (perf.progress > 0) tag("green")(dataIcon := "")(perf.progress)
|
|
else if (perf.progress < 0) tag("red")(dataIcon := "")(-perf.progress)
|
|
else "-"
|
|
),
|
|
". ",
|
|
ratingDeviation(
|
|
strong(
|
|
title := ratingDeviationTooltip.txt(
|
|
lila.rating.Glicko.provisionalDeviation,
|
|
lila.rating.Glicko.standardRankableDeviation,
|
|
lila.rating.Glicko.variantRankableDeviation
|
|
)
|
|
)(decimal(perf.glicko.deviation).toString)
|
|
)
|
|
)
|
|
)
|
|
|
|
private def pct(num: Int, denom: Int): String = {
|
|
(denom != 0) ?? s"${Math.round(num * 100.0 / denom)}%"
|
|
}
|
|
|
|
private def counter(count: lila.perfStat.Count)(implicit lang: Lang): Frag =
|
|
st.section(cls := "counter split")(
|
|
div(
|
|
table(
|
|
tbody(
|
|
tr(
|
|
th(totalGames()),
|
|
td(count.all),
|
|
td
|
|
),
|
|
tr(cls := "full")(
|
|
th(ratedGames()),
|
|
td(count.rated),
|
|
td(pct(count.rated, count.all))
|
|
),
|
|
tr(cls := "full")(
|
|
th(tournamentGames()),
|
|
td(count.tour),
|
|
td(pct(count.tour, count.all))
|
|
),
|
|
tr(cls := "full")(
|
|
th(berserkedGames()),
|
|
td(count.berserk),
|
|
td(pct(count.berserk, count.tour))
|
|
),
|
|
count.seconds > 0 option tr(cls := "full")(
|
|
th(timeSpentPlaying()),
|
|
td(colspan := "2")(showPeriod(count.period))
|
|
)
|
|
)
|
|
)
|
|
),
|
|
div(
|
|
table(
|
|
tbody(
|
|
tr(
|
|
th(averageOpponent()),
|
|
td(decimal(count.opAvg.avg).toString),
|
|
td
|
|
),
|
|
tr(cls := "full")(
|
|
th(victories()),
|
|
td(tag("green")(count.win)),
|
|
td(tag("green")(pct(count.win, count.all)))
|
|
),
|
|
tr(cls := "full")(
|
|
th(trans.draws()),
|
|
td(count.draw),
|
|
td(pct(count.draw, count.all))
|
|
),
|
|
tr(cls := "full")(
|
|
th(defeats()),
|
|
td(tag("red")(count.loss)),
|
|
td(tag("red")(pct(count.loss, count.all)))
|
|
),
|
|
tr(cls := "full")(
|
|
th(disconnections()),
|
|
td(if (count.disconnects > count.all * 100 / 15) tag("red") else emptyFrag)(count.disconnects),
|
|
td(pct(count.disconnects, count.all))
|
|
)
|
|
)
|
|
)
|
|
)
|
|
)
|
|
|
|
private def highlowSide(title: Frag => Frag, opt: Option[lila.perfStat.RatingAt], color: String)(implicit
|
|
lang: Lang
|
|
): Frag =
|
|
opt match {
|
|
case Some(r) =>
|
|
div(
|
|
h2(title(strong(tag(color)(r.int)))),
|
|
a(cls := "glpt", href := routes.Round.watcher(r.gameId, "white"))(absClientDateTime(r.at))
|
|
)
|
|
case None => div(h2(title(emptyFrag)), " ", span(notEnoughGames()))
|
|
}
|
|
|
|
private def highlow(stat: PerfStat)(implicit lang: Lang): Frag =
|
|
st.section(cls := "highlow split")(
|
|
highlowSide(highestRating(_), stat.highest, "green"),
|
|
highlowSide(lowestRating(_), stat.lowest, "red")
|
|
)
|
|
|
|
private def fromTo(s: lila.perfStat.Streak)(implicit lang: Lang): Frag =
|
|
s.from match {
|
|
case Some(from) =>
|
|
fromXToY(
|
|
a(cls := "glpt", href := routes.Round.watcher(from.gameId, "white"))(absClientDateTime(from.at)),
|
|
s.to match {
|
|
case Some(to) =>
|
|
a(cls := "glpt", href := routes.Round.watcher(to.gameId, "white"))(absClientDateTime(to.at))
|
|
case None => now()
|
|
}
|
|
)
|
|
case None => nbsp
|
|
}
|
|
|
|
private def resultStreakSideStreak(s: lila.perfStat.Streak, title: Frag => Frag, color: String)(implicit
|
|
lang: Lang
|
|
): Frag =
|
|
div(cls := "streak")(
|
|
h3(
|
|
title(
|
|
if (s.v > 0) tag(color)(trans.nbGames.plural(s.v, strong(s.v)))
|
|
else "-"
|
|
)
|
|
),
|
|
fromTo(s)
|
|
)
|
|
|
|
private def resultStreakSide(s: lila.perfStat.Streaks, title: Frag, color: String)(implicit
|
|
lang: Lang
|
|
): Frag =
|
|
div(
|
|
h2(title),
|
|
resultStreakSideStreak(s.max, longestStreak(_), color),
|
|
resultStreakSideStreak(s.cur, currentStreak(_), color)
|
|
)
|
|
|
|
private def resultStreak(streak: lila.perfStat.ResultStreak)(implicit lang: Lang): Frag =
|
|
st.section(cls := "resultStreak split")(
|
|
resultStreakSide(streak.win, winningStreak(), "green"),
|
|
resultStreakSide(streak.loss, losingStreak(), "red")
|
|
)
|
|
|
|
private def resultTable(results: lila.perfStat.Results, title: Frag, user: User)(implicit
|
|
lang: Lang
|
|
): Frag =
|
|
div(
|
|
table(
|
|
thead(
|
|
tr(
|
|
th(colspan := 2)(h2(title))
|
|
)
|
|
),
|
|
tbody(
|
|
results.results map { r =>
|
|
tr(
|
|
td(userIdLink(r.opId.value.some, withOnline = false), " (", r.opInt, ")"),
|
|
td(
|
|
a(cls := "glpt", href := s"${routes.Round.watcher(r.gameId, "white")}?pov=${user.username}")(
|
|
absClientDateTime(r.at)
|
|
)
|
|
)
|
|
)
|
|
}
|
|
)
|
|
)
|
|
)
|
|
|
|
private def result(stat: PerfStat, user: User)(implicit lang: Lang): Frag =
|
|
st.section(cls := "result split")(
|
|
resultTable(stat.bestWins, bestRated(), user),
|
|
resultTable(stat.worstLosses, worstRated(), user)
|
|
)
|
|
|
|
private def playStreakNbStreak(s: lila.perfStat.Streak, title: Frag => Frag)(implicit lang: Lang): Frag =
|
|
div(
|
|
div(cls := "streak")(
|
|
h3(
|
|
title(
|
|
if (s.v > 0) trans.nbGames.plural(s.v, strong(s.v))
|
|
else "-"
|
|
)
|
|
),
|
|
fromTo(s)
|
|
)
|
|
)
|
|
|
|
private def playStreakNbStreaks(streaks: lila.perfStat.Streaks)(implicit lang: Lang): Frag =
|
|
div(cls := "split")(
|
|
playStreakNbStreak(streaks.max, longestStreak(_)),
|
|
playStreakNbStreak(streaks.cur, currentStreak(_))
|
|
)
|
|
|
|
private def playStreakNb(playStreak: lila.perfStat.PlayStreak)(implicit lang: Lang): Frag =
|
|
st.section(cls := "playStreak")(
|
|
h2(span(title := lessThanOneHour.txt())(gamesInARow())),
|
|
playStreakNbStreaks(playStreak.nb)
|
|
)
|
|
|
|
private def playStreakTimeStreak(s: lila.perfStat.Streak, title: Frag => Frag)(implicit lang: Lang): Frag =
|
|
div(
|
|
div(cls := "streak")(
|
|
h3(title(showPeriod(s.period))),
|
|
fromTo(s)
|
|
)
|
|
)
|
|
|
|
private def playStreakTimeStreaks(streaks: lila.perfStat.Streaks)(implicit lang: Lang): Frag =
|
|
div(cls := "split")(
|
|
playStreakTimeStreak(streaks.max, longestStreak(_)),
|
|
playStreakTimeStreak(streaks.cur, currentStreak(_))
|
|
)
|
|
|
|
private def playStreakTime(playStreak: lila.perfStat.PlayStreak)(implicit lang: Lang): Frag =
|
|
st.section(cls := "playStreak")(
|
|
h2(span(title := lessThanOneHour.txt())(maxTimePlaying())),
|
|
playStreakTimeStreaks(playStreak.time)
|
|
)
|
|
}
|