Skip to content

Commit

Permalink
[ADD] pivots: add pivot plumbing
Browse files Browse the repository at this point in the history
This commit prepares the ground to the introduction of the spreadsheet
pivot table. It's basically a move of the plumbing necessary to make
a pivot works from odoo codebase to o-spreadsheet codebase.

Some notes on this commit:
- No tests are added, we trust the tests in Odoo.

Adding some tests would require to make a dummy pivot, which is
not ideal. Let's write tests during/after the introduction of spreadsheet
pivot.
- Some references to Odoo are included here

In order to make this commit only a move and not a move/ref, we decided
to introduce some concepts of Odoo here (like Fields, many2one, odoo classes, ...)
As far as possible, these concepts will be reworked/removed/renamed later.

Task: 3748583
  • Loading branch information
pro-odoo committed Apr 18, 2024
1 parent b8f15ff commit d2f4d98
Show file tree
Hide file tree
Showing 44 changed files with 2,982 additions and 10 deletions.
21 changes: 21 additions & 0 deletions src/collaborative/ot/ot_specific.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,18 +14,23 @@ import {
CreateSheetCommand,
DeleteFigureCommand,
DeleteSheetCommand,
DuplicatePivotCommand,
FoldHeaderGroupCommand,
FreezeColumnsCommand,
FreezeRowsCommand,
GroupHeadersCommand,
HeaderIndex,
InsertPivotCommand,
MoveRangeCommand,
RemoveColumnsRowsCommand,
RemoveMergeCommand,
RemovePivotCommand,
RenamePivotCommand,
UnGroupHeadersCommand,
UnfoldHeaderGroupCommand,
UpdateChartCommand,
UpdateFigureCommand,
UpdatePivotCommand,
UpdateTableCommand,
Zone,
} from "../../types";
Expand Down Expand Up @@ -75,6 +80,22 @@ otRegistry.addTransformation(
groupHeadersTransformation
);

otRegistry.addTransformation(
"REMOVE_PIVOT",
["RENAME_PIVOT", "DUPLICATE_PIVOT", "INSERT_PIVOT", "UPDATE_PIVOT"],
pivotTransformation
);

function pivotTransformation(
toTransform: RenamePivotCommand | DuplicatePivotCommand | InsertPivotCommand | UpdatePivotCommand,
executed: RemovePivotCommand
) {
if (toTransform.pivotId === executed.pivotId) {
return undefined;
}
return toTransform;
}

