Skip to content

Commit

Permalink
[IMP] highlight: add drag & drop to array formula highlight
Browse files Browse the repository at this point in the history
With this commit, it is now possible to drag & drop an array formula
by clicking on its highlight.

Task 3869873
  • Loading branch information
hokolomopo committed Apr 16, 2024
1 parent 6a991b0 commit 31b24ba
Show file tree
Hide file tree
Showing 11 changed files with 200 additions and 36 deletions.
3 changes: 2 additions & 1 deletion src/components/composer/composer/composer_store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -743,7 +743,8 @@ export class ComposerStore extends SpreadsheetStore {
zone,
color: rangeColor(rangeString),
sheetId: range.sheetId,
interactive: true,
movable: true,
resizable: true,
};
});
}
Expand Down
8 changes: 6 additions & 2 deletions src/components/grid/grid.ts
Original file line number Diff line number Diff line change
Expand Up @@ -192,8 +192,12 @@ export class Grid extends Component<Props, SpreadsheetChildEnv> {
this.hoveredCell.hover({ col, row });
}

get highlights() {
return this.highlightStore.highlights;
get interactiveHighlight() {
const activeSheetId = this.env.model.getters.getActiveSheetId();
return this.highlightStore.highlights.filter(
(highlight) =>
highlight.sheetId === activeSheetId && (highlight.resizable || highlight.movable)
);
}

get gridOverlayDimensions() {
Expand Down
7 changes: 2 additions & 5 deletions src/components/grid/grid.xml
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,8 @@
<t t-if="env.model.getters.isGridSelectionActive()">
<Autofill position="getAutofillPosition()" isVisible="isAutofillVisible"/>
</t>
<t t-foreach="highlights" t-as="highlight" t-key="highlight_index">
<t
t-if="highlight.interactive and highlight.sheetId === env.model.getters.getActiveSheetId()">
<HighlightOverlay zone="highlight.zone" color="highlight.color"/>
</t>
<t t-foreach="interactiveHighlight" t-as="highlight" t-key="highlight_index">
<HighlightOverlay highlight="highlight"/>
</t>
<Menu
t-if="menuState.isOpen"
Expand Down
30 changes: 21 additions & 9 deletions src/components/highlight/highlight_overlay/highlight_overlay.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Component, useState } from "@odoo/owl";
import { ComponentsImportance } from "../../../constants";
import { clip, isEqual } from "../../../helpers";
import { Color, HeaderIndex, Pixel, SpreadsheetChildEnv, Zone } from "../../../types";
import { HeaderIndex, Highlight, Pixel, SpreadsheetChildEnv, Zone } from "../../../types";
import { css } from "../../helpers/css";
import { gridOverlayPosition } from "../../helpers/dom_helpers";
import { dragAndDropBeyondTheViewport } from "../../helpers/drag_and_drop";
Expand All @@ -15,19 +15,15 @@ css/*SCSS*/ `
`;

interface Props {
zone: Zone;
color: Color;
highlight: Highlight;
}

interface HighlightState {
shiftingMode: "isMoving" | "isResizing" | "none";
}
export class HighlightOverlay extends Component<Props, SpreadsheetChildEnv> {
static template = "o-spreadsheet-HighlightOverlay";
static props = {
zone: Object,
color: String,
};
static props = { highlight: Object };
static components = {
Corner,
Border,
Expand All @@ -38,9 +34,12 @@ export class HighlightOverlay extends Component<Props, SpreadsheetChildEnv> {
});

onResizeHighlight(isLeft: boolean, isTop: boolean) {
if (!this.isResizable) {
return;
}
const activeSheetId = this.env.model.getters.getActiveSheetId();
this.highlightState.shiftingMode = "isResizing";
const z = this.props.zone;
const z = this.props.highlight.zone;

const pivotCol = isLeft ? z.right : z.left;
const pivotRow = isTop ? z.bottom : z.top;
Expand Down Expand Up @@ -85,14 +84,18 @@ export class HighlightOverlay extends Component<Props, SpreadsheetChildEnv> {

const mouseUp = () => {
this.highlightState.shiftingMode = "none";
this.env.model.dispatch("STOP_CHANGE_HIGHLIGHT");
};

dragAndDropBeyondTheViewport(this.env, mouseMove, mouseUp);
}

onMoveHighlight(clientX: Pixel, clientY: Pixel) {
if (!this.isMovable) {
return;
}
this.highlightState.shiftingMode = "isMoving";
const z = this.props.zone;
const z = this.props.highlight.zone;

const position = gridOverlayPosition();
const activeSheetId = this.env.model.getters.getActiveSheetId();
Expand Down Expand Up @@ -141,8 +144,17 @@ export class HighlightOverlay extends Component<Props, SpreadsheetChildEnv> {

const mouseUp = () => {
this.highlightState.shiftingMode = "none";
this.env.model.dispatch("STOP_CHANGE_HIGHLIGHT");
};

dragAndDropBeyondTheViewport(this.env, mouseMove, mouseUp);
}

get isResizable() {
return this.props.highlight.resizable;
}

get isMovable() {
return this.props.highlight.movable;
}
}
14 changes: 9 additions & 5 deletions src/components/highlight/highlight_overlay/highlight_overlay.xml
Original file line number Diff line number Diff line change
@@ -1,21 +1,25 @@
<templates>
<t t-name="o-spreadsheet-HighlightOverlay">
<div class="o-highlight" t-ref="highlight">
<t t-foreach="['n', 's', 'w', 'e']" t-as="orientation" t-key="orientation">
<t t-if="isMovable" t-foreach="['n', 's', 'w', 'e']" t-as="orientation" t-key="orientation">
<Border
onMoveHighlight="(x, y) => this.onMoveHighlight(x,y)"
isMoving='highlightState.shiftingMode === "isMoving"'
orientation="orientation"
zone="props.zone"
zone="props.highlight.zone"
/>
</t>
<t t-foreach="['nw', 'ne', 'sw', 'se']" t-as="orientation" t-key="orientation">
<t
t-if="isResizable"
t-foreach="['nw', 'ne', 'sw', 'se']"
t-as="orientation"
t-key="orientation">
<Corner
onResizeHighlight="(isLeft, isTop) => this.onResizeHighlight(isLeft, isTop)"
isResizing='highlightState.shiftingMode === "isResizing"'
orientation="orientation"
zone="props.zone"
color="props.color"
zone="props.highlight.zone"
color="props.highlight.color"
/>
</t>
</div>
Expand Down
3 changes: 2 additions & 1 deletion src/components/selection_input/selection_input_store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -340,7 +340,8 @@ export class SelectionInputStore extends SpreadsheetStore {
zone: this.getters.getRangeFromSheetXC(this.inputSheetId, xc).zone,
sheetId: (sheetName && this.getters.getSheetIdByName(sheetName)) || this.inputSheetId,
color,
interactive: true,
movable: true,
resizable: true,
};
});
}
Expand Down
85 changes: 83 additions & 2 deletions src/stores/array_formula_highlight.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,98 @@
import { toRaw } from "@odoo/owl";
import { isInside, withOneHistoryStep } from "../helpers";
import { Get } from "../store_engine";
import { Highlight, Zone } from "../types";
import { _t } from "../translation";
import { Command, Highlight, Zone } from "../types";
import { SelectionEvent } from "../types/event_stream";
import { HighlightStore } from "./highlight_store";
import { NotificationStore } from "./notification_store";
import { SpreadsheetStore } from "./spreadsheet_store";

export class ArrayFormulaHighlight extends SpreadsheetStore {
protected highlightStore = this.get(HighlightStore);
private notification = this.get(NotificationStore);
private draggedHighlightZone?: Zone;

constructor(get: Get) {
super(get);
this.highlightStore.register(this);
}

protected handle(cmd: Command): void {
switch (cmd.type) {
case "START_CHANGE_HIGHLIGHT":
const zone = this.getHighlightZone();
if (zone && this.getters.isGridSelectionActive()) {
this.model.selection.capture(
toRaw(this),
{ cell: { col: zone.left, row: zone.top }, zone },
{ handleEvent: this.handleEvent.bind(this) }
);
}
break;
case "STOP_CHANGE_HIGHLIGHT": {
const target = this.draggedHighlightZone;
if (!this.model.selection.isListening(toRaw(this)) || !target) {
return;
}
this.model.selection.release(toRaw(this));
const originalZone = this.getHighlightZone();
if (!originalZone) {
return;
}

if (!this.isZoneEmpty(originalZone, target)) {
this.notification.askConfirmation(
_t("This will overwrite the content of some cells. Move the formula anyway ?"),
() => {
withOneHistoryStep(this.model, () => {
const sheetId = this.model.getters.getActiveSheetId();
this.model.dispatch("DELETE_CONTENT", { sheetId, target: [target] });
this.moveArrayFormula(originalZone, target);
});
}
);
} else {
this.moveArrayFormula(originalZone, target);
}

this.draggedHighlightZone = undefined;
}
}
}

private handleEvent(event: SelectionEvent) {
this.draggedHighlightZone = event.anchor.zone;
}

private moveArrayFormula(originalZone: Zone, target: Zone) {
this.model.selection.selectCell(originalZone.left, originalZone.top);
this.model.dispatch("CUT");
this.model.dispatch("PASTE", { target: [target] });
this.model.selection.selectZone({
cell: { row: target.top, col: target.left },
zone: target,
});
}

/** Check if the target zone is empty, ignoring the overlap with the original zone */
private isZoneEmpty(originalZone: Zone, target: Zone) {
const sheetId = this.getters.getActiveSheetId();
for (let row = target.top; row <= target.bottom; row++) {
for (let col = target.left; col <= target.right; col++) {
if (isInside(col, row, originalZone)) {
continue;
}
if (this.model.getters.getCell({ sheetId, col, row })?.content) {
return false;
}
}
}
return true;
}

get highlights(): Highlight[] {
const zone = this.getHighlightZone();
const zone = this.draggedHighlightZone || this.getHighlightZone();
if (!zone) {
return [];
}
Expand All @@ -25,6 +105,7 @@ export class ArrayFormulaHighlight extends SpreadsheetStore {
color: "#17A2B8",
noFill: true,
thinLine: true,
movable: true,
},
];
}
Expand Down
5 changes: 5 additions & 0 deletions src/types/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -714,6 +714,10 @@ export interface StartChangeHighlightCommand {
zone: Zone;
}

