From 1e1b45ea62a180f01204be232677d28675a7e6ff Mon Sep 17 00:00:00 2001 From: FrancoisGe Date: Mon, 10 Jan 2022 16:27:56 +0000 Subject: [PATCH] [IMP] web: Command Palette footer This commit will change the setting of the emptyMessage and the footer of the main command palette. In order to make the command palette footer more customizable, we will replace it with a Component. For example, using a footerComponent in the main command palette allows us to adapt its text based on the namespaces used. Setting up an empty message: Before: ``` registry.category("command_empty_list") .add("@", _lt("No user found")); ``` After: ``` registry.category("command_setup") .add("@", { emptyMessage: _lt("No user found"), }); ``` With this commit, it is possible to add a namespace to the footer tips: ``` registry.category("command_setup") .add("@", { name: _lt("channels"), }); ``` closes odoo/odoo#82541 Signed-off-by: Aaron Bohy (aab) --- .../webclient/commands/command_category.js | 1 - .../src/webclient/commands/mail_providers.js | 12 ++- .../src/core/commands/command_palette.js | 8 +- .../src/core/commands/command_palette.xml | 4 +- .../src/core/commands/command_service.js | 28 +++++-- .../src/core/commands/default_providers.js | 8 +- .../web/static/src/core/debug/debug_menu.js | 2 +- .../src/webclient/menus/menu_providers.js | 7 +- .../core/commands/command_palette_tests.js | 11 +-- .../core/commands/command_service_tests.js | 80 ++++++++++++++++++- 10 files changed, 130 insertions(+), 31 deletions(-) diff --git a/addons/mail/static/src/webclient/commands/command_category.js b/addons/mail/static/src/webclient/commands/command_category.js index 82b81dd4cc9..517b854168b 100644 --- a/addons/mail/static/src/webclient/commands/command_category.js +++ b/addons/mail/static/src/webclient/commands/command_category.js @@ -1,7 +1,6 @@ /** @odoo-module **/ import { registry } from "@web/core/registry"; -import { _lt } from "@web/core/l10n/translation"; const commandCategoryRegistry = registry.category("command_categories"); commandCategoryRegistry.add("mail", {}, { sequence: 20 }); diff --git a/addons/mail/static/src/webclient/commands/mail_providers.js b/addons/mail/static/src/webclient/commands/mail_providers.js index 0208048cee7..3a5443ed0cc 100644 --- a/addons/mail/static/src/webclient/commands/mail_providers.js +++ b/addons/mail/static/src/webclient/commands/mail_providers.js @@ -14,9 +14,15 @@ DialogCommand.template = xml` `; -const commandEmptyMessageRegistry = registry.category("command_empty_list"); -commandEmptyMessageRegistry.add("@", _lt("No user found")); -commandEmptyMessageRegistry.add("#", _lt("No channel found")); +const commandSetupRegistry = registry.category("command_setup"); +commandSetupRegistry.add("@", { + emptyMessage: _lt("No user found"), + name: _lt("users"), +}); +commandSetupRegistry.add("#", { + emptyMessage: _lt("No channel found"), + name: _lt("channels"), +}); const commandProviderRegistry = registry.category("command_provider"); commandProviderRegistry.add("partner", { diff --git a/addons/web/static/src/core/commands/command_palette.js b/addons/web/static/src/core/commands/command_palette.js index 22b5e8ea4e0..605cfab33b6 100644 --- a/addons/web/static/src/core/commands/command_palette.js +++ b/addons/web/static/src/core/commands/command_palette.js @@ -12,7 +12,7 @@ const { Component, hooks } = owl; const { onWillStart, useState } = hooks; const DEFAULT_PLACEHOLDER = _lt("Search..."); -const DEFAULT_EMPTY_MESSAGE = _lt("No results found"); +const DEFAULT_EMPTY_MESSAGE = _lt("No result found"); const FUZZY_NAMESPACES = ["default"]; /** @@ -37,7 +37,7 @@ const FUZZY_NAMESPACES = ["default"]; * @typedef {{ * categoriesByNamespace?: {[namespace]: string[]}; * emptyMessageByNamespace?: {[namespace]: string}; - * footerTemplate?: string; + * footerComponent?: Component; * placeholder?: string; * providers: Provider[]; * searchValue?: string; @@ -84,7 +84,7 @@ export class CommandPalette extends Component { /** * @type {{ commands: CommandItem[], * emptyMessage: string, - * footerTemplate: string, + * footerComponent: Component, * placeholder: string, * searchValue: string, * selectedCommand: CommandItem }} @@ -130,7 +130,7 @@ export class CommandPalette extends Component { this.emptyMessageByNamespace = config.emptyMessageByNamespace || {}; this.providersByNamespace = result; - this.state.footerTemplate = config.footerTemplate; + this.state.footerComponent = config.footerComponent; this.state.placeholder = config.placeholder || DEFAULT_PLACEHOLDER.toString(); diff --git a/addons/web/static/src/core/commands/command_palette.xml b/addons/web/static/src/core/commands/command_palette.xml index 7abfb172ff2..39c4f8877c0 100644 --- a/addons/web/static/src/core/commands/command_palette.xml +++ b/addons/web/static/src/core/commands/command_palette.xml @@ -28,8 +28,8 @@ - diff --git a/addons/web/static/src/core/commands/command_service.js b/addons/web/static/src/core/commands/command_service.js index e3289c648d6..308073a7452 100644 --- a/addons/web/static/src/core/commands/command_service.js +++ b/addons/web/static/src/core/commands/command_service.js @@ -2,6 +2,7 @@ import { registry } from "@web/core/registry"; import { CommandPaletteDialog } from "./command_palette_dialog"; +const { Component } = owl; const { xml } = owl.tags; @@ -33,12 +34,25 @@ const { xml } = owl.tags; */ const commandCategoryRegistry = registry.category("command_categories"); -const commandEmptyMessageRegistry = registry.category("command_empty_list"); const commandProviderRegistry = registry.category("command_provider"); +const commandSetupRegistry = registry.category("command_setup"); -const footerTemplate = xml` +class DefaultFooter extends Component { + setup() { + this.elements = commandSetupRegistry + .getEntries() + .map((el) => ({ namespace: el[0], name: el[1].name })) + .filter((el) => el.name); + } +} +DefaultFooter.template = xml` - TIP — search for @users, #channels, and /menus + TIP — search for + + , + and + + `; @@ -70,14 +84,16 @@ export const commandService = { }); const emptyMessageByNamespace = {}; - commandEmptyMessageRegistry.getEntries().forEach(([key, message]) => { - emptyMessageByNamespace[key] = message.toString(); + commandSetupRegistry.getEntries().forEach(([key, { emptyMessage }]) => { + if (emptyMessage) { + emptyMessageByNamespace[key] = emptyMessage.toString(); + } }); config = Object.assign( { categoriesByNamespace, emptyMessageByNamespace, - footerTemplate, + footerComponent: DefaultFooter, placeholder: env._t("Search for a command..."), providers: commandProviderRegistry.getAll(), }, diff --git a/addons/web/static/src/core/commands/default_providers.js b/addons/web/static/src/core/commands/default_providers.js index 6f90f228b46..7c7398ab4a3 100644 --- a/addons/web/static/src/core/commands/default_providers.js +++ b/addons/web/static/src/core/commands/default_providers.js @@ -10,6 +10,11 @@ import { DefaultCommandItem } from "./command_palette"; const { Component } = owl; +const commandSetupRegistry = registry.category("command_setup"); +commandSetupRegistry.add("default", { + emptyMessage: _lt("No command found"), +}); + export class HotkeyCommandItem extends Component { setup() { useHotkey(this.props.hotkey, () => { @@ -29,11 +34,8 @@ export class HotkeyCommandItem extends Component { } } HotkeyCommandItem.template = "web.HotkeyCommandItem"; -const commandEmptyMessageRegistry = registry.category("command_empty_list"); -commandEmptyMessageRegistry.add("default", _lt("No commands found")); const commandCategoryRegistry = registry.category("command_categories"); - const commandProviderRegistry = registry.category("command_provider"); commandProviderRegistry.add("command", { provide: (env, options = {}) => { diff --git a/addons/web/static/src/core/debug/debug_menu.js b/addons/web/static/src/core/debug/debug_menu.js index 12ad50e6e6f..78f526336c0 100644 --- a/addons/web/static/src/core/debug/debug_menu.js +++ b/addons/web/static/src/core/debug/debug_menu.js @@ -41,7 +41,7 @@ export class DebugMenu extends DebugMenuBasic { categoriesByNamespace: { default: defaultCategories, }, - emptyMessageByNamespace: { default: this.env._t("No commands found") }, + emptyMessageByNamespace: { default: this.env._t("No command found") }, placeholder: this.env._t("Choose a debug command..."), providers: [provider], }; diff --git a/addons/web/static/src/webclient/menus/menu_providers.js b/addons/web/static/src/webclient/menus/menu_providers.js index 3a4e3789f1e..3c0bcd62815 100644 --- a/addons/web/static/src/webclient/menus/menu_providers.js +++ b/addons/web/static/src/webclient/menus/menu_providers.js @@ -14,8 +14,11 @@ const commandCategoryRegistry = registry.category("command_categories"); commandCategoryRegistry.add("apps", { namespace: "/" }, { sequence: 10 }); commandCategoryRegistry.add("menu_items", { namespace: "/" }, { sequence: 20 }); -const commandEmptyMessageRegistry = registry.category("command_empty_list"); -commandEmptyMessageRegistry.add("/", _lt("No menu found")); +const commandSetupRegistry = registry.category("command_setup"); +commandSetupRegistry.add("/", { + emptyMessage: _lt("No menu found"), + name: _lt("menus"), +}); const commandProviderRegistry = registry.category("command_provider"); commandProviderRegistry.add("menu", { diff --git a/addons/web/static/tests/core/commands/command_palette_tests.js b/addons/web/static/tests/core/commands/command_palette_tests.js index b4fdaf5e6d4..3ebfaa608f3 100644 --- a/addons/web/static/tests/core/commands/command_palette_tests.js +++ b/addons/web/static/tests/core/commands/command_palette_tests.js @@ -26,7 +26,8 @@ let target; let testComponent; const serviceRegistry = registry.category("services"); -const footerTemplate = xml`My footer`; +class footerComponent extends Component {} +footerComponent.template = xml`My footer`; class TestComponent extends Component { get DialogContainer() { @@ -78,7 +79,7 @@ QUnit.test("empty providers", async (assert) => { assert.containsOnce(target, ".o_command_palette_listbox_empty"); assert.strictEqual( target.querySelector(".o_command_palette_listbox_empty").textContent, - "No results found" + "No result found" ); assert.strictEqual( target.querySelector(".o_command_palette_search input").placeholder, @@ -153,7 +154,7 @@ QUnit.test("add a footer", async (assert) => { testComponent = await mount(TestComponent, { env, target }); const config = { providers: [], - footerTemplate, + footerComponent, }; env.services.dialog.add(CommandPaletteDialog, { config, @@ -962,7 +963,7 @@ QUnit.test("multi level command", async (assert) => { ]; const config = { emptyMessageByNamespace, - footerTemplate, + footerComponent, placeholder: "placeholder test", providers, }; @@ -1003,7 +1004,7 @@ QUnit.test("multi level command", async (assert) => { await editSearchBar("empty"); assert.strictEqual( target.querySelector(".o_command_palette_listbox_empty").textContent, - "No results found" + "No result found" ); assert.strictEqual( target.querySelector(".o_command_palette_search input").placeholder, diff --git a/addons/web/static/tests/core/commands/command_service_tests.js b/addons/web/static/tests/core/commands/command_service_tests.js index 690462c49eb..6981a25b0b0 100644 --- a/addons/web/static/tests/core/commands/command_service_tests.js +++ b/addons/web/static/tests/core/commands/command_service_tests.js @@ -394,10 +394,14 @@ QUnit.test("configure the empty message based on the namespace", async (assert) provide: () => [], }); - const commandEmptyMessageRegistry = registry.category("command_empty_list"); - clearRegistryWithCleanup(commandEmptyMessageRegistry); - commandEmptyMessageRegistry.add("default", "Empty Default"); - commandEmptyMessageRegistry.add("@", "Empty @"); + const commandSetupRegistry = registry.category("command_setup"); + clearRegistryWithCleanup(commandSetupRegistry); + commandSetupRegistry.add("default", { + emptyMessage: "Empty Default", + }); + commandSetupRegistry.add("@", { + emptyMessage: "Empty @", + }); testComponent = await mount(TestComponent, { env, target }); @@ -417,6 +421,74 @@ QUnit.test("configure the empty message based on the namespace", async (assert) ); }); +QUnit.test("footer displays the right tips", async (assert) => { + assert.expect(3); + + clearRegistryWithCleanup(commandProviderRegistry); + + commandProviderRegistry.add("default", { + namespace: "default", + provide: () => [], + }); + + commandProviderRegistry.add("@", { + namespace: "@", + provide: () => [], + }); + + commandProviderRegistry.add("!", { + namespace: "!", + provide: () => [], + }); + + commandProviderRegistry.add("#", { + namespace: "#", + provide: () => [], + }); + + const commandSetupRegistry = registry.category("command_setup"); + clearRegistryWithCleanup(commandSetupRegistry); + commandSetupRegistry.add("default", {}); + commandSetupRegistry.add("@", { + name: "FirstName", + }); + + testComponent = await mount(TestComponent, { env, target }); + + // Open palette + triggerHotkey("control+k"); + await nextTick(); + assert.strictEqual( + target.querySelector(".o_command_palette_footer").textContent, + "TIP — search for @FirstName" + ); + + // Close palette + triggerHotkey("escape"); + commandSetupRegistry.add("!", { + name: "SecondName", + }); + // Open palette + triggerHotkey("control+k"); + await nextTick(); + assert.strictEqual( + target.querySelector(".o_command_palette_footer").textContent, + "TIP — search for @FirstName and !SecondName" + ); + // Close palette + triggerHotkey("escape"); + commandSetupRegistry.add("#", { + name: "ThirdName", + }); + // Open palette + triggerHotkey("control+k"); + await nextTick(); + assert.strictEqual( + target.querySelector(".o_command_palette_footer").textContent, + "TIP — search for @FirstName, !SecondName and #ThirdName" + ); +}); + QUnit.test("defined multiple providers with the same namespace", async (assert) => { assert.expect(2);