Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[IMP] pivot: add icon to sort pivot #5363

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 0 additions & 34 deletions src/components/data_validation_overlay/data_validation_overlay.ts

This file was deleted.

15 changes: 0 additions & 15 deletions src/components/data_validation_overlay/data_validation_overlay.xml

This file was deleted.

This file was deleted.

This file was deleted.

41 changes: 41 additions & 0 deletions src/components/grid_cell_icon_overlay/grid_cell_icon_overlay.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { Component } from "@odoo/owl";
import { isDefined } from "../../helpers";
import { Store, useStore } from "../../store_engine";
import { SpreadsheetChildEnv } from "../../types";
import { DataValidationCheckbox } from "../data_validation_overlay/dv_checkbox/dv_checkbox";
import { DataValidationListIcon } from "../data_validation_overlay/dv_list_icon/dv_list_icon";
import { FilterIcon } from "../filters/filter_icon/filter_icon";
import { GridCellIcon } from "../grid_cell_icon/grid_cell_icon";
import { GridCellIconStore } from "./grid_cell_icon_overlay_store";

export class GridCellIconOverlay extends Component<{}, SpreadsheetChildEnv> {
static template = "o-spreadsheet-GridCellIconOverlay";
static props = {};
static components = { GridCellIcon };

store!: Store<GridCellIconStore>;

setup() {
this.store = useStore(GridCellIconStore);
this.store.addIconProvider({
component: FilterIcon,
hasIcon: (getters, cellPosition) => getters.isFilterHeader(cellPosition),
type: "rightIcon",
});
this.store.addIconProvider({
component: DataValidationCheckbox,
hasIcon: (getters, cellPosition) => getters.isCellValidCheckbox(cellPosition),
type: "exclusiveIcon",
});
this.store.addIconProvider({
component: DataValidationListIcon,
hasIcon: (getters, cellPosition) =>
!getters.isReadonly() && getters.cellHasListDataValidationIcon(cellPosition),
type: "rightIcon",
});
Comment on lines +20 to +35
Copy link
Contributor Author

@hokolomopo hokolomopo Jan 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Having the overlay having to register its own providers is not the best, ideally it shouldn't have to know about them ... But we need to register them somewhere (in a component) 🤷

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we can have a register of icon providers somewhere and then the overlay can add all of the entries of this register in the store ?

}

get icons() {
return Object.values(this.store.icons).filter(isDefined);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<templates>
<t t-name="o-spreadsheet-GridCellIconOverlay">
<t t-foreach="icons" t-as="icon" t-key="icon_index">
<GridCellIcon cellPosition="icon.position" horizontalAlign="icon.horizontalAlign">
<t t-component="icon.component" cellPosition="icon.position"/>
</GridCellIcon>
</t>
</t>
</templates>
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { ComponentConstructor } from "@odoo/owl";
import { CellPosition, Getters, SpreadsheetChildEnv } from "../..";
import { positionToXc } from "../../helpers";
import { SpreadsheetStore } from "../../stores";

export interface GridCellIconProvider {
component: ComponentConstructor<{ cellPosition: CellPosition }, SpreadsheetChildEnv>;
hasIcon: (getters: Getters, cellPosition: CellPosition) => boolean;
type: "exclusiveIcon" | "rightIcon";
}

interface GridCellIcon extends Omit<GridCellIconProvider, "hasIcon"> {
position: CellPosition;
horizontalAlign: "right" | undefined;
}

export class GridCellIconStore extends SpreadsheetStore {
mutators = ["addIconProvider", "removeIconProvider"] as const;

private iconProviders: GridCellIconProvider[] = [];

private iconCache: Record<string, GridCellIcon | undefined> | undefined = undefined;

handle() {
this.iconCache = undefined;
}

addIconProvider(iconProvider: GridCellIconProvider) {
this.iconProviders.push(iconProvider);
this.iconCache = undefined;
}

removeIconProvider(iconProvider: GridCellIconProvider) {
this.iconProviders = this.iconProviders.filter((provider) => provider !== iconProvider);
this.iconCache = undefined;
}

getIcon(position: CellPosition): GridCellIcon | undefined {
for (const iconMatcher of this.iconProviders) {
if (iconMatcher.hasIcon(this.getters, position)) {
return {
...iconMatcher,
position,
horizontalAlign: iconMatcher.type === "rightIcon" ? "right" : undefined,
};
}
}
return undefined;
}

get icons(): Record<string, GridCellIcon | undefined> {
if (!this.iconCache) {
this.iconCache = {};
for (const position of this.getters.getVisibleCellPositions()) {
const xc = positionToXc(position);
this.iconCache[xc] = this.getIcon(position);
}
}

return this.iconCache;
}
}
6 changes: 2 additions & 4 deletions src/components/grid_overlay/grid_overlay.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,9 @@ import {
Ref,
SpreadsheetChildEnv,
} from "../../types";
import { DataValidationOverlay } from "../data_validation_overlay/data_validation_overlay";
import { FiguresContainer } from "../figures/figure_container/figure_container";
import { FilterIconsOverlay } from "../filters/filter_icons_overlay/filter_icons_overlay";
import { GridAddRowsFooter } from "../grid_add_rows_footer/grid_add_rows_footer";
import { GridCellIconOverlay } from "../grid_cell_icon_overlay/grid_cell_icon_overlay";
import { css } from "../helpers";
import { getBoundingRectAsPOJO, isCtrlKey } from "../helpers/dom_helpers";
import { useRefListener } from "../helpers/listener_hook";
Expand Down Expand Up @@ -186,9 +185,8 @@ export class GridOverlay extends Component<Props, SpreadsheetChildEnv> {
};
static components = {
FiguresContainer,
DataValidationOverlay,
GridAddRowsFooter,
FilterIconsOverlay,
GridCellIconOverlay,
};
static defaultProps = {
onCellHovered: () => {},
Expand Down
3 changes: 1 addition & 2 deletions src/components/grid_overlay/grid_overlay.xml
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,7 @@
t-on-dblclick.self="onDoubleClick"
t-on-contextmenu.stop.prevent="onContextMenu">
<FiguresContainer onFigureDeleted="props.onFigureDeleted"/>
<DataValidationOverlay/>
<FilterIconsOverlay/>
<GridCellIconOverlay/>
<GridAddRowsFooter
t-if="!env.model.getters.isReadonly()"
t-key="env.model.getters.getActiveSheetId()"
Expand Down
89 changes: 89 additions & 0 deletions src/components/headers_overlay/autoresize_store.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import { CellPosition, Command, HeaderIndex, UID } from "../..";
import { GRID_ICON_MARGIN, ICON_EDGE_LENGTH, PADDING_AUTORESIZE_HORIZONTAL } from "../../constants";
import {
computeIconWidth,
computeTextWidth,
largeMax,
positionToXc,
positions,
splitTextToWidth,
} from "../../helpers";
import { SpreadsheetStore } from "../../stores";
import { GridCellIconStore } from "../grid_cell_icon_overlay/grid_cell_icon_overlay_store";

export class AutoresizeStore extends SpreadsheetStore {
private ctx = document.createElement("canvas").getContext("2d")!;

protected gridCellIconStore = this.get(GridCellIconStore);

handle(cmd: Command) {
switch (cmd.type) {
case "AUTORESIZE_COLUMNS":
for (let col of cmd.cols) {
const size = this.getColMaxWidth(cmd.sheetId, col);
if (size !== 0) {
this.model.dispatch("RESIZE_COLUMNS_ROWS", {
elements: [col],
dimension: "COL",
size,
sheetId: cmd.sheetId,
});
}
}
break;
case "AUTORESIZE_ROWS":
for (let row of cmd.rows) {
this.model.dispatch("RESIZE_COLUMNS_ROWS", {
elements: [row],
dimension: "ROW",
size: null,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why don't we compute the size according to the content, as done for the columns ?

sheetId: cmd.sheetId,
});
}
break;
}
}

getCellWidth(position: CellPosition): number {
const style = this.getters.getCellComputedStyle(position);

let contentWidth = 0;

const content = this.getters.getEvaluatedCell(position).formattedValue;
if (content) {
const multiLineText = splitTextToWidth(this.ctx, content, style, undefined);
contentWidth += Math.max(
...multiLineText.map((line) => computeTextWidth(this.ctx, line, style))
);
}

const icon = this.getters.getCellIconSrc(position);
if (icon) {
contentWidth += computeIconWidth(style);
}

const xc = positionToXc(position);
const cellIcon = this.gridCellIconStore.icons[xc];
if (cellIcon) {
contentWidth += ICON_EDGE_LENGTH + GRID_ICON_MARGIN;
}

if (contentWidth === 0) {
return 0;
}

contentWidth += 2 * PADDING_AUTORESIZE_HORIZONTAL;
if (style.wrapping === "wrap") {
const colWidth = this.getters.getColSize(this.getters.getActiveSheetId(), position.col);
return Math.min(colWidth, contentWidth);
}

return contentWidth;
}

private getColMaxWidth(sheetId: UID, index: HeaderIndex): number {
const cellsPositions = positions(this.getters.getColsZone(sheetId, index, index));
const sizes = cellsPositions.map((position) => this.getCellWidth({ sheetId, ...position }));
return Math.max(0, largeMax(sizes));
}
}
2 changes: 2 additions & 0 deletions src/components/headers_overlay/headers_overlay.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import { isCtrlKey } from "../helpers/dom_helpers";
import { dragAndDropBeyondTheViewport, startDnd } from "../helpers/drag_and_drop";
import { MergeErrorMessage } from "../translations_terms";
import { ComposerFocusStore } from "./../composer/composer_focus_store";
import { AutoresizeStore } from "./autoresize_store";

// -----------------------------------------------------------------------------
// Resizer component
Expand Down Expand Up @@ -112,6 +113,7 @@ abstract class AbstractResizer extends Component<ResizerProps, SpreadsheetChildE

setup(): void {
this.composerFocusStore = useStore(ComposerFocusStore);
useStore(AutoresizeStore);
}

_computeHandleDisplay(ev: MouseEvent) {
Expand Down
10 changes: 10 additions & 0 deletions src/components/icons/icons.xml
Original file line number Diff line number Diff line change
Expand Up @@ -906,6 +906,16 @@
</svg>
</div>
</t>
<t t-name="o-spreadsheet-Icon.ANGLE_UP">
<div class="o-icon">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 224 256">
<path
d="M201.4 342.6c12.5 12.5 32.8 12.5 45.3 0l160-160c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L224 274.7 86.6 137.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3l160 160z"
transform="rotate(180 112 128) translate(0, 9) scale(0.5,0.5)"
/>
</svg>
</div>
</t>
<t t-name="o-spreadsheet-Icon.INSERT_PIVOT">
<svg class="o-icon">
<defs>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
import { Component } from "@odoo/owl";
import { Component, onMounted, onWillUnmount } from "@odoo/owl";
import { getPivotHighlights } from "../../../../helpers/pivot/pivot_highlight";
import { pivotSidePanelRegistry } from "../../../../helpers/pivot/pivot_side_panel_registry";
import { useStore } from "../../../../store_engine";
import { SpreadsheetChildEnv, UID } from "../../../../types";
import {
GridCellIconProvider,
GridCellIconStore,
} from "../../../grid_cell_icon_overlay/grid_cell_icon_overlay_store";
import { useHighlights } from "../../../helpers/highlight_hook";
import { Section } from "../../components/section/section";
import { PivotLayoutConfigurator } from "../pivot_layout_configurator/pivot_layout_configurator";
import { PivotSortIcon } from "../pivot_sort_icon/pivot_sort_icon";

interface Props {
pivotId: UID;
Expand All @@ -23,7 +29,26 @@ export class PivotSidePanel extends Component<Props, SpreadsheetChildEnv> {
};

setup() {
const iconProvider: GridCellIconProvider = {
component: PivotSortIcon,
hasIcon: (getters, cellPosition) => {
const cellPivotId = getters.getPivotIdFromPosition(cellPosition);
if (cellPivotId !== this.props.pivotId) {
return false;
}
const pivotCell = getters.getPivotCellFromPosition(cellPosition);
return pivotCell.type === "MEASURE_HEADER";
},
type: "rightIcon",
};
const gridCellIconStore = useStore(GridCellIconStore);
useHighlights(this);
onMounted(() => {
gridCellIconStore.addIconProvider(iconProvider);
});
onWillUnmount(() => {
gridCellIconStore.removeIconProvider(iconProvider);
});
}

get sidePanelEditor() {
Expand Down
Loading