export interface StopChangeHighlightCommand {
type: "STOP_CHANGE_HIGHLIGHT";
}

export interface ShowFormulaCommand {
type: "SET_FORMULA_VISIBILITY";
show: boolean;
Expand Down Expand Up @@ -969,6 +973,7 @@ export type LocalCommand =
| ActivateSheetCommand
| EvaluateCellsCommand
| StartChangeHighlightCommand
| StopChangeHighlightCommand
| StartCommand
| AutofillCommand
| AutofillSelectCommand
Expand Down
3 changes: 2 additions & 1 deletion src/types/misc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,8 @@ export interface Highlight {
zone: Zone;
sheetId: UID;
color: Color;
interactive?: boolean;
movable?: boolean;
resizable?: boolean;
thinLine?: boolean;
noFill?: boolean;
/** transparency of the fill color (0-1) */
Expand Down
41 changes: 39 additions & 2 deletions tests/grid/array_formula_highlights_store.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import { toZone } from "../../src/helpers";
import { Model } from "../../src";
import { DEFAULT_CELL_HEIGHT, DEFAULT_CELL_WIDTH } from "../../src/constants";
import { toZone, zoneToXc } from "../../src/helpers";
import { ArrayFormulaHighlight } from "../../src/stores/array_formula_highlight";
import { selectCell, setCellContent } from "../test_helpers/commands_helpers";
import { getHighlightsFromStore } from "../test_helpers/helpers";
import { dragElement } from "../test_helpers/dom_helper";
import { getCell, getCellContent } from "../test_helpers/getters_helpers";
import { getHighlightsFromStore, mountSpreadsheet } from "../test_helpers/helpers";
import { makeStore } from "../test_helpers/stores";

describe("array function highlights", () => {
Expand All @@ -15,6 +19,7 @@ describe("array function highlights", () => {
color: "#17A2B8",
noFill: true,
thinLine: true,
movable: true,
};
selectCell(model, "A2"); // main cell
expect(getHighlightsFromStore(container)).toEqual([highlight]);
Expand All @@ -29,3 +34,35 @@ describe("array function highlights", () => {
expect(getHighlightsFromStore(container)).toEqual([]);
});
});

describe("Drag & drop array formula highlight", () => {
test("Can drag & drop the array formula highlight", async () => {
const model = new Model();
setCellContent(model, "A1", "=MUNIT(3)");
const { fixture } = await mountSpreadsheet({ model });

const el = fixture.querySelector(".o-border-w")!;
const dragOffset = { x: DEFAULT_CELL_WIDTH * 2, y: DEFAULT_CELL_HEIGHT * 2 };
await dragElement(el, dragOffset, undefined, true);

expect(getCell(model, "A1")?.content).toEqual(undefined);
expect(getCell(model, "C3")?.content).toEqual("=MUNIT(3)");
expect(zoneToXc(model.getters.getSelectedZone())).toEqual("C3:E5");
});

test("Drag & drop formula on existing cells will give a warning", async () => {
const askConfirmation = jest.fn((_, confirm) => confirm());
const model = new Model();
setCellContent(model, "A1", "=MUNIT(3)");
setCellContent(model, "D4", "text");
const { fixture } = await mountSpreadsheet({ model }, { askConfirmation });

const el = fixture.querySelector(".o-border-w")!;
const dragOffset = { x: DEFAULT_CELL_WIDTH, y: DEFAULT_CELL_HEIGHT };
await dragElement(el, dragOffset, undefined, true);

expect(askConfirmation).toHaveBeenCalled();
expect(getCell(model, "B2")?.content).toEqual("=MUNIT(3)");
expect(getCellContent(model, "D4")).toEqual("1");
});
});

0 comments on commit 31b24ba

Please sign in to comment.