[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) <aab@odoo.com>
This commit is contained in:
@@ -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 });
|
||||
|
||||
@@ -14,9 +14,15 @@ DialogCommand.template = xml`
|
||||
</div>
|
||||
`;
|
||||
|
||||
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", {
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -28,8 +28,8 @@
|
||||
</t>
|
||||
</div>
|
||||
|
||||
<div t-if="state.footerTemplate" class="o_command_palette_footer">
|
||||
<t t-call="{{ state.footerTemplate }}"/>
|
||||
<div t-if="state.footerComponent" class="o_command_palette_footer">
|
||||
<t t-component="state.footerComponent"/>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -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`
|
||||
<span>
|
||||
<span class='o_promote'>TIP</span> — search for <span class='o_promote'>@</span>users, <span class='o_promote'>#</span>channels, and <span class='o_promote'>/</span>menus
|
||||
<span class='o_promote'>TIP</span> — search for
|
||||
<t t-foreach="elements" t-as="element">
|
||||
<t t-if="!(element_first || element_last)">, </t>
|
||||
<t t-if="element_last and !element_first"> and </t>
|
||||
<span t-esc="element.namespace" class='o_promote'/><t t-esc="element.name"/>
|
||||
</t>
|
||||
</span>
|
||||
`;
|
||||
|
||||
@@ -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(),
|
||||
},
|
||||
|
||||
@@ -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 = {}) => {
|
||||
|
||||
@@ -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],
|
||||
};
|
||||
|
||||
@@ -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", {
|
||||
|
||||
@@ -26,7 +26,8 @@ let target;
|
||||
let testComponent;
|
||||
const serviceRegistry = registry.category("services");
|
||||
|
||||
const footerTemplate = xml`<span>My footer</span>`;
|
||||
class footerComponent extends Component {}
|
||||
footerComponent.template = xml`<span>My footer</span>`;
|
||||
|
||||
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,
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user