Skip to content

Commit

Permalink
Merge branch 'master' into global-setting-for-coordinates-on-every-sq…
Browse files Browse the repository at this point in the history
…uare
  • Loading branch information
ornicar committed Jun 29, 2024
2 parents bfabae6 + d2a903c commit d2e5dbd
Show file tree
Hide file tree
Showing 17 changed files with 161 additions and 104 deletions.
16 changes: 16 additions & 0 deletions bin/mongodb/relay-dates-migrate.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// db.relay.aggregate([{$match:{tourId:'KNfeoWE'}},{$project:{name:1,at:{$ifNull:['$startsAt','$startedAt']}}},{$sort:{at:1}},{$group:{_id:null,at:{$push:'$at'}}},{$project:{start:{$first:'$at'},end:{$last:'$at'}}}])

const fetchDates = (tourId) => db.relay.aggregate([
{ $match: { tourId } },
{ $project: { name: 1, at: { $ifNull: ['$startsAt', '$startedAt'] } } },
{ $sort: { at: 1 } }, { $group: { _id: null, at: { $push: '$at' } } },
{ $project: { start: { $first: '$at' }, end: { $last: '$at' } } }
]).next();

db.relay_tour.find({ dates: { $exists: 0 } }).sort({ $natural: -1 }).forEach(tour => {
const dates = fetchDates(tour._id);
if (dates) {
console.log(tour._id + ' ' + tour.createdAt);
db.relay_tour.updateOne({ _id: tour._id }, { $set: { dates } });
}
});
1 change: 1 addition & 0 deletions modules/relay/src/main/BSONHandlers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ object BSONHandlers:

given BSONDocumentHandler[RelayTour.Spotlight] = Macros.handler
given BSONDocumentHandler[RelayTour.Info] = Macros.handler
given BSONDocumentHandler[RelayTour.Dates] = Macros.handler
given tourHandler: BSONDocumentHandler[RelayTour] = Macros.handler

