more challenge refactoring, bootstrap mithril project

pull/1558/head
Thibault Duplessis 2016-01-27 22:01:23 +07:00
parent ba93101c83
commit 911a70dda1
21 changed files with 209 additions and 198 deletions

View File

@ -21,12 +21,6 @@ private[app] final class Renderer extends Actor {
case lila.tournament.actorApi.RemindTournament(tournament, _) =>
sender ! spaceless(V.tournament.reminder(tournament))
case lila.hub.actorApi.setup.RemindChallenge(gameId, from, _) =>
val replyTo = sender
(GameRepo game gameId) zip (UserRepo named from) onSuccess {
case (Some(game), Some(user)) => replyTo ! spaceless(V.setup.challengeNotification(game, user))
}
case lila.hub.actorApi.RemindDeployPre =>
sender ! spaceless(V.notification.deploy("pre"))
case lila.hub.actorApi.RemindDeployPost =>

View File

@ -4,19 +4,22 @@ import play.api.data.Form
import play.api.i18n.Messages.Implicits._
import play.api.libs.json.Json
import play.api.mvc.{ Result, Results, Call, RequestHeader, Accepting }
import play.api.Play.current
import scala.concurrent.duration._
import lila.api.{ Context, BodyContext }
import lila.app._
import lila.common.{ HTTPRequest, LilaCookie }
import lila.user.UserRepo
import views._
object Challenge extends LilaController {
private def env = Env.setup
private def env = Env.challenge
private val PostRateLimit = new lila.memo.RateLimit(5, 1 minute)
def all = Auth { implicit ctx =>
me =>
env.api.findByDestId(me.id) zip
env.api.findByChallengerId(me.id) map {
case (out, in) => Ok(env.jsonView.all(in, out)) as JSON
}
}
}

View File

@ -71,7 +71,7 @@ object Round extends LilaController with TheftPrevention {
}
}
},
Redirect(routes.Setup.await(pov.fullId)).fuccess
notFound
)
},
api = apiVersion => {
@ -176,12 +176,13 @@ object Round extends LilaController with TheftPrevention {
Env.tournament.api.miniStanding(tid, ctx.userId, withStanding)
}
// remove me
private def join(pov: Pov)(implicit ctx: Context): Fu[Result] =
GameRepo initialFen pov.game zip
Env.api.roundApi.watcher(pov, lila.api.Mobile.Api.currentVersion, tv = none) zip
((pov.player.userId orElse pov.opponent.userId) ?? UserRepo.byId) map {
case ((fen, data), opponent) => Ok(html.setup.join(
pov, data, opponent, Env.setup.friendConfigMemo get pov.game.id, fen))
pov, data, opponent, none, fen))
}
def playerText(fullId: String) = Open { implicit ctx =>

View File

@ -80,48 +80,25 @@ object Setup extends LilaController with TheftPrevention {
), {
case config =>
import lila.challenge.Challenge._
Env.challenge.api.create(
val challenge = lila.challenge.Challenge.make(
variant = config.variant,
timeControl = config.makeClock.map {
case chess.Clock(limit, inc) => TimeControl.Clock(limit, inc)
}.orElse config.makeDaysPerTurn.map {
timeControl = config.makeClock map { c =>
TimeControl.Clock(c.limit, c.increment)
} orElse config.makeDaysPerTurn.map {
TimeControl.Correspondence.apply
}.getOrElse TimeControl.Unlimited,
} getOrElse TimeControl.Unlimited,
mode = config.mode,
color = config.color.name,
challenger = ctx.me match {
case Some(
rated: Boolean,
color: String,
challenger: Either[String, lila.user.User],
destUserId: Option[String]): Challenge = new Challenge(
friend config flatMap { pov =>
negotiate(
html = fuccess(redirectPov(pov, routes.Setup.await(pov.fullId, userId))),
api = apiVersion => Env.api.roundApi.player(pov, apiVersion) map { data =>
Created(data) as JSON
}
)
}
challenger = ctx.me,
destUserId = userId)
(Env.challenge.api insert challenge) >> negotiate(
html = fuccess(Redirect(routes.Challenge.all)),
api = apiVersion => ??? // TODO fix me!
)
}
)
}
def decline(gameId: String) = Auth { implicit ctx =>
me =>
OptionResult(GameRepo game gameId) { game =>
if (game.started) BadRequest("Cannot decline started challenge")
else {
GameRepo remove game.id
Env.hub.actor.challenger ! lila.hub.actorApi.setup.DeclineChallenge(gameId)
Ok("ok")
}
}
}
def hookForm = Open { implicit ctx =>
if (HTTPRequest isXhr ctx.req) NoPlaybanOrCurrent {
env.forms.hookFilled(timeModeString = get("time")) map { html.setup.hook(_) }
@ -189,48 +166,27 @@ object Setup extends LilaController with TheftPrevention {
def join(id: String) = Open { implicit ctx =>
OptionFuResult(GameRepo game id) { game =>
env.friendJoiner(game, ctx.me).fold(
err => negotiate(
html = fuccess {
Redirect(routes.Round.watcher(id, "white"))
},
api = _ => fuccess {
BadRequest(Json.obj("error" -> err.toString)) as JSON
}
),
_ flatMap {
case (p, events) => {
Env.hub.socket.round ! lila.hub.actorApi.map.Tell(p.gameId, lila.round.actorApi.EventList(events))
negotiate(
html = fuccess {
implicit val req = ctx.req
redirectPov(p, routes.Round.player(p.fullId))
},
api = apiVersion => Env.api.roundApi.player(p, apiVersion) map { data =>
Created(data) as JSON
})
}
})
???
}
}
def await(fullId: String, userId: Option[String]) = Open { implicit ctx =>
OptionFuResult(GameRepo pov fullId) { pov =>
pov.game.started.fold(
Redirect(routes.Round.player(pov.fullId)).fuccess,
Env.api.roundApi.player(pov, lila.api.Mobile.Api.currentVersion) zip
(userId ?? UserRepo.named) flatMap {
case (data, user) => PreventTheft(pov) {
Ok(html.setup.await(
pov,
data,
env.friendConfigMemo get pov.game.id,
user)).fuccess
}
}
)
}
}
// def await(fullId: String, userId: Option[String]) = Open { implicit ctx =>
// OptionFuResult(GameRepo pov fullId) { pov =>
// pov.game.started.fold(
// Redirect(routes.Round.player(pov.fullId)).fuccess,
// Env.api.roundApi.player(pov, lila.api.Mobile.Api.currentVersion) zip
// (userId ?? UserRepo.named) flatMap {
// case (data, user) => PreventTheft(pov) {
// Ok(html.setup.await(
// pov,
// data,
// env.friendConfigMemo get pov.game.id,
// user)).fuccess
// }
// }
// )
// }
// }
def cancel(fullId: String) = Open { implicit ctx =>
OptionResult(GameRepo pov fullId) { pov =>

View File

@ -1,61 +0,0 @@
@(pov: Pov, data: play.api.libs.json.JsObject, config: Option[lila.setup.FriendConfig], userOption: Option[User])(implicit ctx: Context)
@import pov._
@moreJs = {
@embedJs {
lichess = lichess || {};
lichess.prelude = {
data: @Html(play.api.libs.json.Json.stringify(data)),
};
}
}
@round.layout(
title = userOption.isDefined.fold(trans.challengeToPlay,trans.playWithAFriend).str(),
moreJs = moreJs,
side = Html("")) {
<div class="lichess_game">
<div class="lichess_board_wrap cg-512">
<div class="lichess_board"></div>
</div>
<div class="lichess_overboard">
@userOption.map { user =>
<div id="challenge_await" data-user="@user.id">
<h2>@trans.challengeToPlay()</h2>
@userLink(user, cssClass="target".some)
<span class="loader"><span data-icon="U"></span></span>
<p class="explanations">
@trans.waitingForOpponent()<br />
<a href="@routes.Setup.cancel(fullId)">@trans.cancel()</a>
</p>
</div>
<div id="challenge_declined" class="none">
<h2>Challenge declined</h2>
<a href="@routes.Lobby.home()">@trans.playWithAnotherOpponent()</a>
</div>
}.getOrElse {
@trans.toInviteSomeoneToPlayGiveThisUrl():
<input id="lichess_id_input" class="copyable" spellcheck="false" readonly="true" value="@protocol@commonDomain@routes.Round.watcher(gameId, "white")" />
<button class="copy button" data-rel="lichess_id_input" data-icon="&quot;"></button>
<p class="explanations">
@trans.theFirstPersonToComeOnThisUrlWillPlayWithYou()<br />
<a href="@routes.Setup.cancel(fullId)">@trans.cancel()</a>
</p>
}
@config.map { c =>
<p class="explanations">
@trans.variant(): @views.html.game.variantLink(c.variant, variantName(c.variant))<br />
@game.daysPerTurn.map { days =>
<span data-icon=";"> @{(days == 1).fold(trans.oneDay(), trans.nbDays(days))}</span>
}.getOrElse {
<span data-icon="p"> @shortClockName(c.makeClock)</span>
}
<br />
@trans.mode(): @modeName(c.mode)
</p>
}
@base.quote(lila.quote.Quote.one(pov.gameId))
</div>
</div>
}

View File

@ -1,29 +0,0 @@
@(g: Game, user: User)
<div id="challenge_reminder_@g.id" data-href="@routes.Round.watcher(g.id, "white")" class="notification">
<div class="game_infos" data-icon="@g.perfType match {
case _ if g.fromPosition => {*}
case Some(p) => {@p.iconChar}
case _ => {8}
}">
@userSpan(user, withOnline = false, withPerfRating = g.perfType)
<span class="setup">
@g.clock.map(_.show).getOrElse {
@g.daysPerTurn.map { days =>
@if(days == 1) {@trans.oneDay.en()} else {@trans.nbDays.en(days)}
}.getOrElse {∞}
} •
@if(g.variant.exotic) {
@(if (g.variant == chess.variant.KingOfTheHill) g.variant.shortName else g.variant.name)
} else {
@g.perfType.map(_.name)
} • @g.rated.fold(trans.rated.en(), trans.casual.en())
</span>
</div>
<div class="buttons">
<form action="@routes.Setup.join(g.id)" method="post">
<button name="submit" type="submit" class="submit button" data-icon="E" title="@trans.joinTheGame.en()"></button>
</form>
<a class="button decline" href="@routes.Setup.decline(g.id)" data-icon="L" title="@trans.decline.en()"></a>
</div>
</div>

View File

@ -220,7 +220,6 @@ GET /setup/ai controllers.Setup.aiForm
POST /setup/ai controllers.Setup.ai
GET /setup/friend controllers.Setup.friendForm(user: Option[String] ?= None)
POST /setup/friend controllers.Setup.friend(user: Option[String] ?= None)
POST /setup/decline controllers.Setup.decline(gameId: String)
GET /setup/hook controllers.Setup.hookForm
POST /setup/hook/:uid/like/:gameId controllers.Setup.like(uid: String, gameId: String)
POST /setup/hook/:uid controllers.Setup.hook(uid: String)
@ -229,9 +228,10 @@ POST /setup/filter controllers.Setup.filter
GET /setup/validate-fen controllers.Setup.validateFen
# Challenge
GET /challenge/$fullId<\w{12}>/await controllers.Setup.await(fullId: String, user: Option[String] ?= None)
GET /challenge/$fullId<\w{12}>/cancel controllers.Setup.cancel(fullId: String)
POST /challenge/$id<\w{8}>/join controllers.Setup.join(id: String)
GET /challenge controllers.Challenge.all
# GET /challenge/$fullId<\w{12}>/await controllers.Setup.await(fullId: String, user: Option[String] ?= None)
# GET /challenge/$fullId<\w{12}>/cancel controllers.Setup.cancel(fullId: String)
# POST /challenge/$id<\w{8}>/join controllers.Setup.join(id: String)
# Video
GET /video controllers.Video.index

View File

@ -12,12 +12,7 @@ final class ChallengeApi(
import BSONHandlers._
def findByChallengerId(userId: String): Fu[List[Challenge]] =
coll.find(BSONDocument("challenger.id" -> userId))
.sort(BSONDocument("createdAt" -> -1))
.cursor[Challenge]().collect[List]()
def insert(c: Challenge) =
def insert(c: Challenge): Funit =
coll.insert(c) >> c.challenger.right.toOption.?? { challenger =>
findByChallengerId(challenger.id).flatMap {
case challenges if challenges.size <= maxPerUser => funit
@ -25,6 +20,11 @@ final class ChallengeApi(
}
}
def findByChallengerId(userId: String): Fu[List[Challenge]] =
coll.find(BSONDocument("challenger.id" -> userId))
.sort(BSONDocument("createdAt" -> -1))
.cursor[Challenge]().collect[List]()
def findByDestId(userId: String): Fu[List[Challenge]] =
coll.find(BSONDocument("destUserId" -> userId))
.sort(BSONDocument("createdAt" -> -1))

View File

@ -6,6 +6,10 @@ final class JsonView(getLightUser: String => Option[lila.common.LightUser]) {
import Challenge._
def all(in: List[Challenge], out: List[Challenge]) = Json.obj(
"in" -> in.map(apply),
"out" -> out.map(apply))
def apply(c: Challenge) = Json.obj(
"id" -> c.id,
"challenger" -> c.challenger.right.toOption.map { u =>

View File

@ -63,11 +63,6 @@ case class MarkCheater(userId: String)
case class MarkBooster(userId: String)
}
package setup {
case class RemindChallenge(gameId: String, from: String, to: String)
case class DeclineChallenge(gameId: String)
}
package captcha {
case object AnyCaptcha
case class GetCaptcha(id: String)

View File

@ -161,8 +161,6 @@ private[round] final class Socket(
case AnalysisAvailable => notifyAll("analysisAvailable")
case lila.hub.actorApi.setup.DeclineChallenge(_) => notifyAll("declined")
case Quit(uid) =>
members get uid foreach { member =>
quit(uid)

View File

@ -80,9 +80,6 @@ private[round] final class SocketHandler(
case ("moretime", _) => round(Moretime(playerId))
case ("outoftime", _) => round(Outoftime)
case ("bye", _) => socket ! Bye(ref.color)
case ("challenge", o) => ((o str "d") |@| member.userId) apply {
case (to, from) => hub.actor.challenger ! lila.hub.actorApi.setup.RemindChallenge(gameId, from, to)
}
case ("talk", o) => o str "d" foreach { text =>
messenger.owner(gameId, member, text, socket)
}

View File

@ -11,7 +11,6 @@ import lila.i18n.I18nDomain
import lila.lobby.actorApi.{ AddHook, AddSeek }
import lila.lobby.Hook
import lila.user.{ User, UserContext }
import lila.challenge.Challenge
import makeTimeout.short
import tube.{ userConfigTube, anonConfigTube }

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 perfStat; do
for app in challenge insight editor puzzle round analyse lobby tournament tournamentSchedule opening simul perfStat; do
cd ui/$app
npm install --no-optional && 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 = 'LichessChallenge';
gulp.task('prod', function() {
return browserify('./src/main.js', {
standalone: standalone
}).bundle()
.on('error', onError)
.pipe(source('lichess.challenge.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.challenge.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.challenge.js'))
.pipe(gulp.dest(destination));
}
return rebundle();
});
gulp.task('default', ['watch']);

View File

@ -0,0 +1,33 @@
{
"name": "challenge",
"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",
"challenge"
],
"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"
}
}

View File

@ -0,0 +1,17 @@
var m = require('mithril');
var socket = require('./socket');
var xhr = require('./xhr');
module.exports = function(env) {
this.data = env.data;
this.userId = env.userId;
this.socket = new socket(env.socketSend, this);
this.vm = {
};
this.trans = lichess.trans(env.i18n);
};

View File

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

View File

@ -0,0 +1,18 @@
var m = require('mithril');
var xhr = require('./xhr');
module.exports = function(send, ctrl) {
this.send = send;
var handlers = {
};
this.receive = function(type, data) {
if (handlers[type]) {
handlers[type](data);
return true;
}
return false;
}.bind(this);
};

View File

@ -0,0 +1,6 @@
var m = require('mithril');
module.exports = function(ctrl) {
var d = ctrl.data;
return m('div.challenges', 'hehe');
};

View File

@ -0,0 +1,13 @@
var m = require('mithril');
var xhrConfig = function(xhr) {
xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
xhr.setRequestHeader('Accept', 'application/vnd.lichess.v1+json');
}
function uncache(url) {
return url + '?_=' + new Date().getTime();
}
module.exports = {
};