challenge UI WIP

also, stuff.
more-scalatags
Thibault Duplessis 2019-04-08 10:33:56 +07:00
parent f835092d58
commit 658429d936
23 changed files with 192 additions and 180 deletions

View File

@ -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

View File

@ -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)))
)
}

View File

@ -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>

View File

@ -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)

View File

@ -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)
)
}
}
}

View File

@ -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="&quot;"></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

View File

@ -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

View File

@ -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"),

View File

@ -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))}")}
})}""")

View File

@ -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,

View File

@ -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})}"""
}
),

View File

@ -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();
}

View File

@ -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;
}

View File

@ -0,0 +1,2 @@
@import '../../../common/css/plugin';
@import '../page';

View File

@ -0,0 +1,2 @@
@import '../../../common/css/theme/dark';
@import 'challenge.page';

View File

@ -0,0 +1,2 @@
@import '../../../common/css/theme/light';
@import 'challenge.page';

View File

@ -0,0 +1,2 @@
@import '../../../common/css/theme/transp';
@import 'challenge.page';

View File

@ -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';

View File

@ -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%;

View File

@ -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;
}

View File

@ -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() {

View File

@ -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'))

View File

@ -1,4 +1,4 @@
var lichess = window.lichess = window.lichess || {};
lichess = window.lichess || {};
lichess.engineName = 'Stockfish 10+';