given BSONDocumentHandler[RelayTour.IdName] = Macros.handler
Expand Down
4 changes: 4 additions & 0 deletions modules/relay/src/main/JsonView.scala
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ final class JsonView(

given OWrites[RelayTour.Info] = Json.writes

given Writes[RelayTour.Dates] = Writes: ds =>
JsArray(List(ds.start.some, ds.end).flatten.map(Json.toJson))

given OWrites[RelayTour] = OWrites: t =>
Json
.obj(
Expand All @@ -37,6 +40,7 @@ final class JsonView(
"url" -> s"$baseUrl${t.path}"
)
.add("tier" -> t.tier)
.add("dates" -> t.dates)
.add("image" -> t.image.map(id => RelayTour.thumbnail(picfitUrl, id, _.Size.Large)))

given OWrites[RelayTour.IdName] = Json.writes
Expand Down
34 changes: 27 additions & 7 deletions modules/relay/src/main/RelayApi.scala
Original file line number Diff line number Diff line change
Expand Up @@ -90,14 +90,33 @@ final class RelayApi(

def withRounds(tour: RelayTour) = roundRepo.byTourOrdered(tour.id).dmap(tour.withRounds)

def denormalizeTourActive(tourId: RelayTourId): Funit =
def denormalizeTour(tourId: RelayTourId): Funit =
val unfinished = RelayRoundRepo.selectors.tour(tourId) ++ $doc("finished" -> false)
for
active <- roundRepo.coll.exists(unfinished)
live <- active.so(roundRepo.coll.exists(unfinished ++ $doc("startedAt".$exists(true))))
_ <- tourRepo.setActive(tourId, active, live)
dates <- computeDates(tourId)
_ <- tourRepo.denormalize(tourId, active, live, dates)
yield ()

private def computeDates(tourId: RelayTourId): Fu[Option[RelayTour.Dates]] =
roundRepo.coll
.aggregateOne(): framework =>
import framework.*
Match($doc("tourId" -> tourId)) -> List(
Project($doc("at" -> $doc("ifNull" -> $arr("$startsAt", "$startedAt")))),
Sort(Ascending("at")),
Group(BSONNull)("at" -> PushField("at")),
Project($doc("start" -> $doc("$first" -> "$at"), "end" -> $doc("$first" -> "$at")))
)
.map:
_.flatMap: doc =>
for
start <- doc.getAsOpt[Instant]("start")
end <- doc.getAsOpt[Instant]("end")
endMaybe = Option.when(end != start)(end)
yield RelayTour.Dates(start, endMaybe)

object countOwnedByUser:
private val cache = cacheApi[UserId, Int](16_384, "relay.nb.owned"):
_.expireAfterWrite(5.minutes).buildAsyncFuture(tourRepo.countByOwner(_, false))
Expand Down Expand Up @@ -239,9 +258,10 @@ final class RelayApi(
)
)
.orFail(s"Can't create study for relay $relay")
_ <- roundRepo.coll.insert.one(relay)
_ <- tourRepo.setActive(tour.id, true, relay.hasStarted)
_ <- studyApi.addTopics(relay.studyId, List(StudyTopic.broadcast.value))
_ <- roundRepo.coll.insert.one(relay)
dates <- computeDates(tour.id)
_ <- tourRepo.denormalize(tour.id, true, relay.hasStarted, dates)
_ <- studyApi.addTopics(relay.studyId, List(StudyTopic.broadcast.value))
yield relay.withTour(tour).withStudy(study.study)

def requestPlay(id: RelayRoundId, v: Boolean): Funit =
Expand Down Expand Up @@ -269,7 +289,7 @@ final class RelayApi(
_ <- roundRepo.coll.update.one($id(round.id), round).void
_ <- (round.sync.playing != from.sync.playing)
.so(sendToContributors(round.id, "relaySync", jsonView.sync(round)))
_ <- (round.stateHash != from.stateHash).so(denormalizeTourActive(round.tourId))
_ <- (round.stateHash != from.stateHash).so(denormalizeTour(round.tourId))
yield
round.sync.log.events.lastOption
.ifTrue(round.sync.log != from.sync.log)
Expand All @@ -296,7 +316,7 @@ final class RelayApi(
byIdWithTour(roundId).flatMapz: rt =>
for
_ <- roundRepo.coll.delete.one($id(rt.round.id))
_ <- denormalizeTourActive(rt.tour.id)
_ <- denormalizeTour(rt.tour.id)
yield rt.tour.some

def deleteTourIfOwner(tour: RelayTour)(using me: Me): Fu[Boolean] =
Expand Down
3 changes: 2 additions & 1 deletion modules/relay/src/main/RelayPush.scala
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,8 @@ final class RelayPush(
private def push(rt: RelayRound.WithTour, rawGames: Vector[RelayGame], andSyncTargets: Boolean) =
workQueue(rt.round.id):
for
games <- fidePlayers.enrichGames(rt.tour)(rawGames)
withPlayers <- fuccess(rt.tour.players.fold(rawGames)(_.update(rawGames)))
games <- fidePlayers.enrichGames(rt.tour)(withPlayers)
event <- sync
.updateStudyChapters(rt, rt.tour.players.fold(games)(_.update(games)))
.map: res =>
Expand Down
12 changes: 8 additions & 4 deletions modules/relay/src/main/RelayTour.scala
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ case class RelayTour(
players: Option[RelayPlayersTextarea] = None,
teams: Option[RelayTeamsTextarea] = None,
image: Option[ImageId] = None,
dates: Option[RelayTour.Dates] = None, // denormalized from round dates
pinnedStreamer: Option[UserStr] = None,
pinnedStreamerImage: Option[ImageId] = None
):
Expand Down Expand Up @@ -76,13 +77,15 @@ object RelayTour:
type Selector = RelayTour.Tier.type => RelayTour.Tier

case class Info(
dates: Option[String],
format: Option[String],
tc: Option[String],
players: Option[String]
):
def nonEmpty = List(dates, format, tc, players).exists(_.nonEmpty)
override def toString = List(format, tc, players).flatten.mkString(" | ")
val all = List(format, tc, players).flatten
export all.nonEmpty
override def toString = all.flatten.mkString(" | ")

case class Dates(start: Instant, end: Option[Instant])

case class Spotlight(enabled: Boolean, language: Language, title: Option[String]):
def isEmpty = !enabled && specialLanguage.isEmpty && title.isEmpty
Expand All @@ -101,7 +104,8 @@ object RelayTour:
~round.sync.log.lastErrors.some
.filter(_.nonEmpty)
.orElse:
(round.hasStarted && !round.sync.ongoing).option(List("Not syncing!"))
(round.hasStarted && round.sync.upstream.isDefined && !round.sync.ongoing)
.option(List("Not syncing!"))

case class WithLastRound(tour: RelayTour, round: RelayRound, group: Option[RelayGroup.Name])
extends RelayRound.AndTourAndGroup:
Expand Down
1 change: 0 additions & 1 deletion modules/relay/src/main/RelayTourForm.scala
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ final class RelayTourForm(langList: lila.core.i18n.LangList):
)(unapply)

val infoMapping = mapping(
"dates" -> optional(cleanText(maxLength = 80)),
"format" -> optional(cleanText(maxLength = 80)),
"tc" -> optional(cleanText(maxLength = 80)),
"players" -> optional(cleanText(maxLength = 120))
Expand Down
9 changes: 7 additions & 2 deletions modules/relay/src/main/RelayTourRepo.scala
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,13 @@ final private class RelayTourRepo(val coll: Coll)(using Executor):
def setSyncedNow(tour: RelayTour): Funit =
coll.updateField($id(tour.id), "syncedAt", nowInstant).void

def setActive(tourId: RelayTourId, active: Boolean, live: Boolean): Funit =
coll.update.one($id(tourId), $set("active" -> active, "live" -> live)).void
def denormalize(
tourId: RelayTourId,
active: Boolean,
live: Boolean,
dates: Option[RelayTour.Dates]
): Funit =
coll.update.one($id(tourId), $set("active" -> active, "live" -> live, "dates" -> dates)).void

def lookup(local: String) = $lookup.simple(coll, "tour", local, "_id")

Expand Down
21 changes: 6 additions & 15 deletions modules/relay/src/main/ui/FormUi.scala
Original file line number Diff line number Diff line change
Expand Up @@ -382,33 +382,24 @@ final class FormUi(helpers: Helpers, ui: RelayUi, tourUi: RelayTourUi):
form3.group(form("name"), trb.tournamentName())(form3.input(_)(autofocus)),
form3.fieldset("Optional details", toggle = tg.exists(_.tour.info.nonEmpty).some)(
form3.split(
form3.group(
form("info.dates"),
"Event dates",
help = frag("""Start and end dates, eg "June 17th - 25th" or "June 17th"""").some,
half = true
)(form3.input(_)),
form3.group(
form("info.format"),
"Tournament format",
help = frag("""e.g. "8-player round-robin" or "5-round Swiss"""").some,
half = true
)(form3.input(_))
),
form3.split(
)(form3.input(_)),
form3.group(
form("info.tc"),
"Time control",
help = frag(""""Classical" or "Rapid"""").some,
half = true
)(form3.input(_)),
form3.group(
form("info.players"),
"Top players",
help = frag("Mention up to 4 of the best players participating").some,
half = true
)(form3.input(_))
),
form3.group(
form("info.players"),
"Top players",
help = frag("Mention up to 4 of the best players participating").some
)(form3.input(_)),
form3.group(
form("markdown"),
trb.fullDescription(),
Expand Down
4 changes: 1 addition & 3 deletions modules/user/src/main/UserForm.scala
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ final class UserForm:
"flag" -> optional(text.verifying(Flags.codeSet contains _)),
"location" -> optional(cleanNoSymbolsAndNonEmptyText(maxLength = 80)),
"bio" -> optional(cleanNoSymbolsAndNonEmptyText(maxLength = 400)),
"realName" -> nameField,
"realName" -> optional(cleanNoSymbolsText(minLength = 1, maxLength = 100)),
"fideRating" -> optional(number(min = 1400, max = 3000)),
"uscfRating" -> optional(number(min = 100, max = 3000)),
"ecfRating" -> optional(number(min = 0, max = 3000)),
Expand All @@ -57,8 +57,6 @@ final class UserForm:
def flair(using Me) = Form[Option[Flair]]:
single(FlairApi.formPair())

private def nameField = optional(cleanNoSymbolsText(minLength = 1, maxLength = 20))

object UserForm:

val note = Form:
Expand Down
1 change: 1 addition & 0 deletions ui/@types/lichess/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ interface Site {
};
timeago(date: number | Date): string;
dateFormat: () => (date: Date) => string;
displayLocale: string;
contentLoaded(parent?: HTMLElement): void;
blindMode: boolean;
makeChat(data: any): any;
Expand Down
4 changes: 3 additions & 1 deletion ui/analyse/src/study/relay/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,13 @@ export interface RelayRound {
}

export interface RelayTourInfo {
dates?: string;
format?: string;
tc?: string;
players?: string;
}

export type RelayTourDates = [number] | [number, number];

export interface RelayTour {
id: string;
name: string;
Expand All @@ -48,6 +49,7 @@ export interface RelayTour {
teamTable?: boolean;
leaderboard?: boolean;
tier?: number;
dates?: RelayTourDates;
}

export interface RelaySync {
Expand Down
95 changes: 57 additions & 38 deletions ui/analyse/src/study/relay/relayTourView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ import * as licon from 'common/licon';
import { bind, dataIcon, onInsert, looseH as h } from 'common/snabbdom';
import { VNode } from 'snabbdom';
import { innerHTML } from 'common/richText';
import { RelayGroup, RelayRound, RelayTourInfo } from './interfaces';
import { RelayGroup, RelayRound, RelayTourDates, RelayTourInfo } from './interfaces';
import { view as multiBoardView } from '../multiBoard';
import { defined } from 'common';
import { defined, memoize } from 'common';
import StudyCtrl from '../studyCtrl';
import { toggle } from 'common/controls';
import * as xhr from 'common/xhr';
Expand Down Expand Up @@ -103,11 +103,11 @@ const leaderboard = (ctx: RelayViewContext) => [
ctx.relay.leaderboard && leaderboardView(ctx.relay.leaderboard),
];

const showInfo = (i: RelayTourInfo) =>
const showInfo = (i: RelayTourInfo, dates?: RelayTourDates) =>
h(
'div.relay-tour__info',
[
['dates', i.dates, 'objects.calendar'],
['dates', dates && showDates(dates), 'objects.calendar'],
['format', i.format, 'objects.crown'],
['tc', i.tc, 'objects.mantelpiece-clock'],
['players', i.players, 'activity.sparkles'],
Expand All @@ -119,41 +119,60 @@ const showInfo = (i: RelayTourInfo) =>
),
);

const overview = (ctx: RelayViewContext) => [
...header(ctx),
showInfo(ctx.relay.data.tour.info),
ctx.relay.data.tour.markup
? h('div.relay-tour__markup', {
hook: innerHTML(ctx.relay.data.tour.markup, () => ctx.relay.data.tour.markup!),
})
: undefined,
h('div.relay-tour__share', [
h('h2.text', { attrs: dataIcon(licon.Heart) }, 'Sharing is caring'),
...[
[ctx.relay.data.tour.name, ctx.relay.tourPath()],
[ctx.study.data.name, ctx.relay.roundPath()],
[
`${ctx.study.data.name} PGN`,
`${ctx.relay.roundPath()}.pgn`,
h('div.form-help', [
'A public, real-time PGN source for this round. We also offer a ',
h(
'a',
{ attrs: { href: 'https://lichess.org/api#tag/Broadcasts/operation/broadcastStreamRoundPgn' } },
'streaming API',
),
' for faster and more efficient synchronisation.',
const dateFormat = memoize(() =>
window.Intl && Intl.DateTimeFormat
? new Intl.DateTimeFormat(site.displayLocale, {
month: 'short',
day: '2-digit',
} as any).format
: (d: Date) => d.toLocaleDateString(),
);

const showDates = (dates: RelayTourDates) => {
const rendered = dates.map(date => dateFormat()(new Date(date)));
if (rendered[1]) return `${rendered[0]} - ${rendered[1]}`;
return rendered[0];
};

const overview = (ctx: RelayViewContext) => {
const tour = ctx.relay.data.tour;
console.log(tour.dates);
return [
...header(ctx),
showInfo(tour.info, tour.dates),
tour.markup
? h('div.relay-tour__markup', {
hook: innerHTML(tour.markup, () => tour.markup!),
})
: undefined,
h('div.relay-tour__share', [
h('h2.text', { attrs: dataIcon(licon.Heart) }, 'Sharing is caring'),
...[
[tour.name, ctx.relay.tourPath()],
[ctx.study.data.name, ctx.relay.roundPath()],
[
`${ctx.study.data.name} PGN`,
`${ctx.relay.roundPath()}.pgn`,
h('div.form-help', [
'A public, real-time PGN source for this round. We also offer a ',
h(
'a',
{ attrs: { href: 'https://lichess.org/api#tag/Broadcasts/operation/broadcastStreamRoundPgn' } },
'streaming API',
),
' for faster and more efficient synchronisation.',
]),
],
].map(([i18n, path, help]: [string, string, VNode]) =>
h('div.form-group', [
h('label.form-label', ctx.ctrl.trans.noarg(i18n)),
copyMeInput(`${baseUrl()}${path}`),
help,
]),
],
].map(([i18n, path, help]: [string, string, VNode]) =>
h('div.form-group', [
h('label.form-label', ctx.ctrl.trans.noarg(i18n)),
copyMeInput(`${baseUrl()}${path}`),
help,
]),
),
]),
];
),
]),
];
};

const groupSelect = (relay: RelayCtrl, group: RelayGroup) => {
const toggle = relay.groupSelectShow;
Expand Down
Loading

0 comments on commit d2e5dbd

Please sign in to comment.