[IMP] spreadsheet_dashboard: grid cursor

closes odoo/odoo#101946

X-original-commit: 26ab6164e92b4ab7149a85b74cbff127c35fb4a0
Signed-off-by: Rémi Rahir (rar) <rar@odoo.com>
Signed-off-by: Pierre Rousseau (pro) <pro@odoo.com>
This commit is contained in:
Pierre Rousseau
2022-10-03 16:19:05 +02:00
parent 8af6e04a76
commit bd71c6ae76
5 changed files with 196 additions and 1 deletions
+1 -1
View File
@@ -21,11 +21,11 @@
"assets": {
"spreadsheet.o_spreadsheet": [
"spreadsheet_dashboard/static/src/bundle/**/*.js",
"spreadsheet_dashboard/static/src/bundle/**/*.xml",
],
"web.assets_backend": [
"spreadsheet_dashboard/static/src/assets/**/*.js",
"spreadsheet_dashboard/static/src/**/*.scss",
"spreadsheet_dashboard/static/src/**/*.xml",
],
"web.qunit_suite_tests": [
"spreadsheet_dashboard/static/tests/**/*",
@@ -0,0 +1,90 @@
/** @odoo-module */
import { getNumberOfListFormulas } from "@spreadsheet/list/list_helpers";
import spreadsheet from "@spreadsheet/o_spreadsheet/o_spreadsheet_extended";
import { getNumberOfPivotFormulas } from "@spreadsheet/pivot/pivot_helpers";
import { patch } from "@web/core/utils/patch";
const { Grid } = spreadsheet.components;
function positionToZone(position) {
return { left: position.col, right: position.col, top: position.row, bottom: position.row };
}
/**
* @typedef ClickableCell
* @property {number} left Left position of the cell
* @property {number} top Top position of the cell
* @property {number} width Width of the cell
* @property {number} height Height of the cell
* @property {number} key Unique identifier
*/
/* Used for t-key */
let key = 0;
patch(Grid.prototype, "spreadsheet_dashboard_grid_cursor", {
getCellClickableStyle(coordinates) {
return `
top: ${coordinates.top}px;
left: ${coordinates.left}px;
width: ${coordinates.width}px;
height: ${coordinates.height}px;
`;
},
/**
* Get all the boxes for the cell in the sheet view that are clickable.
* This function is used to render an overlay over each clickable cell in
* order to display a pointer cursor.
*
* @returns {Array<ClickableCell>}
*/
getClickableCells() {
const cells = [];
const sheetId = this.env.model.getters.getActiveSheetId();
for (const col of this.env.model.getters.getSheetViewVisibleCols()) {
for (const row of this.env.model.getters.getSheetViewVisibleRows()) {
const cell = this.env.model.getters.getCell(sheetId, col, row);
if (this.isClickable(cell)) {
let zone;
if (this.env.model.getters.isInMerge(sheetId, col, row)) {
zone = this.env.model.getters.getMerge(sheetId, col, row);
} else {
zone = positionToZone({ col, row });
}
const rect = this.env.model.getters.getVisibleRect(zone);
cells.push({
left: rect.x,
top: rect.y,
width: rect.width,
height: rect.height,
key: ++key,
});
}
}
}
return cells;
},
isClickable(cell) {
if (!cell) {
return false;
}
// Links
if (cell.isLink()) {
return true;
}
// Pivot / Lists
if (
cell.isFormula() &&
cell.evaluated.value !== "" &&
!cell.evaluated.error &&
(getNumberOfPivotFormulas(cell.content) === 1 ||
getNumberOfListFormulas(cell.content) === 1)
) {
return true;
}
return false;
},
});
@@ -0,0 +1,4 @@
.o-dashboard-clickable-cell {
position: absolute;
cursor: pointer;
}
@@ -0,0 +1,12 @@
<odoo>
<t t-name="spreadsheet_dashboard.o-spreadsheet-Grid" t-inherit="spreadsheet.o-spreadsheet-Grid" t-inherit-mode="extension" owl="1">
<xpath expr="//div[hasclass('o-grid-overlay')]" position="inside">
<div t-if="env.model.getters.isDashboard()"
t-foreach="getClickableCells()"
t-as="cell"
class="o-dashboard-clickable-cell"
t-att-style="getCellClickableStyle(cell)"
t-key="cell.key"/>
</xpath>
</t>
</odoo>
@@ -0,0 +1,89 @@
/** @odoo-module */
import spreadsheet from "@spreadsheet/o_spreadsheet/o_spreadsheet_extended";
import { getFixture } from "@web/../tests/helpers/utils";
import { getDashboardServerData } from "../utils/data";
import { createSpreadsheetDashboard } from "../utils/dashboard_action";
import { getBasicData } from "@spreadsheet/../tests/utils/data";
const { Model } = spreadsheet;
const { functionRegistry } = spreadsheet.registries;
const { args } = spreadsheet.helpers;
async function createDashboardWithModel(model) {
const serverData = getDashboardServerData();
const json = JSON.stringify(model.exportData());
const dashboard = serverData.models["spreadsheet.dashboard"].records[0];
dashboard.raw = json;
dashboard.json_data = json;
serverData.models = {
...serverData.models,
...getBasicData(),
};
await createSpreadsheetDashboard({ serverData, spreadsheetId: dashboard.id });
return getFixture();
}
QUnit.module("spreadsheet_dashboard > grid cursor");
QUnit.test("A link in a dashboard should be clickable", async (assert) => {
const data = {
sheets: [
{
cells: { A1: { content: "[Odoo](https://odoo.com)" } },
},
],
};
const model = new Model(data, { mode: "dashboard" });
const target = await createDashboardWithModel(model);
assert.containsOnce(target, ".o-dashboard-clickable-cell");
});
QUnit.test("Invalid pivot/list formulas should not be clickable", async (assert) => {
const data = {
sheets: [
{
cells: {
A1: { content: `=ODOO.PIVOT("1", "measure")` },
A2: { content: `=ODOO.LIST("1", 1, "name")` },
},
},
],
};
const model = new Model(data, { mode: "dashboard" });
const target = await createDashboardWithModel(model);
assert.containsNone(target, ".o-dashboard-clickable-cell");
});
QUnit.test("pivot/list formulas should be clickable", async (assert) => {
const list = functionRegistry.get("ODOO.LIST");
const pivot = functionRegistry.get("ODOO.PIVOT");
const mock = {
description: "Mock function to avoid setup all data sources process",
compute: () => 1,
args: args(``),
returns: ["NUMBER"],
};
functionRegistry.add("ODOO.LIST", mock);
functionRegistry.add("ODOO.PIVOT", mock);
const data = {
sheets: [
{
cells: {
A1: { content: `=ODOO.PIVOT()` },
A2: { content: `=ODOO.LIST()` },
},
},
],
};
const model = new Model(data);
const target = await createDashboardWithModel(model);
assert.containsN(target, ".o-dashboard-clickable-cell", 2);
functionRegistry.add("ODOO.LIST", list);
functionRegistry.add("ODOO.PIVOT", pivot);
});