Merge branch 'master' into improve_copy_styles

# Conflicts:
#	src/tests/regressionTests.test.tsx
This commit is contained in:
dwelle 2020-12-12 23:36:48 +01:00
commit ce4b64b2a3
6 changed files with 163 additions and 58 deletions

View File

@ -10,6 +10,7 @@ import {
import { ExcalidrawElement } from "../element/types";
import { AppState } from "../types";
import { t } from "../i18n";
import { ShortcutName } from "./shortcuts";
export class ActionManager implements ActionsManagerInterface {
actions = {} as ActionsManagerInterface["actions"];
@ -102,6 +103,8 @@ export class ActionManager implements ActionsManagerInterface {
(b.contextMenuOrder !== undefined ? b.contextMenuOrder : 999),
)
.map((action) => ({
// take last bit of the label "labels.<shortcutName>"
shortcutName: action.contextItemLabel?.split(".").pop() as ShortcutName,
label: action.contextItemLabel ? t(action.contextItemLabel) : "",
action: () => {
this.updater(

63
src/actions/shortcuts.ts Normal file
View File

@ -0,0 +1,63 @@
import { t } from "../i18n";
import { isDarwin } from "../keys";
import { getShortcutKey } from "../utils";
export type ShortcutName =
| "cut"
| "copy"
| "paste"
| "copyStyles"
| "pasteStyles"
| "selectAll"
| "delete"
| "duplicateSelection"
| "sendBackward"
| "bringForward"
| "sendToBack"
| "bringToFront"
| "copyAsPng"
| "copyAsSvg"
| "group"
| "ungroup"
| "toggleGridMode"
| "toggleStats"
| "addToLibrary";
const shortcutMap: Record<ShortcutName, string[]> = {
cut: [getShortcutKey("CtrlOrCmd+X")],
copy: [getShortcutKey("CtrlOrCmd+C")],
paste: [getShortcutKey("CtrlOrCmd+V")],
copyStyles: [getShortcutKey("CtrlOrCmd+Alt+C")],
pasteStyles: [getShortcutKey("CtrlOrCmd+Alt+V")],
selectAll: [getShortcutKey("CtrlOrCmd+A")],
delete: [getShortcutKey("Del")],
duplicateSelection: [
getShortcutKey("CtrlOrCmd+D"),
getShortcutKey(`Alt+${t("shortcutsDialog.drag")}`),
],
sendBackward: [getShortcutKey("CtrlOrCmd+[")],
bringForward: [getShortcutKey("CtrlOrCmd+]")],
sendToBack: [
isDarwin
? getShortcutKey("CtrlOrCmd+Alt+[")
: getShortcutKey("CtrlOrCmd+Shift+["),
],
bringToFront: [
isDarwin
? getShortcutKey("CtrlOrCmd+Alt+]")
: getShortcutKey("CtrlOrCmd+Shift+]"),
],
copyAsPng: [getShortcutKey("Shift+Alt+C")],
copyAsSvg: [],
group: [getShortcutKey("CtrlOrCmd+G")],
ungroup: [getShortcutKey("CtrlOrCmd+Shift+G")],
toggleGridMode: [getShortcutKey("CtrlOrCmd+'")],
toggleStats: [],
addToLibrary: [],
};
export const getShortcutFromShortcutName = (name: ShortcutName) => {
const shortcuts = shortcutMap[name];
// if multiple shortcuts availiable, take the first one
return shortcuts && shortcuts.length > 0 ? shortcuts[0] : "";
};

View File

@ -3592,16 +3592,19 @@ class App extends React.Component<ExcalidrawProps, AppState> {
ContextMenu.push({
options: [
navigator.clipboard && {
shortcutName: "paste",
label: t("labels.paste"),
action: () => this.pasteFromClipboard(null),
},
probablySupportsClipboardBlob &&
elements.length > 0 && {
shortcutName: "copyAsPng",
label: t("labels.copyAsPng"),
action: this.copyToClipboardAsPng,
},
probablySupportsClipboardWriteText &&
elements.length > 0 && {
shortcutName: "copyAsSvg",
label: t("labels.copyAsSvg"),
action: this.copyToClipboardAsSvg,
},
@ -3609,10 +3612,12 @@ class App extends React.Component<ExcalidrawProps, AppState> {
CANVAS_ONLY_ACTIONS.includes(action.name),
),
{
shortcutName: "toggleGridMode",
label: t("labels.toggleGridMode"),
action: this.toggleGridMode,
},
{
shortcutName: "toggleStats",
label: t("labels.toggleStats"),
action: this.toggleStats,
},
@ -3630,22 +3635,27 @@ class App extends React.Component<ExcalidrawProps, AppState> {
ContextMenu.push({
options: [
{
shortcutName: "cut",
label: t("labels.cut"),
action: this.cutAll,
},
navigator.clipboard && {
shortcutName: "copy",
label: t("labels.copy"),
action: this.copyAll,
},
navigator.clipboard && {
shortcutName: "paste",
label: t("labels.paste"),
action: () => this.pasteFromClipboard(null),
},
probablySupportsClipboardBlob && {
shortcutName: "copyAsPng",
label: t("labels.copyAsPng"),
action: this.copyToClipboardAsPng,
},
probablySupportsClipboardWriteText && {
shortcutName: "copyAsSvg",
label: t("labels.copyAsSvg"),
action: this.copyToClipboardAsSvg,
},

View File

@ -29,6 +29,18 @@
background-color: transparent;
border: none;
white-space: nowrap;
display: grid;
grid-template-columns: 1fr 0.2fr;
div:nth-child(1) {
justify-self: start;
margin-inline-end: 20px;
}
div:nth-child(2) {
justify-self: end;
opacity: 0.6;
font-size: 0.7rem;
}
}
.context-menu-option:hover {

View File

@ -4,8 +4,13 @@ import clsx from "clsx";
import { Popover } from "./Popover";
import "./ContextMenu.scss";
import {
getShortcutFromShortcutName,
ShortcutName,
} from "../actions/shortcuts";
type ContextMenuOption = {
shortcutName: ShortcutName;
label: string;
action(): void;
};
@ -38,10 +43,15 @@ const ContextMenu = ({ options, onCloseRequest, top, left }: Props) => {
className="context-menu"
onContextMenu={(event) => event.preventDefault()}
>
{options.map(({ action, label }, idx) => (
<li key={idx} onClick={onCloseRequest}>
{options.map(({ action, shortcutName, label }, idx) => (
<li data-testid={shortcutName} key={idx} onClick={onCloseRequest}>
<button className="context-menu-option" onClick={action}>
{label}
<div>{label}</div>
<div>
{shortcutName
? getShortcutFromShortcutName(shortcutName)
: ""}
</div>
</button>
</li>
))}

View File

@ -2,8 +2,9 @@ import { queryByText } from "@testing-library/react";
import React from "react";
import ReactDOM from "react-dom";
import { getDefaultAppState } from "../appState";
import { ShortcutName } from "../actions/shortcuts";
import { ExcalidrawElement } from "../element/types";
import { setLanguage, t } from "../i18n";
import { setLanguage } from "../i18n";
import { CODES, KEYS } from "../keys";
import Excalidraw from "../packages/excalidraw/index";
import { reseed } from "../random";
@ -632,16 +633,19 @@ describe("regression tests", () => {
clientY: 1,
});
const contextMenu = document.querySelector(".context-menu");
const options = contextMenu?.querySelectorAll(".context-menu-option");
const expectedOptions = [
t("labels.selectAll"),
t("labels.toggleGridMode"),
t("labels.toggleStats"),
const expectedShortcutNames: ShortcutName[] = [
"selectAll",
"toggleGridMode",
"toggleStats",
];
expect(contextMenu).not.toBeNull();
expect(options?.length).toBe(3);
expect(options?.item(0).textContent).toBe(expectedOptions[0]);
expect(contextMenu?.children.length).toBe(expectedShortcutNames.length);
expectedShortcutNames.forEach((shortcutName) => {
expect(
contextMenu?.querySelector(`li[data-testid="${shortcutName}"]`),
).not.toBeNull();
});
});
it("shows context menu for element", () => {
@ -655,24 +659,25 @@ describe("regression tests", () => {
clientY: 1,
});
const contextMenu = document.querySelector(".context-menu");
const options = contextMenu?.querySelectorAll(".context-menu-option");
const expectedOptions = [
"Cut",
"Copy styles",
"Paste styles",
"Delete",
"Add to library",
"Send backward",
"Bring forward",
"Send to back",
"Bring to front",
"Duplicate",
const expectedShortcutNames: ShortcutName[] = [
"cut",
"copyStyles",
"pasteStyles",
"delete",
"addToLibrary",
"sendBackward",
"bringForward",
"sendToBack",
"bringToFront",
"duplicateSelection",
];
expect(contextMenu).not.toBeNull();
expect(contextMenu?.children.length).toBe(10);
options?.forEach((opt, i) => {
expect(opt.textContent).toBe(expectedOptions[i]);
expect(contextMenu?.children.length).toBe(expectedShortcutNames.length);
expectedShortcutNames.forEach((shortcutName) => {
expect(
contextMenu?.querySelector(`li[data-testid="${shortcutName}"]`),
).not.toBeNull();
});
});
@ -698,25 +703,26 @@ describe("regression tests", () => {
});
const contextMenu = document.querySelector(".context-menu");
const options = contextMenu?.querySelectorAll(".context-menu-option");
const expectedOptions = [
"Cut",
"Copy styles",
"Paste styles",
"Delete",
"Group selection",
"Add to library",
"Send backward",
"Bring forward",
"Send to back",
"Bring to front",
"Duplicate",
const expectedShortcutNames: ShortcutName[] = [
"cut",
"copyStyles",
"pasteStyles",
"delete",
"group",
"addToLibrary",
"sendBackward",
"bringForward",
"sendToBack",
"bringToFront",
"duplicateSelection",
];
expect(contextMenu).not.toBeNull();
expect(contextMenu?.children.length).toBe(11);
options?.forEach((opt, i) => {
expect(opt.textContent).toBe(expectedOptions[i]);
expect(contextMenu?.children.length).toBe(expectedShortcutNames.length);
expectedShortcutNames.forEach((shortcutName) => {
expect(
contextMenu?.querySelector(`li[data-testid="${shortcutName}"]`),
).not.toBeNull();
});
});
@ -746,25 +752,26 @@ describe("regression tests", () => {
});
const contextMenu = document.querySelector(".context-menu");
const options = contextMenu?.querySelectorAll(".context-menu-option");
const expectedOptions = [
"Cut",
"Copy styles",
"Paste styles",
"Delete",
"Ungroup selection",
"Add to library",
"Send backward",
"Bring forward",
"Send to back",
"Bring to front",
"Duplicate",
const expectedShortcutNames: ShortcutName[] = [
"cut",
"copyStyles",
"pasteStyles",
"delete",
"ungroup",
"addToLibrary",
"sendBackward",
"bringForward",
"sendToBack",
"bringToFront",
"duplicateSelection",
];
expect(contextMenu).not.toBeNull();
expect(contextMenu?.children.length).toBe(11);
options?.forEach((opt, i) => {
expect(opt.textContent).toBe(expectedOptions[i]);
expect(contextMenu?.children.length).toBe(expectedShortcutNames.length);
expectedShortcutNames.forEach((shortcutName) => {
expect(
contextMenu?.querySelector(`li[data-testid="${shortcutName}"]`),
).not.toBeNull();
});
});