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 352822f
Show file tree
Hide file tree
Showing 14 changed files with 265 additions and 53 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
20 changes: 10 additions & 10 deletions src/helpers/recompute_zones.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { deepEqualsArray } from "../helpers";
import { UnboundedZone } from "../types";
import { UnboundedZone, Zone } from "../types";

/**
* ####################################################
Expand Down Expand Up @@ -128,10 +128,10 @@ import { UnboundedZone } from "../types";
* - you will find coordinate of a cell only once among all the zones
* - the number of zones will be reduced to the minimum
*/
export function recomputeZones(
zones: UnboundedZone[],
zonesToRemove: UnboundedZone[]
): UnboundedZone[] {
export function recomputeZones<T extends Zone | UnboundedZone>(
zones: T[],
zonesToRemove: T[]
): T[] {
const profilesStartingPosition: number[] = [0];
const profiles = new Map<number, number[]>([[0, []]]);

Expand All @@ -140,10 +140,10 @@ export function recomputeZones(
return constructZonesFromProfiles(profilesStartingPosition, profiles);
}

export function modifyProfiles( // export for testing only
export function modifyProfiles<T extends Zone | UnboundedZone>( // export for testing only
profilesStartingPosition: number[],
profiles: Map<number, number[]>,
zones: UnboundedZone[],
zones: T[],
toRemove: boolean = false
) {
for (const zone of zones) {
Expand Down Expand Up @@ -298,10 +298,10 @@ function removeContiguousProfiles(
}
}

function constructZonesFromProfiles(
function constructZonesFromProfiles<T extends Zone | UnboundedZone>(
profilesStartingPosition: number[],
profiles: Map<number, number[]>
): UnboundedZone[] {
): T[] {
const mergedZone: UnboundedZone[] = [];
let pendingZones: UnboundedZone[] = [];
for (let colIndex = 0; colIndex < profilesStartingPosition.length; colIndex++) {
Expand Down Expand Up @@ -353,7 +353,7 @@ function constructZonesFromProfiles(
pendingZones = nextPendingZones;
}
mergedZone.push(...pendingZones);
return mergedZone;
return mergedZone as T[];
}

function binaryPredecessorSearch(arr: number[], val: number, start = 0, matchEqual = true) {
Expand Down
86 changes: 84 additions & 2 deletions src/stores/array_formula_highlight.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,99 @@
import { toRaw } from "@odoo/owl";
import { isInside, recomputeZones, 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();
const zoneToDelete = recomputeZones([target], [originalZone]);
this.model.dispatch("DELETE_CONTENT", { sheetId, target: zoneToDelete });
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 +106,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

0 comments on commit 352822f

Please sign in to comment.