work on game bookmarks
parent
76a512ecfd
commit
3f85628258
|
@ -2,17 +2,25 @@ package controllers
|
|||
|
||||
import lila._
|
||||
import views._
|
||||
import http.Context
|
||||
|
||||
import play.api.mvc.Result
|
||||
|
||||
object Game extends LilaController {
|
||||
|
||||
val gameRepo = env.game.gameRepo
|
||||
val paginator = env.game.paginator
|
||||
val cached = env.game.cached
|
||||
val starApi = env.star.api
|
||||
val listMenu = env.game.listMenu
|
||||
|
||||
val maxPage = 40
|
||||
|
||||
val realtime = Open { implicit ctx ⇒
|
||||
IOk(gameRepo recentGames 9 map { games ⇒
|
||||
html.game.realtime(games, cached.nbGames, cached.nbMates)
|
||||
})
|
||||
IOk(for {
|
||||
games ← gameRepo recentGames 9
|
||||
menu ← makeListMenu
|
||||
} yield html.game.realtime(games, menu))
|
||||
}
|
||||
|
||||
def realtimeInner(ids: String) = Open { implicit ctx ⇒
|
||||
|
@ -22,20 +30,33 @@ object Game extends LilaController {
|
|||
}
|
||||
|
||||
def all(page: Int) = Open { implicit ctx ⇒
|
||||
(page < 50).fold(
|
||||
Ok(html.game.all(
|
||||
paginator recent page, cached.nbGames, cached.nbMates
|
||||
)),
|
||||
BadRequest("too old")
|
||||
)
|
||||
reasonable(page) {
|
||||
IOk(makeListMenu map { menu ⇒
|
||||
html.game.all(paginator recent page, menu)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
def checkmate(page: Int) = Open { implicit ctx ⇒
|
||||
(page < 50).fold(
|
||||
Ok(html.game.checkmate(
|
||||
paginator checkmate page, cached.nbGames, cached.nbMates
|
||||
)),
|
||||
BadRequest("too old")
|
||||
)
|
||||
reasonable(page) {
|
||||
IOk(makeListMenu map { menu ⇒
|
||||
html.game.checkmate(paginator checkmate page, menu)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
def star(page: Int) = Auth { implicit ctx ⇒
|
||||
me =>
|
||||
reasonable(page) {
|
||||
IOk(makeListMenu map { menu ⇒
|
||||
html.game.star(starApi.gamePaginatorByUser(me, page), menu)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
private def reasonable(page: Int)(result: ⇒ Result): Result =
|
||||
(page < maxPage).fold(result, BadRequest("too old"))
|
||||
|
||||
private def makeListMenu(implicit ctx: Context) =
|
||||
listMenu(starApi.countByUser, ctx.me)
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@ object Round extends LilaController {
|
|||
private val messenger = env.round.messenger
|
||||
private val rematcher = env.setup.rematcher
|
||||
private val joiner = env.setup.friendJoiner
|
||||
private val starApi = env.star.api
|
||||
|
||||
def websocketWatcher(gameId: String, color: String) = WebSocket.async[JsValue] { req ⇒
|
||||
implicit val ctx = reqToCtx(req)
|
||||
|
@ -41,8 +42,8 @@ object Round extends LilaController {
|
|||
pov.game.started.fold(
|
||||
messenger render pov.game map { roomHtml ⇒
|
||||
Ok(html.round.player(
|
||||
pov,
|
||||
version(pov.gameId),
|
||||
pov,
|
||||
version(pov.gameId),
|
||||
roomHtml map { Html(_) }))
|
||||
},
|
||||
io(Redirect(routes.Setup.await(fullId)))
|
||||
|
@ -53,15 +54,19 @@ object Round extends LilaController {
|
|||
def watcher(gameId: String, color: String) = Open { implicit ctx ⇒
|
||||
IOptionIOResult(gameRepo.pov(gameId, color)) { pov ⇒
|
||||
pov.game.started.fold(
|
||||
io(Ok(html.round.watcher(pov, version(pov.gameId)))),
|
||||
starApi usersByGame pov.game map { bookmarkers ⇒
|
||||
Ok(html.round.watcher(pov, version(pov.gameId), bookmarkers))
|
||||
},
|
||||
join(pov))
|
||||
}
|
||||
}
|
||||
|
||||
private def join(pov: Pov)(implicit ctx: Context): IO[Result] =
|
||||
joiner(pov.game, ctx.me).fold(
|
||||
err ⇒ putFailures(err) map { _ ⇒
|
||||
Ok(html.round.watcher(pov, version(pov.gameId)))
|
||||
err ⇒ putFailures(err) flatMap { _ ⇒
|
||||
starApi usersByGame pov.game map { bookmarkers ⇒
|
||||
Ok(html.round.watcher(pov, version(pov.gameId), bookmarkers))
|
||||
}
|
||||
},
|
||||
_ flatMap {
|
||||
case (p, events) ⇒ performEvents(p.gameId)(events) map { _ ⇒
|
||||
|
|
|
@ -22,4 +22,6 @@ final class GameEnv(
|
|||
maxPerPage = GamePaginatorMaxPerPage)
|
||||
|
||||
lazy val export = Export(gameRepo) _
|
||||
|
||||
lazy val listMenu = ListMenu(cached) _
|
||||
}
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
package lila
|
||||
package game
|
||||
|
||||
import user.User
|
||||
|
||||
import scalaz.effects._
|
||||
|
||||
case class ListMenu(
|
||||
nbGames: Int,
|
||||
nbMates: Int,
|
||||
nbStars: Option[Int])
|
||||
|
||||
object ListMenu {
|
||||
|
||||
type CountStars = User ⇒ IO[Int]
|
||||
|
||||
def apply(cached: Cached)(countStars: CountStars, me: Option[User]): IO[ListMenu] =
|
||||
me.fold(
|
||||
m ⇒ countStars(m) map (_.some),
|
||||
io(none)
|
||||
) map { nbStars ⇒
|
||||
new ListMenu(
|
||||
nbGames = cached.nbGames,
|
||||
nbMates = cached.nbMates,
|
||||
nbStars = nbStars)
|
||||
}
|
||||
}
|
|
@ -86,6 +86,8 @@ final class I18nKeys(translator: Translator) {
|
|||
val gamesBeingPlayedRightNow = new Key("gamesBeingPlayedRightNow")
|
||||
val viewAllNbGames = new Key("viewAllNbGames")
|
||||
val viewNbCheckmates = new Key("viewNbCheckmates")
|
||||
val nbBookmarks = new Key("nbBookmarks")
|
||||
val bookmarkedByNbPlayers = new Key("bookmarkedByNbPlayers")
|
||||
val viewInFullSize = new Key("viewInFullSize")
|
||||
val logOut = new Key("logOut")
|
||||
val signIn = new Key("signIn")
|
||||
|
|
|
@ -1,16 +1,27 @@
|
|||
package lila
|
||||
package star
|
||||
|
||||
import game.{ DbGame, GameRepo }
|
||||
import game.DbGame
|
||||
import user.{ User, UserRepo }
|
||||
|
||||
import scalaz.effects._
|
||||
|
||||
final class StarApi(
|
||||
starRepo: StarRepo,
|
||||
gameRepo: GameRepo,
|
||||
userRepo: UserRepo) {
|
||||
userRepo: UserRepo,
|
||||
paginator: PaginatorBuilder) {
|
||||
|
||||
def starred(game: DbGame, user: User): IO[Boolean] =
|
||||
starRepo.exists(game.id, user.id)
|
||||
|
||||
def countByUser(user: User): IO[Int] =
|
||||
starRepo countByUserId user.id
|
||||
|
||||
def usersByGame(game: DbGame): IO[List[User]] = for {
|
||||
userIds ← starRepo userIdsByGameId game.id
|
||||
users ← (userIds map userRepo.byId).sequence
|
||||
} yield users.flatten
|
||||
|
||||
def gamePaginatorByUser(user: User, page: Int) =
|
||||
paginator.byUser(user: User, page: Int) map (_.game)
|
||||
}
|
||||
|
|
|
@ -17,8 +17,14 @@ final class StarEnv(
|
|||
|
||||
lazy val starRepo = new StarRepo(mongodb(MongoCollectionStar))
|
||||
|
||||
lazy val api = new StarApi(
|
||||
lazy val paginator = new PaginatorBuilder(
|
||||
starRepo = starRepo,
|
||||
gameRepo = gameRepo,
|
||||
userRepo = userRepo)
|
||||
userRepo = userRepo,
|
||||
maxPerPage = GamePaginatorMaxPerPage)
|
||||
|
||||
lazy val api = new StarApi(
|
||||
starRepo = starRepo,
|
||||
userRepo = userRepo,
|
||||
paginator = paginator)
|
||||
}
|
||||
|
|
|
@ -26,6 +26,16 @@ final class StarRepo(val collection: MongoCollection) {
|
|||
(collection count idQuery(gameId, userId)) > 0
|
||||
}
|
||||
|
||||
def countByUserId(userId: String) = io {
|
||||
collection count userIdQuery(userId) toInt
|
||||
}
|
||||
|
||||
def userIdsByGameId(gameId: String): IO[List[String]] = io {
|
||||
(collection find gameIdQuery(gameId) sort sortQuery() map { obj ⇒
|
||||
obj.getAs[String]("u")
|
||||
}).toList.flatten
|
||||
}
|
||||
|
||||
def idQuery(gameId: String, userId: String) = DBObject("_id" -> (gameId + userId))
|
||||
def gameIdQuery(gameId: String) = DBObject("g" -> gameId)
|
||||
def userIdQuery(userId: String) = DBObject("u" -> userId)
|
||||
|
|
|
@ -16,6 +16,7 @@ object Environment
|
|||
with SettingHelper
|
||||
with ConfigHelper
|
||||
with DateHelper
|
||||
with NumberHelper
|
||||
with JsonHelper
|
||||
with PaginatorHelper
|
||||
with FormHelper
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
package lila
|
||||
package templating
|
||||
|
||||
import http.Context
|
||||
import i18n.I18nHelper
|
||||
|
||||
import java.util.Locale
|
||||
import java.text.NumberFormat
|
||||
import scala.collection.mutable
|
||||
|
||||
trait NumberHelper { self: I18nHelper ⇒
|
||||
|
||||
private val formatters = mutable.Map[String, NumberFormat]()
|
||||
|
||||
private def formatter(ctx: Context): NumberFormat =
|
||||
formatters.getOrElseUpdate(
|
||||
lang(ctx).language,
|
||||
NumberFormat getInstance new Locale(lang(ctx).language))
|
||||
|
||||
implicit def richInt(number: Int) = new {
|
||||
def localize(implicit ctx: Context): String =
|
||||
formatter(ctx) format number
|
||||
}
|
||||
}
|
|
@ -35,4 +35,9 @@ trait StringHelper {
|
|||
"<a href='%s'>%s</a>".format(m group 1, m group 1))
|
||||
|
||||
def showNumber(n: Int): String = (n > 0).fold("+" + n, n.toString)
|
||||
|
||||
implicit def richString(str: String) = new {
|
||||
def active(other: String, then: String = "active") =
|
||||
(str == other).fold(then, "")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
@(title: String, active: Option[ui.SiteMenu.Elem] = None, baseline: Option[Html] = None, goodies: Option[Html] = None, chat: Option[Html] = None, robots: Boolean = true, moreCss: Html = Html(""), moreJs: Html = Html(""))(body: Html)(implicit ctx: Context)
|
||||
@(title: String, active: Option[ui.SiteMenu.Elem] = None, baseline: Option[Html] = None, goodies: Option[Html] = None, menu: Option[Html] = None, chat: Option[Html] = None, robots: Boolean = true, moreCss: Html = Html(""), moreJs: Html = Html(""))(body: Html)(implicit ctx: Context)
|
||||
|
||||
<!doctype html>
|
||||
<html lang="@lang.language">
|
||||
|
@ -56,6 +56,9 @@
|
|||
</a>
|
||||
@baseline
|
||||
</h1>
|
||||
@menu.map { side =>
|
||||
<div class="side_menu">@side</div>
|
||||
}
|
||||
@goodies.map { g =>
|
||||
<div class="lichess_goodies_wrap">@g</div>
|
||||
}
|
||||
|
|
|
@ -1,13 +1,7 @@
|
|||
@(paginator: Paginator[DbGame], nbGames: Int, nbMates: Int)(implicit ctx: Context)
|
||||
|
||||
@menu = {
|
||||
<a class="game_list" href="@routes.Game.realtime()">@trans.gamesBeingPlayedRightNow()</a>
|
||||
<a class="game_list" href="@routes.Game.checkmate()">@trans.viewNbCheckmates(nbMates)</a>
|
||||
}
|
||||
@(paginator: Paginator[DbGame], listMenu: lila.game.ListMenu)(implicit ctx: Context)
|
||||
|
||||
@game.list(
|
||||
name = trans.viewAllNbGames.str(nbGames),
|
||||
name = trans.viewAllNbGames.str(listMenu.nbGames.localize),
|
||||
paginator = paginator,
|
||||
next = routes.Game.all(paginator.nextPage | 1),
|
||||
typ = trans.games.str(),
|
||||
menu = menu)
|
||||
next = paginator.nextPage map { n => routes.Game.all(n) },
|
||||
menu = sideMenu(listMenu, "all"))
|
||||
|
|
|
@ -1,13 +1,7 @@
|
|||
@(paginator: Paginator[DbGame], nbGames: Int, nbMates: Int)(implicit ctx: Context)
|
||||
|
||||
@menu = {
|
||||
<a class="game_list" href="@routes.Game.realtime()">@trans.gamesBeingPlayedRightNow()</a>
|
||||
<a class="game_list" href="@routes.Game.all()">@trans.viewAllNbGames(nbGames)</a>
|
||||
}
|
||||
@(paginator: Paginator[DbGame], listMenu: lila.game.ListMenu)(implicit ctx: Context)
|
||||
|
||||
@game.list(
|
||||
name = trans.viewNbCheckmates.str(nbMates),
|
||||
name = trans.viewNbCheckmates.str(listMenu.nbMates.localize),
|
||||
paginator = paginator,
|
||||
next = routes.Game.all(paginator.nextPage | 1),
|
||||
typ = trans.checkmate.str(),
|
||||
menu = menu)
|
||||
next = paginator.nextPage map { n => routes.Game.checkmate(n) },
|
||||
menu = sideMenu(listMenu, "checkmate"))
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
@(title: String, goodies: Option[Html] = None, moreJs: Html = Html(""))(body: Html)(implicit ctx: Context)
|
||||
@(title: String, goodies: Option[Html] = None, menu: Option[Html] = None, moreJs: Html = Html(""))(body: Html)(implicit ctx: Context)
|
||||
|
||||
@base.layout(
|
||||
title = title,
|
||||
goodies = goodies,
|
||||
menu = menu,
|
||||
active = siteMenu.game.some,
|
||||
moreJs = moreJs)(body)
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
@(name: String, paginator: Paginator[DbGame], next: Call, typ: String, menu: Html)(implicit ctx: Context)
|
||||
@(name: String, paginator: Paginator[DbGame], next: Option[Call], menu: Html)(implicit ctx: Context)
|
||||
|
||||
@title = @{ "%s - page %d".format(name, paginator.currentPage) }
|
||||
|
||||
|
@ -6,14 +6,18 @@
|
|||
@jsTag("vendor/jquery.infinitescroll.min.js")
|
||||
}
|
||||
|
||||
@game.layout(title = title, moreJs = moreJs) {
|
||||
@game.layout(
|
||||
title = title,
|
||||
moreJs = moreJs,
|
||||
menu = menu.some) {
|
||||
<div class="content_box no_padding">
|
||||
<div class="content_box_title">
|
||||
<h1 class="title">@typ (@paginator.nbResults)</h1>
|
||||
@menu
|
||||
<h1 class="title">@name</h1>
|
||||
</div>
|
||||
<div class="all_games infinitescroll">
|
||||
<div class="pager none"><a href="@next">Next</a></div>
|
||||
@next.map { n =>
|
||||
<div class="pager none"><a href="@n">Next</a></div>
|
||||
}
|
||||
@game.widgets(paginator.currentPageResults)
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
@(games: List[DbGame], nbGames: Int, nbMates: Int)(implicit ctx: Context)
|
||||
@(games: List[DbGame], listMenu: lila.game.ListMenu)(implicit ctx: Context)
|
||||
|
||||
@game.layout(title = trans.gamesBeingPlayedRightNow.str()) {
|
||||
@game.layout(
|
||||
title = trans.gamesBeingPlayedRightNow.str(),
|
||||
menu = sideMenu(listMenu, "realtime").some) {
|
||||
<div class="content_box current_games_box">
|
||||
<h1 class="title">@trans.gamesBeingPlayedRightNow()</h1>
|
||||
<a class="all_games" href="@routes.Game.all()">@trans.viewAllNbGames(nbGames)</a>
|
||||
<a class="all_games" href="@routes.Game.checkmate()">@trans.viewNbCheckmates(nbMates)</a>
|
||||
<div class="game_list" data-url="@routes.Game.realtimeInner(games.map(_.id).mkString(","))">
|
||||
<div class="game_list realtime" data-url="@routes.Game.realtimeInner(games.map(_.id).mkString(","))">
|
||||
@game.realtimeInner(games)
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
@(listMenu: lila.game.ListMenu, active: String)(implicit ctx: Context)
|
||||
|
||||
<a class="@active.active("realtime")" href="@routes.Game.realtime()">
|
||||
@trans.gamesBeingPlayedRightNow()
|
||||
</a>
|
||||
<a class="@active.active("all")" href="@routes.Game.all()">
|
||||
@trans.viewAllNbGames(listMenu.nbGames.localize)
|
||||
</a>
|
||||
<a class="@active.active("checkmate")" href="@routes.Game.checkmate()">
|
||||
@trans.viewNbCheckmates(listMenu.nbMates.localize)
|
||||
</a>
|
||||
@listMenu.nbStars.map { nb =>
|
||||
<a class="@active.active("star")" href="@routes.Game.star()">
|
||||
@trans.nbBookmarks(nb.localize)
|
||||
</a>
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
@(paginator: Paginator[DbGame], listMenu: lila.game.ListMenu)(implicit ctx: Context)
|
||||
|
||||
@game.list(
|
||||
name = trans.nbBookmarks.str(listMenu.nbStars.fold(_.localize, 0)),
|
||||
paginator = paginator,
|
||||
next = paginator.nextPage map { n => routes.Game.star(n) },
|
||||
menu = sideMenu(listMenu, "star"))
|
|
@ -1,4 +1,4 @@
|
|||
@(pov: Pov, version: Int)(implicit ctx: Context)
|
||||
@(pov: Pov, version: Int, bookmarkers: List[User])(implicit ctx: Context)
|
||||
|
||||
@import pov._
|
||||
|
||||
|
@ -40,8 +40,16 @@
|
|||
}
|
||||
<br /><br />
|
||||
<a class="rotate_board" href="@routes.Round.watcher(gameId, opponent.color.name)">@trans.flipBoard()</a>
|
||||
<br /><br />
|
||||
<a href="@routes.Lobby.home()"><strong>@trans.playANewGame()</strong></a>
|
||||
@if(bookmarkers.nonEmpty) {
|
||||
<div class="bookmarkers">
|
||||
<p>@trans.bookmarkedByNbPlayers(bookmarkers.size)</p>
|
||||
<ul>
|
||||
@bookmarkers.map { bookmarker =>
|
||||
<li>@userLink(bookmarker)</li>
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
|
|
|
@ -23,7 +23,6 @@ random=Random
|
|||
noGameAvailableRightNowCreateOne=No game available right now, create one!
|
||||
whiteIsVictorious=White is victorious
|
||||
blackIsVictorious=Black is victorious
|
||||
playANewGame=Play a new game
|
||||
rematch=Rematch
|
||||
playWithTheSameOpponentAgain=Play with the same opponent again
|
||||
newOpponent=New opponent
|
||||
|
@ -59,9 +58,11 @@ draw=Draw
|
|||
nbConnectedPlayers=%s connected players
|
||||
talkAboutChessAndDiscussLichessFeaturesInTheForum=Talk about chess and discuss lichess features in the forum
|
||||
seeTheGamesBeingPlayedInRealTime=See the games being played in real time
|
||||
gamesBeingPlayedRightNow=Games being played right now
|
||||
viewAllNbGames=View all %s games
|
||||
viewNbCheckmates=View %s checkmates
|
||||
gamesBeingPlayedRightNow=Current Games
|
||||
viewAllNbGames=%s Games
|
||||
viewNbCheckmates=%s Checkmates
|
||||
nbBookmarks=%s Bookmarks
|
||||
bookmarkedByNbPlayers=Bookmarked by %s players
|
||||
viewInFullSize=View in full size
|
||||
logOut=Log out
|
||||
signIn=Sign in
|
||||
|
|
|
@ -6,6 +6,7 @@ GET /games controllers.Game.realtime
|
|||
GET /games/refresh/:ids controllers.Game.realtimeInner(ids: String)
|
||||
GET /games/all controllers.Game.all(page: Int ?= 1)
|
||||
GET /games/checkmate controllers.Game.checkmate(page: Int ?= 1)
|
||||
GET /games/bookmark controllers.Game.star(page: Int ?= 1)
|
||||
|
||||
# Round
|
||||
GET /$gameId<[\w\-]{8}> controllers.Round.watcher(gameId: String, color: String = "white")
|
||||
|
|
|
@ -210,6 +210,32 @@ div.lichess_goodies div.box {
|
|||
div.lichess_goodies div.box .player {
|
||||
margin: 4px 0;
|
||||
}
|
||||
|
||||
div.bookmarkers {
|
||||
margin-top: 1em;
|
||||
}
|
||||
div.bookmarkers li {
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
div.side_menu {
|
||||
margin-top: 15px;
|
||||
}
|
||||
div.side_menu a {
|
||||
padding: 8px 0 8px 8px;
|
||||
display: block;
|
||||
text-decoration: none;
|
||||
width: 203px;
|
||||
font-weight: bold;
|
||||
}
|
||||
div.side_menu a.active {
|
||||
background: white;
|
||||
border: 1px solid #dadada;
|
||||
border-right: none;
|
||||
border-radius: 4px 0 0 4px;
|
||||
box-shadow: -3px 0 6px #d0d0d0;
|
||||
}
|
||||
|
||||
#nb_connected_players, a.goto_nav, #top a.toggle, a#sound_state {
|
||||
text-decoration: none;
|
||||
font-size: 13px;
|
||||
|
|
|
@ -9,18 +9,6 @@ div.current_games_box {
|
|||
width: 638px;
|
||||
}
|
||||
|
||||
a.game_list, a.all_games {
|
||||
margin-left: 2em;
|
||||
}
|
||||
|
||||
div.game_list {
|
||||
margin-top: 2em;
|
||||
}
|
||||
|
||||
div.all_games {
|
||||
margin-top: 1em;
|
||||
}
|
||||
|
||||
div.game_row {
|
||||
display: block;
|
||||
padding: 1.4em 20px;
|
||||
|
@ -60,6 +48,9 @@ div.game_row span.black {
|
|||
background-position: 0 -256px;
|
||||
}
|
||||
|
||||
div.game_list.realtime {
|
||||
margin-top: 1.4em;
|
||||
}
|
||||
div.game_list_inner > div {
|
||||
float: left;
|
||||
margin: 0 10px 10px 10px;
|
||||
|
|
1
todo
1
todo
|
@ -23,6 +23,7 @@ guess friend list
|
|||
star people and games (and forum threads?)
|
||||
autoclose top menus
|
||||
tournaments http://www.chess.com/tournaments/help.html
|
||||
long name display issue http://en.lichess.org/forum/lichess-feedback/long-names-alter-layout
|
||||
|
||||
new translations:
|
||||
-rematchOfferCanceled=Rematch offer canceled
|
||||
|
|
Loading…
Reference in New Issue