From fb8112a06bf211e1c5b9def5a249ab3d0b902108 Mon Sep 17 00:00:00 2001 From: Romain Estievenart Date: Thu, 16 Mar 2023 22:31:34 +0000 Subject: [PATCH] [IMP] mail,hr,web: change the UX of the Many2ManyAvatarFieldPopover MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The commit change the design of the Many2ManyAvatarFieldPopover the goal is to remove the m2m dropdown and fill the dropdown content directly inside the popover. It also reverses the order of the displayed tags inside the m2m avatar field in the kanban view and inside the popover. It removes the possibility to unassign users on mouse hover in kanban view. Note: It's still possible to remove users inside an m2m_avatar_field popover. Task ID: 3229888 closes odoo/odoo#117194 X-original-commit: f0d71e98743421318a798ef9d4f1e75d11a23334 Signed-off-by: Michaƫl Mattiello --- .../many2many_avatar_employee_field.js | 10 +- .../static/tests/m2x_avatar_employee_tests.js | 12 +-- .../tests/web/fields/m2x_avatar_user_tests.js | 8 +- .../src/core/autocomplete/autocomplete.js | 28 ++++- .../src/core/autocomplete/autocomplete.xml | 19 +++- .../src/core/popover/popover_service.js | 1 - .../many2many_tags_avatar_field.js | 66 +++++------- .../many2many_tags_avatar_field.scss | 28 +++-- .../many2many_tags_avatar_field.xml | 26 ++--- .../many2one_avatar/many2one_avatar_field.js | 26 ++--- .../many2one_avatar_field.scss | 30 +++--- .../many2one_avatar/many2one_avatar_field.xml | 12 +-- .../src/views/fields/relational_utils.js | 3 + .../src/views/fields/relational_utils.xml | 4 +- .../many2many_tags_avatar_field_tests.js | 91 ++++++++-------- .../fields/many2one_avatar_field_tests.js | 100 +++++++++++++----- 16 files changed, 262 insertions(+), 202 deletions(-) diff --git a/addons/hr/static/src/views/fields/many2many_avatar_employee_field/many2many_avatar_employee_field.js b/addons/hr/static/src/views/fields/many2many_avatar_employee_field/many2many_avatar_employee_field.js index b966923dc12..0af4875e896 100644 --- a/addons/hr/static/src/views/fields/many2many_avatar_employee_field/many2many_avatar_employee_field.js +++ b/addons/hr/static/src/views/fields/many2many_avatar_employee_field/many2many_avatar_employee_field.js @@ -6,6 +6,7 @@ import { KanbanMany2ManyTagsAvatarUserField, many2ManyTagsAvatarUserField, kanbanMany2ManyTagsAvatarUserField, + listMany2ManyTagsAvatarUserField, } from "@mail/web/fields/many2many_avatar_user_field/many2many_avatar_user_field"; export class Many2ManyTagsAvatarEmployeeField extends Many2ManyTagsAvatarUserField { @@ -43,6 +44,13 @@ export const kanbanMany2ManyTagsAvatarEmployeeField = { registry .category("fields") .add("kanban.many2many_avatar_employee", kanbanMany2ManyTagsAvatarEmployeeField); +export const listMany2ManyTagsAvatarEmployeeField = { + ...listMany2ManyTagsAvatarUserField, + additionalClasses: [ + ...listMany2ManyTagsAvatarUserField.additionalClasses, + "o_field_many2many_avatar_user", + ], +}; registry .category("fields") - .add("list.many2many_avatar_employee", kanbanMany2ManyTagsAvatarEmployeeField); + .add("list.many2many_avatar_employee", listMany2ManyTagsAvatarEmployeeField); diff --git a/addons/hr/static/tests/m2x_avatar_employee_tests.js b/addons/hr/static/tests/m2x_avatar_employee_tests.js index 1108015b67c..5a874d39169 100644 --- a/addons/hr/static/tests/m2x_avatar_employee_tests.js +++ b/addons/hr/static/tests/m2x_avatar_employee_tests.js @@ -347,14 +347,14 @@ QUnit.test("many2many_avatar_employee widget in kanban view", async function (as assert.containsN( document.body, ".o_kanban_record:first .o_field_many2many_avatar_employee img.o_m2m_avatar", - 3, - "should have 2 avatar images and the default empty avatar" + 2, + "should have 2 avatar images" ); assert.strictEqual( document .querySelector(".o_kanban_record .o_field_many2many_avatar_employee img.o_m2m_avatar") .getAttribute("data-src"), - `/web/image/hr.employee.public/${employeeId_1}/avatar_128` + `/web/image/hr.employee.public/${employeeId_2}/avatar_128`, ); assert.strictEqual( document @@ -362,11 +362,11 @@ QUnit.test("many2many_avatar_employee widget in kanban view", async function (as ".o_kanban_record .o_field_many2many_avatar_employee img.o_m2m_avatar" )[1] .getAttribute("data-src"), - `/web/image/hr.employee.public/${employeeId_2}/avatar_128` + `/web/image/hr.employee.public/${employeeId_1}/avatar_128`, ); - await dom.click(document.querySelector(".o_kanban_record .o_m2m_avatar")); - await dom.click(document.querySelectorAll(".o_kanban_record .o_m2m_avatar")[1]); + await dom.click(document.querySelectorAll('.o_kanban_record img.o_m2m_avatar')[1]); + await dom.click(document.querySelectorAll('.o_kanban_record img.o_m2m_avatar')[0]); assert.verifySteps([ `read hr.employee.public ${employeeId_1},${employeeId_2}`, `read hr.employee.public ${employeeId_1}`, diff --git a/addons/mail/static/tests/web/fields/m2x_avatar_user_tests.js b/addons/mail/static/tests/web/fields/m2x_avatar_user_tests.js index 342aa4562bc..7db434a06c2 100644 --- a/addons/mail/static/tests/web/fields/m2x_avatar_user_tests.js +++ b/addons/mail/static/tests/web/fields/m2x_avatar_user_tests.js @@ -59,10 +59,10 @@ QUnit.test("many2many_avatar_user in kanban view", async (assert) => { ); const tags = document.querySelectorAll(".o_popover > .o_field_tags > .o_tag"); assert.strictEqual(tags.length, 4); - assert.strictEqual(tags[0].innerText.trim(), "Mario"); - assert.strictEqual(tags[1].innerText.trim(), "Yoshi"); - assert.strictEqual(tags[2].innerText.trim(), "Luigi"); - assert.strictEqual(tags[3].innerText.trim(), "Tapu"); + assert.strictEqual(tags[0].innerText.trim(), "Tapu"); + assert.strictEqual(tags[1].innerText.trim(), "Luigi"); + assert.strictEqual(tags[2].innerText.trim(), "Yoshi"); + assert.strictEqual(tags[3].innerText.trim(), "Mario"); }); QUnit.test( diff --git a/addons/web/static/src/core/autocomplete/autocomplete.js b/addons/web/static/src/core/autocomplete/autocomplete.js index e2de3f4dcd8..e6567b04946 100644 --- a/addons/web/static/src/core/autocomplete/autocomplete.js +++ b/addons/web/static/src/core/autocomplete/autocomplete.js @@ -41,10 +41,14 @@ export class AutoComplete extends Component { }); // position and size - usePosition(() => this.inputRef.el, { - popper: "sourcesList", - position: "bottom-start", - }); + if (this.props.dropdown) { + usePosition(() => this.inputRef.el, { + popper: "sourcesList", + position: "bottom-start", + }); + } else { + this.open(false); + } } get isOpened() { @@ -99,6 +103,9 @@ export class AutoComplete extends Component { this.navigate(0); }); } + get displayOptions() { + return !this.props.dropdown || (this.isOpened && this.hasOptions); + } loadOptions(options, request) { if (typeof options === "function") { return options(request); @@ -234,7 +241,16 @@ export class AutoComplete extends Component { this.inputRef.el.setSelectionRange(0, this.inputRef.el.value.length); this.props.onFocus(ev); } - + get autoCompleteRootClass() { + let classList = ""; + if (this.props.class) { + classList += this.props.class; + } + if (this.props.dropdown) { + classList += " dropdown"; + } + return classList; + } onInputKeydown(ev) { const hotkey = getActiveHotkey(ev); switch (hotkey) { @@ -324,12 +340,14 @@ Object.assign(AutoComplete, { onBlur: { type: Function, optional: true }, onFocus: { type: Function, optional: true }, input: { type: Function, optional: true }, + dropdown: { type: Boolean, optional: true }, autofocus: { type: Boolean, optional: true }, class: { type: String, optional: true }, }, defaultProps: { placeholder: "", autoSelect: false, + dropdown: true, onInput: () => {}, onChange: () => {}, onBlur: () => {}, diff --git a/addons/web/static/src/core/autocomplete/autocomplete.xml b/addons/web/static/src/core/autocomplete/autocomplete.xml index fef78f82f1c..8ade9a819ed 100644 --- a/addons/web/static/src/core/autocomplete/autocomplete.xml +++ b/addons/web/static/src/core/autocomplete/autocomplete.xml @@ -2,7 +2,7 @@ -