homepage realtime featured game
This commit is contained in:
parent
578f1a9d9d
commit
786d395045
|
@ -18,6 +18,7 @@ object Lobby extends LilaController {
|
|||
def forumRecent = env.forum.recent
|
||||
def timelineRecent = env.timeline.entryRepo.recent
|
||||
def messageRepo = env.lobby.messageRepo
|
||||
def featured = env.game.featured
|
||||
|
||||
val home = Open { implicit ctx ⇒
|
||||
renderHome(none).fold(identity, Ok(_))
|
||||
|
@ -36,7 +37,11 @@ object Lobby extends LilaController {
|
|||
timeline = timelineRecent
|
||||
).unsafePerformIO.bimap(
|
||||
url ⇒ Redirect(url),
|
||||
preload ⇒ html.lobby.home(toJson(preload), myHook, forumRecent(ctx.me))
|
||||
preload ⇒ html.lobby.home(
|
||||
toJson(preload),
|
||||
myHook,
|
||||
forumRecent(ctx.me),
|
||||
featured.one)
|
||||
)
|
||||
|
||||
def socket = WebSocket.async[JsValue] { implicit req ⇒
|
||||
|
|
51
app/game/Featured.scala
Normal file
51
app/game/Featured.scala
Normal file
|
@ -0,0 +1,51 @@
|
|||
package lila
|
||||
package game
|
||||
|
||||
import akka.actor._
|
||||
import akka.dispatch.{ Future, Await }
|
||||
import akka.pattern.ask
|
||||
import akka.util.Duration
|
||||
import akka.util.duration._
|
||||
import akka.util.Timeout
|
||||
import play.api.Play.current
|
||||
import play.api.libs.concurrent._
|
||||
import scalaz.effects._
|
||||
|
||||
final class Featured(
|
||||
gameRepo: GameRepo) {
|
||||
|
||||
import Featured._
|
||||
|
||||
def one: Option[DbGame] = Await.result(
|
||||
actor ? GetOne mapTo manifest[Option[DbGame]],
|
||||
atMost)
|
||||
|
||||
private val atMost = 2.second
|
||||
private implicit val timeout = Timeout(atMost)
|
||||
|
||||
private val actor = Akka.system.actorOf(Props(new Actor {
|
||||
|
||||
private var oneId = none[String]
|
||||
|
||||
def receive = {
|
||||
case GetOne ⇒ sender ! getOne
|
||||
}
|
||||
|
||||
private def getOne = oneId flatMap fetch filter valid orElse {
|
||||
feature ~ { o ⇒ oneId = o map (_.id) }
|
||||
}
|
||||
|
||||
private def fetch(id: String): Option[DbGame] =
|
||||
gameRepo.game(id).unsafePerformIO
|
||||
|
||||
private def valid(game: DbGame) = true
|
||||
|
||||
private def feature: Option[DbGame] =
|
||||
gameRepo.featuredCandidates.unsafePerformIO.headOption
|
||||
}))
|
||||
}
|
||||
|
||||
object Featured {
|
||||
|
||||
case object GetOne
|
||||
}
|
|
@ -22,6 +22,9 @@ final class GameEnv(
|
|||
cached = cached,
|
||||
maxPerPage = GamePaginatorMaxPerPage)
|
||||
|
||||
lazy val featured = new Featured(
|
||||
gameRepo = gameRepo)
|
||||
|
||||
lazy val export = Export(gameRepo) _
|
||||
|
||||
lazy val listMenu = ListMenu(cached) _
|
||||
|
|
|
@ -111,11 +111,11 @@ class GameRepo(collection: MongoCollection)
|
|||
}
|
||||
|
||||
def denormalizeStarted(game: DbGame): IO[Unit] = io {
|
||||
update(idSelector(game),
|
||||
update(idSelector(game),
|
||||
$set("userIds" -> game.players.map(_.userId).flatten))
|
||||
update(idSelector(game), game.mode.rated.fold(
|
||||
$set("isRated" -> true), $unset("isRated")))
|
||||
if (game.variant.exotic) update(idSelector(game),
|
||||
if (game.variant.exotic) update(idSelector(game),
|
||||
$set("initialFen" -> (Forsyth >> game.toChess)))
|
||||
}
|
||||
|
||||
|
@ -155,6 +155,13 @@ class GameRepo(collection: MongoCollection)
|
|||
).toList.map(_.decode).flatten
|
||||
}
|
||||
|
||||
def featuredCandidates: IO[List[DbGame]] = io {
|
||||
find(Query.playable ++
|
||||
Query.clock(true) ++
|
||||
("createdAt" $gt (DateTime.now - 30.minutes))
|
||||
).toList.map(_.decode).flatten
|
||||
}
|
||||
|
||||
def count(query: DBObject): IO[Int] = io {
|
||||
super.count(query).toInt
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@ import play.api.libs.concurrent._
|
|||
import scalaz.effects._
|
||||
|
||||
import implicits.RichJs._
|
||||
import socket.{ Util, PingVersion, Quit }
|
||||
import socket.{ Util, PingVersion, Quit, LiveGames }
|
||||
import timeline.Entry
|
||||
import game.DbGame
|
||||
import security.Flood
|
||||
|
@ -41,6 +41,9 @@ final class Socket(hub: ActorRef, flood: Flood) {
|
|||
case Some("p") ⇒ e int "v" foreach { v ⇒
|
||||
hub ! PingVersion(uid, v)
|
||||
}
|
||||
case Some("liveGames") ⇒ e str "d" foreach { ids ⇒
|
||||
hub ! LiveGames(uid, ids.split(' ').toList)
|
||||
}
|
||||
case _ ⇒
|
||||
}
|
||||
} mapDone { _ ⇒
|
||||
|
|
|
@ -11,13 +11,17 @@ import play.api.libs.concurrent._
|
|||
import play.api.Play.current
|
||||
|
||||
final class MoveNotifier(
|
||||
siteHubName: String,
|
||||
countMove: () => Unit) {
|
||||
siteHubName: String,
|
||||
lobbyHubName: String,
|
||||
countMove: () ⇒ Unit) {
|
||||
|
||||
lazy val siteHubRef = Akka.system.actorFor("/user/" + siteHubName)
|
||||
lazy val hubRefs = List(siteHubName, lobbyHubName) map { name ⇒
|
||||
Akka.system.actorFor("/user/" + name)
|
||||
}
|
||||
|
||||
def apply(gameId: String, fen: String): IO[Unit] = io {
|
||||
countMove()
|
||||
siteHubRef ! Fen(gameId, fen)
|
||||
val message = Fen(gameId, fen)
|
||||
hubRefs foreach (_ ! message)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -41,6 +41,7 @@ final class RoundEnv(
|
|||
|
||||
lazy val moveNotifier = new MoveNotifier(
|
||||
siteHubName = ActorSiteHub,
|
||||
lobbyHubName = ActorLobbyHub,
|
||||
countMove = countMove)
|
||||
|
||||
lazy val socket = new Socket(
|
||||
|
|
|
@ -16,20 +16,5 @@ final class Hub(timeout: Int) extends HubActor[Member](timeout) {
|
|||
addMember(uid, Member(channel, username))
|
||||
sender ! Connected(enumerator, channel)
|
||||
}
|
||||
|
||||
case Fen(gameId, fen) ⇒ notifyFen(gameId, fen)
|
||||
|
||||
case LiveGames(uid, gameIds) ⇒ registerLiveGames(uid, gameIds)
|
||||
}
|
||||
|
||||
def notifyFen(gameId: String, fen: String) {
|
||||
val msg = makeMessage("fen", JsObject(Seq(
|
||||
"id" -> JsString(gameId),
|
||||
"fen" -> JsString(fen))))
|
||||
members.values filter (_ liveGames gameId) foreach (_.channel push msg)
|
||||
}
|
||||
|
||||
def registerLiveGames(uid: String, ids: List[String]) {
|
||||
member(uid) foreach (_ addLiveGames ids)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,17 +3,9 @@ package site
|
|||
|
||||
import socket.SocketMember
|
||||
|
||||
import scala.collection.mutable
|
||||
|
||||
case class Member(
|
||||
channel: JsChannel,
|
||||
username: Option[String]) extends SocketMember {
|
||||
|
||||
val liveGames = mutable.Set[String]()
|
||||
|
||||
def addLiveGames(ids: List[String]) {
|
||||
ids foreach liveGames.+=
|
||||
}
|
||||
}
|
||||
|
||||
case class Join(
|
||||
|
|
|
@ -16,20 +16,24 @@ abstract class HubActor[M <: SocketMember](uidTimeout: Int) extends Actor {
|
|||
// generic message handler
|
||||
def receiveGeneric: Receive = {
|
||||
|
||||
case Ping(uid) ⇒ ping(uid)
|
||||
case Ping(uid) ⇒ ping(uid)
|
||||
|
||||
case Broom ⇒ broom()
|
||||
case Broom ⇒ broom()
|
||||
|
||||
// when a member quits
|
||||
case Quit(uid) ⇒ quit(uid)
|
||||
case Quit(uid) ⇒ quit(uid)
|
||||
|
||||
case GetNbMembers ⇒ sender ! members.size
|
||||
case GetNbMembers ⇒ sender ! members.size
|
||||
|
||||
case NbMembers(nb) ⇒ pong = makePong(nb)
|
||||
case NbMembers(nb) ⇒ pong = makePong(nb)
|
||||
|
||||
case GetUsernames ⇒ sender ! usernames
|
||||
case GetUsernames ⇒ sender ! usernames
|
||||
|
||||
case SendTo(userId, msg) ⇒ sendTo(userId, msg)
|
||||
case LiveGames(uid, gameIds) ⇒ registerLiveGames(uid, gameIds)
|
||||
|
||||
case Fen(gameId, fen) ⇒ notifyFen(gameId, fen)
|
||||
|
||||
case SendTo(userId, msg) ⇒ sendTo(userId, msg)
|
||||
}
|
||||
|
||||
def receive = receiveSpecific orElse receiveGeneric
|
||||
|
@ -94,4 +98,15 @@ abstract class HubActor[M <: SocketMember](uidTimeout: Int) extends Actor {
|
|||
}
|
||||
|
||||
def usernames: Iterable[String] = members.values.map(_.username).flatten
|
||||
|
||||
def notifyFen(gameId: String, fen: String) {
|
||||
val msg = makeMessage("fen", JsObject(Seq(
|
||||
"id" -> JsString(gameId),
|
||||
"fen" -> JsString(fen))))
|
||||
members.values filter (_ liveGames gameId) foreach (_.channel push msg)
|
||||
}
|
||||
|
||||
def registerLiveGames(uid: String, ids: List[String]) {
|
||||
member(uid) foreach (_ addLiveGames ids)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,12 +2,19 @@ package lila
|
|||
package socket
|
||||
|
||||
import play.api.libs.json.JsObject
|
||||
import scala.collection.mutable
|
||||
|
||||
trait SocketMember {
|
||||
val channel: JsChannel
|
||||
val username: Option[String]
|
||||
|
||||
lazy val userId: Option[String] = username map (_.toLowerCase)
|
||||
|
||||
val liveGames = mutable.Set[String]()
|
||||
|
||||
def addLiveGames(ids: List[String]) {
|
||||
ids foreach liveGames.+=
|
||||
}
|
||||
}
|
||||
case object Close
|
||||
case object GetUsernames
|
||||
|
|
|
@ -1,15 +1,15 @@
|
|||
@(preload: String, myHook: Option[lila.lobby.Hook], forumRecent: List[lila.forum.PostView])(implicit ctx: Context)
|
||||
@(preload: String, myHook: Option[lila.lobby.Hook], forumRecent: List[lila.forum.PostView], featured: Option[DbGame])(implicit ctx: Context)
|
||||
|
||||
@chat = {
|
||||
@for(m <- ctx.me; if m.canChat) {
|
||||
<div class="lichess_chat anon_chat lobby_chat">
|
||||
<div class="lichess_chat_top">
|
||||
<span class="title">@trans.chatRoom()</span>
|
||||
<input
|
||||
data-href="@routes.Setting.set("chat")"
|
||||
<input
|
||||
data-href="@routes.Setting.set("chat")"
|
||||
data-enabled="@setting.chat.fold("true", "false")"
|
||||
title="@trans.toggleTheChat()"
|
||||
class="toggle_chat"
|
||||
title="@trans.toggleTheChat()"
|
||||
class="toggle_chat"
|
||||
type="checkbox" />
|
||||
</div>
|
||||
<div class="lichess_chat_inner">
|
||||
|
@ -25,9 +25,29 @@
|
|||
}
|
||||
}
|
||||
|
||||
@lobby.layout(title = "", chat = chat.some) {
|
||||
@underchat = {
|
||||
@featured.map { game =>
|
||||
<div class="featured_game">
|
||||
@gameFen(game, game.creator.color)
|
||||
<div class="vstext">
|
||||
@usernameWithElo(game.creator) - @usernameWithElo(game.invited)<br />
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
@baseline = {
|
||||
<span id="site_baseline">@trans.freeOnlineChess()</span>
|
||||
}
|
||||
|
||||
@base.layout(
|
||||
title = "",
|
||||
baseline = baseline.some,
|
||||
active = siteMenu.play.some,
|
||||
chat = chat.some,
|
||||
underchat = underchat.some) {
|
||||
<div id="call_boxes">
|
||||
@translationCall.map(i18n.callBox(_))
|
||||
@translationCall.map(i18n.callBox(_))
|
||||
</div>
|
||||
<div class="clearfix lichess_homepage">
|
||||
<div class="lichess_board_wrap lichess_player_white">
|
||||
|
|
|
@ -1,11 +0,0 @@
|
|||
@(title: String, chat: Option[Html] = None)(body: Html)(implicit ctx: Context)
|
||||
|
||||
@baseline = {
|
||||
<span id="site_baseline">@trans.freeOnlineChess()</span>
|
||||
}
|
||||
|
||||
@base.layout(
|
||||
title = title,
|
||||
baseline = baseline.some,
|
||||
active = siteMenu.play.some,
|
||||
chat = chat)(body)
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
@title = @{ "Chat logs" }
|
||||
|
||||
@lobby.layout(title = title) {
|
||||
@base.layout(title = title) {
|
||||
|
||||
<style type="text/css">
|
||||
table td {
|
||||
|
|
|
@ -54,7 +54,7 @@ $(function() {
|
|||
if (lichess.socket == null && $('div.server_error_box').length == 0) {
|
||||
lichess.socket = new $.websocket(lichess.socketUrl + "/socket", 0, lichess.socketDefaults);
|
||||
}
|
||||
}, 2000);
|
||||
}, 1000);
|
||||
|
||||
$('input.lichess_id_input').select();
|
||||
|
||||
|
|
|
@ -63,7 +63,10 @@ $(function() {
|
|||
parseFen();
|
||||
$('body').on('lichess.content_loaded', parseFen);
|
||||
|
||||
var socketOpened = false;
|
||||
|
||||
function registerLiveGames() {
|
||||
if (!socketOpened) return;
|
||||
var ids = [];
|
||||
$('a.mini_board.live').each(function() {
|
||||
ids.push($(this).data("live"));
|
||||
|
@ -73,7 +76,10 @@ $(function() {
|
|||
}
|
||||
}
|
||||
$('body').on('lichess.content_loaded', registerLiveGames);
|
||||
$('body').on('socket.open', registerLiveGames);
|
||||
$('body').on('socket.open', function() {
|
||||
socketOpened = true;
|
||||
registerLiveGames();
|
||||
});
|
||||
|
||||
lichess.socketDefaults.events.fen = function(e) {
|
||||
$('a.live_' + e.id).each(function() {
|
||||
|
|
|
@ -82,7 +82,6 @@ $.websocket.prototype = {
|
|||
},
|
||||
schedulePing: function(delay) {
|
||||
var self = this;
|
||||
//self.debug("schedule ping in " + delay + " ms");
|
||||
clearTimeout(self.pingSchedule);
|
||||
self.pingSchedule = setTimeout(function() {
|
||||
self.pingNow();
|
||||
|
@ -92,7 +91,6 @@ $.websocket.prototype = {
|
|||
var self = this;
|
||||
clearTimeout(self.pingSchedule);
|
||||
clearTimeout(self.connectSchedule);
|
||||
//console.debug("ping now");
|
||||
try {
|
||||
self.debug("ping " + self.pingData());
|
||||
self.ws.send(self.pingData());
|
||||
|
@ -104,7 +102,6 @@ $.websocket.prototype = {
|
|||
},
|
||||
pong: function() {
|
||||
var self = this;
|
||||
//self.debug("pong");
|
||||
clearTimeout(self.connectSchedule);
|
||||
self.schedulePing(self.options.pingDelay);
|
||||
self.currentLag = self.now() - self.lastPingTime;
|
||||
|
|
|
@ -568,8 +568,8 @@ div.lichess_cemetery div.lichess_piece {
|
|||
}
|
||||
|
||||
div.under_chat {
|
||||
width: 230px;
|
||||
margin-left: -30px;
|
||||
width: 225px;
|
||||
margin-left: -35px;
|
||||
position: absolute;
|
||||
top: 530px;
|
||||
left: 0;
|
||||
|
|
|
@ -178,7 +178,7 @@ body.dark div.side_menu a.active {
|
|||
}
|
||||
|
||||
body.dark .mini_board {
|
||||
box-shadow: 0 0 5px #000;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
body.dark div.game_row:nth-child(odd),
|
||||
|
|
|
@ -65,7 +65,7 @@ div.game_list_inner div.vstext {
|
|||
height: 192px;
|
||||
position: relative;
|
||||
display: block;
|
||||
box-shadow:0 0 5px #444;
|
||||
box-shadow:0 0 5px #666;
|
||||
}
|
||||
div.all_games a.mini_board {
|
||||
float: left;
|
||||
|
|
|
@ -184,6 +184,14 @@ div.hooks a.user_link {
|
|||
padding-left: 0;
|
||||
}
|
||||
|
||||
div.featured_game {
|
||||
margin-left: 38px;
|
||||
}
|
||||
div.featured_game div.vstext {
|
||||
margin-top: 0.7em;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
div.undertable {
|
||||
width: 514px;
|
||||
font-size: 11.5px;
|
||||
|
|
Loading…
Reference in a new issue