function transformTargetSheetId(
toTransform: MoveRangeCommand,
executed: DeleteSheetCommand
Expand Down
13 changes: 13 additions & 0 deletions src/components/icons/icons.xml
Original file line number Diff line number Diff line change
Expand Up @@ -956,4 +956,17 @@ https://fontawesome.com/license -->
/>
</svg>
</t>
<t t-name="o-spreadsheet-Icon.PIVOT">
<svg class="o-icon" viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg">
<path
fill="currentColor"
d="M17 2v14H1V6h4V2h12m-1 1H6v3h10M2 7v2h3V7m-3 3v2h3v-2m-3 3v2h3v-2m1-6v2h6V7m-6 3v2h6v-2m-6 3v2h6v-2m1-6v2h3V7m-3 3v2h3v-2m-3 3v2h3v-2"
/>
</svg>
</t>
<t t-name="o-spreadsheet-Icon.UNUSED_PIVOT_WARNING">
<div class="o-unused-pivot-icon text-warning" title="This pivot is not used">
<t t-call="o-spreadsheet-Icon.TRIANGLE_EXCLAMATION"/>
</div>
</t>
</templates>
26 changes: 26 additions & 0 deletions src/components/side_panel/pivot/all_pivots_side_panel.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { Component, useRef } from "@odoo/owl";
import { getPivotHighlights } from "../../../helpers/pivot/pivot_highlight";
import { useHighlightsOnHover } from "../../helpers/highlight_hook";

class PivotPreview extends Component {
static template = "o-spreadsheet-PivotPreview";
static props = { pivotId: String };
setup() {
const previewRef = useRef("pivotPreview");
useHighlightsOnHover(previewRef, this);
}

selectPivot() {
this.env.openSidePanel("PIVOT_PROPERTIES_PANEL", { pivotId: this.props.pivotId });
}

get highlights() {
return getPivotHighlights(this.env.model.getters, this.props.pivotId);
}
}

export class AllPivotsSidePanel extends Component {
static template = "o-spreadsheet-AllPivotsSidePanel";
static components = { PivotPreview };
static props = { onCloseSidePanel: Function };
}
13 changes: 13 additions & 0 deletions src/components/side_panel/pivot/all_pivots_side_panel.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<templates>
<div t-name="o-spreadsheet-AllPivotsSidePanel" class="o_spreadsheet_pivot_side_panel">
<t t-foreach="env.model.getters.getPivotIds()" t-as="pivotId" t-key="pivotId">
<PivotPreview pivotId="pivotId"/>
</t>
</div>

<t t-name="o-spreadsheet-PivotPreview">
<div class="o_side_panel_select" t-ref="pivotPreview" t-on-click="this.selectPivot">
<span t-esc="env.model.getters.getPivotDisplayName(props.pivotId)"/>
</div>
</t>
</templates>
37 changes: 37 additions & 0 deletions src/components/side_panel/pivot/editable_name/editable_name.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/** @odoo-module */

import { Component, useState } from "@odoo/owl";
import { SpreadsheetChildEnv } from "../../../..";

interface Props {
name: string;
displayName: string;
onChanged: (name: string) => void;
}

export class EditableName extends Component<Props, SpreadsheetChildEnv> {
static template = "o-spreadsheet-EditableName";
static props = {
name: String,
displayName: String,
onChanged: Function,
};
private state!: { isEditing: boolean; name: string };

setup() {
this.state = useState({
isEditing: false,
name: "",
});
}

rename() {
this.state.isEditing = true;
this.state.name = this.props.name;
}

save() {
this.props.onChanged(this.state.name.trim());
this.state.isEditing = false;
}
}
19 changes: 19 additions & 0 deletions src/components/side_panel/pivot/editable_name/editable_name.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<templates>
<t t-name="o-spreadsheet-EditableName">
<t t-if="state.isEditing">
<div>
<input type="text" class="o_input o_sp_en_name" t-model="state.name"/>
</div>
<div class="btn btn-link o_sp_en_save" t-on-click="save">Save</div>
<br/>
</t>
<t t-else="">
<div class="o_sp_en_display_name" t-esc="props.displayName"/>
<div class="btn btn-link o_sp_en_rename" t-on-click="rename">
<i class="fa fa-pencil me-1"/>
Rename
</div>
<br/>
</t>
</t>
</templates>
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import { Component, useExternalListener, useRef, useState } from "@odoo/owl";
import { SpreadsheetChildEnv } from "../../../../..";
import { fuzzyLookup } from "../../../../../helpers";
import { PivotField } from "../../../../../types/pivot";
import { css } from "../../../../helpers";
import { Popover } from "../../../../popover";

interface Props {
onFieldPicked: (field: string) => void;
fields: PivotField[];
}

css/* scss */ `
input.pivot-dimension-search-field:focus {
outline: none;
}
.pivot-dimension-search-field-icon svg {
width: 13px;
height: 13px;
}
.pivot-dimension-search {
background-color: white;
}
.pivot-dimension-field {
background-color: white;
&:hover {
background-color: #f0f0f0;
}
}
`;

export class AddDimensionButton extends Component<Props, SpreadsheetChildEnv> {
static template = "o-spreadsheet-AddDimensionButton";
static components = { Popover };
static props = {
onFieldPicked: Function,
fields: Array,
};

private buttonRef = useRef("button");
private popover = useState({ isOpen: false });
private search = useState({ input: "" });

// TODO navigation keys. (this looks a lot like auto-complete list. Could maybe be factorized)
setup() {
useExternalListener(window, "click", (ev) => {
if (ev.target !== this.buttonRef.el) {
this.popover.isOpen = false;
}
});
}

get filteredFields() {
if (this.search.input) {
return fuzzyLookup(this.search.input, this.props.fields, (field) => field.string);
}
return this.props.fields;
}

get popoverProps() {
return {
anchorRect: this.buttonRef.el!.getBoundingClientRect(),
positioning: "BottomLeft",
};
}

pickField(field: PivotField) {
this.props.onFieldPicked(field.name);
this.popover.isOpen = false;
this.search.input = "";
}

togglePopover() {
this.popover.isOpen = !this.popover.isOpen;
this.search.input = "";
}

onKeyDown(ev: KeyboardEvent) {
if (this.filteredFields.length === 1 && ev.key === "Enter") {
this.pickField(this.filteredFields[0]);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<templates>
<t t-name="o-spreadsheet-AddDimensionButton">
<span class="btn btn-sm btn-link add-dimension" t-on-click="togglePopover" t-ref="button">
Add
</span>
<Popover t-if="popover.isOpen" t-props="popoverProps">
<div class="p-2 bg-white border-bottom d-flex align-items-baseline pivot-dimension-search">
<i class="pe-1 pivot-dimension-search-field-icon">
<t t-call="o-spreadsheet-Icon.SEARCH"/>
</i>
<input
t-model="search.input"
t-on-keydown="onKeyDown"
class="border-0 w-100 pivot-dimension-search-field"
autofocus="1"
/>
</div>
<div
t-foreach="filteredFields"
t-as="field"
t-key="field.name"
t-esc="field.string"
t-att-title="field.help"
t-on-click="() => this.pickField(field)"
class="p-1 px-2 pivot-dimension-field"
role="button"
/>
</Popover>
</t>
</templates>
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { Component } from "@odoo/owl";
import { SpreadsheetChildEnv } from "../../../../..";
import { css } from "../../../../helpers";

interface Props {
dimension: PivotDimension;
onRemoved: (dimension: PivotDimension) => void;
}

// don't use bg-white since it's flipped to dark in dark mode and we don't support dark mode
css/* scss */ `
.pivot-dimension {
background-color: white;
select > option {
background-color: white;
}
}
`;

export class PivotDimension extends Component<Props, SpreadsheetChildEnv> {
static template = "o-spreadsheet-PivotDimension";
static props = {
dimension: Object,
onRemoved: { type: Function, optional: true },
slots: { type: Object, optional: true },
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<templates>
<t t-name="o-spreadsheet-PivotDimension">
<div class="border py-1 px-2 d-flex flex-column shadow-sm pivot-dimension">
<div class="d-flex flex-row justify-content-between align-items-center">
<span class="fw-bold" t-esc="props.dimension.displayName"/>
<i
class="btn fa fa-times pe-0"
t-if="props.onRemoved"
t-on-click="() => props.onRemoved(props.dimension)"
/>
</div>
<t t-slot="default"/>
</div>
</t>
</templates>
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { Component } from "@odoo/owl";
import { SpreadsheetChildEnv } from "../../../../..";
import { PERIODS } from "../../../../../helpers/pivot/pivot_helpers";
import { PivotDimension } from "../../../../../types/pivot";

interface Props {
dimension: PivotDimension;
onUpdated: (dimension: PivotDimension, ev: InputEvent) => void;
availableGranularities: Set<string>;
}

export class PivotDimensionGranularity extends Component<Props, SpreadsheetChildEnv> {
static template = "o-spreadsheet-PivotDimensionGranularity";
static props = {
dimension: Object,
onUpdated: Function,
availableGranularities: Set,
};
periods = PERIODS;
allGranularities = ["year", "quarter", "month", "week", "day"];
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<templates>
<t t-name="o-spreadsheet-PivotDimensionGranularity">
<div class="d-flex flex-row">
<div class="d-flex flex-row py-1 px-2 w-100 small">
<t t-set="granularity" t-value="props.dimension.granularity"/>
<div class="flex-basis-50">Granularity</div>
<select
class="o_input flex-basis-50"
t-on-change="(ev) => props.onUpdated(props.dimension, ev.target.value)">
<option
t-foreach="allGranularities"
t-as="granularity"
t-key="granularity"
t-if="props.availableGranularities.has(granularity) || granularity === props.dimension.granularity"
t-att-value="granularity"
t-esc="periods[granularity]"
t-att-selected="granularity === props.dimension.granularity or (granularity === 'month' and !props.dimension.granularity)"
/>
</select>
</div>
</div>
</t>
</templates>
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { Component } from "@odoo/owl";
import { SpreadsheetChildEnv } from "../../../../..";
import { PivotDimension } from "../../../../../types/pivot";

interface Props {
dimension: PivotDimension;
onUpdated: (dimension: PivotDimension, ev: InputEvent) => void;
}

export class PivotDimensionOrder extends Component<Props, SpreadsheetChildEnv> {
static template = "o-spreadsheet-PivotDimensionOrder";
static props = {
dimension: Object,
onUpdated: Function,
};
}

0 comments on commit d2f4d98

Please sign in to comment.