From 52a1d997d226244d6a4e66e92f8af13384272aaf Mon Sep 17 00:00:00 2001 From: FrancoisGe Date: Wed, 5 Jan 2022 13:19:18 +0000 Subject: [PATCH] [IMP] web: custom command palette MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit adds the possibility to open the custom main command palette. So it is possible to replace some parts of the default command palette config. For example, we will customize the placeholder: ``` this.command_service.openMainPalette({ placeholder: "My custom placeholder", }) ``` This commit also modify the api of CommandPaletteConfig, we replace namespace by searchValue. Now it is possible to open a command palette with a value in its input. For example: ``` this.command_service.openPalette({ searchValue: "@test", }) ``` closes odoo/odoo#81814 Signed-off-by: Géry Debongnie --- .../src/core/commands/command_palette.js | 36 ++++++------- .../src/core/commands/command_palette.scss | 2 + .../src/core/commands/command_palette.xml | 2 +- .../src/core/commands/command_service.js | 25 +++++---- .../core/commands/command_palette_tests.js | 52 ++++++++++++++++++- 5 files changed, 87 insertions(+), 30 deletions(-) diff --git a/addons/web/static/src/core/commands/command_palette.js b/addons/web/static/src/core/commands/command_palette.js index 30ecec7a56f..22b5e8ea4e0 100644 --- a/addons/web/static/src/core/commands/command_palette.js +++ b/addons/web/static/src/core/commands/command_palette.js @@ -9,7 +9,7 @@ import { debounce } from "@web/core/utils/timing"; import { _lt } from "@web/core/l10n/translation"; const { Component, hooks } = owl; -const { useState } = hooks; +const { onWillStart, useState } = hooks; const DEFAULT_PLACEHOLDER = _lt("Search..."); const DEFAULT_EMPTY_MESSAGE = _lt("No results found"); @@ -36,11 +36,11 @@ const FUZZY_NAMESPACES = ["default"]; /** * @typedef {{ * categoriesByNamespace?: {[namespace]: string[]}; - * namespace?: string; * emptyMessageByNamespace?: {[namespace]: string}; * footerTemplate?: string; * placeholder?: string; * providers: Provider[]; + * searchValue?: string; * }} CommandPaletteConfig */ @@ -69,13 +69,12 @@ export class CommandPalette extends Component { this.keepLast = new KeepLast(); this.DefaultCommandItem = DefaultCommandItem; this.activeElement = useService("ui").activeElement; - const onDebouncedSearchInput = debounce.apply(this, [this.onSearchInput, 200]); - this.onDebouncedSearchInput = (...args) => { - this.inputPromise = onDebouncedSearchInput.apply(this, args).catch(() => { - this.inputPromise = null; + const applyDebounceSearchValue = debounce.apply(this, [this.applySearchValue, 200]); + this.applyDebounceSearchValue = (...args) => { + this.searchValuePromise = applyDebounceSearchValue.apply(this, args).catch(() => { + this.searchValuePromise = null; }); }; - useAutofocus(); useHotkey("Enter", () => this.executeSelectedCommand()); @@ -94,7 +93,7 @@ export class CommandPalette extends Component { commands: [], }); - this.setCommandPaletteConfig(this.props.config); + onWillStart(() => this.setCommandPaletteConfig(this.props.config)); } get commandsByCategory() { @@ -117,7 +116,7 @@ export class CommandPalette extends Component { * Apply the new config to the command pallet * @param {CommandPaletteConfig} config */ - setCommandPaletteConfig(config) { + async setCommandPaletteConfig(config) { const result = { default: [] }; for (const provider of config.providers) { const namespace = provider.namespace || "default"; @@ -135,12 +134,8 @@ export class CommandPalette extends Component { this.state.placeholder = config.placeholder || DEFAULT_PLACEHOLDER.toString(); - const namespace = config.namespace || "default"; - this.setCommands(namespace, { - activeElement: this.activeElement, - searchValue: "", - }); - this.state.searchValue = namespace === "default" ? "" : namespace; + const searchValue = config.searchValue || ""; + await this.applySearchValue(searchValue); } /** @@ -236,7 +231,7 @@ export class CommandPalette extends Component { } async executeSelectedCommand() { - await this.inputPromise; + await this.searchValuePromise; if (this.state.selectedCommand) { this.executeCommand(this.state.selectedCommand); } @@ -250,9 +245,8 @@ export class CommandPalette extends Component { } } - async onSearchInput(ev) { - this.state.searchValue = ev.target.value; - let searchValue = this.state.searchValue; + async applySearchValue(searchValue) { + this.state.searchValue = searchValue; let namespace = "default"; if (searchValue.length && searchValue[0] in this.providersByNamespace) { namespace = searchValue[0]; @@ -263,5 +257,9 @@ export class CommandPalette extends Component { activeElement: this.activeElement, }); } + + onSearchInput(ev) { + this.applyDebounceSearchValue(ev.target.value); + } } CommandPalette.template = "web.CommandPalette"; diff --git a/addons/web/static/src/core/commands/command_palette.scss b/addons/web/static/src/core/commands/command_palette.scss index 93c5d5df4dd..31e59681296 100644 --- a/addons/web/static/src/core/commands/command_palette.scss +++ b/addons/web/static/src/core/commands/command_palette.scss @@ -1,4 +1,6 @@ .o_command_palette { + margin-top: 10vh; + &.modal-content { border: none; border-radius: 3px !important; diff --git a/addons/web/static/src/core/commands/command_palette.xml b/addons/web/static/src/core/commands/command_palette.xml index e79c5654735..7abfb172ff2 100644 --- a/addons/web/static/src/core/commands/command_palette.xml +++ b/addons/web/static/src/core/commands/command_palette.xml @@ -4,7 +4,7 @@
diff --git a/addons/web/static/src/core/commands/command_service.js b/addons/web/static/src/core/commands/command_service.js index 21bb2a757c9..e3289c648d6 100644 --- a/addons/web/static/src/core/commands/command_service.js +++ b/addons/web/static/src/core/commands/command_service.js @@ -54,7 +54,11 @@ export const commandService = { global: true, }); - function openMainPalette() { + /** + * @param {CommandPaletteConfig} config command palette config merged with default config + * @returns the actual command palette config if the command palette is already open + */ + function openMainPalette(config = {}) { const categoriesByNamespace = {}; commandCategoryRegistry.getEntries().forEach(([category, el]) => { const namespace = el.namespace ? el.namespace : "default"; @@ -69,14 +73,16 @@ export const commandService = { commandEmptyMessageRegistry.getEntries().forEach(([key, message]) => { emptyMessageByNamespace[key] = message.toString(); }); - - const config = { - categoriesByNamespace, - emptyMessageByNamespace, - footerTemplate, - placeholder: env._t("Search for a command..."), - providers: commandProviderRegistry.getAll(), - }; + config = Object.assign( + { + categoriesByNamespace, + emptyMessageByNamespace, + footerTemplate, + placeholder: env._t("Search for a command..."), + providers: commandProviderRegistry.getAll(), + }, + config + ); return openPalette(config); } @@ -176,6 +182,7 @@ export const commandService = { (command) => command.activeElement === activeElement || command.global ); }, + openMainPalette, openPalette, }; }, 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 6f467c4d720..b4fdaf5e6d4 100644 --- a/addons/web/static/tests/core/commands/command_palette_tests.js +++ b/addons/web/static/tests/core/commands/command_palette_tests.js @@ -416,6 +416,56 @@ QUnit.test("check the concurrency during a research", async (assert) => { assert.verifySteps(["b"]); }); +QUnit.test( + "open the command palette with a searchValue already in the searchbar", + async (assert) => { + testComponent = await mount(TestComponent, { env, target }); + const action = () => {}; + const providers = [ + { + provide: () => [ + { + name: "Command1", + action, + }, + { + name: "Command2", + action, + }, + ], + }, + { + namespace: "@", + provide: () => [ + { + name: "Command3", + action, + }, + { + name: "Command4", + action, + }, + ], + }, + ]; + const config = { + searchValue: "C1", + providers, + }; + env.services.dialog.add(CommandPaletteDialog, { + config, + }); + await nextTick(); + assert.containsOnce(target, ".o_command_palette"); + assert.strictEqual(target.querySelector(".o_command_palette_search input").value, "C1"); + assert.containsN(target, ".o_command", 1); + assert.deepEqual( + [...target.querySelectorAll(".o_command")].map((el) => el.textContent), + ["Command1"] + ); + } +); + QUnit.test("open the command palette with a namespace already in the searchbar", async (assert) => { testComponent = await mount(TestComponent, { env, target }); const action = () => {}; @@ -447,7 +497,7 @@ QUnit.test("open the command palette with a namespace already in the searchbar", }, ]; const config = { - namespace: "@", + searchValue: "@", providers, }; env.services.dialog.add(CommandPaletteDialog, {