Skip to content
Closed
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
8 changes: 6 additions & 2 deletions demo/data.js
Original file line number Diff line number Diff line change
Expand Up @@ -2978,7 +2978,7 @@ export const demoData = {
C6: "-445248",
D2: "Origin",
D3: "Wales",
D4: "Britain",
D4: "Rome",
D5: "Britain",
D6: "Britain",
E2: "Is king ?",
Expand Down Expand Up @@ -3024,7 +3024,11 @@ export const demoData = {
criterion: {
type: "isValueInList",
values: ["Wales", "Britain", "Rome"],
displayStyle: "arrow",
colors: {
Britain: "#0C343D",
Rome: "#F4CCCC",
},
displayStyle: "chip",
},
ranges: ["D3:D6"],
},
Expand Down
2 changes: 1 addition & 1 deletion src/actions/insert_actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -308,7 +308,7 @@ export const insertDropdown: ActionSpec = {
criterion: {
type: "isValueInList",
values: [],
displayStyle: "arrow",
displayStyle: "chip",
},
},
});
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Component, useEffect, useRef } from "@odoo/owl";
import { AutoCompleteProposal } from "../../../registries/auto_completes";
import { css } from "../../helpers/css";
import { css, cssPropertiesToCss } from "../../helpers/css";
import { HtmlContent } from "../composer/composer";

