Skip to content
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
4 changes: 2 additions & 2 deletions demo/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -263,13 +263,13 @@ class Demo extends Component {
this.transportService = undefined;
this.stateUpdateMessages = [];
}
this.createModel(data || demoData);
// this.createModel(data || demoData);
// this.createModel(makePivotDataset(10_000));
// this.createModel(makeLargeDataset(26, 10_000, ["numbers"]));
// this.createModel(makeLargeDataset(26, 10_000, ["formulas"]));
// this.createModel(makeLargeDataset(26, 10_000, ["arrayFormulas"]));
// this.createModel(makeLargeDataset(26, 10_000, ["vectorizedFormulas"]));
// this.createModel({});
this.createModel({});
}

createModel(data) {
Expand Down
5 changes: 5 additions & 0 deletions packages/o-spreadsheet-engine/src/helpers/locale.ts
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,11 @@ function changeCFRuleLocale(
case "isLessThan":
case "isLessOrEqualTo":
case "customFormula":
case "dateIs":
case "dateIsBefore":
case "dateIsAfter":
case "dateIsOnOrAfter":
case "dateIsOnOrBefore":
rule.values = rule.values.map((v) => changeContentLocale(v));
return rule;
case "beginsWithText":
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
IconSetRule,
IconThreshold,
} from "../../types/conditional_formatting";
import { EvaluatedCriterion, EvaluatedDateCriterion } from "../../types/generic_criterion";
import { DEFAULT_LOCALE } from "../../types/locale";
import { CellPosition, DataBarFill, HeaderIndex, Lazy, Style, UID, Zone } from "../../types/misc";
import { CoreViewPlugin } from "../core_view_plugin";
Expand Down Expand Up @@ -373,9 +374,10 @@ export class EvaluationConditionalFormatPlugin extends CoreViewPlugin {
return false;
}

const evaluatedCriterion = {
const evaluatedCriterion: EvaluatedCriterion | EvaluatedDateCriterion = {
type: rule.operator,
values: evaluatedCriterionValues.map(toScalar),
dateValue: rule.dateValue || "exactDate",
};
return evaluator.isValueValid(cell.value ?? "", evaluatedCriterion, this.getters, sheetId);
}
Expand Down
12 changes: 12 additions & 0 deletions packages/o-spreadsheet-engine/src/types/conditional_formatting.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { DateCriterionValue } from "./generic_criterion";
import { Style, UID } from "./misc";
import { Range } from "./range";

Expand Down Expand Up @@ -54,6 +55,7 @@ export interface CellIsRule extends SingleColorRule {
operator: ConditionalFormattingOperatorValues;
// can be one value for all operator except between, then it is 2 values
values: string[];
dateValue?: DateCriterionValue;
}
export interface ExpressionRule extends SingleColorRule {
type: "ExpressionRule";
Expand Down Expand Up @@ -169,6 +171,11 @@ export type ConditionalFormattingOperatorValues =
| "isNotBetween"
| "notContainsText"
| "isNotEqual"
| "dateIs"
| "dateIsBefore"
| "dateIsAfter"
| "dateIsOnOrBefore"
| "dateIsOnOrAfter"
| "customFormula";

export const availableConditionalFormatOperators: Set<ConditionalFormattingOperatorValues> =
Expand All @@ -188,4 +195,9 @@ export const availableConditionalFormatOperators: Set<ConditionalFormattingOpera
"isNotEqual",
"isEqual",
"customFormula",
"dateIs",
"dateIsBefore",
"dateIsAfter",
"dateIsOnOrBefore",
"dateIsOnOrAfter",
]);
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { ICON_SETS, IconSetType } from "../../components/icons/icons";
import { parseLiteral } from "../../helpers/cells/cell_evaluation";
import { colorNumberToHex } from "../../helpers/color";
import {
CellIsRule,
Expand All @@ -12,6 +13,7 @@ import {
IconThreshold,
ThresholdType,
} from "../../types/conditional_formatting";
import { DEFAULT_LOCALE } from "../../types/locale";
import { ExcelIconSet, XLSXDxf, XMLAttributes, XMLString } from "../../types/xlsx";
import { XLSX_ICONSET_MAP } from "../constants";
import { toXlsxHexColor } from "../helpers/colors";
Expand Down Expand Up @@ -116,6 +118,50 @@ function cellRuleFormula(ranges: string[], rule: CellIsRule): string[] {
case "isBetween":
case "isNotBetween":
return [values[0], values[1]];
case "dateIs":
switch (rule.dateValue || "exactDate") {
case "exactDate": {
const value = values[0].startsWith("=")
? values[0].slice(1)
: (parseLiteral(values[0], DEFAULT_LOCALE) || "").toString();
const roundedValue = `ROUNDDOWN(${value},0)`;
return [`AND(${firstCell}>=${roundedValue},${firstCell}<${roundedValue}+1)`];
}
case "today":
return [`AND(${firstCell}>=TODAY(),${firstCell}<TODAY()+1)`];
case "yesterday":
return [`AND(${firstCell}>=TODAY()-1,${firstCell}<TODAY())`];
case "tomorrow":
return [`AND(${firstCell}>=TODAY()+1,${firstCell}<TODAY()+2)`];
case "lastWeek":
return [`AND(${firstCell}>=TODAY()-7,${firstCell}<TODAY())`];
case "lastMonth":
return [`AND(${firstCell}>=EDATE(TODAY(),-1),${firstCell}<TODAY())`];
case "lastYear":
return [`AND(${firstCell}>=EDATE(TODAY(),-12),${firstCell}<TODAY())`];
}
case "dateIsBefore":
case "dateIsAfter":
case "dateIsOnOrAfter":
case "dateIsOnOrBefore":
switch (rule.dateValue || "exactDate") {
case "exactDate":
return values[0].startsWith("=")
? [values[0].slice(1)]
: [(parseLiteral(values[0], DEFAULT_LOCALE) || "").toString()];
case "today":
return ["TODAY()"];
case "yesterday":
return ["TODAY()-1"];
case "tomorrow":
return ["TODAY()+1"];
case "lastWeek":
return ["TODAY()-7"];
case "lastMonth":
return ["EDATE(TODAY(),-1)"];
case "lastYear":
return ["EDATE(TODAY(),-12)"];
}
}
}

