More work on user profile

pull/1/merge
Thibault Duplessis 2012-05-25 00:35:13 +02:00
parent 58946f8177
commit cfe321e271
12 changed files with 193 additions and 120 deletions

View File

@ -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, _) })

View File

@ -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)

View File

@ -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,

View File

@ -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])

View File

@ -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>
}

View File

@ -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)

View File

@ -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 = {

View File

@ -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>
}

View File

@ -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>
}

View File

@ -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 {

View File

@ -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;
}

View File

@ -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;
}