bootstrap user stats mithril UI
parent
5b7159c21e
commit
785d844b14
|
@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
@()(implicit ctx: Context)
|
||||
@Html(J.stringify(i18nJsObject(
|
||||
)))
|
|
@ -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>
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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]
|
||||
}
|
|
@ -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)
|
||||
|
|
|
@ -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]
|
||||
|
|
2
ui/build
2
ui/build
|
@ -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 -
|
||||
|
|
|
@ -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']);
|
|
@ -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"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
var m = require('mithril');
|
||||
|
||||
module.exports = function(env) {
|
||||
|
||||
this.data = env.data;
|
||||
|
||||
this.vm = {
|
||||
};
|
||||
};
|
|
@ -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;
|
||||
};
|
|
@ -0,0 +1,8 @@
|
|||
var m = require('mithril');
|
||||
|
||||
module.exports = function(ctrl) {
|
||||
return m('div.perfStat', [
|
||||
'perf stats',
|
||||
JSON.stringify(ctrl.data)
|
||||
]);
|
||||
};
|
Loading…
Reference in New Issue