allow editing unstarted simuls

pull/7083/head
Thibault Duplessis 2020-08-03 18:52:03 +02:00
parent c9f014ddd4
commit 7d8bde0195
11 changed files with 228 additions and 173 deletions

View File

@ -138,7 +138,7 @@ final class Simul(
Auth { implicit ctx => me =>
NoLameOrBot {
apiC.teamsIBelongTo(me) map { teams =>
Ok(html.simul.form(forms.create(me), teams))
Ok(html.simul.form.create(forms.create(me), teams))
}
}
}
@ -153,7 +153,7 @@ final class Simul(
.fold(
err =>
apiC.teamsIBelongTo(me) map { teams =>
BadRequest(html.simul.form(err, teams))
BadRequest(html.simul.form.create(err, teams))
},
setup =>
env.simul.api.create(setup, me) map { simul =>
@ -181,10 +181,44 @@ final class Simul(
}
}
def edit(id: String) =
Auth { implicit ctx => me =>
WithEditableSimul(id, me) { simul =>
apiC.teamsIBelongTo(me) map { teams =>
Ok(html.simul.form.edit(forms.edit(me, simul), teams, simul))
}
}
}
def update(id: String) =
AuthBody { implicit ctx => me =>
WithEditableSimul(id, me) { simul =>
implicit val req = ctx.body
forms
.edit(me, simul)
.bindFromRequest()
.fold(
err =>
apiC.teamsIBelongTo(me) map { teams =>
BadRequest(html.simul.form.edit(err, teams, simul))
},
data => env.simul.api.update(simul, data) inject Redirect(routes.Simul.show(id))
)
}
}
private def AsHost(simulId: Sim.ID)(f: Sim => Fu[Result])(implicit ctx: Context): Fu[Result] =
env.simul.repo.find(simulId) flatMap {
case None => notFound
case Some(simul) if ctx.userId.exists(simul.hostId ==) => f(simul)
case _ => fuccess(Unauthorized)
case None => notFound
case Some(simul) if ctx.userId.has(simul.hostId) || isGranted(_.ManageSimul) => f(simul)
case _ => fuccess(Unauthorized)
}
private def WithEditableSimul(id: String, me: lila.user.User)(
f: Sim => Fu[Result]
)(implicit ctx: Context): Fu[Result] =
AsHost(id) { sim =>
if (sim.isStarted) Redirect(routes.Simul.show(sim.id)).fuccess
else f(sim)
}
}

View File

@ -1,21 +1,20 @@
package views.html.simul
import controllers.routes
import play.api.data.Form
import lila.api.Context
import lila.app.templating.Environment._
import lila.app.ui.ScalatagsTemplate._
import controllers.routes
import lila.simul.Simul
import lila.hub.LightTeam
import lila.simul.SimulForm
object form {
def apply(form: Form[lila.simul.SimulForm.Setup], teams: List[lila.hub.LightTeam])(implicit
def create(form: Form[SimulForm.Setup], teams: List[LightTeam])(implicit
ctx: Context
) = {
import lila.simul.SimulForm._
) =
views.html.base.layout(
title = trans.hostANewSimul.txt(),
moreCss = cssTag("simul.form")
@ -27,89 +26,7 @@ object form {
p(trans.whenCreateSimul()),
br,
br,
globalError(form),
form3.group(form("name"), trans.name()) { f =>
div(
form3.input(f),
" Simul",
br,
small(cls := "form-help")(trans.inappropriateNameWarning())
)
},
form3.group(form("variant"), trans.simulVariantsHint()) { f =>
frag(
div(cls := "variants")(
views.html.setup.filter.renderCheckboxes(
form,
"variants",
translatedVariantChoicesWithVariants,
checks = form.value.??(_.variants.map(_.toString).toSet)
)
),
errMsg(f)
)
},
form3.split(
form3.group(
form("clockTime"),
raw("Clock initial time"),
help = trans.simulClockHint().some,
half = true
)(form3.select(_, clockTimeChoices)),
form3.group(form("clockIncrement"), raw("Clock increment"), half = true)(
form3.select(_, clockIncrementChoices)
)
),
form3.split(
form3.group(
form("clockExtra"),
trans.simulHostExtraTime(),
help = trans.simulAddExtraTime().some,
half = true
)(
form3.select(_, clockExtraChoices)
),
form3.group(form("color"), raw("Host color for each game"), half = true)(
form3.select(_, colorChoices)
)
),
form3.split(
(teams.size > 0) ?? {
form3.group(form("team"), raw("Only members of team"), half = true)(
form3.select(_, List(("", "No Restriction")) ::: teams.map(_.pair))
)
},
form3.group(
form("position"),
trans.startPosition(),
klass = "position",
half = true,
help = frag("Custom starting position only works with the standard variant.").some
) { field =>
st.select(
id := form3.id(field),
st.name := field.name,
cls := "form-control"
)(
option(
value := chess.StartingPosition.initial.fen,
field.value.has(chess.StartingPosition.initial.fen) option selected
)(chess.StartingPosition.initial.name),
chess.StartingPosition.categories.map { categ =>
optgroup(attr("label") := categ.name)(
categ.positions.map { v =>
option(value := v.fen, field.value.has(v.fen) option selected)(v.fullName)
}
)
}
)
}
),
form3.group(
form("text"),
raw("Simul description"),
help = frag("Anything you want to tell the participants?").some
)(form3.textarea(_)(rows := 10)),
formContent(form, teams, none),
form3.actions(
a(href := routes.Simul.home())(trans.cancel()),
form3.submit(trans.hostANewSimul(), icon = "g".some)
@ -117,5 +34,114 @@ object form {
)
)
}
def edit(form: Form[SimulForm.Setup], teams: List[LightTeam], simul: Simul)(implicit
ctx: Context
) =
views.html.base.layout(
title = s"Edit ${simul.fullName}",
moreCss = cssTag("simul.form")
) {
main(cls := "box box-pad page-small simul-form")(
h1(s"Edit ${simul.fullName}"),
postForm(cls := "form3", action := routes.Simul.update(simul.id))(
formContent(form, teams, simul.some),
form3.actions(
a(href := routes.Simul.show(simul.id))(trans.cancel()),
form3.submit(trans.save(), icon = "g".some)
)
)
)
}
private def formContent(form: Form[SimulForm.Setup], teams: List[LightTeam], simul: Option[Simul])(implicit
ctx: Context
) = {
import lila.simul.SimulForm._
frag(
globalError(form),
form3.group(form("name"), trans.name()) { f =>
div(
form3.input(f),
" Simul",
br,
small(cls := "form-help")(trans.inappropriateNameWarning())
)
},
form3.group(form("variant"), trans.simulVariantsHint()) { f =>
frag(
div(cls := "variants")(
views.html.setup.filter.renderCheckboxes(
form,
"variants",
translatedVariantChoicesWithVariants,
checks = form.value.??(_.variants.map(_.toString).toSet)
)
),
errMsg(f)
)
},
form3.split(
form3.group(
form("clockTime"),
raw("Clock initial time"),
help = trans.simulClockHint().some,
half = true
)(form3.select(_, clockTimeChoices)),
form3.group(form("clockIncrement"), raw("Clock increment"), half = true)(
form3.select(_, clockIncrementChoices)
)
),
form3.split(
form3.group(
form("clockExtra"),
trans.simulHostExtraTime(),
help = trans.simulAddExtraTime().some,
half = true
)(
form3.select(_, clockExtraChoices)
),
form3.group(form("color"), raw("Host color for each game"), half = true)(
form3.select(_, colorChoices)
)
),
form3.split(
(teams.size > 0) ?? {
form3.group(form("team"), raw("Only members of team"), half = true)(
form3.select(_, List(("", "No Restriction")) ::: teams.map(_.pair))
)
},
form3.group(
form("position"),
trans.startPosition(),
klass = "position",
half = true,
help = frag("Custom starting position only works with the standard variant.").some
) { field =>
st.select(
id := form3.id(field),
st.name := field.name,
cls := "form-control"
)(
option(
value := chess.StartingPosition.initial.fen,
field.value.has(chess.StartingPosition.initial.fen) option selected
)(chess.StartingPosition.initial.name),
chess.StartingPosition.categories.map { categ =>
optgroup(attr("label") := categ.name)(
categ.positions.map { v =>
option(value := v.fen, field.value.has(v.fen) option selected)(v.fullName)
}
)
}
)
}
),
form3.group(
form("text"),
raw("Simul description"),
help = frag("Anything you want to tell the participants?").some
)(form3.textarea(_)(rows := 10))
)
}
}

View File

@ -1,5 +1,6 @@
package views.html.simul
import controllers.routes
import play.api.libs.json.Json
import lila.api.Context
@ -7,8 +8,6 @@ import lila.app.templating.Environment._
import lila.app.ui.ScalatagsTemplate._
import lila.common.String.html.safeJsonValue
import controllers.routes
object show {
def apply(
@ -59,7 +58,11 @@ object show {
div(cls := "setup")(
sim.variants.map(_.name).mkString(", "),
" • ",
trans.casual()
trans.casual(),
ctx.userId.has(sim.hostId) option frag(
" • ",
a(href := routes.Simul.edit(sim.id), title := "Edit simul")(iconTag("%"))
)
)
)
),

View File

@ -267,6 +267,8 @@ GET /simul/new controllers.Simul.form
POST /simul/new controllers.Simul.create
GET /simul/reload controllers.Simul.homeReload
GET /simul/$id<\w{8}> controllers.Simul.show(id: String)
GET /simul/$id<\w{8}>/edit controllers.Simul.edit(id: String)
POST /simul/$id<\w{8}>/edit controllers.Simul.update(id: String)
POST /simul/$id<\w{8}>/host-ping controllers.Simul.hostPing(id: String)
POST /simul/$id<\w{8}>/accept/:user controllers.Simul.accept(id: String, user: String)
POST /simul/$id<\w{8}>/reject/:user controllers.Simul.reject(id: String, user: String)
@ -274,7 +276,6 @@ POST /simul/$id<\w{8}>/start controllers.Simul.start(id: String
POST /simul/$id<\w{8}>/abort controllers.Simul.abort(id: String)
POST /simul/$id<\w{8}>/join/:variant controllers.Simul.join(id: String, variant: String)
POST /simul/$id<\w{8}>/withdraw controllers.Simul.withdraw(id: String)
POST /simul/$id<\w{8}>/set-text controllers.Simul.setText(id: String)
# Team
GET /team controllers.Team.home(page: Int ?= 1)

View File

@ -39,6 +39,7 @@ object Permission {
case object ManageTeam extends Permission("MANAGE_TEAM", "Manage teams")
case object ManageTournament extends Permission("MANAGE_TOURNAMENT", "Manage tournaments")
case object ManageEvent extends Permission("MANAGE_EVENT", "Manage events")
case object ManageSimul extends Permission("MANAGE_SIMUL", "Manage simuls")
case object ChangePermission extends Permission("CHANGE_PERMISSION", "Change permissions")
case object PublicMod extends Permission("PUBLIC_MOD", "Mod badge")
case object Developer extends Permission("DEVELOPER", "Developer badge")
@ -131,6 +132,7 @@ object Permission {
SetEmail,
ManageTeam,
ManageTournament,
ManageSimul,
ManageEvent,
PracticeConfig,
RemoveRanking,
@ -199,6 +201,7 @@ object Permission {
Relay,
ManageEvent,
ManageTournament,
ManageSimul,
StudyAdmin,
PracticeConfig
),

View File

@ -50,16 +50,9 @@ final class SimulApi(
def create(setup: SimulForm.Setup, me: User): Fu[Simul] = {
val simul = Simul.make(
name = setup.name,
clock = SimulClock(
config = chess.Clock.Config(setup.clockTime * 60, setup.clockIncrement),
hostExtraTime = setup.clockExtra * 60
),
variants = setup.variants.flatMap { chess.variant.Variant(_) },
position = setup.position
.map {
SimulForm.startingPosition(_, chess.variant.Standard)
}
.filterNot(_.initial),
clock = setup.clock,
variants = setup.actualVariants,
position = setup.actualPosition,
host = me,
color = setup.color,
text = setup.text,
@ -70,6 +63,19 @@ final class SimulApi(
} inject simul
}
def update(prev: Simul, setup: SimulForm.Setup): Fu[Simul] = {
val simul = prev.copy(
name = setup.name,
clock = setup.clock,
variants = setup.actualVariants,
position = setup.actualPosition,
color = setup.color.some,
text = setup.text,
team = setup.team
)
repo.update(simul) >>- publish() inject simul
}
def addApplicant(simulId: Simul.ID, user: User, variantKey: String): Funit =
WithSimul(repo.findCreated, simulId) { simul =>
if (simul.nbAccepted >= Game.maxPlayingRealtime) simul

View File

@ -1,12 +1,12 @@
package lila.simul
import chess.StartingPosition
import play.api.data._
import play.api.data.Forms._
import play.api.data.validation.{ Constraint, Constraints }
import lila.user.User
import chess.StartingPosition
import lila.common.Form._
import lila.user.User
object SimulForm {
@ -60,6 +60,32 @@ object SimulForm {
)
def create(host: User) =
baseForm(host) fill Setup(
name = host.titleUsername,
clockTime = clockTimeDefault,
clockIncrement = clockIncrementDefault,
clockExtra = clockExtraDefault,
variants = List(chess.variant.Standard.id),
position = StartingPosition.initial.fen.some,
color = colorDefault,
text = "",
team = none
)
def edit(host: User, simul: Simul) =
baseForm(host) fill Setup(
name = simul.name,
clockTime = simul.clock.config.limitInMinutes.toInt,
clockIncrement = simul.clock.config.increment.roundSeconds,
clockExtra = simul.clock.hostExtraMinutes,
variants = simul.variants.map(_.id),
position = simul.position.map(_.fen),
color = simul.color | "random",
text = simul.text,
team = simul.team
)
private def baseForm(host: User) =
Form(
mapping(
"name" -> nameType(host),
@ -86,16 +112,6 @@ object SimulForm {
"text" -> clean(text),
"team" -> optional(nonEmptyText)
)(Setup.apply)(Setup.unapply)
) fill Setup(
name = host.titleUsername,
clockTime = clockTimeDefault,
clockIncrement = clockIncrementDefault,
clockExtra = clockExtraDefault,
variants = List(chess.variant.Standard.id),
position = StartingPosition.initial.fen.some,
color = colorDefault,
text = "",
team = none
)
val positions = StartingPosition.allWithInitial.map(_.fen)
@ -119,5 +135,20 @@ object SimulForm {
color: String,
text: String,
team: Option[String]
)
) {
def clock =
SimulClock(
config = chess.Clock.Config(clockTime * 60, clockIncrement),
hostExtraTime = clockExtra * 60
)
def actualPosition =
position
.map {
startingPosition(_, chess.variant.Standard)
}
.filterNot(_.initial)
def actualVariants = variants.flatMap { chess.variant.Variant(_) }
}
}

View File

@ -30,7 +30,7 @@ $chat-height: calc(90vh - 300px);
font-size: 1.5em;
}
i {
> i {
font-size: 2.5em;
margin-right: .5em;
}

View File

@ -1,6 +1,5 @@
var socket = require('./socket');
var simul = require('./simul');
var text = require('./text');
var xhr = require('./xhr');
module.exports = function(env) {
@ -12,7 +11,6 @@ module.exports = function(env) {
this.userId = env.userId;
this.socket = new socket(env.socketSend, this);
this.text = text.ctrl();
this.reload = function(data) {
if (this.data.isCreated && !data.isCreated) {

View File

@ -1,6 +1,4 @@
var m = require('mithril');
var simul = require('./simul');
var xhr = require('./xhr');
function enrichText(text) {
return m.trust(autolink(lichess.escapeHtml(text), toLink).replace(newLineRegex, '<br>'));
@ -15,43 +13,8 @@ function toLink(url) {
var linkRegex = /(^|[\s\n]|<[A-Za-z]*\/?>)((?:https?|ftp):\/\/[-A-Z0-9+\u0026\u2019@#/%?=()~_|!:,.;]*[-A-Z0-9+\u0026@#/%=~()_|])/gi;
var newLineRegex = /\n/g;
function editor(ctrl) {
return m('div.editor', [
m('button.button.button-empty.open', {
onclick: ctrl.text.toggle
}, 'Edit'),
ctrl.text.editing () ? m('form', {
onsubmit: function(e) {
xhr.setText(ctrl, e.target.querySelector('textarea').value);
ctrl.text.toggle();
return false;
}
}, [
m('textarea', ctrl.data.text),
m('button.button.save', {
type: 'submit'
}, 'Save')
]) : null
]);
}
module.exports = {
ctrl: function() {
var editing = false;
return {
toggle: function() {
editing = !editing;
},
editing: function() {
return editing;
}
};
},
view: function(ctrl) {
return ctrl.data.text || simul.createdByMe(ctrl) ?
m('div.simul-text' + (ctrl.text.editing() ? '.editing' : ''), [
m('p', enrichText(ctrl.data.text)),
simul.createdByMe(ctrl) ? editor(ctrl) : null
]) : null;
return ctrl.data.text ? m('div.simul-text', m('p', enrichText(ctrl.data.text))) : null;
}
}

View File

@ -35,15 +35,5 @@ module.exports = {
},
reject: function(user) {
return partial(simulAction, 'reject/' + user)
},
setText: function(ctrl, text) {
return m.request({
method: 'POST',
url: '/simul/' + ctrl.data.id + '/set-text',
config: xhrConfig,
data: {
text: text
}
});
}
};