rewrite homepage with scalatags

pull/4742/head
Thibault Duplessis 2018-12-03 13:37:47 +07:00
parent 5dd76fa631
commit e1bf05c682
12 changed files with 256 additions and 216 deletions

View File

@ -121,4 +121,5 @@ trait AssetHelper { self: I18nHelper with SecurityHelper =>
}
def embedJs(js: Html)(implicit ctx: Context): Html = embedJsUnsafe(js.body)
def embedJs(js: String)(implicit ctx: Context): Html = embedJsUnsafe(js)
}

View File

@ -61,8 +61,9 @@ object Environment
def reportNbOpen: Int =
lila.report.Env.current.api.nbOpen.awaitOrElse(10.millis, 0)
def NotForKids[Html](f: => Html)(implicit ctx: lila.api.Context) =
if (ctx.kid) emptyHtml else f
def NotForKids(f: => Html)(implicit ctx: lila.api.Context) = if (ctx.kid) emptyHtml else f
def NotForKids(f: => scalatags.Text.all.Frag)(implicit ctx: lila.api.Context) = if (ctx.kid) emptyFrag else f
def signalBars(v: Int) = Html {
val bars = (1 to 4).map { b =>

View File

@ -94,4 +94,29 @@ object bits {
)
)
)
def currentGameInfo(current: lila.app.mashup.Preload.CurrentGame)(implicit ctx: Context) =
div(id := "lobby_current_game")(
h2("Hang on!"),
p("You have a game in progress with ", strong(current.opponent), "."),
br, br,
a(cls := "big text button", dataIcon := "G", href := routes.Round.player(current.pov.fullId))("Join the game"),
br, br,
"or",
br, br,
form(action := routes.Round.resign(current.pov.fullId), method := "post")(
button(cls := "big text button", dataIcon := "L")(
if (current.pov.game.abortable) "Abort" else "Resign", " the game"
)
),
br,
p("You can't start a new game until this one is finished."),
br, br,
p(
"If you want to play several games simultaneously,",
br,
a(href := routes.Simul.home)("create a simultaneous exhibition event"),
"!"
)
)
}

View File

@ -1,27 +0,0 @@
@(current: lila.app.mashup.Preload.CurrentGame)(implicit ctx: Context)
<div id="lobby_current_game">
<h2>Hang on!</h2>
<p>You have a game in progress with <strong>@current.opponent</strong>.</p>
<br />
<br />
<a class="big text button" data-icon="G" href="@routes.Round.player(current.pov.fullId)">Join the game</a>
<br />
<br />
or
<br />
<br />
<form action="@routes.Round.resign(current.pov.fullId)" method="post">
<button class="big text button" data-icon="L">
@if(current.pov.game.abortable) { Abort } else { Resign} the game
</button>
</form>
<br />
<p>You can't start a new game until this one is finished.</p>
<br />
<br />
<p>
If you want to play several games simultaneously,<br />
<a href="@routes.Simul.home">create a simultaneous exhibition event</a>!
</p>
</div>

View File

