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
2 changes: 1 addition & 1 deletion src/actions/format_actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -398,7 +398,7 @@ export const clearFormat: ActionSpec = {
name: _t("Clear formatting"),
shortcut: "Ctrl+<",
execute: (env) =>
env.model.dispatch("CLEAR_FORMATTING", {
env.model.dispatch("CLEAR_ALL_STYLING", {
sheetId: env.model.getters.getActiveSheetId(),
target: env.model.getters.getSelectedZones(),
}),
Expand Down
2 changes: 1 addition & 1 deletion src/components/grid/grid.ts
Original file line number Diff line number Diff line change
Expand Up @@ -797,7 +797,7 @@ export class Grid extends Component<SpreadsheetChildEnv> {
}

private clearFormatting() {
this.env.model.dispatch("CLEAR_FORMATTING", {
this.env.model.dispatch("CLEAR_ALL_STYLING", {
sheetId: this.env.model.getters.getActiveSheetId(),
target: this.env.model.getters.getSelectedZones(),
});
Expand Down
70 changes: 68 additions & 2 deletions src/plugins/ui_feature/ui_sheet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ import {
import { isColorValid } from "../../helpers/color";
import { formatValue } from "../../helpers/format/format";
import { localizeFormula } from "../../helpers/locale";
import { groupConsecutive, largeMax, range } from "../../helpers/misc";
import { deepCopy, groupConsecutive, largeMax, range } from "../../helpers/misc";
import { recomputeZones } from "../../helpers/recompute_zones";
import {
computeMultilineTextSize,
computeTextLinesHeight,
Expand All @@ -17,7 +18,7 @@ import {
getCellContentHeight,
splitTextToWidth,
} from "../../helpers/text_helper";
import { isEqual, positions } from "../../helpers/zones";
import { isEqual, isZoneInside, overlap, positions } from "../../helpers/zones";
import { CellValueType } from "../../types/cells";
import { Command, CommandResult, LocalCommand } from "../../types/commands";
import {
Expand Down Expand Up @@ -96,6 +97,9 @@ export class SheetUIPlugin extends UIPlugin {
});
this.dispatch("SET_SHEET_BACKGROUND_COLOR", { sheetId: cmd.sheetId, color: cmd.color });
break;
case "CLEAR_ALL_STYLING":
this.clearAllStyling(cmd.sheetId, cmd.target);
break;
}
}

Expand Down Expand Up @@ -349,4 +353,66 @@ export class SheetUIPlugin extends UIPlugin {
});
}
}

private clearAllStyling(sheetId: UID, target: Zone[]) {
const tables = this.getters.getTables(sheetId);
for (const table of tables) {
if (target.some((zone) => isZoneInside(table.range.zone, zone))) {
if (table.isPivotTable) {
const position = { sheetId, col: table.range.zone.left, row: table.range.zone.top };
const pivotId = this.getters.getPivotIdFromPosition(position);
if (pivotId) {
const definition = deepCopy(this.getters.getPivotCoreDefinition(pivotId));
definition.style = { ...definition.style, tableStyleId: "None" };
this.dispatch("UPDATE_PIVOT", { pivotId, pivot: definition });
}
} else {
this.dispatch("UPDATE_TABLE", {
sheetId,
zone: table.range.zone,
config: { ...table.config, styleId: "None" },
});
}
}
}

this.dispatch("CLEAR_FORMATTING", { sheetId, target });

const merges = this.getters.getMerges(sheetId);
const mergesInsideTarget = merges.filter((merge) =>
target.some((zone) => isZoneInside(merge, zone))
);
if (mergesInsideTarget.length > 0) {
this.dispatch("REMOVE_MERGE", { sheetId, target: mergesInsideTarget });
}

const conditionalFormats = this.getters.getConditionalFormats(sheetId);
for (const cf of conditionalFormats) {
const cfRanges = cf.ranges.map((range) => this.getters.getRangeFromSheetXC(sheetId, range));
const hasOverlap = target.some((zone) =>
cfRanges
.map((range) => range.zone)
.some((cfZone) => isZoneInside(cfZone, zone) || overlap(cfZone, zone))
);
if (!hasOverlap) {
continue;
}

// Make the target ranges unbounded (if they take the whole sheet) so unbounded CF zones are properly cropped
const unboundedTarget = target.map((zone) => this.getters.getUnboundedZone(sheetId, zone));
const newZones = recomputeZones(
cfRanges.map((range) => range.unboundedZone),
unboundedTarget
);
if (newZones.length === 0) {
this.dispatch("REMOVE_CONDITIONAL_FORMAT", { sheetId, id: cf.id });
} else {
this.dispatch("ADD_CONDITIONAL_FORMAT", {
sheetId,
cf,
ranges: newZones.map((zone) => this.getters.getRangeDataFromZone(sheetId, zone)),
});
}
}
}
}
1 change: 1 addition & 0 deletions src/registries/repeat_commands_registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ repeatCommandTransformRegistry.add("REMOVE_MERGE", genericRepeat);

repeatCommandTransformRegistry.add("SET_FORMATTING", genericRepeat);
repeatCommandTransformRegistry.add("CLEAR_FORMATTING", genericRepeat);
repeatCommandTransformRegistry.add("CLEAR_ALL_STYLING", genericRepeat);
repeatCommandTransformRegistry.add("SET_BORDER", genericRepeat);

repeatCommandTransformRegistry.add("CREATE_TABLE", genericRepeat);
Expand Down
5 changes: 5 additions & 0 deletions src/types/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -792,6 +792,10 @@ export interface ClearFormattingCommand extends TargetDependentCommand {
type: "CLEAR_FORMATTING";
}

export interface ClearAllStylingCommand extends TargetDependentCommand {
type: "CLEAR_ALL_STYLING";
}

export interface SetDecimalCommand extends TargetDependentCommand {
type: "SET_DECIMAL";
step: SetDecimalStep;
Expand Down Expand Up @@ -1392,6 +1396,7 @@ export type LocalCommand =
| DeleteFiguresCommand
| MergeIntoCarouselCommand
| CreateChartAndMergeIntoCarouselCommand
| ClearAllStylingCommand
| ColorAllCellsBackground;

export type Command = CoreCommand | LocalCommand;
Expand Down
1 change: 1 addition & 0 deletions tests/repeat_commands_plugin.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ describe("Repeat commands basics", () => {
"ADD_MERGE",
"REMOVE_MERGE",
"SET_FORMATTING",
"CLEAR_ALL_STYLING",
"CLEAR_FORMATTING",
"SET_BORDER",
"CREATE_TABLE",
Expand Down
99 changes: 99 additions & 0 deletions tests/sheet/ui_sheet.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import { Model, SpreadsheetPivotCoreDefinition, UID } from "../../src";
import { zoneToXc } from "../../src/helpers/zones";
import {
addEqualCf,
clearAllStyling,
createTable,
merge,
setBordersOnTarget,
setCellContent,
setFormatting,
} from "../test_helpers";
import { createModelFromGrid, toCellPosition } from "../test_helpers/helpers";
import { addPivot } from "../test_helpers/pivot_helpers";

let model: Model;
let sheetId: UID;

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

describe("CLEAR_ALL_STYLING command", () => {
test("Can clear the cell style", () => {
setFormatting(model, "A1:A3", { bold: true });
setBordersOnTarget(model, ["A1:A3"], { bottom: { color: "#ff0000", style: "thin" } });

clearAllStyling(model, "A1:A2");
expect(model.getters.getCellStyle(toCellPosition(sheetId, "A1"))).toEqual({});
expect(model.getters.getCellStyle(toCellPosition(sheetId, "A2"))).toEqual({});
expect(model.getters.getCellStyle(toCellPosition(sheetId, "A3"))).toEqual({ bold: true });

expect(model.getters.getCellBorder(toCellPosition(sheetId, "A1"))).toEqual(null);
expect(model.getters.getCellBorder(toCellPosition(sheetId, "A2"))).toEqual(null);
expect(model.getters.getCellBorder(toCellPosition(sheetId, "A3"))).toEqual({
bottom: { color: "#ff0000", style: "thin" },
});
});

test("Clear merges fully inside the target", () => {
merge(model, "A1:B2");
merge(model, "C1:D2");

clearAllStyling(model, "A1:C2");
expect(model.getters.getMerges(sheetId).map(zoneToXc)).toEqual(["C1:D2"]);
});

test("Clear table style for tables fully inside the target", () => {
createTable(model, "A1:B2", { styleId: "TableStyleLight1" });
createTable(model, "C1:D2", { styleId: "TableStyleLight2" });

clearAllStyling(model, "A1:C2");
expect(model.getters.getTables(sheetId)).toMatchObject([
{ config: { styleId: "None" } },
{ config: { styleId: "TableStyleLight2" } },
]);
});

test("Clear pivot table style for pivot tables fully inside the target", () => {
const grid = { A1: "Customer", B1: "Price", A2: "Alice", B2: "10" };
const model = createModelFromGrid(grid);
const pivotDefinition: Partial<SpreadsheetPivotCoreDefinition> = {
columns: [],
rows: [{ fieldName: "Customer" }],
measures: [{ id: "Price:sum", fieldName: "Price", aggregator: "sum" }],
style: { tableStyleId: "PivotTableStyleMedium9" },
};
addPivot(model, "A1:B2", pivotDefinition, "pivot1");
addPivot(model, "A1:B2", pivotDefinition, "pivot2");

setCellContent(model, "A4", "=PIVOT(1)");
setCellContent(model, "D4", "=PIVOT(2)");

clearAllStyling(model, "A1:D10");
expect(model.getters.getPivotCoreDefinition("pivot1")?.style?.tableStyleId).toEqual("None");
expect(model.getters.getPivotCoreDefinition("pivot2")?.style?.tableStyleId).toEqual(
"PivotTableStyleMedium9"
);
});

test("Clear conditional formats", () => {
addEqualCf(model, "A1:A7", { fillColor: "#FF0000" }, "5", "cf1");
addEqualCf(model, "C2:C3", { fillColor: "#FF0000" }, "5", "cf2");

clearAllStyling(model, "A2:C3");
expect(model.getters.getConditionalFormats(sheetId).map((cf) => cf.id)).toEqual(["cf1"]);
expect(model.getters.getConditionalFormats(sheetId)[0].ranges).toEqual(["A1", "A4:A7"]);
});

test("Can clear conditional formats with unbounded ranges", () => {
addEqualCf(model, "A2:A", { fillColor: "#FF0000" }, "5", "cf1");

clearAllStyling(model, "A4:A5");
expect(model.getters.getConditionalFormats(sheetId)[0].ranges).toEqual(["A2:A3", "A6:A"]);

clearAllStyling(model, "A1:A100"); // whole column
expect(model.getters.getConditionalFormats(sheetId)).toHaveLength(0);
});
});
8 changes: 8 additions & 0 deletions tests/test_helpers/commands_helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1332,6 +1332,14 @@ export function clearFormatting(
return model.dispatch("CLEAR_FORMATTING", { sheetId, target: target(targetXc) });
}

export function clearAllStyling(
model: Model,
targetXc: string,
sheetId: UID = model.getters.getActiveSheetId()
) {
return model.dispatch("CLEAR_ALL_STYLING", { sheetId, target: target(targetXc) });
}

/**
* Freeze a given number of rows on top of the sheet
*/
Expand Down