feat: More prominent keyboard shortcuts in hints (#10057)
* Initial * Memoize * Styling * Use double angle brackets for keyboard shortcuts * Use rem in gap * Use an existing function for substituting tags in a string * Revert styling * Avoid unique key warnings * Styling * getTransChildren -> nodesFromTextWithTags * Use height and padding instead of padding only * Initial new idea * WIP shortcut substitutions * Use simple regex for parsing shortcuts * Use single shortcut for combos * Use kbd instead of span * shortcutFromKeyString -> getTaggedShortcutKey * Bug fix * FlowChart -> Flowchart * memo is useless here * Trigger CI * Translate in getShortcutKey * More normalized shortcuts * improve shortcut normalization and replacement & support multi-key tagged shortcuts * fix regex * tweak css --------- Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
This commit is contained in:
@@ -20,7 +20,6 @@ import {
|
|||||||
ENV,
|
ENV,
|
||||||
FONT_FAMILY,
|
FONT_FAMILY,
|
||||||
getFontFamilyFallbacks,
|
getFontFamilyFallbacks,
|
||||||
isDarwin,
|
|
||||||
isAndroid,
|
isAndroid,
|
||||||
isIOS,
|
isIOS,
|
||||||
WINDOWS_EMOJI_FALLBACK_FONT,
|
WINDOWS_EMOJI_FALLBACK_FONT,
|
||||||
@@ -426,19 +425,6 @@ export const allowFullScreen = () =>
|
|||||||
|
|
||||||
export const exitFullScreen = () => document.exitFullscreen();
|
export const exitFullScreen = () => document.exitFullscreen();
|
||||||
|
|
||||||
export const getShortcutKey = (shortcut: string): string => {
|
|
||||||
shortcut = shortcut
|
|
||||||
.replace(/\bAlt\b/i, "Alt")
|
|
||||||
.replace(/\bShift\b/i, "Shift")
|
|
||||||
.replace(/\b(Enter|Return)\b/i, "Enter");
|
|
||||||
if (isDarwin) {
|
|
||||||
return shortcut
|
|
||||||
.replace(/\bCtrlOrCmd\b/gi, "Cmd")
|
|
||||||
.replace(/\bAlt\b/i, "Option");
|
|
||||||
}
|
|
||||||
return shortcut.replace(/\bCtrlOrCmd\b/gi, "Ctrl");
|
|
||||||
};
|
|
||||||
|
|
||||||
export const viewportCoordsToSceneCoords = (
|
export const viewportCoordsToSceneCoords = (
|
||||||
{ clientX, clientY }: { clientX: number; clientY: number },
|
{ clientX, clientY }: { clientX: number; clientY: number },
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { isFrameLikeElement } from "@excalidraw/element";
|
|||||||
|
|
||||||
import { updateFrameMembershipOfSelectedElements } from "@excalidraw/element";
|
import { updateFrameMembershipOfSelectedElements } from "@excalidraw/element";
|
||||||
|
|
||||||
import { KEYS, arrayToMap, getShortcutKey } from "@excalidraw/common";
|
import { KEYS, arrayToMap } from "@excalidraw/common";
|
||||||
|
|
||||||
import { alignElements } from "@excalidraw/element";
|
import { alignElements } from "@excalidraw/element";
|
||||||
|
|
||||||
@@ -30,6 +30,8 @@ import { t } from "../i18n";
|
|||||||
|
|
||||||
import { isSomeElementSelected } from "../scene";
|
import { isSomeElementSelected } from "../scene";
|
||||||
|
|
||||||
|
import { getShortcutKey } from "../shortcut";
|
||||||
|
|
||||||
import { register } from "./register";
|
import { register } from "./register";
|
||||||
|
|
||||||
import type { AppClassProperties, AppState, UIAppState } from "../types";
|
import type { AppClassProperties, AppState, UIAppState } from "../types";
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import {
|
|||||||
MIN_ZOOM,
|
MIN_ZOOM,
|
||||||
THEME,
|
THEME,
|
||||||
ZOOM_STEP,
|
ZOOM_STEP,
|
||||||
getShortcutKey,
|
|
||||||
updateActiveTool,
|
updateActiveTool,
|
||||||
CODES,
|
CODES,
|
||||||
KEYS,
|
KEYS,
|
||||||
@@ -46,6 +45,7 @@ import { t } from "../i18n";
|
|||||||
import { getNormalizedZoom } from "../scene";
|
import { getNormalizedZoom } from "../scene";
|
||||||
import { centerScrollOn } from "../scene/scroll";
|
import { centerScrollOn } from "../scene/scroll";
|
||||||
import { getStateForZoom } from "../scene/zoom";
|
import { getStateForZoom } from "../scene/zoom";
|
||||||
|
import { getShortcutKey } from "../shortcut";
|
||||||
|
|
||||||
import { register } from "./register";
|
import { register } from "./register";
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { getNonDeletedElements } from "@excalidraw/element";
|
|||||||
|
|
||||||
import { isFrameLikeElement } from "@excalidraw/element";
|
import { isFrameLikeElement } from "@excalidraw/element";
|
||||||
|
|
||||||
import { CODES, KEYS, arrayToMap, getShortcutKey } from "@excalidraw/common";
|
import { CODES, KEYS, arrayToMap } from "@excalidraw/common";
|
||||||
|
|
||||||
import { updateFrameMembershipOfSelectedElements } from "@excalidraw/element";
|
import { updateFrameMembershipOfSelectedElements } from "@excalidraw/element";
|
||||||
|
|
||||||
@@ -26,6 +26,8 @@ import { t } from "../i18n";
|
|||||||
|
|
||||||
import { isSomeElementSelected } from "../scene";
|
import { isSomeElementSelected } from "../scene";
|
||||||
|
|
||||||
|
import { getShortcutKey } from "../shortcut";
|
||||||
|
|
||||||
import { register } from "./register";
|
import { register } from "./register";
|
||||||
|
|
||||||
import type { AppClassProperties, AppState } from "../types";
|
import type { AppClassProperties, AppState } from "../types";
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import {
|
|||||||
KEYS,
|
KEYS,
|
||||||
MOBILE_ACTION_BUTTON_BG,
|
MOBILE_ACTION_BUTTON_BG,
|
||||||
arrayToMap,
|
arrayToMap,
|
||||||
getShortcutKey,
|
|
||||||
} from "@excalidraw/common";
|
} from "@excalidraw/common";
|
||||||
|
|
||||||
import { getNonDeletedElements } from "@excalidraw/element";
|
import { getNonDeletedElements } from "@excalidraw/element";
|
||||||
@@ -26,6 +25,7 @@ import { DuplicateIcon } from "../components/icons";
|
|||||||
|
|
||||||
import { t } from "../i18n";
|
import { t } from "../i18n";
|
||||||
import { isSomeElementSelected } from "../scene";
|
import { isSomeElementSelected } from "../scene";
|
||||||
|
import { getShortcutKey } from "../shortcut";
|
||||||
|
|
||||||
import { register } from "./register";
|
import { register } from "./register";
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import {
|
|||||||
replaceAllElementsInFrame,
|
replaceAllElementsInFrame,
|
||||||
} from "@excalidraw/element";
|
} from "@excalidraw/element";
|
||||||
|
|
||||||
import { KEYS, randomId, arrayToMap, getShortcutKey } from "@excalidraw/common";
|
import { KEYS, randomId, arrayToMap } from "@excalidraw/common";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
getSelectedGroupIds,
|
getSelectedGroupIds,
|
||||||
@@ -43,6 +43,8 @@ import { t } from "../i18n";
|
|||||||
|
|
||||||
import { isSomeElementSelected } from "../scene";
|
import { isSomeElementSelected } from "../scene";
|
||||||
|
|
||||||
|
import { getShortcutKey } from "../shortcut";
|
||||||
|
|
||||||
import { register } from "./register";
|
import { register } from "./register";
|
||||||
|
|
||||||
import type { AppClassProperties, AppState } from "../types";
|
import type { AppClassProperties, AppState } from "../types";
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { isEmbeddableElement } from "@excalidraw/element";
|
import { isEmbeddableElement } from "@excalidraw/element";
|
||||||
|
|
||||||
import { KEYS, getShortcutKey } from "@excalidraw/common";
|
import { KEYS } from "@excalidraw/common";
|
||||||
|
|
||||||
import { CaptureUpdateAction } from "@excalidraw/element";
|
import { CaptureUpdateAction } from "@excalidraw/element";
|
||||||
|
|
||||||
@@ -8,8 +8,8 @@ import { ToolButton } from "../components/ToolButton";
|
|||||||
import { getContextMenuLabel } from "../components/hyperlink/Hyperlink";
|
import { getContextMenuLabel } from "../components/hyperlink/Hyperlink";
|
||||||
import { LinkIcon } from "../components/icons";
|
import { LinkIcon } from "../components/icons";
|
||||||
import { t } from "../i18n";
|
import { t } from "../i18n";
|
||||||
|
|
||||||
import { getSelectedElements } from "../scene";
|
import { getSelectedElements } from "../scene";
|
||||||
|
import { getShortcutKey } from "../shortcut";
|
||||||
|
|
||||||
import { register } from "./register";
|
import { register } from "./register";
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ import {
|
|||||||
randomInteger,
|
randomInteger,
|
||||||
arrayToMap,
|
arrayToMap,
|
||||||
getFontFamilyString,
|
getFontFamilyString,
|
||||||
getShortcutKey,
|
|
||||||
getLineHeight,
|
getLineHeight,
|
||||||
isTransparent,
|
isTransparent,
|
||||||
reduceToCommonValue,
|
reduceToCommonValue,
|
||||||
@@ -142,6 +141,8 @@ import {
|
|||||||
restoreCaretPosition,
|
restoreCaretPosition,
|
||||||
} from "../hooks/useTextEditorFocus";
|
} from "../hooks/useTextEditorFocus";
|
||||||
|
|
||||||
|
import { getShortcutKey } from "../shortcut";
|
||||||
|
|
||||||
import { register } from "./register";
|
import { register } from "./register";
|
||||||
|
|
||||||
import type { AppClassProperties, AppState, Primitive } from "../types";
|
import type { AppClassProperties, AppState, Primitive } from "../types";
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { KEYS, CODES, getShortcutKey, isDarwin } from "@excalidraw/common";
|
import { KEYS, CODES, isDarwin } from "@excalidraw/common";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
moveOneLeft,
|
moveOneLeft,
|
||||||
@@ -16,6 +16,7 @@ import {
|
|||||||
SendToBackIcon,
|
SendToBackIcon,
|
||||||
} from "../components/icons";
|
} from "../components/icons";
|
||||||
import { t } from "../i18n";
|
import { t } from "../i18n";
|
||||||
|
import { getShortcutKey } from "../shortcut";
|
||||||
|
|
||||||
import { register } from "./register";
|
import { register } from "./register";
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import { isDarwin, getShortcutKey } from "@excalidraw/common";
|
import { isDarwin } from "@excalidraw/common";
|
||||||
|
|
||||||
import type { SubtypeOf } from "@excalidraw/common/utility-types";
|
import type { SubtypeOf } from "@excalidraw/common/utility-types";
|
||||||
|
|
||||||
import { t } from "../i18n";
|
import { t } from "../i18n";
|
||||||
|
import { getShortcutKey } from "../shortcut";
|
||||||
|
|
||||||
import type { ActionName } from "./types";
|
import type { ActionName } from "./types";
|
||||||
|
|
||||||
|
|||||||
@@ -80,7 +80,6 @@ import {
|
|||||||
wrapEvent,
|
wrapEvent,
|
||||||
updateObject,
|
updateObject,
|
||||||
updateActiveTool,
|
updateActiveTool,
|
||||||
getShortcutKey,
|
|
||||||
isTransparent,
|
isTransparent,
|
||||||
easeToValuesRAF,
|
easeToValuesRAF,
|
||||||
muteFSAbortError,
|
muteFSAbortError,
|
||||||
@@ -406,6 +405,8 @@ import { LassoTrail } from "../lasso";
|
|||||||
|
|
||||||
import { EraserTrail } from "../eraser";
|
import { EraserTrail } from "../eraser";
|
||||||
|
|
||||||
|
import { getShortcutKey } from "../shortcut";
|
||||||
|
|
||||||
import ConvertElementTypePopup, {
|
import ConvertElementTypePopup, {
|
||||||
getConversionTypeFromElements,
|
getConversionTypeFromElements,
|
||||||
convertElementTypePopupAtom,
|
convertElementTypePopupAtom,
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
import { useCallback, useEffect, useRef, useState } from "react";
|
import { useCallback, useEffect, useRef, useState } from "react";
|
||||||
|
|
||||||
import { KEYS, getShortcutKey } from "@excalidraw/common";
|
import { KEYS } from "@excalidraw/common";
|
||||||
|
|
||||||
|
import { getShortcutKey } from "../..//shortcut";
|
||||||
import { useAtom } from "../../editor-jotai";
|
import { useAtom } from "../../editor-jotai";
|
||||||
import { t } from "../../i18n";
|
import { t } from "../../i18n";
|
||||||
import { useDevice } from "../App";
|
import { useDevice } from "../App";
|
||||||
|
|||||||
@@ -7,12 +7,13 @@ import {
|
|||||||
EVENT,
|
EVENT,
|
||||||
KEYS,
|
KEYS,
|
||||||
capitalizeString,
|
capitalizeString,
|
||||||
getShortcutKey,
|
|
||||||
isWritableElement,
|
isWritableElement,
|
||||||
} from "@excalidraw/common";
|
} from "@excalidraw/common";
|
||||||
|
|
||||||
import { actionToggleShapeSwitch } from "@excalidraw/excalidraw/actions/actionToggleShapeSwitch";
|
import { actionToggleShapeSwitch } from "@excalidraw/excalidraw/actions/actionToggleShapeSwitch";
|
||||||
|
|
||||||
|
import { getShortcutKey } from "@excalidraw/excalidraw/shortcut";
|
||||||
|
|
||||||
import type { MarkRequired } from "@excalidraw/common/utility-types";
|
import type { MarkRequired } from "@excalidraw/common/utility-types";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
|||||||
@@ -2,11 +2,12 @@ import React from "react";
|
|||||||
|
|
||||||
import { isDarwin, isFirefox, isWindows } from "@excalidraw/common";
|
import { isDarwin, isFirefox, isWindows } from "@excalidraw/common";
|
||||||
|
|
||||||
import { KEYS, getShortcutKey } from "@excalidraw/common";
|
import { KEYS } from "@excalidraw/common";
|
||||||
|
|
||||||
import { getShortcutFromShortcutName } from "../actions/shortcuts";
|
import { getShortcutFromShortcutName } from "../actions/shortcuts";
|
||||||
import { probablySupportsClipboardBlob } from "../clipboard";
|
import { probablySupportsClipboardBlob } from "../clipboard";
|
||||||
import { t } from "../i18n";
|
import { t } from "../i18n";
|
||||||
|
import { getShortcutKey } from "../shortcut";
|
||||||
|
|
||||||
import { Dialog } from "./Dialog";
|
import { Dialog } from "./Dialog";
|
||||||
import { ExternalLinkIcon, GithubIcon, youtubeIcon } from "./icons";
|
import { ExternalLinkIcon, GithubIcon, youtubeIcon } from "./icons";
|
||||||
|
|||||||
@@ -28,11 +28,24 @@ $wide-viewport-width: 1000px;
|
|||||||
> span {
|
> span {
|
||||||
padding: 0.25rem;
|
padding: 0.25rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
kbd {
|
||||||
|
display: inline-block;
|
||||||
|
margin: 0 1px;
|
||||||
|
font-family: monospace;
|
||||||
|
border: 1px solid var(--color-gray-40);
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 1px 3px;
|
||||||
|
font-size: 10px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.theme--dark {
|
&.theme--dark {
|
||||||
.HintViewer {
|
.HintViewer {
|
||||||
color: var(--color-gray-60);
|
color: var(--color-gray-60);
|
||||||
|
kbd {
|
||||||
|
border-color: var(--color-gray-60);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,11 +9,10 @@ import {
|
|||||||
isTextElement,
|
isTextElement,
|
||||||
} from "@excalidraw/element";
|
} from "@excalidraw/element";
|
||||||
|
|
||||||
import { getShortcutKey } from "@excalidraw/common";
|
|
||||||
|
|
||||||
import { isNodeInFlowchart } from "@excalidraw/element";
|
import { isNodeInFlowchart } from "@excalidraw/element";
|
||||||
|
|
||||||
import { t } from "../i18n";
|
import { t } from "../i18n";
|
||||||
|
import { getShortcutKey } from "../shortcut";
|
||||||
import { isEraserActive } from "../appState";
|
import { isEraserActive } from "../appState";
|
||||||
import { isGridModeEnabled } from "../snapping";
|
import { isGridModeEnabled } from "../snapping";
|
||||||
|
|
||||||
@@ -28,6 +27,11 @@ interface HintViewerProps {
|
|||||||
app: AppClassProperties;
|
app: AppClassProperties;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getTaggedShortcutKey = (key: string | string[]) =>
|
||||||
|
Array.isArray(key)
|
||||||
|
? `<kbd>${key.map(getShortcutKey).join(" + ")}</kbd>`
|
||||||
|
: `<kbd>${getShortcutKey(key)}</kbd>`;
|
||||||
|
|
||||||
const getHints = ({
|
const getHints = ({
|
||||||
appState,
|
appState,
|
||||||
isMobile,
|
isMobile,
|
||||||
@@ -42,7 +46,9 @@ const getHints = ({
|
|||||||
appState.openSidebar.tab === CANVAS_SEARCH_TAB &&
|
appState.openSidebar.tab === CANVAS_SEARCH_TAB &&
|
||||||
appState.searchMatches?.matches.length
|
appState.searchMatches?.matches.length
|
||||||
) {
|
) {
|
||||||
return t("hints.dismissSearch");
|
return t("hints.dismissSearch", {
|
||||||
|
shortcut: getTaggedShortcutKey("Escape"),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (appState.openSidebar && !device.editor.canFitSidebar) {
|
if (appState.openSidebar && !device.editor.canFitSidebar) {
|
||||||
@@ -50,14 +56,21 @@ const getHints = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (isEraserActive(appState)) {
|
if (isEraserActive(appState)) {
|
||||||
return t("hints.eraserRevert");
|
return t("hints.eraserRevert", {
|
||||||
|
shortcut: getTaggedShortcutKey("Alt"),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
if (activeTool.type === "arrow" || activeTool.type === "line") {
|
if (activeTool.type === "arrow" || activeTool.type === "line") {
|
||||||
if (multiMode) {
|
if (multiMode) {
|
||||||
return t("hints.linearElementMulti");
|
return t("hints.linearElementMulti", {
|
||||||
|
shortcut_1: getTaggedShortcutKey("Escape"),
|
||||||
|
shortcut_2: getTaggedShortcutKey("Enter"),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
if (activeTool.type === "arrow") {
|
if (activeTool.type === "arrow") {
|
||||||
return t("hints.arrowTool", { arrowShortcut: getShortcutKey("A") });
|
return t("hints.arrowTool", {
|
||||||
|
shortcut: getTaggedShortcutKey("A"),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
return t("hints.linearElement");
|
return t("hints.linearElement");
|
||||||
}
|
}
|
||||||
@@ -83,31 +96,51 @@ const getHints = ({
|
|||||||
) {
|
) {
|
||||||
const targetElement = selectedElements[0];
|
const targetElement = selectedElements[0];
|
||||||
if (isLinearElement(targetElement) && targetElement.points.length === 2) {
|
if (isLinearElement(targetElement) && targetElement.points.length === 2) {
|
||||||
return t("hints.lockAngle");
|
return t("hints.lockAngle", {
|
||||||
|
shortcut: getTaggedShortcutKey("Shift"),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
return isImageElement(targetElement)
|
return isImageElement(targetElement)
|
||||||
? t("hints.resizeImage")
|
? t("hints.resizeImage", {
|
||||||
: t("hints.resize");
|
shortcut_1: getTaggedShortcutKey("Shift"),
|
||||||
|
shortcut_2: getTaggedShortcutKey("Alt"),
|
||||||
|
})
|
||||||
|
: t("hints.resize", {
|
||||||
|
shortcut_1: getTaggedShortcutKey("Shift"),
|
||||||
|
shortcut_2: getTaggedShortcutKey("Alt"),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isRotating && lastPointerDownWith === "mouse") {
|
if (isRotating && lastPointerDownWith === "mouse") {
|
||||||
return t("hints.rotate");
|
return t("hints.rotate", {
|
||||||
|
shortcut: getTaggedShortcutKey("Shift"),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (selectedElements.length === 1 && isTextElement(selectedElements[0])) {
|
if (selectedElements.length === 1 && isTextElement(selectedElements[0])) {
|
||||||
return t("hints.text_selected");
|
return t("hints.text_selected", {
|
||||||
|
shortcut: getTaggedShortcutKey("Enter"),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (appState.editingTextElement) {
|
if (appState.editingTextElement) {
|
||||||
return t("hints.text_editing");
|
return t("hints.text_editing", {
|
||||||
|
shortcut_1: getTaggedShortcutKey("Escape"),
|
||||||
|
shortcut_2: getTaggedShortcutKey(["CtrlOrCmd", "Enter"]),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (appState.croppingElementId) {
|
if (appState.croppingElementId) {
|
||||||
return t("hints.leaveCropEditor");
|
return t("hints.leaveCropEditor", {
|
||||||
|
shortcut_1: getTaggedShortcutKey("Enter"),
|
||||||
|
shortcut_2: getTaggedShortcutKey("Escape"),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (selectedElements.length === 1 && isImageElement(selectedElements[0])) {
|
if (selectedElements.length === 1 && isImageElement(selectedElements[0])) {
|
||||||
return t("hints.enterCropEditor");
|
return t("hints.enterCropEditor", {
|
||||||
|
shortcut: getTaggedShortcutKey("Enter"),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (activeTool.type === "selection") {
|
if (activeTool.type === "selection") {
|
||||||
@@ -117,33 +150,60 @@ const getHints = ({
|
|||||||
!appState.editingTextElement &&
|
!appState.editingTextElement &&
|
||||||
!appState.selectedLinearElement?.isEditing
|
!appState.selectedLinearElement?.isEditing
|
||||||
) {
|
) {
|
||||||
return [t("hints.deepBoxSelect")];
|
return [
|
||||||
|
t("hints.deepBoxSelect", {
|
||||||
|
shortcut: getTaggedShortcutKey("CtrlOrCmd"),
|
||||||
|
}),
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isGridModeEnabled(app) && appState.selectedElementsAreBeingDragged) {
|
if (isGridModeEnabled(app) && appState.selectedElementsAreBeingDragged) {
|
||||||
return t("hints.disableSnapping");
|
return t("hints.disableSnapping", {
|
||||||
|
shortcut: getTaggedShortcutKey("CtrlOrCmd"),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!selectedElements.length && !isMobile) {
|
if (!selectedElements.length && !isMobile) {
|
||||||
return [t("hints.canvasPanning")];
|
return [
|
||||||
|
t("hints.canvasPanning", {
|
||||||
|
shortcut: getTaggedShortcutKey("Space"),
|
||||||
|
}),
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (selectedElements.length === 1) {
|
if (selectedElements.length === 1) {
|
||||||
if (isLinearElement(selectedElements[0])) {
|
if (isLinearElement(selectedElements[0])) {
|
||||||
if (appState.selectedLinearElement?.isEditing) {
|
if (appState.selectedLinearElement?.isEditing) {
|
||||||
return appState.selectedLinearElement.selectedPointsIndices
|
return appState.selectedLinearElement.selectedPointsIndices
|
||||||
? t("hints.lineEditor_pointSelected")
|
? t("hints.lineEditor_pointSelected", {
|
||||||
: t("hints.lineEditor_nothingSelected");
|
shortcut_1: getTaggedShortcutKey("Delete"),
|
||||||
|
shortcut_2: getTaggedShortcutKey(["CtrlOrCmd", "D"]),
|
||||||
|
})
|
||||||
|
: t("hints.lineEditor_nothingSelected", {
|
||||||
|
shortcut_1: getTaggedShortcutKey("Shift"),
|
||||||
|
shortcut_2: getTaggedShortcutKey("Alt"),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
return isLineElement(selectedElements[0])
|
return isLineElement(selectedElements[0])
|
||||||
? t("hints.lineEditor_line_info")
|
? t("hints.lineEditor_line_info", {
|
||||||
: t("hints.lineEditor_info");
|
shortcut: getTaggedShortcutKey("Enter"),
|
||||||
|
})
|
||||||
|
: t("hints.lineEditor_info", {
|
||||||
|
shortcut_1: getTaggedShortcutKey("CtrlOrCmd"),
|
||||||
|
shortcut_2: getTaggedShortcutKey(["CtrlOrCmd", "Enter"]),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
!appState.newElement &&
|
!appState.newElement &&
|
||||||
!appState.selectedElementsAreBeingDragged &&
|
!appState.selectedElementsAreBeingDragged &&
|
||||||
isTextBindableContainer(selectedElements[0])
|
isTextBindableContainer(selectedElements[0])
|
||||||
) {
|
) {
|
||||||
|
const bindTextToElement = t("hints.bindTextToElement", {
|
||||||
|
shortcut: getTaggedShortcutKey("Enter"),
|
||||||
|
});
|
||||||
|
const createFlowchart = t("hints.createFlowchart", {
|
||||||
|
shortcut: getTaggedShortcutKey("CtrlOrCmd"),
|
||||||
|
});
|
||||||
if (isFlowchartNodeElement(selectedElements[0])) {
|
if (isFlowchartNodeElement(selectedElements[0])) {
|
||||||
if (
|
if (
|
||||||
isNodeInFlowchart(
|
isNodeInFlowchart(
|
||||||
@@ -151,13 +211,13 @@ const getHints = ({
|
|||||||
app.scene.getNonDeletedElementsMap(),
|
app.scene.getNonDeletedElementsMap(),
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
return [t("hints.bindTextToElement"), t("hints.createFlowchart")];
|
return [bindTextToElement, createFlowchart];
|
||||||
}
|
}
|
||||||
|
|
||||||
return [t("hints.bindTextToElement"), t("hints.createFlowchart")];
|
return [bindTextToElement, createFlowchart];
|
||||||
}
|
}
|
||||||
|
|
||||||
return t("hints.bindTextToElement");
|
return bindTextToElement;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -183,16 +243,21 @@ export const HintViewer = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const hint = Array.isArray(hints)
|
const hint = Array.isArray(hints)
|
||||||
? hints
|
? hints.map((hint) => hint.replace(/\. ?$/, "")).join(". ")
|
||||||
.map((hint) => {
|
: hints;
|
||||||
return getShortcutKey(hint).replace(/\. ?$/, "");
|
|
||||||
})
|
const hintJSX = hint.split(/(<kbd>[^<]+<\/kbd>)/g).map((part, index) => {
|
||||||
.join(". ")
|
if (index % 2 === 1) {
|
||||||
: getShortcutKey(hints);
|
const shortcutMatch =
|
||||||
|
part[0] === "<" && part.match(/^<kbd>([^<]+)<\/kbd>$/);
|
||||||
|
return <kbd key={index}>{shortcutMatch ? shortcutMatch[1] : part}</kbd>;
|
||||||
|
}
|
||||||
|
return part;
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="HintViewer">
|
<div className="HintViewer">
|
||||||
<span>{hint}</span>
|
<span>{hintJSX}</span>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { getShortcutKey } from "@excalidraw/common";
|
import { getShortcutKey } from "@excalidraw/excalidraw/shortcut";
|
||||||
|
|
||||||
export const TTDDialogSubmitShortcut = () => {
|
export const TTDDialogSubmitShortcut = () => {
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -337,33 +337,33 @@
|
|||||||
"shapes": "Shapes"
|
"shapes": "Shapes"
|
||||||
},
|
},
|
||||||
"hints": {
|
"hints": {
|
||||||
"dismissSearch": "Escape to dismiss search",
|
"dismissSearch": "{{shortcut}} to dismiss search",
|
||||||
"canvasPanning": "To move canvas, hold mouse wheel or spacebar while dragging, or use the hand tool",
|
"canvasPanning": "To move canvas, hold mouse wheel or {{shortcut}} while dragging, or use the hand tool",
|
||||||
"linearElement": "Click to start multiple points, drag for single line",
|
"linearElement": "Click to start multiple points, drag for single line",
|
||||||
"arrowTool": "Click to start multiple points, drag for single line. Press {{arrowShortcut}} again to change arrow type.",
|
"arrowTool": "Click to start multiple points, drag for single line. Press {{shortcut}} again to change arrow type.",
|
||||||
"freeDraw": "Click and drag, release when you're finished",
|
"freeDraw": "Click and drag, release when you're finished",
|
||||||
"text": "Tip: you can also add text by double-clicking anywhere with the selection tool",
|
"text": "Tip: you can also add text by double-clicking anywhere with the selection tool",
|
||||||
"embeddable": "Click-drag to create a website embed",
|
"embeddable": "Click-drag to create a website embed",
|
||||||
"text_selected": "Double-click or press ENTER to edit text",
|
"text_selected": "Double-click or press {{shortcut}} to edit text",
|
||||||
"text_editing": "Press Escape or CtrlOrCmd+ENTER to finish editing",
|
"text_editing": "Press {{shortcut_1}} or {{shortcut_2}} to finish editing",
|
||||||
"linearElementMulti": "Click on last point or press Escape or Enter to finish",
|
"linearElementMulti": "Click on last point or press {{shortcut_1}} or {{shortcut_2}} to finish",
|
||||||
"lockAngle": "You can constrain angle by holding SHIFT",
|
"lockAngle": "You can constrain angle by holding {{shortcut}}",
|
||||||
"resize": "You can constrain proportions by holding SHIFT while resizing,\nhold ALT to resize from the center",
|
"resize": "You can constrain proportions by holding {{shortcut_1}} while resizing,\nhold {{shortcut_2}} to resize from the center",
|
||||||
"resizeImage": "You can resize freely by holding SHIFT,\nhold ALT to resize from the center",
|
"resizeImage": "You can resize freely by holding {{shortcut_1}},\nhold {{shortcut_2}} to resize from the center",
|
||||||
"rotate": "You can constrain angles by holding SHIFT while rotating",
|
"rotate": "You can constrain angles by holding {{shortcut}} while rotating",
|
||||||
"lineEditor_info": "Hold CtrlOrCmd and Double-click or press CtrlOrCmd + Enter to edit points",
|
"lineEditor_info": "Hold {{shortcut_1}} and Double-click or press {{shortcut_2}} to edit points",
|
||||||
"lineEditor_line_info": "Double-click or press Enter to edit points",
|
"lineEditor_line_info": "Double-click or press {{shortcut}} to edit points",
|
||||||
"lineEditor_pointSelected": "Press Delete to remove point(s),\nCtrlOrCmd+D to duplicate, or drag to move",
|
"lineEditor_pointSelected": "Press {{shortcut_1}} to remove point(s),\n{{shortcut_2}} to duplicate, or drag to move",
|
||||||
"lineEditor_nothingSelected": "Select a point to edit (hold SHIFT to select multiple),\nor hold Alt and click to add new points",
|
"lineEditor_nothingSelected": "Select a point to edit (hold {{shortcut_1}} to select multiple),\nor hold {{shortcut_2}} and click to add new points",
|
||||||
"publishLibrary": "Publish your own library",
|
"publishLibrary": "Publish your own library",
|
||||||
"bindTextToElement": "Press enter to add text",
|
"bindTextToElement": "Press {{shortcut}} to add text",
|
||||||
"createFlowchart": "Hold CtrlOrCmd and Arrow key to create a flowchart",
|
"createFlowchart": "Hold {{shortcut}} and Arrow key to create a flowchart",
|
||||||
"deepBoxSelect": "Hold CtrlOrCmd to deep select, and to prevent dragging",
|
"deepBoxSelect": "Hold {{shortcut}} to deep select, and to prevent dragging",
|
||||||
"eraserRevert": "Hold Alt to revert the elements marked for deletion",
|
"eraserRevert": "Hold {{shortcut}} to revert the elements marked for deletion",
|
||||||
"firefox_clipboard_write": "This feature can likely be enabled by setting the \"dom.events.asyncClipboard.clipboardItem\" flag to \"true\". To change the browser flags in Firefox, visit the \"about:config\" page.",
|
"firefox_clipboard_write": "This feature can likely be enabled by setting the \"dom.events.asyncClipboard.clipboardItem\" flag to \"true\". To change the browser flags in Firefox, visit the \"about:config\" page.",
|
||||||
"disableSnapping": "Hold CtrlOrCmd to disable snapping",
|
"disableSnapping": "Hold {{shortcut}} to disable snapping",
|
||||||
"enterCropEditor": "Double click the image or press ENTER to crop the image",
|
"enterCropEditor": "Double click the image or press {{shortcut}} to crop the image",
|
||||||
"leaveCropEditor": "Click outside the image or press ENTER or ESCAPE to finish cropping"
|
"leaveCropEditor": "Click outside the image or press {{shortcut_1}} or {{shortcut_2}} to finish cropping"
|
||||||
},
|
},
|
||||||
"canvasError": {
|
"canvasError": {
|
||||||
"cannotShowPreview": "Cannot show preview",
|
"cannotShowPreview": "Cannot show preview",
|
||||||
@@ -648,5 +648,16 @@
|
|||||||
},
|
},
|
||||||
"itemNotAvailable": "Command is not available...",
|
"itemNotAvailable": "Command is not available...",
|
||||||
"shortcutHint": "For Command palette, use {{shortcut}}"
|
"shortcutHint": "For Command palette, use {{shortcut}}"
|
||||||
|
},
|
||||||
|
"keys": {
|
||||||
|
"ctrl": "Ctrl",
|
||||||
|
"option": "Option",
|
||||||
|
"cmd": "Cmd",
|
||||||
|
"alt": "Alt",
|
||||||
|
"escape": "Esc",
|
||||||
|
"enter": "Enter",
|
||||||
|
"shift": "Shift",
|
||||||
|
"spacebar": "Space",
|
||||||
|
"delete": "Delete"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
import { isDarwin } from "@excalidraw/common";
|
||||||
|
|
||||||
|
import { t } from "./i18n";
|
||||||
|
|
||||||
|
export const getShortcutKey = (shortcut: string): string =>
|
||||||
|
shortcut
|
||||||
|
.replace(
|
||||||
|
/\b(Opt(?:ion)?|Alt)\b/i,
|
||||||
|
isDarwin ? t("keys.option") : t("keys.alt"),
|
||||||
|
)
|
||||||
|
.replace(/\bShift\b/i, t("keys.shift"))
|
||||||
|
.replace(/\b(Enter|Return)\b/i, t("keys.enter"))
|
||||||
|
.replace(
|
||||||
|
/\b(Ctrl|Cmd|Command|CtrlOrCmd)\b/gi,
|
||||||
|
isDarwin ? t("keys.cmd") : t("keys.ctrl"),
|
||||||
|
)
|
||||||
|
.replace(/\b(Esc(?:ape)?)\b/i, t("keys.escape"))
|
||||||
|
.replace(/\b(Space(?:bar)?)\b/i, t("keys.spacebar"))
|
||||||
|
.replace(/\b(Del(?:ete)?)\b/i, t("keys.delete"));
|
||||||
Reference in New Issue
Block a user