select a reason to decline a challenge from the web UI
also increases TS lib to ES2017 so if something breaks, that's whypull/7978/head
parent
43fbd61029
commit
5950f4f06a
|
@ -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) =
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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' }));
|
||||
}
|
||||
});
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 }
|
||||
})))
|
||||
]);
|
||||
}
|
||||
|
||||
|
|
|
@ -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"]
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue