From f87da2f4424b1393fba65d622ec3d764d2c8627f Mon Sep 17 00:00:00 2001 From: Thibault Duplessis Date: Thu, 28 Jan 2016 14:01:55 +0700 Subject: [PATCH] persistent challenges WIP --- app/views/base/layout.scala.html | 4 +- modules/challenge/src/main/BSONHandlers.scala | 8 ++- modules/challenge/src/main/Challenge.scala | 9 +++- modules/challenge/src/main/JsonView.scala | 14 +++-- public/javascripts/big.js | 11 ++-- public/stylesheets/challengeBox.css | 0 ui/challenge/src/ctrl.js | 12 ++++- ui/challenge/src/main.js | 7 +-- ui/challenge/src/view.js | 51 ++++++++++++++++++- ui/challenge/src/xhr.js | 7 +++ 10 files changed, 102 insertions(+), 21 deletions(-) create mode 100644 public/stylesheets/challengeBox.css diff --git a/app/views/base/layout.scala.html b/app/views/base/layout.scala.html index 9b9725653c..016c292fb1 100644 --- a/app/views/base/layout.scala.html +++ b/app/views/base/layout.scala.html @@ -106,7 +106,9 @@ withLangAnnotations: Boolean = true)(body: Html)(implicit ctx: Context) - + }.getOrElse { @trans.signIn() diff --git a/modules/challenge/src/main/BSONHandlers.scala b/modules/challenge/src/main/BSONHandlers.scala index e0c2ed8ebf..501152e547 100644 --- a/modules/challenge/src/main/BSONHandlers.scala +++ b/modules/challenge/src/main/BSONHandlers.scala @@ -46,8 +46,14 @@ private object BSONHandlers { def read(b: BSONBoolean) = Mode(b.value) def write(m: Mode) = BSONBoolean(m.rated) } + implicit val RatingBSONHandler = new BSON[Rating] { + def reads(r: Reader) = Rating(r.int("i"), r.boolD("p")) + def writes(w: Writer, r: Rating) = BSONDocument( + "i" -> r.int, + "b" -> w.boolO(r.provisional)) + } implicit val EitherChallengerBSONHandler = new BSON[EitherChallenger] { - def reads(r: Reader) = (r.strO("id") |@| r.intO("rating")) { + def reads(r: Reader) = (r.strO("id") |@| r.getO[Rating]("rating")) { case (id, rating) => Right(Registered(id, rating)) } orElse r.strO("secret").map { secret => Left(Anonymous(secret)) diff --git a/modules/challenge/src/main/Challenge.scala b/modules/challenge/src/main/Challenge.scala index 1131b5fe9f..cb1fd8f418 100644 --- a/modules/challenge/src/main/Challenge.scala +++ b/modules/challenge/src/main/Challenge.scala @@ -25,7 +25,12 @@ case class Challenge( object Challenge { - case class Registered(id: String, rating: Int) + case class Rating(int: Int, provisional: Boolean) + object Rating { + def apply(p: lila.rating.Perf): Rating = Rating(p.intRating, p.provisional) + } + + case class Registered(id: String, rating: Rating) case class Anonymous(secret: String) sealed trait TimeControl @@ -78,7 +83,7 @@ object Challenge { case _ => ColorChoice.Random }, challenger = challenger.fold[EitherChallenger](Left(Anonymous(randomId))) { u => - Right(Registered(u.id, u.perfs(perfType(variant, timeControl)).intRating)) + Right(Registered(u.id, Rating(u.perfs(perfType(variant, timeControl))))) }, destUserId = destUserId, createdAt = DateTime.now, diff --git a/modules/challenge/src/main/JsonView.scala b/modules/challenge/src/main/JsonView.scala index 932c9dc54f..75f197dae4 100644 --- a/modules/challenge/src/main/JsonView.scala +++ b/modules/challenge/src/main/JsonView.scala @@ -2,6 +2,8 @@ package lila.challenge import play.api.libs.json._ +import lila.common.PimpedJson._ + final class JsonView(getLightUser: String => Option[lila.common.LightUser]) { import Challenge._ @@ -18,8 +20,9 @@ final class JsonView(getLightUser: String => Option[lila.common.LightUser]) { "id" -> u.id, "name" -> light.fold(u.id)(_.name), "title" -> light.map(_.title), - "rating" -> u.rating - ) + "rating" -> u.rating.int, + "provisional" -> u.rating.provisional + ).noNull }, "destUserId" -> c.destUserId, "variant" -> Json.obj( @@ -28,12 +31,13 @@ final class JsonView(getLightUser: String => Option[lila.common.LightUser]) { "name" -> c.variant.name), "rated" -> c.mode.rated, "timeControl" -> (c.timeControl match { - case TimeControl.Clock(l, i) => Json.obj( + case c@TimeControl.Clock(l, i) => Json.obj( "type" -> "clock", "limit" -> l, - "increment" -> i) + "increment" -> i, + "show" -> c.show) case TimeControl.Correspondence(d) => Json.obj( - "type" -> "clock", + "type" -> "correspondence", "daysPerTurn" -> d) case TimeControl.Unlimited => Json.obj("type" -> "unlimited") }), diff --git a/public/javascripts/big.js b/public/javascripts/big.js index 46db0d3648..576e64a1a7 100644 --- a/public/javascripts/big.js +++ b/public/javascripts/big.js @@ -346,10 +346,9 @@ lichess.challengeBox = (function() { var baseUrl = $('body').data('asset-url'); var isDev = $('body').data('dev'); $('head').append($('') - .attr('href', baseUrl + '/assets/stylesheet/challenge.css')); - $.getScript(baseUrl + "/assets/compiled/challenge" + (isDev ? '.min' : '') + '.js').done(function() { - instance = LichessChallenge({ - element: document.getElementById('#challenge_notifications'), + .attr('href', baseUrl + '/assets/stylesheet/challengeBox.css')); + $.getScript(baseUrl + "/assets/compiled/lichess.challenge" + (isDev ? '' : '.min') + '.js').done(function() { + instance = LichessChallenge(document.getElementById('challenge_notifications'), { setCount: function(nb) { $('#challenge_notifications_tag').attr('data-count', nb).toggleClass('none', !nb); } @@ -1114,9 +1113,9 @@ lichess.unique = function(xs) { } }); }); - $('#challenge_notifications').one('mouseover', function() { + $('#challenge_notifications_tag').one('mouseover click', function() { lichess.challengeBox.load(); - }); + }).trigger('click'); $('#translation_call .close').click(function() { $.post($(this).data("href")); diff --git a/public/stylesheets/challengeBox.css b/public/stylesheets/challengeBox.css new file mode 100644 index 0000000000..e69de29bb2 diff --git a/ui/challenge/src/ctrl.js b/ui/challenge/src/ctrl.js index 919f467ed5..c2aa23eacb 100644 --- a/ui/challenge/src/ctrl.js +++ b/ui/challenge/src/ctrl.js @@ -6,12 +6,20 @@ module.exports = function(env) { this.data = env.data; - this.userId = env.userId; - this.socket = new socket(env.socketSend, this); this.vm = { + initiating: true, + reloading: false }; + this.update = function(data) { + this.data = data; + this.vm.initiating = false; + this.vm.reloading = false; + }.bind(this); + + xhr.load(); + this.trans = lichess.trans(env.i18n); }; diff --git a/ui/challenge/src/main.js b/ui/challenge/src/main.js index 00083ee25e..d3e6a2ee47 100644 --- a/ui/challenge/src/main.js +++ b/ui/challenge/src/main.js @@ -1,12 +1,13 @@ var m = require('mithril'); +var ctrl = require('./ctrl'); module.exports = function(element, opts) { + var controller = new ctrl(opts); + m.module(element, { controller: function() { - return { - data: opts.data - }; + return controller; }, view: require('./view') }); diff --git a/ui/challenge/src/view.js b/ui/challenge/src/view.js index 69d75b762a..52d0d0310f 100644 --- a/ui/challenge/src/view.js +++ b/ui/challenge/src/view.js @@ -1,6 +1,55 @@ var m = require('mithril'); +function user(u) { + var rating = u.rating + (u.provisional ? '?' : ''); + var fullName = (u.title ? u.title + ' ' : '') + u.name; + return { + tag: 'a', + attrs: { + class: 'ulpt user_link', + 'data-href': '/@/' + u.name + }, + children: [ + fullName, + m('span.progress', [rating, ratingDiff]) + ] + }; +} + +function timeControl(c) { + switch (c.type) { + case 'unlimited': + return 'Unlimited'; + case 'correspondence': + return c.daysPerTurn + ' days'; + case 'clock': + return c.show; + } +} + +function challenge(c) { + return m('div.challenge', [ + m('i', { + 'data-icon': c.perf.icon + }), + m('div.content', [ + m('span.title', user(c.challenger)), + m('span.desc', [ + c.rated ? 'Rated' : 'Casual', + timeControl(c.timeControl), + c.variant.name + ].join(' ')) + ]) + ]); +} + module.exports = function(ctrl) { + if (ctrl.vm.initiating) return m('div.square-wrap', m('div.square-spin')); var d = ctrl.data; - return m('div.challenges', 'hehe'); + return m('div', { + class: 'challenges ' + (ctrl.vm.reloading ? ' reloading' : '') + }, [ + d.in.map(challenge), + d.out.map(challenge), + ]) }; diff --git a/ui/challenge/src/xhr.js b/ui/challenge/src/xhr.js index cfec4c69ad..36f2193f9c 100644 --- a/ui/challenge/src/xhr.js +++ b/ui/challenge/src/xhr.js @@ -10,4 +10,11 @@ function uncache(url) { } module.exports = { + load: function() { + return m.request({ + method: 'GET', + url: uncache('/challenge'), + config: xhrConfig, + }).then(ctrl.update); + } };