[IMP] web, mail: field widgets' smart action in command palette
This commit adds the smarts actions: - Assign to... (many2one_avatar and many2many_avatar) - Assign/unassign to me (many2one_avatar and many2many_avatar) - Set priority... (priority) - Set kanban state... (state_selection) These smarts actions are commands with hotkeys accessible from the form views containing the widget to which they are linked. closes odoo/odoo#75312 Task-id: 2588238 Signed-off-by: Lucas Perais (lpe) <lpe@odoo.com>
This commit is contained in:
committed by
Lucas Perais (lpe)
parent
39636ea698
commit
118ddcace0
@@ -1,8 +1,9 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import core from 'web.core';
|
||||
import fieldRegistry from 'web.field_registry';
|
||||
import { Many2OneAvatar } from 'web.relational_fields';
|
||||
import { FieldMany2ManyTagsAvatar, KanbanMany2ManyTagsAvatar, ListMany2ManyTagsAvatar } from 'web.relational_fields';
|
||||
import { FieldMany2ManyTagsAvatar, KanbanMany2ManyTagsAvatar, ListMany2ManyTagsAvatar, Many2OneAvatar } from 'web.relational_fields';
|
||||
import session from 'web.session';
|
||||
|
||||
const { Component } = owl;
|
||||
|
||||
@@ -58,6 +59,124 @@ export const Many2OneAvatarUser = Many2OneAvatar.extend(M2XAvatarMixin, {
|
||||
'click .o_m2o_avatar > img': '_onAvatarClicked',
|
||||
}),
|
||||
|
||||
on_attach_callback() {
|
||||
console.log("on_attach_callback ", this.mode)
|
||||
|
||||
this._registerCommandAssignTo()
|
||||
},
|
||||
on_detach_callback(){
|
||||
this._unregisterCommandAssignTo()
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Private
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @override
|
||||
* @private
|
||||
*/
|
||||
_registerCommandAssignTo() {
|
||||
const self = this;
|
||||
if (self.viewType === "form") {
|
||||
let provide = async (env, options) => {
|
||||
if (self.isDestroyed()) {
|
||||
return
|
||||
}
|
||||
const records = await self._searchAssignTo(options.searchValue, 10)
|
||||
return records.map((record) => ({
|
||||
name: record[1],
|
||||
action: () => {
|
||||
if (self.isDestroyed()) {
|
||||
return
|
||||
}
|
||||
self.reinitialize({ id: record[0], display_name: record[1] });
|
||||
}
|
||||
}))
|
||||
}
|
||||
let getCommandDefinition = (env) => ({
|
||||
name: env._t("Assign to ..."),
|
||||
options: {
|
||||
activeElement: env.services.ui.getActiveElementOf(self.el),
|
||||
category: "smart_action",
|
||||
hotkey: "alt+i",
|
||||
},
|
||||
async action() {
|
||||
return env.services.command.openPalette({
|
||||
emptyMessageByNamespace: { "default": env._t("No users found") },
|
||||
placeholder: env._t("Select a user..."),
|
||||
providers: [{ provide }],
|
||||
})
|
||||
},
|
||||
});
|
||||
core.bus.trigger("set_legacy_command", "web.Many2OneAvatar.assignTo", getCommandDefinition, self.el);
|
||||
|
||||
|
||||
getCommandDefinition = (env) => ({
|
||||
name: env._t("Assign/unassign to me"),
|
||||
options: {
|
||||
activeElement: env.services.ui.getActiveElementOf(self.el),
|
||||
category: "smart_action",
|
||||
hotkey: "alt+shift+i",
|
||||
},
|
||||
action() {
|
||||
if (self.isDestroyed()) {
|
||||
return
|
||||
}
|
||||
if (self.value.res_id === session.user_id[0]) {
|
||||
self._setValue({
|
||||
operation: 'DELETE',
|
||||
ids: [session.user_id[0]],
|
||||
});
|
||||
} else {
|
||||
self.reinitialize({ id: session.user_id[0], display_name: session.name });
|
||||
}
|
||||
},
|
||||
});
|
||||
core.bus.trigger("set_legacy_command", "web.Many2OneAvatar.assignToMe", getCommandDefinition);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* @override
|
||||
* @private
|
||||
*/
|
||||
_unregisterCommandAssignTo() {
|
||||
core.bus.trigger("remove_legacy_command", "web.Many2OneAvatar.assignTo");
|
||||
core.bus.trigger("remove_legacy_command", "web.Many2OneAvatar.assignToMe");
|
||||
},
|
||||
|
||||
/**
|
||||
* @override
|
||||
* @private
|
||||
*/
|
||||
_searchAssignTo(searchValue, limit) {
|
||||
const value = searchValue.trim();
|
||||
const domain = this.record.getDomain(this.recordParams);
|
||||
const context = Object.assign(
|
||||
this.record.getContext(this.recordParams),
|
||||
this.additionalContext,
|
||||
);
|
||||
|
||||
// Exclude black-listed ids from the domain
|
||||
const blackListedIds = this._getSearchBlacklist();
|
||||
if (blackListedIds.length) {
|
||||
domain.push(['id', 'not in', blackListedIds]);
|
||||
}
|
||||
|
||||
const nameSearch = this._rpc({
|
||||
model: this.field.relation,
|
||||
method: "name_search",
|
||||
kwargs: {
|
||||
name: value,
|
||||
args: domain,
|
||||
operator: "ilike",
|
||||
limit: limit + 1,
|
||||
context,
|
||||
}
|
||||
});
|
||||
return this.orderer.add(nameSearch);
|
||||
},
|
||||
//----------------------------------------------------------------------
|
||||
// Handlers
|
||||
//----------------------------------------------------------------------
|
||||
@@ -99,7 +218,108 @@ const M2MAvatarMixin = Object.assign(M2XAvatarMixin, {
|
||||
},
|
||||
});
|
||||
|
||||
export const Many2ManyAvatarUser = FieldMany2ManyTagsAvatar.extend(M2MAvatarMixin, {});
|
||||
export const Many2ManyAvatarUser = FieldMany2ManyTagsAvatar.extend(M2MAvatarMixin, {
|
||||
|
||||
on_attach_callback() {
|
||||
this._registerCommandAssignTo()
|
||||
},
|
||||
on_detach_callback(){
|
||||
this._unregisterCommandAssignTo()
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Private
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @override
|
||||
* @private
|
||||
*/
|
||||
_registerCommandAssignTo() {
|
||||
const self = this;
|
||||
if (self.viewType === "form") {
|
||||
const provide = async (env, options) => {
|
||||
if (self.isDestroyed()) {
|
||||
return
|
||||
}
|
||||
const many2one = new Many2OneAvatarUser(self, self.name, self.record, {
|
||||
mode: 'edit',
|
||||
noOpen: true,
|
||||
noCreate: !self.canCreate,
|
||||
viewType: self.viewType,
|
||||
attrs: self.attrs,
|
||||
});
|
||||
many2one._getSearchBlacklist = function () {
|
||||
return self.value.res_ids;
|
||||
};
|
||||
const records = await many2one._searchAssignTo(options.searchValue, 10)
|
||||
return records.map((record) => ({
|
||||
name: record[1],
|
||||
action: () => {
|
||||
if (self.isDestroyed()) {
|
||||
return
|
||||
}
|
||||
self._setValue({
|
||||
operation: 'ADD_M2M',
|
||||
ids: { id: record[0], display_name: record[1] }
|
||||
});
|
||||
},
|
||||
}))
|
||||
};
|
||||
let getCommandDefinition = (env) => ({
|
||||
name: env._t("Assign to ..."),
|
||||
options: {
|
||||
activeElement: env.services.ui.getActiveElementOf(self.el),
|
||||
category: "smart_action",
|
||||
hotkey: "alt+i",
|
||||
},
|
||||
action() {
|
||||
return env.services.command.openPalette({
|
||||
emptyMessageByNamespace: { "default": env._t("No users found") },
|
||||
placeholder: env._t("Select a user..."),
|
||||
providers: [{ provide }],
|
||||
})
|
||||
},
|
||||
})
|
||||
core.bus.trigger("set_legacy_command", "web.FieldMany2ManyTagsAvatar.assignTo", getCommandDefinition)
|
||||
|
||||
getCommandDefinition = (env) => ({
|
||||
name: env._t("Assign/unassign to me"),
|
||||
options: {
|
||||
activeElement: env.services.ui.getActiveElementOf(self.el),
|
||||
category: "smart_action",
|
||||
hotkey: "alt+shift+i",
|
||||
},
|
||||
action() {
|
||||
if (self.isDestroyed()) {
|
||||
return
|
||||
}
|
||||
if (self.value.res_ids.includes(session.user_id[0])) {
|
||||
self._setValue({
|
||||
operation: 'DELETE',
|
||||
ids: [session.user_id[0]],
|
||||
});
|
||||
} else {
|
||||
self._setValue({
|
||||
operation: 'ADD_M2M',
|
||||
ids: { id: session.user_id[0], display_name: session.name }
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
core.bus.trigger("set_legacy_command", "web.Many2OneAvatar.assignToMe", getCommandDefinition);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* @override
|
||||
* @private
|
||||
*/
|
||||
_unregisterCommandAssignTo() {
|
||||
core.bus.trigger("remove_legacy_command", "web.FieldMany2ManyTagsAvatar.assignTo");
|
||||
core.bus.trigger("remove_legacy_command", "web.Many2OneAvatar.assignToMe");
|
||||
}
|
||||
});
|
||||
|
||||
export const KanbanMany2ManyAvatarUser = KanbanMany2ManyTagsAvatar.extend(M2MAvatarMixin, {});
|
||||
|
||||
|
||||
@@ -1,11 +1,17 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { Many2OneAvatarUser } from '@mail/js/m2x_avatar_user';
|
||||
import { afterEach, beforeEach, start } from '@mail/utils/test_utils';
|
||||
|
||||
import { click, legacyExtraNextTick, patchWithCleanup, triggerHotkey } from "@web/../tests/helpers/utils";
|
||||
import { createWebClient, doAction } from '@web/../tests/webclient/helpers';
|
||||
import { registry } from "@web/core/registry";
|
||||
import { makeLegacyCommandService } from "@web/legacy/utils";
|
||||
import core from 'web.core';
|
||||
import FormView from 'web.FormView';
|
||||
import KanbanView from 'web.KanbanView';
|
||||
import ListView from 'web.ListView';
|
||||
import { Many2OneAvatarUser } from '@mail/js/m2x_avatar_user';
|
||||
import session from 'web.session';
|
||||
import makeTestEnvironment from "web.test_env";
|
||||
import { dom, mock, nextTick } from 'web.test_utils';
|
||||
|
||||
|
||||
@@ -21,7 +27,7 @@ QUnit.module('mail', {}, function () {
|
||||
'foo': {
|
||||
fields: {
|
||||
user_id: { string: "User", type: 'many2one', relation: 'res.users' },
|
||||
user_ids: { string: "Users", type: "many2many", relation: 'res.users' },
|
||||
user_ids: { string: "Users", type: "many2many", relation: 'res.users', default:[] },
|
||||
},
|
||||
records: [
|
||||
{ id: 1, user_id: 11, user_ids: [11, 23], },
|
||||
@@ -408,4 +414,195 @@ QUnit.module('mail', {}, function () {
|
||||
form.destroy();
|
||||
});
|
||||
|
||||
QUnit.test('many2one_avatar_user widget edited by the smart action "Assign to..."', async function (assert) {
|
||||
assert.expect(4);
|
||||
|
||||
const legacyEnv = makeTestEnvironment({ bus: core.bus });
|
||||
const serviceRegistry = registry.category("services");
|
||||
serviceRegistry.add("legacy_command", makeLegacyCommandService(legacyEnv));
|
||||
|
||||
const views = {
|
||||
'foo,false,form': '<form><field name="user_id" widget="many2one_avatar_user"/></form>',
|
||||
'foo,false,search': '<search></search>',
|
||||
};
|
||||
const models = {
|
||||
'foo': this.data.foo,
|
||||
'res.partner': this.data['res.partner'],
|
||||
'res.users': this.data['res.users'],
|
||||
}
|
||||
const serverData = { models, views}
|
||||
const webClient = await createWebClient({serverData});
|
||||
await doAction(webClient, {
|
||||
res_id: 1,
|
||||
type: 'ir.actions.act_window',
|
||||
target: 'current',
|
||||
res_model: 'foo',
|
||||
'view_mode': 'form',
|
||||
'views': [[false, 'form']],
|
||||
});
|
||||
assert.strictEqual(webClient.el.querySelector(".o_m2o_avatar > span").textContent, "Mario")
|
||||
|
||||
triggerHotkey("control+k")
|
||||
await nextTick();
|
||||
const idx = [...webClient.el.querySelectorAll(".o_command")].map(el => el.textContent).indexOf("Assign to ...ALT + I")
|
||||
assert.ok(idx >= 0);
|
||||
|
||||
await click([...webClient.el.querySelectorAll(".o_command")][idx])
|
||||
await nextTick();
|
||||
assert.deepEqual([...webClient.el.querySelectorAll(".o_command")].map(el => el.textContent), [
|
||||
"Your Company, Mitchell Admin",
|
||||
"Public user",
|
||||
"Mario",
|
||||
"Luigi",
|
||||
"Yoshi",
|
||||
])
|
||||
await click(webClient.el, "#o_command_3")
|
||||
await legacyExtraNextTick();
|
||||
assert.strictEqual(webClient.el.querySelector(".o_m2o_avatar > span").textContent, "Luigi")
|
||||
});
|
||||
|
||||
QUnit.test('many2one_avatar_user widget edited by the smart action "Assign to me"', async function (assert) {
|
||||
assert.expect(4);
|
||||
|
||||
patchWithCleanup(session, { user_id: [7] })
|
||||
const legacyEnv = makeTestEnvironment({ bus: core.bus });
|
||||
const serviceRegistry = registry.category("services");
|
||||
serviceRegistry.add("legacy_command", makeLegacyCommandService(legacyEnv));
|
||||
|
||||
const views = {
|
||||
'foo,false,form': '<form><field name="user_id" widget="many2one_avatar_user"/></form>',
|
||||
'foo,false,search': '<search></search>',
|
||||
};
|
||||
const models = {
|
||||
'foo': this.data.foo,
|
||||
'res.partner': this.data['res.partner'],
|
||||
'res.users': this.data['res.users'],
|
||||
}
|
||||
const serverData = { models, views}
|
||||
const webClient = await createWebClient({serverData});
|
||||
await doAction(webClient, {
|
||||
res_id: 1,
|
||||
type: 'ir.actions.act_window',
|
||||
target: 'current',
|
||||
res_model: 'foo',
|
||||
'view_mode': 'form',
|
||||
'views': [[false, 'form']],
|
||||
});
|
||||
assert.strictEqual(webClient.el.querySelector(".o_m2o_avatar > span").textContent, "Mario")
|
||||
triggerHotkey("control+k")
|
||||
await nextTick();
|
||||
const idx = [...webClient.el.querySelectorAll(".o_command")].map(el => el.textContent).indexOf("Assign/unassign to meALT + SHIFT + I")
|
||||
assert.ok(idx >= 0);
|
||||
|
||||
// Assign me (Luigi)
|
||||
triggerHotkey("alt+shift+i")
|
||||
await legacyExtraNextTick();
|
||||
assert.strictEqual(webClient.el.querySelector(".o_m2o_avatar > span").textContent, "Luigi")
|
||||
|
||||
// Unassign me
|
||||
triggerHotkey("control+k");
|
||||
await nextTick();
|
||||
await click([...webClient.el.querySelectorAll(".o_command")][idx])
|
||||
await legacyExtraNextTick();
|
||||
assert.strictEqual(webClient.el.querySelector(".o_m2o_avatar > span").textContent, "")
|
||||
});
|
||||
|
||||
QUnit.test('many2many_avatar_user widget edited by the smart action "Assign to..."', async function (assert) {
|
||||
assert.expect(4);
|
||||
|
||||
const legacyEnv = makeTestEnvironment({ bus: core.bus });
|
||||
const serviceRegistry = registry.category("services");
|
||||
serviceRegistry.add("legacy_command", makeLegacyCommandService(legacyEnv));
|
||||
|
||||
const views = {
|
||||
'foo,false,form': '<form><field name="user_ids" widget="many2many_avatar_user"/></form>',
|
||||
'foo,false,search': '<search></search>',
|
||||
};
|
||||
const models = {
|
||||
'foo': this.data.foo,
|
||||
'res.partner': this.data['res.partner'],
|
||||
'res.users': this.data['res.users'],
|
||||
}
|
||||
const serverData = { models, views}
|
||||
const webClient = await createWebClient({serverData});
|
||||
await doAction(webClient, {
|
||||
res_id: 1,
|
||||
type: 'ir.actions.act_window',
|
||||
target: 'current',
|
||||
res_model: 'foo',
|
||||
'view_mode': 'form',
|
||||
'views': [[false, 'form']],
|
||||
});
|
||||
let userNames = [...webClient.el.querySelectorAll(".o_tag_badge_text")].map((el => el.textContent));
|
||||
assert.deepEqual(userNames, ["Mario", "Yoshi"]);
|
||||
|
||||
triggerHotkey("control+k")
|
||||
await nextTick();
|
||||
const idx = [...webClient.el.querySelectorAll(".o_command")].map(el => el.textContent).indexOf("Assign to ...ALT + I")
|
||||
assert.ok(idx >= 0);
|
||||
|
||||
await click([...webClient.el.querySelectorAll(".o_command")][idx])
|
||||
await nextTick();
|
||||
assert.deepEqual([...webClient.el.querySelectorAll(".o_command")].map(el => el.textContent), [
|
||||
"Your Company, Mitchell Admin",
|
||||
"Public user",
|
||||
"Luigi"
|
||||
]);
|
||||
|
||||
await click(webClient.el, "#o_command_2");
|
||||
await legacyExtraNextTick();
|
||||
userNames = [...webClient.el.querySelectorAll(".o_tag_badge_text")].map(el => el.textContent);
|
||||
assert.deepEqual(userNames, ["Mario", "Yoshi", "Luigi"]);
|
||||
});
|
||||
|
||||
QUnit.test('many2many_avatar_user widget edited by the smart action "Assign to me"', async function (assert) {
|
||||
assert.expect(4);
|
||||
|
||||
patchWithCleanup(session, { user_id: [7] })
|
||||
const legacyEnv = makeTestEnvironment({ bus: core.bus });
|
||||
const serviceRegistry = registry.category("services");
|
||||
serviceRegistry.add("legacy_command", makeLegacyCommandService(legacyEnv));
|
||||
|
||||
const views = {
|
||||
'foo,false,form': '<form><field name="user_ids" widget="many2many_avatar_user"/></form>',
|
||||
'foo,false,search': '<search></search>',
|
||||
};
|
||||
const models = {
|
||||
'foo': this.data.foo,
|
||||
'res.partner': this.data['res.partner'],
|
||||
'res.users': this.data['res.users'],
|
||||
}
|
||||
const serverData = { models, views}
|
||||
const webClient = await createWebClient({serverData});
|
||||
await doAction(webClient, {
|
||||
res_id: 1,
|
||||
type: 'ir.actions.act_window',
|
||||
target: 'current',
|
||||
res_model: 'foo',
|
||||
'view_mode': 'form',
|
||||
'views': [[false, 'form']],
|
||||
});
|
||||
let userNames = [...webClient.el.querySelectorAll(".o_tag_badge_text")].map((el => el.textContent));
|
||||
assert.deepEqual(userNames, ["Mario", "Yoshi"]);
|
||||
|
||||
triggerHotkey("control+k");
|
||||
await nextTick();
|
||||
const idx = [...webClient.el.querySelectorAll(".o_command")].map(el => el.textContent).indexOf("Assign/unassign to meALT + SHIFT + I");
|
||||
assert.ok(idx >= 0);
|
||||
|
||||
// Assign me (Luigi)
|
||||
triggerHotkey("alt+shift+i");
|
||||
await legacyExtraNextTick();
|
||||
userNames = [...webClient.el.querySelectorAll(".o_tag_badge_text")].map((el => el.textContent));
|
||||
assert.deepEqual(userNames, ["Mario", "Yoshi", "Luigi"]);
|
||||
|
||||
// Unassign me
|
||||
triggerHotkey("control+k");
|
||||
await nextTick();
|
||||
await click([...webClient.el.querySelectorAll(".o_command")][idx]);
|
||||
await legacyExtraNextTick();
|
||||
userNames = [...webClient.el.querySelectorAll(".o_tag_badge_text")].map((el => el.textContent));
|
||||
assert.deepEqual(userNames, ["Mario", "Yoshi"]);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -243,6 +243,7 @@ export const hotkeyService = {
|
||||
* @param {string} hotkey
|
||||
* @param {()=>void} callback
|
||||
* @param {Object} options additional options
|
||||
* @param {HTMLElement} [options.activeElement=undefined]
|
||||
* @param {boolean} [options.allowRepeat=false]
|
||||
* allow registration to perform multiple times when hotkey is held down
|
||||
* @param {boolean} [options.global=false]
|
||||
@@ -288,17 +289,19 @@ export const hotkeyService = {
|
||||
const registration = {
|
||||
hotkey: hotkey.toLowerCase(),
|
||||
callback,
|
||||
activeElement: null,
|
||||
activeElement: options.activeElement,
|
||||
allowRepeat: options && options.allowRepeat,
|
||||
global: options && options.global,
|
||||
};
|
||||
registrations.set(token, registration);
|
||||
|
||||
// Due to the way elements are mounted in the DOM by Owl (bottom-to-top),
|
||||
// we need to wait the next micro task tick to set the context owner of the registration.
|
||||
Promise.resolve().then(() => {
|
||||
registration.activeElement = ui.activeElement;
|
||||
});
|
||||
if (!registrations.activeElement) {
|
||||
// Due to the way elements are mounted in the DOM by Owl (bottom-to-top),
|
||||
// we need to wait the next micro task tick to set the context owner of the registration.
|
||||
Promise.resolve().then(() => {
|
||||
registration.activeElement = ui.activeElement;
|
||||
});
|
||||
}
|
||||
|
||||
return token;
|
||||
}
|
||||
@@ -318,6 +321,7 @@ export const hotkeyService = {
|
||||
* @param {() => void} callback
|
||||
* @param {Object} options
|
||||
* @param {boolean} [options.allowRepeat=false]
|
||||
* @param {HTMLElement} [options.activeElement=undefined]
|
||||
* @param {boolean} [options.global=false]
|
||||
* @returns {() => void}
|
||||
*/
|
||||
|
||||
@@ -79,10 +79,18 @@ export const uiService = {
|
||||
activeElems = activeElems.filter((x) => x !== el);
|
||||
bus.trigger("active-element-changed", ui.activeElement);
|
||||
}
|
||||
function getActiveElementOf(el) {
|
||||
for (const activeElement of [...activeElems].reverse()) {
|
||||
if (activeElement.contains(el)) {
|
||||
return activeElement;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Object.assign(ui, {
|
||||
activateElement,
|
||||
deactivateElement,
|
||||
getActiveElementOf,
|
||||
});
|
||||
Object.defineProperty(ui, "activeElement", {
|
||||
get() {
|
||||
|
||||
@@ -2504,6 +2504,44 @@ var PriorityWidget = AbstractField.extend({
|
||||
var checked = this.$("[aria-checked='true']");
|
||||
return checked.length ? checked : this.$("[data-index='1']");
|
||||
},
|
||||
init: function () {
|
||||
this._super.apply(this, arguments);
|
||||
console.log("init ", this.mode)
|
||||
},
|
||||
|
||||
on_attach_callback() {
|
||||
console.log("on_attach_callback ")
|
||||
const self = this;
|
||||
if (self.viewType === "form") {
|
||||
let provide = () => {
|
||||
return self.field.selection.map((value) => ({
|
||||
name: value[1],
|
||||
action: () => {
|
||||
this._setValue(value[0]);
|
||||
}
|
||||
}))
|
||||
}
|
||||
let getCommandDefinition = (env) => ({
|
||||
name: env._t("Set priority..."),
|
||||
options: {
|
||||
activeElement: env.services.ui.getActiveElementOf(self.el),
|
||||
category: "smart_action",
|
||||
hotkey: "alt+r",
|
||||
},
|
||||
action() {
|
||||
return env.services.command.openPalette({
|
||||
placeholder: env._t("Set a priority..."),
|
||||
providers: [{ provide }],
|
||||
})
|
||||
},
|
||||
});
|
||||
core.bus.trigger("set_legacy_command", "web.PriorityWidget.setPriority", getCommandDefinition);
|
||||
}
|
||||
},
|
||||
|
||||
on_detach_callback() {
|
||||
core.bus.trigger("remove_legacy_command", "web.PriorityWidget.setPriority");
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Private
|
||||
@@ -2679,6 +2717,38 @@ var StateSelectionWidget = AbstractField.extend({
|
||||
return this.$("a[data-toggle='dropdown']");
|
||||
},
|
||||
|
||||
on_attach_callback() {
|
||||
const self = this;
|
||||
if (self.viewType === "form") {
|
||||
let provide = () => {
|
||||
return self.field.selection.map((value) => ({
|
||||
name: value[1],
|
||||
action: () => {
|
||||
this._setValue(value[0]);
|
||||
}
|
||||
}))
|
||||
}
|
||||
let getCommandDefinition = (env) => ({
|
||||
name: env._t("Set kanban state..."),
|
||||
options: {
|
||||
activeElement: env.services.ui.getActiveElementOf(self.el),
|
||||
category: "smart_action",
|
||||
hotkey: "alt+shift+r",
|
||||
},
|
||||
action() {
|
||||
return env.services.command.openPalette({
|
||||
placeholder: env._t("Set a kanban state..."),
|
||||
providers: [{ provide }],
|
||||
})
|
||||
},
|
||||
});
|
||||
core.bus.trigger("set_legacy_command", "web.StateSelectionWidget.setKanbanState", getCommandDefinition);
|
||||
}
|
||||
},
|
||||
|
||||
on_detach_callback() {
|
||||
core.bus.trigger("remove_legacy_command", "web.StateSelectionWidget.setKanbanState");
|
||||
},
|
||||
//--------------------------------------------------------------------------
|
||||
// Private
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
makeLegacySessionService,
|
||||
makeLegacyDialogMappingService,
|
||||
makeLegacyCrashManagerService,
|
||||
makeLegacyCommandService,
|
||||
} from "./utils";
|
||||
import * as AbstractService from "web.AbstractService";
|
||||
import * as legacyEnv from "web.env";
|
||||
@@ -43,6 +44,8 @@ export const legacySetupProm = new Promise((resolve) => {
|
||||
serviceRegistry.add("legacy_crash_manager", makeLegacyCrashManagerService(legacyEnv));
|
||||
const legacyDialogMappingService = makeLegacyDialogMappingService(legacyEnv);
|
||||
serviceRegistry.add("legacy_dialog_mapping", legacyDialogMappingService);
|
||||
const legacyCommandService = makeLegacyCommandService(legacyEnv);
|
||||
serviceRegistry.add("legacy_command", legacyCommandService);
|
||||
await Promise.all([whenReady(), session.is_bound]);
|
||||
legacyEnv.qweb.addTemplates(session.owlTemplates);
|
||||
legacySetupResolver(legacyEnv);
|
||||
|
||||
@@ -120,6 +120,41 @@ export function makeLegacyDialogMappingService(legacyEnv) {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Deploys a service allowing legacy to add/remove commands.
|
||||
*
|
||||
* @param {object} legacyEnv
|
||||
* @returns a wowl deployable service
|
||||
*/
|
||||
export function makeLegacyCommandService(legacyEnv) {
|
||||
return {
|
||||
dependencies: ["command"],
|
||||
start(env) {
|
||||
const { command } = env.services;
|
||||
|
||||
const commandRemoveMap = new Map();
|
||||
|
||||
function setLegacyCommand(uniqueId, getCommandDefinition) {
|
||||
console.log("setLegacyCommand ", uniqueId);
|
||||
const { name, action, options } = getCommandDefinition(env);
|
||||
removeLegacyCommand(uniqueId);
|
||||
commandRemoveMap.set(uniqueId, command.add(name, action, options));
|
||||
}
|
||||
|
||||
function removeLegacyCommand(uniqueId) {
|
||||
if (commandRemoveMap.has(uniqueId)) {
|
||||
const removeCommand = commandRemoveMap.get(uniqueId);
|
||||
removeCommand();
|
||||
commandRemoveMap.delete(uniqueId);
|
||||
}
|
||||
}
|
||||
|
||||
legacyEnv.bus.on("set_legacy_command", null, setLegacyCommand);
|
||||
legacyEnv.bus.on("remove_legacy_command", null, removeLegacyCommand);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function makeLegacySessionService(legacyEnv, session) {
|
||||
return {
|
||||
dependencies: ["user"],
|
||||
|
||||
@@ -5,6 +5,7 @@ import { registry } from "@web/core/registry";
|
||||
const commandCategoryRegistry = registry.category("command_categories");
|
||||
commandCategoryRegistry
|
||||
.add("app", {}, { sequence: 10 })
|
||||
.add("smart_action", {}, { sequence: 15 })
|
||||
.add("actions", {}, { sequence: 30 })
|
||||
.add("navbar", {}, { sequence: 40 })
|
||||
.add("default", {}, { sequence: 100 })
|
||||
|
||||
@@ -19,6 +19,7 @@ const { xml } = owl.tags;
|
||||
|
||||
/**
|
||||
* @typedef {{
|
||||
* activeElement?: HTMLElement;
|
||||
* category?: string;
|
||||
* global?: boolean;
|
||||
* hotkey?: string;
|
||||
@@ -27,7 +28,6 @@ const { xml } = owl.tags;
|
||||
|
||||
/**
|
||||
* @typedef {Command & CommandOptions & {
|
||||
* activeElement?: HTMLElement;
|
||||
* removeHotkey?: ()=>void;
|
||||
* }} CommandRegistration
|
||||
*/
|
||||
@@ -114,13 +114,14 @@ export const commandService = {
|
||||
throw new Error("A Command must have a name and an action function.");
|
||||
}
|
||||
|
||||
const registration = Object.assign({}, command, options, { activeElement: null });
|
||||
const registration = Object.assign({}, command, options);
|
||||
|
||||
if (registration.hotkey) {
|
||||
registration.removeHotkey = hotkeyService.add(
|
||||
registration.hotkey,
|
||||
registration.action,
|
||||
{
|
||||
activeElement: registration.activeElement,
|
||||
global: registration.global,
|
||||
}
|
||||
);
|
||||
@@ -128,13 +129,14 @@ export const commandService = {
|
||||
|
||||
const token = nextToken++;
|
||||
registeredCommands.set(token, registration);
|
||||
|
||||
// Due to the way elements are mounted in the DOM by Owl (bottom-to-top),
|
||||
// we need to wait the next micro task tick to set the context activate
|
||||
// element of the subscription.
|
||||
Promise.resolve().then(() => {
|
||||
registration.activeElement = ui.activeElement;
|
||||
});
|
||||
if (!options.activeElement) {
|
||||
// Due to the way elements are mounted in the DOM by Owl (bottom-to-top),
|
||||
// we need to wait the next micro task tick to set the context activate
|
||||
// element of the subscription.
|
||||
Promise.resolve().then(() => {
|
||||
registration.activeElement = ui.activeElement;
|
||||
});
|
||||
}
|
||||
|
||||
return token;
|
||||
}
|
||||
|
||||
@@ -14,6 +14,11 @@ var session = require('web.session');
|
||||
var testUtils = require('web.test_utils');
|
||||
var testUtilsDom = require('web.test_utils_dom');
|
||||
var field_registry = require('web.field_registry');
|
||||
const makeTestEnvironment = require("web.test_env");
|
||||
const { makeLegacyCommandService } = require("@web/legacy/utils");
|
||||
const { registry } = require("@web/core/registry");
|
||||
const { legacyExtraNextTick, patchWithCleanup, triggerHotkey, nextTick, click } = require("@web/../tests/helpers/utils");
|
||||
const { createWebClient, doAction } = require('@web/../tests/webclient/helpers');
|
||||
|
||||
var createView = testUtils.createView;
|
||||
var patchDate = testUtils.mock.patchDate;
|
||||
@@ -6564,6 +6569,48 @@ QUnit.module('basic_fields', {
|
||||
form.destroy();
|
||||
});
|
||||
|
||||
QUnit.test('priority widget edited by the smart action "Set priority..."', async function (assert) {
|
||||
assert.expect(4);
|
||||
|
||||
const legacyEnv = makeTestEnvironment({ bus: core.bus });
|
||||
const serviceRegistry = registry.category("services");
|
||||
serviceRegistry.add("legacy_command", makeLegacyCommandService(legacyEnv));
|
||||
|
||||
const views = {
|
||||
'partner,false,form': '<form>' +
|
||||
'<field name="selection" widget="priority"/>' +
|
||||
'</form>',
|
||||
'partner,false,search': '<search></search>',
|
||||
};
|
||||
const serverData = { models: this.data, views}
|
||||
const webClient = await createWebClient({serverData});
|
||||
await doAction(webClient, {
|
||||
res_id: 1,
|
||||
type: 'ir.actions.act_window',
|
||||
target: 'current',
|
||||
res_model: 'partner',
|
||||
'view_mode': 'form',
|
||||
'views': [[false, 'form']],
|
||||
});
|
||||
assert.containsOnce(webClient, ".fa-star")
|
||||
|
||||
triggerHotkey("control+k")
|
||||
await nextTick();
|
||||
const idx = [...webClient.el.querySelectorAll(".o_command")].map(el => el.textContent).indexOf("Set priority...ALT + R")
|
||||
assert.ok(idx >= 0);
|
||||
|
||||
await click([...webClient.el.querySelectorAll(".o_command")][idx])
|
||||
await nextTick();
|
||||
assert.deepEqual([...webClient.el.querySelectorAll(".o_command")].map(el => el.textContent), [
|
||||
"Normal",
|
||||
"Blocked",
|
||||
"Done"
|
||||
])
|
||||
await click(webClient.el, "#o_command_2")
|
||||
await legacyExtraNextTick();
|
||||
assert.containsN(webClient, ".fa-star", 2)
|
||||
});
|
||||
|
||||
QUnit.module('StateSelection Widget');
|
||||
|
||||
QUnit.test('state_selection widget in form view', async function (assert) {
|
||||
@@ -6821,6 +6868,48 @@ QUnit.module('basic_fields', {
|
||||
list.destroy();
|
||||
});
|
||||
|
||||
QUnit.test('state_selection edited by the smart action "Set kanban state..."', async function (assert) {
|
||||
assert.expect(4);
|
||||
|
||||
const legacyEnv = makeTestEnvironment({ bus: core.bus });
|
||||
const serviceRegistry = registry.category("services");
|
||||
serviceRegistry.add("legacy_command", makeLegacyCommandService(legacyEnv));
|
||||
|
||||
const views = {
|
||||
'partner,false,form': '<form>' +
|
||||
'<field name="selection" widget="state_selection"/>' +
|
||||
'</form>',
|
||||
'partner,false,search': '<search></search>',
|
||||
};
|
||||
const serverData = { models: this.data, views}
|
||||
const webClient = await createWebClient({serverData});
|
||||
await doAction(webClient, {
|
||||
res_id: 1,
|
||||
type: 'ir.actions.act_window',
|
||||
target: 'current',
|
||||
res_model: 'partner',
|
||||
'view_mode': 'form',
|
||||
'views': [[false, 'form']],
|
||||
});
|
||||
assert.containsOnce(webClient, ".o_status_red")
|
||||
|
||||
triggerHotkey("control+k")
|
||||
await nextTick();
|
||||
const idx = [...webClient.el.querySelectorAll(".o_command")].map(el => el.textContent).indexOf("Set kanban state...ALT + SHIFT + R")
|
||||
assert.ok(idx >= 0);
|
||||
|
||||
await click([...webClient.el.querySelectorAll(".o_command")][idx])
|
||||
await nextTick();
|
||||
assert.deepEqual([...webClient.el.querySelectorAll(".o_command")].map(el => el.textContent), [
|
||||
"Normal",
|
||||
"Blocked",
|
||||
"Done"
|
||||
])
|
||||
await click(webClient.el, "#o_command_2")
|
||||
await legacyExtraNextTick();
|
||||
assert.containsOnce(webClient, ".o_status_green")
|
||||
});
|
||||
|
||||
|
||||
QUnit.module('FavoriteWidget');
|
||||
|
||||
|
||||
Reference in New Issue
Block a user