Expand All @@ -141,7 +187,12 @@ function cellRuleTypeAttributes(rule: CellIsRule): XMLAttributes {
case "isLessOrEqualTo":
case "isBetween":
case "isNotBetween":
case "dateIsBefore":
case "dateIsAfter":
case "dateIsOnOrAfter":
case "dateIsOnOrBefore":
return [["type", "cellIs"]];
case "dateIs":
case "customFormula":
return [["type", "expression"]];
}
Expand Down
10 changes: 10 additions & 0 deletions packages/o-spreadsheet-engine/src/xlsx/helpers/content_helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,16 @@ export function convertOperator(operator: ConditionalFormattingOperatorValues):
return "notEqual";
case "customFormula":
return "";
case "dateIs":
return "";
case "dateIsBefore":
return "lessThan";
case "dateIsAfter":
return "greaterThan";
case "dateIsOnOrAfter":
return "greaterThanOrEqual";
case "dateIsOnOrBefore":
return "lessThanOrEqual";
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import {
ConditionalFormattingOperatorValues,
DataBarRule,
GenericCriterion,
GenericDateCriterion,
IconSetRule,
IconThreshold,
ThresholdType,
Expand Down Expand Up @@ -328,6 +329,9 @@ export class ConditionalFormattingEditor extends Component<Props, SpreadsheetChi

editOperator(operator: ConditionalFormattingOperatorValues) {
this.state.rules.cellIs.operator = operator;
if (operator.includes("date") && !this.state.rules.cellIs.dateValue) {
this.state.rules.cellIs.dateValue = "exactDate";
}
this.updateConditionalFormat({ rule: this.state.rules.cellIs, suppressErrors: true });
this.closeMenus();
}
Expand All @@ -347,16 +351,20 @@ export class ConditionalFormattingEditor extends Component<Props, SpreadsheetChi
return criterionComponentRegistry.get(this.state.rules.cellIs.operator).component;
}

