Skip to content

Commit

Permalink
Merge branch 'master' into glicko-first-move
Browse files Browse the repository at this point in the history
  • Loading branch information
ornicar authored Oct 30, 2024
2 parents d22d638 + b648f57 commit 8b5182f
Show file tree
Hide file tree
Showing 88 changed files with 633 additions and 320 deletions.
13 changes: 9 additions & 4 deletions app/controllers/User.scala
Original file line number Diff line number Diff line change
Expand Up @@ -346,8 +346,10 @@ final class User(
ctx: Context,
me: Me
): Fu[Result] =
env.user.api.withPerfsAndEmails(username).orFail(s"No such user $username").flatMap {
case WithPerfsAndEmails(user, emails) =>
env.report.api.inquiries
.ofModId(me.id)
.zip(env.user.api.withPerfsAndEmails(username).orFail(s"No such user $username"))
.flatMap { case (inquiry, WithPerfsAndEmails(user, emails)) =>
import views.mod.{ user as ui }
import lila.ui.ScalatagsExtensions.{ emptyFrag, given }
given lila.mod.IpRender.RenderIp = env.mod.ipRender.apply
Expand All @@ -356,7 +358,10 @@ final class User(

val timeline = env.api.modTimeline
.load(user, withPlayBans = true)
.map(views.mod.timeline.renderGeneral)
.map: tl =>
if inquiry.exists(_.isPlay)
then views.mod.timeline.renderPlay(tl)
else views.mod.timeline.renderGeneral(tl)
.map(lila.mod.ui.mzSection("timeline")(_))

val plan =
Expand Down Expand Up @@ -444,7 +449,7 @@ final class User(
.log("User.renderModZone")
.as(ContentTypes.EVENT_STREAM)
.pipe(noProxyBuffer)
}
}

protected[controllers] def renderModZoneActions(username: UserStr)(using ctx: Context) =
env.user.api.withPerfsAndEmails(username).orFail(s"No such user $username").flatMap {
Expand Down
1 change: 0 additions & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -491,7 +491,6 @@ lazy val tree = module("tree",
Seq(chess.playJson)
)

// todo remove common, move common.Icon to ui.Icon
lazy val ui = module("ui",
Seq(core, coreI18n),
Seq()
Expand Down
27 changes: 20 additions & 7 deletions modules/api/src/main/ModTimeline.scala
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,13 @@ object ModTimeline:
case (p: PublicLine, n: PublicLine) => PublicLine.merge(p, n)
case (p: PlayBans, n: PlayBans) => PlayBans(p.list ::: n.list).some
case (p: AppealMsg, n: AppealMsg) if p.by.is(n.by) => p.copy(text = s"${n.text}\n\n${p.text}").some
case (p: ReportNewAtom, n: ReportNewAtom) if n.like(p.report) =>
p.copy(atoms = n.atoms ::: p.atoms).some
case _ => none
case (p: ReportNewAtom, n: ReportNewAtom) if n.like(p.report) => p.copy(atoms = n.atoms ::: p.atoms).some
case (p: Modlog, n: Modlog) => mergeModlog(p, n)
case _ => none

private def mergeModlog(p: Modlog, n: Modlog): Option[Modlog] =
(p.action == n.action && p.mod.is(n.mod)).option:
p.copy(details = some(List(p.details, n.details).flatten.distinct.mkString(" / ")))

private def reportAtoms(report: Report): List[ReportNewAtom | PublicLine] =
report.atoms
Expand Down Expand Up @@ -109,13 +113,21 @@ object ModTimeline:
case Play
object Angle:
def filter(e: Event)(using angle: Angle): Boolean = e match
case _: PlayBans => angle != Angle.Comm
case l: Modlog if l.action == Modlog.chatTimeout && angle != Angle.Comm => false
case _: PlayBans => angle != Angle.Comm
case l: Modlog if l.action == Modlog.chatTimeout => angle == Angle.Comm
case l: Modlog if l.action == Modlog.deletePost => angle != Angle.Play
case l: Modlog if l.action == Modlog.disableTeam => angle != Angle.Play
case l: Modlog if l.action == Modlog.teamKick => angle != Angle.Play
case l: Modlog if l.action == Modlog.blankedPassword => angle == Angle.None
case l: Modlog if l.action == Modlog.weakPassword => angle == Angle.None
case l: Modlog if l.action == Modlog.troll => angle != Angle.Play
case l: Modlog if l.action == Modlog.modMessage =>
angle match
case Comm => !l.details.has(lila.playban.PlaybanFeedback.sittingAutoPreset.name)
case _ => true
case _ => true
case r: ReportNewAtom if r.report.is(_.Comm) => angle != Angle.Play
case _: PublicLine => angle != Angle.Play
case _ => true

final class ModTimelineApi(
modLogApi: ModlogApi,
Expand Down Expand Up @@ -150,7 +162,8 @@ final class ModTimelineApi(
else if note.text.startsWith("Appeal reply:") then false
else true

private def filterReport(r: Report): Boolean = !r.isSpontaneous
private def filterReport(r: Report): Boolean =
!r.isSpontaneous && !r.isAppeal

private object modsList:
var all: Set[ModId] = Set(UserId.lichess.into(ModId))
Expand Down
2 changes: 1 addition & 1 deletion modules/common/src/main/mon.scala
Original file line number Diff line number Diff line change
Expand Up @@ -726,4 +726,4 @@ object mon:
private def apiTag(api: Option[ApiVersion]) = api.fold("-")(_.toString)

import scala.language.implicitConversions
given Conversion[Map[String, Any], TagSet] = TagSet.from
private given Conversion[Map[String, Any], TagSet] = TagSet.from
9 changes: 9 additions & 0 deletions modules/core/src/main/data.scala
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,12 @@ object data:
def sync[A](v: => A): LazyFu[A] = LazyFu(() => Future.successful(v))

case class CircularDep[A](resolve: () => A)

final class SimpleMemo[A](ttl: Option[FiniteDuration])(compute: () => A):
private var value: A = compute()
private var recomputeAt: Option[Instant] = ttl.map(nowInstant.plus(_))
def get(): A =
if recomputeAt.exists(_.isBeforeNow) then
recomputeAt = ttl.map(nowInstant.plus(_))
value = compute()
value
5 changes: 1 addition & 4 deletions modules/feed/src/main/FeedUi.scala
Original file line number Diff line number Diff line change
Expand Up @@ -171,10 +171,7 @@ final class FeedUi(helpers: Helpers, atomUi: AtomUi)(
)(form3.textarea(_)(rows := 10)),
form3.group(form("flair"), "Icon", half = false): field =>
form3
.flairPicker(field, Flair.from(form("flair").value), label = frag("Update icon"), anyFlair = true):
span(cls := "flair-container"):
Flair.from(form("flair").value).map(f => marker(f.some, "uflair".some))
,
.flairPicker(field, Flair.from(form("flair").value), anyFlair = true),
form3.action(form3.submit("Save"))
)

Expand Down
2 changes: 1 addition & 1 deletion modules/mod/src/main/ui/ModUserUi.scala
Original file line number Diff line number Diff line change
Expand Up @@ -359,7 +359,7 @@ final class ModUserUi(helpers: Helpers, modUi: ModUi):
postForm(action := routes.Report.inquiry(r.id.value))(
reportSubmitButton(r),
" ",
userIdLink(r.user.some),
userIdLink(r.user.some, withOnline = false),
" ",
momentFromNowServer(atom.at),
": ",
Expand Down
4 changes: 1 addition & 3 deletions modules/pref/src/main/ui/AccountPages.scala
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,7 @@ final class AccountPages(helpers: Helpers, ui: AccountUi, flagApi: lila.core.use
): f =>
form3.textarea(f)(rows := 5)
),
form3.flairPickerGroup(form("flair"), u.flair, label = trans.site.setFlair())(
userSpan(u, withPowerTip = false, cssClass = "flair-container".some)
):
form3.flairPickerGroup(form("flair"), u.flair):
p(cls := "form-help"):
a(
href := s"${routes.Pref.form("display")}#showFlairs",
Expand Down
1 change: 1 addition & 0 deletions modules/puzzle/src/main/PuzzleBatch.scala
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ final class PuzzleBatch(colls: PuzzleColls, anonApi: PuzzleAnon, pathApi: Puzzle
me.foldUse(anonApi.getBatchFor(angle, difficulty, nb)): me ?=>
val tier =
if perf.nb > 5000 then PuzzleTier.good
else if angle.opening.isDefined then PuzzleTier.good
else if PuzzleDifficulty.isExtreme(difficulty) then PuzzleTier.good
else PuzzleTier.top
pathApi
Expand Down
19 changes: 17 additions & 2 deletions modules/relay/src/main/RelayListing.scala
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package lila.relay

import reactivemongo.api.bson.*
import monocle.syntax.all.*

import lila.db.dsl.{ *, given }
import lila.relay.RelayTour.ActiveWithSomeRounds
Expand Down Expand Up @@ -128,8 +129,9 @@ final class RelayListing(
doc <- docs
tour <- doc.asOpt[RelayTour]
round <- doc.getAsOpt[RelayRound]("round")
group = RelayListing.group.readFromOne(doc)
yield (tour, round, group)
group = RelayListing.group.readFromOne(doc)
reTiered = decreaseTierOfDistantNextRound(tour, round)
yield (reTiered, round, group)
sorted = tours.sortBy: (tour, round, _) =>
(
!round.hasStarted, // ongoing tournaments first
Expand All @@ -150,6 +152,19 @@ final class RelayListing(
tr.display.hasStarted || tr.display.startsAtTime.exists(_.isBefore(nowInstant.plusMinutes(30)))
active

private def decreaseTierOfDistantNextRound(tour: RelayTour, round: RelayRound): RelayTour =
import RelayTour.Tier.*
val visualTier = for
tier <- tour.tier
nextAt <- round.startsAtTime
days = scalalib.time.daysBetween(nowInstant.withTimeAtStartOfDay, nextAt)
yield
if tier == BEST && days > 10 then NORMAL
else if tier == BEST && days > 5 then HIGH
else if tier == HIGH && days > 5 then NORMAL
else tier
tour.copy(tier = visualTier.orElse(tour.tier))

val upcoming = cacheApi.unit[List[RelayTour.WithLastRound]]:
_.refreshAfterWrite(14 seconds).buildAsyncFuture: _ =>
val max = 64
Expand Down
21 changes: 2 additions & 19 deletions modules/relay/src/main/ui/RelayTourUi.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package ui

import java.time.{ Month, YearMonth }
import scalalib.paginator.Paginator
import monocle.syntax.all.*

import lila.core.LightUser
import lila.relay.RelayTour.{ WithLastRound, WithFirstRound }
Expand All @@ -21,9 +20,8 @@ final class RelayTourUi(helpers: Helpers, ui: RelayUi):
upcoming: List[WithLastRound],
past: Seq[WithLastRound]
)(using Context) =
val reTiered = decreaseTierOfDistantNextRound(active)
def nonEmptyTier(selector: RelayTour.Tier.Selector, tier: String) =
val selected = reTiered.filter(_.tour.tierIs(selector))
val selected = active.filter(_.tour.tierIs(selector))
selected.nonEmpty.option(st.section(cls := s"relay-cards relay-cards--tier-$tier"):
selected.map:
card.render(_, live = _.display.hasStarted)
Expand All @@ -35,7 +33,7 @@ final class RelayTourUi(helpers: Helpers, ui: RelayUi):
pageMenu("index"),
div(cls := "page-menu__content box box-pad")(
boxTop(h1(trc.liveBroadcasts()), searchForm("")),
Granter.opt(_.StudyAdmin).option(adminIndex(reTiered)),
Granter.opt(_.StudyAdmin).option(adminIndex(active)),
nonEmptyTier(_.BEST, "best"),
nonEmptyTier(_.HIGH, "high"),
nonEmptyTier(_.NORMAL, "normal"),
Expand Down Expand Up @@ -70,21 +68,6 @@ final class RelayTourUi(helpers: Helpers, ui: RelayUi):
card.render(tr.copy(link = tr.display), live = _.display.hasStarted, errors = errors.take(5))
)

private def decreaseTierOfDistantNextRound(active: List[RelayTour.ActiveWithSomeRounds]) =
val now = nowInstant.withTimeAtStartOfDay
active.map: a =>
import RelayTour.Tier.*
val visualTier = for
tier <- a.tour.tier
nextAt <- a.display.startsAtTime
days = scalalib.time.daysBetween(now, nextAt)
yield
if tier == BEST && days > 10 then NORMAL
else if tier == BEST && days > 5 then HIGH
else if tier == HIGH && days > 5 then NORMAL
else tier
a.focus(_.tour.tier).replace(visualTier.orElse(a.tour.tier))

private def listLayout(title: String, menu: Tag)(body: Modifier*)(using Context) =
Page(title)
.css("bits.relay.index")
Expand Down
1 change: 1 addition & 0 deletions modules/report/src/main/Report.scala
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ case class Report(

def isRecentComm = open && room == Room.Comm
def isRecentCommOf(sus: Suspect) = isRecentComm && user == sus.user.id
def isPlay = room == Room.Boost || room == Room.Cheat

def isAppeal = room == Room.Other && atoms.head.text == Report.appealText
def isAppealInquiryByMe(using me: MyId) = isAppeal && atoms.head.by.is(me)
Expand Down
3 changes: 1 addition & 2 deletions modules/team/src/main/ui/FormUi.scala
Original file line number Diff line number Diff line change
Expand Up @@ -93,8 +93,7 @@ final class FormUi(helpers: Helpers, bits: TeamUi)(
private val explainInput = input(st.name := "explain", tpe := "hidden")

private def flairField(form: Form[?], team: Team)(using Context) =
form3.flairPickerGroup(form("flair"), Flair.from(form("flair").value), label = trans.site.setFlair()):
span(cls := "flair-container".some)(team.name, teamFlair(team.light))
form3.flairPickerGroup(form("flair"), Flair.from(form("flair").value))

private def textFields(form: Form[?])(using Context) = frag(
form3.group(
Expand Down
31 changes: 14 additions & 17 deletions modules/ui/src/main/helper/Form3.scala
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,7 @@ import lila.core.i18n.{ I18nKey as trans, Translate }
import lila.core.user.FlairApi
import lila.ui.ScalatagsTemplate.{ *, given }

final class Form3(formHelper: FormHelper & I18nHelper, flairApi: FlairApi):

final class Form3(formHelper: FormHelper & I18nHelper & AssetHelper, flairApi: FlairApi):
import formHelper.{ transKey, given }

private val idPrefix = "form3"
Expand Down Expand Up @@ -246,29 +245,27 @@ final class Form3(formHelper: FormHelper & I18nHelper, flairApi: FlairApi):
)

private lazy val exceptEmojis = data("except-emojis") := flairApi.adminFlairs.mkString(" ")
def flairPickerGroup(field: Field, current: Option[Flair], label: Frag)(view: Frag)(using Context): Tag =
def flairPickerGroup(field: Field, current: Option[Flair])(using Context): Tag =
group(field, trans.site.flair(), half = true): f =>
flairPicker(f, current, label)(view)
flairPicker(f, current)

def flairPicker(field: Field, current: Option[Flair], label: Frag, anyFlair: Boolean = false)(view: Frag)(
using ctx: Context
def flairPicker(field: Field, current: Option[Flair], anyFlair: Boolean = false)(using
ctx: Context
): Frag =
frag(
details(cls := "form-control emoji-details")(
summary(cls := "button button-metal button-no-upper")(
label,
":",
nbsp,
view
div(cls := "form-control emoji-details")(
div(cls := "emoji-popup-button")(
st.select(st.id := id(field), name := field.name, cls := "form-control")(
current.map(f => option(value := f, selected := ""))
),
img(src := current.fold("")(formHelper.flairSrc(_)))
),
hidden(field, current.map(_.value)),
div(
cls := "flair-picker",
cls := "flair-picker none",
(!ctx.me.exists(_.isAdmin) && !anyFlair).option(exceptEmojis)
)(
button(cls := "button button-metal emoji-remove")("clear")
)
),
current.isDefined.option(p:
button(cls := "button button-red button-thin button-empty text emoji-remove")(trans.site.delete())
)
)

Expand Down
12 changes: 8 additions & 4 deletions modules/ui/src/main/helper/FormHelper.scala
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@ import scalatags.text.Builder
import lila.ui.ScalatagsTemplate.*
import scala.util.Try
import play.api.i18n.Lang
import lila.core.data.SimpleMemo

trait FormHelper:
self: I18nHelper =>
self: I18nHelper & AssetHelper =>

protected def flairApi: lila.core.user.FlairApi

Expand Down Expand Up @@ -77,7 +78,7 @@ trait FormHelper:
import java.time.{ ZoneId, ZoneOffset }
import scala.jdk.CollectionConverters.*

lazy val zones: List[(ZoneOffset, ZoneId)] =
private val zones: SimpleMemo[List[(ZoneOffset, ZoneId)]] = SimpleMemo(67.minutes.some): () =>
val now = nowInstant
ZoneId.getAvailableZoneIds.asScala.toList
.flatMap: id =>
Expand All @@ -87,6 +88,9 @@ trait FormHelper:
.toList
.sortBy: (offset, zone) =>
(offset, zone.getId)

def translatedChoices(using lang: Lang): List[(String, String)] =
zones.map: (offset, zone) =>
zone.getId -> s"${zone.getDisplayName(java.time.format.TextStyle.NARROW, lang.locale)} $offset"
zones
.get()
.map: (offset, zone) =>
zone.getId -> s"${zone.getDisplayName(java.time.format.TextStyle.NARROW, lang.locale)} $offset"
2 changes: 1 addition & 1 deletion project/Dependencies.scala
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ object Dependencies {
val lettuce = "io.lettuce" % "lettuce-core" % "6.4.0.RELEASE"
val nettyTransport =
("io.netty" % s"netty-transport-native-$notifier" % "4.1.114.Final").classifier(s"$os-$arch")
val lilaSearch = "org.lichess.search" %% "client" % "3.0.1"
val lilaSearch = "org.lichess.search" %% "client" % "3.0.2"
val munit = "org.scalameta" %% "munit" % "1.0.2" % Test
val uaparser = "org.uaparser" %% "uap-scala" % "0.18.0"
val apacheText = "org.apache.commons" % "commons-text" % "1.12.0"
Expand Down
2 changes: 1 addition & 1 deletion project/build.properties
Original file line number Diff line number Diff line change
@@ -1 +1 @@
sbt.version=1.10.3
sbt.version=1.10.4
12 changes: 6 additions & 6 deletions translation/dest/broadcast/de-DE.xml
Original file line number Diff line number Diff line change
Expand Up @@ -52,21 +52,21 @@
<string name="openLichess">In Lichess öffnen</string>
<string name="teams">Teams</string>
<string name="boards">Bretter</string>
<string name="overview">Übersicht</string>
<string name="subscribeTitle">Abonnieren, um bei Rundenbeginn benachrichtigt zu werden. Du kannst in deinen Benutzereinstellungen bei Übertragungen zwischen einer Benachrichtigung per Glocke oder Push-Benachrichtigungen wählen.</string>
<string name="overview">Überblick</string>
<string name="subscribeTitle">Abonnieren, um bei Rundenbeginn benachrichtigt zu werden. Du kannst in deinen Benutzereinstellungen für Übertragungen zwischen einer Benachrichtigung per Glocke oder per Push-Benachrichtigung wählen.</string>
<string name="uploadImage">Turnierbild hochladen</string>
<string name="noBoardsYet">Noch keine Bretter vorhanden. Diese werden angezeigt, sobald die Partien hochgeladen werden.</string>
<string name="boardsCanBeLoaded" comment="%s is 'Broadcaster App'">Die Bretter können per Quelle oder via %s geladen werden</string>
<string name="boardsCanBeLoaded" comment="%s is 'Broadcaster App', and links to https://lichess.org/broadcast/app">Die Bretter können per Quelle oder via %s geladen werden</string>
<string name="startsAfter">Beginnt nach %s</string>
<string name="startVerySoon">Diese Übertragung wird in Kürze beginnen.</string>
<string name="notYetStarted">Die Übertragung hat noch nicht begonnen.</string>
<string name="officialWebsite">Offizielle Webseite</string>
<string name="standings">Rangliste</string>
<string name="iframeHelp" comment="%s is 'webmasters page'">Weitere Optionen auf der %s</string>
<string name="webmastersPage">Webmaster-Seite</string>
<string name="iframeHelp" comment="%s is 'webmasters page', which is available for translation separately.&#10;&#10;Appears on broadcast pages for the person who set up the tournament broadcast.">Weitere Optionen auf der %s</string>
<string name="webmastersPage" comment="Part of the longer string iframeHelp.&#10;&#10;Appears on broadcast pages for the person who set up the tournament broadcast. Links to https://lichess.org/developers#broadcast">Webmaster-Seite</string>
<string name="pgnSourceHelp" comment="%s is 'streaming API'">Eine öffentliche Echtzeit-PGN-Quelle für diese Runde. Wir bieten auch eine %s für eine schnellere und effizientere Synchronisation.</string>
<string name="embedThisBroadcast">Bette diese Übertragung in deine Webseite ein</string>
<string name="embedThisRound" comment="%s is &quot;this round&quot;">Bette %s in deine Webseite ein</string>
<string name="embedThisRound" comment="%s is the name of the round in the broadcasted tournament (e.g. &quot;Round 6&quot;). The round name is defined by the creator of the broadcast and may be in a different language.">Bette %s in deine Webseite ein</string>
<string name="ratingDiff">Wertungsdifferenz</string>
<string name="gamesThisTournament">Partien in diesem Turnier</string>
<string name="score">Punktestand</string>
Expand Down
Loading

0 comments on commit 8b5182f

Please sign in to comment.