diff --git a/app/controllers/Tournament.scala b/app/controllers/Tournament.scala
index ddf071371c..14e4cb5a60 100644
--- a/app/controllers/Tournament.scala
+++ b/app/controllers/Tournament.scala
@@ -118,13 +118,6 @@ object Tournament extends LilaController {
}
}
- def gameStanding(id: String) = Open { implicit ctx =>
- env.api.miniStanding(id, true) map {
- case Some(m) if !m.tour.isCreated => Ok(html.tournament.gameStanding(m))
- case _ => NotFound
- }
- }
-
def userGameNbMini(id: String, user: String, nb: Int) = Open { implicit ctx =>
withUserGameNb(id, user, nb) { pov =>
Ok(html.tournament.miniGame(pov))
diff --git a/app/templating/UserHelper.scala b/app/templating/UserHelper.scala
index ff776b41ee..288287e098 100644
--- a/app/templating/UserHelper.scala
+++ b/app/templating/UserHelper.scala
@@ -71,6 +71,8 @@ trait UserHelper { self: I18nHelper with StringHelper with NumberHelper =>
def lightUser(userId: String): Option[LightUser] = Env.user lightUserSync userId
def lightUser(userId: Option[String]): Option[LightUser] = userId flatMap lightUser
+ // def lightUserSync: LightUser.SyncGetter(userId: String): Option[LightUser] = Env.user lightUserSync userId
+
def usernameOrId(userId: String) = lightUser(userId).fold(userId)(_.titleName)
def usernameOrAnon(userId: Option[String]) = lightUser(userId).fold(User.anonymous)(_.titleName)
diff --git a/app/views/game/side.scala.html b/app/views/game/side.scala.html
index 0e6e73ae62..d467739018 100644
--- a/app/views/game/side.scala.html
+++ b/app/views/game/side.scala.html
@@ -82,7 +82,6 @@
- @tournament.gameStanding(m)
}.getOrElse {
@game.tournamentId.map { tourId =>
diff --git a/app/views/round/jsI18n.scala.html b/app/views/round/jsI18n.scala.html
index f6e76395d8..fcbbe08443 100644
--- a/app/views/round/jsI18n.scala.html
+++ b/app/views/round/jsI18n.scala.html
@@ -1,4 +1,4 @@
-@()(implicit ctx: Context)
+@(g: Game)(implicit ctx: Context)
@Html(J.stringify(i18nJsObject(
trans.flipBoard,
trans.aiNameLevelAiLevel,
@@ -35,7 +35,6 @@ trans.blackIsVictorious,
trans.kingInTheCenter,
trans.threeChecks,
trans.variantEnding,
-trans.backToTournament,
trans.withdraw,
trans.rematch,
trans.rematchOfferSent,
@@ -45,7 +44,6 @@ trans.cancelRematchOffer,
trans.newOpponent,
trans.moveConfirmation,
trans.viewRematch,
-trans.viewTournament,
trans.whitePlays,
trans.blackPlays,
trans.giveNbSeconds,
@@ -56,4 +54,8 @@ trans.yourOpponentWantsToPlayANewGameWithYou,
trans.oneDay,
trans.nbDays,
trans.nbHours
-)))
+) ++ g.isTournament.fold(i18nJsObject(
+trans.backToTournament,
+trans.viewTournament,
+trans.standing
+), J.obj())))
diff --git a/app/views/round/player.scala.html b/app/views/round/player.scala.html
index ff4744f767..1e3e670931 100644
--- a/app/views/round/player.scala.html
+++ b/app/views/round/player.scala.html
@@ -11,8 +11,9 @@ window.customWS = true;
window.onload = function() {
LichessRound.boot({
data: @Html(J.stringify(data)),
-i18n: @jsI18n(),
+i18n: @jsI18n(pov.game),
userId: @jsUserId,
+tour: @jsOrNull(tour.map(m => lila.tournament.JsonView.miniStanding(m, lightUser))),
chat: @jsOrNull(chatOption.map(_.either).map {
case Left(c) => {
chat.ChatJsData.restricted(c, name = trans.chatRoom.txt(), timeout = false, withNote = true, public = false)
diff --git a/app/views/round/watcher.scala.html b/app/views/round/watcher.scala.html
index 5fea4a3c95..7b6e168c15 100644
--- a/app/views/round/watcher.scala.html
+++ b/app/views/round/watcher.scala.html
@@ -9,7 +9,7 @@ window.customWS = true;
window.onload = function() {
LichessRound.boot({
data: @Html(J.stringify(data)),
-i18n: @jsI18n(),
+i18n: @jsI18n(pov.game),
chat: @jsOrNull(chatOption map { c =>
chat.ChatJsData.json(c.chat, name = trans.spectatorRoom.txt(), timeout = c.timeout, withNote = ctx.isAuth, public = true)
})
diff --git a/app/views/tournament/gameStanding.scala.html b/app/views/tournament/gameStanding.scala.html
deleted file mode 100644
index abbd9bc4a8..0000000000
--- a/app/views/tournament/gameStanding.scala.html
+++ /dev/null
@@ -1,28 +0,0 @@
-@(m: lila.tournament.MiniStanding)(implicit ctx: Context)
-@m.standing.map { standing =>
-
-
- @standing.map {
- case lila.tournament.RankedPlayer(rank, player) => {
-
-
- @if(player.withdraw) {
-
- } else {
- @if(m.tour.isFinished && rank == 1) {
-
- } else {
- @rank
- }
- }
- @userInfosLink(player.userId, none, withOnline = false, withPowerTip = true)
- |
-
- @player.score
- |
-
- }
- }
-
-
-}
diff --git a/app/views/tv/index.scala.html b/app/views/tv/index.scala.html
index 2f37aea97c..59a2015f2e 100644
--- a/app/views/tv/index.scala.html
+++ b/app/views/tv/index.scala.html
@@ -8,7 +8,7 @@
window.onload = function() {
LichessRound.boot({
data: @Html(J.stringify(data)),
-i18n: @round.jsI18n()
+i18n: @round.jsI18n(pov.game)
}, document.getElementById('lichess'));
};
}
diff --git a/conf/routes b/conf/routes
index bfdeefc1e6..838bc89604 100644
--- a/conf/routes
+++ b/conf/routes
@@ -220,7 +220,6 @@ POST /tournament/new controllers.Tournament.create
GET /tournament/$id<\w{8}> controllers.Tournament.show(id: String)
GET /tournament/$id<\w{8}>/standing/:page controllers.Tournament.standing(id: String, page: Int)
GET /tournament/$id<\w{8}>/socket/v:apiVersion controllers.Tournament.websocket(id: String, apiVersion: Int)
-GET /tournament/$id<\w{8}>/game-standing controllers.Tournament.gameStanding(id: String)
POST /tournament/$id<\w{8}>/join controllers.Tournament.join(id: String)
POST /tournament/$id<\w{8}>/withdraw controllers.Tournament.withdraw(id: String)
GET /tournament/$id<\w{8}>/mini/:user/:nb controllers.Tournament.userGameNbMini(id: String, user: String, nb: Int)
diff --git a/modules/tournament/src/main/Env.scala b/modules/tournament/src/main/Env.scala
index 42338816d9..973a9198b1 100644
--- a/modules/tournament/src/main/Env.scala
+++ b/modules/tournament/src/main/Env.scala
@@ -93,6 +93,7 @@ final class Env(
indexLeaderboard = leaderboardIndexer.indexOne _,
roundMap = roundMap,
asyncCache = asyncCache,
+ lightUserApi = lightUserApi,
standingChannel = standingChannel
)
diff --git a/modules/tournament/src/main/JsonView.scala b/modules/tournament/src/main/JsonView.scala
index 0474ee69d9..898aa5eca3 100644
--- a/modules/tournament/src/main/JsonView.scala
+++ b/modules/tournament/src/main/JsonView.scala
@@ -7,6 +7,7 @@ import scala.concurrent.duration._
import chess.Clock.{ Config => TournamentClock }
import lila.common.PimpedJson._
+import lila.common.LightUser
import lila.game.{ GameRepo, Pov }
import lila.quote.Quote.quoteWriter
import lila.rating.PerfType
@@ -323,6 +324,20 @@ final class JsonView(
object JsonView {
+ def miniStanding(m: MiniStanding, getLightUser: LightUser.GetterSync): JsObject = Json.obj(
+ "standing" -> (~m.standing).map {
+ case RankedPlayer(rank, player) =>
+ val light = getLightUser(player.userId)
+ Json.obj(
+ "name" -> light.fold(player.userId)(_.name),
+ "rank" -> rank,
+ "score" -> player.score
+ ).add("title" -> light.flatMap(_.title))
+ .add("fire" -> scala.util.Random.nextBoolean) //player.fire)
+ .add("withdraw" -> scala.util.Random.nextBoolean) //player.withdraw)
+ }
+ )
+
private def formatDate(date: DateTime) = ISODateTimeFormat.dateTime print date
private[tournament] def scheduleJson(s: Schedule) = Json.obj(
diff --git a/modules/tournament/src/main/TournamentApi.scala b/modules/tournament/src/main/TournamentApi.scala
index e9db0e1ebd..38a1e5c27f 100644
--- a/modules/tournament/src/main/TournamentApi.scala
+++ b/modules/tournament/src/main/TournamentApi.scala
@@ -36,6 +36,7 @@ final class TournamentApi(
verify: Condition.Verify,
indexLeaderboard: Tournament => Funit,
asyncCache: lila.memo.AsyncCache.Builder,
+ lightUserApi: lila.user.LightUserApi,
standingChannel: ActorRef
) {
@@ -330,7 +331,7 @@ final class TournamentApi(
private val miniStandingCache = asyncCache.multi[String, List[RankedPlayer]](
name = "tournament.miniStanding",
- id => PlayerRepo.bestByTourWithRank(id, 30),
+ id => PlayerRepo.bestByTourWithRank(id, 20),
expireAfter = _.ExpireAfterWrite(3 second)
)
@@ -430,10 +431,12 @@ final class TournamentApi(
import lila.hub.EarlyMultiThrottler
- private def publishNow(tourId: Tournament.ID) = fuccess {
- standingChannel ! lila.socket.Channel.Publish(
- lila.socket.Socket.makeMessage("tournamentStanding", tourId)
- )
+ private def publishNow(tourId: Tournament.ID) = miniStanding(tourId, true) map {
+ _ ?? { m =>
+ standingChannel ! lila.socket.Channel.Publish(
+ lila.socket.Socket.makeMessage("tourStanding", JsonView.miniStanding(m, lightUserApi.sync))
+ )
+ }
}
private val throttler = system.actorOf(Props(new EarlyMultiThrottler(logger = logger)))
diff --git a/public/stylesheets/board.css b/public/stylesheets/board.css
index 80cce4ab59..6237560d62 100644
--- a/public/stylesheets/board.css
+++ b/public/stylesheets/board.css
@@ -938,39 +938,40 @@ div.game_tournament {
max-height: 300px;
overflow: hidden;
}
-div.game_tournament:hover {
- overflow-y: auto;
-}
div.game_tournament .clock {
text-align: center;
font-size: 20px;
font-family: 'Roboto Mono', 'Roboto';
margin: 10px 0;
}
-div.game_tournament table.standing {
+div.tourStanding table {
border-bottom: none;
}
-div.game_tournament td {
+div.tourStanding:hover {
+ overflow-y: auto!important;
+}
+div.tourStanding td {
padding: 0 10px;
text-align: left;
- line-height: 2em;
+ line-height: 1.8em;
max-width: 150px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
-div.game_tournament table.slist td:first-child {
+div.tourStanding .slist td:first-child {
padding-left: 10px;
}
-div.game_tournament tr.me td:first-child {
- border-left: 10px solid #d59120;
- padding-left: 5px;
-}
-div.game_tournament td.name span {
+div.tourStanding .name span {
display: inline-block;
width: 20px;
}
-div.game_tournament td.total {
+div.tourStanding .name span::before {
+ font-size: 0.8em;
+ opacity: 0.4;
+}
+div.tourStanding .total {
+ font-weight: bold;
text-align: right;
}
span.inline_userlist {
diff --git a/public/stylesheets/chat.css b/public/stylesheets/chat.css
index d44f12f68b..9863a51733 100644
--- a/public/stylesheets/chat.css
+++ b/public/stylesheets/chat.css
@@ -37,7 +37,7 @@ div.mchat .chat_tabs .tab.active {
div.mchat .chat_tabs .tab input {
cursor: pointer;
}
-div.mchat .chat_tabs .tab.discussion {
+div.mchat.optional .chat_tabs .tab.discussion {
display: flex;
justify-content: space-between;
align-items: center;
diff --git a/ui/chat/src/ctrl.ts b/ui/chat/src/ctrl.ts
index 2daea11810..f453ed6950 100644
--- a/ui/chat/src/ctrl.ts
+++ b/ui/chat/src/ctrl.ts
@@ -14,8 +14,9 @@ export default function(opts: ChatOpts, redraw: Redraw): Ctrl {
let moderation: ModerationCtrl | undefined;
const vm: ViewModel = {
- tab: 'discussion',
- enabled: !li.storage.get('nochat'),
+ // tab: 'discussion',
+ tab: 'tourStanding',
+ enabled: opts.alwaysEnabled || !li.storage.get('nochat'),
placeholderKey: 'talkInChat',
loading: false,
timeout: opts.timeout,
@@ -105,14 +106,15 @@ export default function(opts: ChatOpts, redraw: Redraw): Ctrl {
opts,
vm,
setTab(t: Tab) {
- vm.tab = t
- redraw()
+ vm.tab = t;
+ redraw()
},
moderation: () => moderation,
note,
preset,
post,
trans,
+ plugin: opts.plugin,
setEnabled(v: boolean) {
vm.enabled = v;
emitEnabled();
diff --git a/ui/chat/src/interfaces.ts b/ui/chat/src/interfaces.ts
index 20b554c25e..b7e62d8579 100644
--- a/ui/chat/src/interfaces.ts
+++ b/ui/chat/src/interfaces.ts
@@ -1,3 +1,5 @@
+import { VNode } from 'snabbdom/vnode'
+
export interface ChatOpts {
data: ChatData
writeable: boolean
@@ -11,12 +13,16 @@ export interface ChatOpts {
preset?: string
noteId?: string
loadCss: (url: string) => void
- extra?: ExtraTab
+ plugin?: ChatPlugin
+ alwaysEnabled: boolean;
}
-export interface ExtraTab {
- name: string; // i18n key
- content: string; // HTML
+export interface ChatPlugin {
+ tab: {
+ key: string;
+ name: string;
+ }
+ view(): VNode;
}
export interface ChatData {
@@ -42,7 +48,7 @@ export interface Permissions {
shadowban?: boolean
}
-export type Tab = 'discussion' | 'note' | 'extra';
+export type Tab = string;
export interface Ctrl {
data: ChatData
@@ -55,6 +61,7 @@ export interface Ctrl {
trans: Trans
setTab(tab: Tab): void
setEnabled(v: boolean): void
+ plugin?: ChatPlugin
}
export interface ViewModel {
diff --git a/ui/chat/src/main.ts b/ui/chat/src/main.ts
index 830e420f61..ac1aced678 100644
--- a/ui/chat/src/main.ts
+++ b/ui/chat/src/main.ts
@@ -10,6 +10,8 @@ import { ChatOpts, Ctrl, PresetCtrl } from './interfaces'
import klass from 'snabbdom/modules/class';
import attributes from 'snabbdom/modules/attributes';
+export { ChatPlugin } from './interfaces';
+
export default function LichessChat(element: Element, opts: ChatOpts): {
preset: PresetCtrl
} {
diff --git a/ui/chat/src/view.ts b/ui/chat/src/view.ts
index 99a93af8c4..f809cd9dd0 100644
--- a/ui/chat/src/view.ts
+++ b/ui/chat/src/view.ts
@@ -10,7 +10,9 @@ export default function(ctrl: Ctrl): VNode {
const mod = ctrl.moderation();
- return h('div#chat.side_box.mchat', {
+ console.log(ctrl);
+
+ return h('div#chat.side_box.mchat' + (ctrl.opts.alwaysEnabled ? '' : '.optional'), {
class: {
mod: !!mod
}
@@ -21,12 +23,12 @@ function normalView(ctrl: Ctrl) {
const active = ctrl.vm.tab;
const tabs: Array = ['discussion'];
if (ctrl.note) tabs.push('note');
- if (ctrl.opts.extra) tabs.push('extra');
+ if (ctrl.plugin) tabs.push(ctrl.plugin.tab.key);
return [
h('div.chat_tabs.nb_' + tabs.length, tabs.map(t => renderTab(ctrl, t, active))),
h('div.content.' + active,
(active === 'note' && ctrl.note) ? [noteView(ctrl.note)] : (
- active === 'extra' ? [extraView(ctrl)] : discussionView(ctrl)
+ ctrl.plugin && active === ctrl.plugin.tab.key ? [ctrl.plugin.view()] : discussionView(ctrl)
))
]
}
@@ -41,7 +43,7 @@ function renderTab(ctrl: Ctrl, tab: Tab, active: Tab) {
function tabName(ctrl: Ctrl, tab: Tab) {
if (tab === 'discussion') return [
h('span', ctrl.data.name),
- h('input.toggle_chat', {
+ ctrl.opts.alwaysEnabled ? undefined : h('input.toggle_chat', {
attrs: {
type: 'checkbox',
title: ctrl.trans.noarg('toggleTheChat'),
@@ -53,5 +55,5 @@ function tabName(ctrl: Ctrl, tab: Tab) {
})
];
if (tab === 'note') return ctrl.trans.noarg('notes');
- if (tab === 'extra') return ctrl.trans.noarg(ctrl.opts.extra!.name);
+ if (ctrl.plugin && tab === ctrl.plugin.tab.key) return ctrl.plugin.tab.name;
}
diff --git a/ui/round/src/boot.ts b/ui/round/src/boot.ts
index ce97bb9469..9f417cea85 100644
--- a/ui/round/src/boot.ts
+++ b/ui/round/src/boot.ts
@@ -1,5 +1,6 @@
import { RoundOpts, RoundData } from './interfaces';
import { RoundApi, RoundMain } from './main';
+import { tourStandingCtrl, TourStandingData } from './tourStanding';
const li = window.lichess;
@@ -39,14 +40,8 @@ export default function(opts: RoundOpts, element: HTMLElement): void {
}
});
},
- tournamentStanding(id: string) {
- if (data.tournament && id === data.tournament.id) $.ajax({
- url: '/tournament/' + id + '/game-standing',
- success: function(html) {
- $('#site_header div.game_tournament').replaceWith(html);
- startTournamentClock();
- }
- });
+ tourStanding(data: TourStandingData) {
+ console.log(data);
}
}
});
@@ -66,7 +61,7 @@ export default function(opts: RoundOpts, element: HTMLElement): void {
};
opts.element = element.querySelector('.round') as HTMLElement;
opts.socketSend = li.socket.send;
- opts.onChange = (d: RoundData) => {
+ if (!opts.tour) opts.onChange = (d: RoundData) => {
if (chat) chat.preset.setGroup(getPresetGroup(d));
};
opts.crosstableEl = element.querySelector('.crosstable') as HTMLElement;
@@ -75,8 +70,14 @@ export default function(opts: RoundOpts, element: HTMLElement): void {
function letsGo() {
round = (window['LichessRound'] as RoundMain).app(opts);
if (opts.chat) {
- opts.chat.preset = getPresetGroup(opts.data);
- opts.chat.parseMoves = true;
+ if (opts.tour) {
+ opts.chat.plugin = tourStandingCtrl(opts.tour, opts.i18n.standing);
+ console.log(opts);
+ opts.chat.alwaysEnabled = true;
+ } else {
+ opts.chat.preset = getPresetGroup(opts.data);
+ opts.chat.parseMoves = true;
+ }
li.makeChat('chat', opts.chat, function(c) {
chat = c;
});
diff --git a/ui/round/src/interfaces.ts b/ui/round/src/interfaces.ts
index e01183f494..b9b4afc8d6 100644
--- a/ui/round/src/interfaces.ts
+++ b/ui/round/src/interfaces.ts
@@ -2,6 +2,8 @@ import { VNode } from 'snabbdom/vnode';
import { GameData, Status } from 'game';
import { ClockData, Seconds, Centis } from './clock/clockCtrl';
import { CorresClockData } from './corresClock/corresClockCtrl';
+import { TourStandingData } from './tourStanding';
+import { ChatPlugin } from 'chat';
import * as cg from 'chessground/types';
export type MaybeVNode = VNode | null | undefined;
@@ -80,11 +82,14 @@ export interface RoundOpts {
crosstableEl: HTMLElement;
i18n: any;
chat?: Chat;
+ tour?: TourStandingData;
}
export interface Chat {
preset: 'start' | 'end' | null;
parseMoves?: boolean;
+ plugin?: ChatPlugin;
+ alwaysEnabled: boolean;
}
export interface Step {
diff --git a/ui/round/src/tourStanding.ts b/ui/round/src/tourStanding.ts
new file mode 100644
index 0000000000..717121166e
--- /dev/null
+++ b/ui/round/src/tourStanding.ts
@@ -0,0 +1,46 @@
+import { h } from 'snabbdom'
+import { VNode } from 'snabbdom/vnode'
+import { ChatPlugin } from 'chat'
+import { justIcon } from './util'
+
+export interface TourStandingData {
+ standing: RankedPlayer[]
+}
+
+interface RankedPlayer {
+ name: string;
+ rank: number;
+ score: number;
+ title?: string;
+ fire: boolean;
+ withdraw: boolean;
+}
+
+export function tourStandingCtrl(data: TourStandingData, name: string): ChatPlugin {
+ return {
+ tab: {
+ key: 'tourStanding',
+ name: name
+ },
+ view(): VNode {
+ return h('table.slist',
+ h('tbody', data.standing.map(p => {
+ return h('tr.' + p.name, [
+ h('td.name', [
+ p.withdraw ? h('span', justIcon('Z')) : h('span.rank', '' + p.rank),
+ h('a.user_link.ulpt', {
+ attrs: {
+ href: `/@/${p.name}`
+ }
+ }, (p.title ? p.title + ' ' : '') + p.name)
+ ]),
+ h('td.total', p.fire ? {
+ class: { 'is-gold': true },
+ attrs: { 'data-icon': 'Q' }
+ } : {}, '' + p.score)
+ ])
+ }))
+ );
+ }
+ };
+}