css/* scss */ `
.o-autocomplete-dropdown {
Expand Down Expand Up @@ -54,4 +55,11 @@ export class TextValueProvider extends Component<Props> {
() => [this.props.selectedIndex, this.autoCompleteListRef.el]
);
}

getCss(html: HtmlContent) {
return cssPropertiesToCss({
color: html.color || "#000000",
background: html.backgroundColor,
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
t-as="content"
t-key="content_index"
t-att-class="content.classes?.join(' ')"
t-attf-style="color: {{content.color || '#000000'}};"
t-att-style="getCss(content)"
t-esc="content.value"
/>
</div>
Expand Down
1 change: 1 addition & 0 deletions src/components/composer/composer/composer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ export type HtmlContent = {
onHover?: (rect: Rect) => void;
onStopHover?: () => void;
color?: Color;
backgroundColor?: Color;
classes?: string[];
};

Expand Down
23 changes: 22 additions & 1 deletion src/components/composer/grid_composer/grid_composer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,13 @@ import {
toXC,
} from "../../../helpers";
import { Store, useStore } from "../../../store_engine";
import { ComposerFocusType, DOMDimension, Rect, SpreadsheetChildEnv } from "../../../types/index";
import {
CellPosition,
ComposerFocusType,
DOMDimension,
Rect,
SpreadsheetChildEnv,
} from "../../../types/index";
import { getTextDecoration } from "../../helpers";
import { css, cssPropertiesToCss } from "../../helpers/css";
import { CellComposerStore } from "../composer/cell_composer_store";
Expand Down Expand Up @@ -62,6 +68,11 @@ export class GridComposer extends Component<Props, SpreadsheetChildEnv> {
private rect: Rect = this.defaultRect;
private isEditing: boolean = false;
private isCellReferenceVisible: boolean = false;
private currentEditedCell: CellPosition = {
col: 0,
row: 0,
sheetId: this.env.model.getters.getActiveSheetId(),
};

private composerStore!: Store<CellComposerStore>;
composerFocusStore!: Store<ComposerFocusStore>;
Expand Down Expand Up @@ -212,12 +223,22 @@ export class GridComposer extends Component<Props, SpreadsheetChildEnv> {
this.composerFocusStore.focusComposer(this.composerInterface, { focusMode: "inactive" });
}

let shouldRecomputeRect = !deepEquals(
this.currentEditedCell,
this.composerStore.currentEditedCell
);

if (this.isEditing !== isEditing) {
this.isEditing = isEditing;
if (!isEditing) {
this.rect = this.defaultRect;
return;
}
this.currentEditedCell = this.composerStore.currentEditedCell;
shouldRecomputeRect = true;
}

if (shouldRecomputeRect) {
const position = this.env.model.getters.getActivePosition();
const zone = this.env.model.getters.expandZone(position.sheetId, positionToZone(position));
this.rect = this.env.model.getters.getVisibleRect(zone);
Expand Down
5 changes: 4 additions & 1 deletion src/components/grid_overlay/grid_overlay.ts
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,10 @@ export class GridOverlay extends Component<Props, SpreadsheetChildEnv> {
onCellClicked(ev: PointerEvent | MouseEvent) {
const openedPopover = this.cellPopovers.persistentCellPopover;
const [col, row] = this.getCartesianCoordinates(ev);
const clickedIcon = this.getInteractiveIconAtEvent(ev);
if (clickedIcon) {
this.env.model.selection.getBackToDefault();
}
this.props.onCellClicked(
col,
row,
Expand All @@ -269,7 +273,6 @@ export class GridOverlay extends Component<Props, SpreadsheetChildEnv> {
ev
);

const clickedIcon = this.getInteractiveIconAtEvent(ev);
if (clickedIcon?.onClick) {
clickedIcon.onClick(clickedIcon.position, this.env);
}
Expand Down
55 changes: 42 additions & 13 deletions src/components/icons/icons.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
TEXT_BODY_MUTED,
} from "../../constants";
import { isDefined } from "../../helpers";
import { Style } from "../../types";
import { ImageSVG } from "../../types/image";
import { css } from "../helpers";

Expand Down Expand Up @@ -119,20 +120,48 @@ const RED_DOT: ImageSVG = {
paths: [{ fillColor: "#E06666", path: DOT_PATH }],
};

export const CARET_DOWN: ImageSVG = {
width: 512,
height: 512,
paths: [{ fillColor: TEXT_BODY_MUTED, path: "M120 195 h270 l-135 130" }],
};
export function getCaretDownSvg(color: Style): ImageSVG {
return {
width: 512,
height: 512,
paths: [{ fillColor: color.textColor || TEXT_BODY_MUTED, path: "M120 195 h270 l-135 130" }],
};
}

export const HOVERED_CARET_DOWN: ImageSVG = {
width: 512,
height: 512,
paths: [
{ fillColor: TEXT_BODY_MUTED, path: "M15 15 h482 v482 h-482" },
{ fillColor: "#fff", path: "M120 195 h270 l-135 130" },
],
};
export function getHoveredCaretDownSvg(color: Style): ImageSVG {
return {
width: 512,
height: 512,
paths: [
{ fillColor: color.textColor || TEXT_BODY_MUTED, path: "M15 15 h482 v482 h-482" },
{ fillColor: color.fillColor || "#fff", path: "M120 195 h270 l-135 130" },
],
};
}

const CHIP_CARET_DOWN_PATH = "M40 185 h270 l-135 128";

export function getChipSvg(chipStyle: Style): ImageSVG {
return {
width: 512,
height: 512,
paths: [{ fillColor: chipStyle.textColor || TEXT_BODY_MUTED, path: CHIP_CARET_DOWN_PATH }],
};
}

export function getHoveredChipSvg(chipStyle: Style): ImageSVG {
return {
width: 512,
height: 512,
paths: [
{
fillColor: chipStyle.textColor || TEXT_BODY_MUTED,
path: "M0,225 A175,175 0 1,0 350,225 A175,175 0 1,0 0,225",
},
{ fillColor: chipStyle.fillColor || TEXT_BODY_MUTED, path: CHIP_CARET_DOWN_PATH },
],
};
}

export const CHECKBOX_UNCHECKED: ImageSVG = {
width: 512,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { onWillStart, onWillUpdateProps, useState } from "@odoo/owl";
import { IsValueInListCriterion } from "../../../../types";
import { Color, IsValueInListCriterion } from "../../../../types";
import { css } from "../../../helpers";
import { RoundColorPicker } from "../../components/round_color_picker/round_color_picker";
import { CriterionForm } from "../criterion_form";
import { CriterionInput } from "../criterion_input/criterion_input";

Expand All @@ -18,7 +19,7 @@ interface State {

export class ListCriterionForm extends CriterionForm<IsValueInListCriterion> {
static template = "o-spreadsheet-ListCriterionForm";
static components = { CriterionInput };
static components = { CriterionInput, RoundColorPicker };

state = useState<State>({
numberOfValues: Math.max(this.props.criterion.values.length, 2),
Expand All @@ -28,7 +29,7 @@ export class ListCriterionForm extends CriterionForm<IsValueInListCriterion> {
super.setup();
const setupDefault = (props: this["props"]) => {
if (props.criterion.displayStyle === undefined) {
this.updateCriterion({ displayStyle: "arrow" });
this.updateCriterion({ displayStyle: "chip" });
}
};
onWillUpdateProps(setupDefault);
Expand All @@ -41,6 +42,12 @@ export class ListCriterionForm extends CriterionForm<IsValueInListCriterion> {
this.updateCriterion({ values });
}

onColorChanged(color: Color, value: string) {
const colors = { ...this.props.criterion.colors };
colors[value] = color || undefined;
this.updateCriterion({ colors });
}

onAddAnotherValue() {
this.state.numberOfValues++;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@
<t t-name="o-spreadsheet-ListCriterionForm">
<t t-foreach="displayedValues" t-as="value" t-key="value_index">
<div class="o-dv-list-values d-flex align-items-center">
<div class="me-1">
<RoundColorPicker
currentColor="props.criterion.colors?.[value] || '#E7E9ED'"
onColorPicked="(c) => this.onColorChanged(c, value)"
/>
</div>
<CriterionInput
value="props.criterion.values[value_index]"
onValueChanged="(v) => this.onValueChanged(v, value_index)"
Expand All @@ -25,6 +31,7 @@

<div class="o-section-subtitle">Display style</div>
<select class="o-dv-display-style o-input" t-on-change="onChangedDisplayStyle">
<option t-att-selected="props.criterion.displayStyle === 'chip'" value="chip">Chip</option>
<option t-att-selected="props.criterion.displayStyle === 'arrow'" value="arrow">Arrow</option>
<option t-att-selected="props.criterion.displayStyle === 'plainText'" value="plainText">
Plain text
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
import { onWillStart, onWillUpdateProps } from "@odoo/owl";
import { IsValueInRangeCriterion } from "../../../../types";
import { Color, IsValueInRangeCriterion } from "../../../../types";
import { SelectionInput } from "../../../selection_input/selection_input";
import { RoundColorPicker } from "../../components/round_color_picker/round_color_picker";
import { CriterionForm } from "../criterion_form";

export class ValueInRangeCriterionForm extends CriterionForm<IsValueInRangeCriterion> {
static template = "o-spreadsheet-ValueInRangeCriterionForm";
static components = { SelectionInput };
static components = { RoundColorPicker, SelectionInput };

setup() {
super.setup();
const setupDefault = (props: this["props"]) => {
if (props.criterion.displayStyle === undefined) {
this.updateCriterion({ displayStyle: "arrow" });
this.updateCriterion({ displayStyle: "chip" });
}
};
onWillUpdateProps(setupDefault);
Expand All @@ -26,4 +27,19 @@ export class ValueInRangeCriterionForm extends CriterionForm<IsValueInRangeCrite
const displayStyle = (ev.target as HTMLInputElement).value as "arrow" | "plainText";
this.updateCriterion({ displayStyle });
}

onColorChanged(color: Color, value: string) {
const colors = { ...this.props.criterion.colors };
colors[value] = color || undefined;
this.updateCriterion({ colors });
}

get values() {
const sheetId = this.env.model.getters.getActiveSheetId();
const values = this.env.model.getters.getDataValidationRangeValues(
sheetId,
this.props.criterion
);
return new Set(values);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,21 @@
required="true"
hasSingleRange="true"
/>
<t t-foreach="values" t-as="value" t-key="value_index">
<div class="o-dv-list-values p-1 d-flex align-items-center">
<div class="me-2">
<RoundColorPicker
currentColor="props.criterion.colors?.[value] || '#E7E9ED'"
onColorPicked="(c) => this.onColorChanged(c, value)"
/>
</div>
<input type="text" class="o-input" t-att-value="value" disabled="1"/>
</div>
</t>

<div class="o-section-subtitle mt-4">Display style</div>
<select class="o-dv-display-style o-input" t-on-change="onChangedDisplayStyle">
<option t-att-selected="props.criterion.displayStyle === 'chip'" value="chip">Chip</option>
<option t-att-selected="props.criterion.displayStyle === 'arrow'" value="arrow">Arrow</option>
<option t-att-selected="props.criterion.displayStyle === 'plainText'" value="plainText">
Plain text
Expand Down
1 change: 1 addition & 0 deletions src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,7 @@ export const GRID_ICON_EDGE_LENGTH = 17;
export const FILTER_ICON_MARGIN = 2;
export const FILTER_ICON_EDGE_LENGTH = 17;
export const FOOTER_HEIGHT = 2 * DEFAULT_CELL_HEIGHT;
export const DATA_VALIDATION_CHIP_MARGIN = 5;

// 768px is a common breakpoint for small screens
// Typically inside Odoo, it is the threshold for switching to mobile view
Expand Down
8 changes: 8 additions & 0 deletions src/helpers/color.ts
Original file line number Diff line number Diff line change
Expand Up @@ -349,10 +349,18 @@ export function darkenColor(color: Color, percentage: number): Color {
if (percentage === 1) {
return "#000";
}
// increase saturation to compensate and make it more vivid
hsla.s = Math.min(100, percentage * hsla.s + hsla.s);
hsla.l = hsla.l - percentage * hsla.l;
return hslaToHex(hsla);
}

export function chipTextColor(chipBackgroundColor: Color): Color {
return relativeLuminance(chipBackgroundColor) < 0.6
? lightenColor(chipBackgroundColor, 0.9)
: darkenColor(chipBackgroundColor, 0.75);
}

const COLORS_SM = [
"#4EA7F2", // Blue
"#EA6175", // Red
Expand Down
2 changes: 1 addition & 1 deletion src/plugins/core/data_validation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,7 @@ export class DataValidationPlugin

return (
(rule.criterion.type === "isValueInList" || rule.criterion.type === "isValueInRange") &&
rule.criterion.displayStyle === "arrow"
(rule.criterion.displayStyle === "arrow" || rule.criterion.displayStyle === "chip")
);
}

Expand Down
Loading