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, {