[IMP] web: new ControlPanel class
We introduce here an incomplete implementation of the new ControlPanel. The idea is to allow the rewritting of the client actions that do not use the search view but only the breadcrumbs or the button zone... The full implementation of the new ControlPanel will be merged when the new search infrastructure is ready to be merged. closes odoo/odoo#75083 Related: odoo/enterprise#20231 Signed-off-by: Lucas Perais (lpe) <lpe@odoo.com>
This commit is contained in:
@@ -92,6 +92,7 @@ This module provides the core of the Odoo Web Client.
|
||||
'web/static/src/core/utils/transitions.scss',
|
||||
'web/static/src/core/**/*',
|
||||
'web/static/src/fields/**/*',
|
||||
'web/static/src/search/**/*',
|
||||
'web/static/src/views/**/*',
|
||||
'web/static/src/webclient/**/*',
|
||||
('remove', 'web/static/src/webclient/clickbot/clickbot.js'), # lazy loaded
|
||||
@@ -110,7 +111,6 @@ This module provides the core of the Odoo Web Client.
|
||||
'web/static/src/legacy/scss/tooltip.scss',
|
||||
'web/static/src/legacy/scss/switch_company_menu.scss',
|
||||
'web/static/src/legacy/scss/ace.scss',
|
||||
'web/static/src/legacy/scss/control_panel.scss',
|
||||
'web/static/src/legacy/scss/fields.scss',
|
||||
'web/static/src/legacy/scss/file_upload.scss',
|
||||
'web/static/src/legacy/scss/views.scss',
|
||||
@@ -480,6 +480,7 @@ This module provides the core of the Odoo Web Client.
|
||||
'web/static/tests/env_tests.js',
|
||||
'web/static/tests/core/**/*.js',
|
||||
'web/static/tests/fields/**/*.js',
|
||||
'web/static/tests/search/**/*.js',
|
||||
'web/static/tests/views/**/*.js',
|
||||
'web/static/tests/webclient/**/*.js',
|
||||
('remove', 'web/static/tests/webclient/**/helpers.js'),
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { useService } from "@web/core/utils/hooks";
|
||||
|
||||
const { Component } = owl;
|
||||
|
||||
export class ControlPanel extends Component {
|
||||
setup() {
|
||||
this.actionService = useService("action");
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {Object}
|
||||
*/
|
||||
get display() {
|
||||
const display = Object.assign(
|
||||
{
|
||||
"top-left": true,
|
||||
"top-right": true,
|
||||
"bottom-left": true,
|
||||
"bottom-right": true,
|
||||
},
|
||||
this.props.display
|
||||
);
|
||||
display.top = display["top-left"] || display["top-right"];
|
||||
display.bottom = display["bottom-left"] || display["bottom-right"];
|
||||
return display;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when an element of the breadcrumbs is clicked.
|
||||
*
|
||||
* @param {string} jsId
|
||||
*/
|
||||
onBreadcrumbClicked(jsId) {
|
||||
this.actionService.restore(jsId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a view is clicked in the view switcher.
|
||||
*
|
||||
* @param {ViewType} viewType
|
||||
*/
|
||||
onViewClicked(viewType) {
|
||||
this.actionService.switchView(viewType);
|
||||
}
|
||||
}
|
||||
|
||||
ControlPanel.template = "web.ControlPanel";
|
||||
ControlPanel.defaultProps = {
|
||||
breadcrumbs: [],
|
||||
display: {},
|
||||
};
|
||||
-1
@@ -31,7 +31,6 @@
|
||||
|
||||
.o_cp_bottom_left {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
> .o_cp_action_menus {
|
||||
padding-right: 10px;
|
||||
@@ -0,0 +1,60 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates xml:space="preserve">
|
||||
|
||||
<t t-name="web.ControlPanel" owl="1">
|
||||
<div class="o_control_panel">
|
||||
<div t-if="display['top']" class="o_cp_top">
|
||||
<div t-if="display['top-left']" class="o_cp_top_left">
|
||||
<t t-slot="control-panel-top-left">
|
||||
<t t-call="web.Breadcrumbs" />
|
||||
</t>
|
||||
</div>
|
||||
<div t-if="display['top-right']" class="o_cp_top_right">
|
||||
<t t-slot="control-panel-top-right"/>
|
||||
</div>
|
||||
</div>
|
||||
<div t-if="display['bottom']" class="o_cp_bottom">
|
||||
<div t-if="display['bottom-left']" class="o_cp_bottom_left">
|
||||
<t t-slot="control-panel-bottom-left"/>
|
||||
</div>
|
||||
<div t-if="display['bottom-right']" class="o_cp_bottom_right">
|
||||
<t t-slot="control-panel-bottom-right"/>
|
||||
|
||||
<div class="o_cp_pager"/>
|
||||
|
||||
<t t-if="props.viewSwitcherEntries and props.viewSwitcherEntries.length > 1">
|
||||
<nav class="btn-group o_cp_switch_buttons">
|
||||
<t t-foreach="props.viewSwitcherEntries" t-as="view" t-key="view.type">
|
||||
<button class="btn btn-secondary fa fa-lg o_switch_view "
|
||||
t-attf-class="o_{{view.type}} {{view.icon}} {{view.active ? 'active' : ''}}"
|
||||
t-att-data-tooltip="view.name"
|
||||
t-on-click="onViewClicked(view.type)"
|
||||
/>
|
||||
</t>
|
||||
</nav>
|
||||
</t>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
<t t-name="web.Breadcrumbs" owl="1">
|
||||
<ol class="breadcrumb">
|
||||
<t t-foreach="props.breadcrumbs or []" t-as="breadcrumb" t-key="breadcrumb.jsId">
|
||||
<li class="breadcrumb-item"
|
||||
t-att-class="{ o_back_button: breadcrumb_last}"
|
||||
t-on-click.prevent="onBreadcrumbClicked(breadcrumb.jsId)"
|
||||
>
|
||||
<a href="#">
|
||||
<t t-esc="breadcrumb.name"/>
|
||||
</a>
|
||||
</li>
|
||||
</t>
|
||||
<li class="breadcrumb-item active">
|
||||
<t t-if="props.displayName" t-esc="props.displayName"/>
|
||||
<em t-else="" class="text-warning">Unnamed</em>
|
||||
</li>
|
||||
</ol>
|
||||
</t>
|
||||
|
||||
</templates>
|
||||
@@ -370,13 +370,16 @@ function makeActionManager(env) {
|
||||
const viewSwitcherEntries = views
|
||||
.filter((v) => v.multiRecord === view.multiRecord)
|
||||
.map((v) => {
|
||||
return {
|
||||
// FIXME: missing accesskey
|
||||
const viewSwitcherEntry = {
|
||||
icon: v.icon,
|
||||
name: v.display_name,
|
||||
name: v.display_name.toString(),
|
||||
type: v.type,
|
||||
multiRecord: v.multiRecord,
|
||||
};
|
||||
if (view.type === v.type) {
|
||||
viewSwitcherEntry.active = true;
|
||||
}
|
||||
return viewSwitcherEntry;
|
||||
});
|
||||
const flags = action.flags || {};
|
||||
const viewProps = Object.assign(_getActionProps(action, props), {
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { actionService } from "@web/webclient/actions/action_service";
|
||||
import { click } from "@web/../tests/helpers/utils";
|
||||
import { ControlPanel } from "@web/search/control_panel/control_panel";
|
||||
import { getFixture } from "@web/../tests/helpers/utils";
|
||||
import { makeTestEnv } from "@web/../tests/helpers/mock_env";
|
||||
import { notificationService } from "@web/core/notifications/notification_service";
|
||||
import { patchWithCleanup } from "@web/../tests/helpers/utils";
|
||||
import { registerCleanup } from "@web/../tests/helpers/cleanup";
|
||||
import { registry } from "@web/core/registry";
|
||||
|
||||
const serviceRegistry = registry.category("services");
|
||||
|
||||
const { mount } = owl;
|
||||
|
||||
/**
|
||||
* @param {MakeViewParams} params
|
||||
* @returns {owl.Component}
|
||||
*/
|
||||
export async function makeControlPanel(params = {}) {
|
||||
const serverData = params.serverData;
|
||||
const mockRPC = params.mockRPC;
|
||||
const props = Object.assign({}, params);
|
||||
delete props.serverData;
|
||||
delete props.mockRPC;
|
||||
|
||||
const env = await makeTestEnv({ serverData, mockRPC });
|
||||
const target = getFixture();
|
||||
|
||||
const controlPanel = await mount(ControlPanel, { env, props, target });
|
||||
|
||||
registerCleanup(() => controlPanel.destroy());
|
||||
|
||||
return controlPanel;
|
||||
}
|
||||
|
||||
QUnit.module("ControlPanel", {
|
||||
async beforeEach() {
|
||||
serviceRegistry.add("action", actionService);
|
||||
serviceRegistry.add("notification", notificationService);
|
||||
},
|
||||
});
|
||||
|
||||
QUnit.test("simple rendering", async (assert) => {
|
||||
const controlPanel = await makeControlPanel({
|
||||
display: {
|
||||
"top-right": false,
|
||||
},
|
||||
});
|
||||
assert.containsOnce(controlPanel, ".o_cp_top");
|
||||
assert.containsOnce(controlPanel, ".o_cp_top_left");
|
||||
assert.containsNone(controlPanel, ".o_cp_top_right");
|
||||
assert.containsOnce(controlPanel, ".o_cp_bottom");
|
||||
assert.containsOnce(controlPanel, ".o_cp_bottom_left");
|
||||
assert.containsOnce(controlPanel, ".o_cp_bottom_right");
|
||||
|
||||
assert.containsNone(controlPanel, ".o_cp_switch_buttons");
|
||||
|
||||
assert.containsOnce(controlPanel, ".breadcrumb");
|
||||
assert.containsOnce(controlPanel, ".breadcrumb li.breadcrumb-item");
|
||||
assert.strictEqual(controlPanel.el.querySelector("li.breadcrumb-item").innerText, "Unnamed");
|
||||
});
|
||||
|
||||
QUnit.test("breadcrumbs prop", async (assert) => {
|
||||
const controlPanel = await makeControlPanel({
|
||||
breadcrumbs: [{ jsId: "controller_7", name: "Previous" }],
|
||||
displayName: "Current",
|
||||
});
|
||||
|
||||
assert.containsN(controlPanel, ".breadcrumb li.breadcrumb-item", 2);
|
||||
const breadcrumbItems = controlPanel.el.querySelectorAll("li.breadcrumb-item");
|
||||
assert.strictEqual(breadcrumbItems[0].innerText, "Previous");
|
||||
assert.hasClass(breadcrumbItems[1], "active");
|
||||
assert.strictEqual(breadcrumbItems[1].innerText, "Current");
|
||||
|
||||
controlPanel.env.services.action.restore = (jsId) => {
|
||||
assert.step(jsId);
|
||||
};
|
||||
|
||||
await click(breadcrumbItems[0]);
|
||||
assert.verifySteps(["controller_7"]);
|
||||
});
|
||||
|
||||
QUnit.test("viewSwitcherEntries prop", async (assert) => {
|
||||
const controlPanel = await makeControlPanel({
|
||||
viewSwitcherEntries: [
|
||||
{ type: "list", active: true, icon: "fa-list-ul", name: "List" },
|
||||
{ type: "kanban", icon: "fa-th-large", name: "Kanban" },
|
||||
],
|
||||
});
|
||||
|
||||
assert.containsOnce(controlPanel, ".o_cp_switch_buttons");
|
||||
assert.containsN(controlPanel, ".o_switch_view", 2);
|
||||
const views = controlPanel.el.querySelectorAll(".o_switch_view");
|
||||
|
||||
assert.strictEqual(views[0].getAttribute("data-tooltip"), "List");
|
||||
assert.hasClass(views[0], "active");
|
||||
assert.strictEqual(views[1].getAttribute("data-tooltip"), "Kanban");
|
||||
assert.hasClass(views[1], "fa-th-large");
|
||||
|
||||
controlPanel.env.services.action.switchView = (viewType) => {
|
||||
assert.step(viewType);
|
||||
};
|
||||
|
||||
await click(views[1]);
|
||||
assert.verifySteps(["kanban"]);
|
||||
});
|
||||
Reference in New Issue
Block a user