@ -0,0 +1,192 @@
package views.html.lobby
import play.api.libs.json.Json
import play.twirl.api.Html
import scalatags.Text.all._
import lila.api.Context
import lila.app.templating.Environment._
import lila.common.HTTPRequest
import lila.common.String.html.{ safeJson, safeJsonValue }
import lila.game.Pov
import lila.i18n.{ I18nKeys => trans }
import controllers.routes
object home {
def apply(
data: play.api.libs.json.JsObject,
userTimeline: Vector[lila.timeline.Entry],
forumRecent: List[lila.forum.MiniForumPost],
tours: List[lila.tournament.Tournament],
events: List[lila.event.Event],
simuls: List[lila.simul.Simul],
featured: Option[lila.game.Game],
leaderboard: List[lila.user.User.LightPerf],
tournamentWinners: List[lila.tournament.Winner],
puzzle: Option[lila.puzzle.DailyPuzzle],
streams: lila.streamer.LiveStreams.WithTitles,
lastPost: List[lila.blog.MiniPost],
playban: Option[lila.playban.TempBan],
currentGame: Option[lila.app.mashup.Preload.CurrentGame],
nbRounds: Int
)(implicit ctx: Context) = views.html.base.layout(
title = "",
fullTitle = Some("lichess.org • " + trans.freeOnlineChess.txt()),
baseline = Some(frag(
a(id := "nb_connected_players", href := routes.User.list)(trans.nbPlayers(nbPlayersPlaceholder)),
a(id := "nb_games_in_play", href := routes.Tv.games)(
trans.nbGamesInPlay.plural(nbRounds, Html(s"<span>${nbRounds}</span>"))
),
ctx.isMobileBrowser option {
if (HTTPRequest isAndroid ctx.req) views.html.mobile.bits.googlePlayButton
else if (HTTPRequest isIOS ctx.req) views.html.mobile.bits.appleStoreButton
else emptyFrag
}
)),
side = Some(frag(
NotForKids { div(id := "streams_on_air")(views.html.streamer liveStreams streams) },
events map { views.html.event.homepageSpotlight(_) },
!ctx.isBot option frag(
lila.tournament.Spotlight.select(tours, ctx.me, 3) map { views.html.tournament.homepageSpotlight(_) },
simuls.find(_.spotlightable) take 2 map { views.html.simul.homepageSpotlight(_) } toList
),
ctx.me map { u =>
div(id := "timeline", attr("data-href") := routes.Timeline.home)(
views.html.timeline entries userTimeline,
div(cls := "links")(
userTimeline.size >= 8 option
a(cls := "more", href := routes.Timeline.home)(trans.more(), " »")
)
)
} getOrElse {
div(cls := "about-side")(
trans.xIsAFreeYLibreOpenSourceChessServer("Lichess", Html(s"""<a class="blue" href="${routes.Plan.features}">${trans.really.txt()}</a>""")),
a(cls := "blue", href := "/about")(trans.aboutX("lichess.org"), "...")
)
}
)),
moreJs = frag(
jsAt(s"compiled/lichess.lobby${isProd ?? (".min")}.js", async = true),
embedJs(s"""window.customWS = true;
lichess_lobby = {
data: ${safeJsonValue(data)},
playban: ${htmlOrNull(playban)(pb => safeJson(Json.obj("minutes" -> pb.mins, "remainingSeconds" -> (pb.remainingSeconds + 3))))},
currentGame: ${htmlOrNull(currentGame)(cg => safeJson(cg.json))},
i18n: ${safeJsonValue(i18nJsObject(translations))},
};""")
),
moreCss = cssTag("home.css"),
underchat = Some(frag(
div(id := "featured_game")(
featured map { g =>
frag(
gameFen(Pov first g, tv = true),
views.html.game.bits.vstext(Pov first g)(ctx.some)
)
}
)
)),
chessground = false,
openGraph = lila.app.ui.OpenGraph(
image = staticUrl("images/large_tile.png").some,
title = "The best free, adless Chess server",
url = netBaseUrl,
description = trans.siteDescription.txt()
).some,
asyncJs = true
) {
frag(
div(cls := List(
"lobby_and_ground" -> true,
"playban" -> playban.isDefined,
"current_game" -> currentGame.isDefined
))(
currentGame map { bits.currentGameInfo(_) },
div(id := "hooks_wrap"),
playban.map(ban => playbanInfo(ban.remainingSeconds)),
div(id := "start_buttons", cls := "lichess_ground")(
a(href := routes.Setup.hookForm, cls := List(
"fat button config_hook" -> true,
"disabled" -> (playban.isDefined || currentGame.isDefined || ctx.isBot)
), trans.createAGame()),
a(href := routes.Setup.friendForm(none), cls := List(
"fat button config_friend" -> true,
"disabled" -> currentGame.isDefined
), trans.playWithAFriend()),
a(href := routes.Setup.aiForm, cls := List(
"fat button config_ai" -> true,
"disabled" -> currentGame.isDefined
), trans.playWithTheMachine())
)
),
puzzle map { p =>
div(id := "daily_puzzle", title := trans.clickToSolve.txt())(
raw(p.html),
div(cls := "vstext")(
trans.puzzleOfTheDay(),
br,
p.color.fold(trans.whitePlays, trans.blackPlays)()
)
)
},
!ctx.isBot option bits.underboards(tours, simuls, leaderboard, tournamentWinners),
NotForKids {
div(cls := "new_posts undertable")(
div(cls := "undertable_top")(
a(cls := "more", href := routes.ForumCateg.index)(trans.more(), " »"),
span(cls := "title text", dataIcon := "d")(trans.latestForumPosts())
),
div(cls := "undertable_inner scroll-shadow-hard")(
div(cls := "content")(views.html.forum.post recent forumRecent)
)
)
},
bits.lastPosts(lastPost),
div(cls := "donation undertable")(
a(href := routes.Plan.index)(
i(dataIcon := patronIconChar),
strong("Lichess Patron"),
span(trans.directlySupportLichess())
),
a(href := routes.Page.swag)(
i(dataIcon := ""),
strong("Swag Store"),
span(trans.playChessInStyle())
)
),
div(cls := "about-footer")(a(href := "/about")(trans.aboutX("lichess.org")))
)
}
private val translations = List(
trans.realTime,
trans.correspondence,
trans.nbGamesInPlay,
trans.player,
trans.time,
trans.joinTheGame,
trans.cancel,
trans.casual,
trans.rated,
trans.variant,
trans.mode,
trans.list,
trans.graph,
trans.filterGames,
trans.youNeedAnAccountToDoThat,
trans.oneDay,
trans.nbDays,
trans.aiNameLevelAiLevel,
trans.yourTurn,
trans.rating,
trans.createAGame,
trans.quickPairing,
trans.lobby,
trans.custom,
trans.anonymous
)
private val nbPlayersPlaceholder = Html("<strong>-,---</strong>")
}

