Merge branch 'master' into atomic_chess
* master: (65 commits) upgrade chessground, enable autoCastle in round tournament style tweaks lazy load sounds fix detection of simul pass formatted dates to mithril build fix lobby now playing mithril key tweak tournament CSS start tournament clock immediately, slightly trim down WS messages fix tournament UI bugs tournament UI: help mithril with element keys tournament UI: watch last games tournament missing translation actor efficiency: listen to StartGame in round socket hub fix funny bug when round socket receives alien games reset round socket user id on game start - fixes #178 tweak Pov priority and simul detection lt "lietuvių kalba" translation #11659. Author: patrimpas. sv "svenska" translation #11658. Author: Titanoboa. There was a typo, and "Rating" has been translated as the same throughout the other translations, so I kept consistent for #91.. add note about sound control in the preferences page fix chessground on puzzle page ... Conflicts: modules/chess public/javascripts/vendor/chessground.min.js ui/analyse/package.json ui/editor/package.json ui/lobby/package.json ui/puzzle/package.json ui/round/package.json
|
@ -54,7 +54,7 @@ object Round extends LilaController with TheftPrevention {
|
|||
PreventTheft(pov) {
|
||||
(pov.game.tournamentId ?? TournamentRepo.byId) zip
|
||||
Env.game.crosstableApi(pov.game) zip
|
||||
otherPovs(pov.gameId) flatMap {
|
||||
(!pov.game.isTournament ?? otherPovs(pov.gameId)) flatMap {
|
||||
case ((tour, crosstable), playing) =>
|
||||
Env.api.roundApi.player(pov, Env.api.version, playing) map { data =>
|
||||
Ok(html.round.player(pov, data, tour = tour, cross = crosstable, playing = playing))
|
||||
|
@ -64,7 +64,10 @@ object Round extends LilaController with TheftPrevention {
|
|||
Redirect(routes.Setup.await(fullId)).fuccess
|
||||
)
|
||||
},
|
||||
api = apiVersion => Env.api.roundApi.player(pov, apiVersion, Nil) map { Ok(_) }
|
||||
api = apiVersion => {
|
||||
if (pov.game.playableByAi) env.roundMap ! Tell(pov.game.id, AiPlay)
|
||||
Env.api.roundApi.player(pov, apiVersion, Nil) map { Ok(_) }
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
package controllers
|
||||
|
||||
import play.api.data.Form
|
||||
import play.api.libs.json.JsValue
|
||||
import play.api.libs.json._
|
||||
import play.api.mvc._
|
||||
|
||||
import lila.api.Context
|
||||
|
@ -18,11 +18,6 @@ object Tournament extends LilaController {
|
|||
|
||||
private def tournamentNotFound(implicit ctx: Context) = NotFound(html.tournament.notFound())
|
||||
|
||||
protected def TourOptionFuRedirect[A](fua: Fu[Option[A]])(op: A => Fu[Call])(implicit ctx: Context) =
|
||||
fua flatMap {
|
||||
_.fold(tournamentNotFound(ctx).fuccess)(a => op(a) map { b => Redirect(b) })
|
||||
}
|
||||
|
||||
val home = Open { implicit ctx =>
|
||||
fetchTournaments zip repo.scheduled zip UserRepo.allSortToints(10) map {
|
||||
case ((((created, started), finished), scheduled), leaderboard) =>
|
||||
|
@ -34,7 +29,7 @@ object Tournament extends LilaController {
|
|||
val system = sysStr flatMap {
|
||||
case "arena" => System.Arena.some
|
||||
case "swiss" => System.Swiss.some
|
||||
case _ => none
|
||||
case _ => none
|
||||
}
|
||||
Ok(html.tournament.faqPage(system)).fuccess
|
||||
}
|
||||
|
@ -47,131 +42,54 @@ object Tournament extends LilaController {
|
|||
}
|
||||
|
||||
private def fetchTournaments =
|
||||
env allCreatedSorted true zip repo.noPasswordStarted zip repo.finished(20)
|
||||
env allCreatedSorted true zip repo.publicStarted zip repo.finished(20)
|
||||
|
||||
def show(id: String) = Open { implicit ctx =>
|
||||
repo byId id flatMap {
|
||||
_ match {
|
||||
case Some(tour: Created) => showCreated(tour) map { Ok(_) }
|
||||
case Some(tour: Started) => showStarted(tour) map { Ok(_) }
|
||||
case Some(tour: Finished) => showFinished(tour) map { Ok(_) }
|
||||
case _ => tournamentNotFound.fuccess
|
||||
_.fold(tournamentNotFound.fuccess) { tour =>
|
||||
env.version(tour.id) zip
|
||||
env.jsonView(tour) zip
|
||||
chatOf(tour) map {
|
||||
case ((version, data), chat) => html.tournament.show(tour, version, data, chat)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private def showCreated(tour: Created)(implicit ctx: Context) =
|
||||
env.version(tour.id) zip chatOf(tour) map {
|
||||
case (version, chat) => html.tournament.show.created(tour, version, chat)
|
||||
}
|
||||
|
||||
private def showStarted(tour: Started)(implicit ctx: Context) =
|
||||
env.version(tour.id) zip
|
||||
chatOf(tour) zip
|
||||
GameRepo.games(tour recentGameIds 4) zip
|
||||
tour.userCurrentPov(ctx.me).??(GameRepo.pov) map {
|
||||
case (((version, chat), games), pov) =>
|
||||
html.tournament.show.started(tour, version, chat, games, pov)
|
||||
}
|
||||
|
||||
private def showFinished(tour: Finished)(implicit ctx: Context) =
|
||||
env.version(tour.id) zip
|
||||
chatOf(tour) zip
|
||||
GameRepo.games(tour recentGameIds 4) map {
|
||||
case ((version, chat), games) =>
|
||||
html.tournament.show.finished(tour, version, chat, games)
|
||||
}
|
||||
|
||||
def join(id: String) = AuthBody { implicit ctx =>
|
||||
implicit me =>
|
||||
NoEngine {
|
||||
TourOptionFuRedirect(repo enterableById id) { tour =>
|
||||
fuccess {
|
||||
if (tour.hasPassword) routes.Tournament.joinPassword(id)
|
||||
else {
|
||||
env.api.join(tour, me, none)
|
||||
routes.Tournament.show(tour.id)
|
||||
}
|
||||
negotiate(
|
||||
html = repo enterableById id map {
|
||||
case None => tournamentNotFound
|
||||
case Some(tour) =>
|
||||
env.api.join(tour, me)
|
||||
Redirect(routes.Tournament.show(tour.id))
|
||||
},
|
||||
api = _ => OptionFuOk(repo enterableById id) { tour =>
|
||||
env.api.join(tour, me)
|
||||
fuccess(Json.obj("ok" -> true))
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
def joinPasswordForm(id: String) = Auth { implicit ctx =>
|
||||
implicit me => NoEngine {
|
||||
repo createdById id flatMap {
|
||||
_.fold(tournamentNotFound(ctx).fuccess) { tour =>
|
||||
renderJoinPassword(tour, env.forms.joinPassword) map { Ok(_) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def joinPassword(id: String) = AuthBody { implicit ctx =>
|
||||
implicit me =>
|
||||
NoEngine {
|
||||
implicit val req = ctx.body
|
||||
repo createdById id flatMap {
|
||||
_.fold(tournamentNotFound(ctx).fuccess) { tour =>
|
||||
env.forms.joinPassword.bindFromRequest.fold(
|
||||
err => renderJoinPassword(tour, err) map { BadRequest(_) },
|
||||
password => {
|
||||
env.api.join(tour, me, password.some)
|
||||
fuccess(Redirect(routes.Tournament.show(tour.id)))
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private def renderJoinPassword(tour: Created, form: Form[_])(implicit ctx: Context) =
|
||||
env version tour.id map { html.tournament.joinPassword(tour, form, _) }
|
||||
|
||||
def withdraw(id: String) = Auth { implicit ctx =>
|
||||
me =>
|
||||
TourOptionFuRedirect(repo byId id) { tour =>
|
||||
OptionResult(repo byId id) { tour =>
|
||||
env.api.withdraw(tour, me.id)
|
||||
fuccess(routes.Tournament.show(tour.id))
|
||||
Ok(Json.obj("ok" -> true)) as JSON
|
||||
}
|
||||
}
|
||||
|
||||
def earlyStart(id: String) = Auth { implicit ctx =>
|
||||
implicit me =>
|
||||
TourOptionFuRedirect(repo.createdByIdAndCreator(id, me.id)) { tour =>
|
||||
OptionResult(repo.createdByIdAndCreator(id, me.id)) { tour =>
|
||||
env.api startIfReady tour
|
||||
fuccess(routes.Tournament show tour.id)
|
||||
Ok(Json.obj("ok" -> true)) as JSON
|
||||
}
|
||||
}
|
||||
|
||||
def reload(id: String) = Open { implicit ctx =>
|
||||
OptionFuOk(repo byId id) {
|
||||
case tour: Created => reloadCreated(tour)
|
||||
case tour: Started => reloadStarted(tour)
|
||||
case tour: Finished => reloadFinished(tour)
|
||||
}
|
||||
}
|
||||
|
||||
private def reloadCreated(tour: Created)(implicit ctx: Context) = fuccess {
|
||||
html.tournament.show.inner(none)(html.tournament.show.createdInner(tour))
|
||||
}
|
||||
|
||||
private def reloadStarted(tour: Started)(implicit ctx: Context) =
|
||||
GameRepo.games(tour recentGameIds 4) zip
|
||||
tour.userCurrentPov(ctx.me).??(GameRepo.pov) map {
|
||||
case (games, pov) => {
|
||||
val pairings = html.tournament.pairings(tour)
|
||||
val inner = html.tournament.show.startedInner(tour, games, pov)
|
||||
html.tournament.show.inner(pairings.some)(inner)
|
||||
}
|
||||
}
|
||||
|
||||
private def reloadFinished(tour: Finished)(implicit ctx: Context) =
|
||||
GameRepo games (tour recentGameIds 4) map { games =>
|
||||
val pairings = html.tournament.pairings(tour)
|
||||
val inner = html.tournament.show.finishedInner(tour, games)
|
||||
html.tournament.show.inner(pairings.some)(inner)
|
||||
}
|
||||
|
||||
def form = Auth { implicit ctx =>
|
||||
me =>
|
||||
NoEngine {
|
||||
|
@ -191,7 +109,7 @@ object Tournament extends LilaController {
|
|||
}
|
||||
}
|
||||
|
||||
def websocket(id: String) = Socket[JsValue] { implicit ctx =>
|
||||
def websocket(id: String, apiVersion: Int) = Socket[JsValue] { implicit ctx =>
|
||||
~(getInt("version") |@| get("sri") apply {
|
||||
case (version, uid) => env.socketHandler.join(id, version, uid, ctx.me)
|
||||
})
|
||||
|
|
|
@ -44,8 +44,9 @@ trait SetupHelper { self: I18nHelper =>
|
|||
variantTuple(Variant.FromPosition)
|
||||
|
||||
def translatedVariantChoicesWithFenAndKingOfTheHill(implicit ctx: Context) =
|
||||
translatedVariantChoicesWithFen(ctx) :+
|
||||
variantTuple(Variant.KingOfTheHill)
|
||||
translatedVariantChoices(ctx) :+
|
||||
variantTuple(Variant.KingOfTheHill) :+
|
||||
variantTuple(Variant.FromPosition)
|
||||
|
||||
def translatedVariantChoicesWithVariantsAndFen(implicit ctx: Context) =
|
||||
translatedVariantChoicesWithVariants :+
|
||||
|
|
|
@ -7,8 +7,8 @@
|
|||
<div class="signup_box">
|
||||
<h1 class="lichess_title">Close your account</h1>
|
||||
<p class="explanation">
|
||||
Are you sure you want to close your account? Closing your account is a permanent decision.
|
||||
You will no longer be able to login, and your profile page will no longer be accessible.
|
||||
Are you sure you want to close your account? Closing your account is a permanent decision.
|
||||
You will no longer be able to login, and your profile page will no longer be accessible.
|
||||
</p>
|
||||
<form action="@routes.Account.closeConfirm" method="POST">
|
||||
<br /><br />
|
||||
|
|
|
@ -89,6 +89,14 @@
|
|||
</li>
|
||||
</ul>
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<legend>Sound</legend>
|
||||
<ul>
|
||||
<li>
|
||||
The sound control is in the top bar of every page, on the right side.
|
||||
</li>
|
||||
</ul>
|
||||
</fieldset>
|
||||
<p data-icon="E"> Your preferences have been saved.</p>
|
||||
</form>
|
||||
</div>
|
||||
|
|
|
@ -29,6 +29,7 @@ LichessEditor(document.getElementById('board_editor'), {
|
|||
trans.loadPosition,
|
||||
trans.whitePlays,
|
||||
trans.blackPlays,
|
||||
trans.continueFromHere,
|
||||
trans.playWithTheMachine,
|
||||
trans.playWithAFriend,
|
||||
trans.analysis
|
||||
|
|
|
@ -1,54 +0,0 @@
|
|||
@(tour: lila.tournament.StartedOrFinished)(implicit ctx: Context)
|
||||
|
||||
@import lila.tournament.arena.ScoringSystem
|
||||
|
||||
<div class="standing_wrap scroll-shadow-soft">
|
||||
<table class="slist standing @if(tour.scheduled) { scheduled }">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="large">@trans.standing() (@tour.nbPlayers)</th>
|
||||
<th class="legend">
|
||||
<span class="streakstarter">Streak starter</span>
|
||||
<span class="double">Double points</span>
|
||||
</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@tour.rankedPlayers.map {
|
||||
case (rank, player) => {
|
||||
@defining((
|
||||
if(tour.isFinished && rank == 1) "winner" else if (player.withdraw) "withdraw" else "",
|
||||
ScoringSystem.scoreSheet(tour, player.id)
|
||||
)) {
|
||||
case (flag, scoreSheet) => {
|
||||
<tr @if(ctx.userId.exists(player.id==)) { class="me" }>
|
||||
<td class="name">
|
||||
@if(player.withdraw) {
|
||||
<span data-icon="b" title="@trans.withdraw()"></span>
|
||||
} else {
|
||||
@if(tour.isFinished && rank == 1) {
|
||||
<span data-icon="g" title="@trans.winner()"></span>
|
||||
} else {
|
||||
<span class="rank">@rank</span>
|
||||
}
|
||||
}
|
||||
@userInfosLink(player.id, none, withOnline = false)
|
||||
</td>
|
||||
<td class="sheet">
|
||||
@scoreSheet.scores.take(20).reverse.map { score =>
|
||||
<span class="@score.flag.toString.toLowerCase">@score.value</span>
|
||||
}
|
||||
</td>
|
||||
<td class="total">
|
||||
<strong@if(tour.isRunning && scoreSheet.onFire) { class="is-gold" data-icon="Q" }>@scoreSheet.total</strong>
|
||||
</td>
|
||||
</tr>
|
||||
<tr><td class="around-bar" colspan="3"><div class="bar" data-value="@scoreSheet.total"></div></td></tr>
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
|
@ -1,8 +1,12 @@
|
|||
@(nbPlayers: Option[String] = Some("all"), rated: Option[Boolean] = None, system: Option[lila.tournament.System] = None)
|
||||
@(nbPlayers: Option[String] = Some("all"), rated: Option[Boolean] = None, system: Option[lila.tournament.System] = None, privateId: Option[String] = None)
|
||||
|
||||
@import lila.tournament.System
|
||||
|
||||
<article class="faq">
|
||||
@privateId.map { id =>
|
||||
<h2>This is a private tournament</h2>
|
||||
Share this URL to let people join it: @netBaseUrl@routes.Tournament.show(id)
|
||||
}
|
||||
@nbPlayers.map { players =>
|
||||
<h2>When will the tournament start?</h2>
|
||||
As soon as @players players join it.
|
||||
|
@ -82,7 +86,7 @@
|
|||
}
|
||||
|
||||
<h2>How does it end?</h2>
|
||||
The tournament has a countdown clock.
|
||||
The tournament has a countdown clock.
|
||||
When it reaches zero, the tournament rankings are frozen, and the winner is announced.
|
||||
Games in progress must be finished, however they don't count for the tournament.
|
||||
|
||||
|
|
|
@ -5,23 +5,20 @@
|
|||
@moreJs = {
|
||||
<script>
|
||||
$(function() {
|
||||
var $pass = $('#tournament tr.password');
|
||||
var $check = $('#tournament #isprivate');
|
||||
var $time = $('#tournament tr.time td');
|
||||
var $minutes = $('#tournament tr.minutes td');
|
||||
function showPassword() {
|
||||
function showPrivate() {
|
||||
if ($check.prop('checked')) {
|
||||
$pass.show(500);
|
||||
$time.html($('#tournament .private_time').html());
|
||||
$minutes.html($('#tournament .private_minutes').html());
|
||||
} else {
|
||||
$pass.hide().find('input').val('');
|
||||
$time.html($('#tournament .public_time').html());
|
||||
$minutes.html($('#tournament .public_minutes').html());
|
||||
}
|
||||
};
|
||||
$check.on('change', showPassword);
|
||||
showPassword();
|
||||
$check.on('change', showPrivate);
|
||||
showPrivate();
|
||||
})
|
||||
</script>
|
||||
}
|
||||
|
@ -40,11 +37,7 @@ moreJs = moreJs) {
|
|||
<table>
|
||||
<tr>
|
||||
<th><label for="isprivate">@trans.isPrivate()</label></th>
|
||||
<td><input type="checkbox" id="isprivate" @if(form("password").value.filter(_.nonEmpty).isDefined) { checked } /></td>
|
||||
</tr>
|
||||
<tr class="password">
|
||||
<th><label for="@form("password").id">@trans.password()</label></th>
|
||||
<td>@base.input(form("password"))</td>
|
||||
<td><input type="checkbox" name="private" id="isprivate" @if(form("private").value.isDefined) { checked } /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th><label for="@form("system").id">System</label></th>
|
||||
|
|
|
@ -1,10 +0,0 @@
|
|||
@(games: List[Game])(implicit ctx: Context)
|
||||
|
||||
<div class="game_list playing">
|
||||
@games.map { g =>
|
||||
<div>
|
||||
@gameFen(g, g.firstPlayer.color)
|
||||
@game.vstext(g)(ctx.some)
|
||||
</div>
|
||||
}
|
||||
</div>
|
|
@ -27,7 +27,7 @@
|
|||
@tournament.layout(
|
||||
title = trans.tournaments.str(),
|
||||
side = side.some) {
|
||||
<div id="tournament" data-href="@routes.Tournament.homeReload()">
|
||||
<div id="tournament_list" data-href="@routes.Tournament.homeReload()">
|
||||
@tournament.homeInner(createds, starteds, finisheds)
|
||||
</div>
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
<span class="joined label" data-icon="E"> JOINED</span>
|
||||
} else {
|
||||
<form class="inline" action="@routes.Tournament.join(tour.id)" method="POST">
|
||||
<button data-icon="@tour.hasPassword.fold("a", "G")" type="submit" class="submit button"> @trans.join()</button>
|
||||
<button data-icon="G" type="submit" class="submit button text">@trans.join()</button>
|
||||
</form>
|
||||
}
|
||||
}
|
||||
|
@ -54,10 +54,7 @@
|
|||
@if(ctx.isAuth) {
|
||||
<tr class="create">
|
||||
<td colspan="5">
|
||||
<br />
|
||||
<a href="@routes.Tournament.form()" class="action button" data-icon="g"> @trans.createANewTournament()</a>
|
||||
<br />
|
||||
<br />
|
||||
<a href="@routes.Tournament.form()" class="action button text">@trans.createANewTournament()</a>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
|
|
|
@ -1,21 +0,0 @@
|
|||
@(tour: lila.tournament.Created, form: Form[_], version: Int)(implicit ctx: Context)
|
||||
|
||||
@formHtml = {
|
||||
<div class="content_box_content join_password">
|
||||
<h2>A password is required to join this tournament</h2>
|
||||
<form action="@routes.Tournament.joinPassword(tour.id)" method="POST">
|
||||
@base.input(form("password"), "Password".some)
|
||||
<input type="submit" class="submit" value="@trans.join()" />
|
||||
<a href="@routes.Tournament.show(tour.id)">@trans.cancel()</a>
|
||||
</form>
|
||||
</div>
|
||||
}
|
||||
|
||||
@tournament.show.layout(
|
||||
tour = tour,
|
||||
side = tournament.side(tour),
|
||||
chat = None,
|
||||
version = version,
|
||||
title = s"${trans.join.str()}${tour.fullName}") {
|
||||
@tournament.show.createdInner(tour, formHtml.some)
|
||||
}
|
14
app/views/tournament/jsI18n.scala.html
Normal file
|
@ -0,0 +1,14 @@
|
|||
@()(implicit ctx: Context)
|
||||
@Html(J.stringify(i18nJsObject(
|
||||
trans.standing,
|
||||
trans.isPrivate,
|
||||
trans.tournamentIsStarting,
|
||||
trans.tournamentPoints,
|
||||
trans.viewTournament,
|
||||
trans.backToTournament,
|
||||
trans.waitingForNbPlayers,
|
||||
trans.join,
|
||||
trans.withdraw,
|
||||
trans.joinTheGame,
|
||||
trans.finished
|
||||
)))
|
|
@ -1,4 +1,4 @@
|
|||
@(title: String, moreJs: Html = Html(""), side: Option[Html] = None, chat: Option[Html] = None, underchat: Option[Html] = None)(body: Html)(implicit ctx: Context)
|
||||
@(title: String, moreJs: Html = Html(""), side: Option[Html] = None, chat: Option[Html] = None, underchat: Option[Html] = None, chessground: Boolean = true)(body: Html)(implicit ctx: Context)
|
||||
|
||||
@moreCss = {
|
||||
@cssTag("tournament.css")
|
||||
|
@ -11,6 +11,7 @@ moreJs = moreJs,
|
|||
side = side,
|
||||
chat = chat,
|
||||
active = siteMenu.tournament.some,
|
||||
underchat = underchat) {
|
||||
underchat = underchat,
|
||||
chessground = chessground) {
|
||||
@body
|
||||
}
|
||||
|
|
|
@ -1,13 +0,0 @@
|
|||
@(tour: lila.tournament.Tournament)(implicit ctx: Context)
|
||||
|
||||
@showUser(p: lila.tournament.Pairing, u: String) = {
|
||||
<span class="@p.finished.fold(p.draw.fold("draw", p.wonBy(u).fold("win", "loss")), "playing")">@usernameOrId(u)</span>
|
||||
}
|
||||
|
||||
<div class="pairings">
|
||||
@tour.pairings.map { pairing =>
|
||||
<a class="revert-underline" href="@routes.Round.watcher(pairing.gameId, "white")">
|
||||
@showUser(pairing, pairing.user1) <em>vs</em> @showUser(pairing, pairing.user2)
|
||||
</a>
|
||||
}
|
||||
</div>
|
35
app/views/tournament/show.scala.html
Normal file
|
@ -0,0 +1,35 @@
|
|||
@(tour: lila.tournament.Tournament, socketVersion: Int, data: play.api.libs.json.JsObject, chat: Option[lila.chat.UserChat])(implicit ctx: Context)
|
||||
|
||||
@underchat = {
|
||||
<div class="watchers" data-icon="v">
|
||||
<span class="list inline_userlist"></span>
|
||||
</div>
|
||||
}
|
||||
|
||||
@moreJs = {
|
||||
@jsAt(s"compiled/lichess.tournament${isProd??(".min")}.js")
|
||||
@embedJs {
|
||||
lichess = lichess || {};
|
||||
lichess.tournament = {
|
||||
data: @Html(play.api.libs.json.Json.stringify(data)),
|
||||
i18n: @jsI18n(),
|
||||
socketVersion: @socketVersion,
|
||||
userId: @Html(ctx.userId.fold("null")(id => s""""$id""""))
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@tournament.layout(
|
||||
title = tour.fullName,
|
||||
side = tournament.side(tour).some,
|
||||
chat = chat.map(c => base.chat(c, trans.chatRoom.str())),
|
||||
underchat = underchat.some,
|
||||
moreJs = moreJs,
|
||||
chessground = false) {
|
||||
<div id="tournament" @if(tour.scheduled) { class="scheduled" }></div>
|
||||
@if(!tour.isRunning && !tour.isFinished) {
|
||||
<div id="tournament_faq" class="none">
|
||||
@faq(!tour.scheduled option tour.minPlayers.toString, tour.rated.some, tour.system.some, tour.`private`.option(tour.id))
|
||||
</div>
|
||||
}
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
@(tour: lila.tournament.Created, version: Int, chat: Option[lila.chat.UserChat])(implicit ctx: Context)
|
||||
|
||||
@tournament.show.layout(
|
||||
tour = tour,
|
||||
side = tournament.side(tour),
|
||||
chat = chat,
|
||||
version = version,
|
||||
title = tour.fullName) {
|
||||
|
||||
@tournament.show.createdInner(tour, none)
|
||||
}
|
|
@ -1,56 +0,0 @@
|
|||
@(tour: lila.tournament.Created, form: Option[Html] = None)(implicit ctx: Context)
|
||||
|
||||
<h1 data-icon="g">
|
||||
@tour.fullName
|
||||
@if(tour.isSwiss) { [beta] }
|
||||
@if(tour.hasPassword) {
|
||||
<span data-icon="a"> @trans.isPrivate()</span>
|
||||
}
|
||||
</h1>
|
||||
@form
|
||||
<div class="user_list">
|
||||
<table class="slist user_list">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="large">
|
||||
@tour.schedule.map { s =>
|
||||
Starting @momentFromNow(s.at)
|
||||
}.getOrElse {
|
||||
@if(tour.enoughPlayersToStart) {
|
||||
@trans.tournamentIsStarting()
|
||||
} else {
|
||||
@trans.waitingForNbPlayers(tour.missingPlayers)
|
||||
}
|
||||
}
|
||||
</th>
|
||||
@ctx.me.map { me =>
|
||||
<th>
|
||||
@if(tour contains me) {
|
||||
@if(tour.isCreator(me.id) && tour.enoughPlayersToEarlyStart) {
|
||||
<form class="inline" action="@routes.Tournament.earlyStart(tour.id)" method="POST">
|
||||
<input type="submit" class="submit button" value="Early Start" />
|
||||
</form>
|
||||
}
|
||||
<form class="inline" action="@routes.Tournament.withdraw(tour.id)" method="POST">
|
||||
<button type="submit" class="submit button" data-icon="b"> @trans.withdraw()</button>
|
||||
</form>
|
||||
} else {
|
||||
<form class="inline" action="@routes.Tournament.join(tour.id)" method="POST">
|
||||
<button type="submit" class="submit button" data-icon="@tour.hasPassword.fold("a", "G")"> @trans.join()</button>
|
||||
</form>
|
||||
}
|
||||
</th>
|
||||
}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@tour.players.map { player =>
|
||||
<tr>
|
||||
<td colspan="2">@userInfosLink(player.id, player.rating.some)</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<br />
|
||||
<div class="content_box_content">@tournament.faq(!tour.scheduled option tour.minPlayers.toString, tour.rated.some, Some(tour.system))</div>
|
|
@ -1,11 +0,0 @@
|
|||
@(tour: lila.tournament.Finished, version: Int, chat: Option[lila.chat.UserChat], games: List[Game])(implicit ctx: Context)
|
||||
|
||||
@tournament.show.layout(
|
||||
tour = tour,
|
||||
side = tournament.side(tour),
|
||||
chat = chat,
|
||||
version = version,
|
||||
pairings = tournament.pairings(tour).some,
|
||||
title = tour.fullName) {
|
||||
@tournament.show.finishedInner(tour, games)
|
||||
}
|
|
@ -1,17 +0,0 @@
|
|||
@(tour: lila.tournament.Finished, games: List[Game])(implicit ctx: Context)
|
||||
|
||||
<span class="title_tag">@trans.finished()</span>
|
||||
|
||||
<h1 data-icon="g"> @tour.fullName</h1>
|
||||
|
||||
@tour.system match {
|
||||
case lila.tournament.System.Arena => {
|
||||
@tournament.arenaStanding(tour)
|
||||
}
|
||||
|
||||
case lila.tournament.System.Swiss => {
|
||||
@tournament.swissStanding(tour)
|
||||
}
|
||||
}
|
||||
|
||||
@tournament.games(games)
|
|
@ -1,10 +0,0 @@
|
|||
@(pairings: Option[Html])(body: Html)(implicit ctx: Context)
|
||||
|
||||
@pairings.map { s =>
|
||||
<div id="tournament_side" class="scroll-shadow-soft">@s</div>
|
||||
}
|
||||
<div id="tournament_main">
|
||||
<div class="content_box no_padding tournament_box tournament_show">
|
||||
@body
|
||||
</div>
|
||||
</div>
|
|
@ -1,22 +0,0 @@
|
|||
@(tour: lila.tournament.Tournament, chat: Option[lila.chat.UserChat], title: String, version: Int, side: Html, pairings: Option[Html] = None)(body: Html)(implicit ctx: Context)
|
||||
|
||||
@underchat = {
|
||||
<div class="watchers" data-icon="v">
|
||||
<span class="list inline_userlist"></span>
|
||||
</div>
|
||||
}
|
||||
|
||||
@tournament.layout(
|
||||
title = title,
|
||||
side = side.some,
|
||||
chat = chat.map(c => base.chat(c, trans.chatRoom.str())),
|
||||
underchat = underchat.some) {
|
||||
<div
|
||||
id="tournament"
|
||||
@if(tour.scheduled) { class="scheduled" }
|
||||
data-href="@routes.Tournament.reload(tour.id)"
|
||||
data-socket-url="@routes.Tournament.websocket(tour.id)">
|
||||
@tournament.show.inner(pairings)(body)
|
||||
</div>
|
||||
@embedJs("var _ld_ = " + tournamentJsData(tour, version, ctx.me))
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
@(tour: lila.tournament.Started, version: Int, chat: Option[lila.chat.UserChat], games: List[Game], pov: Option[Pov])(implicit ctx: Context)
|
||||
|
||||
@tournament.show.layout(
|
||||
tour = tour,
|
||||
side = tournament.side(tour),
|
||||
chat = chat,
|
||||
version = version,
|
||||
pairings = tournament.pairings(tour).some,
|
||||
title = tour.fullName) {
|
||||
@tournament.show.startedInner(tour, games, pov)
|
||||
}
|
|
@ -1,28 +0,0 @@
|
|||
@(tour: lila.tournament.Started, games: List[Game], pov: Option[Pov])(implicit ctx: Context)
|
||||
|
||||
<div class="tournament_clock title_tag" data-time="@tour.remainingSeconds">
|
||||
<div class="time" data-icon="p">@tour.clockStatus</div>
|
||||
</div>
|
||||
|
||||
<h1 data-icon="g">
|
||||
@tour.fullName
|
||||
@if(tour.isSwiss) { [beta] }
|
||||
</h1>
|
||||
|
||||
@pov.map { p =>
|
||||
<a class="is pov button glowing" href="@routes.Round.player(p.fullId)">
|
||||
You are playing @usernameOrAnon(p.opponent.userId)
|
||||
<span class="pov_join" data-icon="G"> @trans.joinTheGame()</span>
|
||||
</a>
|
||||
}
|
||||
|
||||
@tour.system match {
|
||||
case lila.tournament.System.Arena => {
|
||||
@tournament.arenaStanding(tour)
|
||||
}
|
||||
case lila.tournament.System.Swiss => {
|
||||
@tournament.swissStanding(tour)
|
||||
}
|
||||
}
|
||||
|
||||
@tournament.games(games)
|
|
@ -8,16 +8,6 @@
|
|||
}.getOrElse {
|
||||
@momentFormat(tour.createdAt)
|
||||
}
|
||||
@tour.password.map { password =>
|
||||
<br />
|
||||
<span data-icon="a">
|
||||
@if(ctx.userId == tour.createdBy.some) {
|
||||
@trans.password(): @tour.password
|
||||
} else {
|
||||
@trans.isPrivate()
|
||||
}
|
||||
</span>
|
||||
}
|
||||
<br /><br />
|
||||
<span data-icon="p"> @tour.clock.show</span>,
|
||||
@game.variantLink(tour.variant, variantName(tour.variant)),
|
||||
|
@ -27,18 +17,6 @@
|
|||
(<a href="@routes.Tournament.help(tour.system.toString.toLowerCase.some)">help</a>)
|
||||
<br /><br />
|
||||
@trans.duration(): @tour.minutes minutes
|
||||
@if(tour.isRunning) {
|
||||
<br /><br />
|
||||
@if(tour isActive ctx.me) {
|
||||
<form action="@routes.Tournament.withdraw(tour.id)" method="POST">
|
||||
<button type="submit" class="submit button strong" data-icon="b"> @trans.withdraw()</button>
|
||||
</form>
|
||||
} else {
|
||||
<form class="inline" action="@routes.Tournament.join(tour.id)" method="POST">
|
||||
<button data-icon="@tour.hasPassword.fold("a", "G")" type="submit" class="submit button"> @trans.join()</button>
|
||||
</form>
|
||||
}
|
||||
}
|
||||
@tour.winner.filter(_ => tour.isFinished).map { winner =>
|
||||
<br /><br />
|
||||
@trans.winner(): @userInfosLink(winner.id, none)
|
||||
|
|
|
@ -1,49 +0,0 @@
|
|||
@(tour: lila.tournament.StartedOrFinished)(implicit ctx: Context)
|
||||
|
||||
@import lila.tournament.swiss.SwissSystem
|
||||
|
||||
<div class="standing_wrap scroll-shadow-soft">
|
||||
<table class="slist standing @if(tour.scheduled) { scheduled }">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="large">@trans.standing() (@tour.nbPlayers)</th>
|
||||
<th></th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@defining(SwissSystem.scoreSheets(tour)) { scoreSheets =>
|
||||
@tour.rankedPlayers.map {
|
||||
case (rank, player) => {
|
||||
@defining(scoreSheets(player.id)) { scoreSheet =>
|
||||
<tr @if(ctx.userId.exists(player.id==)) { class="me" }>
|
||||
<td class="name">
|
||||
@if(player.withdraw) {
|
||||
<span data-icon="b" title="@trans.withdraw()"></span>
|
||||
} else {
|
||||
@if(tour.isFinished && rank == 1) {
|
||||
<span data-icon="g" title="@trans.winner()"></span>
|
||||
} else {
|
||||
<span class="rank">@rank</span>
|
||||
}
|
||||
}
|
||||
@userInfosLink(player.id, none, withOnline = false)
|
||||
</td>
|
||||
<td class="sheet">
|
||||
@scoreSheet.scores.reverse.map { score =>
|
||||
<span class="normal">@score.repr</span>
|
||||
}
|
||||
</td>
|
||||
<td class="total">
|
||||
<strong>@scoreSheet.totalRepr</strong>
|
||||
<span data-hint='Tie-breaker "Neustadtl" score' class="hint--bottom-left">(@scoreSheet.neustadtlRepr)</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr><td class="around-bar" colspan="3"><div class="bar" data-value="@scoreSheet.total"></div></td></tr>
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
|
@ -87,6 +87,7 @@ oneDay=Aдзін дзень
|
|||
nbDays=%s Дзён
|
||||
nbHours=%s Гадзін
|
||||
time=Час
|
||||
rating=Рэйтынг
|
||||
username=Карыстальнік
|
||||
password=Пароль
|
||||
haveAnAccount=Ужо зарэгістраваны?
|
||||
|
@ -168,6 +169,7 @@ tournaments=Турніры
|
|||
tournamentPoints=Турнірныя балы
|
||||
viewTournament=Назіраць за турнірам
|
||||
backToTournament=Вараціцца да турніру
|
||||
backToGame=Вярнуцца да гульні
|
||||
freeOnlineChessGamePlayChessNowInACleanInterfaceNoRegistrationNoAdsNoPluginRequiredPlayChessWithComputerFriendsOrRandomOpponents=Бясплатныя он-лайн шахматы. Гуляй у шахматы з зручным інтэрфэйсам. Без рэгістрацыі, без рэклямы, без дадатковых праграм. Гуляй у шахматы з камп'ютарам, сябрамі ці выпадковым супернікам.
|
||||
teams=Каманды
|
||||
nbMembers=%s чальцоў
|
||||
|
@ -217,7 +219,7 @@ unblock=Разблякаваць
|
|||
followsYou=Прыхільнікі
|
||||
xStartedFollowingY=%s пачалі сачыць %s
|
||||
nbFollowers=%s прыхільнікаў
|
||||
nbFollowing=%s сочаць
|
||||
nbFollowing=%s сочыць
|
||||
more=Больш
|
||||
memberSince=Чалец ад
|
||||
lastLogin=Апошні логін
|
||||
|
|
|
@ -88,6 +88,7 @@ oneDay=Jedan dan
|
|||
nbDays=%s dana
|
||||
nbHours=%s sati
|
||||
time=Vrijeme
|
||||
rating=Rejting
|
||||
username=Korisničko ime
|
||||
password=Lozinka
|
||||
haveAnAccount=Već imate račun?
|
||||
|
|
|
@ -88,6 +88,7 @@ oneDay=En dag
|
|||
nbDays=%s dage
|
||||
nbHours=%s timer
|
||||
time=Tid
|
||||
rating=Rating
|
||||
username=Brugernavn
|
||||
password=Password
|
||||
haveAnAccount=Har du en konto?
|
||||
|
|
|
@ -88,6 +88,7 @@ oneDay=Un día
|
|||
nbDays=%s días
|
||||
nbHours=%s horas
|
||||
time=Tempo
|
||||
rating=Puntuación
|
||||
username=Nome de usuario
|
||||
password=Contrasinal
|
||||
haveAnAccount=Tén unha conta?
|
||||
|
|
|
@ -48,6 +48,7 @@ theComputerAnalysisHasFailed=Analysis computore defuit
|
|||
viewTheComputerAnalysis=Videre analysim computore
|
||||
requestAComputerAnalysis=Quaerere analysim computore
|
||||
computerAnalysis=Analysis computore
|
||||
analysis=Explicare
|
||||
blunders=Errores magni
|
||||
mistakes=Errores
|
||||
inaccuracies=Inaccuratitates
|
||||
|
@ -79,6 +80,7 @@ minutesPerSide=Minutae pro utriusque lusore
|
|||
variant=Varietas
|
||||
timeControl=Temporis curatio
|
||||
nbDays=%s dies
|
||||
nbHours=%s horae
|
||||
time=Tempum
|
||||
username=Usoris nomen
|
||||
password=Tessera
|
||||
|
@ -147,10 +149,13 @@ takebackPropositionAccepted=Accepta est motus reversionis conditio
|
|||
takebackPropositionCanceled=Sublata est motus reversionis conditio
|
||||
yourOpponentProposesATakeback=Motus reversionem adversarius offert
|
||||
bookmarkThisGame=Lusionem bookmarkare
|
||||
search=Quaerere
|
||||
advancedSearch=Investigatio multiplex
|
||||
tournament=Certamen
|
||||
tournamentPoints=Certaminis puncta
|
||||
viewTournament=Certamen videre
|
||||
backToTournament=Referre ad torneamento
|
||||
backToGame=Referre ad ludo
|
||||
freeOnlineChessGamePlayChessNowInACleanInterfaceNoRegistrationNoAdsNoPluginRequiredPlayChessWithComputerFriendsOrRandomOpponents=Liberi scacci in rete. Lude jam scaccos super clarissimas pagines. Nec conventum, nec praeconia mercatoria, nec plugin necesse sunt. Lude scaccos sive cum computatro, sive cum amicis, sive cum aleatoriis adversariis.
|
||||
teams=Legiones
|
||||
nbMembers=%s sodales
|
||||
|
@ -180,6 +185,14 @@ continueFromHere=Hinc persequi
|
|||
importGame=Lusionem imponere
|
||||
nbImportedGames=%s impositae lusiones
|
||||
thisIsAChessCaptcha=Hic ludus CAPTCHA est
|
||||
retry=Adparare iterum
|
||||
findFriends=Reperire amici
|
||||
follow=Spectare
|
||||
following=Spectans
|
||||
unfollow=Non Spectare
|
||||
block=Operire
|
||||
unblock=Non operire
|
||||
nbFollowers=%s spectatores
|
||||
nbFollowing=%s spectans
|
||||
more=Multus
|
||||
isPrivate=Privatis
|
||||
|
|
|
@ -81,12 +81,14 @@ players=pl@Y3Rs
|
|||
minutesPerSide=mINU7e$ p3R siDe
|
||||
variant=V@ri@n7
|
||||
timeControl=7IM3 Con7R0l
|
||||
realTime=R341 71m3
|
||||
correspondence=(0|?|?3$|20|\||)3|\|(3
|
||||
daysPerTurn=|)4j$ |?3|? TU|?|\|
|
||||
oneDay=0|\|3 |)4Y
|
||||
nbDays=%s D4YS
|
||||
nbHours=%s |-|0u|?$
|
||||
time=7im3
|
||||
rating=R4t1ng
|
||||
username=us3r N@M3
|
||||
password=P@5$w0Rd
|
||||
haveAnAccount=h@V3 @n @Cc0unt?
|
||||
|
@ -168,6 +170,7 @@ tournaments=70uRn@M3N75
|
|||
tournamentPoints=70uRN@m3n7 P0In75
|
||||
viewTournament=vi3W 70uRN@m3n7
|
||||
backToTournament=b@ck 70 70URN@m3N7
|
||||
backToGame=R3turn 2 g4m3
|
||||
freeOnlineChessGamePlayChessNowInACleanInterfaceNoRegistrationNoAdsNoPluginRequiredPlayChessWithComputerFriendsOrRandomOpponents=FR33 0NLiN3 Ch35$ g@M3. Pl@Y Ch3$$ n0w iN @ Cl3@N in73RF@c3. N0 R3GI$7R@7I0N, n0 @D$, N0 pLUgin R3quIr3D. PL@y Ch35s with 7H3 C0Mpu73R, fRi3nD$ 0r R@NdoM 0pp0N3N7$.
|
||||
teams=73@M$
|
||||
nbMembers=%s mem83r$
|
||||
|
@ -304,3 +307,5 @@ thisPuzzleIsCorrect=7HI5 PuZZl3 i$ C0RR3c7 @nd in73r3$7INg
|
|||
thisPuzzleIsWrong=7hi$ PuzZl3 I5 wR0ng 0R 80RInG
|
||||
youHaveNbSecondsToMakeYourFirstMove=Y0u h@V3 %s 53c0nd5 2 m@k3 y0ur |=1r5t m0v3!
|
||||
nbGamesInPlay=%s 94|\/|3$ 1|\| |2|4j
|
||||
automaticallyProceedToNextGameAfterMoving=4ut0m4t1c411y pr0c33d 2 n3x7 g4m3 4ft3r m0v1ng
|
||||
autoSwitch=4ut0 sw1tch
|
||||
|
|
|
@ -82,12 +82,15 @@ minutesPerSide=Žaidimo trukmė
|
|||
variant=Variantas
|
||||
timeControl=Laiko kontrolė
|
||||
daysPerTurn=Dienos vienam ėjimui
|
||||
oneDay=Viena diena
|
||||
time=Laikas
|
||||
rating=Reitingas
|
||||
username=Prisijungimo Vardas
|
||||
password=Slaptažodis
|
||||
haveAnAccount=Turite paskyrą?
|
||||
allYouNeedIsAUsernameAndAPassword=Viskas ko reikia yra jūsų pasirinktas vardas ir slaptažodis.
|
||||
changePassword=Pasikeisti slaptažodį
|
||||
changeEmail=Pakeisti el. pašto adresą
|
||||
learnMoreAboutLichess=Sužinokite daugiau apie Lichess
|
||||
rank=Rangas
|
||||
gamesPlayed=Sužaisti žaidimai
|
||||
|
@ -159,6 +162,7 @@ tournaments=Turnyrai
|
|||
tournamentPoints=Turnyro taškai
|
||||
viewTournament=Stebėti turnyrą
|
||||
backToTournament=Grįžti į turnyrą
|
||||
backToGame=Grįžti į žaidimą
|
||||
freeOnlineChessGamePlayChessNowInACleanInterfaceNoRegistrationNoAdsNoPluginRequiredPlayChessWithComputerFriendsOrRandomOpponents=Nemokamas šachmatų žaidimas tinkle. Žaiskite šachmatais patrauklioje vartotojo sąsajoje. Nebūtina registracija, nėra reklamos, nereikia jokių priedų. Žaiskite šachmatais prieš kompiuterį, su draugu arba su atsitiktiniais varžovais.
|
||||
teams=Komandos
|
||||
nbMembers=%s narių
|
||||
|
|
|
@ -88,6 +88,7 @@ oneDay=Én dag
|
|||
nbDays=%s dager
|
||||
nbHours=%s timer
|
||||
time=Tid
|
||||
rating=Rating
|
||||
username=Brukernavn
|
||||
password=Passord
|
||||
haveAnAccount=Har du en konto?
|
||||
|
|
|
@ -88,6 +88,7 @@ oneDay=En dag
|
|||
nbDays=%s dagar
|
||||
nbHours=%s timmar
|
||||
time=Tid
|
||||
rating=Rating
|
||||
username=Användarnamn
|
||||
password=Lösenord
|
||||
haveAnAccount=Har du ett konto?
|
||||
|
@ -187,7 +188,7 @@ teamBestPlayers=Topplista
|
|||
teamRecentMembers=Nya medlemmar
|
||||
xJoinedTeamY=%s gick med i lag %s
|
||||
xCreatedTeamY=%s skapade lag %s
|
||||
averageElo=Medelrakning
|
||||
averageElo=Medelrating
|
||||
location=Plats
|
||||
settings=Inställningar
|
||||
filterGames=Filtrera partier
|
||||
|
|
|
@ -118,12 +118,9 @@ GET /tournament/reload controllers.Tournament.homeReload
|
|||
GET /tournament/new controllers.Tournament.form
|
||||
POST /tournament/new controllers.Tournament.create
|
||||
GET /tournament/$id<\w{8}> controllers.Tournament.show(id: String)
|
||||
GET /tournament/$id<\w{8}>/socket controllers.Tournament.websocket(id: String)
|
||||
GET /tournament/$id<\w{8}>/socket/v:apiVersion controllers.Tournament.websocket(id: String, apiVersion: Int)
|
||||
POST /tournament/$id<\w{8}>/join controllers.Tournament.join(id: String)
|
||||
GET /tournament/$id<\w{8}>/join/password controllers.Tournament.joinPasswordForm(id: String)
|
||||
POST /tournament/$id<\w{8}>/join/password controllers.Tournament.joinPassword(id: String)
|
||||
POST /tournament/$id<\w{8}>/withdraw controllers.Tournament.withdraw(id: String)
|
||||
GET /tournament/$id<\w{8}>/reload controllers.Tournament.reload(id: String)
|
||||
POST /tournament/$id<\w{8}>/early-start controllers.Tournament.earlyStart(id: String)
|
||||
GET /tournament/help controllers.Tournament.help(system: Option[String] ?= None)
|
||||
|
||||
|
|
|
@ -41,7 +41,8 @@ private[api] final class RoundApi(
|
|||
}
|
||||
|
||||
private def withOtherPovs(otherPovs: List[Pov])(json: JsObject) =
|
||||
if (otherPovs.isEmpty) json else json + ("simul" -> JsBoolean(true))
|
||||
if (otherPovs.exists(_.game.nonAi)) json + ("simul" -> JsBoolean(true))
|
||||
else json
|
||||
|
||||
private def withNote(note: String)(json: JsObject) =
|
||||
if (note.isEmpty) json else json + ("note" -> JsString(note))
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit ec8c85122d752da9c3b5bbdddc8f8061bc9784c3
|
||||
Subproject commit e0989842735e58803c2ac20b2548c4d8f8c03ac2
|
|
@ -26,8 +26,8 @@ final class Cached(
|
|||
private implicit val userHandler = User.userBSONHandler
|
||||
|
||||
private val isPlayingSimulCache = AsyncCache[String, Boolean](
|
||||
f = userId => GameRepo.countPlayingRealTime(userId) map (1 <),
|
||||
timeToLive = 10.seconds)
|
||||
f = userId => GameRepo.countPlayingRealTimeHuman(userId) map (1 <),
|
||||
timeToLive = 15.seconds)
|
||||
|
||||
val isPlayingSimul: String => Fu[Boolean] = isPlayingSimulCache.apply _
|
||||
|
||||
|
|
|
@ -84,8 +84,7 @@ final class Env(
|
|||
|
||||
def onStart(gameId: String) = GameRepo game gameId foreach {
|
||||
_ foreach { game =>
|
||||
// nobody needs it for now
|
||||
// system.lilaBus.publish(actorApi.StartGame(game), 'startGame)
|
||||
system.lilaBus.publish(actorApi.StartGame(game), 'startGame)
|
||||
game.userIds foreach { userId =>
|
||||
system.lilaBus.publish(
|
||||
actorApi.UserStartGame(userId, game),
|
||||
|
|
|
@ -136,8 +136,8 @@ object GameRepo {
|
|||
_.sortBy(_.updatedAt).lastOption flatMap { Pov(_, user) }
|
||||
}
|
||||
|
||||
def countPlayingRealTime(userId: String): Fu[Int] =
|
||||
$count(Query.nowPlaying(userId) ++ Query.clock(true))
|
||||
def countPlayingRealTimeHuman(userId: String): Fu[Int] =
|
||||
$count(Query.nowPlaying(userId) ++ Query.clock(true) ++ Query.noAi)
|
||||
|
||||
def setTv(id: ID) {
|
||||
$update.fieldUnchecked(id, F.tvAt, $date(DateTime.now))
|
||||
|
|
|
@ -51,12 +51,14 @@ object Pov {
|
|||
def apply(game: Game, user: lila.user.User): Option[Pov] =
|
||||
game player user map { apply(game, _) }
|
||||
|
||||
def priority(pov: Pov) =
|
||||
if (pov.isMyTurn) {
|
||||
def priority(pov: Pov) = {
|
||||
val base = if (pov.isMyTurn) {
|
||||
if (pov.hasMoved) pov.remainingSeconds.getOrElse(Int.MaxValue - 1)
|
||||
else 10 // first move has priority over games with more than 10s left
|
||||
}
|
||||
else Int.MaxValue
|
||||
if (pov.game.hasClock) base - 1000 else base
|
||||
}
|
||||
}
|
||||
|
||||
case class PovRef(gameId: String, color: Color) {
|
||||
|
|
|
@ -48,6 +48,10 @@ object Query {
|
|||
def user(u: String) = Json.obj(F.playerUids -> u)
|
||||
def users(u: Seq[String]) = Json.obj(F.playerUids -> $in(u))
|
||||
|
||||
val noAi = Json.obj(
|
||||
"p0.ai" -> $exists(false),
|
||||
"p1.ai" -> $exists(false))
|
||||
|
||||
def nowPlaying(u: String) = Json.obj(F.playingUids -> u)
|
||||
|
||||
def recentlyPlayingWithClock(u: String) =
|
||||
|
|
|
@ -29,11 +29,14 @@ object Rewind {
|
|||
castleLastMoveTime = CastleLastMoveTime(
|
||||
castles = rewindedHistory.castles,
|
||||
lastMove = rewindedHistory.lastMove,
|
||||
lastMoveTime = Some(nowSeconds - game.createdAt.getSeconds.toInt),
|
||||
lastMoveTime = Some(((nowMillis - game.createdAt.getMillis) / 100).toInt),
|
||||
check = if (rewindedSituation.check) rewindedSituation.kingPos else None),
|
||||
binaryMoveTimes = BinaryFormat.moveTime write (game.moveTimes take rewindedGame.turns),
|
||||
status = game.status,
|
||||
clock = game.clock map (_.takeback))
|
||||
Progress(game, newGame, newGame.clock.map(Event.Clock.apply).toList)
|
||||
Progress(game, newGame, List(
|
||||
newGame.clock.map(Event.Clock.apply),
|
||||
newGame.correspondenceClock.map(Event.CorrespondenceClock.apply)
|
||||
).flatten)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -53,6 +53,7 @@ private[i18n] final class JsDump(
|
|||
keys.challengeToPlay,
|
||||
keys.youNeedAnAccountToDoThat,
|
||||
keys.createANewTournament,
|
||||
keys.withdraw,
|
||||
keys.join,
|
||||
keys.joinTheGame,
|
||||
keys.tournamentIsStarting)
|
||||
|
|
|
@ -41,7 +41,7 @@ private[lobby] final class Socket(
|
|||
addMember(uid, member)
|
||||
sender ! Connected(enumerator, member)
|
||||
|
||||
case ReloadTournaments(html) => notifyTournaments(html)
|
||||
case ReloadTournaments(html) => notifyAll(makeMessage("tournaments", html))
|
||||
|
||||
case NewForumPost => notifyAll("reload_forum")
|
||||
|
||||
|
@ -88,10 +88,6 @@ private[lobby] final class Socket(
|
|||
|
||||
private def playerUrl(fullId: String) = s"/$fullId"
|
||||
|
||||
private def notifyTournaments(html: String) {
|
||||
notifyAll(makeMessage("tournaments", html))
|
||||
}
|
||||
|
||||
private def notifySeeks() {
|
||||
notifyAll(makeMessage("reload_seeks"))
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@ import scala.concurrent.duration._
|
|||
|
||||
import actorApi.{ GetSocketStatus, SocketStatus }
|
||||
import lila.common.PimpedConfig._
|
||||
import lila.hub.actorApi.map.Ask
|
||||
import lila.hub.actorApi.map.{Ask,Tell,TellAll}
|
||||
import lila.socket.actorApi.GetVersion
|
||||
import makeTimeout.large
|
||||
|
||||
|
@ -88,13 +88,15 @@ final class Env(
|
|||
isPlayingSimul = isPlayingSimul)
|
||||
def receive: Receive = ({
|
||||
case msg@lila.chat.actorApi.ChatLine(id, line) =>
|
||||
self ! lila.hub.actorApi.map.Tell(id take 8, msg)
|
||||
case m: lila.hub.actorApi.game.ChangeFeatured =>
|
||||
self ! lila.hub.actorApi.map.TellAll(m)
|
||||
self ! Tell(id take 8, msg)
|
||||
case msg: lila.hub.actorApi.game.ChangeFeatured =>
|
||||
self ! TellAll(msg)
|
||||
case msg: lila.game.actorApi.StartGame =>
|
||||
self ! Tell(msg.game.id, msg)
|
||||
}: Receive) orElse socketHubReceive
|
||||
}),
|
||||
name = SocketName)
|
||||
system.lilaBus.subscribe(actor, 'changeFeaturedGame)
|
||||
system.lilaBus.subscribe(actor, 'changeFeaturedGame, 'startGame)
|
||||
actor
|
||||
}
|
||||
|
||||
|
|
|
@ -62,7 +62,8 @@ final class JsonView(
|
|||
"check" -> game.check.map(_.key),
|
||||
"rematch" -> game.next,
|
||||
"source" -> game.source.map(sourceJson),
|
||||
"status" -> statusJson(game.status)),
|
||||
"status" -> statusJson(game.status),
|
||||
"tournamentId" -> game.tournamentId),
|
||||
"clock" -> game.clock.map(clockJson),
|
||||
"correspondence" -> game.correspondenceClock.map(correspondenceJson),
|
||||
"player" -> Json.obj(
|
||||
|
|
|
@ -10,7 +10,7 @@ import play.api.libs.json._
|
|||
|
||||
import actorApi._
|
||||
import lila.common.LightUser
|
||||
import lila.game.actorApi.UserStartGame
|
||||
import lila.game.actorApi.{ StartGame, UserStartGame }
|
||||
import lila.game.Event
|
||||
import lila.hub.actorApi.game.ChangeFeatured
|
||||
import lila.hub.TimeBomb
|
||||
|
@ -87,6 +87,11 @@ private[round] final class Socket(
|
|||
whitePlayer.userId = game.player(White).userId
|
||||
blackPlayer.userId = game.player(Black).userId
|
||||
|
||||
// from lilaBus 'startGame
|
||||
// sets definitive user ids
|
||||
// in case one joined after the socket creation
|
||||
case StartGame(game) => self ! SetGame(game.some)
|
||||
|
||||
case PingVersion(uid, v) =>
|
||||
timeBomb.delay
|
||||
ping(uid)
|
||||
|
|
|
@ -76,7 +76,8 @@ final class DataForm(val captcher: akka.actor.ActorSelection) extends lila.hub.C
|
|||
|
||||
private val lameSuffixes = List("-", "_")
|
||||
|
||||
private val lameUsernames = List(
|
||||
private val lameUsernames = for {
|
||||
base <- List(
|
||||
"hitler",
|
||||
"fuck",
|
||||
"penis",
|
||||
|
@ -99,7 +100,10 @@ final class DataForm(val captcher: akka.actor.ActorSelection) extends lila.hub.C
|
|||
"pussy",
|
||||
"slut",
|
||||
"whore",
|
||||
"nazi")
|
||||
"nazi",
|
||||
"morteza")
|
||||
replacement <- List("" -> "", "o" -> "0", "i" -> "1")
|
||||
} yield base.replace(replacement._1, replacement._2)
|
||||
}
|
||||
|
||||
object DataForm {
|
||||
|
|
|
@ -80,6 +80,7 @@ trait Positional { self: Config =>
|
|||
case sit@SituationPlus(Situation(board, _), _) => game.copy(
|
||||
variant = Variant.FromPosition,
|
||||
castleLastMoveTime = game.castleLastMoveTime.copy(
|
||||
lastMove = board.history.lastMove,
|
||||
castles = board.history.castles
|
||||
),
|
||||
turns = sit.turns)
|
||||
|
|
|
@ -23,16 +23,16 @@ private[setup] final class FriendJoiner(
|
|||
game.updatePlayer(color, _.withUser(u.id, PerfPicker.mainOrDefault(game)(u.perfs)))
|
||||
}
|
||||
for {
|
||||
p1 ← GameRepo.setUsers(g1.id, g1.player(_.white).userInfos, g1.player(_.black).userInfos) inject Progress(game, g1)
|
||||
p2 = p1 map (_.start)
|
||||
p3 = p2 + Event.RedirectOwner(
|
||||
_ ← GameRepo.setUsers(g1.id, g1.player(_.white).userInfos, g1.player(_.black).userInfos)
|
||||
p1 = Progress(game, g1.start)
|
||||
p2 = p1 + Event.RedirectOwner(
|
||||
!color,
|
||||
p2.game fullIdOf !color,
|
||||
AnonCookie.json(p2.game, !color))
|
||||
_ ← GameRepo save p3
|
||||
p1.game fullIdOf !color,
|
||||
AnonCookie.json(p1.game, !color))
|
||||
_ ← GameRepo save p2
|
||||
} yield {
|
||||
onStart(p3.game.id)
|
||||
Pov(p3.game, color) -> p3.events
|
||||
onStart(p2.game.id)
|
||||
Pov(p2.game, color) -> p2.events
|
||||
}
|
||||
} toValid "Can't join started game " + game.id
|
||||
}
|
||||
|
|
|
@ -28,7 +28,7 @@ object BSONHandlers {
|
|||
minPlayers = r int "minPlayers",
|
||||
variant = r.intO("variant").fold[Variant](Variant.default)(Variant.orDefault),
|
||||
mode = r.intO("mode").fold[Mode](Mode.default)(Mode.orDefault),
|
||||
password = r strO "password",
|
||||
`private` = r boolD "private",
|
||||
schedule = r.getO[Schedule]("schedule"),
|
||||
createdAt = r date "createdAt",
|
||||
createdBy = r str "createdBy")
|
||||
|
@ -40,24 +40,11 @@ object BSONHandlers {
|
|||
"minPlayers" -> o.minPlayers,
|
||||
"variant" -> o.variant.id,
|
||||
"mode" -> o.mode.id,
|
||||
"password" -> o.password,
|
||||
"private" -> w.boolO(o.`private`),
|
||||
"schedule" -> o.schedule,
|
||||
"createdAt" -> w.date(o.createdAt),
|
||||
"createdBy" -> w.str(o.createdBy))
|
||||
}
|
||||
|
||||
// private implicit val playerHandler = new BSON[Player] {
|
||||
// def reads(r: BSON.Reader) = Player(
|
||||
// id = r str "id",
|
||||
// rating = r int "rating",
|
||||
// withdraw = r boolD "withdraw",
|
||||
// score = r intD "score")
|
||||
// def writes(w: BSON.Writer, o: Player) = BSONDocument(
|
||||
// "id" -> o.id,
|
||||
// "rating" -> o.rating,
|
||||
// "withdraw" -> w.boolO(o.withdraw),
|
||||
// "score" -> w.intO(o.score))
|
||||
// }
|
||||
private implicit val playerBSONHandler = Macros.handler[Player]
|
||||
|
||||
private implicit val pairingHandler = new BSON[Pairing] {
|
||||
|
|
|
@ -45,7 +45,7 @@ final class DataForm(isDev: Boolean) {
|
|||
"variant" -> number.verifying(Set(Variant.Standard.id, Variant.Chess960.id, Variant.KingOfTheHill.id,
|
||||
Variant.ThreeCheck.id, Variant.Antichess.id, Variant.Atomic.id) contains _),
|
||||
"mode" -> optional(number.verifying(Mode.all map (_.id) contains _)),
|
||||
"password" -> optional(nonEmptyText)
|
||||
"private" -> optional(text.verifying("on" == _))
|
||||
)(TournamentSetup.apply)(TournamentSetup.unapply)
|
||||
.verifying("Invalid clock", _.validClock)
|
||||
.verifying("Increase tournament duration, or decrease game clock", _.validTiming)
|
||||
|
@ -56,12 +56,8 @@ final class DataForm(isDev: Boolean) {
|
|||
minPlayers = minPlayerDefault,
|
||||
system = System.default.id,
|
||||
variant = Variant.Standard.id,
|
||||
password = none,
|
||||
`private` = None,
|
||||
mode = Mode.Casual.id.some)
|
||||
|
||||
lazy val joinPassword = Form(single(
|
||||
"password" -> nonEmptyText
|
||||
))
|
||||
}
|
||||
|
||||
private[tournament] case class TournamentSetup(
|
||||
|
@ -72,7 +68,7 @@ private[tournament] case class TournamentSetup(
|
|||
system: Int,
|
||||
variant: Int,
|
||||
mode: Option[Int],
|
||||
password: Option[String]) {
|
||||
`private`: Option[String]) {
|
||||
|
||||
def validClock = (clockTime + clockIncrement) > 0
|
||||
|
||||
|
|
|
@ -45,6 +45,7 @@ final class Env(
|
|||
lazy val forms = new DataForm(isDev)
|
||||
|
||||
lazy val api = new TournamentApi(
|
||||
system = system,
|
||||
sequencers = sequencerMap,
|
||||
autoPairing = autoPairing,
|
||||
router = hub.actor.router,
|
||||
|
@ -67,11 +68,14 @@ final class Env(
|
|||
|
||||
lazy val cached = new Cached
|
||||
|
||||
lazy val jsonView = new JsonView(lightUser)
|
||||
|
||||
private val socketHub = system.actorOf(
|
||||
Props(new lila.socket.SocketHubActor.Default[Socket] {
|
||||
def mkActor(tournamentId: String) = new Socket(
|
||||
tournamentId = tournamentId,
|
||||
history = new History(ttl = HistoryMessageTtl),
|
||||
jsonView = jsonView,
|
||||
uidTimeout = UidTimeout,
|
||||
socketTimeout = SocketTimeout,
|
||||
lightUser = lightUser)
|
||||
|
@ -97,7 +101,7 @@ final class Env(
|
|||
socketHub ? Ask(tourId, GetVersion) mapTo manifest[Int]
|
||||
|
||||
val allCreatedSorted =
|
||||
lila.memo.AsyncCache.single(TournamentRepo.noPasswordCreatedSorted, timeToLive = CreatedCacheTtl)
|
||||
lila.memo.AsyncCache.single(TournamentRepo.publicCreatedSorted, timeToLive = CreatedCacheTtl)
|
||||
|
||||
val promotable =
|
||||
lila.memo.AsyncCache.single(TournamentRepo.promotable, timeToLive = CreatedCacheTtl)
|
||||
|
|
|
@ -5,28 +5,8 @@ import org.joda.time.DateTime
|
|||
// Metadata about running tournaments: who got byed, when a round completed, this sort of things.
|
||||
sealed abstract class Event(val id: Int) {
|
||||
def timestamp: DateTime
|
||||
def encode: RawEvent
|
||||
}
|
||||
|
||||
case class RoundEnd(timestamp: DateTime) extends Event(1) {
|
||||
def encode = RawEvent(id, timestamp, None)
|
||||
}
|
||||
case class RoundEnd(timestamp: DateTime) extends Event(1)
|
||||
|
||||
case class Bye(user: String, timestamp: DateTime) extends Event(10) {
|
||||
def encode = RawEvent(id, timestamp, Some(user))
|
||||
}
|
||||
|
||||
private[tournament] case class RawEvent(
|
||||
i: Int,
|
||||
t: DateTime,
|
||||
u: Option[String]) {
|
||||
|
||||
def decode: Option[Event] = roundEnd orElse bye
|
||||
|
||||
def roundEnd: Option[RoundEnd] = (i == 1) option RoundEnd(t)
|
||||
|
||||
def bye: Option[Bye] = for {
|
||||
usr <- u
|
||||
if i == 10
|
||||
} yield Bye(usr, t)
|
||||
}
|
||||
case class Bye(user: String, timestamp: DateTime) extends Event(10)
|
||||
|
|
113
modules/tournament/src/main/JsonView.scala
Normal file
|
@ -0,0 +1,113 @@
|
|||
package lila.tournament
|
||||
|
||||
import play.api.libs.json._
|
||||
|
||||
import lila.common.LightUser
|
||||
import lila.common.PimpedJson._
|
||||
import lila.game.{ Game, GameRepo }
|
||||
import lila.user.User
|
||||
|
||||
final class JsonView(
|
||||
getLightUser: String => Option[LightUser]) {
|
||||
|
||||
def apply(id: String): Fu[JsObject] =
|
||||
TournamentRepo byId id flatten s"No such tournament: $id" flatMap apply
|
||||
|
||||
def apply(tour: Tournament): Fu[JsObject] =
|
||||
lastGames(tour) map { games =>
|
||||
val sheets = tour.system.scoringSystem scoreSheets tour
|
||||
Json.obj(
|
||||
"id" -> tour.id,
|
||||
"createdBy" -> tour.createdBy,
|
||||
"system" -> tour.system.toString.toLowerCase,
|
||||
"fullName" -> tour.fullName,
|
||||
"private" -> tour.`private`,
|
||||
"schedule" -> tour.schedule.map(scheduleJson),
|
||||
"variant" -> tour.variant.key,
|
||||
"players" -> tour.rankedPlayers.map((playerJson(sheets) _).tupled),
|
||||
"winner" -> tour.winner.map(_.id),
|
||||
"pairings" -> tour.pairings.take(50).map(pairingJson),
|
||||
"isOpen" -> tour.isOpen,
|
||||
"isRunning" -> tour.isRunning,
|
||||
"isFinished" -> tour.isFinished,
|
||||
"lastGames" -> games.map(gameJson)) ++ specifics(tour)
|
||||
}
|
||||
|
||||
private def specifics(tour: Tournament) = tour match {
|
||||
case t: Created => Json.obj(
|
||||
"enoughPlayersToStart" -> t.enoughPlayersToStart,
|
||||
"enoughPlayersToEarlyStart" -> t.enoughPlayersToEarlyStart,
|
||||
"missingPlayers" -> (t.missingPlayers != -1).option(t.missingPlayers)
|
||||
).noNull
|
||||
case t: Started => Json.obj(
|
||||
"seconds" -> t.remainingSeconds)
|
||||
case _ => Json.obj()
|
||||
}
|
||||
|
||||
private def lastGames(tour: Tournament) = tour match {
|
||||
case t: StartedOrFinished => GameRepo.games(t recentGameIds 4)
|
||||
case _ => fuccess(Nil)
|
||||
}
|
||||
|
||||
private def scheduleJson(s: Schedule) = Json.obj(
|
||||
"seconds" -> s.inSeconds)
|
||||
|
||||
private def gameUserJson(player: lila.game.Player) = {
|
||||
val light = player.userId flatMap getLightUser
|
||||
Json.obj(
|
||||
"id" -> player.userId,
|
||||
"name" -> light.map(_.name),
|
||||
"title" -> light.map(_.title),
|
||||
"rating" -> player.rating
|
||||
).noNull
|
||||
}
|
||||
|
||||
private def gameJson(g: Game) = Json.obj(
|
||||
"id" -> g.id,
|
||||
"fen" -> (chess.format.Forsyth exportBoard g.toChess.board),
|
||||
"color" -> g.firstColor.name,
|
||||
"lastMove" -> ~g.castleLastMoveTime.lastMoveString,
|
||||
"user1" -> gameUserJson(g.firstPlayer),
|
||||
"user2" -> gameUserJson(g.secondPlayer))
|
||||
|
||||
private def sheetJson(sheet: ScoreSheet) = sheet match {
|
||||
case s: arena.ScoringSystem.Sheet => Json.obj(
|
||||
"scores" -> s.scores.take(20).reverse.map { score =>
|
||||
if (score.flag == arena.ScoringSystem.Normal) Json.arr(score.value)
|
||||
else Json.arr(score.value, score.flag.toString.toLowerCase)
|
||||
},
|
||||
"total" -> s.total,
|
||||
"fire" -> s.onFire.option(true)
|
||||
).noNull
|
||||
case s: swiss.SwissSystem.Sheet => Json.obj(
|
||||
"scores" -> s.scores.take(20).reverse.map(_.repr),
|
||||
"total" -> s.totalRepr,
|
||||
"neustadtl" -> s.neustadtlRepr)
|
||||
}
|
||||
|
||||
private def playerJson(sheets: Map[String, ScoreSheet])(rank: Int, p: Player) = {
|
||||
val light = getLightUser(p.id)
|
||||
Json.obj(
|
||||
"rank" -> rank,
|
||||
"id" -> p.id,
|
||||
"username" -> light.map(_.name),
|
||||
"title" -> light.map(_.title),
|
||||
"rating" -> p.rating,
|
||||
"withdraw" -> p.withdraw.option(true),
|
||||
"score" -> p.score,
|
||||
"sheet" -> sheets.get(p.id).map(sheetJson)).noNull
|
||||
}
|
||||
|
||||
private def pairingUserJson(userId: String) = {
|
||||
val name = getLightUser(userId).fold(userId)(_.name)
|
||||
if (name == userId) Json.arr(userId)
|
||||
else Json.arr(userId, name)
|
||||
}
|
||||
|
||||
private def pairingJson(p: Pairing) = Json.obj(
|
||||
"gameId" -> p.gameId,
|
||||
"status" -> p.status.id,
|
||||
"user1" -> pairingUserJson(p.user1),
|
||||
"user2" -> pairingUserJson(p.user2),
|
||||
"winner" -> p.winner)
|
||||
}
|
|
@ -60,7 +60,6 @@ private[tournament] final class Organizer(
|
|||
if (!tour.isAlmostFinished) {
|
||||
withUserIds(tour.id) { ids =>
|
||||
(tour.activeUserIds intersect ids) |> { users =>
|
||||
|
||||
tour.system.pairingSystem.createPairings(tour, users) onSuccess {
|
||||
case (pairings, events) =>
|
||||
pairings.toNel foreach { pairings =>
|
||||
|
|
|
@ -7,6 +7,8 @@ case class Schedule(
|
|||
speed: Schedule.Speed,
|
||||
at: DateTime) {
|
||||
|
||||
def inSeconds: Int = (at.getSeconds - nowSeconds).toInt
|
||||
|
||||
def name = s"${freq.toString} ${speed.toString}"
|
||||
|
||||
def sameSpeed(other: Schedule) = speed == other.speed
|
||||
|
|
|
@ -18,6 +18,7 @@ import lila.socket.{ SocketActor, History, Historical }
|
|||
private[tournament] final class Socket(
|
||||
tournamentId: String,
|
||||
val history: History[Messadata],
|
||||
jsonView: JsonView,
|
||||
lightUser: String => Option[LightUser],
|
||||
uidTimeout: Duration,
|
||||
socketTimeout: Duration) extends SocketActor[Member](uidTimeout) with Historical[Member, Messadata] {
|
||||
|
@ -41,10 +42,6 @@ private[tournament] final class Socket(
|
|||
|
||||
case Reload => notifyReload
|
||||
|
||||
case Start => notifyVersion("start", JsNull, Messadata())
|
||||
|
||||
case ReloadPage => notifyVersion("reloadPage", JsNull, Messadata())
|
||||
|
||||
case WithUserIds(f) => f(userIds)
|
||||
|
||||
case PingVersion(uid, v) => {
|
||||
|
@ -91,7 +88,9 @@ private[tournament] final class Socket(
|
|||
|
||||
case NotifyReload =>
|
||||
delayedReloadNotification = false
|
||||
notifyAll(makeMessage("reload"))
|
||||
jsonView(tournamentId) foreach { obj =>
|
||||
notifyVersion("reload", obj, Messadata())
|
||||
}
|
||||
}
|
||||
|
||||
def notifyCrowd {
|
||||
|
@ -104,7 +103,9 @@ private[tournament] final class Socket(
|
|||
def notifyReload {
|
||||
if (!delayedReloadNotification) {
|
||||
delayedReloadNotification = true
|
||||
context.system.scheduler.scheduleOnce(1 second, self, NotifyReload)
|
||||
// keep the delay low for immediate response to join/withdraw,
|
||||
// but still debounce to avoid tourney start message rush
|
||||
context.system.scheduler.scheduleOnce(50 millis, self, NotifyReload)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -15,7 +15,7 @@ private[tournament] case class Data(
|
|||
minPlayers: Int,
|
||||
variant: Variant,
|
||||
mode: Mode,
|
||||
password: Option[String],
|
||||
`private`: Boolean,
|
||||
schedule: Option[Schedule],
|
||||
createdAt: DateTime,
|
||||
createdBy: String)
|
||||
|
@ -31,6 +31,7 @@ sealed trait Tournament {
|
|||
def isOpen: Boolean = false
|
||||
def isRunning: Boolean = false
|
||||
def isFinished: Boolean = false
|
||||
def `private`: Boolean = data.`private`
|
||||
|
||||
def name = data.name
|
||||
def fullName = s"$name $system"
|
||||
|
@ -44,8 +45,6 @@ sealed trait Tournament {
|
|||
def mode = data.mode
|
||||
def speed = Speed(clock.chessClock.some)
|
||||
def rated = mode.rated
|
||||
def password = data.password
|
||||
def hasPassword = password.isDefined
|
||||
def schedule = data.schedule
|
||||
def scheduled = data.schedule.isDefined
|
||||
|
||||
|
@ -69,6 +68,7 @@ sealed trait Tournament {
|
|||
def isActive(user: User): Boolean = isActive(user.id)
|
||||
def isActive(user: Option[User]): Boolean = ~user.map(isActive)
|
||||
def missingPlayers = minPlayers - players.size
|
||||
def rankedPlayers: RankedPlayers = system.scoringSystem.rank(this, players)
|
||||
|
||||
def createdBy = data.createdBy
|
||||
def createdAt = data.createdAt
|
||||
|
@ -90,16 +90,14 @@ sealed trait Enterable extends Tournament {
|
|||
|
||||
def withPlayers(s: Players): Enterable
|
||||
|
||||
def join(user: User, pass: Option[String]): Valid[Enterable]
|
||||
def join(user: User): Valid[Enterable]
|
||||
|
||||
def withdraw(userId: String): Valid[Enterable]
|
||||
|
||||
def joinNew(user: User, pass: Option[String]): Valid[Enterable] = contains(user).fold(
|
||||
def joinNew(user: User): Valid[Enterable] = contains(user).fold(
|
||||
!!("User %s is already part of the tournament" format user.id),
|
||||
(pass != password).fold(
|
||||
!!("Invalid tournament password"),
|
||||
withPlayers(players :+ Player.make(user, perfLens)).success
|
||||
))
|
||||
withPlayers(players :+ Player.make(user, perfLens)).success
|
||||
)
|
||||
|
||||
def ejectCheater(userId: String): Option[Enterable] =
|
||||
activePlayers.find(_.id == userId) map { player =>
|
||||
|
@ -111,14 +109,11 @@ sealed trait Enterable extends Tournament {
|
|||
}
|
||||
|
||||
sealed trait StartedOrFinished extends Tournament {
|
||||
type RankedPlayers = List[(Int, Player)]
|
||||
|
||||
def startedAt: DateTime
|
||||
def withPlayers(s: Players): StartedOrFinished
|
||||
def refreshPlayers: StartedOrFinished
|
||||
|
||||
def rankedPlayers: RankedPlayers = system.scoringSystem.rank(this, players)
|
||||
|
||||
def winner = players.headOption
|
||||
def winnerUserId = winner map (_.id)
|
||||
|
||||
|
@ -161,7 +156,7 @@ case class Created(
|
|||
|
||||
def asScheduled = schedule map { Scheduled(this, _) }
|
||||
|
||||
def join(user: User, pass: Option[String]) = joinNew(user, pass)
|
||||
def join(user: User) = joinNew(user)
|
||||
}
|
||||
|
||||
case class Scheduled(tour: Created, schedule: Schedule) {
|
||||
|
@ -243,16 +238,14 @@ case class Started(
|
|||
def withPlayers(s: Players) = copy(players = s)
|
||||
def refreshPlayers = withPlayers(Player refresh this)
|
||||
|
||||
def join(user: User, pass: Option[String]) = joinNew(user, pass) orElse joinBack(user, pass)
|
||||
def join(user: User) = joinNew(user) orElse joinBack(user)
|
||||
|
||||
private def joinBack(user: User, pass: Option[String]) = withdrawnPlayers.find(_ is user) match {
|
||||
private def joinBack(user: User) = withdrawnPlayers.find(_ is user) match {
|
||||
case None => !!("User %s is already part of the tournament" format user.id)
|
||||
case Some(player) => (pass != password).fold(
|
||||
!!("Invalid tournament password"),
|
||||
withPlayers(players map {
|
||||
case p if p is player => p.unWithdraw
|
||||
case p => p
|
||||
}).success)
|
||||
case Some(player) => withPlayers(players map {
|
||||
case p if p is player => p.unWithdraw
|
||||
case p => p
|
||||
}).success
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -282,7 +275,7 @@ object Tournament {
|
|||
system: System,
|
||||
variant: Variant,
|
||||
mode: Mode,
|
||||
password: Option[String]): Created = {
|
||||
`private`: Boolean): Created = {
|
||||
val tour = Created(
|
||||
id = Random nextStringUppercase 8,
|
||||
data = Data(
|
||||
|
@ -293,7 +286,7 @@ object Tournament {
|
|||
createdAt = DateTime.now,
|
||||
variant = variant,
|
||||
mode = mode,
|
||||
password = password,
|
||||
`private` = `private`,
|
||||
minutes = minutes,
|
||||
schedule = None,
|
||||
minPlayers = minPlayers),
|
||||
|
@ -311,7 +304,7 @@ object Tournament {
|
|||
createdAt = DateTime.now,
|
||||
variant = Variant.Standard,
|
||||
mode = Mode.Rated,
|
||||
password = None,
|
||||
`private` = false,
|
||||
minutes = minutes,
|
||||
schedule = Some(sched),
|
||||
minPlayers = 0),
|
||||
|
|
|
@ -1,13 +1,15 @@
|
|||
package lila.tournament
|
||||
|
||||
import akka.actor.{ ActorRef, ActorSelection }
|
||||
import akka.actor.{ Props, ActorRef, ActorSelection, ActorSystem }
|
||||
import akka.pattern.{ ask, pipe }
|
||||
import chess.{ Mode, Variant }
|
||||
import org.joda.time.DateTime
|
||||
import play.api.libs.json._
|
||||
import scala.concurrent.duration._
|
||||
import scalaz.NonEmptyList
|
||||
|
||||
import actorApi._
|
||||
import lila.common.Debouncer
|
||||
import lila.db.api._
|
||||
import lila.game.{ Game, GameRepo }
|
||||
import lila.hub.actorApi.lobby.ReloadTournaments
|
||||
|
@ -20,6 +22,7 @@ import lila.user.{ User, UserRepo }
|
|||
import makeTimeout.short
|
||||
|
||||
private[tournament] final class TournamentApi(
|
||||
system: ActorSystem,
|
||||
sequencers: ActorRef,
|
||||
autoPairing: AutoPairing,
|
||||
router: ActorSelection,
|
||||
|
@ -55,19 +58,18 @@ private[tournament] final class TournamentApi(
|
|||
minutes = setup.minutes,
|
||||
minPlayers = setup.minPlayers,
|
||||
mode = setup.mode.fold(Mode.default)(Mode.orDefault),
|
||||
password = setup.password,
|
||||
`private` = setup.`private`.isDefined,
|
||||
system = System orDefault setup.system,
|
||||
variant = Variant orDefault setup.variant)
|
||||
TournamentRepo.insert(created).void >>-
|
||||
(withdrawIds foreach socketReload) >>-
|
||||
reloadSiteSocket >>-
|
||||
lobbyReload inject created
|
||||
publish() inject created
|
||||
}
|
||||
|
||||
def createScheduled(schedule: Schedule): Funit =
|
||||
(Schedule durationFor schedule) ?? { minutes =>
|
||||
val created = Tournament.schedule(schedule, minutes)
|
||||
TournamentRepo.insert(created).void >>- reloadSiteSocket >>- lobbyReload
|
||||
TournamentRepo.insert(created).void >>- publish()
|
||||
}
|
||||
|
||||
def startIfReady(created: Created) {
|
||||
|
@ -84,9 +86,8 @@ private[tournament] final class TournamentApi(
|
|||
case Some(created) =>
|
||||
val started = created.start
|
||||
TournamentRepo.update(started).void >>-
|
||||
sendTo(started.id, Start) >>-
|
||||
reloadSiteSocket >>-
|
||||
lobbyReload
|
||||
sendTo(started.id, Reload) >>-
|
||||
publish()
|
||||
case None => fufail("Can't start missing tournament " + oldTour.id)
|
||||
}
|
||||
}
|
||||
|
@ -95,18 +96,18 @@ private[tournament] final class TournamentApi(
|
|||
def wipeEmpty(created: Created): Funit = created.isEmpty ?? doWipe(created)
|
||||
|
||||
private def doWipe(created: Created): Funit =
|
||||
TournamentRepo.remove(created).void >>- reloadSiteSocket >>- lobbyReload
|
||||
TournamentRepo.remove(created).void >>- publish()
|
||||
|
||||
def finish(oldTour: Started) {
|
||||
sequence(oldTour.id) {
|
||||
TournamentRepo startedById oldTour.id flatMap {
|
||||
case Some(started) =>
|
||||
if (started.pairings.isEmpty) TournamentRepo.remove(started).void >>- reloadSiteSocket >>- lobbyReload
|
||||
if (started.pairings.isEmpty) TournamentRepo.remove(started).void >>- publish()
|
||||
else started.readyToFinish ?? {
|
||||
val finished = started.finish
|
||||
TournamentRepo.update(finished).void >>-
|
||||
sendTo(finished.id, ReloadPage) >>-
|
||||
reloadSiteSocket >>-
|
||||
sendTo(finished.id, Reload) >>-
|
||||
publish() >>-
|
||||
finished.players.filter(_.score > 0).map { p =>
|
||||
UserRepo.incToints(p.id, p.score)
|
||||
}.sequenceFu
|
||||
|
@ -116,16 +117,15 @@ private[tournament] final class TournamentApi(
|
|||
}
|
||||
}
|
||||
|
||||
def join(oldTour: Enterable, me: User, password: Option[String]) {
|
||||
def join(oldTour: Enterable, me: User) {
|
||||
sequence(oldTour.id) {
|
||||
TournamentRepo enterableById oldTour.id flatMap {
|
||||
case Some(tour) => (tour.join(me, password)).future flatMap { tour2 =>
|
||||
case Some(tour) => tour.join(me).future flatMap { tour2 =>
|
||||
TournamentRepo withdraw me.id flatMap { withdrawIds =>
|
||||
TournamentRepo.update(tour2).void >>- {
|
||||
sendTo(tour.id, Joining(me.id))
|
||||
(tour.id :: withdrawIds) foreach socketReload
|
||||
reloadSiteSocket
|
||||
lobbyReload
|
||||
publish()
|
||||
import lila.hub.actorApi.timeline.{ Propagate, TourJoin }
|
||||
timeline ! (Propagate(TourJoin(me.id, tour2.id, tour2.fullName)) toFollowersOf me.id)
|
||||
}
|
||||
|
@ -141,7 +141,7 @@ private[tournament] final class TournamentApi(
|
|||
TournamentRepo byId oldTour.id flatMap {
|
||||
case Some(created: Created) => (created withdraw userId).fold(
|
||||
err => fulogwarn(err.shows),
|
||||
tour2 => TournamentRepo.update(tour2).void >>- socketReload(tour2.id) >>- reloadSiteSocket >>- lobbyReload
|
||||
tour2 => TournamentRepo.update(tour2).void >>- socketReload(tour2.id) >>- publish()
|
||||
)
|
||||
case Some(started: Started) => (started withdraw userId).fold(
|
||||
err => fufail(err.shows),
|
||||
|
@ -150,7 +150,7 @@ private[tournament] final class TournamentApi(
|
|||
roundMap ! Tell(povRef.gameId, ResignColor(povRef.color))
|
||||
}) >>-
|
||||
socketReload(tour2.id) >>-
|
||||
reloadSiteSocket
|
||||
publish()
|
||||
)
|
||||
case _ => fufail("Cannot withdraw from finished or missing tournament " + oldTour.id)
|
||||
}
|
||||
|
@ -175,14 +175,6 @@ private[tournament] final class TournamentApi(
|
|||
}
|
||||
}
|
||||
|
||||
private def lobbyReload {
|
||||
TournamentRepo.promotable foreach { tours =>
|
||||
renderer ? TournamentTable(tours) map {
|
||||
case view: play.twirl.api.Html => ReloadTournaments(view.body)
|
||||
} pipeToSelection lobby
|
||||
}
|
||||
}
|
||||
|
||||
def ejectCheater(userId: String) {
|
||||
TournamentRepo.allEnterable foreach {
|
||||
_ foreach { oldTour =>
|
||||
|
@ -207,9 +199,18 @@ private[tournament] final class TournamentApi(
|
|||
sendTo(tourId, Reload)
|
||||
}
|
||||
|
||||
private val reloadMessage = SendToFlag("tournament", Json.obj("t" -> "reload"))
|
||||
private def reloadSiteSocket {
|
||||
site ! reloadMessage
|
||||
private object publish {
|
||||
private val siteMessage = SendToFlag("tournament", Json.obj("t" -> "reload"))
|
||||
private val debouncer = system.actorOf(Props(new Debouncer(2 seconds, {
|
||||
(_: Debouncer.Nothing) =>
|
||||
site ! siteMessage
|
||||
TournamentRepo.promotable foreach { tours =>
|
||||
renderer ? TournamentTable(tours) map {
|
||||
case view: play.twirl.api.Html => ReloadTournaments(view.body)
|
||||
} pipeToSelection lobby
|
||||
}
|
||||
})))
|
||||
def apply() { debouncer ! Debouncer.Nothing }
|
||||
}
|
||||
|
||||
private def sendTo(tourId: String, msg: Any) {
|
||||
|
|
|
@ -41,11 +41,13 @@ object TournamentRepo {
|
|||
"schedule" -> BSONDocument("$exists" -> false)
|
||||
)).toList[Created](None)
|
||||
|
||||
def createdIncludingScheduled: Fu[List[Created]] = coll.find(createdSelect).toList[Created](None)
|
||||
|
||||
def started: Fu[List[Started]] =
|
||||
coll.find(startedSelect).sort(BSONDocument("createdAt" -> -1)).toList[Started](None)
|
||||
|
||||
def noPasswordStarted: Fu[List[Started]] =
|
||||
coll.find(startedSelect ++ BSONDocument("password" -> BSONDocument("$exists" -> false))).sort(BSONDocument("createdAt" -> -1)).toList[Started](None)
|
||||
def publicStarted: Fu[List[Started]] =
|
||||
coll.find(startedSelect ++ BSONDocument("private" -> BSONDocument("$exists" -> false))).sort(BSONDocument("createdAt" -> -1)).toList[Started](None)
|
||||
|
||||
def finished(limit: Int): Fu[List[Finished]] =
|
||||
coll.find(finishedSelect).sort(BSONDocument("startedAt" -> -1)).toList[Finished](limit.some)
|
||||
|
@ -57,18 +59,18 @@ object TournamentRepo {
|
|||
)
|
||||
)
|
||||
|
||||
def noPasswordCreatedSorted: Fu[List[Created]] = coll.find(
|
||||
allCreatedSelect ++ BSONDocument("password" -> BSONDocument("$exists" -> false))
|
||||
def publicCreatedSorted: Fu[List[Created]] = coll.find(
|
||||
allCreatedSelect ++ BSONDocument("private" -> BSONDocument("$exists" -> false))
|
||||
).sort(BSONDocument("schedule.at" -> 1, "createdAt" -> 1)).toList[Created](None)
|
||||
|
||||
def allCreated: Fu[List[Created]] = coll.find(allCreatedSelect).toList[Created](None)
|
||||
|
||||
def recentlyStartedSorted: Fu[List[Started]] = coll.find(startedSelect ++ BSONDocument(
|
||||
"password" -> BSONDocument("$exists" -> false),
|
||||
"private" -> BSONDocument("$exists" -> false),
|
||||
"startedAt" -> BSONDocument("$gt" -> (DateTime.now minusMinutes 20))
|
||||
)).sort(BSONDocument("schedule.at" -> 1, "createdAt" -> 1)).toList[Started](none)
|
||||
|
||||
def promotable: Fu[List[Enterable]] = noPasswordCreatedSorted zip recentlyStartedSorted map {
|
||||
def promotable: Fu[List[Enterable]] = publicCreatedSorted zip recentlyStartedSorted map {
|
||||
case (created, started) => created ::: started
|
||||
}
|
||||
|
||||
|
@ -89,7 +91,7 @@ object TournamentRepo {
|
|||
def exists(id: String) = coll.db command Count(coll.name, BSONDocument("_id" -> id).some) map (0 !=)
|
||||
|
||||
def withdraw(userId: String): Fu[List[String]] = for {
|
||||
createds ← created
|
||||
createds ← createdIncludingScheduled
|
||||
createdIds ← (createds map (_ withdraw userId) collect {
|
||||
case scalaz.Success(tour) => update(tour) inject tour.id
|
||||
}).sequenceFu
|
||||
|
|
|
@ -24,9 +24,7 @@ private[tournament] case class Join(
|
|||
user: Option[User],
|
||||
version: Int)
|
||||
private[tournament] case class Talk(tourId: String, u: String, t: String, troll: Boolean)
|
||||
private[tournament] case object Start
|
||||
private[tournament] case object Reload
|
||||
private[tournament] case object ReloadPage
|
||||
private[tournament] case class StartGame(game: Game)
|
||||
private[tournament] case class Joining(userId: String)
|
||||
private[tournament] case class Connected(enumerator: JsEnumerator, member: Member)
|
||||
|
|
|
@ -6,6 +6,8 @@ package object tournament extends PackageObject with WithPlay with WithSocket {
|
|||
|
||||
private[tournament]type Players = List[tournament.Player]
|
||||
|
||||
private[tournament]type RankedPlayers = List[(Int, Player)]
|
||||
|
||||
private[tournament]type Pairings = List[tournament.Pairing]
|
||||
|
||||
private[tournament]type Events = List[tournament.Event]
|
||||
|
|
|
@ -54,8 +54,7 @@ trait UserRepo {
|
|||
|
||||
def byOrderedIds(ids: Iterable[ID]): Fu[List[User]] = $find byOrderedIds ids
|
||||
|
||||
def enabledByIds(ids: Seq[ID]): Fu[List[User]] =
|
||||
$find(enabledSelect ++ $select.byIds(ids))
|
||||
def enabledByIds(ids: Seq[ID]): Fu[List[User]] = $find(enabledSelect ++ $select.byIds(ids))
|
||||
|
||||
def enabledById(id: ID): Fu[Option[User]] =
|
||||
$find.one(enabledSelect ++ $select.byId(id))
|
||||
|
|
|
@ -45,7 +45,7 @@ object Dependencies {
|
|||
val elastic4s = "com.sksamuel.elastic4s" %% "elastic4s" % "1.3.2"
|
||||
val RM = "org.reactivemongo" %% "reactivemongo" % "0.10.5.0.akka23"
|
||||
val PRM = "org.reactivemongo" %% "play2-reactivemongo" % "0.10.5.0.akka23"
|
||||
val maxmind = "com.sanoma.cda" %% "maxmind-geoip2-scala" % "1.2.2-THIB"
|
||||
val maxmind = "com.sanoma.cda" %% "maxmind-geoip2-scala" % "1.2.3-THIB"
|
||||
val prismic = "io.prismic" %% "scala-kit" % "1.2.4"
|
||||
|
||||
object play {
|
||||
|
|
|
@ -367,6 +367,7 @@ lichess.storage = {
|
|||
};
|
||||
};
|
||||
|
||||
var nbEl = document.querySelector('#nb_connected_players > strong');
|
||||
lichess.socket = null;
|
||||
lichess.idleTime = 20 * 60 * 1000;
|
||||
$.extend(true, lichess.StrongSocket.defaults, {
|
||||
|
@ -381,16 +382,15 @@ lichess.storage = {
|
|||
$('#friend_box').friends('leaves', name);
|
||||
},
|
||||
n: function(e) {
|
||||
var $tag = $('#nb_connected_players > strong');
|
||||
if ($tag.length && e) {
|
||||
var prev = parseInt($tag.text(), 10) || Math.max(0, (e - 10));
|
||||
var k = 6;
|
||||
if (nbEl && e) {
|
||||
var prev = parseInt(nbEl.textContent, 10) || Math.max(0, (e - 10));
|
||||
var k = 5;
|
||||
var interv = lichess.socket.pingInterval() / k;
|
||||
$.fp.range(k).forEach(function(it) {
|
||||
setTimeout(function() {
|
||||
var val = Math.round(((prev * (k - 1 - it)) + (e * (it + 1))) / k);
|
||||
if (val != prev) {
|
||||
$tag.text(val);
|
||||
nbEl.textContent = val;
|
||||
prev = val;
|
||||
}
|
||||
}, Math.round(it * interv));
|
||||
|
@ -416,6 +416,7 @@ lichess.storage = {
|
|||
$('#tournament_reminder').remove();
|
||||
return false;
|
||||
});
|
||||
$('body').trigger('lichess.content_loaded');
|
||||
}
|
||||
},
|
||||
challengeReminder: function(data) {
|
||||
|
@ -567,6 +568,7 @@ lichess.storage = {
|
|||
else if (lichess.analyse) startAnalyse(document.getElementById('lichess'), lichess.analyse);
|
||||
else if (lichess.user_analysis) startUserAnalysis(document.getElementById('lichess'), lichess.user_analysis);
|
||||
else if (lichess.lobby) startLobby(document.getElementById('hooks_wrap'), lichess.lobby);
|
||||
else if (lichess.tournament) startTournament(document.getElementById('tournament'), lichess.tournament);
|
||||
|
||||
$('#lichess').on('click', '.socket-link:not(.disabled)', function() {
|
||||
lichess.socket.send($(this).data('msg'), $(this).data('data'));
|
||||
|
@ -622,6 +624,7 @@ lichess.storage = {
|
|||
$('#message_notifications_tag').on('click', function() {
|
||||
$.ajax({
|
||||
url: $(this).data('href'),
|
||||
cache: false,
|
||||
success: function(html) {
|
||||
$('#message_notifications_display').html(html)
|
||||
.find('a.mark_as_read').click(function() {
|
||||
|
@ -680,6 +683,7 @@ lichess.storage = {
|
|||
var $themepicker = $('#themepicker');
|
||||
$.ajax({
|
||||
url: $(this).data('url'),
|
||||
cache: false,
|
||||
success: function(html) {
|
||||
$themepicker.append(html);
|
||||
var $body = $('body');
|
||||
|
@ -902,6 +906,7 @@ lichess.storage = {
|
|||
langs = acceptLanguages.split(',');
|
||||
$.ajax({
|
||||
url: $links.data('url'),
|
||||
cache: false,
|
||||
success: function(list) {
|
||||
$links.prepend(list.map(function(lang) {
|
||||
var klass = $.fp.contains(langs, lang[0]) ? 'class="accepted"' : '';
|
||||
|
@ -955,22 +960,38 @@ lichess.storage = {
|
|||
}, 1500);
|
||||
});
|
||||
|
||||
$.lazy = function(factory) {
|
||||
var loaded = {};
|
||||
return function(key) {
|
||||
if (!loaded[key]) loaded[key] = factory(key);
|
||||
return loaded[key];
|
||||
};
|
||||
};
|
||||
|
||||
$.sound = (function() {
|
||||
var baseUrl = $('body').data('sound-dir') + '/';
|
||||
var a = new Audio();
|
||||
var hasOgg = !!a.canPlayType && a.canPlayType('audio/ogg; codecs="vorbis"');
|
||||
var hasMp3 = !!a.canPlayType && a.canPlayType('audio/mpeg;');
|
||||
var ext = hasOgg ? 'ogg' : 'mp3';
|
||||
var audio = {
|
||||
dong: new Audio(baseUrl + 'dong2.' + ext),
|
||||
moveW: new Audio(baseUrl + 'move3.' + ext),
|
||||
moveB: new Audio(baseUrl + 'move3.' + ext),
|
||||
take: new Audio(baseUrl + 'take2.' + ext),
|
||||
lowtime: new Audio(baseUrl + 'lowtime.' + ext)
|
||||
var names = {
|
||||
dong: 'dong2',
|
||||
moveW: 'move3',
|
||||
moveB: 'move3',
|
||||
take: 'take2',
|
||||
lowtime: 'lowtime'
|
||||
};
|
||||
var volumes = {
|
||||
lowtime: 0.6
|
||||
lowtime: 0.5
|
||||
};
|
||||
var computeVolume = function(k, v) {
|
||||
return v * (volumes[k] || 1);
|
||||
};
|
||||
var get = new $.lazy(function(k) {
|
||||
var audio = new Audio(baseUrl + names[k] + '.' + ext);
|
||||
audio.volume = computeVolume(k, getVolume());
|
||||
return audio;
|
||||
});
|
||||
var canPlay = hasOgg || hasMp3;
|
||||
var $control = $('#sound_control');
|
||||
var $toggle = $('#sound_state');
|
||||
|
@ -984,18 +1005,18 @@ lichess.storage = {
|
|||
var play = {
|
||||
move: function(white) {
|
||||
if (shouldPlay()) {
|
||||
if (white) audio.moveW.play();
|
||||
else audio.moveB.play();
|
||||
if (white) get('moveW').play();
|
||||
else get('moveB').play();
|
||||
}
|
||||
},
|
||||
take: function() {
|
||||
if (shouldPlay()) audio.take.play();
|
||||
if (shouldPlay()) get('take').play();
|
||||
},
|
||||
dong: function() {
|
||||
if (shouldPlay()) audio.dong.play();
|
||||
if (shouldPlay()) get('dong').play();
|
||||
},
|
||||
lowtime: function() {
|
||||
if (shouldPlay()) audio.lowtime.play();
|
||||
if (shouldPlay()) get('lowtime').play();
|
||||
}
|
||||
};
|
||||
var getVolume = function() {
|
||||
|
@ -1003,15 +1024,14 @@ lichess.storage = {
|
|||
};
|
||||
var setVolume = function(v) {
|
||||
lichess.storage.set('sound-volume', v);
|
||||
Object.keys(audio).forEach(function(k) {
|
||||
audio[k].volume = v * (volumes[k] ? volumes[k] : 1);
|
||||
Object.keys(names).forEach(function(k) {
|
||||
get(k).volume = computeVolume(k, v);
|
||||
});
|
||||
};
|
||||
var manuallySetVolume = $.fp.debounce(function(v) {
|
||||
setVolume(v);
|
||||
play.move(true);
|
||||
}, 100);
|
||||
setVolume(getVolume());
|
||||
if (canPlay) {
|
||||
$toggle.click(function() {
|
||||
$control.add($toggle).toggleClass('sound_state_on', !enabled());
|
||||
|
@ -1059,9 +1079,8 @@ lichess.storage = {
|
|||
var startTournamentClock = function() {
|
||||
$("div.game_tournament div.clock").each(function() {
|
||||
$(this).clock({
|
||||
time: $(this).data("time"),
|
||||
showTenths: false
|
||||
}).clock("start");
|
||||
time: parseFloat($(this).data("time"))
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -1102,10 +1121,14 @@ lichess.storage = {
|
|||
},
|
||||
end: function() {
|
||||
var url = (data.tv ? cfg.routes.Tv.side : cfg.routes.Round.sideWatcher)(data.game.id, data.player.color).url;
|
||||
$.get(url, function(html) {
|
||||
$('#site_header div.side').replaceWith(html);
|
||||
$('body').trigger('lichess.content_loaded');
|
||||
startTournamentClock();
|
||||
$.ajax({
|
||||
url: url,
|
||||
cache: false,
|
||||
success: function(html) {
|
||||
$('#site_header div.side').replaceWith(html);
|
||||
$('body').trigger('lichess.content_loaded');
|
||||
startTournamentClock();
|
||||
}
|
||||
});
|
||||
},
|
||||
checkCount: function(e) {
|
||||
|
@ -1345,91 +1368,42 @@ lichess.storage = {
|
|||
|
||||
$.widget("lichess.clock", {
|
||||
_create: function() {
|
||||
var o = this.options;
|
||||
this.options.time = parseFloat(this.options.time) * 1000;
|
||||
this.options.barTime = parseFloat(this.options.barTime) * 1000;
|
||||
this.options.emerg = parseFloat(this.options.emerg) * 1000;
|
||||
$.extend(this.options, {
|
||||
state: 'ready'
|
||||
});
|
||||
var self = this;
|
||||
this.options.time = this.options.time * 1000;
|
||||
this.$time = this.element.find('>div.time');
|
||||
this.$bar = this.element.find('>div.bar>span');
|
||||
this._show();
|
||||
var end_time = new Date().getTime() + self.options.time;
|
||||
var tick = function() {
|
||||
var current_time = Math.round(end_time - new Date().getTime());
|
||||
if (current_time <= 0) {
|
||||
clearInterval(self.options.interval);
|
||||
current_time = 0;
|
||||
}
|
||||
self.options.time = current_time;
|
||||
self._show();
|
||||
};
|
||||
self.options.interval = setInterval(tick, 1000);
|
||||
tick();
|
||||
},
|
||||
destroy: function() {
|
||||
this.stop();
|
||||
$.Widget.prototype.destroy.apply(this);
|
||||
},
|
||||
start: function() {
|
||||
var self = this;
|
||||
self.options.state = 'running';
|
||||
self.element.addClass('running');
|
||||
var end_time = new Date().getTime() + self.options.time;
|
||||
self.options.interval = setInterval(function() {
|
||||
if (self.options.state == 'running') {
|
||||
var current_time = Math.round(end_time - new Date().getTime());
|
||||
if (current_time <= 0) {
|
||||
clearInterval(self.options.interval);
|
||||
current_time = 0;
|
||||
}
|
||||
|
||||
self.options.time = current_time;
|
||||
self._show();
|
||||
|
||||
//If the timer completed, fire the buzzer callback
|
||||
if (current_time === 0 && $.isFunction(self.options.buzzer)) self.options.buzzer(self.element);
|
||||
} else {
|
||||
clearInterval(self.options.interval);
|
||||
}
|
||||
},
|
||||
100);
|
||||
},
|
||||
|
||||
setTime: function(time) {
|
||||
this.options.time = parseFloat(time) * 1000;
|
||||
this._show();
|
||||
},
|
||||
|
||||
getSeconds: function() {
|
||||
return Math.round(this.options.time / 1000);
|
||||
},
|
||||
|
||||
stop: function() {
|
||||
clearInterval(this.options.interval);
|
||||
this.options.state = 'stop';
|
||||
this.element.removeClass('running');
|
||||
this.element.toggleClass('outoftime', this.options.time <= 0);
|
||||
},
|
||||
|
||||
_show: function() {
|
||||
var html = this._formatDate(new Date(this.options.time));
|
||||
if (html != this.$time.html()) {
|
||||
this.$time.html(html);
|
||||
this.element.toggleClass('emerg', this.options.time < this.options.emerg);
|
||||
}
|
||||
if (this.options.showBar) {
|
||||
var barWidth = Math.max(0, Math.min(100, (this.options.time / this.options.barTime) * 100));
|
||||
this.$bar.css('width', barWidth + '%');
|
||||
}
|
||||
this.$time.html(this._formatDate(new Date(this.options.time)));
|
||||
},
|
||||
|
||||
_formatDate: function(date) {
|
||||
var minutes = this._prefixInteger(date.getUTCMinutes(), 2);
|
||||
var seconds = this._prefixInteger(date.getSeconds(), 2);
|
||||
var b = function(x) {
|
||||
return '<b>' + x + '</b>';
|
||||
};
|
||||
if (this.options.showTenths && this.options.time < 10000) {
|
||||
tenths = Math.floor(date.getMilliseconds() / 100);
|
||||
return b(minutes) + ':' + b(seconds) + '<span>.' + b(tenths) + '</span>';
|
||||
} else if (this.options.time >= 3600000) {
|
||||
if (this.options.time >= 3600000) {
|
||||
var hours = this._prefixInteger(date.getUTCHours(), 2);
|
||||
return b(hours) + ':' + b(minutes) + ':' + b(seconds);
|
||||
} else {
|
||||
return b(minutes) + ':' + b(seconds);
|
||||
}
|
||||
},
|
||||
|
||||
_prefixInteger: function(num, length) {
|
||||
return (num / Math.pow(10, length)).toFixed(length).substr(2);
|
||||
}
|
||||
|
@ -1521,13 +1495,13 @@ lichess.storage = {
|
|||
'/lobby/socket/v1',
|
||||
cfg.data.version, {
|
||||
receive: function(t, d) {
|
||||
if (lobby) lobby.socketReceive(t, d);
|
||||
else console.log('missed', t, d);
|
||||
lobby.socketReceive(t, d);
|
||||
},
|
||||
events: {
|
||||
reload_timeline: function() {
|
||||
$.ajax({
|
||||
url: $("#timeline").data('href'),
|
||||
cache: false,
|
||||
success: function(html) {
|
||||
$('#timeline').html(html);
|
||||
$('body').trigger('lichess.content_loaded');
|
||||
|
@ -1551,7 +1525,9 @@ lichess.storage = {
|
|||
},
|
||||
reload_forum: function() {
|
||||
setTimeout(function() {
|
||||
$.ajax($newposts.data('url'), {
|
||||
$.ajax({
|
||||
url: $newposts.data('url'),
|
||||
cache: false,
|
||||
success: function(data) {
|
||||
$newposts.find('ol').html(data).end().scrollTop(0);
|
||||
$('body').trigger('lichess.content_loaded');
|
||||
|
@ -1843,6 +1819,7 @@ lichess.storage = {
|
|||
$('.lichess_overboard').remove();
|
||||
$.ajax({
|
||||
url: $(this).attr('href'),
|
||||
cache: false,
|
||||
success: function(html) {
|
||||
$('.lichess_overboard').remove();
|
||||
$('#hooks_wrap').prepend(html);
|
||||
|
@ -1868,91 +1845,43 @@ lichess.storage = {
|
|||
|
||||
$(function() {
|
||||
|
||||
var $wrap = $('#tournament');
|
||||
if (!$wrap.length) return;
|
||||
|
||||
if (!lichess.StrongSocket.available) return;
|
||||
|
||||
if (typeof _ld_ == "undefined") {
|
||||
var $tournamentList = $('#tournament_list');
|
||||
if ($tournamentList.length) {
|
||||
// handle tournament list
|
||||
lichess.StrongSocket.defaults.params.flag = "tournament";
|
||||
lichess.StrongSocket.defaults.events.reload = function() {
|
||||
$wrap.load($wrap.data("href"), function() {
|
||||
$tournamentList.load($tournamentList.data("href"), function() {
|
||||
$('body').trigger('lichess.content_loaded');
|
||||
});
|
||||
};
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
$('body').data('tournament-id', _ld_.tournament.id);
|
||||
|
||||
function startTournament(element, cfg) {
|
||||
$('body').data('tournament-id', cfg.data.id);
|
||||
var $watchers = $("div.watchers").watchers();
|
||||
|
||||
var $chat = $('#chat');
|
||||
if ($chat.length) $chat.chat({
|
||||
if (lichess_chat) $('#chat').chat({
|
||||
messages: lichess_chat
|
||||
});
|
||||
|
||||
function startClock() {
|
||||
$("div.tournament_clock").each(function() {
|
||||
$(this).clock({
|
||||
time: $(this).data("time")
|
||||
}).clock("start");
|
||||
});
|
||||
}
|
||||
startClock();
|
||||
|
||||
function drawBars() {
|
||||
$wrap.find('table.standing').each(function() {
|
||||
var $bars = $(this).find('.bar');
|
||||
var max = Math.max.apply(Math, $bars.map(function() {
|
||||
return parseInt(this.getAttribute('data-value'));
|
||||
}));
|
||||
$bars.each(function() {
|
||||
var width = Math.ceil((parseInt($(this).data('value')) * 100) / max);
|
||||
$(this).css('width', width + '%');
|
||||
});
|
||||
});
|
||||
}
|
||||
drawBars();
|
||||
|
||||
function reload() {
|
||||
$.ajax({
|
||||
url: $wrap.data('href'),
|
||||
success: function(html) {
|
||||
var $tour = $(html);
|
||||
if ($wrap.find('table.standing').length) {
|
||||
// started
|
||||
$wrap.find('table.standing thead').replaceWith($tour.find('table.standing thead'));
|
||||
$wrap.find('table.standing tbody').replaceWith($tour.find('table.standing tbody'));
|
||||
drawBars();
|
||||
$wrap.find('div.pairings').replaceWith($tour.find('div.pairings'));
|
||||
$wrap.find('div.game_list').replaceWith($tour.find('div.game_list'));
|
||||
} else {
|
||||
// created
|
||||
$wrap.find('table.user_list').replaceWith($tour.find('table.user_list'));
|
||||
}
|
||||
$('body').trigger('lichess.content_loaded');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
lichess.socket = new lichess.StrongSocket($wrap.data("socket-url"), _ld_.version, {
|
||||
events: {
|
||||
start: reload,
|
||||
reload: reload,
|
||||
reloadPage: function() {
|
||||
location.reload();
|
||||
var tournament;
|
||||
lichess.socket = new lichess.StrongSocket(
|
||||
'/tournament/' + cfg.data.id + '/socket/v1', cfg.socketVersion, {
|
||||
receive: function(t, d) {
|
||||
tournament.socketReceive(t, d)
|
||||
},
|
||||
crowd: function(data) {
|
||||
$watchers.watchers("set", data);
|
||||
events: {
|
||||
crowd: function(data) {
|
||||
$watchers.watchers("set", data);
|
||||
}
|
||||
},
|
||||
options: {
|
||||
name: "tournament"
|
||||
}
|
||||
},
|
||||
options: {
|
||||
name: "tournament"
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
cfg.socketSend = lichess.socket.send.bind(lichess.socket);
|
||||
tournament = LichessTournament(element, cfg);
|
||||
};
|
||||
|
||||
////////////////
|
||||
// analyse.js //
|
||||
|
@ -2091,6 +2020,20 @@ lichess.storage = {
|
|||
});
|
||||
return false;
|
||||
});
|
||||
$panels.find('div.pgn').click(function() {
|
||||
var range, selection;
|
||||
if (document.body.createTextRange) {
|
||||
range = document.body.createTextRange();
|
||||
range.moveToElementText($(this)[0]);
|
||||
range.select();
|
||||
} else if (window.getSelection) {
|
||||
selection = window.getSelection();
|
||||
range = document.createRange();
|
||||
range.selectNodeContents($(this)[0]);
|
||||
selection.removeAllRanges();
|
||||
selection.addRange(range);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
////////////////
|
||||
|
@ -2140,4 +2083,16 @@ lichess.storage = {
|
|||
} else startListening();
|
||||
}, 5000);
|
||||
};
|
||||
|
||||
$.modal = function(html) {
|
||||
var $wrap = $('<div id="modal-wrap">').html(html.clone().show());
|
||||
var $overlay = $('<div id="modal-overlay">').html($wrap);
|
||||
$overlay.one('click', function() {
|
||||
$('#modal-overlay').remove();
|
||||
});
|
||||
$wrap.click(function(e) {
|
||||
e.stopPropagation();
|
||||
});
|
||||
$('body').prepend($overlay);
|
||||
};
|
||||
})();
|
||||
|
|
4
public/javascripts/vendor/chessground.min.js
vendored
|
@ -1,4 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" version="1.1"
|
||||
viewBox="0 0 500 500" width="500" height="500" id="clear_svg">
|
||||
</svg>
|
Before Width: | Height: | Size: 179 B |
|
@ -1,4 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" version="1.1"
|
||||
viewBox="0 0 500 500" width="500" height="500" id="clear_svg">
|
||||
</svg>
|
Before Width: | Height: | Size: 179 B |
|
@ -1,4 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" version="1.1"
|
||||
viewBox="0 0 500 500" width="500" height="500" id="clear_svg">
|
||||
</svg>
|
Before Width: | Height: | Size: 179 B |
|
@ -1,4 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" version="1.1"
|
||||
viewBox="0 0 500 500" width="500" height="500" id="clear_svg">
|
||||
</svg>
|
Before Width: | Height: | Size: 179 B |
|
@ -1,30 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<!-- Creator: CorelDRAW X5 -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" width="42.3332mm" height="42.3332mm" style="shape-rendering:geometricPrecision; text-rendering:geometricPrecision; image-rendering:optimizeQuality; fill-rule:evenodd; clip-rule:evenodd"
|
||||
viewBox="0 0 4233 4233"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<defs>
|
||||
<style type="text/css">
|
||||
<![CDATA[
|
||||
.fil1 {fill:#111111}
|
||||
.fil0 {fill:#2E2E2E}
|
||||
]]>
|
||||
</style>
|
||||
</defs>
|
||||
<g id="Warstwa_x0020_1">
|
||||
<metadata id="CorelCorpID_0Corel-Layer"/>
|
||||
<path class="fil0" d="M2082 838c0,0 621,269 458,883 -163,614 -570,260 -570,260l113 -1142 0 0z"/>
|
||||
<rect class="fil0" x="1010" y="3594" width="2217" height="204"/>
|
||||
<rect class="fil0" x="1179" y="3741" width="1885" height="153"/>
|
||||
<rect class="fil0" x="1006" y="3860" width="2217" height="204"/>
|
||||
<polygon class="fil0" points="1792,1909 2390,1909 2891,3613 1336,3613 "/>
|
||||
<rect class="fil0" x="1497" y="1866" width="1200" height="152"/>
|
||||
<path class="fil0" d="M2097 839c0,0 -623,269 -459,883 164,614 572,260 572,260l-113 -1142 0 0z"/>
|
||||
<ellipse class="fil0" cx="2092" cy="804" rx="174" ry="143"/>
|
||||
<path class="fil1" d="M2177 679c0,0 43,54 43,115 0,61 -14,118 -14,118 0,0 153,-118 -29,-233z"/>
|
||||
<polygon class="fil1" points="1179,3798 3064,3798 3064,3860 1179,3860 "/>
|
||||
<path class="fil1" d="M2206 912c0,0 221,144 300,333 79,189 66,259 50,397 -16,138 -68,224 -68,224l209 0 0 152 -321 0 -17 -152c0,0 -2,-42 -12,-77 -10,-36 93,-85 104,-240 10,-155 1,-253 -53,-368 -53,-116 -194,-269 -194,-269z"/>
|
||||
<polygon class="fil1" points="1752,2018 2422,2018 2887,3594 3227,3594 3227,3798 3064,3798 3064,3860 3223,3860 3223,4064 2743,4064 2743,3860 2743,3798 2658,3594 2347,2164 "/>
|
||||
</g>
|
||||
</svg>
|
Before Width: | Height: | Size: 1.9 KiB |
|
@ -1,37 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<!-- Creator: CorelDRAW X5 -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" width="42.3332mm" height="42.3332mm" style="shape-rendering:geometricPrecision; text-rendering:geometricPrecision; image-rendering:optimizeQuality; fill-rule:evenodd; clip-rule:evenodd"
|
||||
viewBox="0 0 4233 4233"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<defs>
|
||||
<style type="text/css">
|
||||
<![CDATA[
|
||||
.fil1 {fill:#111111}
|
||||
.fil0 {fill:#2E2E2E}
|
||||
]]>
|
||||
</style>
|
||||
</defs>
|
||||
<g id="Warstwa_x0020_1">
|
||||
<metadata id="CorelCorpID_0Corel-Layer"/>
|
||||
<polygon class="fil0" points="1554,2064 2652,2073 2968,3725 1280,3702 "/>
|
||||
<rect class="fil0" x="1010" y="3672" width="2217" height="204"/>
|
||||
<rect class="fil0" x="1179" y="3819" width="1885" height="153"/>
|
||||
<rect class="fil0" x="1006" y="3939" width="2217" height="204"/>
|
||||
<path class="fil1" d="M1612 2183l1061 0 288 1489 266 0 0 204 -161 -3 0 63 157 3 0 204 -316 0c0,0 -26,-149 -71,-267 -45,-117 -85,-80 -135,-448 -49,-368 -150,-1101 -150,-1101l-939 -145z"/>
|
||||
<polygon class="fil1" points="1181,3874 3066,3874 3066,3936 1181,3936 "/>
|
||||
<rect class="fil0" x="1300" y="1963" width="1638" height="220"/>
|
||||
<path class="fil0" d="M1490 2042c0,0 -241,-328 -199,-572 50,-285 382,-408 875,-435 148,-8 98,1007 98,1007l-774 0z"/>
|
||||
<path class="fil0" d="M2726 2042c0,0 241,-328 199,-572 -50,-285 -382,-408 -875,-435 -148,-8 -98,1007 -98,1007l774 0z"/>
|
||||
<path class="fil0" d="M1772 1087c0,0 253,-63 226,-169 -27,-106 114,-19 114,-19l10 146 -350 43z"/>
|
||||
<path class="fil0" d="M2384 1079c0,0 -154,-75 -159,-161 -5,-86 -114,-19 -114,-19l-10 147 282 34z"/>
|
||||
<ellipse class="fil0" cx="1967" cy="711" rx="91" ry="89"/>
|
||||
<ellipse class="fil0" cx="1995" cy="848" rx="64" ry="62"/>
|
||||
<ellipse class="fil0" cx="2250" cy="711" rx="91" ry="89"/>
|
||||
<ellipse class="fil0" cx="2222" cy="849" rx="64" ry="62"/>
|
||||
<ellipse class="fil0" cx="2112" cy="632" rx="97" ry="101"/>
|
||||
<polygon class="fil0" points="2047,957 1967,711 2112,632 2250,711 2176,962 "/>
|
||||
<path class="fil1" d="M2226 918c0,0 -49,94 18,144 66,50 231,81 319,139 89,58 240,120 241,284 2,164 -33,236 -139,351 -107,116 -175,126 -175,126l288 0c0,0 87,-152 110,-226 23,-74 52,-167 36,-267 -16,-100 -90,-225 -206,-280 -116,-56 -86,-51 -170,-75 -83,-24 -94,-26 -103,-28 -9,-2 -95,-27 -95,-27 0,0 -45,-25 -70,-51 -25,-26 -55,-91 -55,-91z"/>
|
||||
<polygon class="fil1" points="2490,1963 2524,2183 2938,2183 2938,1963 "/>
|
||||
</g>
|
||||
</svg>
|
Before Width: | Height: | Size: 2.5 KiB |
|
@ -1,25 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<!-- Creator: CorelDRAW X5 -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" width="42.3332mm" height="42.3332mm" style="shape-rendering:geometricPrecision; text-rendering:geometricPrecision; image-rendering:optimizeQuality; fill-rule:evenodd; clip-rule:evenodd"
|
||||
viewBox="0 0 4233 4233"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<defs>
|
||||
<style type="text/css">
|
||||
<![CDATA[
|
||||
.fil1 {fill:#111111}
|
||||
.fil0 {fill:#2E2E2E}
|
||||
]]>
|
||||
</style>
|
||||
</defs>
|
||||
<g id="Warstwa_x0020_1">
|
||||
<metadata id="CorelCorpID_0Corel-Layer"/>
|
||||
<path class="fil0" d="M1040 1733c-165,151 -145,140 -72,271 72,131 241,188 400,142 158,-46 506,-183 633,-154 127,30 159,91 167,157 8,66 -186,227 -400,433 -214,205 -402,243 -373,671 30,428 19,525 -42,592 -61,67 1074,263 1221,196 147,-68 543,-55 465,-460 -78,-406 -208,-900 -10,-1238 198,-338 398,-1042 -527,-1423 -925,-381 -558,35 -648,83 -90,47 -121,57 -175,83 -98,47 -462,486 -639,647z"/>
|
||||
<rect class="fil0" x="1102" y="3595" width="2217" height="204"/>
|
||||
<rect class="fil0" x="1271" y="3742" width="1885" height="153"/>
|
||||
<rect class="fil0" x="1098" y="3861" width="2217" height="204"/>
|
||||
<path class="fil1" d="M1827 2000c0,0 85,-124 262,-131 176,-7 75,-331 75,-331 0,0 227,293 227,444 0,151 -239,217 -239,217 0,0 -30,-182 -324,-200z"/>
|
||||
<path class="fil1" d="M2831 1100c0,0 425,238 221,842 -204,604 -560,915 -498,1226 62,311 9,364 31,391 22,27 93,27 195,-4 102,-31 84,44 93,89 9,44 72,421 72,421l369 0 0 -204 -159 0 0 -63 163 0 0 -204 -277 0c0,0 -95,-448 -116,-728 -21,-280 58,-454 170,-653 111,-200 231,-777 -265,-1113z"/>
|
||||
<polygon class="fil1" points="1272,3798 3157,3798 3157,3861 1272,3861 "/>
|
||||
</g>
|
||||
</svg>
|
Before Width: | Height: | Size: 1.8 KiB |
|
@ -1,30 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<!-- Creator: CorelDRAW X5 -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" width="42.3332mm" height="42.3332mm" style="shape-rendering:geometricPrecision; text-rendering:geometricPrecision; image-rendering:optimizeQuality; fill-rule:evenodd; clip-rule:evenodd"
|
||||
viewBox="0 0 4233 4233"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<defs>
|
||||
<style type="text/css">
|
||||
<![CDATA[
|
||||
.fil1 {fill:#111111}
|
||||
.fil0 {fill:#2E2E2E}
|
||||
]]>
|
||||
</style>
|
||||
</defs>
|
||||
<g id="Warstwa_x0020_1">
|
||||
<metadata id="CorelCorpID_0Corel-Layer"/>
|
||||
<rect class="fil0" x="1165" y="3595" width="1907" height="204"/>
|
||||
<rect class="fil0" x="1310" y="3742" width="1621" height="153"/>
|
||||
<rect class="fil0" x="1162" y="3862" width="1907" height="204"/>
|
||||
<polygon class="fil1" points="1311,3799 2932,3799 2932,3861 1311,3861 "/>
|
||||
<polygon class="fil0" points="1871,2415 2167,2411 2167,3693 1550,3684 "/>
|
||||
<polygon class="fil0" points="2352,2415 2088,2411 2087,3693 2705,3684 "/>
|
||||
<rect class="fil0" x="1566" y="2263" width="1115" height="158"/>
|
||||
<ellipse class="fil0" cx="2101" cy="1905" rx="414" ry="433"/>
|
||||
<path class="fil1" d="M2191 1482c0,0 195,147 218,349 36,315 -162,418 -446,351 -143,-34 -244,-110 -231,-82 0,0 21,51 59,90 37,39 82,75 82,75l457 0c0,0 74,-41 114,-117 40,-77 72,-137 72,-244 0,-107 -41,-199 -68,-237 -27,-38 -56,-71 -105,-114 -48,-44 -152,-71 -152,-71z"/>
|
||||
<polygon class="fil1" points="2286,2263 2355,2421 2681,2421 2681,2263 "/>
|
||||
<polygon class="fil1" points="1770,2421 2355,2421 2701,3663 2446,3663 2277,2568 "/>
|
||||
<polygon class="fil1" points="2678,3595 2748,4065 3069,4065 3069,3862 2932,3861 2932,3799 3072,3799 3072,3595 "/>
|
||||
</g>
|
||||
</svg>
|
Before Width: | Height: | Size: 1.8 KiB |
|
@ -1,33 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<!-- Creator: CorelDRAW X5 -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" width="42.3332mm" height="42.3332mm" style="shape-rendering:geometricPrecision; text-rendering:geometricPrecision; image-rendering:optimizeQuality; fill-rule:evenodd; clip-rule:evenodd"
|
||||
viewBox="0 0 4233 4233"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<defs>
|
||||
<style type="text/css">
|
||||
<![CDATA[
|
||||
.fil1 {fill:#111111}
|
||||
.fil0 {fill:#2E2E2E}
|
||||
]]>
|
||||
</style>
|
||||
</defs>
|
||||
<g id="Warstwa_x0020_1">
|
||||
<metadata id="CorelCorpID_0Corel-Layer"/>
|
||||
<polygon class="fil0" points="1656,1982 2548,1985 2968,3643 1280,3620 "/>
|
||||
<rect class="fil0" x="1010" y="3590" width="2217" height="204"/>
|
||||
<rect class="fil0" x="1179" y="3737" width="1885" height="153"/>
|
||||
<rect class="fil0" x="1006" y="3857" width="2217" height="204"/>
|
||||
<path class="fil1" d="M1612 2101l965 0 383 1489 266 0 0 204 -161 0 0 60 157 3 0 204 -316 0c0,0 -26,-149 -71,-267 -45,-117 -85,-80 -135,-448 -49,-368 -253,-1117 -253,-1117l-836 -129z"/>
|
||||
<polygon class="fil1" points="1181,3792 3066,3792 3066,3854 1181,3854 "/>
|
||||
<rect class="fil0" x="1300" y="1881" width="1638" height="220"/>
|
||||
<polygon class="fil0" points="1564,1918 1161,1154 1658,1413 1618,730 2096,1286 2128,1918 "/>
|
||||
<polygon class="fil0" points="2628,1919 3031,1154 2533,1414 2573,730 2095,1287 2076,1919 "/>
|
||||
<circle class="fil0" cx="1161" cy="1154" r="100"/>
|
||||
<circle class="fil0" cx="1618" cy="730" r="100"/>
|
||||
<circle class="fil0" cx="2573" cy="730" r="100"/>
|
||||
<circle class="fil1" cx="3031" cy="1154" r="100"/>
|
||||
<path class="fil1" d="M2633 650l-97 173 -240 1058 351 0 337 -639 -42 -42 -409 213 40 -584c0,0 173,-34 60,-180z"/>
|
||||
<polygon class="fil1" points="2472,1881 2472,2101 2938,2101 2938,1881 "/>
|
||||
</g>
|
||||
</svg>
|
Before Width: | Height: | Size: 1.9 KiB |
|
@ -1,26 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<!-- Creator: CorelDRAW X5 -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" width="42.3332mm" height="42.3332mm" style="shape-rendering:geometricPrecision; text-rendering:geometricPrecision; image-rendering:optimizeQuality; fill-rule:evenodd; clip-rule:evenodd"
|
||||
viewBox="0 0 4233 4233"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<defs>
|
||||
<style type="text/css">
|
||||
<![CDATA[
|
||||
.fil1 {fill:#111111}
|
||||
.fil0 {fill:#2E2E2E}
|
||||
]]>
|
||||
</style>
|
||||
</defs>
|
||||
<g id="Warstwa_x0020_1">
|
||||
<metadata id="CorelCorpID_0Corel-Layer"/>
|
||||
<polygon class="fil0" points="1471,2050 2754,2054 2963,3644 1274,3621 "/>
|
||||
<polygon class="fil0" points="1213,2103 3015,2103 3233,951 2709,982 2709,1264 2402,1264 2402,991 1852,991 1852,1264 1533,1264 1530,992 1009,951 "/>
|
||||
<rect class="fil0" x="1005" y="3591" width="2217" height="204"/>
|
||||
<rect class="fil0" x="1174" y="3738" width="1885" height="153"/>
|
||||
<rect class="fil0" x="1001" y="3857" width="2217" height="204"/>
|
||||
<path class="fil1" d="M3064 961c0,0 -146,919 -168,986 -22,67 -94,155 -94,155l213 0 217 -1151 -169 10z"/>
|
||||
<path class="fil1" d="M1598 2103l1162 0 195 1488 266 0 0 204 -161 -3 0 63 157 3 0 204 -316 0c0,0 -24,-171 -67,-269 -42,-99 -90,-78 -139,-445 -49,-368 -153,-1020 -153,-1020l-945 -223z"/>
|
||||
<polygon class="fil1" points="1176,3792 3061,3792 3061,3855 1176,3855 "/>
|
||||
</g>
|
||||
</svg>
|
Before Width: | Height: | Size: 1.5 KiB |
|
@ -1,30 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<!-- Creator: CorelDRAW X5 -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" width="42.3332mm" height="42.3332mm" style="shape-rendering:geometricPrecision; text-rendering:geometricPrecision; image-rendering:optimizeQuality; fill-rule:evenodd; clip-rule:evenodd"
|
||||
viewBox="0 0 4233 4233"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<defs>
|
||||
<style type="text/css">
|
||||
<![CDATA[
|
||||
.fil1 {fill:#E3E3E3}
|
||||
.fil0 {fill:white}
|
||||
]]>
|
||||
</style>
|
||||
</defs>
|
||||
<g id="Warstwa_x0020_1">
|
||||
<metadata id="CorelCorpID_0Corel-Layer"/>
|
||||
<path class="fil0" d="M2082 821c0,0 621,269 458,883 -163,614 -570,260 -570,260l113 -1142 0 0z"/>
|
||||
<rect class="fil0" x="1010" y="3577" width="2217" height="204"/>
|
||||
<rect class="fil0" x="1179" y="3724" width="1885" height="153"/>
|
||||
<rect class="fil0" x="1006" y="3843" width="2217" height="204"/>
|
||||
<polygon class="fil0" points="1792,1892 2390,1892 2891,3596 1336,3596 "/>
|
||||
<rect class="fil0" x="1497" y="1849" width="1200" height="152"/>
|
||||
<path class="fil0" d="M2097 822c0,0 -623,269 -459,883 164,614 572,260 572,260l-113 -1142 0 0z"/>
|
||||
<ellipse class="fil0" cx="2092" cy="787" rx="174" ry="143"/>
|
||||
<path class="fil1" d="M2182 664c0,0 38,51 38,113 0,61 -14,118 -14,118 0,0 162,-115 -24,-231z"/>
|
||||
<polygon class="fil1" points="1179,3781 3064,3781 3064,3843 1179,3843 "/>
|
||||
<path class="fil1" d="M2206 895c0,0 221,144 300,333 79,189 66,259 50,397 -16,138 -68,224 -68,224l209 0 0 152 -321 0 -17 -152c0,0 -2,-42 -12,-77 -10,-36 93,-85 104,-240 10,-155 1,-253 -53,-368 -53,-116 -194,-269 -194,-269z"/>
|
||||
<polygon class="fil1" points="1752,2001 2422,2001 2887,3577 3227,3577 3227,3781 3064,3781 3064,3843 3223,3843 3223,4047 2743,4047 2743,3843 2743,3781 2658,3577 2347,2147 "/>
|
||||
</g>
|
||||
</svg>
|
Before Width: | Height: | Size: 1.9 KiB |
|
@ -1,38 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<!-- Creator: CorelDRAW X5 -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" width="42.3332mm" height="42.3332mm" style="shape-rendering:geometricPrecision; text-rendering:geometricPrecision; image-rendering:optimizeQuality; fill-rule:evenodd; clip-rule:evenodd"
|
||||
viewBox="0 0 4233 4233"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<defs>
|
||||
<style type="text/css">
|
||||
<![CDATA[
|
||||
.fil2 {fill:#E3E3E3}
|
||||
.fil0 {fill:#FEFEFE}
|
||||
.fil1 {fill:white}
|
||||
]]>
|
||||
</style>
|
||||
</defs>
|
||||
<g id="Warstwa_x0020_1">
|
||||
<metadata id="CorelCorpID_0Corel-Layer"/>
|
||||
<polygon class="fil0" points="1554,1987 2652,1996 2968,3649 1280,3626 "/>
|
||||
<rect class="fil1" x="1010" y="3596" width="2217" height="204"/>
|
||||
<rect class="fil1" x="1179" y="3743" width="1885" height="153"/>
|
||||
<rect class="fil1" x="1006" y="3863" width="2217" height="204"/>
|
||||
<path class="fil2" d="M1612 2107l1061 0 288 1489 266 0 0 204 -161 -3 0 63 157 3 0 204 -316 0c0,0 -26,-149 -71,-267 -45,-117 -85,-80 -135,-448 -49,-368 -150,-1101 -150,-1101l-939 -145z"/>
|
||||
<polygon class="fil2" points="1181,3797 3066,3797 3066,3860 1181,3860 "/>
|
||||
<rect class="fil1" x="1300" y="1887" width="1638" height="220"/>
|
||||
<path class="fil1" d="M1490 1966c0,0 -241,-328 -199,-572 50,-285 382,-408 875,-435 148,-8 98,1007 98,1007l-774 0z"/>
|
||||
<path class="fil1" d="M2726 1966c0,0 241,-328 199,-572 -50,-285 -382,-408 -875,-435 -148,-8 -98,1007 -98,1007l774 0z"/>
|
||||
<path class="fil1" d="M1772 1010c0,0 253,-63 226,-169 -27,-106 114,-19 114,-19l10 146 -350 43z"/>
|
||||
<path class="fil1" d="M2384 1003c0,0 -154,-75 -159,-161 -5,-86 -114,-19 -114,-19l-10 147 282 34z"/>
|
||||
<ellipse class="fil1" cx="1967" cy="634" rx="91" ry="89"/>
|
||||
<ellipse class="fil1" cx="1995" cy="772" rx="64" ry="62"/>
|
||||
<ellipse class="fil1" cx="2250" cy="635" rx="91" ry="89"/>
|
||||
<ellipse class="fil1" cx="2222" cy="773" rx="64" ry="62"/>
|
||||
<ellipse class="fil1" cx="2112" cy="556" rx="97" ry="101"/>
|
||||
<polygon class="fil1" points="2047,881 1967,634 2112,556 2250,635 2176,886 "/>
|
||||
<path class="fil2" d="M2226 842c0,0 -49,94 18,144 66,50 231,81 319,139 89,58 240,120 241,284 2,164 -33,236 -139,351 -107,116 -175,126 -175,126l288 0c0,0 87,-152 110,-226 23,-74 52,-167 36,-267 -16,-100 -90,-225 -206,-280 -116,-56 -86,-51 -170,-75 -83,-24 -94,-26 -103,-28 -9,-2 -95,-27 -95,-27 0,0 -45,-25 -70,-51 -25,-26 -55,-91 -55,-91z"/>
|
||||
<polygon class="fil2" points="2490,1887 2524,2107 2938,2107 2938,1887 "/>
|
||||
</g>
|
||||
</svg>
|
Before Width: | Height: | Size: 2.6 KiB |
|
@ -1,25 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<!-- Creator: CorelDRAW X5 -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" width="42.3332mm" height="42.3332mm" style="shape-rendering:geometricPrecision; text-rendering:geometricPrecision; image-rendering:optimizeQuality; fill-rule:evenodd; clip-rule:evenodd"
|
||||
viewBox="0 0 4233 4233"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<defs>
|
||||
<style type="text/css">
|
||||
<![CDATA[
|
||||
.fil1 {fill:#E3E3E3}
|
||||
.fil0 {fill:white}
|
||||
]]>
|
||||
</style>
|
||||
</defs>
|
||||
<g id="Warstwa_x0020_1">
|
||||
<metadata id="CorelCorpID_0Corel-Layer"/>
|
||||
<path class="fil0" d="M1040 1733c-183,169 -132,155 -60,286 72,131 195,227 503,77 308,-150 390,-133 517,-103 127,30 159,91 167,157 8,66 -186,227 -400,433 -214,205 -402,243 -373,671 30,428 19,525 -42,592 -61,67 1074,263 1221,196 147,-68 543,-55 465,-460 -78,-406 -208,-900 -10,-1238 198,-338 398,-1042 -527,-1423 -925,-381 -558,35 -648,83 -90,47 -121,57 -175,83 -98,47 -463,485 -639,647z"/>
|
||||
<rect class="fil0" x="1101" y="3595" width="2217" height="204"/>
|
||||
<rect class="fil0" x="1271" y="3742" width="1885" height="153"/>
|
||||
<rect class="fil0" x="1098" y="3861" width="2217" height="204"/>
|
||||
<path class="fil1" d="M1826 2000c0,0 85,-124 262,-131 176,-7 75,-331 75,-331 0,0 227,293 227,444 0,151 -239,217 -239,217 0,0 -30,-182 -324,-200z"/>
|
||||
<path class="fil1" d="M2831 1100c0,0 425,238 221,842 -204,604 -560,915 -498,1226 62,311 9,364 31,391 22,27 93,27 195,-4 102,-31 84,44 93,89 9,44 72,421 72,421l369 0 0 -204 -159 0 0 -63 163 0 0 -204 -277 0c0,0 -95,-448 -116,-728 -21,-280 58,-454 170,-653 111,-200 231,-777 -265,-1113z"/>
|
||||
<polygon class="fil1" points="1272,3798 3156,3798 3156,3861 1272,3861 "/>
|
||||
</g>
|
||||
</svg>
|
Before Width: | Height: | Size: 1.8 KiB |
|
@ -1,30 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<!-- Creator: CorelDRAW X5 -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" width="42.3332mm" height="42.3332mm" style="shape-rendering:geometricPrecision; text-rendering:geometricPrecision; image-rendering:optimizeQuality; fill-rule:evenodd; clip-rule:evenodd"
|
||||
viewBox="0 0 4233 4233"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<defs>
|
||||
<style type="text/css">
|
||||
<![CDATA[
|
||||
.fil1 {fill:#E3E3E3}
|
||||
.fil0 {fill:white}
|
||||
]]>
|
||||
</style>
|
||||
</defs>
|
||||
<g id="Warstwa_x0020_1">
|
||||
<metadata id="CorelCorpID_0Corel-Layer"/>
|
||||
<rect class="fil0" x="1165" y="3595" width="1907" height="204"/>
|
||||
<rect class="fil0" x="1310" y="3742" width="1621" height="153"/>
|
||||
<rect class="fil0" x="1162" y="3862" width="1907" height="204"/>
|
||||
<polygon class="fil1" points="1311,3799 2932,3799 2932,3861 1311,3861 "/>
|
||||
<polygon class="fil0" points="1871,2415 2167,2411 2167,3693 1550,3684 "/>
|
||||
<polygon class="fil0" points="2352,2415 2088,2411 2087,3693 2705,3684 "/>
|
||||
<rect class="fil0" x="1566" y="2263" width="1115" height="158"/>
|
||||
<ellipse class="fil0" cx="2101" cy="1905" rx="414" ry="433"/>
|
||||
<path class="fil1" d="M2191 1482c0,0 195,147 218,349 36,315 -162,418 -446,351 -143,-34 -244,-110 -231,-82 0,0 21,51 59,90 37,39 82,75 82,75l457 0c0,0 74,-41 114,-117 40,-77 72,-137 72,-244 0,-107 -41,-199 -68,-237 -27,-38 -56,-71 -105,-114 -48,-44 -152,-71 -152,-71z"/>
|
||||
<polygon class="fil1" points="2286,2263 2355,2421 2681,2421 2681,2263 "/>
|
||||
<polygon class="fil1" points="1770,2421 2355,2421 2701,3663 2446,3663 2277,2568 "/>
|
||||
<polygon class="fil1" points="2678,3595 2748,4065 3069,4065 3069,3862 2932,3861 2932,3799 3072,3799 3072,3595 "/>
|
||||
</g>
|
||||
</svg>
|
Before Width: | Height: | Size: 1.8 KiB |
|
@ -1,33 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<!-- Creator: CorelDRAW X5 -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" width="42.3332mm" height="42.3332mm" style="shape-rendering:geometricPrecision; text-rendering:geometricPrecision; image-rendering:optimizeQuality; fill-rule:evenodd; clip-rule:evenodd"
|
||||
viewBox="0 0 4233 4233"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<defs>
|
||||
<style type="text/css">
|
||||
<![CDATA[
|
||||
.fil1 {fill:#E3E3E3}
|
||||
.fil0 {fill:white}
|
||||
]]>
|
||||
</style>
|
||||
</defs>
|
||||
<g id="Warstwa_x0020_1">
|
||||
<metadata id="CorelCorpID_0Corel-Layer"/>
|
||||
<polygon class="fil0" points="1656,1982 2548,1985 2968,3643 1280,3620 "/>
|
||||
<rect class="fil0" x="1010" y="3590" width="2217" height="204"/>
|
||||
<rect class="fil0" x="1179" y="3737" width="1885" height="153"/>
|
||||
<rect class="fil0" x="1006" y="3857" width="2217" height="204"/>
|
||||
<path class="fil1" d="M1612 2101l965 0 383 1489 266 0 0 204 -161 0 0 60 157 3 0 204 -316 0c0,0 -26,-149 -71,-267 -45,-117 -85,-80 -135,-448 -49,-368 -253,-1117 -253,-1117l-836 -129z"/>
|
||||
<polygon class="fil1" points="1181,3792 3066,3792 3066,3854 1181,3854 "/>
|
||||
<rect class="fil0" x="1300" y="1881" width="1638" height="220"/>
|
||||
<polygon class="fil0" points="1564,1918 1161,1154 1658,1413 1618,730 2096,1286 2128,1918 "/>
|
||||
<polygon class="fil0" points="2628,1919 3031,1154 2533,1414 2573,730 2095,1287 2076,1919 "/>
|
||||
<circle class="fil0" cx="1161" cy="1154" r="100"/>
|
||||
<circle class="fil0" cx="1618" cy="730" r="100"/>
|
||||
<circle class="fil0" cx="2573" cy="730" r="100"/>
|
||||
<circle class="fil1" cx="3031" cy="1154" r="100"/>
|
||||
<path class="fil1" d="M2633 650l-97 173 -240 1058 351 0 337 -639 -42 -42 -409 213 43 -584c0,0 182,-32 57,-180z"/>
|
||||
<polygon class="fil1" points="2472,1881 2472,2101 2938,2101 2938,1881 "/>
|
||||
</g>
|
||||
</svg>
|
Before Width: | Height: | Size: 1.9 KiB |
|
@ -1,26 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<!-- Creator: CorelDRAW X5 -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" width="42.3332mm" height="42.3332mm" style="shape-rendering:geometricPrecision; text-rendering:geometricPrecision; image-rendering:optimizeQuality; fill-rule:evenodd; clip-rule:evenodd"
|
||||
viewBox="0 0 4233 4233"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<defs>
|
||||
<style type="text/css">
|
||||
<![CDATA[
|
||||
.fil1 {fill:#E3E3E3}
|
||||
.fil0 {fill:white}
|
||||
]]>
|
||||
</style>
|
||||
</defs>
|
||||
<g id="Warstwa_x0020_1">
|
||||
<metadata id="CorelCorpID_0Corel-Layer"/>
|
||||
<polygon class="fil0" points="1471,2050 2754,2054 2963,3644 1274,3621 "/>
|
||||
<polygon class="fil0" points="1213,2103 3015,2103 3233,951 2709,982 2709,1264 2402,1264 2402,991 1852,991 1852,1264 1533,1264 1530,992 1009,951 "/>
|
||||
<rect class="fil0" x="1005" y="3591" width="2217" height="204"/>
|
||||
<rect class="fil0" x="1174" y="3738" width="1885" height="153"/>
|
||||
<rect class="fil0" x="1001" y="3857" width="2217" height="204"/>
|
||||
<path class="fil1" d="M3064 961c0,0 -146,919 -168,986 -22,67 -94,155 -94,155l213 0 217 -1151 -169 10z"/>
|
||||
<path class="fil1" d="M1598 2103l1162 0 195 1488 266 0 0 204 -161 -3 0 63 157 3 0 204 -316 0c0,0 -24,-171 -67,-269 -42,-99 -90,-78 -139,-445 -49,-368 -153,-1020 -153,-1020l-945 -223z"/>
|
||||
<polygon class="fil1" points="1176,3792 3061,3792 3061,3855 1176,3855 "/>
|
||||
</g>
|
||||
</svg>
|
Before Width: | Height: | Size: 1.5 KiB |
|
@ -1,4 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" version="1.1"
|
||||
viewBox="0 0 500 500" width="500" height="500" id="clear_svg">
|
||||
</svg>
|
Before Width: | Height: | Size: 179 B |
|
@ -38,7 +38,7 @@
|
|||
#editor-side {
|
||||
position: absolute;
|
||||
left: 552px;
|
||||
top: 90px;
|
||||
top: 114px;
|
||||
width: 228px;
|
||||
}
|
||||
#editor-side > div {
|
||||
|
|
|
@ -76,7 +76,8 @@ time {
|
|||
font-style: normal;
|
||||
}
|
||||
.is::before,
|
||||
[data-icon]::before {
|
||||
[data-icon]::before,
|
||||
.is-after::after {
|
||||
font-size: 1.2em;
|
||||
vertical-align: middle;
|
||||
font-family: "lichess" !important;
|
||||
|
@ -127,7 +128,6 @@ body.blind_mode [data-icon]::before {
|
|||
.is.color-icon.random::before {
|
||||
content: "l";
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'ChessSansPiratf';
|
||||
src: url("../fonts/ChessSansPiratf.eot");
|
||||
|
@ -886,7 +886,8 @@ div.side_box .game_infos .bookmark {
|
|||
opacity: 0;
|
||||
transform: translate(0, -20px);
|
||||
transition: 0.3s;
|
||||
z-index: 10; /* to go over the board */
|
||||
z-index: 10;
|
||||
/* to go over the board */
|
||||
}
|
||||
div.side_box .game_infos:hover .bookmark {
|
||||
transform: translate(0, 0);
|
||||
|
@ -895,8 +896,6 @@ div.side_box .game_infos:hover .bookmark {
|
|||
div.side_box .game_infos .setup {
|
||||
display: block;
|
||||
}
|
||||
div.side_box div.players {
|
||||
}
|
||||
div.side_box div.status {
|
||||
text-align: center;
|
||||
margin-top: 5px;
|
||||
|
@ -2194,3 +2193,35 @@ input.copyable {
|
|||
-moz-transform: translateX(-8px);
|
||||
transform: translateX(-8px);
|
||||
}
|
||||
.continue_with {
|
||||
display: none;
|
||||
}
|
||||
.continue_with .button {
|
||||
display: inline-block;
|
||||
margin: 1em;
|
||||
}
|
||||
#modal-overlay {
|
||||
display: block;
|
||||
position: fixed;
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
overflow: auto;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
z-index: 5;
|
||||
cursor: pointer;
|
||||
}
|
||||
#modal-wrap {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
top: 40%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
padding: 20px;
|
||||
border-radius: 3px;
|
||||
box-shadow: 0 0 95px 25px rgba(0, 0, 0, 0.8);
|
||||
background: #fff;
|
||||
text-align: center;
|
||||
cursor: default;
|
||||
}
|
||||
|
|
|
@ -140,7 +140,8 @@ body.dark .crosstable td:nth-last-child(3) {
|
|||
body.dark .crosstable td.current {
|
||||
box-shadow: none;
|
||||
}
|
||||
body.dark .crosstable td.current a {
|
||||
body.dark .crosstable td.current a,
|
||||
body.dark #modal-wrap {
|
||||
background-color: #303030;
|
||||
}
|
||||
body.dark .ui-slider,
|
||||
|
|
|
@ -107,10 +107,6 @@
|
|||
height: 22px;
|
||||
display:inline-block;
|
||||
}
|
||||
#puzzle div.continue.links {
|
||||
margin: 20px 0 20px 0;
|
||||
text-align: center;
|
||||
}
|
||||
#puzzle #GameButtons {
|
||||
display: inline-block;
|
||||
margin-left: 10px;
|
||||
|
|
|
@ -1,11 +1,14 @@
|
|||
#tournament table.slist td:first-child a {
|
||||
#tournament table.slist td:first-child a,
|
||||
#tournament_list table.slist td:first-child a,
|
||||
ol.scheduled_tournaments a {
|
||||
font-weight: bold;
|
||||
text-decoration: none;
|
||||
}
|
||||
#tournament table.slist td:first-child a:hover {
|
||||
#tournament table.slist td:first-child a:hover,
|
||||
#tournament_list table.slist td:first-child a:hover,
|
||||
ol.scheduled_tournaments a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
#tournament table.slist tbody.scheduled a,
|
||||
#tournament.scheduled h1,
|
||||
#tournament table tr.standing.scheduled:first-child td:first-child {
|
||||
color: #d59120;
|
||||
|
@ -26,6 +29,12 @@
|
|||
padding-top: 20px;
|
||||
overflow: hidden;
|
||||
}
|
||||
#tournament a.user_link em {
|
||||
font-weight: lighter;
|
||||
font-style: italic;
|
||||
font-size: 0.8em;
|
||||
padding-left: 5px;
|
||||
}
|
||||
#tournament a.pov {
|
||||
display: block;
|
||||
margin: 0 25px 1em 25px;
|
||||
|
@ -57,8 +66,8 @@
|
|||
#tournament a.pov::after {
|
||||
right: 15px;
|
||||
}
|
||||
#tournament table.slist .create td {
|
||||
padding: 0.6em 1.2em 0.6em 0.6em;
|
||||
#tournament_list table.slist .create td {
|
||||
padding: 2em;
|
||||
text-align: center;
|
||||
}
|
||||
#tournament table.slist span.rank {
|
||||
|
@ -81,11 +90,12 @@
|
|||
}
|
||||
#tournament table.standing td.name,
|
||||
#tournament table.standing th:first-child {
|
||||
padding-left: 15px;
|
||||
padding-left: 10px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
#tournament table.standing td.name span {
|
||||
display: inline-block;
|
||||
width: 20px;
|
||||
width: 24px;
|
||||
}
|
||||
#tournament table.standing tr.me td.name {
|
||||
border-left: 10px solid #d59120;
|
||||
|
@ -97,16 +107,19 @@
|
|||
#tournament table.standing .legend span {
|
||||
margin-left: 10px;
|
||||
}
|
||||
#tournament table.standing .sheet {
|
||||
letter-spacing: -1.5px;
|
||||
}
|
||||
#tournament table.standing .sheet,
|
||||
#tournament table.standing .total {
|
||||
text-align: right;
|
||||
font-family: monospace;
|
||||
letter-spacing: 0;
|
||||
}
|
||||
#tournament div.standing_wrap:hover td.total {
|
||||
padding-right: 22px;
|
||||
#tournament table.standing .sheet span,
|
||||
#tournament table.standing .total span {
|
||||
display: inline-block;
|
||||
width: 12px;
|
||||
}
|
||||
#tournament div.standing_wrap td.total {
|
||||
padding-right: 20px;
|
||||
}
|
||||
#tournament .double {
|
||||
color: #d59120;
|
||||
|
@ -124,11 +137,11 @@
|
|||
width: 0;
|
||||
background: #759900;
|
||||
opacity: 0.5;
|
||||
transition: width 0.5s;
|
||||
}
|
||||
#tournament form.inline {
|
||||
display: inline;
|
||||
#tournament button.right {
|
||||
float: right;
|
||||
padding-right: 0.6em;
|
||||
margin-left: 5px;
|
||||
}
|
||||
#tournament span.joined {
|
||||
font-weight: bold;
|
||||
|
@ -182,29 +195,31 @@
|
|||
border: 1px solid #ccc;
|
||||
padding: 3px;
|
||||
overflow: auto;
|
||||
}
|
||||
#tournament_side div.pairings {
|
||||
text-align: center;
|
||||
}
|
||||
#tournament_side div.pairings a {
|
||||
#tournament_side a {
|
||||
padding: 0.3em 0.2em;
|
||||
display: block;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-decoration: none;
|
||||
}
|
||||
#tournament_side div.pairings span {
|
||||
#tournament_side a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
#tournament_side span {
|
||||
font-weight: bold;
|
||||
}
|
||||
#tournament_side div.pairings span.win {
|
||||
#tournament_side span.win {
|
||||
color: #00aa00;
|
||||
}
|
||||
#tournament_side div.pairings span.loss {
|
||||
#tournament_side span.loss {
|
||||
color: #aa0000;
|
||||
}
|
||||
#tournament_side div.pairings span.draw {
|
||||
#tournament_side span.draw {
|
||||
color: #aaaa00;
|
||||
}
|
||||
#tournament_side div.pairings em {
|
||||
#tournament_side em {
|
||||
font-style: italic;
|
||||
}
|
||||
#tournament article.faq h2 {
|
||||
|
@ -251,10 +266,3 @@ ol.scheduled_tournaments li::before {
|
|||
ol.scheduled_tournaments li:hover::before {
|
||||
opacity: 1;
|
||||
}
|
||||
ol.scheduled_tournaments a {
|
||||
font-weight: bold;
|
||||
text-decoration: none;
|
||||
}
|
||||
ol.scheduled_tournaments a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
|
|
@ -30,11 +30,11 @@
|
|||
"watchify": "^1.0.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"chessground": "1.8.6",
|
||||
"chessground": "1.8.10",
|
||||
"chessli.js": "file:../chessli",
|
||||
"game": "file:../game",
|
||||
"lodash-node": "^2.4.1",
|
||||
"mithril": "0.1.27",
|
||||
"mithril": "0.1.28",
|
||||
"mousetrap": "0.0.1"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,14 +25,15 @@ module.exports = {
|
|||
prev: function(ctrl) {
|
||||
var p = ctrl.vm.path;
|
||||
var len = p.length;
|
||||
if (len == 1) {
|
||||
if (p[0].ply == 1) return;
|
||||
if (len === 1) {
|
||||
if (p[0].ply === 0) return;
|
||||
p[0].ply--;
|
||||
} else {
|
||||
if (p[len - 1].ply > p[len - 2].ply) p[len - 1].ply--;
|
||||
else {
|
||||
p.pop();
|
||||
p[len - 2].variation = null;
|
||||
if (p[len - 2].ply > 1) p[len - 2].ply--;
|
||||
}
|
||||
}
|
||||
ctrl.jump(p);
|
||||
|
|
|
@ -18,7 +18,6 @@ module.exports = function(cfg, router, i18n, onChange) {
|
|||
path: initialPath,
|
||||
pathStr: treePath.write(initialPath),
|
||||
situation: null,
|
||||
continue: false,
|
||||
comments: true,
|
||||
flip: false
|
||||
};
|
||||
|
|
|
@ -23,11 +23,11 @@ module.exports = function(ctrl) {
|
|||
control.next(ctrl);
|
||||
m.redraw();
|
||||
}));
|
||||
k.bind(['up', 'j'], preventing(function() {
|
||||
k.bind(['up', 'k'], preventing(function() {
|
||||
control.first(ctrl);
|
||||
m.redraw();
|
||||
}));
|
||||
k.bind(['down', 'k'], preventing(function() {
|
||||
k.bind(['down', 'j'], preventing(function() {
|
||||
control.last(ctrl);
|
||||
m.redraw();
|
||||
}));
|
||||
|
|
|
@ -24,3 +24,7 @@ module.exports = function(element, config, router, i18n, onChange) {
|
|||
}
|
||||
};
|
||||
};
|
||||
|
||||
// lol, that's for the rest of lichess to access mithril
|
||||
// without having to include it a second time
|
||||
window.Chessground = require('chessground');
|
||||
|
|
|
@ -270,20 +270,22 @@ function buttons(ctrl) {
|
|||
m('div.jumps.hint--bottom', {
|
||||
'data-hint': 'Tip: use your keyboard arrow keys!'
|
||||
}, [
|
||||
['first', 'W', control.first],
|
||||
['first', 'W', control.first, ],
|
||||
['prev', 'Y', control.prev],
|
||||
['next', 'X', control.next],
|
||||
['last', 'V', control.last]
|
||||
].map(function(b) {
|
||||
var enabled = true;
|
||||
return m('a', {
|
||||
class: 'button ' + b[0] + ' ' + classSet({
|
||||
disabled: (ctrl.broken || !enabled),
|
||||
glowing: ctrl.vm.late && b[0] === 'last'
|
||||
}),
|
||||
'data-icon': b[1],
|
||||
onclick: enabled ? partial(b[2], ctrl) : null
|
||||
});
|
||||
return {
|
||||
tag: 'a',
|
||||
attrs: {
|
||||
class: 'button ' + b[0] + ' ' + classSet({
|
||||
disabled: ctrl.broken,
|
||||
glowing: ctrl.vm.late && b[0] === 'last'
|
||||
}),
|
||||
'data-icon': b[1],
|
||||
onclick: partial(b[2], ctrl)
|
||||
}
|
||||
};
|
||||
})),
|
||||
m('a.button.hint--bottom', flipAttrs, m('span[data-icon=B]')),
|
||||
ctrl.data.inGame ? null : m('a.button.hint--bottom', {
|
||||
|
@ -294,20 +296,21 @@ function buttons(ctrl) {
|
|||
ctrl.data.inGame ? null : m('a.button.hint--bottom', {
|
||||
'data-hint': ctrl.trans('continueFromHere'),
|
||||
onclick: function() {
|
||||
ctrl.vm.continue = !ctrl.vm.continue
|
||||
$.modal($('.continue_with.' + ctrl.data.game.id));
|
||||
}
|
||||
}, m('span[data-icon=U]'))
|
||||
]),
|
||||
ctrl.vm.continue ? m('div.continue', [
|
||||
m('div.continue_with.' + ctrl.data.game.id, [
|
||||
m('a.button', {
|
||||
href: ctrl.data.userAnalysis ? '/?fen=' + ctrl.vm.situation.fen + '#ai' : ctrl.router.Round.continue(ctrl.data.game.id, 'ai').url + '?fen=' + ctrl.vm.situation.fen,
|
||||
rel: 'nofollow'
|
||||
}, ctrl.trans('playWithTheMachine')),
|
||||
m('br'),
|
||||
m('a.button', {
|
||||
href: ctrl.data.userAnalysis ? '/?fen=' + ctrl.vm.situation.fen + '#friend' : ctrl.router.Round.continue(ctrl.data.game.id, 'friend').url + '?fen=' + ctrl.vm.situation.fen,
|
||||
rel: 'nofollow'
|
||||
}, ctrl.trans('playWithAFriend'))
|
||||
]) : null
|
||||
])
|
||||
];
|
||||
}
|
||||
|
||||
|
|
2
ui/build
|
@ -5,7 +5,7 @@ target=${1-dev}
|
|||
|
||||
mkdir -p public/compiled
|
||||
|
||||
for app in editor puzzle round analyse lobby; do
|
||||
for app in editor puzzle round analyse lobby tournament; do
|
||||
cd ui/$app
|
||||
npm install && gulp $target
|
||||
cd -
|
||||
|
|
|
@ -30,8 +30,8 @@
|
|||
"watchify": "^1.0.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"chessground": "1.8.6",
|
||||
"chessground": "1.8.10",
|
||||
"lodash-node": "^2.4.1",
|
||||
"mithril": "0.1.27"
|
||||
"mithril": "0.1.28"
|
||||
}
|
||||
}
|
||||
|
|