Skip to content
Draft
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
1 change: 1 addition & 0 deletions src/components/figures/figure/figure.css
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
div.o-figure {
color-scheme: light;
color: var(--os-text-body);
border: solid var(--os-figure-border-color);

position: absolute;
width: 100%;
Expand Down
23 changes: 21 additions & 2 deletions src/components/figures/figure/figure.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,11 +69,11 @@ export class FigureComponent extends Component<Props, SpreadsheetChildEnv> {
if (this.env.isDashboard()) {
return 0;
}
return this.isSelected ? ACTIVE_BORDER_WIDTH : this.borderWidth;
return this.isSelected ? ACTIVE_BORDER_WIDTH : 0;
}

getBorderStyle(position: "top" | "right" | "bottom" | "left"): string {
return `border-${position}-width: ${this.getBorderWidth()}px;`;
return `border-${position}-width: ${this.getBorderWidth()}px`;
}

get wrapperStyle() {
Expand All @@ -86,6 +86,25 @@ export class FigureComponent extends Component<Props, SpreadsheetChildEnv> {
});
}

get figureStyle() {
const properties: CSSProperties = {
"border-width": `${this.borderWidth}px`,
};
if (this.isSelected) {
// ADRM TODO: wait for real carousel data view and improve this (we have 2 different borders handler, and it's ugly w/ data view)
properties["border-color"] = "transparent";
}
return cssPropertiesToCss(properties) + ";" + this.props.style;
}

get figureClass() {
return (
this.props.class +
(this.props.figureUI.roundedBorders ? " rounded-3" : "") +
(this.props.figureUI.shadow ? " shadow" : "")
);
}

