parent
f835092d58
commit
658429d936
|
@ -19,6 +19,7 @@ trait ScalatagsAttrs {
|
|||
lazy val datatime24h = attr("data-time_24h")
|
||||
lazy val dataColor = attr("data-color")
|
||||
lazy val dataFen = attr("data-fen")
|
||||
lazy val dataRel = attr("data-rel")
|
||||
lazy val novalidate = attr("novalidate")
|
||||
object frame {
|
||||
val scrolling = attr("scrolling")
|
||||
|
@ -41,6 +42,7 @@ trait ScalatagsSnippets extends Cap {
|
|||
val styleTag = tag("style")(`type` := "text/css")
|
||||
val ratingTag = tag("rating")
|
||||
val countTag = tag("count")
|
||||
val badTag = tag("bad")
|
||||
|
||||
lazy val dataBotAttr = attr("data-bot").empty
|
||||
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
package views.html.challenge
|
||||
|
||||
import lila.api.Context
|
||||
import lila.app.templating.Environment._
|
||||
import lila.app.ui.ScalatagsTemplate._
|
||||
import lila.common.String.html.safeJsonValue
|
||||
|
||||
import controllers.routes
|
||||
|
||||
object bits {
|
||||
|
||||
def js(c: lila.challenge.Challenge, json: play.api.libs.json.JsObject, owner: Boolean)(implicit ctx: Context) =
|
||||
frag(
|
||||
jsTag("challenge.js", async = true),
|
||||
embedJs(s"""lichess=window.lichess||{};customWs=true;lichess_challenge = {
|
||||
socketUrl: '${routes.Challenge.websocket(c.id, apiVersion.value)}',
|
||||
xhrUrl: '${routes.Challenge.show(c.id)}',
|
||||
owner: $owner,
|
||||
data: ${safeJsonValue(json)}
|
||||
};""")
|
||||
)
|
||||
|
||||
def explanation(c: lila.challenge.Challenge)(implicit ctx: Context) = p(
|
||||
views.html.game.bits.variantLink(c.variant, variantName(c.variant)),
|
||||
" • ",
|
||||
modeName(c.mode),
|
||||
br,
|
||||
c.daysPerTurn map { days =>
|
||||
span(cls := "text", dataIcon := ";")(
|
||||
if (days == 1) trans.oneDay.frag()
|
||||
else trans.nbDays.pluralSameFrag(days)
|
||||
)
|
||||
} getOrElse span(cls := "text", dataIcon := "p")(shortClockName(c.clock.map(_.config)))
|
||||
)
|
||||
}
|
|
@ -1,10 +0,0 @@
|
|||
@(c: lila.challenge.Challenge)(implicit ctx: Context)
|
||||
|
||||
<p>
|
||||
@views.html.game.bits.variantLink(c.variant, variantName(c.variant)).toHtml • @modeName(c.mode)<br />
|
||||
@c.daysPerTurn.map { days =>
|
||||
<span class="text" data-icon=";">@if(days == 1){@trans.oneDay()}else{@trans.nbDays.pluralSame(days)}</span>
|
||||
}.getOrElse {
|
||||
<span class="text" data-icon="p">@shortClockName(c.clock.map(_.config))</span>
|
||||
}
|
||||
</p>
|
|
@ -1,11 +0,0 @@
|
|||
@(c: lila.challenge.Challenge, json: play.api.libs.json.JsObject, owner: Boolean)(implicit ctx: Context)
|
||||
|
||||
@embedJs {
|
||||
lichess_challenge = {
|
||||
socketUrl: '@routes.Challenge.websocket(c.id, apiVersion.value)',
|
||||
xhrUrl: '@routes.Challenge.show(c.id)',
|
||||
owner: @owner,
|
||||
data: @toJsonHtml(json)
|
||||
};
|
||||
}
|
||||
@jsTag("challenge.js", async = false)
|
|
@ -0,0 +1,90 @@
|
|||
package views.html.challenge
|
||||
|
||||
import lila.api.Context
|
||||
import lila.app.templating.Environment._
|
||||
import lila.app.ui.ScalatagsTemplate._
|
||||
import lila.challenge.Challenge.Status
|
||||
|
||||
import controllers.routes
|
||||
|
||||
object mine {
|
||||
|
||||
def apply(c: lila.challenge.Challenge, json: play.api.libs.json.JsObject, error: Option[String])(implicit ctx: Context) = {
|
||||
|
||||
val cancelForm =
|
||||
form(method := "post", action := routes.Challenge.cancel(c.id), cls := "xhr")(
|
||||
button(tpe := "submit", cls := "button text", dataIcon := "L")(trans.cancel.frag())
|
||||
)
|
||||
|
||||
views.html.base.layout(
|
||||
title = challengeTitle(c),
|
||||
openGraph = challengeOpenGraph(c).some,
|
||||
responsive = true,
|
||||
moreJs = bits.js(c, json, true),
|
||||
moreCss = responsiveCssTag("challenge.page")
|
||||
) {
|
||||
main(cls := "page-small challenge-page box box-pad")(
|
||||
c.status match {
|
||||
case Status.Created | Status.Offline => div(id := "ping-challenge")(
|
||||
h1(trans.challengeToPlay.frag()),
|
||||
c.destUserId.map { destId =>
|
||||
frag(
|
||||
userIdLink(destId.some, cssClass = "target".some),
|
||||
spinner,
|
||||
trans.waitingForOpponent.frag(),
|
||||
br,
|
||||
cancelForm
|
||||
)
|
||||
} getOrElse frag(
|
||||
trans.toInviteSomeoneToPlayGiveThisUrl(),
|
||||
": ",
|
||||
input(
|
||||
id := "challenge-id",
|
||||
cls := "copyable autoselect",
|
||||
spellcheck := "false",
|
||||
readonly := "true",
|
||||
value := s"$netBaseUrl${routes.Round.watcher(c.id, "white")}"
|
||||
),
|
||||
button(title := "Copy URL", cls := "copy button", dataRel := "challenge-id", dataIcon := "\""),
|
||||
br,
|
||||
trans.theFirstPersonToComeOnThisUrlWillPlayWithYou.frag(),
|
||||
br,
|
||||
ctx.isAuth option frag(
|
||||
br,
|
||||
"Or invite a lichess user:",
|
||||
br,
|
||||
form(cls := "user-invite", action := routes.Challenge.toFriend(c.id), method := "POST")(
|
||||
input(name := "username", cls := "friend-autocomplete", placeholder := trans.search.txt()),
|
||||
error.map { badTag(_) }
|
||||
),
|
||||
br
|
||||
),
|
||||
cancelForm
|
||||
),
|
||||
c.initialFen.map { fen =>
|
||||
frag(
|
||||
br,
|
||||
views.html.game.bits.miniBoard(fen, color = c.finalColor)
|
||||
)
|
||||
}
|
||||
)
|
||||
case Status.Declined => div(cls := "declined")(
|
||||
h2("Challenge declined"),
|
||||
a(cls := "button", href := routes.Lobby.home())(trans.newOpponent.frag())
|
||||
)
|
||||
case Status.Accepted => div(cls := "accepted")(
|
||||
h2("Challenge accepted!"),
|
||||
a(id := "challenge_redirect", href := routes.Round.watcher(c.id, "white"), cls := "button")(
|
||||
trans.joinTheGame.frag()
|
||||
)
|
||||
)
|
||||
case Status.Canceled => div(cls := "canceled")(
|
||||
h2("Challenge canceled."),
|
||||
a(cls := "button", href := routes.Lobby.home())(trans.newOpponent.frag())
|
||||
)
|
||||
},
|
||||
bits.explanation(c)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,81 +0,0 @@
|
|||
@(c: lila.challenge.Challenge, json: play.api.libs.json.JsObject, error: Option[String])(implicit ctx: Context)
|
||||
|
||||
@import lila.challenge.Challenge.Status
|
||||
|
||||
@cancelForm = {
|
||||
<form method="post" action="@routes.Challenge.cancel(c.id)" class="xhr">
|
||||
<button type="submit" class="button text" data-icon="L">@trans.cancel()</button>
|
||||
</form>
|
||||
}
|
||||
|
||||
@round.bits.layout(
|
||||
variant = c.variant,
|
||||
title = challengeTitle(c),
|
||||
openGraph = challengeOpenGraph(c).some,
|
||||
moreJs = js(c, json, true),
|
||||
moreCss = cssTag("challenge.css")) {
|
||||
<div class="lichess_game" id="challenge">
|
||||
<div class="lichess_board_wrap cg-512">
|
||||
<div class="lichess_overboard padded">
|
||||
@c.status match {
|
||||
case Status.Created | Status.Offline => {
|
||||
<div id="ping_challenge">
|
||||
<h1>@trans.challengeToPlay()</h1>
|
||||
@c.destUserId.map { destId =>
|
||||
@userIdLink(destId.some, cssClass="target".some)
|
||||
@spinner
|
||||
@trans.waitingForOpponent()<br />
|
||||
@cancelForm
|
||||
}.getOrElse {
|
||||
@trans.toInviteSomeoneToPlayGiveThisUrl():
|
||||
<input id="lichess_id_input" class="copyable autoselect" spellcheck="false" readonly="true" value="@netBaseUrl@routes.Round.watcher(c.id, "white")" />
|
||||
<button title="Copy URL" class="copy button" data-rel="lichess_id_input" data-icon="""></button>
|
||||
<br />
|
||||
@trans.theFirstPersonToComeOnThisUrlWillPlayWithYou()
|
||||
<br />
|
||||
@if(ctx.isAuth) {
|
||||
<br />
|
||||
Or invite a lichess user:<br />
|
||||
<form class="user-invite" action="@routes.Challenge.toFriend(c.id)" method="POST">
|
||||
<input name="username" class="friend-autocomplete" placeholder="@trans.search()" />
|
||||
@error.map { e =>
|
||||
<p style="color: red">@e</p>
|
||||
}
|
||||
</form>
|
||||
<br />
|
||||
}
|
||||
@cancelForm
|
||||
}
|
||||
@c.initialFen.map { fen =>
|
||||
<br />
|
||||
@views.html.game.bits.miniBoard(fen, color = c.finalColor).toHtml
|
||||
}
|
||||
</div>
|
||||
}
|
||||
case Status.Declined => {
|
||||
<div class="declined">
|
||||
<h2>Challenge declined</h2>
|
||||
<a class="button" href="@routes.Lobby.home()">@trans.newOpponent()</a>
|
||||
</div>
|
||||
}
|
||||
case Status.Accepted => {
|
||||
<div class="accepted">
|
||||
<h2>Challenge accepted!</h2>
|
||||
<a id="challenge_redirect" href="@routes.Round.watcher(c.id, "white")" class="button">@trans.joinTheGame()</a>
|
||||
</div>
|
||||
}
|
||||
case Status.Canceled => {
|
||||
<div class="canceled">
|
||||
<h2>Challenge canceled.</h2>
|
||||
<a class="button" href="@routes.Lobby.home()">@trans.newOpponent()</a>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
<br />
|
||||
@explanation(c)
|
||||
</div>
|
||||
<div class="lichess_board"></div>
|
||||
</div>
|
||||
<div class="lichess_ground"></div>
|
||||
</div>
|
||||
}.toHtml
|
|
@ -6,14 +6,14 @@
|
|||
variant = c.variant,
|
||||
title = challengeTitle(c),
|
||||
openGraph = challengeOpenGraph(c).some,
|
||||
moreJs = js(c, json, false)) {
|
||||
moreJs = bits.js(c, json, false)) {
|
||||
<div class="lichess_game" id="challenge">
|
||||
<div class="lichess_board_wrap cg-512">
|
||||
<div class="lichess_board"></div>
|
||||
<div class="lichess_overboard padded">
|
||||
@userIdLink(c.challengerUserId)
|
||||
<br />
|
||||
@explanation(c)
|
||||
bits.explanation(c)
|
||||
@c.initialFen.map { fen =>
|
||||
<br />
|
||||
@views.html.game.bits.miniBoard(fen, color = !c.finalColor).toHtml
|
||||
|
|
|
@ -38,7 +38,7 @@ object home {
|
|||
embedJs {
|
||||
val playbanJs = playban.fold("null")(pb => safeJsonValue(Json.obj("minutes" -> pb.mins, "remainingSeconds" -> (pb.remainingSeconds + 3))))
|
||||
val transJs = safeJsonValue(i18nJsObject(translations))
|
||||
s"""window.lichess=window.lichess||{};window.customWS=true;lichess_lobby={data:${safeJsonValue(data)},playban:$playbanJs,i18n:$transJs}"""
|
||||
s"""lichess=window.lichess||{};customWS=true;lichess_lobby={data:${safeJsonValue(data)},playban:$playbanJs,i18n:$transJs}"""
|
||||
}
|
||||
),
|
||||
moreCss = responsiveCssTag("lobby"),
|
||||
|
|
|
@ -34,7 +34,7 @@ object player {
|
|||
moreJs = frag(
|
||||
roundNvuiTag,
|
||||
roundTag,
|
||||
embedJs(s"""window.lichess=window.lichess||{};customWS=true;onload=function(){
|
||||
embedJs(s"""lichess=window.lichess||{};customWS=true;onload=function(){
|
||||
LichessRound.boot({data:${safeJsonValue(data)},i18n:${jsI18n(pov.game)},userId:$jsUserId,chat:${jsOrNull(chatJson)}
|
||||
${tour.flatMap(_.top).??(top => s",tour:${safeJsonValue(lila.tournament.JsonView.top(top, lightUser))}")}
|
||||
})}""")
|
||||
|
|
|
@ -33,7 +33,7 @@ object watcher {
|
|||
moreJs = frag(
|
||||
roundNvuiTag,
|
||||
roundTag,
|
||||
embedJs(s"""window.lichess=window.lichess||{};customWS=true;onload=function(){
|
||||
embedJs(s"""lichess=window.lichess||{};customWS=true;onload=function(){
|
||||
LichessRound.boot({data:${safeJsonValue(data)},i18n:${jsI18n(pov.game)},chat:${jsOrNull(chatJson)}})}""")
|
||||
),
|
||||
openGraph = povOpenGraph(pov).some,
|
||||
|
|
|
@ -26,7 +26,7 @@ object index {
|
|||
roundTag,
|
||||
embedJs {
|
||||
val transJs = views.html.round.jsI18n(pov.game)
|
||||
s"""window.lichess=window.lichess||{};customWS=true;
|
||||
s"""lichess=window.lichess||{};customWS=true;
|
||||
onload=function(){LichessRound.boot({data:${safeJsonValue(data)},i18n:$transJs})}"""
|
||||
}
|
||||
),
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
window.onload = function() {
|
||||
if (!window.lichess_challenge) return;
|
||||
var opts = lichess_challenge;
|
||||
var element = document.getElementById('challenge');
|
||||
var selector = '.challenge-page';
|
||||
var element = document.querySelector(selector);
|
||||
var challenge = opts.data.challenge;
|
||||
var accepting;
|
||||
|
||||
lichess.socket = new lichess.StrongSocket(
|
||||
opts.socketUrl,
|
||||
opts.data.socketVersion, {
|
||||
|
@ -15,7 +16,7 @@ window.onload = function() {
|
|||
$.ajax({
|
||||
url: opts.xhrUrl,
|
||||
success: function(html) {
|
||||
$('.lichess_overboard').replaceWith($(html).find('.lichess_overboard'));
|
||||
$(selector).replaceWith($(html).find(selector));
|
||||
init();
|
||||
}
|
||||
});
|
||||
|
@ -23,15 +24,15 @@ window.onload = function() {
|
|||
}
|
||||
});
|
||||
|
||||
var init = function() {
|
||||
if (!accepting) $('#challenge_redirect').each(function() {
|
||||
function init() {
|
||||
if (!accepting) $('#challenge-redirect').each(function() {
|
||||
location.href = $(this).attr('href');
|
||||
});
|
||||
$('.lichess_overboard').find('form.accept').submit(function() {
|
||||
$(selector).find('form.accept').submit(function() {
|
||||
accepting = true;
|
||||
$(this).html('<span class="ddloader"></span>');
|
||||
});
|
||||
$('.lichess_overboard').find('form.xhr').submit(function(e) {
|
||||
$(selector).find('form.xhr').submit(function(e) {
|
||||
e.preventDefault();
|
||||
$.ajax({
|
||||
url: $(this).attr('action'),
|
||||
|
@ -39,7 +40,7 @@ window.onload = function() {
|
|||
});
|
||||
$(this).html('<span class="ddloader"></span>');
|
||||
});
|
||||
$('.lichess_overboard').find('input.friend-autocomplete').each(function() {
|
||||
$(selector).find('input.friend-autocomplete').each(function() {
|
||||
var $input = $(this);
|
||||
lichess.userAutocomplete($input, {
|
||||
focus: 1,
|
||||
|
@ -50,26 +51,16 @@ window.onload = function() {
|
|||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
init();
|
||||
|
||||
var pingNow = function() {
|
||||
if (document.getElementById('ping_challenge')) {
|
||||
function pingNow() {
|
||||
if (document.getElementById('ping-challenge')) {
|
||||
lichess.socket.send('ping');
|
||||
setTimeout(pingNow, 2000);
|
||||
}
|
||||
};
|
||||
pingNow();
|
||||
}
|
||||
|
||||
var ground = Chessground(element.querySelector('.lichess_board'), {
|
||||
viewOnly: true,
|
||||
drawable: { enabled: false, visible: false },
|
||||
fen: challenge.initialFen,
|
||||
orientation: (opts.owner ^ challenge.color === 'black') ? 'white' : 'black',
|
||||
coordinates: false,
|
||||
disableContextMenu: true
|
||||
});
|
||||
setTimeout(function() {
|
||||
$('.lichess_overboard_wrap', element).addClass('visible');
|
||||
}, 100);
|
||||
};
|
||||
pingNow();
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
font-size: 1.2em;
|
||||
padding: 1em 0;
|
||||
}
|
||||
#ping_challenge .spinner {
|
||||
#ping-challenge .spinner {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
margin-bottom: 20px;
|
||||
|
@ -14,6 +14,3 @@
|
|||
border: 1px solid #ccc;
|
||||
padding: 5px 8px;
|
||||
}
|
||||
body.dark .user-invite .friend-autocomplete {
|
||||
border-color: #3d3d3d;
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
@import '../../../common/css/plugin';
|
||||
@import '../page';
|
|
@ -0,0 +1,2 @@
|
|||
@import '../../../common/css/theme/dark';
|
||||
@import 'challenge.page';
|
|
@ -0,0 +1,2 @@
|
|||
@import '../../../common/css/theme/light';
|
||||
@import 'challenge.page';
|
|
@ -0,0 +1,2 @@
|
|||
@import '../../../common/css/theme/transp';
|
||||
@import 'challenge.page';
|
|
@ -5,13 +5,14 @@
|
|||
@import 'abstract/all';
|
||||
|
||||
@import 'base/elements';
|
||||
@import 'base/form';
|
||||
@import 'base/fonts';
|
||||
@import 'base/typography';
|
||||
@import 'base/data-icon';
|
||||
@import 'base/unread';
|
||||
@import 'base/util';
|
||||
|
||||
@include theme-style
|
||||
@include theme-style;
|
||||
|
||||
@import 'layout/base';
|
||||
@import 'layout/page-menu';
|
||||
|
|
|
@ -53,30 +53,6 @@ hr {
|
|||
background: $c-border;
|
||||
}
|
||||
|
||||
button, input, optgroup, select, textarea {
|
||||
font: inherit;
|
||||
color: $c-font;
|
||||
}
|
||||
option,
|
||||
optgroup {
|
||||
background: $c-bg-box-opaque;
|
||||
color: $c-font-clear;
|
||||
}
|
||||
input, textarea, select {
|
||||
@extend %box-radius;
|
||||
background: $c-bg-input;
|
||||
border: $border;
|
||||
padding: .6em 1em;
|
||||
}
|
||||
textarea {
|
||||
overflow: auto;
|
||||
resize: vertical;
|
||||
padding: .8em 1em;
|
||||
}
|
||||
button, a {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
table, tbody, tfoot, thead, tr, th, td {
|
||||
border: 0;
|
||||
font-size: 100%;
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
button, input, optgroup, select, textarea {
|
||||
font: inherit;
|
||||
color: $c-font;
|
||||
}
|
||||
option,
|
||||
optgroup {
|
||||
background: $c-bg-box-opaque;
|
||||
color: $c-font-clear;
|
||||
}
|
||||
input, textarea, select {
|
||||
@extend %box-radius;
|
||||
background: $c-bg-input;
|
||||
border: $border;
|
||||
padding: .6em 1em;
|
||||
}
|
||||
textarea {
|
||||
overflow: auto;
|
||||
resize: vertical;
|
||||
padding: .8em 1em;
|
||||
}
|
||||
button, a {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.copyable {
|
||||
background: $c-bg-low;
|
||||
color: $c-font-clear;
|
||||
}
|
|
@ -227,21 +227,9 @@
|
|||
});
|
||||
|
||||
$('#lichess').on('click', 'button.copy', function() {
|
||||
var prev = $('#' + $(this).data('rel'));
|
||||
if (!prev) return;
|
||||
var usePrompt = function() {
|
||||
prompt('Your browser does not support automatic copying. Copy this text manually with Ctrl + C:', prev.val());
|
||||
};
|
||||
try {
|
||||
if (document.queryCommandSupported('copy')) {
|
||||
// Awesome! Done in five seconds, can go home.
|
||||
prev.select();
|
||||
document.execCommand('copy');
|
||||
} else throw '';
|
||||
$(this).attr('data-icon', 'E');
|
||||
} catch (e) {
|
||||
usePrompt();
|
||||
}
|
||||
$('#' + $(this).data('rel')).select();
|
||||
document.execCommand('copy');
|
||||
$(this).attr('data-icon', 'E');
|
||||
});
|
||||
|
||||
$('body').on('click', 'a.relation-button', function() {
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
var lichess = window.lichess = window.lichess || {};
|
||||
|
||||
lichess.trans = function(i18n) {
|
||||
var format = function(str, args) {
|
||||
if (args.length && str.includes('$s'))
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
var lichess = window.lichess = window.lichess || {};
|
||||
lichess = window.lichess || {};
|
||||
|
||||
lichess.engineName = 'Stockfish 10+';
|
||||
|
||||
|
|
Loading…
Reference in New Issue