homepage realtime featured game

This commit is contained in:
Thibault Duplessis 2012-06-20 23:30:35 +02:00
parent 578f1a9d9d
commit 786d395045
21 changed files with 159 additions and 66 deletions

View file

@ -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
View 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
}

View file

@ -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) _

View file

@ -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
}

View file

@ -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 { _

View file

@ -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)
}
}

View file

@ -41,6 +41,7 @@ final class RoundEnv(
lazy val moveNotifier = new MoveNotifier(
siteHubName = ActorSiteHub,
lobbyHubName = ActorLobbyHub,
countMove = countMove)
lazy val socket = new Socket(

View file

@ -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)
}
}

View file

@ -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(

View file

@ -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)
}
}

View file

@ -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

View file

@ -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">

View file

@ -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)

View file

@ -2,7 +2,7 @@
@title = @{ "Chat logs" }
@lobby.layout(title = title) {
@base.layout(title = title) {
<style type="text/css">
table td {

View file

@ -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();

View file

@ -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() {

View file

@ -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;

View file

@ -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;

View file

@ -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),

View file

@ -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;

View file

@ -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;