getResizerPosition(resizer: ResizeAnchor): string {
const anchorCenteringOffset = (ANCHOR_SIZE - ACTIVE_BORDER_WIDTH) / 2;
const style: CSSProperties = {};
Expand Down
6 changes: 3 additions & 3 deletions src/components/figures/figure/figure.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@
t-att-style="wrapperStyle"
t-ref="figureWrapper">
<div
class="o-figure w-100 h-100"
t-att-class="props.class"
class="o-figure w-100 h-100 overflow-hidden"
t-att-class="this.figureClass"
t-on-pointerdown.stop="(ev) => this.onMouseDown(ev)"
t-on-click="onClick"
t-on-contextmenu.prevent.stop="(ev) => !env.model.getters.isReadonly() and this.onContextMenu(ev)"
t-ref="figure"
t-att-style="props.style"
t-att-style="figureStyle"
t-att-data-id="props.figureUI.id"
tabindex="0"
t-on-keydown.stop="(ev) => this.onKeyDown(ev)"
Expand Down
3 changes: 2 additions & 1 deletion src/components/side_panel/carousel_panel/carousel_panel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { SpreadsheetChildEnv } from "../../../types/spreadsheet_env";
import { getBoundingRectAsPOJO } from "../../helpers/dom_helpers";
import { useDragAndDropListItems } from "../../helpers/drag_and_drop_dom_items_hook";
import { TextInput } from "../../text_input/text_input";
import { FigureOptions } from "../chart/building_blocks/figure_options/figure_options";
import { TextStyler } from "../chart/building_blocks/text_styler/text_styler";
import { CogWheelMenu } from "../components/cog_wheel_menu/cog_wheel_menu";
import { Section } from "../components/section/section";
Expand All @@ -24,7 +25,7 @@ interface Props {
export class CarouselPanel extends Component<Props, SpreadsheetChildEnv> {
static template = "o-spreadsheet-CarouselPanel";
static props = { onCloseSidePanel: Function, figureId: String };
static components = { Section, TextInput, TextStyler, CogWheelMenu };
static components = { Section, TextInput, TextStyler, CogWheelMenu, FigureOptions };

DEFAULT_CAROUSEL_TITLE_STYLE = DEFAULT_CAROUSEL_TITLE_STYLE;

Expand Down
27 changes: 15 additions & 12 deletions src/components/side_panel/carousel_panel/carousel_panel.xml
Original file line number Diff line number Diff line change
Expand Up @@ -51,19 +51,22 @@
</div>
</t>
</div>
<div
class="o-button-link o-carousel-add-chart float-end d-flex align-items-center py-4 pe-4 gap-2"
t-on-click="addNewChartToCarousel"
t-att-title="carouselAddChartInfoMessage">
+ Add chart
</div>
<div
t-if="!hasDataView"
class="o-button-link o-carousel-add-data-view float-end py-4 pe-4"
t-on-click="addDataViewToCarousel"
t-att-title="carouselDataViewMessage">
+ Add data view
<div class="d-flex justify-content-end">
<div
class="o-button-link o-carousel-add-chart d-flex align-items-center py-4 pe-4 gap-2"
t-on-click="addNewChartToCarousel"
t-att-title="carouselAddChartInfoMessage">
+ Add chart
</div>
<div
t-if="!hasDataView"
class="o-button-link o-carousel-add-data-view py-4 pe-4"
t-on-click="addDataViewToCarousel"
t-att-title="carouselDataViewMessage">
+ Add data view
</div>
</div>
<FigureOptions figureId="this.props.figureId"/>
</div>
</t>
</templates>
Original file line number Diff line number Diff line change
Expand Up @@ -31,5 +31,6 @@
/>
</t>
</SidePanelCollapsible>
<FigureOptions isInitiallyCollapsed="true" figureId="this.figureId"/>
</t>
</templates>
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { Component } from "@odoo/owl";
import { Figure, UID } from "../../../../..";
import { SpreadsheetChildEnv } from "../../../../../types/spreadsheet_env";
import { Checkbox } from "../../../components/checkbox/checkbox";
import { SidePanelCollapsible } from "../../../components/collapsible/side_panel_collapsible";
import { Section } from "../../../components/section/section";

interface Props {
figureId: UID;
isInitiallyCollapsed?: boolean;
}

export class FigureOptions extends Component<Props, SpreadsheetChildEnv> {
static template = "o-spreadsheet.FigureOptions";
static components = { Section, Checkbox, SidePanelCollapsible };
static props = {
figureId: String,
isInitiallyCollapsed: { type: Boolean, optional: true },
};

get figure(): Figure {
const figure = this.env.model.getters.getFigure(this.sheetId, this.props.figureId);
if (!figure) {
throw new Error(`Figure with id ${this.props.figureId} not found`);
}
return figure;
}

get sheetId(): UID {
const sheetId = this.env.model.getters.getFigureSheetId(this.props.figureId);
if (!sheetId) {
throw new Error(`Sheet id for figure with id ${this.props.figureId} not found`);
}
return sheetId;
}

updateRoundedBorders(value: boolean) {
this.env.model.dispatch("UPDATE_FIGURE", {
sheetId: this.sheetId,
figureId: this.props.figureId,
...this.figure,
roundedBorders: value,
});
}

updateShadow(value: boolean) {
this.env.model.dispatch("UPDATE_FIGURE", {
sheetId: this.sheetId,
figureId: this.props.figureId,
...this.figure,
shadow: value,
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<templates>
<t t-name="o-spreadsheet.FigureOptions">
<SidePanelCollapsible
isInitiallyCollapsed="!!this.props.isInitiallyCollapsed"
title.translate="Figure Options">
<t t-set-slot="content">
<Section class="'o-figure-options pt-0'" title.translate="Figure style">
<Checkbox
name="'roundedBorders'"
label.translate="Rounded Borders"
value="!!this.figure.roundedBorders"
onChange.bind="updateRoundedBorders"
/>
<Checkbox
name="'shadow'"
label.translate="Shadow"
value="!!this.figure.shadow"
onChange.bind="updateShadow"
/>
</Section>
</t>
</SidePanelCollapsible>
</t>
</templates>
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
AxisDefinition,
AxisDesignEditor,
} from "../building_blocks/axis_design/axis_design_editor";
import { FigureOptions } from "../building_blocks/figure_options/figure_options";
import { GeneralDesignEditor } from "../building_blocks/general_design/general_design_editor";
import { ChartHumanizeNumbers } from "../building_blocks/humanize_numbers/humanize_numbers";
import { ChartLegend } from "../building_blocks/legend/legend";
Expand All @@ -29,6 +30,7 @@ export class ChartWithAxisDesignPanel<
ChartLegend,
ChartShowValues,
ChartHumanizeNumbers,
FigureOptions,
};
static props = ChartSidePanelPropsObject;

Expand All @@ -43,4 +45,8 @@ export class ChartWithAxisDesignPanel<
}
return axes;
}

get figureId() {
return this.env.model.getters.getFigureIdFromChartId(this.props.chartId);
}
}
2 changes: 2 additions & 0 deletions src/plugins/core/figures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,8 @@ export class FigurePlugin extends CorePlugin<FigureState> implements FigureState
case "row":
case "width":
case "height":
case "roundedBorders":
case "shadow":
if (value !== undefined) {
this.history.update("figures", sheetId, figure.id, key, value as number);
}
Expand Down
2 changes: 2 additions & 0 deletions src/types/figure.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ export interface FigureInfo {
width: Pixel;
height: Pixel;
tag: string;
roundedBorders?: boolean;
shadow?: boolean;
}

export interface Figure extends FigureInfo, AnchorOffset {}
Expand Down
21 changes: 21 additions & 0 deletions tests/figures/figure_component.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import {
setCellContent,
setViewportOffset,
setZoom,
updateFigure,
} from "../test_helpers/commands_helpers";
import { TEST_CHART_DATA } from "../test_helpers/constants";
import {
Expand Down Expand Up @@ -1854,6 +1855,26 @@ describe("figures", () => {
});
});
});

