Skip to content

Commit

Permalink
[ADD] Paste toolbox: a post-paste correction tool
Browse files Browse the repository at this point in the history
After pasting, a user might want to change the type of pasting they do:
- only paste the value
- only paste the format
- ...

This revision adds a 'paste toolbox' to allow the user to alter the type
of paste they want to do.

Task: 4385451
  • Loading branch information
rrahir committed Dec 6, 2024
1 parent 7b1c39b commit 88a4d7c
Show file tree
Hide file tree
Showing 13 changed files with 401 additions and 33 deletions.
39 changes: 17 additions & 22 deletions src/components/autofill/autofill.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Component, useState, xml } from "@odoo/owl";
import { AUTOFILL_EDGE_LENGTH } from "../../constants";
import { clip } from "../../helpers";
import { HeaderIndex, SpreadsheetChildEnv } from "../../types";
import { DOMCoordinates, HeaderIndex, SpreadsheetChildEnv } from "../../types";
import { css, cssPropertiesToCss } from "../helpers/css";
import { dragAndDropBeyondTheViewport } from "../helpers/drag_and_drop";

Expand Down Expand Up @@ -41,16 +41,11 @@ css/* scss */ `

interface Props {
isVisible: boolean;
position: Position;
}

interface Position {
top: HeaderIndex;
left: HeaderIndex;
position: DOMCoordinates;
}

interface State {
position: Position;
position: DOMCoordinates;
handler: boolean;
}

Expand All @@ -61,31 +56,31 @@ export class Autofill extends Component<Props, SpreadsheetChildEnv> {
isVisible: Boolean,
};
state: State = useState({
position: { left: 0, top: 0 },
position: { x: 0, y: 0 },
handler: false,
});

get style() {
const { left, top } = this.props.position;
const { x, y } = this.props.position;
return cssPropertiesToCss({
top: `${top}px`,
left: `${left}px`,
top: `${y}px`,
left: `${x}px`,
visibility: this.props.isVisible ? "visible" : "hidden",
});
}
get handlerStyle() {
const { left, top } = this.state.handler ? this.state.position : this.props.position;
const { x, y } = this.state.handler ? this.state.position : this.props.position;
return cssPropertiesToCss({
top: `${top}px`,
left: `${left}px`,
top: `${y}px`,
left: `${x}px`,
});
}

get styleNextValue() {
const { left, top } = this.state.position;
const { x, y } = this.state.position;
return cssPropertiesToCss({
top: `${top + 5}px`,
left: `${left + 15}px`,
top: `${y + 5}px`,
left: `${x + 15}px`,
});
}

Expand All @@ -103,8 +98,8 @@ export class Autofill extends Component<Props, SpreadsheetChildEnv> {
let lastCol: HeaderIndex | undefined;
let lastRow: HeaderIndex | undefined;
const start = {
left: ev.clientX - this.props.position.left,
top: ev.clientY - this.props.position.top,
left: ev.clientX - this.props.position.x,
top: ev.clientY - this.props.position.y,
};
const onMouseUp = () => {
this.state.handler = false;
Expand All @@ -114,8 +109,8 @@ export class Autofill extends Component<Props, SpreadsheetChildEnv> {

const onMouseMove = (col: HeaderIndex, row: HeaderIndex, ev: MouseEvent) => {
this.state.position = {
left: ev.clientX - start.left,
top: ev.clientY - start.top,
x: ev.clientX - start.left,
y: ev.clientY - start.top,
};
if (lastCol !== col || lastRow !== row) {
const activeSheetId = this.env.model.getters.getActiveSheetId();
Expand Down
22 changes: 18 additions & 4 deletions src/components/grid/grid.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ import { useWheelHandler } from "../helpers/wheel_hook";
import { Highlight } from "../highlight/highlight/highlight";
import { Menu, MenuState } from "../menu/menu";
import { PaintFormatStore } from "../paint_format_button/paint_format_store";
import { PasteToolbox } from "../paste_toolbox/paste_toolbox";
import { CellPopoverStore } from "../popover";
import { Popover } from "../popover/popover";
import { HorizontalScrollBar, VerticalScrollBar } from "../scrollbar/";
Expand Down Expand Up @@ -126,6 +127,7 @@ export class Grid extends Component<Props, SpreadsheetChildEnv> {
HeadersOverlay,
Menu,
Autofill,
PasteToolbox,
ClientTag,
Highlight,
Popover,
Expand Down Expand Up @@ -251,7 +253,7 @@ export class Grid extends Component<Props, SpreadsheetChildEnv> {
} else if (this.paintFormatStore.isActive) {
this.paintFormatStore.cancel();
} else {
this.env.model.dispatch("CLEAN_CLIPBOARD_HIGHLIGHT");
this.env.model.dispatch("CLEAR_CLIPBOARD_HIGHLIGHT");
}
},
"Ctrl+A": () => this.env.model.selection.loopSelection(),
Expand Down Expand Up @@ -419,12 +421,20 @@ export class Grid extends Component<Props, SpreadsheetChildEnv> {
return this.gridRef.el;
}

getAutofillPosition() {
getBottomSelectionPosition(): DOMCoordinates {
const zone = this.env.model.getters.getSelectedZone();
const rect = this.env.model.getters.getVisibleRect(zone);
return {
left: rect.x + rect.width - AUTOFILL_EDGE_LENGTH / 2,
top: rect.y + rect.height - AUTOFILL_EDGE_LENGTH / 2,
x: rect.x + rect.width,
y: rect.y + rect.height,
};
}

getAutofillPosition(): DOMCoordinates {
const { x, y } = this.getBottomSelectionPosition();
return {
x: x - AUTOFILL_EDGE_LENGTH / 2,
y: y - AUTOFILL_EDGE_LENGTH / 2,
};
}

Expand All @@ -439,6 +449,10 @@ export class Grid extends Component<Props, SpreadsheetChildEnv> {
return !(rect.width === 0 || rect.height === 0);
}

get isPasteToolboxVisible(): boolean {
return this.env.model.getters.canModifyPaste();
}

onGridResized({ height, width }: DOMDimension) {
this.env.model.dispatch("RESIZE_SHEETVIEW", {
width: width,
Expand Down
5 changes: 5 additions & 0 deletions src/components/grid/grid.xml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,11 @@
/>
<t t-if="env.model.getters.isGridSelectionActive()">
<Autofill position="getAutofillPosition()" isVisible="isAutofillVisible"/>
<PasteToolbox
t-if="isPasteToolboxVisible"
position="getBottomSelectionPosition()"
onClosed.bind="focusDefaultElement"
/>
</t>
<t t-foreach="highlights" t-as="highlight" t-key="highlight_index">
<t
Expand Down
96 changes: 96 additions & 0 deletions src/components/paste_toolbox/paste_toolbox.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import { Component, useRef, useState } from "@odoo/owl";
import { createActions } from "../../actions/action";
import { paste, pasteSpecialFormat, pasteSpecialValue } from "../../actions/edit_actions";
import { ComponentsImportance, ICON_EDGE_LENGTH } from "../../constants";
import { DOMCoordinates, SpreadsheetChildEnv } from "../../types";
import { css, cssPropertiesToCss } from "../helpers/css";
import { Menu, MenuState } from "../menu/menu";

css/* scss */ `
.o-paste {
position: absolute;
height: ${ICON_EDGE_LENGTH + 10}px;
border: 1px solid lightgrey;
box-sizing: border-box !important;
background-color: white;
cursor: pointer;
z-index: ${ComponentsImportance.Clipboard};
align-content: center;
.o-icon {
margin: auto;
}
}
`;

interface Props {
position: DOMCoordinates;
onClosed: () => void;
}

export class PasteToolbox extends Component<Props, SpreadsheetChildEnv> {
static template = "o-spreadsheet-PasteToolbox";
static props = {
position: Object,
onClosed: Function,
};
static components = { Menu };

private menuState: MenuState = useState({
isOpen: false,
position: { x: 0, y: 0 },
menuItems: [],
});

private toolboxRef = useRef("toolboxButton");

get style() {
const { x, y } = this.props.position;
return cssPropertiesToCss({
left: `${x}px`,
top: `${y + 5}px`,
});
}

showMenu() {
this.menuState.isOpen = true;
const { x, y } = this.toolboxRef.el!.getBoundingClientRect();
this.menuState.menuItems = this.getMenuItems();
this.menuState.position = { x, y };
}

onMenuClosed() {
this.menuState.isOpen = false;
this.props.onClosed();
}

private getMenuItems() {
return createActions([
{
...paste,
icon: undefined,
id: "paste",
execute: (env) => {
env.model.dispatch("REQUEST_UNDO");
paste.execute?.(this.env);
},
},
{
name: pasteSpecialValue.name,
id: "paste_special_value",
execute: (env) => {
env.model.dispatch("REQUEST_UNDO");
pasteSpecialValue.execute?.(env);
},
},
{
name: pasteSpecialFormat.name,
id: "paste_special_format",
execute: (env) => {
env.model.dispatch("REQUEST_UNDO");
pasteSpecialFormat.execute?.(env);
},
},
]);
}
}
18 changes: 18 additions & 0 deletions src/components/paste_toolbox/paste_toolbox.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<templates>
<t t-name="o-spreadsheet-PasteToolbox">
<div
t-ref="toolboxButton"
class="o-paste d-flex ps-1"
t-att-style="style"
t-on-click="showMenu">
<t t-call="o-spreadsheet-Icon.PASTE"/>
<t t-call="o-spreadsheet-Icon.CARET_DOWN"/>
</div>
<Menu
t-if="menuState.isOpen"
position="menuState.position"
menuItems="menuState.menuItems"
onClose.bind="onMenuClosed"
/>
</t>
</templates>
1 change: 1 addition & 0 deletions src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,7 @@ export const LOADING = "Loading...";
export enum ComponentsImportance {
Grid = 0,
Highlight = 5,
Clipboard = 6,
HeaderGroupingButton = 6,
Figure = 10,
ScrollBar = 15,
Expand Down
32 changes: 30 additions & 2 deletions src/plugins/ui_stateful/clipboard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import {
isCoreCommand,
} from "../../types/index";
import { xmlEscape } from "../../xlsx/helpers/xml_helpers";
import { UIPlugin } from "../ui_plugin";
import { UIPlugin, UIPluginConfig } from "../ui_plugin";

interface InsertDeleteCellsTargets {
cut: Zone[];
Expand Down Expand Up @@ -57,14 +57,27 @@ export class ClipboardPlugin extends UIPlugin {
"getClipboardId",
"getClipboardTextContent",
"isCutOperation",
"canModifyPaste",
] as const;

private status: "visible" | "invisible" = "invisible";
private originSheetId?: UID;
private copiedData?: MinimalClipboardData;
private _isCutOperation: boolean = false;
private recentlyPasted: boolean = false;
private clipboardId = new UuidGenerator().uuidv4();

constructor(config: UIPluginConfig) {
super(config);
this.selection.observe(this, {
handleEvent: this.handleEvent.bind(this),
});
}

handleEvent() {
this.recentlyPasted = false;
}

// ---------------------------------------------------------------------------
// Command Handling
// ---------------------------------------------------------------------------
Expand Down Expand Up @@ -118,6 +131,9 @@ export class ClipboardPlugin extends UIPlugin {
}

handle(cmd: Command) {
if (isCoreCommand(cmd)) {
this.recentlyPasted = false;
}
switch (cmd.type) {
case "COPY":
case "CUT":
Expand All @@ -126,6 +142,7 @@ export class ClipboardPlugin extends UIPlugin {
this.originSheetId = this.getters.getActiveSheetId();
this.copiedData = this.copy(zones);
this._isCutOperation = cmd.type === "CUT";
this.recentlyPasted = false;
break;
case "PASTE_FROM_OS_CLIPBOARD": {
this._isCutOperation = false;
Expand All @@ -141,6 +158,7 @@ export class ClipboardPlugin extends UIPlugin {
isCutOperation: false,
});
this.status = "invisible";
this.recentlyPasted = true;
break;
}
case "PASTE": {
Expand All @@ -154,6 +172,9 @@ export class ClipboardPlugin extends UIPlugin {
if (this._isCutOperation) {
this.copiedData = undefined;
this._isCutOperation = false;
this.recentlyPasted = false;
} else {
this.recentlyPasted = true;
}
break;
}
Expand Down Expand Up @@ -191,9 +212,12 @@ export class ClipboardPlugin extends UIPlugin {
});
}
break;
case "CLEAN_CLIPBOARD_HIGHLIGHT":
case "CLEAR_CLIPBOARD_HIGHLIGHT":
this.status = "invisible";
break;
case "CLEAR_PASTE_HANDLER":
this.recentlyPasted = false;
break;
case "DELETE_CELL": {
const { cut, paste } = this.getDeleteCellsTargets(cmd.zone, cmd.shiftDimension);
if (!isZoneValid(cut[0])) {
Expand Down Expand Up @@ -553,6 +577,10 @@ export class ClipboardPlugin extends UIPlugin {
return this._isCutOperation ?? false;
}

canModifyPaste(): boolean {
return this.recentlyPasted ?? false;
}

// ---------------------------------------------------------------------------
// Private methods
// ---------------------------------------------------------------------------
Expand Down
Loading

0 comments on commit 88a4d7c

Please sign in to comment.