get genericCriterion(): GenericCriterion {
get genericCriterion(): GenericDateCriterion | GenericCriterion {
return {
type: this.state.rules.cellIs.operator,
values: this.state.rules.cellIs.values,
dateValue: this.state.rules.cellIs.dateValue,
};
}

onRuleValuesChanged(rule: CellIsRule) {
this.state.rules.cellIs.values = rule.values;
this.updateConditionalFormat({ rule: { ...this.state.rules.cellIs, values: rule.values } });
this.state.rules.cellIs.dateValue = rule.dateValue;
this.updateConditionalFormat({
rule: { ...this.state.rules.cellIs, values: rule.values, dateValue: rule.dateValue },
});
}

/*****************************************************************************
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { _t } from "@odoo/o-spreadsheet-engine/translation";
import { onWillStart, onWillUpdateProps } from "@odoo/owl";
import { DateCriterionValue, GenericDateCriterion } from "../../../../types";
import { CriterionForm } from "../criterion_form";
import { CriterionInput } from "../criterion_input/criterion_input";
Expand All @@ -18,19 +17,15 @@ export class DateCriterionForm extends CriterionForm<GenericDateCriterion> {
static template = "o-spreadsheet-DataValidationDateCriterion";
static components = { CriterionInput };

setup() {
super.setup();
const setupDefault = (props: this["props"]) => {
if (props.criterion.dateValue === undefined) {
this.updateCriterion({ dateValue: "exactDate" });
}
};
onWillUpdateProps(setupDefault);
onWillStart(() => setupDefault(this.props));
get currentDateValue() {
return this.props.criterion.dateValue || "exactDate";
}

onValueChanged(value: string) {
this.updateCriterion({ values: [value] });
this.updateCriterion({
values: [value],
dateValue: this.currentDateValue,
});
}

onDateValueChanged(ev: Event) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@
t-key="dateValue.value"
t-att-value="dateValue.value"
t-esc="dateValue.title"
t-att-selected="dateValue.value === props.criterion.dateValue"
t-att-selected="dateValue.value === currentDateValue"
/>
</select>

<CriterionInput
t-if="props.criterion.dateValue === 'exactDate'"
t-if="currentDateValue === 'exactDate'"
value="props.criterion.values[0]"
onValueChanged.bind="onValueChanged"
criterionType="props.criterion.type"
Expand Down
23 changes: 23 additions & 0 deletions src/components/spreadsheet/spreadsheet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,29 @@ export class Spreadsheet extends Component<SpreadsheetProps, SpreadsheetChildEnv

const render = batched(this.render.bind(this, true));
onMounted(() => {
// setTimeout(() => {
// if (!this.model.getters.isDashboard()) {
// const a = topbarMenuRegistry
// .get("file")
// .children?.find((menu) => menu.name.toString().includes("dashboard"));
// (a as unknown as Action)?.execute?.(this.env);
// }
// }, 50);

// const sheetId = this.env.model.getters.getActiveSheetId();
// const chartId = this.env.model.getters.getFigures(sheetId)[0]?.id;
// if (chartId) {
// this.env.model.dispatch("SELECT_FIGURE", { id: chartId });
// this.sidePanel.open("ChartPanel");
// setTimeout(() => {
// document.querySelector<HTMLElement>(".o-panel-design")?.click();
// }, 60);
// }

// const pivotId = this.model.getters.getPivotIds()[0];
// if (pivotId) {
// this.sidePanel.open("ConditionalFormatting");
// }
this.checkViewportSize();
stores.on("store-updated", this, render);
resizeObserver.observe(this.spreadsheetRef.el!);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,28 @@ describe("UI of conditional formats", () => {
});
});

test("can edit a date CellIsRule", async () => {
await click(fixture.querySelectorAll(selectors.listPreview)[0]);
await nextTick();

await changeRuleOperatorType(fixture, "dateIs");
expect(".o-composer").toHaveClass("active");
editStandaloneComposer(selectors.ruleEditor.editor.valueInput, "10/10/2025");

await click(fixture, selectors.ruleEditor.editor.bold);
await click(fixture, selectors.buttonSave);

const sheetId = model.getters.getActiveSheetId();
const cf = model.getters.getConditionalFormats(sheetId).find((c) => c.id === "1");
expect(cf?.rule).toEqual({
operator: "dateIs",
dateValue: "exactDate",
style: { bold: true, fillColor: "#FF0000" },
type: "CellIsRule",
values: ["10/10/2025"],
});
});

test("Can cycle on reference (with F4) in a CellIsRule editor input", async () => {
await click(fixture.querySelectorAll(selectors.listPreview)[0]);
await changeRuleOperatorType(fixture, "beginsWithText");
Expand Down
Loading