[FIX] board: display all data when force 1 column layout on small screen
Before this commit, on a dashboard having a multi-column layout, the user couldn't see all those columns' actions when the layout fallback to the "1 column" on a small screen. This commit fixes it by, not only keeping the fallback to "1 column" to optimize the screen's real-estate, but also properly bringing the hidden columns' actions in the single column, so they become accessible by the user. Steps to reproduce: - Create My dashboard on PC with two views set one next to the other - Open My dashboard on mobile devices, and you see only the action inside the column on the left and not the action inside the columns on the right => bug opw-3145706 closes odoo/odoo#124360 X-original-commit: fa1da53af1d327c4fafd944547dbf3c7312a93b4 Signed-off-by: Pierre Paridans (app) <app@odoo.com> Signed-off-by: Romain Estievenart (res) <res@odoo.com>
This commit is contained in:
@@ -18,29 +18,29 @@ export class BoardController extends Component {
|
||||
this.rpc = useService("rpc");
|
||||
this.dialogService = useService("dialog");
|
||||
if (this.env.isSmall) {
|
||||
this.board.layout = "1";
|
||||
this.board.colNumber = 1;
|
||||
this.selectLayout("1", false);
|
||||
} else {
|
||||
const mainRef = useRef("main");
|
||||
useSortable({
|
||||
ref: mainRef,
|
||||
elements: ".o-dashboard-action",
|
||||
handle: ".o-dashboard-action-header",
|
||||
cursor: "move",
|
||||
groups: ".o-dashboard-column",
|
||||
connectGroups: true,
|
||||
onDrop: ({ element, previous, parent }) => {
|
||||
const fromColIdx = parseInt(element.parentElement.dataset.idx, 10);
|
||||
const fromActionIdx = parseInt(element.dataset.idx, 10);
|
||||
const toColIdx = parseInt(parent.dataset.idx, 10);
|
||||
const toActionIdx = previous ? parseInt(previous.dataset.idx, 10) + 1 : 0;
|
||||
if (fromColIdx !== toColIdx) {
|
||||
// to reduce visual flickering
|
||||
element.classList.add("d-none");
|
||||
}
|
||||
this.moveAction(fromColIdx, fromActionIdx, toColIdx, toActionIdx);
|
||||
},
|
||||
});
|
||||
}
|
||||
const mainRef = useRef("main");
|
||||
useSortable({
|
||||
ref: mainRef,
|
||||
elements: ".o-dashboard-action",
|
||||
handle: ".o-dashboard-action-header",
|
||||
cursor: "move",
|
||||
groups: ".o-dashboard-column",
|
||||
connectGroups: true,
|
||||
onDrop: ({ element, previous, parent }) => {
|
||||
const fromColIdx = parseInt(element.parentElement.dataset.idx, 10);
|
||||
const fromActionIdx = parseInt(element.dataset.idx, 10);
|
||||
const toColIdx = parseInt(parent.dataset.idx, 10);
|
||||
const toActionIdx = previous ? parseInt(previous.dataset.idx, 10) + 1 : 0;
|
||||
if (fromColIdx !== toColIdx) {
|
||||
// to reduce visual flickering
|
||||
element.classList.add("d-none");
|
||||
}
|
||||
this.moveAction(fromColIdx, fromActionIdx, toColIdx, toActionIdx);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
moveAction(fromColIdx, fromActionIdx, toColIdx, toActionIdx) {
|
||||
@@ -66,7 +66,7 @@ export class BoardController extends Component {
|
||||
this.saveBoard();
|
||||
}
|
||||
|
||||
selectLayout(layout) {
|
||||
selectLayout(layout, save = true) {
|
||||
const currentColNbr = this.board.colNumber;
|
||||
const nextColNbr = layout.split("-").length;
|
||||
if (nextColNbr < currentColNbr) {
|
||||
@@ -80,7 +80,9 @@ export class BoardController extends Component {
|
||||
}
|
||||
this.board.layout = layout;
|
||||
this.board.colNumber = nextColNbr;
|
||||
this.saveBoard();
|
||||
if (save) {
|
||||
this.saveBoard();
|
||||
}
|
||||
if (document.querySelector("canvas")) {
|
||||
// horrible hack to force charts to be recreated so they pick up the
|
||||
// proper size. also, no idea why raf is needed :(
|
||||
@@ -100,9 +102,11 @@ export class BoardController extends Component {
|
||||
});
|
||||
}
|
||||
|
||||
toggleAction(action) {
|
||||
toggleAction(action, save = true) {
|
||||
action.isFolded = !action.isFolded;
|
||||
this.saveBoard();
|
||||
if (save) {
|
||||
this.saveBoard();
|
||||
}
|
||||
}
|
||||
|
||||
saveBoard() {
|
||||
|
||||
@@ -34,9 +34,9 @@
|
||||
<div class="o-dashboard-action mx-3 my-1 bg-view border" t-att-data-idx="action_index">
|
||||
<h3 t-attf-class="o-dashboard-action-header {{action.title ? '' : 'oe_header_empty'}} p-2 m-2">
|
||||
<span> <t t-esc="action.title"/> </span>
|
||||
<span class="btn float-end p-1 text-muted" t-on-click="() => this.closeAction(column, action)"><i class="fa fa-close"/></span>
|
||||
<span class="btn float-end p-1 text-muted" t-if="!action.isFolded" t-on-click="() => this.toggleAction(action)"><i class="fa fa-window-minimize"/></span>
|
||||
<span class="btn float-end p-1 text-muted" t-if="action.isFolded" t-on-click="() => this.toggleAction(action)"><i class="fa fa-window-maximize"/></span>
|
||||
<span t-if="!env.isSmall" class="btn float-end p-1 text-muted" t-on-click="() => this.closeAction(column, action)"><i class="fa fa-close"/></span>
|
||||
<span class="btn float-end p-1 text-muted" t-if="!action.isFolded" t-on-click="() => this.toggleAction(action, !env.isSmall)"><i class="fa fa-window-minimize"/></span>
|
||||
<span class="btn float-end p-1 text-muted" t-if="action.isFolded" t-on-click="() => this.toggleAction(action, !env.isSmall)"><i class="fa fa-window-maximize"/></span>
|
||||
</h3>
|
||||
<BoardAction t-if="!action.isFolded" action="action" />
|
||||
</div>
|
||||
|
||||
+47
-4
@@ -62,10 +62,7 @@ QUnit.module("Board", (hooks) => {
|
||||
if (route === "/web/action/load") {
|
||||
return Promise.resolve({
|
||||
res_model: "partner",
|
||||
views: [
|
||||
[4, "list"],
|
||||
[5, "form"],
|
||||
],
|
||||
views: [[4, "list"]],
|
||||
});
|
||||
}
|
||||
},
|
||||
@@ -77,5 +74,51 @@ QUnit.module("Board", (hooks) => {
|
||||
target.querySelector(".o-dashboard-action .o_control_panel"),
|
||||
"views in the dashboard do not have a control panel"
|
||||
);
|
||||
assert.containsNone(
|
||||
target,
|
||||
".o-dashboard-action-header .fa-close",
|
||||
"Should not have a close action button"
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test("Correctly soft switch to '1' layout on small screen", async function (assert) {
|
||||
serverData.views["partner,4,list"] = '<tree string="Partner"><field name="foo"/></tree>';
|
||||
|
||||
await makeView({
|
||||
serverData,
|
||||
type: "form",
|
||||
resModel: "board",
|
||||
arch: `
|
||||
<form string="My Dashboard" js_class="board">
|
||||
<board style="2-1">
|
||||
<column>
|
||||
<action context="{"orderedBy": [{"name": "foo", "asc": True}]}" view_mode="list" string="ABC" name="51" domain="[['foo', '!=', 'False']]"></action>
|
||||
</column>
|
||||
<column>
|
||||
<action context="{"orderedBy": [{"name": "foo", "asc": True}]}" view_mode="list" string="ABC" name="51" domain="[['foo', '!=', 'False']]"></action>
|
||||
</column>
|
||||
</board>
|
||||
</form>`,
|
||||
mockRPC(route, args) {
|
||||
if (route === "/web/action/load") {
|
||||
return Promise.resolve({
|
||||
res_model: "partner",
|
||||
views: [[4, "list"]],
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
assert.containsOnce(target, ".o-dashboard-layout-1", "The display layout is force to 1");
|
||||
assert.containsOnce(
|
||||
target,
|
||||
".o-dashboard-column",
|
||||
"The display layout is force to 1 column"
|
||||
);
|
||||
assert.containsN(
|
||||
target,
|
||||
".o-dashboard-action",
|
||||
2,
|
||||
"The display should contains the 2 actions"
|
||||
);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user