more template rewrite

pull/4742/head
Thibault Duplessis 2018-12-03 08:07:16 +07:00
parent 55e6cdd896
commit 0e228891e8
17 changed files with 224 additions and 163 deletions

View File

@ -18,6 +18,9 @@ trait I18nHelper {
def i18nJsObject(keys: I18nKey*)(implicit lang: Lang): JsObject =
JsDump.keysToObject(keys, I18nDb.Site, lang)
def i18nJsObject(keys: List[I18nKey])(implicit lang: Lang): JsObject =
JsDump.keysToObject(keys, I18nDb.Site, lang)
def i18nOptionJsObject(keys: Option[I18nKey]*)(implicit lang: Lang): JsObject =
JsDump.keysToObject(keys.flatten, I18nDb.Site, lang)

View File

@ -245,20 +245,19 @@ trait UserHelper { self: I18nHelper with StringHelper with HtmlHelper with Numbe
private def userHref(username: String, params: String = "") =
s"""href="${routes.User.show(username)}$params""""
private def addClass(cls: Option[String]) = cls.fold("")(" " + _)
protected def userClass(
userId: String,
cssClass: Option[String],
withOnline: Boolean,
withPowerTip: Boolean = true
) = {
"user_link" :: List(
cssClass,
withPowerTip option "ulpt",
withOnline option {
if (isOnline(userId)) "online" else "offline"
}
).flatten
}.mkString("class=\"", " ", "\"")
): String = {
val online = if (withOnline) {
if (isOnline(userId)) " online" else " offline"
} else ""
s"""class="user_link${addClass(cssClass)}${addClass(withPowerTip option "ulpt")}$online""""
}
def userGameFilterTitle(u: User, nbs: UserInfo.NbGames, filter: GameFilter)(implicit ctx: UserContext) =
splitNumber(userGameFilterTitleNoTag(u, nbs, filter))

View File

@ -21,6 +21,7 @@ trait Scalatags {
implicit val playCallAttr = genericAttr[play.api.mvc.Call]
lazy val dataIcon = attr("data-icon")
lazy val dataHint = attr("data-hint")
implicit val charAttr = genericAttr[Char]
@ -32,9 +33,9 @@ trait Scalatags {
}
}
/* for class maps such as Map("foo" -> true, "active" -> isActive) */
implicit val stringMapAttr = new AttrValue[Map[String, Boolean]] {
def apply(t: scalatags.text.Builder, a: Attr, m: Map[String, Boolean]): Unit = {
/* for class maps such as List("foo" -> true, "active" -> isActive) */
implicit val classesAttr = new AttrValue[List[(String, Boolean)]] {
def apply(t: scalatags.text.Builder, a: Attr, m: List[(String, Boolean)]): Unit = {
val cls = m collect { case (s, true) => s } mkString " "
if (cls.nonEmpty) t.setAttr(a.name, scalatags.text.Builder.GenericAttrValueSource(cls))
}

View File

@ -43,7 +43,7 @@ tablebaseEndpoint: "@tablebaseEndpoint"
@analyse.layout(
title = title,
side = views.html.game.side(pov, initialFen, none, simul = simul, userTv = userTv, bookmarked = bookmarked).some,
side = Some(views.html.game.side(pov, initialFen, none, simul = simul, userTv = userTv, bookmarked = bookmarked)),
chat = chat.html.some,
underchat = underchat.some,
moreCss = moreCss,

View File

@ -6,7 +6,7 @@
@analyse.layout(
title = title,
side = views.html.game.side(pov, initialFen, none, simul = simul, bookmarked = false).some,
side = Some(views.html.game.side(pov, initialFen, none, simul = simul, bookmarked = false)),
chat = none,
underchat = views.html.game.bits.watchers.some,
moreCss = cssTag("analyse.css"),

View File

@ -0,0 +1,27 @@
package views.html
import scalatags.Text.all._
import lila.api.Context
import lila.app.templating.Environment._
import lila.i18n.{ I18nKeys => trans }
import controllers.routes
object bookmark {
def toggle(g: lila.game.Game, bookmarked: Boolean)(implicit ctx: Context) = ctx.me map { m =>
a(cls := List(
"bookmark" -> true,
"bookmarked" -> bookmarked,
"hint--top" -> true
), href := routes.Bookmark.toggle(g.id), dataHint := trans.bookmarkThisGame.txt())(
span(dataIcon := "t", cls := "on is3")(g.showBookmarks),
span(dataIcon := "s", cls := "off is3")(g.showBookmarks)
)
} orElse {
g.hasBookmarks option span(cls := "bookmark")(
span(dataIcon := "s", cls := "is3")(g.showBookmarks)
)
}
}

View File

@ -1,14 +0,0 @@
@(g: Game, bookmarked: Boolean)(implicit ctx: Context)
@ctx.me.map { m =>
<a class="bookmark@if(bookmarked){ bookmarked} hint--top" href="@routes.Bookmark.toggle(g.id)" data-hint="@trans.bookmarkThisGame()">
<span data-icon="t" class="on is3">@g.showBookmarks</span>
<span data-icon="s" class="off is3">@g.showBookmarks</span>
</a>
}.getOrElse {
@if(g.hasBookmarks) {
<span class="bookmark">
<span data-icon="s" class="is3">@g.showBookmarks</span>
</span>
}
}

View File

@ -25,4 +25,13 @@ object bits {
def watchers(implicit ctx: Context) = Html {
s"""<div class="watchers hidden"><span class="number">&nbsp;</span> ${trans.spectators.txt().replace(":", "")} <span class="list inline_userlist"></span></div>"""
}
def gameIcon(game: lila.game.Game): Char = game.perfType match {
case _ if game.fromPosition => '*'
case _ if game.imported => '/'
case Some(p) if game.variant.exotic => p.iconChar
case _ if game.hasAi => 'n'
case Some(p) => p.iconChar
case _ => '8'
}
}

View File

@ -1,9 +0,0 @@
@(game: Game)(implicit ctx: Context)
@game.perfType match {
case _ if game.fromPosition => {*}
case _ if game.imported => {/}
case Some(p) if game.variant.exotic => {@p.iconChar}
case _ if game.hasAi => {n}
case Some(p) => {@p.iconChar}
case _ => {8}
}

View File

@ -0,0 +1,148 @@
package views.html
package game
import scalatags.Text.all._
import lila.api.Context
import lila.app.templating.Environment._
import lila.i18n.{ I18nKeys => trans }
import controllers.routes
object side {
private val separator = " • "
def apply(
pov: lila.game.Pov,
initialFen: Option[chess.format.FEN],
tour: Option[lila.tournament.Tournament],
simul: Option[lila.simul.Simul],
userTv: Option[lila.user.User] = None,
bookmarked: Boolean
)(implicit ctx: Context) = {
import pov._
div(cls := "side")(
div(cls := "side_box padded")(
div(cls := "game_infos", dataIcon := bits.gameIcon(game))(
div(cls := "header")(
span(cls := "setup")(
views.html.bookmark.toggle(game, bookmarked),
if (game.imported) frag(
a(cls := "hint--top", href := routes.Importer.importGame, dataHint := trans.importGame.txt())("IMPORT"),
separator,
if (game.variant.exotic)
variantLink(game.variant, (if (game.variant == chess.variant.KingOfTheHill) game.variant.shortName else game.variant.name).toUpperCase, cssClass = "hint--top", initialFen = initialFen)
else
game.variant.name.toUpperCase
)
else frag(
game.clock.map { clock =>
frag(clock.config.show)
} getOrElse {
game.daysPerTurn.map { days =>
span(cls := "hint--top", dataHint := trans.correspondence.txt())(
if (days == 1) trans.oneDay() else trans.nbDays.pluralSame(days)
)
}.getOrElse {
span(cls := "hint--top", dataHint := trans.unlimited.txt())("∞")
}
},
separator,
if (game.rated) trans.rated.txt() else trans.casual.txt(),
separator,
if (game.variant.exotic)
variantLink(game.variant, (if (game.variant == chess.variant.KingOfTheHill) game.variant.shortName else game.variant.name).toUpperCase, cssClass = "hint--top", initialFen = initialFen)
else
game.perfType.map { pt =>
span(cls := "hint--top", dataHint := pt.title)(pt.shortName)
}
)
),
game.pgnImport.flatMap(_.date).map(frag(_)) getOrElse {
frag(if (game.isBeingPlayed) trans.playingRightNow() else momentFromNow(game.createdAt))
}
),
game.pgnImport.flatMap(_.date).map { date =>
frag(
"Imported",
game.pgnImport.flatMap(_.user).map { user =>
frag(
"by ",
userIdLink(user.some, None, false),
br
)
}
)
}
),
div(cls := "players")(
game.players.map { p =>
div(cls := s"player color-icon is ${p.color.name} text")(
playerLink(p, withOnline = false, withDiff = true, withBerserk = true)
)
}
),
game.finishedOrAborted option {
div(cls := "status")(
gameEndStatus(game),
game.winner.map { winner =>
frag(
separator,
winner.color.fold(trans.whiteIsVictorious(), trans.blackIsVictorious())
)
}
)
},
initialFen.ifTrue(game.variant.chess960).map(_.value).flatMap {
chess.variant.Chess960.positionNumber
}.map { number =>
frag(
"Chess960 start position: ",
strong(number)
)
}
),
game.userIds.filter(isStreaming).map { id =>
a(cls := "context-streamer text side_box", dataIcon := "", href := routes.Streamer.show(id))(
usernameOrId(id),
" is streaming"
)
},
userTv.map { u =>
div(cls := "side_box")(
h2(cls := "top user_tv text", attr("data-user-tv") := u.id, dataIcon := "1")(u.titleUsername)
)
} orElse {
lila.common.HTTPRequest.isMobile(ctx.req) option
a(cls := "side_box text deep_link", dataIcon := "", href := "lichess://analyse/@pov.gameId")(
"Open with ",
strong("Mobile app")
)
},
tour.map { t =>
div(cls := "game_tournament side_box no_padding scroll-shadow-soft")(
p(cls := "top text", dataIcon := "g")(a(href := routes.Tournament.show(t.id))(t.fullName)),
div(cls := "clock", attr("data-time") := t.secondsToFinish)(
div(cls := "time")(t.clockStatus)
)
)
} orElse {
game.tournamentId map { tourId =>
div(cls := "game_tournament side_box no_padding")(
p(cls := "top text", dataIcon := "g")(a(href := routes.Tournament.show(tourId))(tournamentIdToName(tourId)))
)
}
},
simul.map { sim =>
div(cls := "game_simul side_box no_padding")(
p(cls := "top text", dataIcon := "|")(a(href := routes.Simul.show(sim.id))(sim.fullName))
)
}
)
}
}

View File

@ -1,104 +0,0 @@
@(pov: Pov, initialFen: Option[chess.format.FEN], tour: Option[lila.tournament.Tournament], simul: Option[lila.simul.Simul], userTv: Option[User] = None, bookmarked: Boolean)(implicit ctx: Context)
@import pov._
<div class="side">
<div class="side_box padded">
<div class="game_infos" data-icon="@gameIcon(game)">
<div class="header">
<span class="setup">
@bookmark.toggle(game, bookmarked)
@if(game.imported) {
<a class="hint--top" href="@routes.Importer.importGame" data-hint="@trans.importGame()">IMPORT</a>
@if(game.variant.exotic) {
@variantLink(game.variant, (if (game.variant == chess.variant.KingOfTheHill) game.variant.shortName else game.variant.name).toUpperCase, cssClass = "hint--top", initialFen = initialFen)
} else {
@game.variant.name.toUpperCase
}
} else {
@game.clock.map(_.config.show).getOrElse {
@game.daysPerTurn.map { days =>
<span data-hint="@trans.correspondence()" class="hint--top">@if(days == 1){@trans.oneDay()}else{@trans.nbDays.pluralSame(days)}</span>
}.getOrElse {
<span data-hint="@trans.unlimited()" class="hint--top"></span>
}
} • @if(game.rated){@trans.rated.txt()}else{@trans.casual.txt()} •
@if(game.variant.exotic) {
@variantLink(game.variant, (if (game.variant == chess.variant.KingOfTheHill) game.variant.shortName else game.variant.name).toUpperCase, cssClass = "hint--top", initialFen = initialFen)
} else {
@game.perfType.map { pt => <span class="hint--top" data-hint="@pt.title">@pt.shortName</span> }
}
}
</span>
@game.pgnImport.flatMap(_.date).getOrElse(
if (game.isBeingPlayed) trans.playingRightNow() else momentFromNow(game.createdAt)
)
</div>
@game.pgnImport.flatMap(_.date).map { date =>
Imported
@game.pgnImport.flatMap(_.user).map { user =>
by @userIdLink(user.some, None, false)
<br />
}
}
</div>
<div class="players">
@List(game.whitePlayer, game.blackPlayer).map { p =>
<div class="player color-icon is @{p.color.name} text">
@playerLink(p, withOnline = false, withDiff = true, withBerserk = true)
</div>
}
</div>
@if(game.finishedOrAborted) {
<div class="status">
@gameEndStatus(game)
@game.winner.map { winner =>
• @winner.color.fold(trans.whiteIsVictorious(), trans.blackIsVictorious())
}
</div>
}
@if(game.variant.chess960) {
@initialFen.map { fen =>
@chess.variant.Chess960.positionNumber(fen.value).map { number =>
Chess960 start position: <strong>@number</strong>
}
}
}
</div>
@game.userIds.filter(isStreaming).map { id =>
<a href="@routes.Streamer.show(id)" class="context-streamer text side_box" data-icon="">@usernameOrId(id) is streaming</a>
}
@userTv.map { u =>
<div class="side_box">
<h2 class="top user_tv text" data-user-tv="@u.id" data-icon="1">@u.titleUsername</h2>
</div>
}.getOrElse {
@if(lila.common.HTTPRequest.isMobile(ctx.req)) {
<a class="side_box text deep_link" data-icon="" href="lichess://analyse/@pov.gameId">Open with <strong>Mobile app</strong></a>
}
}
@tour.map { t =>
<div class="game_tournament side_box no_padding scroll-shadow-soft">
<p class="top text" data-icon="g"><a href="@routes.Tournament.show(t.id)">@t.fullName</a></p>
<div class="clock" data-time="@t.secondsToFinish">
<div class="time">@t.clockStatus</div>
</div>
</div>
}.getOrElse {
@game.tournamentId.map { tourId =>
<div class="game_tournament side_box no_padding">
<p class="top text" data-icon="g"><a href="@routes.Tournament.show(tourId)">@tournamentIdToName(tourId)</a></p>
</div>
}
}
@simul.map { sim =>
<div class="game_simul side_box no_padding">
<p class="top text" data-icon="|"><a href="@routes.Simul.show(sim.id)">@sim.fullName</a></p>
</div>
}
</div>

View File

@ -31,7 +31,7 @@
@defining(fromPlayer | g.firstPlayer ) { firstPlayer =>
@gameFen(Pov(g, firstPlayer), ownerLink, withTitle = false)
<a class="game_link_overlay" href="@gameLink(g, firstPlayer.color, ownerLink)"></a>
<div class="infos" data-icon="@gameIcon(g)">
<div class="infos" data-icon="@bits.gameIcon(g)">
<div class="header">
<strong>
@if(g.imported) {

View File

@ -20,7 +20,9 @@ object bits {
attr("data-lastmove") := lastMove
)(miniBoardContent)
def jsI18n(implicit context: Context) = toJson(i18nJsObject(
def jsI18n(implicit context: Context) = toJson(i18nJsObject(translations))
private val translations = List(
trans.training,
trans.yourPuzzleRatingX,
trans.goodMove,
@ -66,5 +68,5 @@ object bits {
trans.gameOver,
trans.inLocalBrowser,
trans.toggleLocalEvaluation
))
)
}

View File

@ -21,7 +21,7 @@ object message {
views.html.base.layout(title = title, moreCss = ~moreCss) {
div(cls := "content_box small_box")(
div(cls := "head")(
h1(cls := Map("text" -> icon.isDefined), dataIcon := icon)(title)
h1(cls := List("text" -> icon.isDefined), dataIcon := icon)(title)
),
br, br,
p(message),

View File

@ -50,7 +50,7 @@ object variant {
menu = Some(frag(
lila.rating.PerfType.variants map { pt =>
a(
cls := Map("text" -> true, "active" -> active.has(pt)),
cls := List("text" -> true, "active" -> active.has(pt)),
href := routes.Page.variant(pt.key),
dataIcon := pt.iconChar
)(pt.name)

View File

@ -57,14 +57,14 @@ object side {
}
)
},
verdicts.relevant option div(dataIcon := "7", cls := Map(
verdicts.relevant option div(dataIcon := "7", cls := List(
"game_infos conditions" -> true,
"accepted" -> (ctx.isAuth && verdicts.accepted),
"refused" -> (ctx.isAuth && !verdicts.accepted)
))(
(verdicts.list.size < 2) option p(trans.conditionOfEntry()),
verdicts.list map { v =>
p(cls := Map(
p(cls := List(
"condition text" -> true,
"accepted" -> v.verdict.accepted,
"refused" -> !v.verdict.accepted

View File

@ -67,19 +67,18 @@ case class ContentSecurityPolicy(
def withPrismic(editor: Boolean): ContentSecurityPolicy = withPrismicEditor(editor).withTwitter
override def toString: String =
List(
"default-src " -> defaultSrc,
"connect-src " -> connectSrc,
"style-src " -> styleSrc,
"font-src " -> fontSrc,
"frame-src " -> frameSrc,
"worker-src " -> workerSrc,
"img-src " -> imgSrc,
"script-src " -> scriptSrc,
"base-uri " -> baseUri
) collect {
case (directive, sources) if sources.nonEmpty =>
sources.mkString(directive, " ", ";")
} mkString (" ")
override def toString: String = List(
"default-src " -> defaultSrc,
"connect-src " -> connectSrc,
"style-src " -> styleSrc,
"font-src " -> fontSrc,
"frame-src " -> frameSrc,
"worker-src " -> workerSrc,
"img-src " -> imgSrc,
"script-src " -> scriptSrc,
"base-uri " -> baseUri
) collect {
case (directive, sources) if sources.nonEmpty =>
sources.mkString(directive, " ", ";")
} mkString " "
}