View File

@ -1,142 +0,0 @@
@(data: play.api.libs.json.JsObject, userTimeline: Vector[lila.timeline.Entry], forumRecent: List[lila.forum.MiniForumPost], tours: List[Tournament], events: List[lila.event.Event], simuls: List[lila.simul.Simul], featured: Option[Game], leaderboard: List[User.LightPerf], tournamentWinners: List[lila.tournament.Winner], puzzle: Option[lila.puzzle.DailyPuzzle], streams: lila.streamer.LiveStreams.WithTitles, lastPost: List[lila.blog.MiniPost], playban: Option[lila.playban.TempBan], currentGame: Option[lila.app.mashup.Preload.CurrentGame], nbRounds: Int)(implicit ctx: Context)
@import play.api.libs.json.Json
@underchat = {
<div id="featured_game">
@featured.map { g =>
@gameFen(Pov.first(g), tv = true)
@game.bits.vstext(Pov.first(g))(ctx.some).toHtml
}
</div>
}
@side = {
@NotForKids {
<div id="streams_on_air">@streamer.liveStreams(streams)</div>
}
@events.map { e =>
@event.homepageSpotlight(e)
}
@if(!ctx.isBot) {
@lila.tournament.Spotlight.select(tours, ctx.me, 3).map { tour =>
@tournament.homepageSpotlight(tour)
}
@simuls.find(_.spotlightable).take(2).map { s =>
@simul.homepageSpotlight(s)
}
}
@ctx.me.map { u =>
<div id="timeline" data-href="@routes.Timeline.home">
@timeline.entries(userTimeline)
<div class="links">
@if(userTimeline.size >= 8) {
<a class="more" href="@routes.Timeline.home">@trans.more() »</a>
}
</div>
</div>
}.getOrElse {
<div class="about-side">
@trans.xIsAFreeYLibreOpenSourceChessServer("Lichess", Html(s"""<a class="blue" href="${routes.Plan.features}">${trans.really.txt()}</a>"""))
<a class="blue" href="/about">@trans.aboutX("lichess.org")...</a>
</div>
}
}
@baseline = {
<a id="nb_connected_players" href="@routes.User.list">
@trans.nbPlayers(Html("<strong>-,---</strong>"))
</a>
<a id="nb_games_in_play" href="@routes.Tv.games">
@trans.nbGamesInPlay.plural(nbRounds, Html(s"<span>${nbRounds}</span>"))
</a>
@if(ctx.isMobileBrowser) {
@if(lila.common.HTTPRequest.isAndroid(ctx.req)) {
@mobile.googlePlayButton()
} else {
@if(lila.common.HTTPRequest.isIOS(ctx.req)) {
@mobile.appleStoreButton()
}
}
}
}
@moreJs = {
@jsAt(s"compiled/lichess.lobby${isProd??(".min")}.js", async = true)
@embedJs {
window.customWS = true;
lichess_lobby = {
data: @safeJson(data),
playban: @htmlOrNull(playban) { pb =>
@safeJson(Json.obj("minutes" -> pb.mins, "remainingSeconds" -> (pb.remainingSeconds + 3)))
},
currentGame: @htmlOrNull(currentGame) { cg =>
@safeJson(cg.json)
},
i18n: @jsI18n()
};
}
}
@base.layout(
title = "",
fullTitle = Some("lichess.org • " + trans.freeOnlineChess.txt()),
baseline = baseline.some,
side = side.some,
moreJs = moreJs,
moreCss = cssTag("home.css"),
underchat = underchat.some,
chessground = false,
openGraph = lila.app.ui.OpenGraph(
image = staticUrl("images/large_tile.png").some,
title = "The best free, adless Chess server",
url = netBaseUrl,
description = trans.siteDescription.txt()
).some,
asyncJs = true) {
<div class="lobby_and_ground@if(playban.isDefined){ playban}@if(currentGame.isDefined){ current_game}">
@currentGame.map(currentGameInfo(_))
<div id="hooks_wrap"></div>
@playban.map(ban => playbanInfo(ban.remainingSeconds))
<div id="start_buttons" class="lichess_ground">
<a class="fat button config_hook@if(playban.isDefined || currentGame.isDefined || ctx.isBot){ disabled}" href="@routes.Setup.hookForm">@trans.createAGame()</a>
<a class="fat button config_friend@if(currentGame.isDefined){ disabled}" href="@routes.Setup.friendForm(none)">@trans.playWithAFriend()</a>
<a class="fat button config_ai@if(currentGame.isDefined){ disabled}" href="@routes.Setup.aiForm">@trans.playWithTheMachine()</a>
</div>
</div>
@puzzle.map { p =>
<div id="daily_puzzle" title="@trans.clickToSolve()">
@Html(p.html)
<div class="vstext">
@trans.puzzleOfTheDay()<br />
@p.color.fold(trans.whitePlays, trans.blackPlays)()
</div>
</div>
}
@if(!ctx.isBot) {@bits.underboards(tours, simuls, leaderboard, tournamentWinners).toHtml}
@NotForKids {
<div class="new_posts undertable">
<div class="undertable_top">
<a class="more" href="@routes.ForumCateg.index()">@trans.more() »</a>
<span class="title" data-icon="d"> @trans.latestForumPosts()</span>
</div>
<div class="undertable_inner scroll-shadow-hard">
<div class="content">@forum.post.recent(forumRecent).toHtml</div>
</div>
</div>
}
@bits.lastPosts(lastPost).map(_.toHtml)
<div class="donation undertable">
<a href="@routes.Plan.index">
<i data-icon="@patronIconChar"></i>
<strong>Lichess Patron</strong>
<span>@trans.directlySupportLichess()</span>
</a>
<a href="@routes.Page.swag">
<i data-icon=""></i>
<strong>Swag Store</strong>
<span>@trans.playChessInStyle()</span>
</a>
</div>
<div class="about-footer"><a href="/about">@trans.aboutX("lichess.org")</a></div>
}

