More work on user profile
parent
58946f8177
commit
cfe321e271
|
@ -3,6 +3,8 @@ package controllers
|
|||
import lila._
|
||||
import views._
|
||||
import security.Permission
|
||||
import user.GameFilter
|
||||
import http.Context
|
||||
|
||||
import play.api._
|
||||
import play.api.mvc._
|
||||
|
@ -24,18 +26,20 @@ object User extends LilaController {
|
|||
env.user.userInfo(u, ctx) map { info ⇒
|
||||
val filters = user.GameFilterMenu(info, ctx.me)
|
||||
val filter = filters(filterName)
|
||||
val games = gamePaginator.userAll(u, page)
|
||||
(u.some == ctx.me).fold(
|
||||
html.user.home(u = u, info = info, games = games, filters = filters, filter = filter),
|
||||
html.user.show(u = u, info = info, games = games, filters = filters, filter = filter)
|
||||
)
|
||||
html.user.show(u, info,
|
||||
games = gamePaginator(filterPaginator(u, filter))(page),
|
||||
filters = filters,
|
||||
filter = filter)
|
||||
},
|
||||
io(html.user.disabled(u)))
|
||||
}
|
||||
}
|
||||
|
||||
def showTemplate(user: User, me: Option[User]) =
|
||||
(user.some == me).fold(html.user.show, html.user.home)
|
||||
def filterPaginator(user: User, filter: GameFilter)(implicit ctx: Context) =
|
||||
filter match {
|
||||
case GameFilter.All ⇒ gamePaginator.userAdapter(user)
|
||||
case GameFilter.Me ⇒ gamePaginator.opponentsAdapter(user, ctx.me | user)
|
||||
}
|
||||
|
||||
def list(page: Int) = Open { implicit ctx ⇒
|
||||
IOk(onlineUsers map { html.user.list(paginator elo page, _) })
|
||||
|
|
|
@ -130,10 +130,9 @@ class GameRepo(collection: MongoCollection)
|
|||
}
|
||||
|
||||
def candidatesToAutofinish: IO[List[DbGame]] = io {
|
||||
find(
|
||||
find(startedQuery ++
|
||||
("clock.l" $exists true) ++
|
||||
("status" -> Status.Started.id) ++
|
||||
("updatedAt" $lt (DateTime.now - 2.hour))
|
||||
("updatedAt" $lt (DateTime.now - 2.hour))
|
||||
).toList.map(_.decode).flatten
|
||||
}
|
||||
|
||||
|
@ -173,8 +172,10 @@ class GameRepo(collection: MongoCollection)
|
|||
def opponentsQuery(user1: User, user2: User) =
|
||||
"userIds" $all List(user1.id.toString, user2.id.toString)
|
||||
|
||||
val startedQuery = ("status" $gte Status.Started.id)
|
||||
|
||||
def recentGames(limit: Int): IO[List[DbGame]] = io {
|
||||
find(DBObject("status" -> Status.Started.id))
|
||||
find(startedQuery)
|
||||
.sort(DBObject("updatedAt" -> -1))
|
||||
.limit(limit)
|
||||
.toList.map(_.decode).flatten sortBy (_.id)
|
||||
|
|
|
@ -17,8 +17,14 @@ final class PaginatorBuilder(
|
|||
def checkmate(page: Int): Paginator[DbGame] =
|
||||
paginator(checkmateAdapter, page)
|
||||
|
||||
def userAll(user: User, page: Int): Paginator[DbGame] =
|
||||
paginator(userAllAdapter(user), page)
|
||||
def apply(adapter: Adapter[DbGame])(page: Int): Paginator[DbGame] =
|
||||
paginator(adapter, page)
|
||||
|
||||
def userAdapter(user: User) =
|
||||
adapter(gameRepo.startedQuery ++ ("userIds" -> user.id.toString))
|
||||
|
||||
def opponentsAdapter(u1: User, u2: User) =
|
||||
adapter(gameRepo.startedQuery ++ gameRepo.opponentsQuery(u1, u2))
|
||||
|
||||
private val recentAdapter =
|
||||
adapter(DBObject())
|
||||
|
@ -26,9 +32,6 @@ final class PaginatorBuilder(
|
|||
private val checkmateAdapter =
|
||||
adapter(DBObject("status" -> Status.Mate.id))
|
||||
|
||||
private def userAllAdapter(user: User) =
|
||||
adapter(("status" $gte Status.Started.id) ++ ("userIds" -> user.id.toString))
|
||||
|
||||
private def adapter(query: DBObject) = SalatAdapter(
|
||||
dao = gameRepo,
|
||||
query = query,
|
||||
|
|
|
@ -14,6 +14,8 @@ sealed abstract class Context(val req: RequestHeader, val me: Option[User]) {
|
|||
|
||||
def isGranted(permission: Permission): Boolean =
|
||||
me.fold(Granter(permission), false)
|
||||
|
||||
def is(user: User) = me == Some(user)
|
||||
}
|
||||
|
||||
final class BodyContext(val body: Request[_], m: Option[User])
|
||||
|
|
|
@ -14,9 +14,7 @@
|
|||
</div>
|
||||
<div class="all_games infinitescroll">
|
||||
<div class="pager"><a href="@next">Next</a></div>
|
||||
<div class="all_games_inner">
|
||||
@game.widgets(paginator.currentPageResults)
|
||||
</div>
|
||||
@game.widgets(paginator.currentPageResults)
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
|
|
@ -1,23 +0,0 @@
|
|||
@(u: User, info: lila.user.UserInfo, games: Paginator[DbGame], filters: lila.user.GameFilterMenu, filter: lila.user.GameFilter)(implicit ctx: Context)
|
||||
|
||||
@bio = {
|
||||
<div class="editable" data-url="@routes.User.setBio">
|
||||
<span class="user_bio" data-name="bio" data-type="textarea" data-provider-url="@routes.User.getBio">
|
||||
@shorten(u.bio | "Click here to tell about yourself.", 400)
|
||||
</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
@actions = {
|
||||
<br />
|
||||
<a class="small" href="@routes.User.close">Close your account</a>
|
||||
}
|
||||
|
||||
@user.profile(
|
||||
u = u,
|
||||
info = info,
|
||||
games = games,
|
||||
bio = bio,
|
||||
actions = actions,
|
||||
filters = filters,
|
||||
filter = filter)
|
|
@ -1,8 +1,9 @@
|
|||
@(title: String, goodies: Option[Html] = None, robots: Boolean = true, evenMoreJs: Html = Html(""))(body: Html)(implicit ctx: Context)
|
||||
@(title: String, goodies: Option[Html] = None, robots: Boolean = true, evenMoreJs: Html = Html(""), evenMoreCss: Html = Html(""))(body: Html)(implicit ctx: Context)
|
||||
|
||||
@moreCss = {
|
||||
@cssTag("user-list.css")
|
||||
@cssTag("user-show.css")
|
||||
@evenMoreCss
|
||||
}
|
||||
|
||||
@moreJs = {
|
||||
|
|
|
@ -1,68 +0,0 @@
|
|||
@(u: User, info: lila.user.UserInfo, games: Paginator[DbGame], bio: Html, actions: Html, filters: lila.user.GameFilterMenu, filter: lila.user.GameFilter)(implicit ctx: Context)
|
||||
|
||||
@title = @{ "%s %s - page %d".format(u.username, filterTitle(info, filter), games.currentPage) }
|
||||
|
||||
@evenMoreJs = {
|
||||
<script src="http://www.google.com/jsapi"></script>
|
||||
@jsTag("chart.js")
|
||||
@jsTag("user-chart.js")
|
||||
}
|
||||
|
||||
@user.layout(
|
||||
title = title,
|
||||
robots = false,
|
||||
evenMoreJs = evenMoreJs) {
|
||||
<div class="content_box no_padding user_show">
|
||||
<div class="content_box_top">
|
||||
@if(ctx.me.fold(u !=, false)) {
|
||||
<a href="#" class="send_message">@trans.composeMessage()</a>
|
||||
}
|
||||
<a href="@routes.User.export(u.username)">@trans.exportGames()</a>
|
||||
<div class="status @isUsernameOnline(u.username).fold("connected", "")"></div>
|
||||
<h1 class="lichess_title">@u.usernameWithElo</h1>
|
||||
@info.rank.map { r =>
|
||||
<span class="rank">
|
||||
@trans.rank(): <strong>@r._1</strong> / @r._2
|
||||
</span>
|
||||
}
|
||||
</div>
|
||||
<div class="content_box_content clearfix">
|
||||
@if(isGranted(Permission.Admin, u)) {
|
||||
<div class="staff">STAFF</div>
|
||||
}
|
||||
@info.eloChart.map { eloChart =>
|
||||
<div class="elo_history" title="Elo history" data-columns="@eloChart.columns" data-rows="@eloChart.rows"></div>
|
||||
}
|
||||
@if(u.engine && ctx.me.fold(u !=, true)) {
|
||||
<div class="engine_warning">@trans.thisPlayerUsesChessComputerAssistance()</div>
|
||||
}
|
||||
@bio
|
||||
@info.eloWithMe.map { eloWithMe =>
|
||||
<div class="elo_with_me">
|
||||
@eloWithMe.map { e =>
|
||||
@e._1.capitalize: <strong>@showNumber(e._2)</strong>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
<div class="stats">
|
||||
@info.winChart.map { winChart =>
|
||||
<div class="win_stats" title="@trans.gamesPlayed(): @u.nbGames" data-columns="@winChart.columns" data-rows="@winChart.rows(trans)"></div>
|
||||
}
|
||||
@actions
|
||||
</div>
|
||||
</div>
|
||||
@if(u.hasGames) {
|
||||
<div class="content_box_inter clearfix">
|
||||
@filters.list.map { f =>
|
||||
<a @(filter == f).fold("class='active' ", "")href="@routes.User.showFilter(u.username, f.name)">
|
||||
@filterTitle(info, f)
|
||||
</a>
|
||||
}
|
||||
</div>
|
||||
<div class="games infinitescroll all_games">
|
||||
<div class="pager"><a href="@routes.User.showFilter(u.username, filter.name, games.nextPage | 1)">Next</a></div>
|
||||
@game.widgets(games.currentPageResults, u.some)
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
|
@ -1,12 +1,42 @@
|
|||
@(u: User, info: lila.user.UserInfo, games: Paginator[DbGame], filters: lila.user.GameFilterMenu, filter: lila.user.GameFilter)(implicit ctx: Context)
|
||||
|
||||
@title = @{ "%s : %s - page %d".format(u.username, filterTitle(info, filter), games.currentPage) }
|
||||
|
||||
@evenMoreJs = {
|
||||
<script src="http://www.google.com/jsapi"></script>
|
||||
@jsTag("chart.js")
|
||||
@jsTag("user-chart.js")
|
||||
@if(ctx is u) {
|
||||
@jsTag("jquery-editable-set.js")
|
||||
@jsTag("user-edit.js")
|
||||
}
|
||||
}
|
||||
|
||||
@evenMoreCss = {
|
||||
@if(ctx is u) {
|
||||
@cssTag("user-edit.css")
|
||||
}
|
||||
}
|
||||
|
||||
@bio = {
|
||||
@if(ctx is u) {
|
||||
<div class="editable" data-url="@routes.User.setBio">
|
||||
<span class="user_bio" data-name="bio" data-type="textarea" data-provider-url="@routes.User.getBio">
|
||||
@shorten(u.bio | "Click here to tell about yourself.", 400)
|
||||
</span>
|
||||
</div>
|
||||
} else {
|
||||
@u.nonEmptyBio.map { bio =>
|
||||
<span class="user_bio">@shorten(bio, 400)</span>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@actions = {
|
||||
@if(ctx is u) {
|
||||
<br />
|
||||
<a class="small" href="@routes.User.close">Close your account</a>
|
||||
} else {
|
||||
@if(isGranted(Permission.MarkEngine)) {
|
||||
<form method="post" action="@routes.User.engine(u.username)">
|
||||
<input class="confirm" type="submit" value="Mark as engine" />
|
||||
|
@ -18,12 +48,63 @@
|
|||
</form>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@user.profile(
|
||||
u = u,
|
||||
info = info,
|
||||
games = games,
|
||||
bio = bio,
|
||||
actions = actions,
|
||||
filters = filters,
|
||||
filter = filter)
|
||||
@user.layout(
|
||||
title = title,
|
||||
robots = false,
|
||||
evenMoreJs = evenMoreJs) {
|
||||
<div class="content_box no_padding user_show">
|
||||
<div class="content_box_top">
|
||||
@if(ctx.me.fold(u !=, false)) {
|
||||
<a href="#" class="send_message">@trans.composeMessage()</a>
|
||||
}
|
||||
<a href="@routes.User.export(u.username)">@trans.exportGames()</a>
|
||||
<div class="status @isUsernameOnline(u.username).fold("connected", "")"></div>
|
||||
<h1 class="lichess_title">@u.usernameWithElo</h1>
|
||||
@info.rank.map { r =>
|
||||
<span class="rank">
|
||||
@trans.rank(): <strong>@r._1</strong> / @r._2
|
||||
</span>
|
||||
}
|
||||
</div>
|
||||
<div class="content_box_content clearfix">
|
||||
@if(isGranted(Permission.Admin, u)) {
|
||||
<div class="staff">STAFF</div>
|
||||
}
|
||||
@info.eloChart.map { eloChart =>
|
||||
<div class="elo_history" title="Elo history" data-columns="@eloChart.columns" data-rows="@eloChart.rows"></div>
|
||||
}
|
||||
@if(u.engine && ctx.me.fold(u !=, true)) {
|
||||
<div class="engine_warning">@trans.thisPlayerUsesChessComputerAssistance()</div>
|
||||
}
|
||||
@bio
|
||||
@info.eloWithMe.map { eloWithMe =>
|
||||
<div class="elo_with_me">
|
||||
@eloWithMe.map { e =>
|
||||
@e._1.capitalize: <strong>@showNumber(e._2)</strong>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
<div class="stats">
|
||||
@info.winChart.map { winChart =>
|
||||
<div class="win_stats" title="@trans.gamesPlayed(): @u.nbGames" data-columns="@winChart.columns" data-rows="@winChart.rows(trans)"></div>
|
||||
}
|
||||
@actions
|
||||
</div>
|
||||
</div>
|
||||
@if(u.hasGames) {
|
||||
<div class="content_box_inter clearfix">
|
||||
@filters.list.map { f =>
|
||||
<a @{ (filter == f).fold("class='active'", "") } href="@routes.User.showFilter(u.username, f.name)">
|
||||
@filterTitle(info, f)
|
||||
</a>
|
||||
}
|
||||
</div>
|
||||
<div class="games infinitescroll all_games">
|
||||
<div class="pager none"><a href="@routes.User.showFilter(u.username, filter.name, games.nextPage | 1)">Next</a></div>
|
||||
@game.widgets(games.currentPageResults, u.some)
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
|
|
@ -29,7 +29,7 @@ div.game_row {
|
|||
border-bottom: 1px solid #eaeaea;
|
||||
position: relative;
|
||||
}
|
||||
div.game_row:nth-child(even) {
|
||||
div.game_row:nth-child(odd) {
|
||||
background: #f4f4f4;
|
||||
}
|
||||
div.game_row div.infos span.win {
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
div.editable span {
|
||||
width: 290px;
|
||||
padding: 5px;
|
||||
border: 1px solid #88aaff;
|
||||
cursor: pointer;
|
||||
display: block;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
div.editable span:hover {
|
||||
background: #dfefff;
|
||||
}
|
||||
|
||||
div.editable form {
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
div.editable textarea {
|
||||
width: 290px;
|
||||
padding: 5px;
|
||||
height: 7em;
|
||||
}
|
||||
div.editable input {
|
||||
margin-right: 5px;
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
div.signup_box p.explanation {
|
||||
font-size: 1.2em;
|
||||
}
|
||||
|
||||
div.signup_box h1 {
|
||||
margin-bottom: 2em;
|
||||
}
|
||||
|
||||
div.signup_box .box_right_text {
|
||||
margin-top: -5px;
|
||||
float: right;
|
||||
}
|
||||
|
||||
form.signup {
|
||||
font-size: 1.3em;
|
||||
}
|
||||
|
||||
form.signup li {
|
||||
list-style: none;
|
||||
display: block;
|
||||
margin: 1em 0 1em 1em;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
form.signup label {
|
||||
float: left;
|
||||
display: block;
|
||||
width: 100px;
|
||||
text-align: right;
|
||||
margin-right: 10px;
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
form.signup .username input, form.signup .password input {
|
||||
width: 150px;
|
||||
padding: 0 3px;
|
||||
}
|
||||
|
||||
form.signup input.submit {
|
||||
margin-left: 110px;
|
||||
padding: 3px 1em;
|
||||
}
|
||||
|
||||
/* Errors */
|
||||
form.signup li ul {
|
||||
position: absolute;
|
||||
left: 270px;
|
||||
top: -9px;
|
||||
font-size: 12px;
|
||||
color: red;
|
||||
}
|
Loading…
Reference in New Issue