[IMP] spreadsheet: update o_spreadsheet to latest version
https://github.com/odoo/o-spreadsheet/commit/331a62e8 [REF] menu: simplify menu api https://github.com/odoo/o-spreadsheet/commit/9c5f0ebc [IMP/REF] bottom_bar: improve style and usability https://github.com/odoo/o-spreadsheet/commit/a378c53c [IMP] composer: fuzzy search to autocomplete https://github.com/odoo/o-spreadsheet/commit/35b1263a [FIX] bottom_bar_sheet: fix innerHTML security issue closes odoo/odoo#110106 Related: odoo/enterprise#35897 Signed-off-by: Pierre Rousseau (pro) <pro@odoo.com>
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,33 @@
|
||||
<odoo>
|
||||
<t t-name="o-spreadsheet-Ripple" owl="1">
|
||||
<div
|
||||
class="o-ripple-container position-relative"
|
||||
t-att-class="props.class"
|
||||
t-on-click="onClick">
|
||||
<div class="position-absolute" t-att-style="getStyle()">
|
||||
<t t-foreach="state.ripples" t-as="ripple" t-key="ripple.id">
|
||||
<RippleEffect t-props="getRippleEffectProps(ripple.id)"/>
|
||||
</t>
|
||||
</div>
|
||||
<div t-ref="childContainer">
|
||||
<t t-slot="default"/>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
<t t-name="o-spreadsheet-RippleEffect" owl="1">
|
||||
<div
|
||||
class="position-absolute h-100 w-100"
|
||||
t-att-class="{ 'overflow-hidden': !props.allowOverflow }">
|
||||
<div
|
||||
class="o-ripple position-relative pe-none"
|
||||
t-ref="ripple"
|
||||
t-att-style="rippleStyle"
|
||||
t-on-animationend="onAnimationEnd"
|
||||
/>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
<t t-name="o-spreadsheet-Autofill" owl="1">
|
||||
<div
|
||||
class="o-autofill"
|
||||
@@ -15,59 +44,138 @@
|
||||
|
||||
<t t-name="o-spreadsheet-BottomBar" owl="1">
|
||||
<div
|
||||
class="o-spreadsheet-bottom-bar o-two-columns"
|
||||
class="o-spreadsheet-bottom-bar o-two-columns d-flex align-items-center overflow-hidden"
|
||||
t-on-click="props.onClick"
|
||||
t-ref="bottomBar">
|
||||
<div
|
||||
class="o-sheet-item o-add-sheet"
|
||||
t-att-class="{'disabled': env.model.getters.isReadonly()}"
|
||||
t-on-click="addSheet">
|
||||
<t t-call="o-spreadsheet-Icon.PLUS"/>
|
||||
</div>
|
||||
<div class="o-sheet-item o-list-sheets" t-on-click="listSheets">
|
||||
<t t-call="o-spreadsheet-Icon.LIST"/>
|
||||
</div>
|
||||
<div class="o-all-sheets flex-shrink-0 me-3">
|
||||
<t t-foreach="getVisibleSheets()" t-as="sheet" t-key="sheet.id">
|
||||
<div
|
||||
class="o-sheet-item o-sheet"
|
||||
t-on-click="(ev) => this.activateSheet(sheet.id, ev)"
|
||||
t-on-contextmenu.prevent="(ev) => this.onContextMenu(sheet.id, ev)"
|
||||
t-att-title="sheet.name"
|
||||
t-att-data-id="sheet.id"
|
||||
t-att-class="{active: sheet.id === env.model.getters.getActiveSheetId()}">
|
||||
<span
|
||||
class="o-sheet-name"
|
||||
t-esc="sheet.name"
|
||||
t-on-dblclick="(ev) => this.onDblClick(sheet.id, ev)"
|
||||
t-ref="bottomBar"
|
||||
t-on-contextmenu.prevent="">
|
||||
<Ripple>
|
||||
<div
|
||||
class="o-sheet-item o-add-sheet position-relative me-2 p-1"
|
||||
t-att-class="{'disabled': env.model.getters.isReadonly()}"
|
||||
t-on-click="clickAddSheet">
|
||||
<t t-call="o-spreadsheet-Icon.PLUS"/>
|
||||
</div>
|
||||
</Ripple>
|
||||
<Ripple>
|
||||
<div
|
||||
class="o-sheet-item o-list-sheets position-relative me-2 p-1"
|
||||
t-on-click="clickListSheets">
|
||||
<t t-call="o-spreadsheet-Icon.LIST"/>
|
||||
</div>
|
||||
</Ripple>
|
||||
<div class="o-all-sheets position-relative flex-shrink-0 d-flex h-100 me-3">
|
||||
<div
|
||||
class="o-bottom-bar-fade-in position-absolute h-100 w-100 pe-none"
|
||||
t-if="state.isSheetListScrollableLeft"
|
||||
/>
|
||||
<div
|
||||
class="o-sheet-list d-flex w-100 overflow-hidden"
|
||||
t-ref="sheetList"
|
||||
t-on-wheel="onWheel"
|
||||
t-on-scroll="onScroll">
|
||||
<t t-foreach="getVisibleSheets()" t-as="sheet" t-key="sheet.id">
|
||||
<BottomBarSheet
|
||||
sheetId="sheet.id"
|
||||
openContextMenu="(registry, ev) => this.onSheetContextMenu(sheet.id, registry, ev)"
|
||||
/>
|
||||
<span class="o-sheet-icon" t-on-click.stop="(ev) => this.onIconClick(sheet.id, ev)">
|
||||
<t t-call="o-spreadsheet-Icon.TRIANGLE_DOWN"/>
|
||||
</span>
|
||||
</t>
|
||||
</div>
|
||||
<div
|
||||
class="o-bottom-bar-fade-out position-absolute h-100 w-100 pe-none"
|
||||
t-if="state.isSheetListScrollableRight"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="o-bottom-bar-arrows d-flex h-100 me-5 align-items-center"
|
||||
t-if="state.isSheetListScrollableLeft || state.isSheetListScrollableRight">
|
||||
<Ripple
|
||||
ignoreClickPosition="true"
|
||||
width="20"
|
||||
height="20"
|
||||
offsetX="1"
|
||||
allowOverflow="true"
|
||||
enabled="state.isSheetListScrollableLeft">
|
||||
<div
|
||||
class="o-bottom-bar-arrow o-bottom-bar-arrow-left d-flex align-items-center me-2"
|
||||
t-att-class="{'o-disabled': !state.isSheetListScrollableLeft}"
|
||||
t-on-click="onArrowLeft">
|
||||
<t t-call="o-spreadsheet-Icon.CARET_LEFT"/>
|
||||
</div>
|
||||
</t>
|
||||
</Ripple>
|
||||
<Ripple
|
||||
ignoreClickPosition="true"
|
||||
width="20"
|
||||
height="20"
|
||||
offsetX="-1"
|
||||
allowOverflow="true"
|
||||
enabled="state.isSheetListScrollableRight">
|
||||
<div
|
||||
class="o-bottom-bar-arrow o-bottom-bar-arrow-right d-flex align-items-center me-4"
|
||||
t-att-class="{'o-disabled': !state.isSheetListScrollableRight}"
|
||||
t-on-click="onArrowRight">
|
||||
<t t-call="o-spreadsheet-Icon.CARET_RIGHT"/>
|
||||
</div>
|
||||
</Ripple>
|
||||
</div>
|
||||
|
||||
<t t-set="selectedStatistic" t-value="getSelectedStatistic()"/>
|
||||
<div
|
||||
t-if="selectedStatistic !== undefined"
|
||||
class="o-selection-statistic text-truncate"
|
||||
t-on-click="listSelectionStatistics">
|
||||
<t t-esc="selectedStatistic"/>
|
||||
<span>
|
||||
<t t-call="o-spreadsheet-Icon.TRIANGLE_DOWN"/>
|
||||
</span>
|
||||
</div>
|
||||
<BottomBarStatistic
|
||||
openContextMenu="(x, y, registry) => this.openContextMenu(x, y, 'listSelectionStatistics', registry)"
|
||||
/>
|
||||
|
||||
<Menu
|
||||
t-if="menuState.isOpen"
|
||||
position="menuState.position"
|
||||
menuItems="menuState.menuItems"
|
||||
onClose="() => this.menuState.isOpen=false"
|
||||
maxHeight="menuMaxHeight"
|
||||
onClose="() => this.closeMenu()"
|
||||
/>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
<t t-name="o-spreadsheet-BottomBarSheet" owl="1">
|
||||
<Ripple>
|
||||
<div
|
||||
class="o-sheet position-relative d-flex align-items-center user-select-none text-nowrap "
|
||||
t-on-click="() => this.clickSheet()"
|
||||
t-on-contextmenu.prevent="(ev) => this.onContextMenu(ev)"
|
||||
t-ref="sheetDiv"
|
||||
t-att-title="sheetName"
|
||||
t-att-data-id="props.sheetId"
|
||||
t-att-class="{active: isSheetActive}">
|
||||
<span
|
||||
class="o-sheet-name"
|
||||
t-att-class="{'o-sheet-name-editable': state.isEditing }"
|
||||
t-ref="sheetNameSpan"
|
||||
t-esc="sheetName"
|
||||
t-on-click="(ev) => this.onClickSheetName(ev)"
|
||||
t-on-dblclick="() => this.onDblClick()"
|
||||
t-on-focusout="() => this.onFocusOut()"
|
||||
t-on-keydown="(ev) => this.onKeyDown(ev)"
|
||||
t-att-contenteditable="state.isEditing.toString()"
|
||||
/>
|
||||
<span
|
||||
class="o-sheet-icon position-relative ms-1"
|
||||
t-on-click.stop="(ev) => this.onIconClick(ev)">
|
||||
<t t-call="o-spreadsheet-Icon.TRIANGLE_DOWN"/>
|
||||
</span>
|
||||
</div>
|
||||
</Ripple>
|
||||
</t>
|
||||
|
||||
<t t-name="o-spreadsheet-BottomBarStatisic" owl="1">
|
||||
<t t-set="selectedStatistic" t-value="getSelectedStatistic()"/>
|
||||
<Ripple class="'ms-auto'" t-if="selectedStatistic !== undefined">
|
||||
<div
|
||||
class="o-selection-statistic position-relative text-truncate user-select-none me-4 bg-white rounded shadow"
|
||||
t-on-click="listSelectionStatistics">
|
||||
<t t-esc="selectedStatistic"/>
|
||||
<span class="ms-2">
|
||||
<t t-call="o-spreadsheet-Icon.TRIANGLE_DOWN"/>
|
||||
</span>
|
||||
</div>
|
||||
</Ripple>
|
||||
</t>
|
||||
|
||||
<t t-name="o-spreadsheet-ClientTag" owl="1">
|
||||
<div>
|
||||
<div class="o-client-tag" t-att-style="tagStyle" t-esc="props.name"/>
|
||||
@@ -484,7 +592,7 @@
|
||||
<Menu
|
||||
t-if="menuState.isOpen"
|
||||
position="menuState.position"
|
||||
menuItems="menuState.menuItems"
|
||||
menuItems="menuItems"
|
||||
onClose="() => this.menuState.isOpen=false"
|
||||
/>
|
||||
</t>
|
||||
@@ -1222,6 +1330,20 @@
|
||||
/>
|
||||
</svg>
|
||||
</t>
|
||||
<t t-name="o-spreadsheet-Icon.CARET_RIGHT" owl="1">
|
||||
<svg class="o-icon caret-right" viewBox="0 0 192 512">
|
||||
<path
|
||||
d="M0 384.662V127.338c0-17.818 21.543-26.741 34.142-14.142l128.662 128.662c7.81 7.81 7.81 20.474 0 28.284L34.142 398.804C21.543 411.404 0 402.48 0 384.662z"
|
||||
/>
|
||||
</svg>
|
||||
</t>
|
||||
<t t-name="o-spreadsheet-Icon.CARET_LEFT" owl="1">
|
||||
<svg class="o-icon caret-left" viewBox="0 0 192 512">
|
||||
<path
|
||||
d="M192 127.338v257.324c0 17.818-21.543 26.741-34.142 14.142L29.196 270.142c-7.81-7.81-7.81-20.474 0-28.284l128.662-128.662c12.599-12.6 34.142-3.676 34.142 14.142z"
|
||||
/>
|
||||
</svg>
|
||||
</t>
|
||||
|
||||
<t t-name="o-spreadsheet-Icon.TRASH" owl="1">
|
||||
<svg class="o-cf-icon trash" viewBox="0 0 448 512">
|
||||
@@ -1465,7 +1587,7 @@
|
||||
|
||||
<t t-name="o-spreadsheet-Menu" owl="1">
|
||||
<Popover
|
||||
t-if="props.menuItems.length"
|
||||
t-if="visibleMenuItems.length"
|
||||
anchorRect="popover.anchorRect"
|
||||
positioning="popover.positioning"
|
||||
verticalOffset="popover.verticalOffset"
|
||||
@@ -1479,7 +1601,7 @@
|
||||
t-on-wheel.stop=""
|
||||
t-on-click.stop=""
|
||||
t-on-contextmenu.prevent="">
|
||||
<t t-foreach="props.menuItems" t-as="menuItem" t-key="menuItem.id">
|
||||
<t t-foreach="visibleMenuItems" t-as="menuItem" t-key="menuItem.id">
|
||||
<t t-set="isMenuRoot" t-value="isRoot(menuItem)"/>
|
||||
<t t-set="isMenuEnabled" t-value="isEnabled(menuItem)"/>
|
||||
<div
|
||||
@@ -1492,9 +1614,9 @@
|
||||
t-att-style="getColor(menuItem)">
|
||||
<span class="o-menu-item-name" t-esc="getName(menuItem)"/>
|
||||
<span
|
||||
t-if="getDescription(menuItem)"
|
||||
t-if="menuItem.description"
|
||||
class="o-menu-item-description"
|
||||
t-esc="getDescription(menuItem)"
|
||||
t-esc="menuItem.description"
|
||||
/>
|
||||
<t t-if="isMenuRoot">
|
||||
<span t-call="o-spreadsheet-Icon.TRIANGLE_RIGHT"/>
|
||||
@@ -1511,6 +1633,7 @@
|
||||
position="subMenuPosition"
|
||||
menuItems="subMenu.menuItems"
|
||||
depth="props.depth + 1"
|
||||
maxHeight="props.maxHeight"
|
||||
onMenuClicked="props.onMenuClicked"
|
||||
onClose="() => this.close()"
|
||||
/>
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import { nextTick } from "@web/../tests/helpers/utils";
|
||||
|
||||
import { selectCell } from "@spreadsheet/../tests/utils/commands";
|
||||
import { doMenuAction } from "@spreadsheet/../tests/utils/ui";
|
||||
import { createSpreadsheetWithPivot } from "@spreadsheet/../tests/utils/pivot";
|
||||
|
||||
import spreadsheet from "@spreadsheet/o_spreadsheet/o_spreadsheet_extended";
|
||||
@@ -41,8 +42,7 @@ QUnit.test("Can open see records on headers col", async function (assert) {
|
||||
const { env, model } = await createSpreadsheetWithPivot();
|
||||
selectCell(model, "B1");
|
||||
await nextTick();
|
||||
const root = cellMenuRegistry.getAll().find((item) => item.id === "pivot_see_records");
|
||||
await root.action(env);
|
||||
await doMenuAction(cellMenuRegistry, ["pivot_see_records"], env);
|
||||
assert.verifySteps(["doAction"]);
|
||||
});
|
||||
|
||||
@@ -63,8 +63,7 @@ QUnit.test("Can open see records on headers row", async function (assert) {
|
||||
const { env, model } = await createSpreadsheetWithPivot();
|
||||
selectCell(model, "A3");
|
||||
await nextTick();
|
||||
const root = cellMenuRegistry.getAll().find((item) => item.id === "pivot_see_records");
|
||||
await root.action(env);
|
||||
await doMenuAction(cellMenuRegistry, ["pivot_see_records"], env);
|
||||
assert.verifySteps(["doAction"]);
|
||||
});
|
||||
|
||||
@@ -85,8 +84,6 @@ QUnit.test("Can open see records on measure headers", async function (assert) {
|
||||
const { env, model } = await createSpreadsheetWithPivot();
|
||||
selectCell(model, "B2");
|
||||
await nextTick();
|
||||
const root = cellMenuRegistry.getAll().find((item) => item.id === "pivot_see_records");
|
||||
await root.action(env);
|
||||
await doMenuAction(cellMenuRegistry, ["pivot_see_records"], env);
|
||||
assert.verifySteps(["doAction"]);
|
||||
});
|
||||
|
||||
|
||||
@@ -7,7 +7,6 @@ import { loadJS, templates } from "@web/core/assets";
|
||||
|
||||
const { App } = owl;
|
||||
const { Spreadsheet } = spreadsheet;
|
||||
const { getMenuChildren } = spreadsheet.helpers;
|
||||
|
||||
/** @typedef {import("@spreadsheet/o_spreadsheet/o_spreadsheet").Model} Model */
|
||||
|
||||
@@ -31,14 +30,22 @@ export async function mountSpreadsheet(model) {
|
||||
}
|
||||
|
||||
export async function doMenuAction(registry, path, env) {
|
||||
const root = path[0];
|
||||
let node = registry.get(root);
|
||||
for (const p of path.slice(1)) {
|
||||
const children = getMenuChildren(node, env);
|
||||
node = children.find((child) => child.id === p);
|
||||
}
|
||||
if (!node) {
|
||||
throw new Error(`Cannot find menu with path "${path.join("/")}"`);
|
||||
}
|
||||
await node.action(env);
|
||||
await getMenuItem(registry, path, env).action(env);
|
||||
}
|
||||
|
||||
function getMenuItem(registry, _path, env) {
|
||||
const path = [..._path];
|
||||
let items = registry.getMenuItems();
|
||||
while (items.length && path.length) {
|
||||
const id = path.shift();
|
||||
const item = items.find((item) => item.id === id);
|
||||
if (!item) {
|
||||
throw new Error(`Menu item ${id} not found`);
|
||||
}
|
||||
if (path.length === 0) {
|
||||
return item;
|
||||
}
|
||||
items = item.children(env);
|
||||
}
|
||||
throw new Error(`Menu item not found`);
|
||||
}
|
||||
|
||||
@@ -68,7 +68,9 @@ QUnit.module("spreadsheet_account > Accounting Drill down", { beforeEach }, () =
|
||||
setCellContent(model, "A2", `=ODOO.BALANCE("100", 0)`);
|
||||
await waitForDataSourcesLoaded(model);
|
||||
selectCell(model, "A1");
|
||||
const root = cellMenuRegistry.getAll().find((item) => item.id === "move_lines_see_records");
|
||||
const root = cellMenuRegistry
|
||||
.getMenuItems()
|
||||
.find((item) => item.id === "move_lines_see_records");
|
||||
assert.equal(root.isVisible(env), true);
|
||||
await root.action(env);
|
||||
assert.verifySteps(["drill down action"]);
|
||||
|
||||
Reference in New Issue
Block a user