more chat rewrite WIP

This commit is contained in:
Thibault Duplessis 2016-06-15 10:49:05 +02:00
parent 1f8528f8c7
commit 227de5cb9c
18 changed files with 85 additions and 286 deletions

View file

@ -57,9 +57,10 @@ object Round extends LilaController with TheftPrevention {
PreventTheft(pov) {
myTour(pov.game.tournamentId, true) zip
(pov.game.simulId ?? Env.simul.repo.find) zip
getPlayerChat(pov.game) zip
Env.game.crosstableApi(pov.game) zip
(pov.game.isSwitchable ?? otherPovs(pov.game)) flatMap {
case (((tour, simul), crosstable), playing) =>
case ((((tour, simul), chatOption), crosstable), playing) =>
simul foreach Env.simul.api.onPlayerConnection(pov.game, ctx.me)
Env.api.roundApi.player(pov, lila.api.Mobile.Api.currentVersion) map { data =>
Ok(html.round.player(pov, data,
@ -67,7 +68,7 @@ object Round extends LilaController with TheftPrevention {
simul = simul,
cross = crosstable,
playing = playing,
prefs = ctx.isAuth option (Env.pref.forms miniPrefOf ctx.pref)))
chatOption = chatOption))
}
}
}.mon(_.http.response.player.website),
@ -189,8 +190,11 @@ object Round extends LilaController with TheftPrevention {
}
private[controllers] def getWatcherChat(game: GameModel)(implicit ctx: Context) =
ctx.me ?? { me =>
Env.chat.api.userChat.findMine(s"${game.id}/w", me) map (_.some)
Env.chat.api.userChat.findMine(s"${game.id}/w", ctx.me)
private[controllers] def getPlayerChat(game: GameModel)(implicit ctx: Context) =
game.hasChat ?? {
Env.chat.api.playerChat.find(game.id) map (_.some)
}
def playerText(fullId: String) = Open { implicit ctx =>

View file

@ -40,7 +40,7 @@ object Simul extends LilaController {
_.fold(simulNotFound.fuccess) { sim =>
env.version(sim.id) zip
env.jsonView(sim) zip
chatOf(sim) map {
Env.chat.api.userChat.findMine(sim.id, ctx.me) map {
case ((version, data), chat) => html.simul.show(sim, version, data, chat)
}
}
@ -120,11 +120,6 @@ object Simul extends LilaController {
}
}
private def chatOf(sim: Sim)(implicit ctx: Context): Fu[Option[lila.chat.UserChat.Mine]] =
ctx.me ?? { me =>
Env.chat.api.userChat.findMine(sim.id, me) map (_.some)
}
private def AsHost(simulId: Sim.ID)(f: Sim => Result)(implicit ctx: Context): Fu[Result] =
env.repo.find(simulId) flatMap {
case None => notFound

View file

@ -53,7 +53,9 @@ object Tournament extends LilaController {
negotiate(
html = repo byId id flatMap {
_.fold(tournamentNotFound.fuccess) { tour =>
env.version(tour.id).zip(chatOf(tour)).flatMap {
env.version(tour.id).zip {
Env.chat.api.userChat.findMine(tour.id, ctx.me)
}.flatMap {
case (version, chat) => env.jsonView(tour, page, ctx.userId, none, version.some) map {
html.tournament.show(tour, _, chat)
}
@ -177,9 +179,4 @@ object Tournament extends LilaController {
env.socketHandler.join(id, uid, ctx.me)
}
}
private def chatOf(tour: lila.tournament.Tournament)(implicit ctx: Context) =
ctx.me ?? { me =>
Env.chat.api.userChat.findMine(tour.id, me) map (_.some)
}
}

View file

@ -1,4 +1,4 @@
@(pov: Pov, data: play.api.libs.json.JsObject, initialFen: Option[String], pgn: String, analysis: Option[lila.analyse.Analysis], analysisInProgress: Option[lila.fishnet.Work.InProgress], simul: Option[lila.simul.Simul], cross: Option[lila.game.Crosstable], userTv: Option[User], myChat: Option[lila.chat.UserChat.Mine])(implicit ctx: Context)
@(pov: Pov, data: play.api.libs.json.JsObject, initialFen: Option[String], pgn: String, analysis: Option[lila.analyse.Analysis], analysisInProgress: Option[lila.fishnet.Work.InProgress], simul: Option[lila.simul.Simul], cross: Option[lila.game.Crosstable], userTv: Option[User], myChat: lila.chat.UserChat.Mine)(implicit ctx: Context)
@import pov._
@ -22,9 +22,7 @@ tablebaseEndpoint: "@tablebaseEndpoint"
}
};
}
@myChat.map { c =>
@chat.js(c, name = trans.spectatorRoom.str(), withNote = true)
}
@chat.js(myChat.chat, name = trans.spectatorRoom.str(), timeout = myChat.timeout, withNote = true)
}
@atom = {
@ -53,7 +51,7 @@ tablebaseEndpoint: "@tablebaseEndpoint"
@analyse.layout(
title = title,
side = views.html.game.side(pov, initialFen, none, simul = simul, userTv = userTv).some,
chat = chat.dom(myChat).some,
chat = chat.dom().some,
underchat = underchat.some,
moreCss = moreCss,
moreJs = moreJs,

View file

@ -1,5 +1,3 @@
@(c: Option[lila.chat.UserChat.Mine])(implicit ctx: Context)
@()
@if(c.isDefined) {
<div id="chat" class="side_box"></div>
}

View file

@ -1,16 +1,17 @@
@(c: lila.chat.UserChat.Mine, name: String, withNote: Boolean = false, writeable: Boolean = true)(implicit ctx: Context)
@(chat: lila.chat.AnyChat, name: String, timeout: Boolean, withNote: Boolean = false, writeable: Boolean = true)(implicit ctx: Context)
@embedJs {
lichess.makeChat('chat', {
lines: @Html(J.stringify(lila.chat.JsonView(c.chat))),
lines: @Html(J.stringify(lila.chat.JsonView(chat))),
i18n: @jsI18n(withNote = withNote),
id: "@c.chat.id",
id: "@chat.id",
name: "@name",
userId: @Html(ctx.userId.fold("null")(id => s""""$id"""")),
loginRequired: @chat.loginRequired,
writeable: @writeable,
noteId: @if(withNote) {"@c.chat.id.take(8)"} else {null},
noteId: @if(withNote) {"@chat.id.take(8)"} else {null},
kobold: @ctx.troll,
mod: @isGranted(_.MarkTroll),
timeout: @c.timeout,
timeout: @timeout,
timeoutReasons: @if(isGranted(_.MarkTroll)) { @Html(J.stringify(lila.chat.JsonView.timeoutReasons)) } else { null }
});
}

View file

@ -1,4 +1,4 @@
@(pov: Pov, data: play.api.libs.json.JsObject, tour: Option[lila.tournament.MiniStanding], simul: Option[lila.simul.Simul], cross: Option[lila.game.Crosstable], playing: List[Pov], prefs: Option[Form[_]])(implicit ctx: Context)
@(pov: Pov, data: play.api.libs.json.JsObject, tour: Option[lila.tournament.MiniStanding], simul: Option[lila.simul.Simul], cross: Option[lila.game.Crosstable], playing: List[Pov], chatOption: Option[lila.chat.MixedChat])(implicit ctx: Context)
@import pov._
@ -15,6 +15,9 @@ userId: @jsUserId
});
});
}
@chatOption.map { c =>
@chat.js(c, name = trans.chatRoom.str(), timeout = false, withNote = true)
}
}
@atom = {
@ -24,7 +27,7 @@ userId: @jsUserId
@round.layout(
title = title,
side = views.html.game.side(pov, (data\"game"\"initialFen").asOpt[String], tour, simul),
chat = game.tournamentId.isEmpty.option(leftabs(pov, prefs)),
chat = chatOption.isDefined.option(chat.dom()),
underchat = views.html.game.watchers().some,
moreJs = moreJs,
openGraph = povOpenGraph(pov).some,

View file

@ -1,4 +1,4 @@
@(pov: Pov, data: play.api.libs.json.JsObject, tour: Option[lila.tournament.MiniStanding], simul: Option[lila.simul.Simul], cross: Option[lila.game.Crosstable], userTv: Option[User] = None, myChat: Option[lila.chat.UserChat.Mine])(implicit ctx: Context)
@(pov: Pov, data: play.api.libs.json.JsObject, tour: Option[lila.tournament.MiniStanding], simul: Option[lila.simul.Simul], cross: Option[lila.game.Crosstable], userTv: Option[User] = None, myChat: lila.chat.UserChat.Mine)(implicit ctx: Context)
@title = @{ s"${gameVsText(pov.game, withRatings = true)} in ${pov.gameId}" }
@ -12,9 +12,7 @@ i18n: @jsI18n()
});
});
}
@myChat.map { c =>
@chat.js(c, name = trans.spectatorRoom.str(), withNote = true)
}
@chat.js(myChat.chat, name = trans.spectatorRoom.str(), timeout = myChat.timeout, withNote = true)
}
@atom = {
@ -24,7 +22,7 @@ i18n: @jsI18n()
@round.layout(
title = title,
side = views.html.game.side(pov, (data\"game"\"initialFen").asOpt[String], tour, simul = simul, userTv = userTv),
chat = chat.dom(myChat).some,
chat = chat.dom().some,
underchat = views.html.game.watchers().some,
moreJs = moreJs,
openGraph = povOpenGraph(pov).some,

View file

@ -1,4 +1,4 @@
@(sim: lila.simul.Simul, socketVersion: Int, data: play.api.libs.json.JsObject, myChat: Option[lila.chat.UserChat.Mine])(implicit ctx: Context)
@(sim: lila.simul.Simul, socketVersion: Int, data: play.api.libs.json.JsObject, myChat: lila.chat.UserChat.Mine)(implicit ctx: Context)
@underchat = {
<div class="watchers none" data-icon="v">
@ -16,16 +16,14 @@ socketVersion: @socketVersion,
userId: @jsUserId
};
}
@myChat.map { c =>
@chat.js(c, name = trans.chatRoom.str())
}
@chat.js(myChat.chat, name = trans.chatRoom.str(), timeout = myChat.timeout)
}
@simul.layout(
title = sim.fullName,
side = simul.side(sim).some,
underchat = underchat.some,
chat = chat.dom(myChat).some,
chat = chat.dom().some,
moreJs = moreJs,
chessground = false) {
<div id="simul"></div>

View file

@ -1,4 +1,4 @@
@(title: String, side: Html, chat: Html, underchat: Html, moreJs: Html)(body: Html)(implicit ctx: Context)
@(title: String, side: Html, underchat: Html, moreJs: Html)(body: Html)(implicit ctx: Context)
@moreCss = {
@cssTag("analyse.css")
@ -8,7 +8,7 @@
@base.layout(
title = title,
side = side.some,
chat = chat.some,
chat = chat.dom().some,
underchat = underchat.some,
moreCss = moreCss,
moreJs = moreJs,

View file

@ -17,7 +17,7 @@ socketUrl: "@routes.Study.websocket(s.id, apiVersion)",
socketVersion: @socketVersion
};
}
@chat.js(myChat, name = trans.chatRoom.str(), writeable = ctx.userId.??(s.canChat))
@chat.js(myChat.chat, name = trans.chatRoom.str(), timeout = myChat.timeout, writeable = ctx.userId.??(s.canChat))
}
@side = {
@ -33,7 +33,6 @@ socketVersion: @socketVersion
@study.layout(
title = s.name,
side = side,
chat = chat.dom(myChat.some),
underchat = underchat,
moreJs = moreJs) {
<div class="analyse cg-512">@miniBoardContent</div>

View file

@ -1,4 +1,4 @@
@(tour: Tournament, data: play.api.libs.json.JsObject, myChat: Option[lila.chat.UserChat.Mine])(implicit ctx: Context)
@(tour: Tournament, data: play.api.libs.json.JsObject, myChat: lila.chat.UserChat.Mine)(implicit ctx: Context)
@underchat = {
<div class="watchers none" data-icon="v">
@ -16,15 +16,13 @@ i18n: @jsI18n(),
userId: @jsUserId
};
}
@myChat.map { c =>
@chat.js(c, name = trans.chatRoom.str())
}
@chat.js(myChat.chat, name = trans.chatRoom.str(), timeout = myChat.timeout)
}
@tournament.layout(
title = s"${tour.fullName} #${tour.id}",
side = tournament.side(tour).some,
chat = chat.dom(myChat).some,
chat = chat.dom().some,
underchat = underchat.some,
moreJs = moreJs,
chessground = false,

View file

@ -6,6 +6,8 @@ sealed trait AnyChat {
def id: ChatId
def lines: List[Line]
val loginRequired: Boolean
def forUser(u: Option[User]): AnyChat
def isEmpty = lines.isEmpty
@ -21,6 +23,8 @@ case class UserChat(
id: ChatId,
lines: List[UserLine]) extends Chat[UserLine] {
val loginRequired = true
def forUser(u: Option[User]) = u.??(_.troll).fold(this,
copy(lines = lines filterNot (_.troll)))
@ -40,6 +44,8 @@ case class MixedChat(
id: ChatId,
lines: List[Line]) extends Chat[Line] {
val loginRequired = false
def forUser(u: Option[User]) = u.??(_.troll).fold(this,
copy(lines = lines filter {
case l: UserLine => !l.troll

View file

@ -180,9 +180,6 @@ lichess.notifyApp = (function() {
following_leaves: function(name) {
$('#friend_box').friends('leaves', name);
},
message: function(msg) {
$('#chat').chat("append", msg);
},
new_notification: function(e) {
var notification = e.notification;
@ -1055,7 +1052,6 @@ lichess.notifyApp = (function() {
}
}
});
var $chat;
cfg.element = element.querySelector('.round');
cfg.socketSend = lichess.socket.send;
cfg.onChange = data.player.spectator ? $.noop : function(data) {
@ -1066,15 +1062,10 @@ lichess.notifyApp = (function() {
else if (data.game.status.id >= 30) presets = [
'gg/Good game', 'wp/Well played', 'ty/Thank you', 'gtg/I\'ve got to go', 'bye/Bye!'
];
$chat.chat('setPresets', presets);
lichess.pubsub.emit('chat.presets', presets);
};
round = LichessRound(cfg);
$('.crosstable', element).prependTo($('.underboard .center', element)).show();
$chat = $('#chat').chat({
messages: data.chat,
initialNote: data.note,
gameId: data.game.id
});
var $watchers = $('#site_header div.watchers').watchers();
var $nowPlaying = $('#now_playing');
startTournamentClock();
@ -1162,157 +1153,6 @@ lichess.notifyApp = (function() {
}
});
lichess.widget("chat", {
_create: function() {
this.options = $.extend({
messages: [],
writeable: true,
initialNote: '',
gameId: null,
presets: [],
presetCount: 0
}, this.options);
var self = this;
var $parent = self.element.parent();
self.$msgs = self.element.find('.messages');
self.withMsgs = !!self.$msgs.length;
if (self.withMsgs) {
self.$msgs.on('click', 'a', function() {
$(this).attr('target', '_blank');
});
var $form = self.element.find('form');
var $input = self.element.find('input.lichess_say');
self.options.placeholder = $input.attr('placeholder');
self.writeable(self.options.writeable);
// send a message
$form.submit(function() {
var text = $.trim($input.val());
if (!text) return false;
if (text.length > 140) {
alert('Max length: 140 chars. ' + text.length + ' chars used.');
return false;
}
$input.val('');
lichess.socket.send('talk', text);
return false;
});
self.element.find('a.send').click(function() {
$input.trigger('click');
$form.submit();
});
// toggle the chat
var $toggle = $parent.find('input.toggle_chat');
$toggle.change(function() {
var enabled = $toggle.is(':checked');
self.element.toggleClass('hidden', !enabled);
if (!enabled) lichess.storage.set('nochat', 1);
else lichess.storage.remove('nochat');
});
$toggle[0].checked = lichess.storage.get('nochat') != 1;
if (!$toggle[0].checked) {
self.element.addClass('hidden');
}
if (self.options.messages.length > 0) self._appendMany(self.options.messages);
self.element.on('click', '.presets button', function() {
$input.val($(this).data('hint'));
$form.submit();
if (++self.options.presetCount >= 2) self.element.find('.presets').remove();
$input.focus();
});
self._renderPresets();
}
$panels = self.element.find('div.chat_panels > div');
$menu = $parent.find('.chat_menu');
$menu.on('click', 'a', function() {
var panel = $(this).data('panel');
$(this).siblings('.active').removeClass('active').end().addClass('active');
$panels.removeClass('active').filter('.' + panel).addClass('active');
}).find('a[data-panel=preferences]').one('click', function() {
self.element.find('.preferences form').each(function() {
var $form = $(this);
$form.find('group.radio').removeClass('radio');
$form.find('input').change(function() {
$.ajax({
url: $form.attr('action'),
method: $form.attr('method'),
data: $form.serialize()
});
});
});
});
$menu.find('a:first').click();
$notes = self.element.find('.notes textarea');
if (self.options.gameId && $notes.length) {
$notes.on('change keyup paste', $.fp.debounce(function() {
$.post('/' + self.options.gameId + '/note', {
text: $notes.val()
});
}, 1000));
$notes.val(self.options.initialNote || '');
}
},
writeable: function(v) {
this.options.writeable = v;
this.element.find('input.lichess_say')
.prop('disabled', !v)
.prop('placeholder', v ? this.options.placeholder : 'Invited members only');
},
append: function(msg) {
this._appendHtml(this._render(msg));
},
setPresets: function(presets) {
if (presets.join('|') === this.options.presets.join('|')) return;
this.options.presetCount = 0;
this.options.presets = presets;
this._renderPresets();
},
_renderPresets: function() {
var $e = this.element.find('.messages_container');
$e.find('.presets').remove();
if (!this.options.presets.length) return;
$e.append($('<div>').addClass('presets').html(
this.options.presets.map(function(p) {
var s = p.split('/');
return '<button class="button hint--top thin" data-hint="' + s[1] + '">' + s[0] + '</button>';
}).join('')
));
},
_appendMany: function(objs) {
var self = this,
html = "";
$.each(objs, function() {
html += self._render(this);
});
self._appendHtml(html);
},
_render: function(msg) {
var user, sys = false;
if (msg.c) {
user = '<span class="color">[' + msg.c + ']</span>';
} else if (msg.u == 'lichess') {
sys = true;
user = '<span class="system"></span>';
} else {
user = $.userLinkLimit(msg.u, 14);
}
return '<li class="' + (sys ? 'system trans_me' : '') + (msg.r ? ' troll' : '') + '">' + user + $.urlToLink(msg.t) + '</li>';
},
_appendHtml: function(html) {
if (!html) return;
this.$msgs.each(function(i, el) {
var autoScroll = (el.scrollTop == 0 || (el.scrollTop > (el.scrollHeight - el.clientHeight - 50)));
$(el).append(html);
if (autoScroll) el.scrollTop = 999999;
});
$('body').trigger('lichess.content_loaded');
}
});
lichess.widget("clock", {
_create: function() {
var self = this;
@ -1916,11 +1756,6 @@ lichess.notifyApp = (function() {
function startAnalyse(element, cfg) {
var data = cfg.data;
if (data.chat) $('#chat').chat({
messages: data.chat,
initialNote: data.note,
gameId: data.game.id
});
var $watchers = $('#site_header div.watchers').watchers();
var analyse, $panels;
lichess.socket = lichess.StrongSocket(

View file

@ -3,6 +3,9 @@ div.mchat {
display: flex;
flex-flow: column nowrap;
}
body.kid div.mchat {
display: none;
}
div.mchat .chat_tabs {
display: flex;
}

View file

@ -670,69 +670,6 @@ div.side_box .padded {
width: 196px!important;
}
}
div.chat_menu {
margin-left: -30px;
margin-top: 12px;
margin-bottom: -12px;
text-align: center;
white-space: nowrap;
}
body.kid div.chat_menu,
body.kid #chat {
display: none;
}
div.chat_menu > a {
background: #f0f0f0;
border: 1px solid #c0c0c0;
height: 20px;
padding: 3px 8px 0 8px;
margin: -1px 0 -1px -5px;
display: inline-block;
}
div.chat_menu > a.active {
background-color: #eee;
border-bottom: 1px solid #eee;
}
div.chat_panels {
height: 325px;
}
.is3d div.chat_panels {
height: 290px;
}
div.chat_panels > * {
display: none;
}
div.chat_panels textarea {
resize: none;
height: calc(100% + 10px);
width: calc(100% - 12px);
padding: 0.5em;
border: none;
background-color: transparent;
}
div.chat_panels div.active {
display: block;
height: calc(100% - 22px);
}
div.chat_panels .preferences {
padding: 8px;
-webkit-user-select: none;
-moz-user-select: none;
overflow-y: auto;
}
div.chat_panels .preferences form > div {
margin-bottom: 15px;
}
div.chat_panels .preferences label {
cursor: pointer;
}
div.chat_panels .preferences input {
vertical-align: middle;
}
div.chat_panels .preferences a.prefs {
display: block;
text-align: center;
}
.notification .message_infos,
.notification .game_infos,
div.side_box .game_infos {
@ -1593,7 +1530,6 @@ table.slist thead {
text-shadow: 0 1px 0 #FFF;
color: #848484;
}
#chat div.top,
.undertable_top,
.button,
a.button,

View file

@ -7,7 +7,9 @@ module.exports = function(opts) {
var data = {
id: opts.id,
name: opts.name,
lines: opts.lines
lines: opts.lines,
userId: opts.userId,
loginRequired: opts.loginRequired
};
var vm = {
@ -19,7 +21,8 @@ module.exports = function(opts) {
placeholderKey: 'talkInChat',
moderating: m.prop(null),
tab: m.prop('discussion'),
loading: m.prop(false)
loading: m.prop(false),
presets: m.prop([])
};
var trans = lichess.trans(opts.i18n);
@ -28,12 +31,12 @@ module.exports = function(opts) {
data.lines.forEach(function(l) {
if (l.u === username) l.d = true;
});
if (username.toLowerCase() === opts.userId) vm.isTimeout(true);
if (username.toLowerCase() === data.userId) vm.isTimeout(true);
m.redraw();
};
var onReinstate = function(userId) {
if (userId === opts.userId) {
if (userId === data.userId) {
vm.isTimeout(false);
m.redraw();
}
@ -50,7 +53,7 @@ module.exports = function(opts) {
send: lichess.pubsub.emit('socket.send')
}) : null;
var note = opts.noteId ? makeNote({
var note = data.userId && opts.noteId ? makeNote({
id: opts.noteId,
trans: trans
}) : null;
@ -60,10 +63,18 @@ module.exports = function(opts) {
m.redraw();
};
var setPresets = function(p) {
if (p.toString() !== vm.presets().toString()) {
vm.presets(p);
m.redraw();
}
};
lichess.pubsub.on('socket.in.message', onMessage);
lichess.pubsub.on('socket.in.chat_timeout', onTimeout);
lichess.pubsub.on('socket.in.chat_reinstate', onReinstate);
lichess.pubsub.on('chat.writeable', setWriteable);
lichess.pubsub.on('chat.presets', setPresets);
return {
data: data,

View file

@ -29,6 +29,10 @@ function selectLines(lines) {
}
function input(ctrl) {
if (ctrl.data.loginRequired && !ctrl.data.userId) return m('input.lichess_say', {
placeholder: 'Login to chat',
disabled: true
});
var placeholder;
if (ctrl.vm.isTimeout()) placeholder = 'You have been timed out.';
else if (!ctrl.vm.writeable()) placeholder = 'Invited members only.';
@ -50,6 +54,20 @@ function input(ctrl) {
})
}
function presets(ctrl) {
var ps = ctrl.vm.presets();
if (ps.length) return m('div.presets', {}, ps.map(function(p) {
var s = p.split('/');
return m('button', {
class: 'button hint--top thin',
'data-hint': s[1],
onclick: function() {
ctrl.post(s[1]);
}
}, s[0]);
}));
}
module.exports = {
view: function(ctrl) {
if (!ctrl.vm.enabled()) return null;
@ -68,7 +86,8 @@ module.exports = {
},
selectLines(ctrl.data.lines).map(renderLine(ctrl))
),
input(ctrl)
input(ctrl),
presets(ctrl)
];
}
};