[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:
FrancoisGe
2022-01-12 11:19:00 +00:00
parent fae6762bfc
commit 1e1b45ea62
10 changed files with 130 additions and 31 deletions
@@ -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);