Skip to content

Commit 945f5c2

Browse files
committed
[IMP] Added some shortcuts
shift+F11 (new sheet), ctrl+enter (fill range) and alt+t (new table) Task: 5231802 [WIP] m
1 parent 85f2ecb commit 945f5c2

File tree

11 files changed

+197
-10
lines changed

11 files changed

+197
-10
lines changed

packages/o-spreadsheet-engine/src/plugins/ui_stateful/clipboard.ts

Lines changed: 57 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,14 +95,53 @@ export class ClipboardPlugin extends UIPlugin {
9595
if (zones.length > 1 || (zones[0].top === 0 && zones[0].bottom === 0)) {
9696
return CommandResult.InvalidCopyPasteSelection;
9797
}
98-
break;
98+
const zone = this.getters.getSelectedZone();
99+
const multipleRowsInSelection = zone.top !== zone.bottom;
100+
const copyTarget = {
101+
...zone,
102+
bottom: multipleRowsInSelection ? zone.top : zone.top - 1,
103+
top: multipleRowsInSelection ? zone.top : zone.top - 1,
104+
};
105+
this.originSheetId = this.getters.getActiveSheetId();
106+
const copiedData = this.copy([copyTarget]);
107+
return this.isPasteAllowed(zones, copiedData, {
108+
isCutOperation: false,
109+
});
99110
}
100111
case "COPY_PASTE_CELLS_ON_LEFT": {
101112
const zones = this.getters.getSelectedZones();
102113
if (zones.length > 1 || (zones[0].left === 0 && zones[0].right === 0)) {
103114
return CommandResult.InvalidCopyPasteSelection;
104115
}
105-
break;
116+
const zone = this.getters.getSelectedZone();
117+
const multipleColsInSelection = zone.left !== zone.right;
118+
const copyTarget = {
119+
...zone,
120+
right: multipleColsInSelection ? zone.left : zone.left - 1,
121+
left: multipleColsInSelection ? zone.left : zone.left - 1,
122+
};
123+
this.originSheetId = this.getters.getActiveSheetId();
124+
const copiedData = this.copy([copyTarget]);
125+
return this.isPasteAllowed(zones, copiedData, {
126+
isCutOperation: false,
127+
});
128+
}
129+
case "COPY_PASTE_CELLS_ON_ZONE": {
130+
const zones = this.getters.getSelectedZones();
131+
if (zones.length > 1) {
132+
return CommandResult.InvalidCopyPasteSelection;
133+
}
134+
const zone = this.getters.getSelectedZone();
135+
const copyTarget = {
136+
...zone,
137+
right: zone.left,
138+
bottom: zone.top,
139+
};
140+
this.originSheetId = this.getters.getActiveSheetId();
141+
const copiedData = this.copy([copyTarget]);
142+
return this.isPasteAllowed(zones, copiedData, {
143+
isCutOperation: false,
144+
});
106145
}
107146
case "INSERT_CELL": {
108147
const { cut, paste } = this.getInsertCellsTargets(cmd.zone, cmd.shiftDimension);
@@ -212,6 +251,22 @@ export class ClipboardPlugin extends UIPlugin {
212251
});
213252
}
214253
break;
254+
case "COPY_PASTE_CELLS_ON_ZONE":
255+
{
256+
const zone = this.getters.getSelectedZone();
257+
const copyTarget = {
258+
...zone,
259+
right: zone.left,
260+
bottom: zone.top,
261+
};
262+
this.originSheetId = this.getters.getActiveSheetId();
263+
const copiedData = this.copy([copyTarget]);
264+
this.paste([zone], copiedData, {
265+
isCutOperation: false,
266+
selectTarget: true,
267+
});
268+
}
269+
break;
215270
case "CLEAN_CLIPBOARD_HIGHLIGHT":
216271
this.status = "invisible";
217272
break;

packages/o-spreadsheet-engine/src/types/commands.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -860,6 +860,10 @@ export interface CopyPasteCellsOnLeftCommand {
860860
type: "COPY_PASTE_CELLS_ON_LEFT";
861861
}
862862

