diff --git a/sao/CHANGELOG b/sao/CHANGELOG index cdec25be88..338d6f8667 100644 --- a/sao/CHANGELOG +++ b/sao/CHANGELOG @@ -1,3 +1,4 @@ +* Add simple chat widget on sidebar * Support color as type of icon and image * Support color widget on form view diff --git a/sao/Gruntfile.js b/sao/Gruntfile.js index f4082e28dc..58f5bcd7a3 100644 --- a/sao/Gruntfile.js +++ b/sao/Gruntfile.js @@ -23,6 +23,7 @@ module.exports = function(grunt) { 'src/wizard.js', 'src/board.js', 'src/bus.js', + 'src/chat.js', 'src/plugins.js', 'src/html_sanitizer.js' ]; diff --git a/sao/images/tryton-chat.svg b/sao/images/tryton-chat.svg new file mode 100644 index 0000000000..8f0557ea3e --- /dev/null +++ b/sao/images/tryton-chat.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/sao/images/tryton-note.svg b/sao/images/tryton-note.svg index a10719ede4..ba38a98242 100644 --- a/sao/images/tryton-note.svg +++ b/sao/images/tryton-note.svg @@ -1,4 +1 @@ - - - - + \ No newline at end of file diff --git a/sao/src/bus.js b/sao/src/bus.js index dbdd1afa1c..6ef8f2f980 100644 --- a/sao/src/bus.js +++ b/sao/src/bus.js @@ -7,23 +7,30 @@ // Bus Identifier Sao.Bus.id = Sao.common.uuid4(); - Sao.Bus.channels = ['client:' + Sao.Bus.id]; + Sao.Bus.channel_actions = {}; + Sao.Bus.request = null; + Sao.Bus.last_message = undefined; + Sao.Bus.listening = false; Sao.Bus.listen = function(last_message, wait) { wait = wait || 1; + last_message = last_message || Sao.Bus.last_message; var session = Sao.Session.current_session; if (!session) { return; } + Sao.Bus.listening = true; - var prm = jQuery.ajax({ + let channels = Object.keys(Sao.Bus.channel_actions); + Sao.Bus.last_message = last_message; + Sao.Bus.request = jQuery.ajax({ headers: { Authorization: 'Session ' + session.get_auth(), }, contentType: 'application/json', data: JSON.stringify({ last_message: last_message, - channels: Sao.Bus.channels + channels: channels, }), dataType: 'json', url: '/' + session.database + '/bus', @@ -31,7 +38,7 @@ timeout: Sao.config.bus_timeout, }); - prm.done(function(response) { + Sao.Bus.request.done(function(response) { if (Sao.Session.current_session != session) { return; } @@ -40,25 +47,26 @@ Sao.Logger.info( "poll channels %s with last message", Sao.Bus.channels, last_message); - Sao.Bus.handle(response.message); + Sao.Bus.handle(response.channel, response.message); } Sao.Bus.listen(last_message, 1); }); - prm.fail(function(response, status, error) { + Sao.Bus.request.fail(function(response, status, error) { if (Sao.Session.current_session != session) { return; } - if (error === "timeout") { + if ((error == "abort") || (error === "timeout")) { Sao.Bus.listen(last_message, 1); } else if (response.status == 501) { Sao.Logger.info("Bus not supported"); - return; + Sao.Bus.listening = false; } else { Sao.Logger.error( "An exception occured while connection to the bus. " + "Sleeping for %s seconds", wait, error); + Sao.Bus.listening = false; window.setTimeout( Sao.Bus.listen, Math.min(wait * 1000, Sao.config.bus_timeout), @@ -67,27 +75,56 @@ }); }; - Sao.Bus.handle = function(message) { - var notify = function(message) { - try { - if (Notification.permission != "granted") { - return; - } - } catch (e) { - Sao.Logger.error(e.message, e.stack); - return; - } - - new Notification(message.title, { - body: message.body || '', - }); - }; - - switch (message.type) { - case 'notification': - notify(message); - break; + Sao.Bus.handle = function(channel, message) { + let actions = Sao.Bus.channel_actions[channel] || []; + for (let callback of actions) { + callback(message); } }; + Sao.Bus.register = function(channel, func) { + if (!Object.hasOwn(Sao.Bus.channel_actions, channel)) { + Sao.Bus.channel_actions[channel] = []; + } + Sao.Bus.channel_actions[channel].push(func); + + if (Sao.Bus.request) { + Sao.Bus.request.abort(); + } + }; + + Sao.Bus.unregister = function(channel, func) { + if (Object.hasOwn(Sao.Bus.channel_actions, channel)) { + let actions = Sao.Bus.channel_actions[channel]; + let func_idx = actions.indexOf(func); + if (func_idx != -1) { + actions.splice(func_idx, 1); + } + } + + if ((Sao.Bus.channel_actions[channel] || []).length == 0) { + delete Sao.Bus.channel_actions[channel]; + } + } + + let popup_notification = function(message) { + if (message.type != 'notification') { + return; + } + + try { + if (Notification.permission != "granted") { + return; + } + } catch (e) { + Sao.Logger.error(e.message, e.stack); + return; + } + + new Notification(message.title, { + body: message.body || '', + }); + } + Sao.Bus.register(`client:${Sao.Bus.id}`, popup_notification); + }()); diff --git a/sao/src/chat.js b/sao/src/chat.js new file mode 100644 index 0000000000..f8ac11b425 --- /dev/null +++ b/sao/src/chat.js @@ -0,0 +1,149 @@ +/* This file is part of Tryton. The COPYRIGHT file at the top level of + this repository contains the full copyright notices and license terms. */ +(function() { + 'use strict'; + + class _Chat { + constructor(record) { + this.record = record; + Sao.Bus.register(`chat:${this.record}`, this.notify.bind(this)); + this.el = this.__build(); + } + + unregister() { + Sao.Bus.unregister(`chat:${this.record}`, this.notify); + } + + send_message(message, internal) { + return Sao.rpc({ + 'method': 'model.ir.chat.channel.post', + 'params': [this.record, message, internal ? 'internal' : 'public', {}], + }, Sao.Session.current_session) + } + + get_messages() { + return Sao.rpc({ + 'method': 'model.ir.chat.channel.get', + 'params': [this.record, {}], + }, Sao.Session.current_session); + } + + notify(message) { + this.refresh(); + } + + refresh() { + let prm = this.get_messages(); + prm.done((posts) => { + this._messages.empty(); + for (let post of posts) { + this._messages.append(this.create_message(post)); + } + + let messages = this._messages[this._messages.length - 1]; + messages.scrollTop = messages.scrollHeight; + }); + } + + __build() { + let el = jQuery('
', { + 'class': 'chat', + }); + + this._messages = jQuery('
', { + 'class': 'chat-messages', + }).appendTo(jQuery('
', { + 'class': 'chat-messages-outer', + }).appendTo(el)); + + let input = jQuery('