test("Can display a figure with a shadow", async () => {
createFigure(model, { id: "figureId" });
await nextTick();
expect(".o-figure").not.toHaveClass("shadow");

updateFigure(model, { figureId: "figureId", shadow: true });
await nextTick();
expect(".o-figure").toHaveClass("shadow");
});

test("Can display a figure with rounded borders", async () => {
createFigure(model, { id: "figureId" });
await nextTick();
expect(".o-figure").not.toHaveClass("rounded-3");

updateFigure(model, { figureId: "figureId", roundedBorders: true });
await nextTick();
expect(".o-figure").toHaveClass("rounded-3");
});
});

describe.each(ZOOM_VALUES.map((zoom) => zoom / 100))("figures with zoom %s", (zoom) => {
Expand Down
46 changes: 46 additions & 0 deletions tests/side_panels/building_blocks/figure_options.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { FigureOptions } from "../../../src/components/side_panel/chart/building_blocks/figure_options/figure_options";
import { Model } from "../../../src/model";
import { createFigure, simulateClick } from "../../test_helpers";
import { mountComponentWithPortalTarget } from "../../test_helpers/helpers";

let model: Model;
let sheetId: string;
const figureId = "figureId";

beforeEach(() => {
model = new Model();
sheetId = model.getters.getActiveSheetId();
});

async function mountComponent(props: FigureOptions["props"]) {
await mountComponentWithPortalTarget(FigureOptions, {
props: { ...props },
model,
});
}

describe("Figure options", () => {
test("Can add a shadow to the figure", async () => {
createFigure(model, { figureId });
await mountComponent({ figureId });

expect("input[name='shadow']").toHaveValue(false);
expect(model.getters.getFigure(sheetId, figureId)?.shadow).toBe(undefined);
await simulateClick("input[name='shadow']");

expect(model.getters.getFigure(sheetId, figureId)?.shadow).toBe(true);
expect("input[name='shadow']").toHaveValue(true);
});

test("Can add rounded borders to the figure", async () => {
createFigure(model, { figureId });
await mountComponent({ figureId });

expect("input[name='roundedBorders']").toHaveValue(false);
expect(model.getters.getFigure(sheetId, figureId)?.roundedBorders).toBe(undefined);
await simulateClick("input[name='roundedBorders']");

expect(model.getters.getFigure(sheetId, figureId)?.roundedBorders).toBe(true);
expect("input[name='roundedBorders']").toHaveValue(true);
});
});
9 changes: 7 additions & 2 deletions tests/test_helpers/commands_helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -181,8 +181,13 @@ export function createFigure(
});
}

export function updateFigure(model: Model, cmd: Omit<UpdateFigureCommand, "type">) {
return model.dispatch("UPDATE_FIGURE", cmd);
export function updateFigure(
model: Model,
cmd: Partial<Omit<UpdateFigureCommand, "type" | "figureId">> & { figureId: UID }
) {
const sheetId = model.getters.getFigureSheetId(cmd.figureId)!;
const figure = model.getters.getFigure(sheetId, cmd.figureId)!;
return model.dispatch("UPDATE_FIGURE", { ...figure, ...cmd, sheetId, figureId: cmd.figureId });
}

export function deleteFigure(
Expand Down