select a reason to decline a challenge from the web UI

also increases TS lib to ES2017

so if something breaks, that's why
pull/7978/head
Thibault Duplessis 2021-01-21 17:11:37 +01:00
parent 43fbd61029
commit 5950f4f06a
10 changed files with 87 additions and 28 deletions

View File

@ -12,9 +12,9 @@ import lila.challenge.{ Challenge => ChallengeModel }
import lila.common.{ HTTPRequest, IpAddress }
import lila.game.{ AnonCookie, Pov }
import lila.oauth.{ AccessToken, OAuthScope }
import lila.setup.ApiConfig
import lila.socket.Socket.SocketVersion
import lila.user.{ User => UserModel }
import lila.setup.ApiConfig
final class Challenge(
env: Env
@ -136,9 +136,16 @@ final class Challenge(
}
def decline(id: String) =
Auth { implicit ctx => _ =>
AuthBody { implicit ctx => _ =>
OptionFuResult(api byId id) { c =>
isForMe(c) ?? api.decline(c, ChallengeModel.DeclineReason.default)
implicit val req = ctx.body
isForMe(c) ??
api.decline(
c,
env.challenge.forms.decline
.bindFromRequest()
.fold(_ => ChallengeModel.DeclineReason.default, _.realReason)
)
}
}
def apiDecline(id: String) =

View File

@ -126,12 +126,20 @@ object Challenge {
}
object DeclineReason {
case object Generic extends DeclineReason(I18nKeys.challenge.declineGeneric)
case object Later extends DeclineReason(I18nKeys.challenge.declineLater)
case object Generic extends DeclineReason(I18nKeys.challenge.declineGeneric)
case object Later extends DeclineReason(I18nKeys.challenge.declineLater)
case object TooFast extends DeclineReason(I18nKeys.challenge.declineTooFast)
case object TooSlow extends DeclineReason(I18nKeys.challenge.declineTooSlow)
case object TimeControl extends DeclineReason(I18nKeys.challenge.declineTimeControl)
case object Rated extends DeclineReason(I18nKeys.challenge.declineRated)
case object Casual extends DeclineReason(I18nKeys.challenge.declineCasual)
case object Standard extends DeclineReason(I18nKeys.challenge.declineStandard)
case object Variant extends DeclineReason(I18nKeys.challenge.declineVariant)
val default: DeclineReason = Generic
val all: List[DeclineReason] = List(Generic, Later)
def apply(key: String) = all.find(_.key == key) | Generic
val default: DeclineReason = Generic
val all: List[DeclineReason] =
List(Generic, Later, TooFast, TooSlow, TimeControl, Rated, Casual, Standard, Variant)
def apply(key: String) = all.find { d => d.key == key.toLowerCase || d.trans.key == key } | Generic
}
case class Rating(int: Int, provisional: Boolean) {

View File

@ -35,7 +35,10 @@ final class JsonView(
Json.obj(
"in" -> a.in.map(apply(Direction.In.some)),
"out" -> a.out.map(apply(Direction.Out.some)),
"i18n" -> lila.i18n.JsDump.keysToObject(i18nKeys, lang)
"i18n" -> lila.i18n.JsDump.keysToObject(i18nKeys, lang),
"reasons" -> JsObject(Challenge.DeclineReason.all.map { r =>
r.key -> JsString(r.trans.txt())
})
)
def show(challenge: Challenge, socketVersion: SocketVersion, direction: Option[Direction])(implicit

View File

@ -1961,6 +1961,13 @@ val `cannotChallengeDueToProvisionalXRating` = new I18nKey("challenge:cannotChal
val `xOnlyAcceptsChallengesFromFriends` = new I18nKey("challenge:xOnlyAcceptsChallengesFromFriends")
val `declineGeneric` = new I18nKey("challenge:declineGeneric")
val `declineLater` = new I18nKey("challenge:declineLater")
val `declineTooFast` = new I18nKey("challenge:declineTooFast")
val `declineTooSlow` = new I18nKey("challenge:declineTooSlow")
val `declineTimeControl` = new I18nKey("challenge:declineTimeControl")
val `declineRated` = new I18nKey("challenge:declineRated")
val `declineCasual` = new I18nKey("challenge:declineCasual")
val `declineStandard` = new I18nKey("challenge:declineStandard")
val `declineVariant` = new I18nKey("challenge:declineVariant")
}
}

View File

@ -12,5 +12,12 @@
<string name="cannotChallengeDueToProvisionalXRating">Cannot challenge due to provisional %s rating.</string>
<string name="xOnlyAcceptsChallengesFromFriends">%s only accepts challenges from friends.</string>
<string name="declineGeneric">I'm not accepting challenges at the moment.</string>
<string name="declineLater">I'm not accepting challenges right now, please ask again later.</string>
<string name="declineLater">This is not the right time for me, please ask again later.</string>
<string name="declineTooFast">This time control is too fast for me, please challenge again with a slower game.</string>
<string name="declineTooSlow">This time control is too slow for me, please challenge again with a faster game.</string>
<string name="declineTimeControl">I'm not accepting challenges with this time control.</string>
<string name="declineRated">Please send me a rated challenge instead.</string>
<string name="declineCasual">Please send me a casual challenge instead.</string>
<string name="declineStandard">I'm not accepting variant challenges right now.</string>
<string name="declineVariant">I'm not willing to play this variant right now.</string>
</resources>

View File

@ -64,11 +64,15 @@
position: absolute;
top: 0;
left: 0;
width: 50%;
width: 40%;
}
.buttons > *:last-child {
left: 50%;
.buttons .decline {
left: 40%;
}
.buttons .decline-reason {
left: 80%;
width: 20%;
}
button {

View File

@ -1,15 +1,17 @@
import * as xhr from 'common/xhr';
import notify from 'common/notification';
import { Ctrl, ChallengeOpts, ChallengeData, ChallengeUser } from './interfaces';
import { Ctrl, ChallengeOpts, ChallengeData, ChallengeUser, Reasons } from './interfaces';
export default function(opts: ChallengeOpts, data: ChallengeData, redraw: () => void): Ctrl {
let trans = (key: string) => key;
let redirecting = false;
let reasons: Reasons = {};
function update(d: ChallengeData) {
data = d;
if (d.i18n) trans = lichess.trans(d.i18n).noarg;
if (d.reasons) reasons = d.reasons;
opts.setCount(countActiveIn());
notifyNew();
}
@ -43,14 +45,15 @@ export default function(opts: ChallengeOpts, data: ChallengeData, redraw: () =>
return {
data: () => data,
trans: () => trans,
reasons: () => reasons,
update,
decline(id) {
decline(id, reason) {
data.in.forEach(c => {
if (c.id === id) {
c.declined = true;
xhr.text(
`/challenge/${id}/decline`,
{ method: 'post' }
{ method: 'post', body: xhr.form({reason}) }
).catch(() => lichess.announce({ msg: 'Failed to send challenge decline' }));
}
});

View File

@ -9,7 +9,8 @@ export interface Ctrl {
update(data: ChallengeData): void
data(): ChallengeData
trans(): (key: string) => string
decline(id: string): void
reasons(): Reasons
decline(id: string, reason: string): void
cancel(id: string): void
onRedirect(): void
redirecting(): boolean
@ -56,12 +57,17 @@ export interface Challenge {
declined?: boolean
}
export type Reasons = {
[key: string]: string
}
export interface ChallengeData {
in: Array<Challenge>
out: Array<Challenge>
i18n?: {
[key: string]: string
}
reasons?: Reasons
}
export type Redraw = () => void

View File

@ -1,11 +1,11 @@
import { Ctrl, Challenge, ChallengeData, ChallengeDirection, ChallengeUser, TimeControl } from './interfaces'
import { h } from 'snabbdom'
import { VNode } from 'snabbdom/vnode'
import { Ctrl, Challenge, ChallengeData, ChallengeDirection, ChallengeUser, TimeControl } from './interfaces'
export function loaded(ctrl: Ctrl): VNode {
return ctrl.redirecting() ?
h('div#challenge-app.dropdown', h('div.initiating', spinner())) :
h('div#challenge-app.links.dropdown.rendered', renderContent(ctrl));
h('div#challenge-app.dropdown', h('div.initiating', spinner())) :
h('div#challenge-app.links.dropdown.rendered', renderContent(ctrl));
}
export function loading(): VNode {
@ -54,7 +54,7 @@ function challenge(ctrl: Ctrl, dir: ChallengeDirection) {
].join(' • '))
]),
h('i', {
attrs: {'data-icon': c.perf.icon}
attrs: { 'data-icon': c.perf.icon }
}),
h('div.buttons', (dir === 'in' ? inButtons : outButtons)(ctrl, c))
]);
@ -84,8 +84,22 @@ function inButtons(ctrl: Ctrl, c: Challenge): VNode[] {
'data-icon': 'L',
title: trans('decline')
},
hook: onClick(() => ctrl.decline(c.id))
})
hook: onClick(() => ctrl.decline(c.id, 'generic'))
}),
h('select.decline-reason', {
hook: {
insert: (vnode: VNode) => {
const select = (vnode.elm as HTMLSelectElement);
select.addEventListener('change', () =>
ctrl.decline(c.id, select.value)
);
}
}
},
Object.entries(ctrl.reasons()).map(([key, name]) =>
h('option', { attrs: { value: key } }, key == 'generic' ? '' : name)
)
)
];
}
@ -127,7 +141,7 @@ function renderUser(u?: ChallengeUser): VNode {
if (!u) return h('span', 'Open challenge');
const rating = u.rating + (u.provisional ? '?' : '');
return h('a.ulpt.user-link', {
attrs: { href: `/@/${u.name}`},
attrs: { href: `/@/${u.name}` },
class: { online: !!u.online }
}, [
h('i.line' + (u.patron ? '.patron' : '')),
@ -135,9 +149,9 @@ function renderUser(u?: ChallengeUser): VNode {
u.title && h('span.utitle', u.title == 'BOT' ? { attrs: { 'data-bot': true } } : {}, u.title + ' '),
u.name + ' (' + rating + ') '
]),
h('signal', u.lag === undefined ? [] : [1, 2, 3, 4].map((i) => h('i', {
class: { off: u.lag! < i}
})))
h('signal', u.lag === undefined ? [] : [1, 2, 3, 4].map((i) => h('i', {
class: { off: u.lag! < i }
})))
]);
}

View File

@ -10,7 +10,7 @@
"moduleResolution": "node",
"target": "ES2016",
"module": "commonjs",
"lib": ["DOM", "ES2016", "DOM.iterable"],
"lib": ["DOM", "ES2017", "DOM.iterable"],
"types": ["lichess", "cash", "defer-promise"]
}
}