View File

@ -1,28 +0,0 @@
@()(implicit ctx: Context)
@safeJson(i18nJsObject(
trans.realTime,
trans.correspondence,
trans.nbGamesInPlay,
trans.player,
trans.time,
trans.joinTheGame,
trans.cancel,
trans.casual,
trans.rated,
trans.variant,
trans.mode,
trans.list,
trans.graph,
trans.filterGames,
trans.youNeedAnAccountToDoThat,
trans.oneDay,
trans.nbDays,
trans.aiNameLevelAiLevel,
trans.yourTurn,
trans.rating,
trans.createAGame,
trans.quickPairing,
trans.lobby,
trans.custom,
trans.anonymous
))

View File

@ -1,7 +0,0 @@
<a class="store"
href="https://itunes.apple.com/us/app/lichess-free-online-chess/id968371784">
<img alt="Download on the Apple App Store"
width="172"
height="50"
src="https://upload.wikimedia.org/wikipedia/commons/3/3c/Download_on_the_App_Store_Badge.svg" />
</a>

View File

@ -0,0 +1,32 @@
package views.html.mobile
import scalatags.Text.all._
import lila.api.Context
import lila.app.templating.Environment._
import lila.i18n.{ I18nKeys => trans }
import controllers.routes
object bits {
lazy val appleStoreButton = raw("""
<a class="store"
href="https://itunes.apple.com/us/app/lichess-free-online-chess/id968371784">
<img alt="Download on the Apple App Store"
width="172"
height="50"
src="https://upload.wikimedia.org/wikipedia/commons/3/3c/Download_on_the_App_Store_Badge.svg" />
</a>
""")
lazy val googlePlayButton = raw("""
<a class="store"
href="https://play.google.com/store/apps/details?id=org.lichess.mobileapp">
<img alt="Android app on Google Play"
width="192"
height="74"
src="https://play.google.com/intl/en_us/badges/images/generic/en_badge_web_generic.png" />
</a>
""")
}

View File

@ -1,7 +0,0 @@
<a class="store"
href="https://play.google.com/store/apps/details?id=org.lichess.mobileapp">
<img alt="Android app on Google Play"
width="192"
height="74"
src="https://play.google.com/intl/en_us/badges/images/generic/en_badge_web_generic.png" />
</a>

View File

@ -10,8 +10,8 @@ moreCss = cssTag("mobile.css")) {
</div>
<div class="left-side">
<h1>@trans.playChessEverywhere()</h1>
@googlePlayButton()
@appleStoreButton()
@bits.googlePlayButton.toHtml
@bits.appleStoreButton.toHtml
<div class="apk" style="margin-top: 20px">
@Html(~apkDoc.getHtml("doc.content", resolver))
</div>

View File

@ -131,7 +131,7 @@ lazy val evaluation = module("evaluation", Seq(
// )
lazy val common = module("common", Seq()).settings(
libraryDependencies ++= provided(play.api, play.test, reactivemongo.driver, kamon.core)
libraryDependencies ++= provided(play.api, play.test, reactivemongo.driver, kamon.core, scalatags)
)
lazy val rating = module("rating", Seq(common, db)).settings(