From d25459e6cbfc1dbe92350bc5b17bb33062ac4f12 Mon Sep 17 00:00:00 2001 From: Mohammed Shekha Date: Tue, 9 Mar 2021 10:11:34 +0000 Subject: [PATCH] [IMP] mail,web: add many2many_avatar_user widget to open chat with this commit, we adds many2many_avatar_user widget to have same behaviour as many2one_avatar_user where clicking on avatar image opens chat box, so to have same feature on many2many_tags which is related to res.users we adds many2many_avatar_user widget. task-2451204 --- addons/mail/__manifest__.py | 6 +- addons/mail/static/src/js/m2x_avatar_user.js | 100 ++++++++++++++++++ .../static/src/js/many2one_avatar_user.js | 63 ----------- .../mail/static/src/scss/m2x_avatar_user.scss | 19 ++++ ...user_tests.js => m2x_avatar_user_tests.js} | 86 ++++++++++++++- addons/web/static/src/scss/fields.scss | 19 ++-- addons/web/static/src/xml/base.xml | 4 +- 7 files changed, 217 insertions(+), 80 deletions(-) create mode 100644 addons/mail/static/src/js/m2x_avatar_user.js delete mode 100644 addons/mail/static/src/js/many2one_avatar_user.js create mode 100644 addons/mail/static/src/scss/m2x_avatar_user.scss rename addons/mail/static/tests/{many2one_avatar_user_tests.js => m2x_avatar_user_tests.js} (57%) diff --git a/addons/mail/__manifest__.py b/addons/mail/__manifest__.py index 37d335142d4..4ad4d20a89d 100644 --- a/addons/mail/__manifest__.py +++ b/addons/mail/__manifest__.py @@ -58,7 +58,7 @@ 'web.assets_backend': [ 'mail/static/src/js/core/translation.js', 'mail/static/src/js/many2many_tags_email.js', - 'mail/static/src/js/many2one_avatar_user.js', + 'mail/static/src/js/m2x_avatar_user.js', 'mail/static/src/js/field_char.js', 'mail/static/src/js/document_viewer.js', 'mail/static/src/js/basic_view.js', @@ -86,7 +86,7 @@ 'mail/static/src/scss/thread.scss', 'mail/static/src/scss/systray.scss', 'mail/static/src/scss/mail_activity.scss', - 'mail/static/src/scss/many2one_avatar_user.scss', + 'mail/static/src/scss/m2x_avatar_user.scss', 'mail/static/src/scss/activity_view.scss', 'mail/static/src/scss/kanban_view.scss', 'mail/static/src/component_hooks/use_drag_visible_dropzone/use_drag_visible_dropzone.js', @@ -280,7 +280,7 @@ 'mail/static/tests/chatter_tests.js', 'mail/static/tests/mail_utils_tests.js', 'mail/static/tests/document_viewer_tests.js', - 'mail/static/tests/many2one_avatar_user_tests.js', + 'mail/static/tests/m2x_avatar_user_tests.js', 'mail/static/tests/systray/systray_activity_menu_tests.js', 'mail/static/tests/tools/debug_manager_tests.js', 'mail/static/tests/activity_tests.js', diff --git a/addons/mail/static/src/js/m2x_avatar_user.js b/addons/mail/static/src/js/m2x_avatar_user.js new file mode 100644 index 00000000000..76ec83c440f --- /dev/null +++ b/addons/mail/static/src/js/m2x_avatar_user.js @@ -0,0 +1,100 @@ +/** @odoo-module **/ + +import fieldRegistry from 'web.field_registry'; +import { Many2OneAvatar } from 'web.relational_fields'; +import { FieldMany2ManyTagsAvatar } from 'web.relational_fields'; + +const { Component } = owl; + + +// This module defines extensions of the Many2OneAvatar and Many2ManyAvatar +// widgets, which are integrated with the messaging system. They are designed +// to display people, and when the avatar of those people is clicked, it +// opens a DM chat window with the corresponding user. +// +// These widgets are supported on many2one and many2many fields pointing to +// 'res.users'. +// +// Usage: +// +// +// The widgets are designed to be extended, to support fields pointing to other +// models than 'res.users'. + +const M2XAvatarMixin = { + supportedModels: ['res.users'], + + init() { + this._super(...arguments); + if (!this.supportedModels.includes(this.field.relation)) { + throw new Error(`This widget is only supported on many2one and many2many fields pointing to ${JSON.stringify(this.supportedModels)}`); + } + this.className += ' o_clickable_m2x_avatar'; + }, + + //-------------------------------------------------------------------------- + // Private + //-------------------------------------------------------------------------- + + /** + * Opens a chat window with the given user id. + * + * @private + * @param {Object} params + * @returns {Promise} + */ + _openChat(params) { + const env = Component.env; + return env.messaging.openChat(params); + }, +}; + +export const Many2OneAvatarUser = Many2OneAvatar.extend(M2XAvatarMixin, { + events: Object.assign({}, Many2OneAvatar.prototype.events, { + 'click .o_m2o_avatar > img': '_onAvatarClicked', + }), + + //---------------------------------------------------------------------- + // Handlers + //---------------------------------------------------------------------- + + /** + * When the avatar is clicked, open a DM chat window with the + * corresponding user. + * + * @private + * @param {MouseEvent} ev + */ + _onAvatarClicked(ev) { + ev.stopPropagation(); // in list view, prevent from opening the record + this._openChat({ userId: this.value.res_id }); + } +}); + +export const KanbanMany2OneAvatarUser = Many2OneAvatarUser.extend({ + _template: 'mail.KanbanMany2OneAvatarUser', +}); + +export const Many2ManyAvatarUser = FieldMany2ManyTagsAvatar.extend(M2XAvatarMixin, { + events: Object.assign({}, FieldMany2ManyTagsAvatar.prototype.events, { + 'click .o_m2m_avatar': '_onAvatarClicked', + }), + + //---------------------------------------------------------------------- + // Handlers + //---------------------------------------------------------------------- + + /** + * @private + * @param {MouseEvent} ev + */ + _onAvatarClicked(ev) { + ev.stopPropagation(); // in list view, prevent from opening the record + const userId = parseInt(ev.target.getAttribute('data-id'), 10); + this._openChat({ userId: userId }); + }, +}); + +fieldRegistry.add('many2one_avatar_user', Many2OneAvatarUser); +fieldRegistry.add('kanban.many2one_avatar_user', KanbanMany2OneAvatarUser); +fieldRegistry.add('many2many_avatar_user', Many2ManyAvatarUser); diff --git a/addons/mail/static/src/js/many2one_avatar_user.js b/addons/mail/static/src/js/many2one_avatar_user.js deleted file mode 100644 index 391b46dd062..00000000000 --- a/addons/mail/static/src/js/many2one_avatar_user.js +++ /dev/null @@ -1,63 +0,0 @@ -/** @odoo-module **/ - -// This module defines an extension of the Many2OneAvatar widget, which is -// integrated with the messaging system. The Many2OneAvatarUser is designed -// to display people, and when the avatar of those people is clicked, it -// opens a DM chat window with the corresponding user. -// -// This widget is supported on many2one fields pointing to 'res.users'. -// -// Usage: -// -// -// The widget is designed to be extended, to support many2one fields pointing -// to other models than 'res.users'. - -import fieldRegistry from 'web.field_registry'; -import { Many2OneAvatar } from 'web.relational_fields'; - -const { Component } = owl; - -const Many2OneAvatarUser = Many2OneAvatar.extend({ - events: Object.assign({}, Many2OneAvatar.prototype.events, { - 'click .o_m2o_avatar > img': '_onAvatarClicked', - }), - // This widget is only supported on many2ones pointing to 'res.users' - supportedModels: ['res.users'], - - init() { - this._super(...arguments); - if (!this.supportedModels.includes(this.field.relation)) { - throw new Error(`This widget is only supported on many2one fields pointing to ${JSON.stringify(this.supportedModels)}`); - } - }, - - //---------------------------------------------------------------------- - // Handlers - //---------------------------------------------------------------------- - - /** - * When the avatar is clicked, open a DM chat window with the - * corresponding user. - * - * @private - * @param {MouseEvent} ev - */ - async _onAvatarClicked(ev) { - ev.stopPropagation(); // in list view, prevent from opening the record - const env = Component.env; - await env.messaging.openChat({ userId: this.value.res_id }); - } -}); - -const KanbanMany2OneAvatarUser = Many2OneAvatarUser.extend({ - _template: 'mail.KanbanMany2OneAvatarUser', -}); - -fieldRegistry.add('many2one_avatar_user', Many2OneAvatarUser); -fieldRegistry.add('kanban.many2one_avatar_user', KanbanMany2OneAvatarUser); - -export { - Many2OneAvatarUser, - KanbanMany2OneAvatarUser, -}; diff --git a/addons/mail/static/src/scss/m2x_avatar_user.scss b/addons/mail/static/src/scss/m2x_avatar_user.scss new file mode 100644 index 00000000000..5772dab986e --- /dev/null +++ b/addons/mail/static/src/scss/m2x_avatar_user.scss @@ -0,0 +1,19 @@ +@mixin o-avatar-image-hover { + cursor: pointer; + filter: brightness(0.8); +} + +.o_field_many2one_avatar.o_clickable_m2x_avatar { + .o_m2o_avatar:hover { + @include o-avatar-image-hover; + } +} + +.o_field_many2manytags.avatar.o_clickable_m2x_avatar { + img.o_m2m_avatar { + margin-right: 2px; + &:hover { + @include o-avatar-image-hover; + } + } +} diff --git a/addons/mail/static/tests/many2one_avatar_user_tests.js b/addons/mail/static/tests/m2x_avatar_user_tests.js similarity index 57% rename from addons/mail/static/tests/many2one_avatar_user_tests.js rename to addons/mail/static/tests/m2x_avatar_user_tests.js index fb6a6d62788..0852641d5d3 100644 --- a/addons/mail/static/tests/many2one_avatar_user_tests.js +++ b/addons/mail/static/tests/m2x_avatar_user_tests.js @@ -2,14 +2,15 @@ import { afterEach, beforeEach, start } from '@mail/utils/test_utils'; +import FormView from 'web.FormView'; import KanbanView from 'web.KanbanView'; import ListView from 'web.ListView'; -import { Many2OneAvatarUser } from '@mail/js/many2one_avatar_user'; +import { Many2OneAvatarUser } from '@mail/js/m2x_avatar_user'; import { dom, mock } from 'web.test_utils'; QUnit.module('mail', {}, function () { - QUnit.module('Many2OneAvatarUser', { + QUnit.module('M2XAvatarUser', { beforeEach() { beforeEach(this); @@ -20,9 +21,10 @@ QUnit.module('mail', {}, function () { 'foo': { fields: { user_id: { string: "User", type: 'many2one', relation: 'res.users' }, + user_ids: { string: "Users", type: "many2many", relation: 'res.users' }, }, records: [ - { id: 1, user_id: 11 }, + { id: 1, user_id: 11, user_ids: [11, 23], }, { id: 2, user_id: 7 }, { id: 3, user_id: 11 }, { id: 4, user_id: 23 }, @@ -118,4 +120,82 @@ QUnit.module('mail', {}, function () { kanban.destroy(); }); + + QUnit.test('many2many_avatar_user widget in form view', async function (assert) { + assert.expect(7); + + const { widget: form } = await start({ + hasView: true, + View: FormView, + model: 'foo', + data: this.data, + arch: '
', + mockRPC(route, args) { + if (args.method === 'read') { + assert.step(`read ${args.model} ${args.args[0]}`); + } + return this._super(...arguments); + }, + res_id: 1, + }); + + assert.containsN(form, '.o_field_many2manytags.avatar.o_field_widget .badge', 2, + "should have 2 records"); + assert.strictEqual(form.$('.o_field_many2manytags.avatar.o_field_widget .badge:first img').data('src'), '/web/image/res.users/11/image_128', + "should have correct avatar image"); + + await dom.click(form.$('.o_field_many2manytags.avatar .badge:first .o_m2m_avatar')); + await dom.click(form.$('.o_field_many2manytags.avatar .badge:nth(1) .o_m2m_avatar')); + + assert.verifySteps([ + "read foo 1", + 'read res.users 11,23', + "read res.users 11", + "read res.users 23", + ]); + + form.destroy(); + }); + + QUnit.test('many2many_avatar_user widget in list view', async function (assert) { + assert.expect(7); + + const { widget: list } = await start({ + hasView: true, + View: ListView, + model: 'foo', + data: this.data, + arch: '', + mockRPC(route, args) { + if (args.method === 'read') { + assert.step(`read ${args.model} ${args.args[0]}`); + } + return this._super(...arguments); + }, + }); + + mock.intercept(list, 'open_record', () => { + assert.step('open record'); + }); + + assert.containsN(list.$(".o_data_cell:first"), '.o_field_many2manytags.avatar.o_field_widget .badge', 2, + "should have 2 records"); + assert.strictEqual(list.$('.o_data_cell:first .o_tag_badge_text').text().trim(), 'MarioYoshi'); + + // sanity check: later on, we'll check that clicking on the avatar doesn't open the record + await dom.click(list.$('.o_data_row:first .o_field_many2manytags')); + + await dom.click(list.$('.o_data_cell:nth(0) .o_m2m_avatar:nth(0)')); + await dom.click(list.$('.o_data_cell:nth(0) .o_m2m_avatar:nth(1)')); + + assert.verifySteps([ + 'read res.users 11,23', + "open record", + "read res.users 11", + "read res.users 23", + ]); + + list.destroy(); + }); + }); diff --git a/addons/web/static/src/scss/fields.scss b/addons/web/static/src/scss/fields.scss index 21ea022c5d6..e6f725342cd 100644 --- a/addons/web/static/src/scss/fields.scss +++ b/addons/web/static/src/scss/fields.scss @@ -229,27 +229,28 @@ } // Many2many tags avatar &.avatar { + align-items: center; .badge { - padding-left: 4px; + padding: 0 0.6em 0 0; img { - height: 18px; - width: 18px; + height: 17px; + width: 17px; object-fit: cover; - } - &.o_tag_color_0 { - background-color: #F1F1F1; - box-shadow: none; - border: 1px solid gray('300'); + margin-top: 1px; + margin-left: 1px; } .o_badge_text { display: flex; align-items:center; + padding-top: 0.25em; + padding-bottom: 0.25em; } .o_tag_badge_text { padding-left:2px; } .o_delete { - margin-top: 3px; + padding-top: 0.25em; + padding-bottom: 0.25em; } } } diff --git a/addons/web/static/src/xml/base.xml b/addons/web/static/src/xml/base.xml index f7a5bb592cd..14e59e0600c 100644 --- a/addons/web/static/src/xml/base.xml +++ b/addons/web/static/src/xml/base.xml @@ -1311,8 +1311,8 @@ - - + +