diff --git a/addons/hr_holidays/static/src/components/thread_view/thread_view.js b/addons/hr_holidays/static/src/components/thread_view/thread_view.js index c07ab2189a9..73ca28d4d0c 100644 --- a/addons/hr_holidays/static/src/components/thread_view/thread_view.js +++ b/addons/hr_holidays/static/src/components/thread_view/thread_view.js @@ -17,11 +17,10 @@ patch(components.ThreadView, 'hr_holidays/static/src/components/thread_view/thre */ _useStoreSelector(props) { const res = this._super(...arguments); - const threadView = this.env.models['mail.thread_view'].get(props.threadViewLocalId); - const thread = threadView ? threadView.thread : undefined; - const correspondent = thread ? thread.correspondent : undefined; + const thread = res.thread; + const correspondent = thread && thread.correspondent; return Object.assign({}, res, { - correspondent: correspondent ? correspondent.__state : undefined, + correspondentOutOfOfficeText: correspondent && correspondent.outOfOfficeText, }); }, }); diff --git a/addons/im_livechat/static/src/components/discuss_sidebar/discuss_sidebar.js b/addons/im_livechat/static/src/components/discuss_sidebar/discuss_sidebar.js index 2dafeed4c79..35070cb8e6d 100644 --- a/addons/im_livechat/static/src/components/discuss_sidebar/discuss_sidebar.js +++ b/addons/im_livechat/static/src/components/discuss_sidebar/discuss_sidebar.js @@ -69,15 +69,8 @@ patch(components.DiscussSidebar, 'im_livechat/static/src/components/discuss_side */ _useStoreSelector(props) { return Object.assign(this._super(...arguments), { - allOrderedAndPinnedLivechats: this.env.models['mail.thread'] - .all(thread => - thread.channel_type === 'livechat' && - thread.isPinned && - thread.model === 'mail.channel' - ) - .map(livechat => livechat.__state), - } - ); + allOrderedAndPinnedLivechats: this.quickSearchOrderedAndPinnedLivechatList(), + }); }, }); diff --git a/addons/mail/static/src/bugfix/bugfix.js b/addons/mail/static/src/bugfix/bugfix.js index 5a7474b5c59..b1b1d43d03b 100644 --- a/addons/mail/static/src/bugfix/bugfix.js +++ b/addons/mail/static/src/bugfix/bugfix.js @@ -4,7 +4,3 @@ * versions of Odoo. Any module that is defined in this file should be isolated * in its own file in master. */ -odoo.define('mail/static/src/bugfix/bugfix.js', function (require) { -'use strict'; - -}); diff --git a/addons/mail/static/src/component_hooks/use_should_update_based_on_props/use_should_update_based_on_props.js b/addons/mail/static/src/component_hooks/use_should_update_based_on_props/use_should_update_based_on_props.js new file mode 100644 index 00000000000..b0757bd5ae0 --- /dev/null +++ b/addons/mail/static/src/component_hooks/use_should_update_based_on_props/use_should_update_based_on_props.js @@ -0,0 +1,72 @@ +odoo.define('mail/static/src/component_hooks/use_should_update_based_on_props/use_should_update_based_on_props.js', function (require) { +'use strict'; + +const { Component } = owl; + +/** + * Compares `a` and `b` up to the given `compareDepth`. + * + * @param {any} a + * @param {any} b + * @param {Object|integer} compareDepth + * @returns {boolean} + */ +function isEqual(a, b, compareDepth) { + const keys = Object.keys(a); + if (Object.keys(b).length !== keys.length) { + return false; + } + for (const key of keys) { + // the depth can be given either as a number (for all keys) or as + // an object (for each key) + let depth; + if (typeof compareDepth === 'number') { + depth = compareDepth; + } else { + depth = compareDepth[key] || 0; + } + if (depth === 0 && a[key] !== b[key]) { + return false; + } + if (depth !== 0) { + let nextDepth; + if (typeof depth === 'number') { + nextDepth = depth - 1; + } else { + nextDepth = depth; + } + if (!isEqual(a[key], b[key], nextDepth)) { + return false; + } + } + } + return true; +} + +/** + * This hook overrides the `shouldUpdate` method to ensure the component is only + * updated if its props actually changed. This is especially useful to use on + * components for which an extra render costs proportionally a lot more than + * comparing props. + * + * @param {Object} [param0={}] + * @param {Object} [param0.compareDepth={}] allows to specify the comparison + * depth to use for each prop. Default is shallow compare (depth = 0). + */ +function useShouldUpdateBasedOnProps({ compareDepth = {} } = {}) { + const component = Component.current; + component.shouldUpdate = nextProps => { + const allNewProps = Object.assign({}, nextProps); + const defaultProps = component.constructor.defaultProps; + for (const key in defaultProps) { + if (allNewProps[key] === undefined) { + allNewProps[key] = defaultProps[key]; + } + } + return !isEqual(component.props, allNewProps, compareDepth); + }; +} + +return useShouldUpdateBasedOnProps; + +}); diff --git a/addons/mail/static/src/components/activity/activity.js b/addons/mail/static/src/components/activity/activity.js index 1fd16d77464..1ee7ecf394c 100644 --- a/addons/mail/static/src/components/activity/activity.js +++ b/addons/mail/static/src/components/activity/activity.js @@ -6,6 +6,7 @@ const components = { FileUploader: require('mail/static/src/components/file_uploader/file_uploader.js'), MailTemplate: require('mail/static/src/components/mail_template/mail_template.js'), }; +const useShouldUpdateBasedOnProps = require('mail/static/src/component_hooks/use_should_update_based_on_props/use_should_update_based_on_props.js'); const useStore = require('mail/static/src/component_hooks/use_store/use_store.js'); const { @@ -24,6 +25,7 @@ class Activity extends Component { */ constructor(...args) { super(...args); + useShouldUpdateBasedOnProps(); this.state = useState({ areDetailsVisible: false, }); diff --git a/addons/mail/static/src/components/activity_box/activity_box.js b/addons/mail/static/src/components/activity_box/activity_box.js index 8f5c13c94dd..ca19169438b 100644 --- a/addons/mail/static/src/components/activity_box/activity_box.js +++ b/addons/mail/static/src/components/activity_box/activity_box.js @@ -4,6 +4,7 @@ odoo.define('mail/static/src/components/activity_box/activity_box.js', function const components = { Activity: require('mail/static/src/components/activity/activity.js'), }; +const useShouldUpdateBasedOnProps = require('mail/static/src/component_hooks/use_should_update_based_on_props/use_should_update_based_on_props.js'); const useStore = require('mail/static/src/component_hooks/use_store/use_store.js'); const { Component } = owl; @@ -15,6 +16,7 @@ class ActivityBox extends Component { */ constructor(...args) { super(...args); + useShouldUpdateBasedOnProps(); useStore(props => { const chatter = this.env.models['mail.chatter'].get(props.chatterLocalId); const thread = chatter && chatter.thread; diff --git a/addons/mail/static/src/components/activity_mark_done_popover/activity_mark_done_popover.js b/addons/mail/static/src/components/activity_mark_done_popover/activity_mark_done_popover.js index e951b5e4737..de1ea5ce232 100644 --- a/addons/mail/static/src/components/activity_mark_done_popover/activity_mark_done_popover.js +++ b/addons/mail/static/src/components/activity_mark_done_popover/activity_mark_done_popover.js @@ -1,6 +1,7 @@ odoo.define('mail/static/src/components/activity_mark_done_popover/activity_mark_done_popover.js', function (require) { 'use strict'; +const useShouldUpdateBasedOnProps = require('mail/static/src/component_hooks/use_should_update_based_on_props/use_should_update_based_on_props.js'); const useStore = require('mail/static/src/component_hooks/use_store/use_store.js'); const { Component } = owl; @@ -13,6 +14,7 @@ class ActivityMarkDonePopover extends Component { */ constructor(...args) { super(...args); + useShouldUpdateBasedOnProps(); useStore(props => { const activity = this.env.models['mail.activity'].get(props.activityLocalId); return { diff --git a/addons/mail/static/src/components/attachment/attachment.js b/addons/mail/static/src/components/attachment/attachment.js index f62113c4c41..32b31d58e20 100644 --- a/addons/mail/static/src/components/attachment/attachment.js +++ b/addons/mail/static/src/components/attachment/attachment.js @@ -1,6 +1,7 @@ odoo.define('mail/static/src/components/attachment/attachment.js', function (require) { 'use strict'; +const useShouldUpdateBasedOnProps = require('mail/static/src/component_hooks/use_should_update_based_on_props/use_should_update_based_on_props.js'); const useStore = require('mail/static/src/component_hooks/use_store/use_store.js'); const components = { @@ -16,6 +17,11 @@ class Attachment extends Component { */ constructor(...args) { super(...args); + useShouldUpdateBasedOnProps({ + compareDepth: { + attachmentLocalIds: 1, + }, + }); useStore(props => { const attachment = this.env.models['mail.attachment'].get(props.attachmentLocalId); return { diff --git a/addons/mail/static/src/components/attachment_box/attachment_box.js b/addons/mail/static/src/components/attachment_box/attachment_box.js index 6e0c9d7c55e..5bfe7c06463 100644 --- a/addons/mail/static/src/components/attachment_box/attachment_box.js +++ b/addons/mail/static/src/components/attachment_box/attachment_box.js @@ -7,6 +7,7 @@ const components = { FileUploader: require('mail/static/src/components/file_uploader/file_uploader.js'), }; const useDragVisibleDropZone = require('mail/static/src/component_hooks/use_drag_visible_dropzone/use_drag_visible_dropzone.js'); +const useShouldUpdateBasedOnProps = require('mail/static/src/component_hooks/use_should_update_based_on_props/use_should_update_based_on_props.js'); const useStore = require('mail/static/src/component_hooks/use_store/use_store.js'); const { Component } = owl; @@ -20,14 +21,19 @@ class AttachmentBox extends Component { constructor(...args) { super(...args); this.isDropZoneVisible = useDragVisibleDropZone(); + useShouldUpdateBasedOnProps(); useStore(props => { const thread = this.env.models['mail.thread'].get(props.threadLocalId); return { - attachments: thread - ? thread.allAttachments.map(attachment => attachment.__state) - : [], - thread: thread ? thread.__state : undefined, + thread, + threadAllAttachments: thread ? thread.allAttachments : [], + threadId: thread && thread.id, + threadModel: thread && thread.model, }; + }, { + compareDepth: { + threadAllAttachments: 1, + }, }); /** * Reference of the file uploader. diff --git a/addons/mail/static/src/components/attachment_delete_confirm_dialog/attachment_delete_confirm_dialog.js b/addons/mail/static/src/components/attachment_delete_confirm_dialog/attachment_delete_confirm_dialog.js index 46fa1c65260..ab7e155a809 100644 --- a/addons/mail/static/src/components/attachment_delete_confirm_dialog/attachment_delete_confirm_dialog.js +++ b/addons/mail/static/src/components/attachment_delete_confirm_dialog/attachment_delete_confirm_dialog.js @@ -1,6 +1,7 @@ odoo.define('mail/static/src/components/attachment_delete_confirm_dialog/attachment_delete_confirm_dialog.js', function (require) { 'use strict'; +const useShouldUpdateBasedOnProps = require('mail/static/src/component_hooks/use_should_update_based_on_props/use_should_update_based_on_props.js'); const useStore = require('mail/static/src/component_hooks/use_store/use_store.js'); const components = { @@ -17,6 +18,7 @@ class AttachmentDeleteConfirmDialog extends Component { */ constructor(...args) { super(...args); + useShouldUpdateBasedOnProps(); useStore(props => { const attachment = this.env.models['mail.attachment'].get(props.attachmentLocalId); return { diff --git a/addons/mail/static/src/components/attachment_list/attachment_list.js b/addons/mail/static/src/components/attachment_list/attachment_list.js index ecd7e2f47c8..d8658ac83dc 100644 --- a/addons/mail/static/src/components/attachment_list/attachment_list.js +++ b/addons/mail/static/src/components/attachment_list/attachment_list.js @@ -4,16 +4,24 @@ odoo.define('mail/static/src/components/attachment_list/attachment_list.js', fun const components = { Attachment: require('mail/static/src/components/attachment/attachment.js'), }; + +const useShouldUpdateBasedOnProps = require('mail/static/src/component_hooks/use_should_update_based_on_props/use_should_update_based_on_props.js'); const useStore = require('mail/static/src/component_hooks/use_store/use_store.js'); const { Component } = owl; class AttachmentList extends Component { + /** * @override */ constructor(...args) { super(...args); + useShouldUpdateBasedOnProps({ + compareDepth: { + attachmentLocalIds: 1, + }, + }); useStore(props => { const attachments = this.env.models['mail.attachment'].all().filter(attachment => props.attachmentLocalIds.includes(attachment.localId) @@ -23,6 +31,10 @@ class AttachmentList extends Component { ? attachments.map(attachment => attachment.__state) : undefined, }; + }, { + compareDepth: { + attachments: 1, + }, }); } diff --git a/addons/mail/static/src/components/attachment_viewer/attachment_viewer.js b/addons/mail/static/src/components/attachment_viewer/attachment_viewer.js index a161b7c5bd0..1cd680df8b0 100644 --- a/addons/mail/static/src/components/attachment_viewer/attachment_viewer.js +++ b/addons/mail/static/src/components/attachment_viewer/attachment_viewer.js @@ -2,6 +2,7 @@ odoo.define('mail/static/src/components/attachment_viewer/attachment_viewer.js', 'use strict'; const useRefs = require('mail/static/src/component_hooks/use_refs/use_refs.js'); +const useShouldUpdateBasedOnProps = require('mail/static/src/component_hooks/use_should_update_based_on_props/use_should_update_based_on_props.js'); const useStore = require('mail/static/src/component_hooks/use_store/use_store.js'); const { Component, QWeb } = owl; @@ -19,6 +20,7 @@ class AttachmentViewer extends Component { constructor(...args) { super(...args); this.MIN_SCALE = MIN_SCALE; + useShouldUpdateBasedOnProps(); useStore(props => { const attachmentViewer = this.env.models['mail.attachment_viewer'].get(props.localId); return { diff --git a/addons/mail/static/src/components/autocomplete_input/autocomplete_input.js b/addons/mail/static/src/components/autocomplete_input/autocomplete_input.js index c80bbafc4c7..8059cc3571a 100644 --- a/addons/mail/static/src/components/autocomplete_input/autocomplete_input.js +++ b/addons/mail/static/src/components/autocomplete_input/autocomplete_input.js @@ -1,10 +1,17 @@ odoo.define('mail/static/src/components/autocomplete_input/autocomplete_input.js', function (require) { 'use strict'; +const useShouldUpdateBasedOnProps = require('mail/static/src/component_hooks/use_should_update_based_on_props/use_should_update_based_on_props.js'); + const { Component } = owl; class AutocompleteInput extends Component { + constructor(...args) { + super(...args); + useShouldUpdateBasedOnProps(); + } + mounted() { if (this.props.isFocusOnMount) { this.el.focus(); diff --git a/addons/mail/static/src/components/chat_window/chat_window.js b/addons/mail/static/src/components/chat_window/chat_window.js index 8ae13de6d92..7e31cd3b6d4 100644 --- a/addons/mail/static/src/components/chat_window/chat_window.js +++ b/addons/mail/static/src/components/chat_window/chat_window.js @@ -6,6 +6,7 @@ const components = { ChatWindowHeader: require('mail/static/src/components/chat_window_header/chat_window_header.js'), ThreadView: require('mail/static/src/components/thread_view/thread_view.js'), }; +const useShouldUpdateBasedOnProps = require('mail/static/src/component_hooks/use_should_update_based_on_props/use_should_update_based_on_props.js'); const useStore = require('mail/static/src/component_hooks/use_store/use_store.js'); const useUpdate = require('mail/static/src/component_hooks/use_update/use_update.js'); const { isEventHandled } = require('mail/static/src/utils/utils.js'); @@ -22,14 +23,24 @@ class ChatWindow extends Component { */ constructor(...args) { super(...args); + useShouldUpdateBasedOnProps(); useStore(props => { const chatWindow = this.env.models['mail.chat_window'].get(props.chatWindowLocalId); const thread = chatWindow ? chatWindow.thread : undefined; return { - chatWindow: chatWindow ? chatWindow.__state : undefined, + chatWindow, + chatWindowHasNewMessageForm: chatWindow && chatWindow.hasNewMessageForm, + chatWindowIsDoFocus: chatWindow && chatWindow.isDoFocus, + chatWindowIsFocused: chatWindow && chatWindow.isFocused, + chatWindowIsFolded: chatWindow && chatWindow.isFolded, + chatWindowThreadView: chatWindow && chatWindow.threadView, + chatWindowVisibleIndex: chatWindow && chatWindow.visibleIndex, + chatWindowVisibleOffset: chatWindow && chatWindow.visibleOffset, isDeviceMobile: this.env.messaging.device.isMobile, localeTextDirection: this.env.messaging.locale.textDirection, - thread: thread ? thread.__state : undefined, + thread, + threadMassMailing: thread && thread.mass_mailing, + threadModel: thread && thread.model, }; }); useUpdate({ func: () => this._update() }); diff --git a/addons/mail/static/src/components/chat_window_header/chat_window_header.js b/addons/mail/static/src/components/chat_window_header/chat_window_header.js index 428e97048c0..5b30eb80805 100644 --- a/addons/mail/static/src/components/chat_window_header/chat_window_header.js +++ b/addons/mail/static/src/components/chat_window_header/chat_window_header.js @@ -4,6 +4,7 @@ odoo.define('mail/static/src/components/chat_window_header/chat_window_header.js const components = { ThreadIcon: require('mail/static/src/components/thread_icon/thread_icon.js'), }; +const useShouldUpdateBasedOnProps = require('mail/static/src/component_hooks/use_should_update_based_on_props/use_should_update_based_on_props.js'); const useStore = require('mail/static/src/component_hooks/use_store/use_store.js'); const { isEventHandled, @@ -19,13 +20,21 @@ class ChatWindowHeader extends Component { */ constructor(...args) { super(...args); + useShouldUpdateBasedOnProps(); useStore(props => { const chatWindow = this.env.models['mail.chat_window'].get(props.chatWindowLocalId); const thread = chatWindow && chatWindow.thread; + const correspondent = thread && thread.correspondent; return { - chatWindow: chatWindow ? chatWindow.__state : undefined, + chatWindow, + chatWindowHasShiftNext: chatWindow && chatWindow.hasShiftNext, + chatWindowHasShiftPrev: chatWindow && chatWindow.hasShiftPrev, + chatWindowName: chatWindow && chatWindow.name, + correspondent, isDeviceMobile: this.env.messaging.device.isMobile, - thread: thread ? thread.__state : undefined, + thread, + threadLocalMessageUnreadCounter: thread && thread.localMessageUnreadCounter, + threadMassMailing: thread && thread.mass_mailing, }; }); } diff --git a/addons/mail/static/src/components/chat_window_hidden_menu/chat_window_hidden_menu.js b/addons/mail/static/src/components/chat_window_hidden_menu/chat_window_hidden_menu.js index c6ec0b74e9f..926ca0839d0 100644 --- a/addons/mail/static/src/components/chat_window_hidden_menu/chat_window_hidden_menu.js +++ b/addons/mail/static/src/components/chat_window_hidden_menu/chat_window_hidden_menu.js @@ -4,6 +4,7 @@ odoo.define('mail/static/src/components/chat_window_hidden_menu/chat_window_hidd const components = { ChatWindowHeader: require('mail/static/src/components/chat_window_header/chat_window_header.js'), }; +const useShouldUpdateBasedOnProps = require('mail/static/src/component_hooks/use_should_update_based_on_props/use_should_update_based_on_props.js'); const useStore = require('mail/static/src/component_hooks/use_store/use_store.js'); const { Component } = owl; diff --git a/addons/mail/static/src/components/chat_window_manager/chat_window_manager.js b/addons/mail/static/src/components/chat_window_manager/chat_window_manager.js index 88a840328ea..a0c49a9614b 100644 --- a/addons/mail/static/src/components/chat_window_manager/chat_window_manager.js +++ b/addons/mail/static/src/components/chat_window_manager/chat_window_manager.js @@ -5,6 +5,7 @@ const components = { ChatWindow: require('mail/static/src/components/chat_window/chat_window.js'), ChatWindowHiddenMenu: require('mail/static/src/components/chat_window_hidden_menu/chat_window_hidden_menu.js'), }; +const useShouldUpdateBasedOnProps = require('mail/static/src/component_hooks/use_should_update_based_on_props/use_should_update_based_on_props.js'); const useStore = require('mail/static/src/component_hooks/use_store/use_store.js'); const { Component } = owl; @@ -16,19 +17,23 @@ class ChatWindowManager extends Component { */ constructor(...args) { super(...args); + useShouldUpdateBasedOnProps(); useStore(props => { const chatWindowManager = this.env.messaging && this.env.messaging.chatWindowManager; const allOrderedVisible = chatWindowManager ? chatWindowManager.allOrderedVisible : []; return { - allOrderedVisible: allOrderedVisible.map(chatWindow => chatWindow ? chatWindow.__state : undefined), - chatWindowManager: chatWindowManager ? chatWindowManager.__state : undefined, + allOrderedVisible, + allOrderedVisibleThread: allOrderedVisible.map(chatWindow => chatWindow.thread), + chatWindowManager, + chatWindowManagerHasHiddenChatWindows: chatWindowManager && chatWindowManager.hasHiddenChatWindows, isMessagingInitialized: this.env.isMessagingInitialized(), }; }, { compareDepth: { allOrderedVisible: 1, + allOrderedVisibleThread: 1, }, }); } diff --git a/addons/mail/static/src/components/chat_window_manager/chat_window_manager_tests.js b/addons/mail/static/src/components/chat_window_manager/chat_window_manager_tests.js index 1da10ea91d7..d97ece53081 100644 --- a/addons/mail/static/src/components/chat_window_manager/chat_window_manager_tests.js +++ b/addons/mail/static/src/components/chat_window_manager/chat_window_manager_tests.js @@ -855,11 +855,12 @@ QUnit.test('[technical] chat window: scroll conservation on toggle home menu', a eventName: 'o-component-message-list-scrolled', func: () => document.querySelector('.o_NotificationList_preview').click(), message: "should wait until channel 20 scrolled to its last message after opening it from the messaging menu", - predicate: ({ scrollTop, threadViewer }) => { + predicate: ({ scrollTop, thread }) => { const messageList = document.querySelector('.o_ThreadView_messageList'); return ( - threadViewer.thread.model === 'mail.channel' && - threadViewer.thread.id === 20 && + thread && + thread.model === 'mail.channel' && + thread.id === 20 && scrollTop === messageList.scrollHeight - messageList.clientHeight ); }, @@ -871,10 +872,11 @@ QUnit.test('[technical] chat window: scroll conservation on toggle home menu', a document.querySelector(`.o_ThreadView_messageList`).scrollTop = 142; }, message: "should wait until channel 20 scrolled to 142 after setting this value manually", - predicate: ({ scrollTop, threadViewer }) => { + predicate: ({ scrollTop, thread }) => { return ( - threadViewer.thread.model === 'mail.channel' && - threadViewer.thread.id === 20 && + thread && + thread.model === 'mail.channel' && + thread.id === 20 && scrollTop === 142 ); }, @@ -890,10 +892,11 @@ QUnit.test('[technical] chat window: scroll conservation on toggle home menu', a eventName: 'o-component-message-list-scrolled', func: () => this.showHomeMenu(), message: "should wait until channel 20 restored its scroll to 142 after showing the home menu", - predicate: ({ scrollTop, threadViewer }) => { + predicate: ({ scrollTop, thread }) => { return ( - threadViewer.thread.model === 'mail.channel' && - threadViewer.thread.id === 20 && + thread && + thread.model === 'mail.channel' && + thread.id === 20 && scrollTop === 142 ); }, @@ -1623,11 +1626,12 @@ QUnit.test('chat window with a thread: keep scroll position in message list on f eventName: 'o-component-message-list-scrolled', func: () => document.querySelector('.o_NotificationList_preview').click(), message: "should wait until channel 20 scrolled to its last message after opening it from the messaging menu", - predicate: ({ scrollTop, threadViewer }) => { + predicate: ({ scrollTop, thread }) => { const messageList = document.querySelector('.o_ThreadView_messageList'); return ( - threadViewer.thread.model === 'mail.channel' && - threadViewer.thread.id === 20 && + thread && + thread.model === 'mail.channel' && + thread.id === 20 && scrollTop === messageList.scrollHeight - messageList.clientHeight ); }, @@ -1639,10 +1643,11 @@ QUnit.test('chat window with a thread: keep scroll position in message list on f document.querySelector(`.o_ThreadView_messageList`).scrollTop = 142; }, message: "should wait until channel 20 scrolled to 142 after setting this value manually", - predicate: ({ scrollTop, threadViewer }) => { + predicate: ({ scrollTop, thread }) => { return ( - threadViewer.thread.model === 'mail.channel' && - threadViewer.thread.id === 20 && + thread && + thread.model === 'mail.channel' && + thread.id === 20 && scrollTop === 142 ); }, @@ -1666,10 +1671,11 @@ QUnit.test('chat window with a thread: keep scroll position in message list on f eventName: 'o-component-message-list-scrolled', func: () => document.querySelector('.o_ChatWindow_header').click(), message: "should wait until channel 20 restored its scroll position to 142", - predicate: ({ scrollTop, threadViewer }) => { + predicate: ({ scrollTop, thread }) => { return ( - threadViewer.thread.model === 'mail.channel' && - threadViewer.thread.id === 20 && + thread && + thread.model === 'mail.channel' && + thread.id === 20 && scrollTop === 142 ); @@ -1823,11 +1829,12 @@ QUnit.test('[technical] chat window with a thread: keep scroll position in messa eventName: 'o-component-message-list-scrolled', func: () => document.querySelector('.o_NotificationList_preview').click(), message: "should wait until channel 20 scrolled to its last message after opening it from the messaging menu", - predicate: ({ scrollTop, threadViewer }) => { + predicate: ({ scrollTop, thread }) => { const messageList = document.querySelector('.o_ThreadView_messageList'); return ( - threadViewer.thread.model === 'mail.channel' && - threadViewer.thread.id === 20 && + thread && + thread.model === 'mail.channel' && + thread.id === 20 && scrollTop === messageList.scrollHeight - messageList.clientHeight ); }, @@ -1837,10 +1844,11 @@ QUnit.test('[technical] chat window with a thread: keep scroll position in messa eventName: 'o-component-message-list-scrolled', func: () => document.querySelector(`.o_ThreadView_messageList`).scrollTop = 142, message: "should wait until channel 20 scrolled to 142 after setting this value manually", - predicate: ({ scrollTop, threadViewer }) => { + predicate: ({ scrollTop, thread }) => { return ( - threadViewer.thread.model === 'mail.channel' && - threadViewer.thread.id === 20 && + thread && + thread.model === 'mail.channel' && + thread.id === 20 && scrollTop === 142 ); }, @@ -1853,10 +1861,11 @@ QUnit.test('[technical] chat window with a thread: keep scroll position in messa eventName: 'o-component-message-list-scrolled', func: () => document.querySelector('.o_ChatWindow_header').click(), message: "should wait until channel 20 restored its scroll to 142 after unfolding it", - predicate: ({ scrollTop, threadViewer }) => { + predicate: ({ scrollTop, thread }) => { return ( - threadViewer.thread.model === 'mail.channel' && - threadViewer.thread.id === 20 && + thread && + thread.model === 'mail.channel' && + thread.id === 20 && scrollTop === 142 ); }, @@ -1876,10 +1885,11 @@ QUnit.test('[technical] chat window with a thread: keep scroll position in messa eventName: 'o-component-message-list-scrolled', func: () => document.querySelector('.o_ChatWindow_header').click(), message: "should wait until channel 20 restored its scroll position to the last saved value (142)", - predicate: ({ scrollTop, threadViewer }) => { + predicate: ({ scrollTop, thread }) => { return ( - threadViewer.thread.model === 'mail.channel' && - threadViewer.thread.id === 20 && + thread && + thread.model === 'mail.channel' && + thread.id === 20 && scrollTop === 142 ); }, diff --git a/addons/mail/static/src/components/chatter/chatter.js b/addons/mail/static/src/components/chatter/chatter.js index 8c19c3ace07..42462e930f9 100644 --- a/addons/mail/static/src/components/chatter/chatter.js +++ b/addons/mail/static/src/components/chatter/chatter.js @@ -8,6 +8,7 @@ const components = { Composer: require('mail/static/src/components/composer/composer.js'), ThreadView: require('mail/static/src/components/thread_view/thread_view.js'), }; +const useShouldUpdateBasedOnProps = require('mail/static/src/component_hooks/use_should_update_based_on_props/use_should_update_based_on_props.js'); const useStore = require('mail/static/src/component_hooks/use_store/use_store.js'); const useUpdate = require('mail/static/src/component_hooks/use_update/use_update.js'); @@ -21,6 +22,7 @@ class Chatter extends Component { */ constructor(...args) { super(...args); + useShouldUpdateBasedOnProps(); useStore(props => { const chatter = this.env.models['mail.chatter'].get(props.chatterLocalId); const thread = chatter ? chatter.thread : undefined; @@ -31,7 +33,9 @@ class Chatter extends Component { return { attachments: attachments.map(attachment => attachment.__state), chatter: chatter ? chatter.__state : undefined, - thread: thread ? thread.__state : undefined, + composer: thread && thread.composer, + thread, + threadActivitiesLength: thread && thread.activities.length, }; }, { compareDepth: { diff --git a/addons/mail/static/src/components/chatter_container/chatter_container.js b/addons/mail/static/src/components/chatter_container/chatter_container.js index 19c847d01d9..2b186e62fcb 100644 --- a/addons/mail/static/src/components/chatter_container/chatter_container.js +++ b/addons/mail/static/src/components/chatter_container/chatter_container.js @@ -4,6 +4,7 @@ odoo.define('mail/static/src/components/chatter_container/chatter_container.js', const components = { Chatter: require('mail/static/src/components/chatter/chatter.js'), }; +const useShouldUpdateBasedOnProps = require('mail/static/src/component_hooks/use_should_update_based_on_props/use_should_update_based_on_props.js'); const useStore = require('mail/static/src/component_hooks/use_store/use_store.js'); const useUpdate = require('mail/static/src/component_hooks/use_update/use_update.js'); const { clear } = require('mail/static/src/model/model_field_command.js'); @@ -27,6 +28,7 @@ class ChatterContainer extends Component { super(...args); this.chatter = undefined; this._wasMessagingInitialized = false; + useShouldUpdateBasedOnProps(); useStore(props => { const isMessagingInitialized = this.env.isMessagingInitialized(); // Delay creation of chatter record until messaging is initialized. diff --git a/addons/mail/static/src/components/chatter_topbar/chatter_topbar.js b/addons/mail/static/src/components/chatter_topbar/chatter_topbar.js index aaed5266514..41d2a4615f0 100644 --- a/addons/mail/static/src/components/chatter_topbar/chatter_topbar.js +++ b/addons/mail/static/src/components/chatter_topbar/chatter_topbar.js @@ -5,6 +5,7 @@ const components = { FollowButton: require('mail/static/src/components/follow_button/follow_button.js'), FollowerListMenu: require('mail/static/src/components/follower_list_menu/follower_list_menu.js'), }; +const useShouldUpdateBasedOnProps = require('mail/static/src/component_hooks/use_should_update_based_on_props/use_should_update_based_on_props.js'); const useStore = require('mail/static/src/component_hooks/use_store/use_store.js'); const { Component } = owl; @@ -16,6 +17,7 @@ class ChatterTopbar extends Component { */ constructor(...args) { super(...args); + useShouldUpdateBasedOnProps(); useStore(props => { const chatter = this.env.models['mail.chatter'].get(props.chatterLocalId); const thread = chatter ? chatter.thread : undefined; @@ -23,7 +25,7 @@ class ChatterTopbar extends Component { return { areThreadAttachmentsLoaded: thread && thread.areAttachmentsLoaded, chatter: chatter ? chatter.__state : undefined, - composer: chatter && chatter.composer ? chatter.composer.__state : undefined, + composerIsLog: chatter && chatter.composer && chatter.composer.isLog, threadAttachmentsAmount: threadAttachments.length, }; }); diff --git a/addons/mail/static/src/components/composer/composer.js b/addons/mail/static/src/components/composer/composer.js index 991d5fc4482..31654a4f15c 100644 --- a/addons/mail/static/src/components/composer/composer.js +++ b/addons/mail/static/src/components/composer/composer.js @@ -11,8 +11,9 @@ const components = { ThreadTextualTypingStatus: require('mail/static/src/components/thread_textual_typing_status/thread_textual_typing_status.js'), }; const useDragVisibleDropZone = require('mail/static/src/component_hooks/use_drag_visible_dropzone/use_drag_visible_dropzone.js'); -const useUpdate = require('mail/static/src/component_hooks/use_update/use_update.js'); +const useShouldUpdateBasedOnProps = require('mail/static/src/component_hooks/use_should_update_based_on_props/use_should_update_based_on_props.js'); const useStore = require('mail/static/src/component_hooks/use_store/use_store.js'); +const useUpdate = require('mail/static/src/component_hooks/use_update/use_update.js'); const { isEventHandled, markEventHandled, @@ -29,15 +30,33 @@ class Composer extends Component { constructor(...args) { super(...args); this.isDropZoneVisible = useDragVisibleDropZone(); + useShouldUpdateBasedOnProps({ + compareDepth: { + textInputSendShortcuts: 1, + }, + }); useStore(props => { const composer = this.env.models['mail.composer'].get(props.composerLocalId); + const thread = composer && composer.thread; return { - composer: composer ? composer.__state : undefined, + composer, + composerAttachments: composer ? composer.attachments : [], + composerCanPostMessage: composer && composer.canPostMessage, + composerHasFocus: composer && composer.hasFocus, + composerIsLog: composer && composer.isLog, + composerSubjectContent: composer && composer.subjectContent, isDeviceMobile: this.env.messaging.device.isMobile, - thread: composer && composer.thread - ? composer.thread.__state - : undefined, + thread, + threadChannelType: thread && thread.channel_type, // for livechat override + threadDisplayName: thread && thread.displayName, + threadMassMailing: thread && thread.mass_mailing, + threadModel: thread && thread.model, + threadName: thread && thread.name, }; + }, { + compareDepth: { + composerAttachments: 1, + }, }); useUpdate({ func: () => this._update() }); /** @@ -198,6 +217,9 @@ class Composer extends Component { * @private */ _update() { + if (this.props.isDoFocus) { + this.focus(); + } if (!this.composer) { return; } @@ -364,7 +386,6 @@ class Composer extends Component { Object.assign(Composer, { components, defaultProps: { - attachmentLocalIds: [], hasCurrentPartnerAvatar: true, hasDiscardButton: false, hasFollowers: false, @@ -372,13 +393,10 @@ Object.assign(Composer, { hasThreadName: false, hasThreadTyping: false, isCompact: true, + isDoFocus: false, isExpandable: false, }, props: { - attachmentLocalIds: { - type: Array, - element: String, - }, attachmentsDetailsMode: { type: String, optional: true, @@ -394,6 +412,10 @@ Object.assign(Composer, { hasSendButton: Boolean, hasThreadName: Boolean, hasThreadTyping: Boolean, + /** + * Determines whether this should become focused. + */ + isDoFocus: Boolean, showAttachmentsExtensions: { type: Boolean, optional: true, @@ -402,15 +424,6 @@ Object.assign(Composer, { type: Boolean, optional: true, }, - initialAttachmentLocalIds: { - type: Array, - element: String, - optional: true, - }, - initialTextInputHtmlContent: { - type: String, - optional: true, - }, isCompact: Boolean, isExpandable: Boolean, /** diff --git a/addons/mail/static/src/components/composer/composer_tests.js b/addons/mail/static/src/components/composer/composer_tests.js index b93670aaf97..5a6b786cea5 100644 --- a/addons/mail/static/src/components/composer/composer_tests.js +++ b/addons/mail/static/src/components/composer/composer_tests.js @@ -820,8 +820,6 @@ QUnit.test("channel with no commands should not prompt any command suggestions o await afterNextRender(() => { document.querySelector('.o_ComposerTextInput_textarea').focus(); document.execCommand('insertText', false, "/"); - }); - await afterNextRender(() => { const composer_text_input = document.querySelector('.o_ComposerTextInput_textarea'); composer_text_input.dispatchEvent(new window.KeyboardEvent('keydown')); composer_text_input.dispatchEvent(new window.KeyboardEvent('keyup')); diff --git a/addons/mail/static/src/components/composer_suggested_recipient/composer_suggested_recipient.js b/addons/mail/static/src/components/composer_suggested_recipient/composer_suggested_recipient.js index f9b3944454b..efedf6629f8 100644 --- a/addons/mail/static/src/components/composer_suggested_recipient/composer_suggested_recipient.js +++ b/addons/mail/static/src/components/composer_suggested_recipient/composer_suggested_recipient.js @@ -1,6 +1,7 @@ odoo.define('mail/static/src/components/composer_suggested_recipient/composer_suggested_recipient.js', function (require) { 'use strict'; +const useShouldUpdateBasedOnProps = require('mail/static/src/component_hooks/use_should_update_based_on_props/use_should_update_based_on_props.js'); const useStore = require('mail/static/src/component_hooks/use_store/use_store.js'); const useUpdate = require('mail/static/src/component_hooks/use_update/use_update.js'); @@ -30,7 +31,7 @@ class ComposerSuggestedRecipient extends Component { constructor(...args) { super(...args); this.id = _.uniqueId('o_ComposerSuggestedRecipient_'); - + useShouldUpdateBasedOnProps(); useStore(props => { const suggestedRecipientInfo = this.env.models['mail.suggested_recipient_info'].get(props.suggestedRecipientLocalId); const partner = suggestedRecipientInfo && suggestedRecipientInfo.partner; diff --git a/addons/mail/static/src/components/composer_suggested_recipient_list/composer_suggested_recipient_list.js b/addons/mail/static/src/components/composer_suggested_recipient_list/composer_suggested_recipient_list.js index 330072960bd..61da098c37c 100644 --- a/addons/mail/static/src/components/composer_suggested_recipient_list/composer_suggested_recipient_list.js +++ b/addons/mail/static/src/components/composer_suggested_recipient_list/composer_suggested_recipient_list.js @@ -1,6 +1,7 @@ odoo.define('mail/static/src/components/composer_suggested_recipient_list/composer_suggested_recipient_list.js', function (require) { 'use strict'; +const useShouldUpdateBasedOnProps = require('mail/static/src/component_hooks/use_should_update_based_on_props/use_should_update_based_on_props.js'); const useStore = require('mail/static/src/component_hooks/use_store/use_store.js'); const { Component } = owl; @@ -17,15 +18,19 @@ class ComposerSuggestedRecipientList extends Component { */ constructor(...args) { super(...args); + useShouldUpdateBasedOnProps(); this.state = useState({ hasShowMoreButton: false, }); - useStore(props => { const thread = this.env.models['mail.thread'].get(props.threadLocalId); return { - thread: thread ? thread.__state : undefined, + threadSuggestedRecipientInfoList: thread ? thread.suggestedRecipientInfoList : [], }; + }, { + compareDepth: { + threadSuggestedRecipientInfoList: 1, + }, }); } diff --git a/addons/mail/static/src/components/composer_suggestion/composer_suggestion.js b/addons/mail/static/src/components/composer_suggestion/composer_suggestion.js index 308dee76821..4ae48db623b 100644 --- a/addons/mail/static/src/components/composer_suggestion/composer_suggestion.js +++ b/addons/mail/static/src/components/composer_suggestion/composer_suggestion.js @@ -1,6 +1,7 @@ odoo.define('mail/static/src/components/composer_suggestion/composer_suggestion.js', function (require) { 'use strict'; +const useShouldUpdateBasedOnProps = require('mail/static/src/component_hooks/use_should_update_based_on_props/use_should_update_based_on_props.js'); const useStore = require('mail/static/src/component_hooks/use_store/use_store.js'); const useUpdate = require('mail/static/src/component_hooks/use_update/use_update.js'); @@ -17,11 +18,12 @@ class ComposerSuggestion extends Component { */ constructor(...args) { super(...args); + useShouldUpdateBasedOnProps(); useStore(props => { const composer = this.env.models['mail.composer'].get(this.props.composerLocalId); const record = this.env.models[props.modelName].get(props.recordLocalId); return { - composer: composer && composer.__state, + composerHasToScrollToActiveSuggestion: composer && composer.hasToScrollToActiveSuggestion, record: record ? record.__state : undefined, }; }); diff --git a/addons/mail/static/src/components/composer_suggestion_list/composer_suggestion_list.js b/addons/mail/static/src/components/composer_suggestion_list/composer_suggestion_list.js index c033feb28ed..ea2153dbf6f 100644 --- a/addons/mail/static/src/components/composer_suggestion_list/composer_suggestion_list.js +++ b/addons/mail/static/src/components/composer_suggestion_list/composer_suggestion_list.js @@ -4,6 +4,7 @@ odoo.define('mail/static/src/components/composer_suggestion_list/composer_sugges const components = { ComposerSuggestion: require('mail/static/src/components/composer_suggestion/composer_suggestion.js'), }; +const useShouldUpdateBasedOnProps = require('mail/static/src/component_hooks/use_should_update_based_on_props/use_should_update_based_on_props.js'); const useStore = require('mail/static/src/component_hooks/use_store/use_store.js'); const { Component } = owl; @@ -15,6 +16,7 @@ class ComposerSuggestionList extends Component { */ constructor(...args) { super(...args); + useShouldUpdateBasedOnProps(); useStore(props => { const composer = this.env.models['mail.composer'].get(props.composerLocalId); const activeSuggestedRecord = composer @@ -27,14 +29,11 @@ class ComposerSuggestionList extends Component { ? composer.mainSuggestedRecordsList : []; return { - activeSuggestedRecord: activeSuggestedRecord ? activeSuggestedRecord.__state : undefined, - composer: composer ? composer.__state : undefined, - extraSuggestedRecordsList: extraSuggestedRecordsList - ? extraSuggestedRecordsList.map(record => record.__state) - : [], - mainSuggestedRecordsList: mainSuggestedRecordsList - ? mainSuggestedRecordsList.map(record => record.__state) - : [], + activeSuggestedRecord, + composer, + composerSuggestionModelName: composer && composer.suggestionModelName, + extraSuggestedRecordsList: extraSuggestedRecordsList, + mainSuggestedRecordsList: mainSuggestedRecordsList, }; }, { compareDepth: { diff --git a/addons/mail/static/src/components/composer_text_input/composer_text_input.js b/addons/mail/static/src/components/composer_text_input/composer_text_input.js index 75bce5ac833..bf9ec6bdb59 100644 --- a/addons/mail/static/src/components/composer_text_input/composer_text_input.js +++ b/addons/mail/static/src/components/composer_text_input/composer_text_input.js @@ -1,6 +1,7 @@ odoo.define('mail/static/src/components/composer_text_input/composer_text_input.js', function (require) { 'use strict'; +const useShouldUpdateBasedOnProps = require('mail/static/src/component_hooks/use_should_update_based_on_props/use_should_update_based_on_props.js'); const useStore = require('mail/static/src/component_hooks/use_store/use_store.js'); const useUpdate = require('mail/static/src/component_hooks/use_update/use_update.js'); @@ -12,9 +13,6 @@ const { markEventHandled } = require('mail/static/src/utils/utils.js'); const { Component } = owl; const { useRef } = owl.hooks; -/** - * ComposerInput relies on a minimal HTML editor in order to support mentions. - */ class ComposerTextInput extends Component { /** @@ -22,15 +20,29 @@ class ComposerTextInput extends Component { */ constructor(...args) { super(...args); + useShouldUpdateBasedOnProps({ + compareDepth: { + sendShortcuts: 1, + }, + }); useStore(props => { const composer = this.env.models['mail.composer'].get(props.composerLocalId); const thread = composer && composer.thread; const correspondent = thread ? thread.correspondent : undefined; return { - composer: composer ? composer.__state : undefined, - correspondent: correspondent ? correspondent.__state : undefined, + composerHasFocus: composer && composer.hasFocus, + composerHasSuggestions: composer && composer.hasSuggestions, + composerIsLastStateChangeProgrammatic: composer && composer.isLastStateChangeProgrammatic, + composerIsLog: composer && composer.isLog, + composerTextInputContent: composer && composer.textInputContent, + composerTextInputCursorEnd: composer && composer.textInputCursorEnd, + composerTextInputCursorStart: composer && composer.textInputCursorStart, + composerTextInputSelectionDirection: composer && composer.textInputSelectionDirection, + correspondent, + correspondentNameOrDisplayName: correspondent && correspondent.nameOrDisplayName, isDeviceMobile: this.env.messaging.device.isMobile, - thread: thread ? thread.__state : undefined, + threadDisplayName: thread && thread.displayName, + threadModel: thread && thread.model, }; }); /** @@ -78,7 +90,7 @@ class ComposerTextInput extends Component { } if (this.composer.thread.model === 'mail.channel') { if (this.composer.thread.correspondent) { - return _.str.sprintf("Message %s...", this.composer.thread.correspondent.nameOrDisplayName) + return _.str.sprintf("Message %s...", this.composer.thread.correspondent.nameOrDisplayName); } return _.str.sprintf("Message #%s...", this.composer.thread.displayName); } @@ -196,6 +208,7 @@ class ComposerTextInput extends Component { */ _onFocusinTextarea() { this.composer.focus(); + this.trigger('o-focusin-composer'); } /** diff --git a/addons/mail/static/src/components/dialog/dialog.js b/addons/mail/static/src/components/dialog/dialog.js index fa15f1c4b23..65902c9ce23 100644 --- a/addons/mail/static/src/components/dialog/dialog.js +++ b/addons/mail/static/src/components/dialog/dialog.js @@ -1,6 +1,7 @@ odoo.define('mail/static/src/components/dialog/dialog.js', function (require) { 'use strict'; +const useShouldUpdateBasedOnProps = require('mail/static/src/component_hooks/use_should_update_based_on_props/use_should_update_based_on_props.js'); const useStore = require('mail/static/src/component_hooks/use_store/use_store.js'); const patchMixin = require('web.patchMixin'); @@ -21,6 +22,7 @@ class Dialog extends Component { this._componentRef = useRef('component'); this._onClickGlobal = this._onClickGlobal.bind(this); this._onKeydownDocument = this._onKeydownDocument.bind(this); + useShouldUpdateBasedOnProps(); useStore(props => { const dialog = this.env.models['mail.dialog'].get(props.dialogLocalId); return { diff --git a/addons/mail/static/src/components/dialog_manager/dialog_manager.js b/addons/mail/static/src/components/dialog_manager/dialog_manager.js index 9b1901577e1..69b64a27ff5 100644 --- a/addons/mail/static/src/components/dialog_manager/dialog_manager.js +++ b/addons/mail/static/src/components/dialog_manager/dialog_manager.js @@ -4,6 +4,7 @@ odoo.define('mail/static/src/components/dialog_manager/dialog_manager.js', funct const components = { Dialog: require('mail/static/src/components/dialog/dialog.js'), }; +const useShouldUpdateBasedOnProps = require('mail/static/src/component_hooks/use_should_update_based_on_props/use_should_update_based_on_props.js'); const useStore = require('mail/static/src/component_hooks/use_store/use_store.js'); const { Component } = owl; @@ -15,6 +16,7 @@ class DialogManager extends Component { */ constructor(...args) { super(...args); + useShouldUpdateBasedOnProps(); useStore(props => { const dialogManager = this.env.messaging && this.env.messaging.dialogManager; return { diff --git a/addons/mail/static/src/components/discuss/discuss.js b/addons/mail/static/src/components/discuss/discuss.js index ff577956c75..e303e495015 100644 --- a/addons/mail/static/src/components/discuss/discuss.js +++ b/addons/mail/static/src/components/discuss/discuss.js @@ -12,8 +12,10 @@ const components = { NotificationList: require('mail/static/src/components/notification_list/notification_list.js'), ThreadView: require('mail/static/src/components/thread_view/thread_view.js'), }; +const useShouldUpdateBasedOnProps = require('mail/static/src/component_hooks/use_should_update_based_on_props/use_should_update_based_on_props.js'); const useStore = require('mail/static/src/component_hooks/use_store/use_store.js'); -const useUpdate = require('mail/static/src/component_hooks/use_update/use_update.js'); + +const patchMixin = require('web.patchMixin'); const { Component } = owl; const { useRef } = owl.hooks; @@ -25,27 +27,13 @@ class Discuss extends Component { */ constructor(...args) { super(...args); - useStore(props => { - const discuss = this.env.messaging && this.env.messaging.discuss; - const threadView = discuss && discuss.threadView; - return { - checkedMessages: threadView ? threadView.checkedMessages.map(message => message.__state) : [], - discuss: discuss ? discuss.__state : undefined, - isDeviceMobile: this.env.messaging && this.env.messaging.device.isMobile, - isMessagingInitialized: this.env.isMessagingInitialized(), - thread: discuss && discuss.thread ? discuss.thread.__state : undefined, - threadCache: (threadView && threadView.threadCache) - ? threadView.threadCache.__state - : undefined, - uncheckedMessages: threadView ? threadView.uncheckedMessages.map(message => message.__state) : [], - }; - }, { + useShouldUpdateBasedOnProps(); + useStore((...args) => this._useStoreSelector(...args), { compareDepth: { checkedMessages: 1, uncheckedMessages: 1, }, }); - useUpdate({ func: () => this._update() }); this._updateLocalStoreProps(); /** * Reference of the composer. Useful to focus it. @@ -64,7 +52,7 @@ class Discuss extends Component { this.discuss.update({ isOpen: true }); if (this.discuss.thread) { this.trigger('o-push-state-action-manager'); - } else if (this.env.messaging.isInitialized) { + } else if (this.env.isMessagingInitialized()) { this.discuss.openInitThread(); } this._updateLocalStoreProps(); @@ -142,27 +130,6 @@ class Discuss extends Component { // Private //-------------------------------------------------------------------------- - /** - * @private - */ - _update() { - if (!this.discuss) { - return; - } - if (this.discuss.isDoFocus) { - this.discuss.update({ isDoFocus: false }); - const composer = this._composerRef.comp; - if (composer) { - composer.focus(); - } else { - const threadView = this._threadViewRef.comp; - if (threadView) { - threadView.focus(); - } - } - } - } - /** * @private */ @@ -187,6 +154,50 @@ class Discuss extends Component { ); } + /** + * Returns data selected from the store. + * + * @private + * @param {Object} props + * @returns {Object} + */ + _useStoreSelector(props) { + const discuss = this.env.messaging && this.env.messaging.discuss; + const thread = discuss && discuss.thread; + const threadView = discuss && discuss.threadView; + const replyingToMessage = discuss && discuss.replyingToMessage; + const replyingToMessageOriginThread = replyingToMessage && replyingToMessage.originThread; + const checkedMessages = threadView ? threadView.checkedMessages : []; + return { + checkedMessages, + checkedMessagesIsModeratedByCurrentPartner: checkedMessages && checkedMessages.some(message => message.isModeratedByCurrentPartner), // for widget + discuss, + discussActiveId: discuss && discuss.activeId, // for widget + discussActiveMobileNavbarTabId: discuss && discuss.activeMobileNavbarTabId, + discussHasModerationDiscardDialog: discuss && discuss.hasModerationDiscardDialog, + discussHasModerationRejectDialog: discuss && discuss.hasModerationRejectDialog, + discussIsAddingChannel: discuss && discuss.isAddingChannel, + discussIsAddingChat: discuss && discuss.isAddingChat, + discussIsDoFocus: discuss && discuss.isDoFocus, + discussReplyingToMessageOriginThreadComposer: replyingToMessageOriginThread && replyingToMessageOriginThread.composer, + inbox: this.env.messaging.inbox, + isDeviceMobile: this.env.messaging && this.env.messaging.device.isMobile, + isMessagingInitialized: this.env.isMessagingInitialized(), + replyingToMessage, + starred: this.env.messaging.starred, // for widget + thread, + threadCache: threadView && threadView.threadCache, + threadChannelType: thread && thread.channel_type, // for widget + threadDisplayName: thread && thread.displayName, // for widget + threadCounter: thread && thread.counter, + threadModel: thread && thread.model, + threadPublic: thread && thread.public, // for widget + threadView, + threadViewMessagesLength: threadView && threadView.messages.length, // for widget + uncheckedMessages: threadView ? threadView.uncheckedMessages : [], // for widget + }; + } + //-------------------------------------------------------------------------- // Handlers //-------------------------------------------------------------------------- @@ -205,6 +216,14 @@ class Discuss extends Component { this.discuss.update({ hasModerationRejectDialog: false }); } + /** + * @private + * @param {CustomEvent} ev + */ + _onFocusinComposer(ev) { + this.discuss.update({ isDoFocus: false }); + } + /** * @private * @param {CustomEvent} ev @@ -289,6 +308,6 @@ Object.assign(Discuss, { template: 'mail.Discuss', }); -return Discuss; +return patchMixin(Discuss); }); diff --git a/addons/mail/static/src/components/discuss/discuss.xml b/addons/mail/static/src/components/discuss/discuss.xml index fa190895e16..1fadb224ce1 100644 --- a/addons/mail/static/src/components/discuss/discuss.xml +++ b/addons/mail/static/src/components/discuss/discuss.xml @@ -60,7 +60,9 @@ hasSquashCloseMessages="discuss.thread.model !== 'mail.box'" haveMessagesMarkAsReadIcon="discuss.thread === env.messaging.inbox" haveMessagesReplyIcon="discuss.thread === env.messaging.inbox" + isDoFocus="discuss.isDoFocus" threadViewLocalId="discuss.threadView.localId" + t-on-o-focusin-composer="_onFocusinComposer" t-on-o-rendered="_onThreadRendered" t-ref="threadView" /> @@ -91,7 +93,9 @@ hasCurrentPartnerAvatar="!env.messaging.device.isMobile" hasDiscardButton="true" hasThreadName="true" + isDoFocus="discuss.isDoFocus" textInputSendShortcuts="['ctrl-enter', 'meta-enter']" + t-on-o-focusin-composer="_onFocusinComposer" t-on-o-message-posted="_onReplyingToMessageMessagePosted" t-ref="composer" /> diff --git a/addons/mail/static/src/components/discuss/tests/discuss_tests.js b/addons/mail/static/src/components/discuss/tests/discuss_tests.js index 97b826487cd..59a2b8cc239 100644 --- a/addons/mail/static/src/components/discuss/tests/discuss_tests.js +++ b/addons/mail/static/src/components/discuss/tests/discuss_tests.js @@ -1709,11 +1709,12 @@ QUnit.test('auto-scroll to bottom of thread', async function (assert) { waitUntilEvent: { eventName: 'o-component-message-list-scrolled', message: "should wait until channel 20 scrolled to its last message initially", - predicate: ({ scrollTop, threadViewer }) => { + predicate: ({ scrollTop, thread }) => { const messageList = document.querySelector('.o_ThreadView_messageList'); return ( - threadViewer.thread.model === 'mail.channel' && - threadViewer.thread.id === 20 && + thread && + thread.model === 'mail.channel' && + thread.id === 20 && scrollTop === messageList.scrollHeight - messageList.clientHeight ); }, @@ -1756,11 +1757,12 @@ QUnit.test('load more messages from channel (auto-load on scroll)', async functi waitUntilEvent: { eventName: 'o-component-message-list-scrolled', message: "should wait until channel 20 scrolled to its last message initially", - predicate: ({ scrollTop, threadViewer }) => { + predicate: ({ scrollTop, thread }) => { const messageList = document.querySelector(`.o_Discuss_thread .o_ThreadView_messageList`); return ( - threadViewer.thread.model === 'mail.channel' && - threadViewer.thread.id === 20 && + thread && + thread.model === 'mail.channel' && + thread.id === 20 && scrollTop === messageList.scrollHeight - messageList.clientHeight ); }, @@ -1844,11 +1846,12 @@ QUnit.test('new messages separator [REQUIRE FOCUS]', async function (assert) { waitUntilEvent: { eventName: 'o-component-message-list-scrolled', message: "should wait until channel 20 scrolled to its last message initially", - predicate: ({ scrollTop, threadViewer }) => { + predicate: ({ scrollTop, thread }) => { const messageList = document.querySelector(`.o_Discuss_thread .o_ThreadView_messageList`); return ( - threadViewer.thread.model === 'mail.channel' && - threadViewer.thread.id === 20 && + thread && + thread.model === 'mail.channel' && + thread.id === 20 && scrollTop === messageList.scrollHeight - messageList.clientHeight ); }, @@ -1872,10 +1875,11 @@ QUnit.test('new messages separator [REQUIRE FOCUS]', async function (assert) { document.querySelector(`.o_Discuss_thread .o_ThreadView_messageList`).scrollTop = 0; }, message: "should wait until channel scrolled to top", - predicate: ({ scrollTop, threadViewer }) => { + predicate: ({ scrollTop, thread }) => { return ( - threadViewer.thread.model === 'mail.channel' && - threadViewer.thread.id === 20 && + thread && + thread.model === 'mail.channel' && + thread.id === 20 && scrollTop === 0 ); }, @@ -1912,11 +1916,12 @@ QUnit.test('new messages separator [REQUIRE FOCUS]', async function (assert) { messageList.scrollTop = messageList.scrollHeight - messageList.clientHeight; }, message: "should wait until channel scrolled to bottom", - predicate: ({ scrollTop, threadViewer }) => { + predicate: ({ scrollTop, thread }) => { const messageList = document.querySelector(`.o_Discuss_thread .o_ThreadView_messageList`); return ( - threadViewer.thread.model === 'mail.channel' && - threadViewer.thread.id === 20 && + thread && + thread.model === 'mail.channel' && + thread.id === 20 && scrollTop === messageList.scrollHeight - messageList.clientHeight ); }, @@ -1973,8 +1978,8 @@ QUnit.test('restore thread scroll position', async function (assert) { waitUntilEvent: { eventName: 'o-component-message-list-scrolled', message: "should wait until channel 11 scrolled to its last message", - predicate: ({ threadViewer }) => { - return threadViewer.thread.model === 'mail.channel' && threadViewer.thread.id === 11; + predicate: ({ thread }) => { + return thread && thread.model === 'mail.channel' && thread.id === 11; }, }, }); @@ -1999,8 +2004,8 @@ QUnit.test('restore thread scroll position', async function (assert) { eventName: 'o-component-message-list-scrolled', func: () => document.querySelector(`.o_Discuss_thread .o_ThreadView_messageList`).scrollTop = 0, message: "should wait until channel 11 changed its scroll position to top", - predicate: ({ threadViewer }) => { - return threadViewer.thread.model === 'mail.channel' && threadViewer.thread.id === 11; + predicate: ({ thread }) => { + return thread && thread.model === 'mail.channel' && thread.id === 11; }, }); assert.strictEqual( @@ -2028,11 +2033,12 @@ QUnit.test('restore thread scroll position', async function (assert) { `).click(); }, message: "should wait until channel 12 scrolled to its last message", - predicate: ({ scrollTop, threadViewer }) => { + predicate: ({ scrollTop, thread }) => { const messageList = document.querySelector('.o_ThreadView_messageList'); return ( - threadViewer.thread.model === 'mail.channel' && - threadViewer.thread.id === 12 && + thread && + thread.model === 'mail.channel' && + thread.id === 12 && scrollTop === messageList.scrollHeight - messageList.clientHeight ); }, @@ -2060,10 +2066,11 @@ QUnit.test('restore thread scroll position', async function (assert) { `).click(); }, message: "should wait until channel 11 restored its scroll position", - predicate: ({ scrollTop, threadViewer }) => { + predicate: ({ scrollTop, thread }) => { return ( - threadViewer.thread.model === 'mail.channel' && - threadViewer.thread.id === 11 && + thread && + thread.model === 'mail.channel' && + thread.id === 11 && scrollTop === 0 ); }, @@ -2089,11 +2096,12 @@ QUnit.test('restore thread scroll position', async function (assert) { `).click(); }, message: "should wait until channel 12 recovered its scroll position (to bottom)", - predicate: ({ scrollTop, threadViewer }) => { + predicate: ({ scrollTop, thread }) => { const messageList = document.querySelector('.o_ThreadView_messageList'); return ( - threadViewer.thread.model === 'mail.channel' && - threadViewer.thread.id === 12 && + thread && + thread.model === 'mail.channel' && + thread.id === 12 && scrollTop === messageList.scrollHeight - messageList.clientHeight ); }, @@ -3669,11 +3677,12 @@ QUnit.test('load recent messages from thread (already loaded some old messages)' `).click(); }, message: "should wait until channel scrolled to bottom after opening it from the discuss sidebar", - predicate: ({ scrollTop, threadViewer }) => { + predicate: ({ scrollTop, thread }) => { const messageList = document.querySelector('.o_ThreadView_messageList'); return ( - threadViewer.thread.model === 'mail.channel' && - threadViewer.thread.id === 20 && + thread && + thread.model === 'mail.channel' && + thread.id === 20 && scrollTop === messageList.scrollHeight - messageList.clientHeight ); }, @@ -3975,7 +3984,7 @@ QUnit.test('mark a single message as read should only move this message to "Hist }); QUnit.test('all messages in "Inbox" in "History" after marked all as read', async function (assert) { - assert.expect(4); + assert.expect(2); const messageOffset = 200; for (let id = messageOffset; id < messageOffset + 40; id++) { @@ -3998,22 +4007,18 @@ QUnit.test('all messages in "Inbox" in "History" after marked all as read', asyn waitUntilEvent: { eventName: 'o-component-message-list-scrolled', message: "should wait until inbox scrolled to its last message initially", - predicate: ({ scrollTop, threadViewer }) => { + predicate: ({ orderedMessages, scrollTop, thread }) => { const messageList = document.querySelector(`.o_Discuss_thread .o_ThreadView_messageList`); return ( - threadViewer.thread.model === 'mail.box' && - threadViewer.thread.id === 'inbox' && + thread && + thread.model === 'mail.box' && + thread.id === 'inbox' && + orderedMessages.length === 30 && scrollTop === messageList.scrollHeight - messageList.clientHeight ); }, }, }); - assert.containsN( - document.body, - '.o_Message', - 30, - "there should be 30 messages that are loaded in Inbox" - ); await afterNextRender(async () => { const markAllReadButton = document.querySelector('.o_widget_Discuss_controlPanelButtonMarkAllRead'); @@ -4035,21 +4040,17 @@ QUnit.test('all messages in "Inbox" in "History" after marked all as read', asyn `).click(); }, message: "should wait until history scrolled to its last message after opening it from the discuss sidebar", - predicate: ({ scrollTop, threadViewer }) => { + predicate: ({ orderedMessages, scrollTop, thread }) => { const messageList = document.querySelector('.o_MessageList'); return ( - threadViewer.thread.model === 'mail.box' && - threadViewer.thread.id === 'history' && + thread && + thread.model === 'mail.box' && + thread.id === 'history' && + orderedMessages.length === 30 && scrollTop === messageList.scrollHeight - messageList.clientHeight ); }, }); - assert.containsN( - document.body, - '.o_Message', - 30, - "there should be 30 messages in History" - ); // simulate a scroll to top to load more messages await this.afterEvent({ diff --git a/addons/mail/static/src/components/discuss_mobile_mailbox_selection/discuss_mobile_mailbox_selection.js b/addons/mail/static/src/components/discuss_mobile_mailbox_selection/discuss_mobile_mailbox_selection.js index d2fa4aa936a..b78faa67659 100644 --- a/addons/mail/static/src/components/discuss_mobile_mailbox_selection/discuss_mobile_mailbox_selection.js +++ b/addons/mail/static/src/components/discuss_mobile_mailbox_selection/discuss_mobile_mailbox_selection.js @@ -1,6 +1,7 @@ odoo.define('mail/static/src/components/discuss_mobile_mailbox_selection/discuss_mobile_mailbox_selection.js', function (require) { 'use strict'; +const useShouldUpdateBasedOnProps = require('mail/static/src/component_hooks/use_should_update_based_on_props/use_should_update_based_on_props.js'); const useStore = require('mail/static/src/component_hooks/use_store/use_store.js'); const { Component } = owl; @@ -12,6 +13,7 @@ class DiscussMobileMailboxSelection extends Component { */ constructor(...args) { super(...args); + useShouldUpdateBasedOnProps(); useStore(props => { return { allOrderedAndPinnedMailboxes: this.orderedMailboxes.map(mailbox => mailbox.__state), diff --git a/addons/mail/static/src/components/discuss_sidebar/discuss_sidebar.js b/addons/mail/static/src/components/discuss_sidebar/discuss_sidebar.js index 0d567df080b..09b512aaad7 100644 --- a/addons/mail/static/src/components/discuss_sidebar/discuss_sidebar.js +++ b/addons/mail/static/src/components/discuss_sidebar/discuss_sidebar.js @@ -5,6 +5,7 @@ const components = { AutocompleteInput: require('mail/static/src/components/autocomplete_input/autocomplete_input.js'), DiscussSidebarItem: require('mail/static/src/components/discuss_sidebar_item/discuss_sidebar_item.js'), }; +const useShouldUpdateBasedOnProps = require('mail/static/src/component_hooks/use_should_update_based_on_props/use_should_update_based_on_props.js'); const useStore = require('mail/static/src/component_hooks/use_store/use_store.js'); const useUpdate = require('mail/static/src/component_hooks/use_update/use_update.js'); @@ -18,9 +19,10 @@ class DiscussSidebar extends Component { */ constructor(...args) { super(...args); + useShouldUpdateBasedOnProps(); useStore( (...args) => this._useStoreSelector(...args), - { compareDepth: () => this._useStoreCompareDepth() } + { compareDepth: this._useStoreCompareDepth() } ); useUpdate({ func: () => this._update() }); /** @@ -150,7 +152,6 @@ class DiscussSidebar extends Component { allOrderedAndPinnedChats: 1, allOrderedAndPinnedMailboxes: 1, allOrderedAndPinnedMultiUserChannels: 1, - allPinnedChannelAmount: 1, }; } @@ -160,33 +161,20 @@ class DiscussSidebar extends Component { * @returns {Object} */ _useStoreSelector(props) { + const discuss = this.env.messaging.discuss; return { - allOrderedAndPinnedChats: - this.env.models['mail.thread'] - .all(thread => - thread.channel_type === 'chat' && - thread.isPinned && - thread.model === 'mail.channel' - ) - .sort((c1, c2) => c1.displayName < c2.displayName ? -1 : 1) - .map(chat => chat.__state), - allOrderedAndPinnedMailboxes: this.orderedMailboxes.map(mailbox => mailbox.__state), - allOrderedAndPinnedMultiUserChannels: - this.env.models['mail.thread'] - .all(thread => - thread.channel_type === 'channel' && - thread.isPinned && - thread.model === 'mail.channel' - ) - .sort((c1, c2) => c1.displayName < c2.displayName ? -1 : 1) - .map(channel => channel.__state), + allOrderedAndPinnedChats: this.quickSearchPinnedAndOrderedChats, + allOrderedAndPinnedMailboxes: this.orderedMailboxes, + allOrderedAndPinnedMultiUserChannels: this.quickSearchOrderedAndPinnedMultiUserChannels, allPinnedChannelAmount: this.env.models['mail.thread'] .all(thread => thread.isPinned && thread.model === 'mail.channel' ).length, - discuss: this.env.messaging.discuss.__state, + discussIsAddingChannel: discuss && discuss.isAddingChannel, + discussIsAddingChat: discuss && discuss.isAddingChat, + discussSidebarQuickSearchValue: discuss && discuss.sidebarQuickSearchValue, }; } diff --git a/addons/mail/static/src/components/discuss_sidebar_item/discuss_sidebar_item.js b/addons/mail/static/src/components/discuss_sidebar_item/discuss_sidebar_item.js index e513914d2f9..0226035c2ba 100644 --- a/addons/mail/static/src/components/discuss_sidebar_item/discuss_sidebar_item.js +++ b/addons/mail/static/src/components/discuss_sidebar_item/discuss_sidebar_item.js @@ -5,6 +5,7 @@ const components = { EditableText: require('mail/static/src/components/editable_text/editable_text.js'), ThreadIcon: require('mail/static/src/components/thread_icon/thread_icon.js'), }; +const useShouldUpdateBasedOnProps = require('mail/static/src/component_hooks/use_should_update_based_on_props/use_should_update_based_on_props.js'); const useStore = require('mail/static/src/component_hooks/use_store/use_store.js'); const { isEventHandled } = require('mail/static/src/utils/utils.js'); @@ -19,13 +20,25 @@ class DiscussSidebarItem extends Component { */ constructor(...args) { super(...args); + useShouldUpdateBasedOnProps(); useStore(props => { + const discuss = this.env.messaging.discuss; const thread = this.env.models['mail.thread'].get(props.threadLocalId); const correspondent = thread ? thread.correspondent : undefined; return { - correspondent: correspondent ? correspondent.__state : undefined, - discuss: this.env.messaging.discuss.__state, - thread: thread ? thread.__state : undefined, + correspondentName: correspondent && correspondent.name, + discussIsRenamingThread: discuss && discuss.renamingThreads.includes(thread), + isDiscussThread: discuss && discuss.thread === thread, + starred: this.env.messaging.starred, + thread, + threadChannelType: thread && thread.channel_type, + threadCounter: thread && thread.counter, + threadDisplayName: thread && thread.displayName, + threadGroupBasedSubscription: thread && thread.group_based_subscription, + threadLocalMessageUnreadCounter: thread && thread.localMessageUnreadCounter, + threadMassMailing: thread && thread.mass_mailing, + threadMessageNeedactionCounter: thread && thread.message_needaction_counter, + threadModel: thread && thread.model, }; }); } diff --git a/addons/mail/static/src/components/drop_zone/drop_zone.js b/addons/mail/static/src/components/drop_zone/drop_zone.js index 2eb4dc8ef74..dcbb701994c 100644 --- a/addons/mail/static/src/components/drop_zone/drop_zone.js +++ b/addons/mail/static/src/components/drop_zone/drop_zone.js @@ -1,6 +1,8 @@ odoo.define('mail/static/src/components/drop_zone/drop_zone.js', function (require) { 'use strict'; +const useShouldUpdateBasedOnProps = require('mail/static/src/component_hooks/use_should_update_based_on_props/use_should_update_based_on_props.js'); + const { Component, useState } = owl; class DropZone extends Component { @@ -10,6 +12,7 @@ class DropZone extends Component { */ constructor(...args) { super(...args); + useShouldUpdateBasedOnProps(); this.state = useState({ /** * Determine whether the user is dragging files over the dropzone. diff --git a/addons/mail/static/src/components/editable_text/editable_text.js b/addons/mail/static/src/components/editable_text/editable_text.js index 6e0b682fc53..be7e7ddc506 100644 --- a/addons/mail/static/src/components/editable_text/editable_text.js +++ b/addons/mail/static/src/components/editable_text/editable_text.js @@ -2,11 +2,17 @@ odoo.define('mail/static/src/components/editable_text/editable_text.js', functio 'use strict'; const { markEventHandled } = require('mail/static/src/utils/utils.js'); +const useShouldUpdateBasedOnProps = require('mail/static/src/component_hooks/use_should_update_based_on_props/use_should_update_based_on_props.js'); const { Component } = owl; class EditableText extends Component { + constructor(...args) { + super(...args); + useShouldUpdateBasedOnProps(); + } + mounted() { this.el.focus(); this.el.setSelectionRange(0, (this.el.value && this.el.value.length) || 0); diff --git a/addons/mail/static/src/components/emojis_popover/emojis_popover.js b/addons/mail/static/src/components/emojis_popover/emojis_popover.js index 9bda844a030..a312eed40c7 100644 --- a/addons/mail/static/src/components/emojis_popover/emojis_popover.js +++ b/addons/mail/static/src/components/emojis_popover/emojis_popover.js @@ -2,6 +2,7 @@ odoo.define('mail/static/src/components/emojis_popover/emojis_popover.js', funct 'use strict'; const emojis = require('mail.emojis'); +const useShouldUpdateBasedOnProps = require('mail/static/src/component_hooks/use_should_update_based_on_props/use_should_update_based_on_props.js'); const useUpdate = require('mail/static/src/component_hooks/use_update/use_update.js'); const { Component } = owl; @@ -14,6 +15,7 @@ class EmojisPopover extends Component { constructor(...args) { super(...args); this.emojis = emojis; + useShouldUpdateBasedOnProps(); useUpdate({ func: () => this._update() }); } diff --git a/addons/mail/static/src/components/file_uploader/file_uploader.js b/addons/mail/static/src/components/file_uploader/file_uploader.js index bb8c6c5b460..7907d81638b 100644 --- a/addons/mail/static/src/components/file_uploader/file_uploader.js +++ b/addons/mail/static/src/components/file_uploader/file_uploader.js @@ -1,6 +1,8 @@ odoo.define('mail/static/src/components/file_uploader/file_uploader.js', function (require) { 'use strict'; +const useShouldUpdateBasedOnProps = require('mail/static/src/component_hooks/use_should_update_based_on_props/use_should_update_based_on_props.js'); + const core = require('web.core'); const { Component } = owl; @@ -16,6 +18,12 @@ class FileUploader extends Component { this._fileInputRef = useRef('fileInput'); this._fileUploadId = _.uniqueId('o_FileUploader_fileupload'); this._onAttachmentUploaded = this._onAttachmentUploaded.bind(this); + useShouldUpdateBasedOnProps({ + compareDepth: { + attachmentLocalIds: 1, + newAttachmentExtraData: 3, + }, + }); } mounted() { diff --git a/addons/mail/static/src/components/follow_button/follow_button.js b/addons/mail/static/src/components/follow_button/follow_button.js index b1639567c14..3c1808cb316 100644 --- a/addons/mail/static/src/components/follow_button/follow_button.js +++ b/addons/mail/static/src/components/follow_button/follow_button.js @@ -1,6 +1,7 @@ odoo.define('mail/static/src/components/follow_button/follow_button.js', function (require) { 'use strict'; +const useShouldUpdateBasedOnProps = require('mail/static/src/component_hooks/use_should_update_based_on_props/use_should_update_based_on_props.js'); const useStore = require('mail/static/src/component_hooks/use_store/use_store.js'); const { Component } = owl; @@ -12,6 +13,7 @@ class FollowButton extends Component { */ constructor(...args) { super(...args); + useShouldUpdateBasedOnProps(); this.state = useState({ /** * Determine whether the unfollow button is highlighted or not. @@ -21,7 +23,7 @@ class FollowButton extends Component { useStore(props => { const thread = this.env.models['mail.thread'].get(props.threadLocalId); return { - thread: thread ? thread.__state : undefined, + threadIsCurrentPartnerFollowing: thread && thread.isCurrentPartnerFollowing, }; }); } diff --git a/addons/mail/static/src/components/follower/follower.js b/addons/mail/static/src/components/follower/follower.js index dc3e395f03c..bafcd88afc9 100644 --- a/addons/mail/static/src/components/follower/follower.js +++ b/addons/mail/static/src/components/follower/follower.js @@ -4,6 +4,7 @@ odoo.define('mail/static/src/components/follower/follower.js', function (require const components = { FollowerSubtypeList: require('mail/static/src/components/follower_subtype_list/follower_subtype_list.js'), }; +const useShouldUpdateBasedOnProps = require('mail/static/src/component_hooks/use_should_update_based_on_props/use_should_update_based_on_props.js'); const useStore = require('mail/static/src/component_hooks/use_store/use_store.js'); const { Component } = owl; @@ -15,6 +16,7 @@ class Follower extends Component { */ constructor(...args) { super(...args); + useShouldUpdateBasedOnProps(); useStore(props => { const follower = this.env.models['mail.follower'].get(props.followerLocalId); return [follower ? follower.__state : undefined]; diff --git a/addons/mail/static/src/components/follower_list_menu/follower_list_menu.js b/addons/mail/static/src/components/follower_list_menu/follower_list_menu.js index 54eac6e284c..996ef1f58a0 100644 --- a/addons/mail/static/src/components/follower_list_menu/follower_list_menu.js +++ b/addons/mail/static/src/components/follower_list_menu/follower_list_menu.js @@ -4,6 +4,7 @@ odoo.define('mail/static/src/components/follower_list_menu/follower_list_menu.js const components = { Follower: require('mail/static/src/components/follower/follower.js'), }; +const useShouldUpdateBasedOnProps = require('mail/static/src/component_hooks/use_should_update_based_on_props/use_should_update_based_on_props.js'); const useStore = require('mail/static/src/component_hooks/use_store/use_store.js'); const { Component } = owl; @@ -15,6 +16,7 @@ class FollowerListMenu extends Component { */ constructor(...args) { super(...args); + useShouldUpdateBasedOnProps(); this.state = useState({ /** * Determine whether the dropdown is open or not. @@ -25,8 +27,8 @@ class FollowerListMenu extends Component { const thread = this.env.models['mail.thread'].get(props.threadLocalId); const followers = thread ? thread.followers : []; return { - followers: followers.map(follower => follower.__state), - thread: thread ? thread.__state : undefined, + followers, + threadChannelType: thread && thread.channel_type, }; }, { compareDepth: { diff --git a/addons/mail/static/src/components/follower_subtype/follower_subtype.js b/addons/mail/static/src/components/follower_subtype/follower_subtype.js index 8ff47ada34f..ae3ba3211e1 100644 --- a/addons/mail/static/src/components/follower_subtype/follower_subtype.js +++ b/addons/mail/static/src/components/follower_subtype/follower_subtype.js @@ -1,6 +1,7 @@ odoo.define('mail/static/src/components/follower_subtype/follower_subtype.js', function (require) { 'use strict'; +const useShouldUpdateBasedOnProps = require('mail/static/src/component_hooks/use_should_update_based_on_props/use_should_update_based_on_props.js'); const useStore = require('mail/static/src/component_hooks/use_store/use_store.js'); const { Component } = owl; @@ -12,6 +13,7 @@ class FollowerSubtype extends Component { */ constructor(...args) { super(...args); + useShouldUpdateBasedOnProps(); useStore(props => { const followerSubtype = this.env.models['mail.follower_subtype'].get(props.followerSubtypeLocalId); return [followerSubtype ? followerSubtype.__state : undefined]; diff --git a/addons/mail/static/src/components/follower_subtype_list/follower_subtype_list.js b/addons/mail/static/src/components/follower_subtype_list/follower_subtype_list.js index 851382861ea..d5cca5b5afe 100644 --- a/addons/mail/static/src/components/follower_subtype_list/follower_subtype_list.js +++ b/addons/mail/static/src/components/follower_subtype_list/follower_subtype_list.js @@ -4,6 +4,7 @@ odoo.define('mail/static/src/components/follower_subtype_list/follower_subtype_l const components = { FollowerSubtype: require('mail/static/src/components/follower_subtype/follower_subtype.js'), }; +const useShouldUpdateBasedOnProps = require('mail/static/src/component_hooks/use_should_update_based_on_props/use_should_update_based_on_props.js'); const useStore = require('mail/static/src/component_hooks/use_store/use_store.js'); const { Component, QWeb } = owl; @@ -15,6 +16,7 @@ class FollowerSubtypeList extends Component { */ constructor(...args) { super(...args); + useShouldUpdateBasedOnProps(); useStore(props => { const followerSubtypeList = this.env.models['mail.follower_subtype_list'].get(props.localId); const follower = followerSubtypeList diff --git a/addons/mail/static/src/components/mail_template/mail_template.js b/addons/mail/static/src/components/mail_template/mail_template.js index 45dc8c344c6..32c334bebb6 100644 --- a/addons/mail/static/src/components/mail_template/mail_template.js +++ b/addons/mail/static/src/components/mail_template/mail_template.js @@ -1,6 +1,7 @@ odoo.define('mail/static/src/components/mail_template/mail_template.js', function (require) { 'use strict'; +const useShouldUpdateBasedOnProps = require('mail/static/src/component_hooks/use_should_update_based_on_props/use_should_update_based_on_props.js'); const useStore = require('mail/static/src/component_hooks/use_store/use_store.js'); const { Component } = owl; @@ -12,6 +13,7 @@ class MailTemplate extends Component { */ constructor(...args) { super(...args); + useShouldUpdateBasedOnProps(); useStore(props => { const activity = this.env.models['mail.activity'].get(props.activityLocalId); const mailTemplate = this.env.models['mail.mail_template'].get(props.mailTemplateLocalId); diff --git a/addons/mail/static/src/components/message/message.js b/addons/mail/static/src/components/message/message.js index d7800cbfc56..6d6e64e2b18 100644 --- a/addons/mail/static/src/components/message/message.js +++ b/addons/mail/static/src/components/message/message.js @@ -10,6 +10,7 @@ const components = { NotificationPopover: require('mail/static/src/components/notification_popover/notification_popover.js'), PartnerImStatusIcon: require('mail/static/src/components/partner_im_status_icon/partner_im_status_icon.js'), }; +const useShouldUpdateBasedOnProps = require('mail/static/src/component_hooks/use_should_update_based_on_props/use_should_update_based_on_props.js'); const useStore = require('mail/static/src/component_hooks/use_store/use_store.js'); const useUpdate = require('mail/static/src/component_hooks/use_update/use_update.js'); @@ -43,6 +44,7 @@ class Message extends Component { */ isClicked: false, }); + useShouldUpdateBasedOnProps(); useStore(props => { const message = this.env.models['mail.message'].get(props.messageLocalId); const author = message ? message.author : undefined; @@ -50,31 +52,37 @@ class Message extends Component { const originThread = message ? message.originThread : undefined; const threadView = this.env.models['mail.thread_view'].get(props.threadViewLocalId); const thread = threadView ? threadView.thread : undefined; - const threadStringifiedDomain = threadView - ? threadView.stringifiedDomain - : undefined; return { attachments: message ? message.attachments.map(attachment => attachment.__state) - : undefined, - author: author ? author.__state : undefined, + : [], + author, + authorAvatarUrl: author && author.avatarUrl, + authorImStatus: author && author.im_status, + authorNameOrDisplayName: author && author.nameOrDisplayName, + correspondent: thread && thread.correspondent, hasMessageCheckbox: message ? message.hasCheckbox : false, isDeviceMobile: this.env.messaging.device.isMobile, isMessageChecked: message && threadView - ? message.isChecked(thread, threadStringifiedDomain) + ? message.isChecked(thread, threadView.stringifiedDomain) : false, isMessageSelected: message && threadView && threadView.threadViewer ? threadView.threadViewer.selectedMessage === message : false, message: message ? message.__state : undefined, notifications: message ? message.notifications.map(notif => notif.__state) : [], - originThread: originThread ? originThread.__state : undefined, - partnerRoot: partnerRoot ? partnerRoot.__state : undefined, - thread: thread ? thread.__state : undefined, - threadView: threadView ? threadView.__state : undefined, + originThread, + originThreadModel: originThread && originThread.model, + originThreadName: originThread && originThread.name, + originThreadUrl: originThread && originThread.url, + partnerRoot, + thread, + threadHasSeenIndicators: thread && thread.hasSeenIndicators, + threadMassMailing: thread && thread.mass_mailing, }; }, { compareDepth: { + attachments: 1, notifications: 1, }, }); diff --git a/addons/mail/static/src/components/message_author_prefix/message_author_prefix.js b/addons/mail/static/src/components/message_author_prefix/message_author_prefix.js index 07fb1d054f0..21fb18a5223 100644 --- a/addons/mail/static/src/components/message_author_prefix/message_author_prefix.js +++ b/addons/mail/static/src/components/message_author_prefix/message_author_prefix.js @@ -1,6 +1,7 @@ odoo.define('mail/static/src/components/message_author_prefix/message_author_prefix.js', function (require) { 'use strict'; +const useShouldUpdateBasedOnProps = require('mail/static/src/component_hooks/use_should_update_based_on_props/use_should_update_based_on_props.js'); const useStore = require('mail/static/src/component_hooks/use_store/use_store.js'); const { Component } = owl; @@ -12,6 +13,7 @@ class MessageAuthorPrefix extends Component { */ constructor(...args) { super(...args); + useShouldUpdateBasedOnProps(); useStore(props => { const message = this.env.models['mail.message'].get(props.messageLocalId); const author = message ? message.author : undefined; diff --git a/addons/mail/static/src/components/message_list/message_list.js b/addons/mail/static/src/components/message_list/message_list.js index 4c3c9408421..75c71cc43f1 100644 --- a/addons/mail/static/src/components/message_list/message_list.js +++ b/addons/mail/static/src/components/message_list/message_list.js @@ -6,6 +6,7 @@ const components = { }; const useRefs = require('mail/static/src/component_hooks/use_refs/use_refs.js'); const useRenderedValues = require('mail/static/src/component_hooks/use_rendered_values/use_rendered_values.js'); +const useShouldUpdateBasedOnProps = require('mail/static/src/component_hooks/use_should_update_based_on_props/use_should_update_based_on_props.js'); const useStore = require('mail/static/src/component_hooks/use_store/use_store.js'); const useUpdate = require('mail/static/src/component_hooks/use_update/use_update.js'); @@ -19,22 +20,30 @@ class MessageList extends Component { */ constructor(...args) { super(...args); + useShouldUpdateBasedOnProps(); useStore(props => { const threadView = this.env.models['mail.thread_view'].get(props.threadViewLocalId); const thread = threadView ? threadView.thread : undefined; const threadCache = threadView ? threadView.threadCache : undefined; return { isDeviceMobile: this.env.messaging.device.isMobile, - messages: threadCache - ? threadCache.orderedMessages.map(message => message.__state) - : [], - thread: thread ? thread.__state : undefined, - threadCache: threadCache ? threadCache.__state : undefined, - threadView: threadView ? threadView.__state : undefined, + thread, + threadCache, + threadCacheIsAllHistoryLoaded: threadCache && threadCache.isAllHistoryLoaded, + threadCacheIsLoaded: threadCache && threadCache.isLoaded, + threadCacheIsLoadingMore: threadCache && threadCache.isLoadingMore, + threadCacheLastMessage: threadCache && threadCache.lastMessage, + threadCacheOrderedMessages: threadCache ? threadCache.orderedMessages : [], + threadIsTemporary: thread && thread.isTemporary, + threadMainCache: thread && thread.mainCache, + threadMessageAfterNewMessageSeparator: thread && thread.messageAfterNewMessageSeparator, + threadViewComponentHintList: threadView ? threadView.componentHintList : [], + threadViewNonEmptyMessagesLength: threadView && threadView.nonEmptyMessages.length, }; }, { compareDepth: { - messages: 1, + threadCacheOrderedMessages: 1, + threadViewComponentHintList: 1, }, }); this._getRefs = useRefs(); @@ -63,13 +72,16 @@ class MessageList extends Component { this._lastRenderedValues = useRenderedValues(() => { const threadView = this.threadView; const thread = threadView && threadView.thread; + const threadCache = threadView && threadView.threadCache; return { componentHintList: threadView ? [...threadView.componentHintList] : [], hasAutoScrollOnMessageReceived: threadView && threadView.hasAutoScrollOnMessageReceived, hasScrollAdjust: this.props.hasScrollAdjust, mainCache: thread && thread.mainCache, order: this.props.order, - threadCache: threadView && threadView.threadCache, + orderedMessages: threadCache ? [...threadCache.orderedMessages] : [], + thread, + threadCache, threadCacheInitialScrollHeight: threadView && threadView.threadCacheInitialScrollHeight, threadCacheInitialScrollPosition: threadView && threadView.threadCacheInitialScrollPosition, threadView, @@ -493,6 +505,8 @@ class MessageList extends Component { _onScrollThrottled(ev) { const { order, + orderedMessages, + thread, threadCache, threadView, threadViewer, @@ -503,7 +517,9 @@ class MessageList extends Component { } const scrollTop = this.el.scrollTop; this.env.messagingBus.trigger('o-component-message-list-scrolled', { + orderedMessages, scrollTop, + thread, threadViewer, }); if (!this._isLastScrollProgrammatic && threadView && threadView.exists()) { diff --git a/addons/mail/static/src/components/message_list/message_list.xml b/addons/mail/static/src/components/message_list/message_list.xml index 97915c5ce56..6d3730fdcea 100644 --- a/addons/mail/static/src/components/message_list/message_list.xml +++ b/addons/mail/static/src/components/message_list/message_list.xml @@ -37,7 +37,7 @@ - + @@ -75,7 +75,7 @@ - + diff --git a/addons/mail/static/src/components/message_seen_indicator/message_seen_indicator.js b/addons/mail/static/src/components/message_seen_indicator/message_seen_indicator.js index 9a3d9172cd1..ed555b0c5a4 100644 --- a/addons/mail/static/src/components/message_seen_indicator/message_seen_indicator.js +++ b/addons/mail/static/src/components/message_seen_indicator/message_seen_indicator.js @@ -1,6 +1,7 @@ odoo.define('mail/static/src/components/message_seen_indicator/message_seen_indicator.js', function (require) { 'use strict'; +const useShouldUpdateBasedOnProps = require('mail/static/src/component_hooks/use_should_update_based_on_props/use_should_update_based_on_props.js'); const useStore = require('mail/static/src/component_hooks/use_store/use_store.js'); const { Component } = owl; @@ -12,6 +13,7 @@ class MessageSeenIndicator extends Component { */ constructor(...args) { super(...args); + useShouldUpdateBasedOnProps(); useStore(props => { const message = this.env.models['mail.message'].get(props.messageLocalId); const thread = this.env.models['mail.thread'].get(props.threadLocalId); diff --git a/addons/mail/static/src/components/messaging_menu/messaging_menu.js b/addons/mail/static/src/components/messaging_menu/messaging_menu.js index 89cb6b1fb7c..9eb7fd71a49 100644 --- a/addons/mail/static/src/components/messaging_menu/messaging_menu.js +++ b/addons/mail/static/src/components/messaging_menu/messaging_menu.js @@ -6,6 +6,7 @@ const components = { MobileMessagingNavbar: require('mail/static/src/components/mobile_messaging_navbar/mobile_messaging_navbar.js'), NotificationList: require('mail/static/src/components/notification_list/notification_list.js'), }; +const useShouldUpdateBasedOnProps = require('mail/static/src/component_hooks/use_should_update_based_on_props/use_should_update_based_on_props.js'); const useStore = require('mail/static/src/component_hooks/use_store/use_store.js'); const patchMixin = require('web.patchMixin'); @@ -25,6 +26,7 @@ class MessagingMenu extends Component { * item is not considered as a click away from messaging menu in mobile. */ this.id = _.uniqueId('o_messagingMenu_'); + useShouldUpdateBasedOnProps(); useStore(props => { return { isDeviceMobile: this.env.messaging && this.env.messaging.device.isMobile, diff --git a/addons/mail/static/src/components/mobile_messaging_navbar/mobile_messaging_navbar.js b/addons/mail/static/src/components/mobile_messaging_navbar/mobile_messaging_navbar.js index 923480b3f73..45b53e87233 100644 --- a/addons/mail/static/src/components/mobile_messaging_navbar/mobile_messaging_navbar.js +++ b/addons/mail/static/src/components/mobile_messaging_navbar/mobile_messaging_navbar.js @@ -1,10 +1,21 @@ odoo.define('mail/static/src/components/mobile_messaging_navbar/mobile_messaging_navbar.js', function (require) { 'use strict'; +const useShouldUpdateBasedOnProps = require('mail/static/src/component_hooks/use_should_update_based_on_props/use_should_update_based_on_props.js'); + const { Component } = owl; class MobileMessagingNavbar extends Component { + constructor(...args) { + super(...args); + useShouldUpdateBasedOnProps({ + compareDepth: { + tabs: 2, + }, + }); + } + //-------------------------------------------------------------------------- // Handlers //-------------------------------------------------------------------------- diff --git a/addons/mail/static/src/components/moderation_ban_dialog/moderation_ban_dialog.js b/addons/mail/static/src/components/moderation_ban_dialog/moderation_ban_dialog.js index c7a5ed71a65..c96bd902800 100644 --- a/addons/mail/static/src/components/moderation_ban_dialog/moderation_ban_dialog.js +++ b/addons/mail/static/src/components/moderation_ban_dialog/moderation_ban_dialog.js @@ -1,6 +1,7 @@ odoo.define('mail/static/src/components/moderation_ban_dialog/moderation_ban_dialog.js', function (require) { 'use strict'; +const useShouldUpdateBasedOnProps = require('mail/static/src/component_hooks/use_should_update_based_on_props/use_should_update_based_on_props.js'); const useStore = require('mail/static/src/component_hooks/use_store/use_store.js'); const components = { @@ -17,6 +18,11 @@ class ModerationBanDialog extends Component { */ constructor(...args) { super(...args); + useShouldUpdateBasedOnProps({ + compareDepth: { + messageLocalIds: 1, + }, + }); useStore(props => { const messages = props.messageLocalIds.map(localId => this.env.models['mail.message'].get(localId) diff --git a/addons/mail/static/src/components/moderation_discard_dialog/moderation_discard_dialog.js b/addons/mail/static/src/components/moderation_discard_dialog/moderation_discard_dialog.js index 87e4ec84f94..4c4446837c2 100644 --- a/addons/mail/static/src/components/moderation_discard_dialog/moderation_discard_dialog.js +++ b/addons/mail/static/src/components/moderation_discard_dialog/moderation_discard_dialog.js @@ -1,6 +1,7 @@ odoo.define('mail/static/src/components/moderation_discard_dialog/moderation_discard_dialog.js', function (require) { 'use strict'; +const useShouldUpdateBasedOnProps = require('mail/static/src/component_hooks/use_should_update_based_on_props/use_should_update_based_on_props.js'); const useStore = require('mail/static/src/component_hooks/use_store/use_store.js'); const components = { @@ -17,6 +18,11 @@ class ModerationDiscardDialog extends Component { */ constructor(...args) { super(...args); + useShouldUpdateBasedOnProps({ + compareDepth: { + messageLocalIds: 1, + }, + }); useStore(props => { const messages = props.messageLocalIds.map(localId => this.env.models['mail.message'].get(localId) diff --git a/addons/mail/static/src/components/moderation_reject_dialog/moderation_reject_dialog.js b/addons/mail/static/src/components/moderation_reject_dialog/moderation_reject_dialog.js index e488aea4ea7..44b82bc6526 100644 --- a/addons/mail/static/src/components/moderation_reject_dialog/moderation_reject_dialog.js +++ b/addons/mail/static/src/components/moderation_reject_dialog/moderation_reject_dialog.js @@ -1,6 +1,7 @@ odoo.define('mail/static/src/components/moderation_reject_dialog/moderation_reject_dialog.js', function (require) { 'use strict'; +const useShouldUpdateBasedOnProps = require('mail/static/src/component_hooks/use_should_update_based_on_props/use_should_update_based_on_props.js'); const useStore = require('mail/static/src/component_hooks/use_store/use_store.js'); const components = { @@ -17,6 +18,11 @@ class ModerationRejectDialog extends Component { */ constructor(...args) { super(...args); + useShouldUpdateBasedOnProps({ + compareDepth: { + messageLocalIds: 1, + }, + }); this.state = useState({ title: this.env._t("Message Rejected"), comment: this.env._t("Your message was rejected by moderator."), diff --git a/addons/mail/static/src/components/notification_alert/notification_alert.js b/addons/mail/static/src/components/notification_alert/notification_alert.js index 6e582fcdc6b..7ef9e3b12db 100644 --- a/addons/mail/static/src/components/notification_alert/notification_alert.js +++ b/addons/mail/static/src/components/notification_alert/notification_alert.js @@ -1,6 +1,7 @@ odoo.define('mail/static/src/components/notification_alert/notification_alert.js', function (require) { 'use strict'; +const useShouldUpdateBasedOnProps = require('mail/static/src/component_hooks/use_should_update_based_on_props/use_should_update_based_on_props.js'); const useStore = require('mail/static/src/component_hooks/use_store/use_store.js'); const { Component } = owl; @@ -12,6 +13,7 @@ class NotificationAlert extends Component { */ constructor(...args) { super(...args); + useShouldUpdateBasedOnProps(); useStore(props => { const isMessagingInitialized = this.env.isMessagingInitialized(); return { diff --git a/addons/mail/static/src/components/notification_group/notification_group.js b/addons/mail/static/src/components/notification_group/notification_group.js index 65cfcea3076..17936986574 100644 --- a/addons/mail/static/src/components/notification_group/notification_group.js +++ b/addons/mail/static/src/components/notification_group/notification_group.js @@ -1,6 +1,7 @@ odoo.define('mail/static/src/components/notification_group/notification_group.js', function (require) { 'use strict'; +const useShouldUpdateBasedOnProps = require('mail/static/src/component_hooks/use_should_update_based_on_props/use_should_update_based_on_props.js'); const useStore = require('mail/static/src/component_hooks/use_store/use_store.js'); const { Component } = owl; @@ -13,6 +14,7 @@ class NotificationGroup extends Component { */ constructor(...args) { super(...args); + useShouldUpdateBasedOnProps(); useStore(props => { const group = this.env.models['mail.notification_group'].get(props.notificationGroupLocalId); return { diff --git a/addons/mail/static/src/components/notification_list/notification_list.js b/addons/mail/static/src/components/notification_list/notification_list.js index ea256c13963..33737ba4c4b 100644 --- a/addons/mail/static/src/components/notification_list/notification_list.js +++ b/addons/mail/static/src/components/notification_list/notification_list.js @@ -7,6 +7,7 @@ const components = { ThreadNeedactionPreview: require('mail/static/src/components/thread_needaction_preview/thread_needaction_preview.js'), ThreadPreview: require('mail/static/src/components/thread_preview/thread_preview.js'), }; +const useShouldUpdateBasedOnProps = require('mail/static/src/component_hooks/use_should_update_based_on_props/use_should_update_based_on_props.js'); const useStore = require('mail/static/src/component_hooks/use_store/use_store.js'); const { Component } = owl; @@ -18,6 +19,7 @@ class NotificationList extends Component { */ constructor(...args) { super(...args); + useShouldUpdateBasedOnProps(); this.storeProps = useStore((...args) => this._useStoreSelector(...args), { compareDepth: { // list + notification object created in useStore diff --git a/addons/mail/static/src/components/notification_popover/notification_popover.js b/addons/mail/static/src/components/notification_popover/notification_popover.js index 0f3fce77a58..6be3647eada 100644 --- a/addons/mail/static/src/components/notification_popover/notification_popover.js +++ b/addons/mail/static/src/components/notification_popover/notification_popover.js @@ -2,6 +2,7 @@ odoo.define('mail/static/src/components/notification_popover/notification_popove 'use strict'; const { Component } = owl; +const useShouldUpdateBasedOnProps = require('mail/static/src/component_hooks/use_should_update_based_on_props/use_should_update_based_on_props.js'); const useStore = require('mail/static/src/component_hooks/use_store/use_store.js'); class NotificationPopover extends Component { @@ -11,6 +12,11 @@ class NotificationPopover extends Component { */ constructor(...args) { super(...args); + useShouldUpdateBasedOnProps({ + compareDepth: { + notificationLocalIds: 1, + }, + }); useStore(props => { const notifications = props.notificationLocalIds.map( notificationLocalId => this.env.models['mail.notification'].get(notificationLocalId) diff --git a/addons/mail/static/src/components/notification_request/notification_request.js b/addons/mail/static/src/components/notification_request/notification_request.js index 6f1f085813c..2d4b0101a92 100644 --- a/addons/mail/static/src/components/notification_request/notification_request.js +++ b/addons/mail/static/src/components/notification_request/notification_request.js @@ -4,6 +4,7 @@ odoo.define('mail/static/src/components/notification_request/notification_reques const components = { PartnerImStatusIcon: require('mail/static/src/components/partner_im_status_icon/partner_im_status_icon.js'), }; +const useShouldUpdateBasedOnProps = require('mail/static/src/component_hooks/use_should_update_based_on_props/use_should_update_based_on_props.js'); const useStore = require('mail/static/src/component_hooks/use_store/use_store.js'); const { Component } = owl; @@ -15,6 +16,7 @@ class NotificationRequest extends Component { */ constructor(...args) { super(...args); + useShouldUpdateBasedOnProps(); useStore(props => { return { isDeviceMobile: this.env.messaging.device.isMobile, diff --git a/addons/mail/static/src/components/partner_im_status_icon/partner_im_status_icon.js b/addons/mail/static/src/components/partner_im_status_icon/partner_im_status_icon.js index 68fbab4f7c9..e4af9da6a04 100644 --- a/addons/mail/static/src/components/partner_im_status_icon/partner_im_status_icon.js +++ b/addons/mail/static/src/components/partner_im_status_icon/partner_im_status_icon.js @@ -1,6 +1,7 @@ odoo.define('mail/static/src/components/partner_im_status_icon/partner_im_status_icon.js', function (require) { 'use strict'; +const useShouldUpdateBasedOnProps = require('mail/static/src/component_hooks/use_should_update_based_on_props/use_should_update_based_on_props.js'); const useStore = require('mail/static/src/component_hooks/use_store/use_store.js'); const { Component } = owl; @@ -12,13 +13,13 @@ class PartnerImStatusIcon extends Component { */ constructor(...args) { super(...args); + useShouldUpdateBasedOnProps(); useStore(props => { const partner = this.env.models['mail.partner'].get(props.partnerLocalId); return { - partner: partner ? partner.__state : undefined, - partnerRoot: this.env.messaging.partnerRoot - ? this.env.messaging.partnerRoot.__state - : undefined, + partner, + partnerImStatus: partner && partner.im_status, + partnerRoot: this.env.messaging.partnerRoot, }; }); } diff --git a/addons/mail/static/src/components/thread_icon/thread_icon.js b/addons/mail/static/src/components/thread_icon/thread_icon.js index 0b82d0c9510..71017ec0e55 100644 --- a/addons/mail/static/src/components/thread_icon/thread_icon.js +++ b/addons/mail/static/src/components/thread_icon/thread_icon.js @@ -4,6 +4,7 @@ odoo.define('mail/static/src/components/thread_icon/thread_icon.js', function (r const components = { ThreadTypingIcon: require('mail/static/src/components/thread_typing_icon/thread_typing_icon.js'), }; +const useShouldUpdateBasedOnProps = require('mail/static/src/component_hooks/use_should_update_based_on_props/use_should_update_based_on_props.js'); const useStore = require('mail/static/src/component_hooks/use_store/use_store.js'); const { Component } = owl; @@ -15,15 +16,24 @@ class ThreadIcon extends Component { */ constructor(...args) { super(...args); + useShouldUpdateBasedOnProps(); useStore(props => { const thread = this.env.models['mail.thread'].get(props.threadLocalId); const correspondent = thread ? thread.correspondent : undefined; return { - correspondent: correspondent ? correspondent.__state : undefined, - partnerRoot: this.env.messaging.partnerRoot - ? this.env.messaging.partnerRoot.__state - : undefined, - thread: thread ? thread.__state : undefined, + correspondent, + correspondentImStatus: correspondent && correspondent.im_status, + history: this.env.messaging.history, + inbox: this.env.messaging.inbox, + moderation: this.env.messaging.moderation, + partnerRoot: this.env.messaging.partnerRoot, + starred: this.env.messaging.starred, + thread, + threadChannelType: thread && thread.channel_type, + threadModel: thread && thread.model, + threadOrderedOtherTypingMembersLength: thread && thread.orderedOtherTypingMembers.length, + threadPublic: thread && thread.public, + threadTypingStatusText: thread && thread.typingStatusText, }; }); } diff --git a/addons/mail/static/src/components/thread_needaction_preview/thread_needaction_preview.js b/addons/mail/static/src/components/thread_needaction_preview/thread_needaction_preview.js index c313a1dd2c1..d6e30f5a6ff 100644 --- a/addons/mail/static/src/components/thread_needaction_preview/thread_needaction_preview.js +++ b/addons/mail/static/src/components/thread_needaction_preview/thread_needaction_preview.js @@ -5,6 +5,7 @@ const components = { MessageAuthorPrefix: require('mail/static/src/components/message_author_prefix/message_author_prefix.js'), PartnerImStatusIcon: require('mail/static/src/components/partner_im_status_icon/partner_im_status_icon.js'), }; +const useShouldUpdateBasedOnProps = require('mail/static/src/component_hooks/use_should_update_based_on_props/use_should_update_based_on_props.js'); const useStore = require('mail/static/src/component_hooks/use_store/use_store.js'); const mailUtils = require('mail.utils'); @@ -18,6 +19,7 @@ class ThreadNeedactionPreview extends Component { */ constructor(...args) { super(...args); + useShouldUpdateBasedOnProps(); useStore(props => { const thread = this.env.models['mail.thread'].get(props.threadLocalId); const mainThreadCache = thread ? thread.mainCache : undefined; diff --git a/addons/mail/static/src/components/thread_preview/thread_preview.js b/addons/mail/static/src/components/thread_preview/thread_preview.js index 92fcb6101d1..94df29e0e67 100644 --- a/addons/mail/static/src/components/thread_preview/thread_preview.js +++ b/addons/mail/static/src/components/thread_preview/thread_preview.js @@ -5,6 +5,7 @@ const components = { MessageAuthorPrefix: require('mail/static/src/components/message_author_prefix/message_author_prefix.js'), PartnerImStatusIcon: require('mail/static/src/components/partner_im_status_icon/partner_im_status_icon.js'), }; +const useShouldUpdateBasedOnProps = require('mail/static/src/component_hooks/use_should_update_based_on_props/use_should_update_based_on_props.js'); const useStore = require('mail/static/src/component_hooks/use_store/use_store.js'); const mailUtils = require('mail.utils'); @@ -18,6 +19,7 @@ class ThreadPreview extends Component { */ constructor(...args) { super(...args); + useShouldUpdateBasedOnProps(); useStore(props => { const thread = this.env.models['mail.thread'].get(props.threadLocalId); let lastMessageAuthor; diff --git a/addons/mail/static/src/components/thread_textual_typing_status/thread_textual_typing_status.js b/addons/mail/static/src/components/thread_textual_typing_status/thread_textual_typing_status.js index 94f8c2b95bb..f053abc7fb2 100644 --- a/addons/mail/static/src/components/thread_textual_typing_status/thread_textual_typing_status.js +++ b/addons/mail/static/src/components/thread_textual_typing_status/thread_textual_typing_status.js @@ -4,6 +4,7 @@ odoo.define('mail/static/src/components/thread_textual_typing_status/thread_text const components = { ThreadTypingIcon: require('mail/static/src/components/thread_typing_icon/thread_typing_icon.js'), }; +const useShouldUpdateBasedOnProps = require('mail/static/src/component_hooks/use_should_update_based_on_props/use_should_update_based_on_props.js'); const useStore = require('mail/static/src/component_hooks/use_store/use_store.js'); const { Component } = owl; @@ -15,10 +16,12 @@ class ThreadTextualTypingStatus extends Component { */ constructor(...args) { super(...args); + useShouldUpdateBasedOnProps(); useStore(props => { const thread = this.env.models['mail.thread'].get(props.threadLocalId); return { - thread: thread ? thread.__state : undefined, + threadOrderedOtherTypingMembersLength: thread && thread.orderedOtherTypingMembersLength, + threadTypingStatusText: thread && thread.typingStatusText, }; }); } diff --git a/addons/mail/static/src/components/thread_typing_icon/thread_typing_icon.js b/addons/mail/static/src/components/thread_typing_icon/thread_typing_icon.js index 95caee984d4..4c94a749fdd 100644 --- a/addons/mail/static/src/components/thread_typing_icon/thread_typing_icon.js +++ b/addons/mail/static/src/components/thread_typing_icon/thread_typing_icon.js @@ -1,9 +1,18 @@ odoo.define('mail/static/src/components/thread_typing_icon/thread_typing_icon.js', function (require) { 'use strict'; +const useShouldUpdateBasedOnProps = require('mail/static/src/component_hooks/use_should_update_based_on_props/use_should_update_based_on_props.js'); + const { Component } = owl; -class ThreadTypingIcon extends Component {} +class ThreadTypingIcon extends Component { + + constructor(...args) { + super(...args); + useShouldUpdateBasedOnProps(); + } + +} Object.assign(ThreadTypingIcon, { defaultProps: { diff --git a/addons/mail/static/src/components/thread_view/thread_view.js b/addons/mail/static/src/components/thread_view/thread_view.js index 89334c7d6c3..e4795bc0d45 100644 --- a/addons/mail/static/src/components/thread_view/thread_view.js +++ b/addons/mail/static/src/components/thread_view/thread_view.js @@ -5,6 +5,7 @@ const components = { Composer: require('mail/static/src/components/composer/composer.js'), MessageList: require('mail/static/src/components/message_list/message_list.js'), }; +const useShouldUpdateBasedOnProps = require('mail/static/src/component_hooks/use_should_update_based_on_props/use_should_update_based_on_props.js'); const useStore = require('mail/static/src/component_hooks/use_store/use_store.js'); const useUpdate = require('mail/static/src/component_hooks/use_update/use_update.js'); @@ -18,6 +19,7 @@ class ThreadView extends Component { */ constructor(...args) { super(...args); + useShouldUpdateBasedOnProps(); useStore((...args) => this._useStoreSelector(...args)); useUpdate({ func: () => this._update() }); /** @@ -121,11 +123,18 @@ class ThreadView extends Component { const threadView = this.env.models['mail.thread_view'].get(props.threadViewLocalId); const thread = threadView ? threadView.thread : undefined; const threadCache = threadView ? threadView.threadCache : undefined; + const correspondent = thread && thread.correspondent; return { + composer: thread && thread.composer, + correspondentId: correspondent && correspondent.id, isDeviceMobile: this.env.messaging.device.isMobile, - thread: thread ? thread.__state : undefined, - threadCache: threadCache ? threadCache.__state : undefined, - threadView: threadView ? threadView.__state : undefined, + thread, + threadCacheIsLoaded: threadCache && threadCache.isLoaded, + threadIsTemporary: thread && thread.isTemporary, + threadMassMailing: thread && thread.mass_mailing, + threadModel: thread && thread.model, + threadView, + threadViewIsLoading: threadView && threadView.isLoading, }; } @@ -140,6 +149,7 @@ Object.assign(ThreadView, { hasSquashCloseMessages: false, haveMessagesMarkAsReadIcon: false, haveMessagesReplyIcon: false, + isDoFocus: false, order: 'asc', showComposerAttachmentsExtensions: true, showComposerAttachmentsFilenames: true, @@ -175,6 +185,10 @@ Object.assign(ThreadView, { hasSquashCloseMessages: Boolean, haveMessagesMarkAsReadIcon: Boolean, haveMessagesReplyIcon: Boolean, + /** + * Determines whether this should become focused. + */ + isDoFocus: Boolean, order: { type: String, validate: prop => ['asc', 'desc'].includes(prop), diff --git a/addons/mail/static/src/components/thread_view/thread_view.xml b/addons/mail/static/src/components/thread_view/thread_view.xml index 681920ae952..67234d77eb9 100644 --- a/addons/mail/static/src/components/thread_view/thread_view.xml +++ b/addons/mail/static/src/components/thread_view/thread_view.xml @@ -34,6 +34,7 @@ hasSendButton="props.hasComposerSendButton" hasThreadTyping="props.hasComposerThreadTyping" isCompact="(threadView.thread.model === 'mail.channel' and threadView.thread.mass_mailing) ? false : undefined" + isDoFocus="props.isDoFocus" showAttachmentsExtensions="props.showComposerAttachmentsExtensions" showAttachmentsFilenames="props.showComposerAttachmentsFilenames" textInputSendShortcuts="(threadView.thread.model === 'mail.channel' and threadView.thread.mass_mailing) ? ['ctrl-enter', 'meta-enter'] : ['enter']" diff --git a/addons/mail/static/src/widgets/form_renderer/form_renderer_tests.js b/addons/mail/static/src/widgets/form_renderer/form_renderer_tests.js index 19f5381be37..90cdb1693b3 100644 --- a/addons/mail/static/src/widgets/form_renderer/form_renderer_tests.js +++ b/addons/mail/static/src/widgets/form_renderer/form_renderer_tests.js @@ -462,24 +462,16 @@ QUnit.test('basic chatter rendering without messages', async function (assert) { }); QUnit.test('chatter updating', async function (assert) { - assert.expect(3); + assert.expect(1); this.data['mail.message'].records.push({ body: "not empty", model: 'res.partner', res_id: 12 }); this.data['res.partner'].records.push( { display_name: "first partner", id: 11 }, { display_name: "second partner", id: 12 } ); - const messageFetchChannelDef = makeDeferred(); await this.createView({ data: this.data, hasView: true, - async mockRPC(route, args) { - const res = await this._super(...arguments); - if (route.includes('message_fetch')) { - messageFetchChannelDef.resolve(); - } - return res; - }, // View params View: FormView, model: 'res.partner', @@ -498,27 +490,35 @@ QUnit.test('chatter updating', async function (assert) { `, + waitUntilEvent: { + eventName: 'o-thread-view-hint-processed', + message: "should wait until partner 11 thread loaded messages initially", + predicate: ({ hint, threadViewer }) => { + return ( + hint.type === 'messages-loaded' && + threadViewer.thread.model === 'res.partner' && + threadViewer.thread.id === 11 + ); + }, + } }); - assert.containsOnce( - document.body, - '.o_Chatter', - "there should be a chatter" - ); - assert.containsNone( - document.body, - '.o_Message', - "there should be no message" - ); - await afterNextRender(async () => { - document.querySelector('.o_pager_next').click(); - // wait until messages are fetched, ignore other renders that are too early - await messageFetchChannelDef; + await this.afterEvent({ + eventName: 'o-thread-view-hint-processed', + func: () => document.querySelector('.o_pager_next').click(), + message: "should wait until partner 12 thread loaded messages after clicking on next", + predicate: ({ hint, threadViewer }) => { + return ( + hint.type === 'messages-loaded' && + threadViewer.thread.model === 'res.partner' && + threadViewer.thread.id === 12 + ); + }, }); assert.containsOnce( document.body, '.o_Message', - "there should be a message" + "there should be a message in partner 12 thread" ); }); diff --git a/addons/mail/views/assets.xml b/addons/mail/views/assets.xml index ae43f537318..28d6c1d734a 100644 --- a/addons/mail/views/assets.xml +++ b/addons/mail/views/assets.xml @@ -59,6 +59,7 @@ diff --git a/addons/website_livechat/static/src/components/discuss/discuss.js b/addons/website_livechat/static/src/components/discuss/discuss.js index 18b41cea432..778b1513730 100644 --- a/addons/website_livechat/static/src/components/discuss/discuss.js +++ b/addons/website_livechat/static/src/components/discuss/discuss.js @@ -6,6 +6,24 @@ const components = { VisitorBanner: require('website_livechat/static/src/components/visitor_banner/visitor_banner.js'), }; +components.Discuss.patch('website_livechat/static/src/components/discuss/discuss.js', T => + class extends T { + + /** + * @override + */ + _useStoreSelector(props) { + const res = super._useStoreSelector(...arguments); + const thread = res.thread; + const visitor = thread && thread.visitor; + return Object.assign({}, res, { + visitor, + }); + } + + } +); + Object.assign(components.Discuss.components, { VisitorBanner: components.VisitorBanner, }); diff --git a/addons/website_livechat/static/src/components/visitor_banner/visitor_banner.js b/addons/website_livechat/static/src/components/visitor_banner/visitor_banner.js index 0b39134dcfc..4d0b95a82d0 100644 --- a/addons/website_livechat/static/src/components/visitor_banner/visitor_banner.js +++ b/addons/website_livechat/static/src/components/visitor_banner/visitor_banner.js @@ -14,7 +14,9 @@ class VisitorBanner extends Component { super(...args); useStore(props => { const visitor = this.env.models['website_livechat.visitor'].get(props.visitorLocalId); + const country = visitor && visitor.country; return { + country: country && country.__state, visitor: visitor ? visitor.__state : undefined, }; });