[IMP] mail: put chat windows in a dropdown in case of overflow

Former algorithm to display chat windows was pretty basic: each window was
positionned 5 pixels to the left of the previous one, starting from the bottom
right corner of the screen. Thus, when there were too many chat windows to fit
in the screen, they overflowed to the left.

This rev. handles this case by hidding overflowing chat windows and making them
available in a dropdown positionned 5 pixels to the left of the last visible
chat window.
This commit is contained in:
Aaron Bohy
2016-01-14 11:22:25 +01:00
parent 37238d343a
commit 18fccd9d6c
4 changed files with 202 additions and 22 deletions
+15 -8
View File
@@ -29,6 +29,7 @@ return Widget.extend({
display_stars: true,
});
this.unread_msgs = unread_msgs || 0;
this.is_hidden = false;
},
start: function () {
this.$content = this.$('.o_chat_content');
@@ -50,23 +51,17 @@ return Widget.extend({
return $.when(this._super(), def);
},
render: function (messages) {
this.update_header();
this.update_unread(this.unread_msgs);
this.thread.render(messages, {display_load_more: false});
},
update_header: function () {
var title = this.unread_msgs > 0 ?
this.title + ' (' + this.unread_msgs + ')' : this.title;
this.$('.o_chat_title').text(title);
},
update_unread: function (counter) {
this.unread_msgs = counter;
this.update_header();
this.$('.o_unread_counter').text(counter > 0 ? '(' + counter + ')' : '');
},
scrollBottom: function () {
this.$content.scrollTop(this.$content[0].scrollHeight);
},
fold: function () {
this.update_header();
this.$el.animate({
height: this.folded ? HEIGHT_FOLDED : HEIGHT_OPEN
});
@@ -79,6 +74,18 @@ return Widget.extend({
}
this.fold();
},
do_show: function () {
this.is_hidden = false;
this._super.apply(this, arguments);
},
do_hide: function () {
this.is_hidden = true;
this._super.apply(this, arguments);
},
do_toggle: function (display) {
this.is_hidden = _.isBoolean(display) ? !display : !this.is_hidden;
this._super.apply(this, arguments);
},
on_keydown: function (event) {
// ENTER key (avoid requiring jquery ui for external livechat)
if (event.which === 13) {
+107 -6
View File
@@ -5,16 +5,26 @@ var chat_manager = require('mail.chat_manager');
var ChatWindow = require('mail.ChatWindow');
var core = require('web.core');
var utils = require('web.utils');
var web_client = require('web.web_client');
var QWeb = core.qweb;
// chat window management
//----------------------------------------------------------------
var CHAT_WINDOW_WIDTH = 300 + 5; // 5 pixels between windows
var chat_sessions = [];
var CHAT_WINDOW_WIDTH = 300;
var display_state = {
hidden_sessions: [],
hidden_unread_counter: 0, // total number of unread msgs in hidden chat windows
nb_slots: 0,
space_left: 0,
windows_dropdown_is_open: false, // used to keep dropdown open when closing chat windows
};
function open_chat (session) {
if (!_.findWhere(chat_sessions, {id: session.id})) {
var chat_session = _.findWhere(chat_sessions, {id: session.id});
if (!chat_session) {
var channel = chat_manager.get_channel(session.id);
var chat_session = {
id: session.id,
@@ -58,7 +68,10 @@ function open_chat (session) {
}
});
chat_sessions.push(chat_session);
// insert chat_session such that it will be the right-most visible window
compute_available_slots(chat_sessions.length+1);
chat_sessions.splice(display_state.nb_slots-1, 0, chat_session);
chat_session.window.appendTo($('body'))
.then(reposition_windows)
.then(function () {
@@ -67,6 +80,8 @@ function open_chat (session) {
chat_session.window.render(messages);
chat_session.window.scrollBottom();
});
} else if (chat_session.window.is_hidden) {
make_session_visible(chat_session);
}
}
@@ -86,16 +101,92 @@ function toggle_fold_chat (channel) {
}
}
function reposition_windows () {
function compute_available_slots (nb_windows) {
var width = window.innerWidth;
var nb_slots = Math.floor(width/CHAT_WINDOW_WIDTH);
var space_left = width - (Math.min(nb_slots, nb_windows)*CHAT_WINDOW_WIDTH);
if (nb_slots < nb_windows && space_left < 50) {
nb_slots--; // leave at least 50px for the hidden windows dropdown button
space_left += CHAT_WINDOW_WIDTH;
}
display_state.nb_slots = nb_slots;
display_state.space_left = space_left;
}
var reposition_windows = _.debounce(function () {
compute_available_slots(chat_sessions.length);
var hidden_sessions = [];
var hidden_unread_counter = 0;
var nb_slots = display_state.nb_slots;
_.each(chat_sessions, function (session, index) {
session.window.$el.css({right: (CHAT_WINDOW_WIDTH + 5) * index, bottom: 0});
if (index < nb_slots) {
session.window.$el.css({right: CHAT_WINDOW_WIDTH*index, bottom: 0});
session.window.do_show();
} else {
hidden_sessions.push(session);
hidden_unread_counter += session.window.unread_msgs;
session.window.do_hide();
}
});
display_state.hidden_sessions = hidden_sessions;
display_state.hidden_unread_counter = hidden_unread_counter;
if (display_state.$hidden_windows_dropdown) {
display_state.$hidden_windows_dropdown.remove();
}
if (hidden_sessions.length) {
display_state.$hidden_windows_dropdown = render_hidden_sessions_dropdown();
var $hidden_windows_dropdown = display_state.$hidden_windows_dropdown;
$hidden_windows_dropdown.css({right: CHAT_WINDOW_WIDTH * nb_slots, bottom: 0})
.appendTo($('body'));
reposition_hidden_sessions_dropdown();
display_state.windows_dropdown_is_open = false;
$hidden_windows_dropdown.on('click', '.o_chat_header', function (event) {
var session_id = $(event.currentTarget).data('session-id');
var session = _.findWhere(hidden_sessions, {id: session_id});
if (session) {
make_session_visible(session);
}
});
$hidden_windows_dropdown.on('click', '.o_chat_window_close', function (event) {
var session_id = $(event.currentTarget).closest('.o_chat_header').data('session-id');
var session = _.findWhere(hidden_sessions, {id: session_id});
if (session) {
session.window.on_click_close(event);
display_state.windows_dropdown_is_open = true; // keep the dropdown open
}
});
}
}, 100);
function make_session_visible (session) {
utils.swap(chat_sessions, session, chat_sessions[display_state.nb_slots-1]);
session.window.toggle_fold(false);
reposition_windows();
}
function render_hidden_sessions_dropdown () {
var $dropdown = $(QWeb.render("mail.ChatWindowsDropdown", {
sessions: display_state.hidden_sessions,
open: display_state.windows_dropdown_is_open,
unread_counter: display_state.hidden_unread_counter,
}));
return $dropdown;
}
function reposition_hidden_sessions_dropdown () {
// Unfold dropdown to the left if there is enough place
var $dropdown_ul = display_state.$hidden_windows_dropdown.children('ul');
if (display_state.space_left > $dropdown_ul.width() + 10) {
$dropdown_ul.addClass('dropdown-menu-right');
}
}
function update_sessions (message, scrollBottom) {
_.each(chat_sessions, function (session) {
if (_.contains(message.channel_ids, session.id)) {
if (!session.window.folded) {
if (!session.window.folded && !session.window.is_hidden) {
chat_manager.mark_channel_as_seen(chat_manager.get_channel(session.id));
}
chat_manager.get_messages({channel_id: session.id}).then(function (messages) {
@@ -139,11 +230,19 @@ core.bus.on('web_client_ready', null, function () {
});
chat_manager.bus.on('update_channel_unread_counter', null, function (channel) {
display_state.hidden_unread_counter = 0;
_.each(chat_sessions, function (session) {
if (channel.id === session.id) {
session.window.update_unread(channel.unread_counter);
}
if (session.window.is_hidden) {
display_state.hidden_unread_counter += session.window.unread_msgs;
}
});
if (display_state.$hidden_windows_dropdown) {
display_state.$hidden_windows_dropdown.html(render_hidden_sessions_dropdown().html());
reposition_hidden_sessions_dropdown();
}
});
chat_manager.is_ready.then(function() {
@@ -153,6 +252,8 @@ core.bus.on('web_client_ready', null, function () {
}
});
});
core.bus.on('resize', null, reposition_windows);
});
});
+43 -1
View File
@@ -1,10 +1,11 @@
@o-chat-window-bg: white;
@o-chat-window-width: 300px;
.o_chat_window {
.o-flex-display();
.o-flex-flow(column, nowrap);
position: fixed;
width: 300px;
width: @o-chat-window-width;
max-width: 100%;
height: 400px;
max-height: 100%;
@@ -28,11 +29,13 @@
.o_chat_title {
cursor: pointer;
.o-flex(1, 1, auto);
.o-text-overflow();
}
.o_chat_window_close {
color: white;
padding: 3px;
margin-left: 5px;
&:hover {
background-color: darken(@odoo-brand-primary, 10%);
}
@@ -67,3 +70,42 @@
}
}
}
.o_chat_window_dropdown {
width: auto;
height: 28px;
color: white;
background-color: @odoo-brand-primary;
cursor: pointer;
.o_chat_window_dropdown_toggler {
padding: 5px;
.o_total_unread_counter {
.o-position-absolute(-10px, 0, auto, auto);
background-color: @odoo-brand-optional;
padding: 0 2px;
font-size: smaller;
}
}
&.open .o_chat_window_dropdown_toggler .o_total_unread_counter {
display: none;
}
> ul {
max-width: @o-chat-window-width;
padding: 0;
> li.o_chat_header {
font-size: 12px;
padding: 3px 5px;
&~li.o_chat_header {
border-top: 1px solid white;
}
&:hover {
background-color: darken(@odoo-brand-primary, 10%);
color: white;
}
}
}
}
+37 -7
View File
@@ -1,16 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">
<t t-name="mail.ChatWindow">
<div class="o_chat_window">
<div class="o_chat_header">
<span class="o_chat_title">
<t t-esc="widget.title"/>
</span>
<span>
<a href="#" class="o_chat_window_close fa fa-close"></a>
</span>
<t t-call="mail.ChatWindowHeaderContent">
<t t-set="title" t-value="widget.title"/>
<t t-set="unread_counter" t-value="widget.unread_msgs"/>
</t>
</div>
<div class="o_chat_content">
</div>
@@ -20,4 +17,37 @@
</div>
</t>
<t t-name="mail.ChatWindowsDropdown">
<div t-attf-class="o_chat_window o_chat_window_dropdown dropup #{open ? 'open' : ''}">
<span class="o_chat_window_dropdown_toggler dropdown-toggle" data-toggle="dropdown">
<i class="fa fa-comments-o"/> <t t-esc="sessions.length"/>
<span class="o_total_unread_counter">
<t t-if="unread_counter"><t t-esc="unread_counter"/></t>
</span>
</span>
<ul class="dropdown-menu">
<t t-foreach="sessions" t-as="session">
<li class="o_chat_header" t-att-data-session-id="session.id">
<t t-call="mail.ChatWindowHeaderContent">
<t t-set="title" t-value="session.name"/>
<t t-set="unread_counter" t-value="session.window.unread_msgs"/>
</t>
</li>
</t>
</ul>
</div>
</t>
<t t-name="mail.ChatWindowHeaderContent">
<span class="o_chat_title">
<t t-esc="title"/>
<span class="o_unread_counter">
<t t-if="unread_counter">(<t t-esc="unread_counter"/>)</t>
</span>
</span>
<span>
<a href="#" class="o_chat_window_close fa fa-close"/>
</span>
</t>
</templates>