863+
export interface CopyPasteCellsOnZoneCommand {
864+
type: "COPY_PASTE_CELLS_ON_ZONE";
865+
}
866+
863867
export interface RepeatPasteCommand {
864868
type: "REPEAT_PASTE";
865869
target: Zone[];
@@ -1237,6 +1241,7 @@ export type LocalCommand =
12371241
| PasteCommand
12381242
| CopyPasteCellsAboveCommand
12391243
| CopyPasteCellsOnLeftCommand
1244+
| CopyPasteCellsOnZoneCommand
12401245
| RepeatPasteCommand
12411246
| CleanClipBoardHighlightCommand
12421247
| AutoFillCellCommand

src/actions/insert_actions.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,7 @@ export const insertImage: ActionSpec = {
204204

205205
export const insertTable: ActionSpec = {
206206
name: () => _t("Table"),
207+
description: "Alt+T",
207208
execute: ACTIONS.INSERT_TABLE,
208209
isVisible: (env) =>
209210
ACTIONS.IS_SELECTION_CONTINUOUS(env) && !env.model.getters.getFirstTableInSelection(),
@@ -275,6 +276,7 @@ export const categoriesFunctionListMenuBuilder: ActionBuilder = () => {
275276

276277
export const insertLink: ActionSpec = {
277278
name: _t("Link"),
279+
description: "Ctrl+K",
278280
execute: ACTIONS.INSERT_LINK,
279281
icon: "o-spreadsheet-Icon.INSERT_LINK",
280282
};
@@ -336,6 +338,7 @@ export const insertDropdown: ActionSpec = {
336338

337339
export const insertSheet: ActionSpec = {
338340
name: _t("Insert sheet"),
341+
description: "Shift+F11",
339342
execute: (env) => {
340343
const activeSheetId = env.model.getters.getActiveSheetId();
341344
const position = env.model.getters.getSheetIds().indexOf(activeSheetId) + 1;

src/components/grid/grid.ts

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import {
1717
useRef,
1818
useState,
1919
} from "@odoo/owl";
20+
import { insertSheet, insertTable } from "../../actions/insert_actions";
2021
import {
2122
CREATE_IMAGE,
2223
INSERT_COLUMNS_BEFORE_ACTION,
@@ -27,7 +28,11 @@ import {
2728
import { canUngroupHeaders } from "../../actions/view_actions";
2829
import { isInside } from "../../helpers/index";
2930
import { interactiveCut } from "../../helpers/ui/cut_interactive";
30-
import { interactivePaste, interactivePasteFromOS } from "../../helpers/ui/paste_interactive";
31+
import {
32+
handleCopyPasteResult,
33+
interactivePaste,
34+
interactivePasteFromOS,
35+
} from "../../helpers/ui/paste_interactive";
3136
import { cellMenuRegistry } from "../../registries/menus/cell_menu_registry";
3237
import { colMenuRegistry } from "../../registries/menus/col_menu_registry";
3338
import {
@@ -359,8 +364,15 @@ export class Grid extends Component<Props, SpreadsheetChildEnv> {
359364
const position = this.env.model.getters.getActivePosition();
360365
this.env.model.selection.selectZone({ cell: position, zone: newZone });
361366
},
362-
"Ctrl+D": async () => this.env.model.dispatch("COPY_PASTE_CELLS_ABOVE"),
363-
"Ctrl+R": async () => this.env.model.dispatch("COPY_PASTE_CELLS_ON_LEFT"),
367+
"Ctrl+D": () => {
368+
handleCopyPasteResult(this.env, { type: "COPY_PASTE_CELLS_ABOVE" });
369+
},
370+
"Ctrl+R": () => {
371+
handleCopyPasteResult(this.env, { type: "COPY_PASTE_CELLS_ON_LEFT" });
372+
},
373+
"Ctrl+Enter": () => {
374+
handleCopyPasteResult(this.env, { type: "COPY_PASTE_CELLS_ON_ZONE" });
375+
},
364376
"Ctrl+H": () => this.sidePanel.open("FindAndReplace", {}),
365377
"Ctrl+F": () => this.sidePanel.open("FindAndReplace", {}),
366378
"Ctrl+Shift+E": () => this.setHorizontalAlign("center"),
@@ -409,6 +421,12 @@ export class Grid extends Component<Props, SpreadsheetChildEnv> {
409421
"Shift+PageUp": () => {
410422
this.env.model.dispatch("ACTIVATE_PREVIOUS_SHEET");
411423
},
424+
"Shift+F11": () => {
425+
insertSheet.execute?.(this.env);
426+
},
427+
"Alt+T": () => {
428+
insertTable.execute?.(this.env);
429+
},
412430
PageDown: () => this.env.model.dispatch("SHIFT_VIEWPORT_DOWN"),
413431
PageUp: () => this.env.model.dispatch("SHIFT_VIEWPORT_UP"),
414432
"Ctrl+K": () => INSERT_LINK(this.env),

src/helpers/ui/paste_interactive.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,32 @@
1-
import { RemoveDuplicateTerms } from "@odoo/o-spreadsheet-engine/components/translations_terms";
1+
import {
2+
MergeErrorMessage,
3+
RemoveDuplicateTerms,
4+
} from "@odoo/o-spreadsheet-engine/components/translations_terms";
25
import { getCurrentVersion } from "@odoo/o-spreadsheet-engine/migrations/data";
36
import { _t } from "@odoo/o-spreadsheet-engine/translation";
47
import { SpreadsheetChildEnv } from "@odoo/o-spreadsheet-engine/types/spreadsheet_env";
58
import {
69
ClipboardPasteOptions,
710
CommandResult,
11+
CopyPasteCellsAboveCommand,
12+
CopyPasteCellsOnLeftCommand,
13+
CopyPasteCellsOnZoneCommand,
814
DispatchResult,
915
ParsedOSClipboardContent,
1016
ParsedOsClipboardContentWithImageData,
1117
Zone,
1218
} from "../../types";
1319

20+
export const handleCopyPasteResult = (
21+
env: SpreadsheetChildEnv,
22+
command: CopyPasteCellsAboveCommand | CopyPasteCellsOnLeftCommand | CopyPasteCellsOnZoneCommand
23+
) => {
24+
const result = env.model.dispatch(command.type);
25+
if (result.isCancelledBecause(CommandResult.WillRemoveExistingMerge)) {
26+
env.raiseError(MergeErrorMessage);
27+
}
28+
};
29+
1430
export const PasteInteractiveContent = {
1531
wrongPasteSelection: _t("This operation is not allowed with multiple selections."),
1632
willRemoveExistingMerge: RemoveDuplicateTerms.Errors.WillRemoveExistingMerge,

tests/clipboard/clipboard_plugin.test.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import {
3434
copy,
3535
copyPasteAboveCells,
3636
copyPasteCellsOnLeft,
37+
copyPasteCellsOnZone,
3738
createDynamicTable,
3839
createImage,
3940
createSheet,
@@ -2412,6 +2413,33 @@ describe("clipboard: pasting outside of sheet", () => {
24122413
expect(getCellContent(model, "D2")).toBe("d1");
24132414
});
24142415

2416+
test("do not fill down if filling down would unmerge cells", () => {
2417+
const model = new Model();
2418+
setCellContent(model, "A1", "a1");
2419+
merge(model, "A2:A3");
2420+
setSelection(model, ["A1:A3"]);
2421+
const result = copyPasteAboveCells(model);
2422+
expect(result).toBeCancelledBecause(CommandResult.WillRemoveExistingMerge);
2423+
});
2424+
2425+
test("do not fill right if filling right would unmerge cells", () => {
2426+
const model = new Model();
2427+
setCellContent(model, "A1", "a1");
2428+
merge(model, "B1:C1");
2429+
setSelection(model, ["A1:C1"]);
2430+
const result = copyPasteCellsOnLeft(model);
2431+
expect(result).toBeCancelledBecause(CommandResult.WillRemoveExistingMerge);
2432+
});
2433+
2434+
test("do not fill if filling would unmerge cells", () => {
2435+
const model = new Model();
2436+
setCellContent(model, "A1", "a1");
2437+
merge(model, "A2:A3");
2438+
setSelection(model, ["A1:A3"]);
2439+
const result = copyPasteCellsOnZone(model);
2440+
expect(result).toBeCancelledBecause(CommandResult.WillRemoveExistingMerge);
2441+
});
2442+
24152443
test("fill right selection with single column -> for each cell, replicates the cell on its left", async () => {
24162444
const model = new Model();
24172445
setCellContent(model, "B1", "b1");

tests/grid/grid_component.test.ts

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,11 @@ import { resetTimeoutDuration } from "../../src/components/helpers/touch_scroll_
2424
import { PaintFormatStore } from "../../src/components/paint_format_button/paint_format_store";
2525
import { CellPopoverStore } from "../../src/components/popover";
2626
import { buildSheetLink, toCartesian, toZone, zoneToXc } from "../../src/helpers";
27+
import { handleCopyPasteResult } from "../../src/helpers/ui/paste_interactive";
2728
import { Store } from "../../src/store_engine";
2829
import { ClientFocusStore } from "../../src/stores/client_focus_store";
2930
import { HighlightStore } from "../../src/stores/highlight_store";
31+
import { NotificationStore } from "../../src/stores/notification_store";
3032
import { Align, ClipboardMIMEType } from "../../src/types";
3133
import { FileStore } from "../__mocks__/mock_file_store";
3234
import { MockTransportService } from "../__mocks__/transport_service";
@@ -127,6 +129,14 @@ let composerFocusStore: Store<ComposerFocusStore>;
127129

128130
jest.useFakeTimers();
129131
mockChart();
132+
jest.mock("../../src/actions/menu_items_actions.ts", () => {
133+
const originalModule = jest.requireActual("../../src/actions/menu_items_actions.ts");
134+
return {
135+
__esModule: true,
136+
...originalModule,
137+
INSERT_TABLE: jest.fn(originalModule.INSERT_TABLE),
138+
};
139+
});
130140

131141
describe("Grid component", () => {
132142
beforeEach(async () => {
@@ -942,6 +952,14 @@ describe("Grid component", () => {
942952
expect(model.getters.getActiveSheetId()).toBe("third");
943953
});
944954

955+
test("Pressing Shift+F11 insert a new sheet", () => {
956+
expect(model.getters.getSheetIds()).toHaveLength(1);
957+
keyDown({ key: "F11", shiftKey: true });
958+
const sheetIds = model.getters.getSheetIds();
959+
expect(sheetIds).toHaveLength(2);
960+
expect(model.getters.getActiveSheetId()).toBe(sheetIds[1]);
961+
});
962+
945963
test("pressing Ctrl+K opens the link editor", async () => {
946964
await keyDown({ key: "k", ctrlKey: true });
947965
expect(fixture.querySelector(".o-link-editor")).not.toBeNull();
@@ -1791,7 +1809,7 @@ describe("Copy paste keyboard shortcut", () => {
17911809
const fileStore = new FileStore();
17921810
beforeEach(async () => {
17931811
clipboardData = new MockClipboardData();
1794-
({ parent, model, fixture } = await mountSpreadsheet({
1812+
({ parent, model, fixture, env } = await mountSpreadsheet({
17951813
model: new Model({}, { external: { fileStore } }),
17961814
}));
17971815
sheetId = model.getters.getActiveSheetId();
@@ -1964,6 +1982,16 @@ describe("Copy paste keyboard shortcut", () => {
19641982
expect(getCell(model, "D2")?.content).toBe("d1");
19651983
});
19661984

1985+
test("banane", () => {
1986+
setCellContent(model, "A1", "a1");
1987+
merge(model, "A2:A3");
1988+
setSelection(model, ["A1:A3"]);
1989+
handleCopyPasteResult(env, { type: "COPY_PASTE_CELLS_ON_ZONE" });
1990+
// @ts-ignore
1991+
const notificationStore = env.__spreadsheet_stores__.get(NotificationStore);
1992+
expect(notificationStore.raiseError).toHaveBeenCalled();
1993+
});
1994+
19671995
test("can copy and paste cell(s) on left using CTRL+R", async () => {
19681996
setCellContent(model, "A2", "a2");
19691997
setCellContent(model, "B2", "b2");
@@ -1980,6 +2008,26 @@ describe("Copy paste keyboard shortcut", () => {
19802008
expect(getCell(model, "B4")?.content).toBe("a4");
19812009
});
19822010

2011+
test("can copy and paste cell(s) on zone using CTRL+ENTER", async () => {
2012+
setCellContent(model, "A1", "a1");
2013+
setSelection(model, ["A1:B2"]);
2014+
keyDown({ key: "Enter", ctrlKey: true });
2015+
expect(getCell(model, "A1")?.content).toBe("a1");
2016+
expect(getCell(model, "A2")?.content).toBe("a1");
2017+
expect(getCell(model, "B1")?.content).toBe("a1");
2018+
expect(getCell(model, "B2")?.content).toBe("a1");
2019+
});
2020+
2021+
test("Alt+T -> Table", async () => {
2022+
setSelection(model, ["A1:A5"]);
2023+
await keyDown({ key: "T", altKey: true });
2024+
expect(model.getters.getTable({ sheetId, row: 0, col: 0 })).toMatchObject({
2025+
range: { zone: toZone("A1:A5") },
2026+
});
2027+
const { INSERT_TABLE } = require("../../src/actions/menu_items_actions");
2028+
expect(INSERT_TABLE as jest.Mock).toHaveBeenCalled();
2029+
});
2030+
19832031
test("Clipboard visible zones (copy) will be cleaned after hitting esc", async () => {
19842032
setCellContent(model, "A1", "things");
19852033
selectCell(model, "A1");

tests/link/link_display_component.test.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -97,13 +97,15 @@ describe("link display component", () => {
9797
setCellContent(model, "A1", "HELLO");
9898
await rightClickCell(model, "A1");
9999
expect(
100-
fixture.querySelector(".o-menu .o-menu-item[data-name='insert_link']")?.textContent
100+
fixture.querySelector(".o-menu .o-menu-item[data-name='insert_link'] .o-menu-item-name")
101+
?.textContent
101102
).toBe("Insert link");
102103

103104
setCellContent(model, "A1", "[label](url.com)");
104105
await rightClickCell(model, "A1");
105106
expect(
106-
fixture.querySelector(".o-menu .o-menu-item[data-name='insert_link']")?.textContent
107+
fixture.querySelector(".o-menu .o-menu-item[data-name='insert_link'] .o-menu-item-name")
108+
?.textContent
107109
).toBe("Edit link");
108110
});
109111

tests/menus/__snapshots__/context_menu_component.test.ts.snap

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -435,6 +435,11 @@ exports[`Context MenuPopover integration tests context menu simple rendering 1`]
435435
>
436436
Insert link
437437
</div>
438+
<div
439+
class="o-menu-item-description ms-auto text-truncate"
440+
>
441+
Ctrl+K
442+
</div>
438443
439444
440445

tests/test_helpers/commands_helpers.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -544,6 +544,13 @@ export function copyPasteCellsOnLeft(model: Model): DispatchResult {
544544
return model.dispatch("COPY_PASTE_CELLS_ON_LEFT");
545545
}
546546

547+
/**
548+
* Copy cell and paste on zone
549+
*/
550+
export function copyPasteCellsOnZone(model: Model): DispatchResult {
551+
return model.dispatch("COPY_PASTE_CELLS_ON_ZONE");
552+
}
553+
547554
/**
548555
* Clean clipboard highlight selection.
549556
*/

0 commit comments

Comments
 (0)