Skip to content

Commit 0325a11

Browse files
committed
[IMP] Added some shortcuts
shift+F11 (new sheet), ctrl+enter (fill range) and alt+t (new table) Task: 5231802
1 parent 1f63e08 commit 0325a11

File tree

11 files changed

+198
-12
lines changed

11 files changed

+198
-12
lines changed

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

Lines changed: 54 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,14 +95,50 @@ 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+
const copiedData = this.copy([copyTarget]);
106+
return this.isPasteAllowed(zones, copiedData, {
107+
isCutOperation: false,
108+
});
99109
}
100110
case "COPY_PASTE_CELLS_ON_LEFT": {
101111
const zones = this.getters.getSelectedZones();
102112
if (zones.length > 1 || (zones[0].left === 0 && zones[0].right === 0)) {
103113
return CommandResult.InvalidCopyPasteSelection;
104114
}
105-
break;
115+
const zone = this.getters.getSelectedZone();
116+
const multipleColsInSelection = zone.left !== zone.right;
117+
const copyTarget = {
118+
...zone,
119+
right: multipleColsInSelection ? zone.left : zone.left - 1,
120+
left: multipleColsInSelection ? zone.left : zone.left - 1,
121+
};
122+
const copiedData = this.copy([copyTarget]);
123+
return this.isPasteAllowed(zones, copiedData, {
124+
isCutOperation: false,
125+
});
126+
}
127+
case "COPY_PASTE_CELLS_ON_ZONE": {
128+
const zones = this.getters.getSelectedZones();
129+
if (zones.length > 1) {
130+
return CommandResult.InvalidCopyPasteSelection;
131+
}
132+
const zone = this.getters.getSelectedZone();
133+
const copyTarget = {
134+
...zone,
135+
right: zone.left,
136+
bottom: zone.top,
137+
};
138+
const copiedData = this.copy([copyTarget]);
139+
return this.isPasteAllowed(zones, copiedData, {
140+
isCutOperation: false,
141+
});
106142
}
107143
case "INSERT_CELL": {
108144
const { cut, paste } = this.getInsertCellsTargets(cmd.zone, cmd.shiftDimension);
@@ -212,6 +248,22 @@ export class ClipboardPlugin extends UIPlugin {
212248
});
213249
}
214250
break;
251+
case "COPY_PASTE_CELLS_ON_ZONE":
252+
{
253+
const zone = this.getters.getSelectedZone();
254+
const copyTarget = {
255+
...zone,
256+
right: zone.left,
257+
bottom: zone.top,
258+
};
259+
this.originSheetId = this.getters.getActiveSheetId();
260+
const copiedData = this.copy([copyTarget]);
261+
this.paste([zone], copiedData, {
262+
isCutOperation: false,
263+
selectTarget: true,
264+
});
265+
}
266+
break;
215267
case "CLEAN_CLIPBOARD_HIGHLIGHT":
216268
this.status = "invisible";
217269
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: 51 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,17 @@ describe("Copy paste keyboard shortcut", () => {
19641982
expect(getCell(model, "D2")?.content).toBe("d1");
19651983
});
19661984

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

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

tests/link/link_display_component.test.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -96,14 +96,15 @@ describe("link display component", () => {
9696
test("right-click on a linked cell should show 'Edit Link' instead of 'Insert Link' in the context menu", async () => {
9797
setCellContent(model, "A1", "HELLO");
9898
await rightClickCell(model, "A1");
99-
expect(
100-
fixture.querySelector(".o-menu .o-menu-item[data-name='insert_link']")?.textContent
101-
).toBe("Insert link");
99+
expect(".o-menu .o-menu-item[data-name='insert_link'] .o-menu-item-name").toHaveText(
100+
"Insert link"
101+
);
102102

103103
setCellContent(model, "A1", "[label](url.com)");
104104
await rightClickCell(model, "A1");
105105
expect(
106-
fixture.querySelector(".o-menu .o-menu-item[data-name='insert_link']")?.textContent
106+
fixture.querySelector(".o-menu .o-menu-item[data-name='insert_link'] .o-menu-item-name")
107+
?.textContent
107108
).toBe("Edit link");
108109
});
109110

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)