bootstrap user stats mithril UI

pull/1352/head
Thibault Duplessis 2015-12-25 17:41:45 +07:00
parent 5b7159c21e
commit 785d844b14
14 changed files with 193 additions and 20 deletions

View File

@ -228,7 +228,8 @@ object User extends LilaController {
else lila.rating.PerfType(perfKey).fold(notFound) { perfType =>
Env.perfStat.get(u, perfType).flatMap { perfStat =>
Env.current.userInfo(u, ctx).map { info =>
Ok(html.user.perfStat(u, info, perfStat))
val data = Env.perfStat.jsonView(perfStat)
Ok(html.user.perfStat(u, info, perfStat.perfType, data))
}
}
}

View File

@ -12,7 +12,7 @@ lichess = lichess || {};
lichess.insight = LichessInsight(document.getElementById('insight'), {
ui: @Html(toJson(ui)),
initialQuestion: @Html(toJson(question)),
i18n: @jsI18n(),
i18n: {},
myUserId: @Html(ctx.userId.fold("null")(id => s""""$id"""")),
user: {
id: "@u.id",

View File

@ -1,3 +0,0 @@
@()(implicit ctx: Context)
@Html(J.stringify(i18nJsObject(
)))

View File

@ -1,9 +1,23 @@
@(u: User, info: lila.app.mashup.UserInfo, perfStat: lila.perfStat.PerfStat)(implicit ctx: Context)
@(u: User, info: lila.app.mashup.UserInfo, perfType: lila.rating.PerfType, data: play.api.libs.json.JsObject)(implicit ctx: Context)
@moreJs = {
@jsAt(s"compiled/lichess.perfStat${isProd??(".min")}.js")
@embedJs {
$(function() {
lichess = lichess || {};
lichess.perfStat = LichessPerfStat(document.getElementById('perfStat'), {
data: @Html(toJson(data)),
i18n: {}
});
});
}
}
@user.layout(
title = s"${u.username} ${perfStat.perfType.name} stats",
title = s"${u.username} ${perfType.name} stats",
side = side(u, info).some,
robots = false) {
robots = false,
evenMoreJs = moreJs) {
<div class="content_box no_padding user_show">
<div class="content_box_top">
<h1 class="lichess_title">
@ -11,14 +25,12 @@ robots = false) {
<span@if(isOnline(u.id)) { class="connected" title="Online" } else { title="Offline" } data-icon="r"></span>
@u.titleUsername
</a>
@perfStat.perfType.name stats
@perfType.name stats
</h1>
</div>
@perfStat
<a href="@routes.User.showFilter(u.username, "search")?perf=@perfStat.perfType.id">
View all @perfStat.perfType.name games
</a>
<div class="content_box_content" id="perfStat"></div>
<!-- <a href="@routes.User.showFilter(u.username, "search")?perf=@perfType.id"> -->
<!-- View all @perfType.name games -->
<!-- </a> -->
</div>
}

View File

@ -10,6 +10,7 @@ import lila.common.PimpedConfig._
final class Env(
config: Config,
system: ActorSystem,
lightUser: String => Option[lila.common.LightUser],
db: lila.db.Env) {
private val settings = new {
@ -24,6 +25,8 @@ final class Env(
storage = storage,
sequencer = system.actorOf(Props(classOf[lila.hub.Sequencer], None, None)))
lazy val jsonView = new JsonView(lightUser)
def get(user: lila.user.User, perfType: lila.rating.PerfType) =
storage.find(user.id, perfType) orElse {
indexer.userPerf(user, perfType) >> storage.find(user.id, perfType)
@ -42,5 +45,6 @@ object Env {
lazy val current: Env = "perfStat" boot new Env(
config = lila.common.PlayApp loadConfig "perfStat",
system = lila.common.PlayApp.system,
lightUser = lila.user.Env.current.lightUser,
db = lila.db.Env.current)
}

View File

@ -0,0 +1,29 @@
package lila.perfStat
import play.api.libs.json._
import lila.common.LightUser
import lila.rating.PerfType
final class JsonView(getLightUser: String => Option[LightUser]) {
def apply(ps: PerfStat) = perfStatWrites writes ps
private implicit def perfTypeWriter: OWrites[PerfType] = OWrites { pt =>
Json.obj("key" -> pt.key, "name" -> pt.name)
}
private implicit def userIdWriter: OWrites[UserId] = OWrites { u =>
val light = getLightUser(u.value)
Json.obj(
"id" -> u.value,
"name" -> light.fold(u.value)(_.name),
"title" -> light.flatMap(_.title))
}
private implicit val ratingAtWrites = Json.writes[RatingAt]
private implicit val resultWrites = Json.writes[Result]
private implicit val playStreakWrites = Json.writes[PlayStreak]
private implicit val resultStreakWrites = Json.writes[ResultStreak]
private implicit val avgWrites = Json.writes[Avg]
private implicit val countWrites = Json.writes[Count]
private implicit val perfStatWrites = Json.writes[PerfStat]
}

View File

@ -7,7 +7,7 @@ import org.joda.time.DateTime
case class PerfStat(
_id: String, // userId/perfId
userId: String,
userId: UserId,
perfType: PerfType,
highest: Option[RatingAt],
lowest: Option[RatingAt],
@ -36,7 +36,7 @@ object PerfStat {
def init(userId: String, perfType: PerfType) = PerfStat(
_id = makeId(userId, perfType),
userId = userId,
userId = UserId(userId),
perfType = perfType,
highest = none,
lowest = none,
@ -113,12 +113,14 @@ object RatingAt {
} orElse cur
}
case class Result(opInt: Int, opId: String, at: DateTime, gameId: String)
case class Result(opInt: Int, opId: UserId, at: DateTime, gameId: String)
object Result {
def agg(cur: Option[Result], pov: Pov, comp: Int) =
pov.opponent.rating.filter { r =>
cur.fold(true) { c => r.compare(c.opInt) == comp }
}.map {
Result(_, ~pov.opponent.userId, pov.game.updatedAtOrCreatedAt, pov.game.id)
Result(_, UserId(~pov.opponent.userId), pov.game.updatedAtOrCreatedAt, pov.game.id)
} orElse cur
}
case class UserId(value: String)

View File

@ -17,6 +17,10 @@ final class PerfStatStorage(coll: Coll) {
def read(b: BSONInteger) = PerfType.byId get b.value err s"Invalid perf type id ${b.value}"
def write(p: PerfType) = BSONInteger(p.id)
}
implicit val UserIdBSONHandler = new BSONHandler[BSONString, UserId] {
def read(b: BSONString) = UserId(b.value)
def write(u: UserId) = BSONString(u.value)
}
private implicit val RatingAtBSONHandler = Macros.handler[RatingAt]
private implicit val ResultBSONHandler = Macros.handler[Result]
private implicit val PlayStreakBSONHandler = Macros.handler[PlayStreak]

View File

@ -5,7 +5,7 @@ target=${1-dev}
mkdir -p public/compiled
for app in insight editor puzzle round analyse lobby tournament tournamentSchedule opening simul; do
for app in insight editor puzzle round analyse lobby tournament tournamentSchedule opening simul perfStat; do
cd ui/$app
npm install && gulp $target
cd -

View File

@ -0,0 +1,54 @@
var source = require('vinyl-source-stream');
var gulp = require('gulp');
var gutil = require('gulp-util');
var watchify = require('watchify');
var browserify = require('browserify');
var uglify = require('gulp-uglify');
var streamify = require('gulp-streamify');
var sources = ['./src/main.js'];
var destination = '../../public/compiled/';
var onError = function(error) {
gutil.log(gutil.colors.red(error.message));
};
var standalone = 'LichessPerfStat';
gulp.task('prod', function() {
return browserify('./src/main.js', {
standalone: standalone
}).bundle()
.on('error', onError)
.pipe(source('lichess.perfStat.min.js'))
.pipe(streamify(uglify()))
.pipe(gulp.dest(destination));
});
gulp.task('dev', function() {
return browserify('./src/main.js', {
standalone: standalone
}).bundle()
.on('error', onError)
.pipe(source('lichess.perfStat.js'))
.pipe(gulp.dest(destination));
});
gulp.task('watch', function() {
var opts = watchify.args;
opts.debug = true;
opts.standalone = standalone;
var bundleStream = watchify(browserify(sources, opts))
.on('update', rebundle)
.on('log', gutil.log);
function rebundle() {
return bundleStream.bundle()
.on('error', onError)
.pipe(source('lichess.perfStat.js'))
.pipe(gulp.dest(destination));
}
return rebundle();
});
gulp.task('default', ['watch']);

View File

@ -0,0 +1,35 @@
{
"name": "perfStat",
"version": "1.0.0",
"description": "lichess.org chess performance statistics",
"main": "src/main.js",
"repository": {
"type": "git",
"url": "https://github.com/ornicar/lila"
},
"keywords": [
"chess",
"lichess",
"performance",
"statistic"
],
"author": "ornicar",
"license": "MIT",
"bugs": {
"url": "https://github.com/ornicar/lila/issues"
},
"homepage": "https://github.com/ornicar/lila",
"devDependencies": {
"browserify": "~9.0.8",
"gulp": "~3.9.0",
"gulp-streamify": "~0.0.5",
"gulp-uglify": "~1.2.0",
"gulp-util": "~3.0.4",
"vinyl-source-stream": "~1.1.0",
"watchify": "~3.1.1"
},
"dependencies": {
"mithril": "github:ornicar/mithril.js#v1.0.0",
"numeral": "^1.5.3"
}
}

View File

@ -0,0 +1,9 @@
var m = require('mithril');
module.exports = function(env) {
this.data = env.data;
this.vm = {
};
};

View File

@ -0,0 +1,18 @@
var m = require('mithril');
var ctrl = require('./ctrl');
var view = require('./view');
module.exports = function(element, opts) {
var controller = new ctrl(opts, element);
m.module(element, {
controller: function() {
return controller;
},
view: view
});
return controller;
};

View File

@ -0,0 +1,8 @@
var m = require('mithril');
module.exports = function(ctrl) {
return m('div.perfStat', [
'perf stats',
JSON.stringify(ctrl.data)
]);
};