diff options
| author | Sébastien Theys <seb@odoo.com> | 2020-06-15 18:12:21 +0000 |
|---|---|---|
| committer | Sébastien Theys <seb@odoo.com> | 2020-06-15 18:12:21 +0000 |
| commit | 3fea5b2136627c1d7e83b2a5e111952fe3eb10a1 (patch) | |
| tree | 5eefad07972a87ce4689b83260f981bc87aadad4 /addons/mail/static/src/models/thread_cache | |
| parent | 0a82848606151ef60eb6523e9c4c161171f214e7 (diff) | |
[REF] mail, *: refactor messaging with OWL
This commit is a significant rewriting of client-side discuss, chatter,
chat window, and messaging menu using OWL. The behavior should be broadly
the same, with some slight functional changes here and there.
From a technical standpoint, the code of messaging is mainly organized in 2
main groups of modules:
- models, which are logical entities that depict the client-side state of
messaging as a whole.
- components, which are in charge of displaying information from models.
This refactoring also introduces new JS guidelines regarding folder structure
(/static) and naming rules for JS modules.
Community PR: https://github.com/odoo/odoo/pull/39023
Enterprise PR: https://github.com/odoo/enterprise/pull/6249
Task-1914207
This PR is a collaborative work by Alexandre, Julien, Sébastien and Xavier,
with the precious help of Lucas to speed it up towards the end.
closes odoo/odoo#39023
Related: odoo/enterprise#6249
Signed-off-by: Alexandre Kühn (aku) <aku@odoo.com>
Co-authored-by: Alexandre Kühn <aku@odoo.com>
Co-authored-by: Julien Giannone <jgi@odoo.com>
Co-authored-by: Lucas Perais <lpe@odoo.com>
Co-authored-by: Sébastien Theys <seb@odoo.com>
Co-authored-by: Xavier Dubuc <xdu@odoo.com>
Diffstat (limited to 'addons/mail/static/src/models/thread_cache')
| -rw-r--r-- | addons/mail/static/src/models/thread_cache/thread_cache.js | 263 |
1 files changed, 263 insertions, 0 deletions
diff --git a/addons/mail/static/src/models/thread_cache/thread_cache.js b/addons/mail/static/src/models/thread_cache/thread_cache.js new file mode 100644 index 00000000000..edd991c693c --- /dev/null +++ b/addons/mail/static/src/models/thread_cache/thread_cache.js @@ -0,0 +1,263 @@ +odoo.define('mail/static/src/models/thread_cache/thread_cache.js', function (require) { +'use strict'; + +const { registerNewModel } = require('mail/static/src/model/model_core.js'); +const { attr, many2many, many2one } = require('mail/static/src/model/model_field.js'); + +const MESSAGE_FETCH_LIMIT = 30; + +function factory(dependencies) { + + class ThreadCache extends dependencies['mail.model'] { + + //---------------------------------------------------------------------- + // Public + //---------------------------------------------------------------------- + + /** + * Load this thread cache, by fetching the most recent messages in this + * conversation. + */ + async loadMessages() { + if (this.isLoaded && this.isLoading) { + return; + } + const searchDomain = JSON.parse(this.stringifiedDomain); + let domain = searchDomain.length ? searchDomain : []; + domain = this._extendMessageDomain(domain); + this.update({ isLoading: true }); + let messagesData = []; + if (!this.thread.isTemporary) { + messagesData = await this.async(() => this.env.services.rpc({ + model: 'mail.message', + method: 'message_fetch', + args: [domain], + kwargs: this._getFetchMessagesKwargs(), + }, { shadow: true })); + } + this._handleMessagesLoaded(messagesData); + } + + async loadMoreMessages() { + const searchDomain = JSON.parse(this.stringifiedDomain); + let domain = searchDomain.length ? searchDomain : []; + domain = this._extendMessageDomain(domain); + if (this.isAllHistoryLoaded && this.isLoadingMore) { + return; + } + this.update({ isLoadingMore: true }); + const minMessageId = Math.min( + ...this.messages.map(message => message.id) + ); + domain = [['id', '<', minMessageId]].concat(domain); + const messagesData = await this.async(() => this.env.services.rpc({ + model: 'mail.message', + method: 'message_fetch', + args: [domain], + kwargs: this._getFetchMessagesKwargs(), + }, { shadow: true })); + for (const viewer of this.thread.viewers) { + viewer.addComponentHint('more-messages-loaded'); + } + this._handleMessagesLoaded(messagesData); + } + + async loadNewMessages() { + if (this.isLoading) { + return; + } + if (!this.isLoaded) { + await this.async(() => this.loadMessages()); + return; + } + const messageIds = this.messages.map(message => message.id); + const searchDomain = JSON.parse(this.stringifiedDomain); + let domain = searchDomain.length ? searchDomain : []; + domain = this._extendMessageDomain(domain); + if (messageIds.length > 0) { + const lastMessageId = Math.max(...messageIds); + domain = [['id', '>', lastMessageId]].concat(domain); + } + this.update({ isLoading: true }); + const messageFetchKwargs = this._getFetchMessagesKwargs(); + messageFetchKwargs.limit = false; + const messagesData = await this.async(() => this.env.services.rpc({ + model: 'mail.message', + method: 'message_fetch', + args: [domain], + kwargs: messageFetchKwargs, + }, { shadow: true })); + this._handleMessagesLoaded(messagesData); + } + + //---------------------------------------------------------------------- + // Private + //---------------------------------------------------------------------- + + /** + * @private + * @returns {mail.message|undefined} + */ + _computeLastMessage() { + const { length: l, [l - 1]: lastMessage } = this.orderedMessages; + if (!lastMessage) { + return [['unlink-all']]; + } + return [['replace', lastMessage]]; + } + + /** + * @private + * @returns {mail.message[]} + */ + _computeOrderedMessages() { + return [['replace', this.messages.sort((m1, m2) => m1.id < m2.id ? -1 : 1)]]; + } + + /** + * @private + * @returns {mail.message[]} + */ + _computeUncheckedMessages() { + return [['replace', this.messages.filter( + message => message.hasCheckbox && !this.checkedMessages.includes(message) + )]]; + } + + /** + * @override + */ + _createRecordLocalId(data) { + const { + stringifiedDomain = '[]', + thread: [[commandInsert, thread]], + } = data; + const ThreadCache = this.env.models['mail.thread_cache']; + return `${ThreadCache.modelName}_[${thread.localId}]_<${stringifiedDomain}>`; + } + + /** + * @private + * @param {Array} domain + * @returns {Array} + */ + _extendMessageDomain(domain) { + const thread = this.thread; + if (thread.model === 'mail.channel') { + return domain.concat([['channel_ids', 'in', [thread.id]]]); + } else if (thread === this.env.messaging.inbox) { + return domain.concat([['needaction', '=', true]]); + } else if (thread === this.env.messaging.starred) { + return domain.concat([['starred', '=', true]]); + } else if (thread === this.env.messaging.history) { + return domain.concat([['needaction', '=', false]]); + } else if (thread === this.env.messaging.moderation) { + return domain.concat([['need_moderation', '=', true]]); + } else { + return domain.concat([['model', '=', thread.model], ['res_id', '=', thread.id]]); + } + } + + /** + * @private + * @returns {Object} + */ + _getFetchMessagesKwargs() { + const thread = this.thread; + let kwargs = { + limit: MESSAGE_FETCH_LIMIT, + context: this.env.session.user_context, + }; + if (thread.moderation) { + // thread is a channel + kwargs.moderated_channel_ids = [thread.id]; + } + return kwargs; + } + + /** + * @private + * @param {Object[]} messageData + */ + _handleMessagesLoaded(messagesData) { + this.update({ + isAllHistoryLoaded: messagesData.length < MESSAGE_FETCH_LIMIT, + isLoaded: true, + isLoading: false, + isLoadingMore: false, + messages: [['insert', messagesData.map(data => + this.env.models['mail.message'].convertData(data)) + ]], + }); + if (!this.thread) { + return; + } + this.thread.update({ + messageSeenIndicators: [['insert', messagesData.map(messageData => { + return { + id: this.env.models['mail.message_seen_indicator'].computeId(messageData.id, this.thread.id), + message: [['insert', { id: messageData.id }]], + }; + })]], + }); + for (const viewer of this.thread.viewers) { + viewer.handleThreadCacheLoaded(this); + } + } + + } + + ThreadCache.fields = { + checkedMessages: many2many('mail.message', { + inverse: 'checkedThreadCaches', + }), + isAllHistoryLoaded: attr({ + default: false, + }), + isLoaded: attr({ + default: false, + }), + isLoading: attr({ + default: false, + }), + isLoadingMore: attr({ + default: false, + }), + lastMessage: many2one('mail.message', { + compute: '_computeLastMessage', + dependencies: ['orderedMessages'], + }), + messagesCheckboxes: attr({ + related: 'messages.hasCheckbox', + }), + messages: many2many('mail.message', { + inverse: 'threadCaches', + }), + orderedMessages: many2many('mail.message', { + compute: '_computeOrderedMessages', + dependencies: ['messages'], + }), + stringifiedDomain: attr({ + default: '[]', + }), + thread: many2one('mail.thread', { + inverse: 'caches', + }), + uncheckedMessages: many2many('mail.message', { + compute: '_computeUncheckedMessages', + dependencies: [ + 'checkedMessages', + 'messagesCheckboxes', + 'messages', + ], + }), + }; + + ThreadCache.modelName = 'mail.thread_cache'; + + return ThreadCache; +} + +registerNewModel('mail.thread_cache', factory); + +}); |
