feat: add hand/panning tool (#6141)
* feat: add hand/panning tool * move hand tool right of tool lock separator * tweak i18n * rename `panning` -> `hand` * toggle between last tool and hand on `H` shortcut * hide properties sidebar when `hand` active * revert to rendering HandButton manually due to mobile toolbar
This commit is contained in:
parent
849e6a0c86
commit
d4afd66268
@ -1,7 +1,7 @@
|
||||
import { ColorPicker } from "../components/ColorPicker";
|
||||
import { eraser, ZoomInIcon, ZoomOutIcon } from "../components/icons";
|
||||
import { ZoomInIcon, ZoomOutIcon } from "../components/icons";
|
||||
import { ToolButton } from "../components/ToolButton";
|
||||
import { MIN_ZOOM, THEME, ZOOM_STEP } from "../constants";
|
||||
import { CURSOR_TYPE, MIN_ZOOM, THEME, ZOOM_STEP } from "../constants";
|
||||
import { getCommonBounds, getNonDeletedElements } from "../element";
|
||||
import { ExcalidrawElement } from "../element/types";
|
||||
import { t } from "../i18n";
|
||||
@ -10,12 +10,15 @@ import { getNormalizedZoom, getSelectedElements } from "../scene";
|
||||
import { centerScrollOn } from "../scene/scroll";
|
||||
import { getStateForZoom } from "../scene/zoom";
|
||||
import { AppState, NormalizedZoomValue } from "../types";
|
||||
import { getShortcutKey, updateActiveTool } from "../utils";
|
||||
import { getShortcutKey, setCursor, updateActiveTool } from "../utils";
|
||||
import { register } from "./register";
|
||||
import { Tooltip } from "../components/Tooltip";
|
||||
import { newElementWith } from "../element/mutateElement";
|
||||
import { getDefaultAppState, isEraserActive } from "../appState";
|
||||
import clsx from "clsx";
|
||||
import {
|
||||
getDefaultAppState,
|
||||
isEraserActive,
|
||||
isHandToolActive,
|
||||
} from "../appState";
|
||||
|
||||
export const actionChangeViewBackgroundColor = register({
|
||||
name: "changeViewBackgroundColor",
|
||||
@ -306,15 +309,15 @@ export const actionToggleTheme = register({
|
||||
},
|
||||
});
|
||||
|
||||
export const actionErase = register({
|
||||
name: "eraser",
|
||||
export const actionToggleEraserTool = register({
|
||||
name: "toggleEraserTool",
|
||||
trackEvent: { category: "toolbar" },
|
||||
perform: (elements, appState) => {
|
||||
let activeTool: AppState["activeTool"];
|
||||
|
||||
if (isEraserActive(appState)) {
|
||||
activeTool = updateActiveTool(appState, {
|
||||
...(appState.activeTool.lastActiveToolBeforeEraser || {
|
||||
...(appState.activeTool.lastActiveTool || {
|
||||
type: "selection",
|
||||
}),
|
||||
lastActiveToolBeforeEraser: null,
|
||||
@ -337,17 +340,38 @@ export const actionErase = register({
|
||||
};
|
||||
},
|
||||
keyTest: (event) => event.key === KEYS.E,
|
||||
PanelComponent: ({ elements, appState, updateData, data }) => (
|
||||
<ToolButton
|
||||
type="button"
|
||||
icon={eraser}
|
||||
className={clsx("eraser", { active: isEraserActive(appState) })}
|
||||
title={`${t("toolBar.eraser")}-${getShortcutKey("E")}`}
|
||||
aria-label={t("toolBar.eraser")}
|
||||
onClick={() => {
|
||||
updateData(null);
|
||||
}}
|
||||
size={data?.size || "medium"}
|
||||
></ToolButton>
|
||||
),
|
||||
});
|
||||
|
||||
export const actionToggleHandTool = register({
|
||||
name: "toggleHandTool",
|
||||
trackEvent: { category: "toolbar" },
|
||||
perform: (elements, appState, _, app) => {
|
||||
let activeTool: AppState["activeTool"];
|
||||
|
||||
if (isHandToolActive(appState)) {
|
||||
activeTool = updateActiveTool(appState, {
|
||||
...(appState.activeTool.lastActiveTool || {
|
||||
type: "selection",
|
||||
}),
|
||||
lastActiveToolBeforeEraser: null,
|
||||
});
|
||||
} else {
|
||||
activeTool = updateActiveTool(appState, {
|
||||
type: "hand",
|
||||
lastActiveToolBeforeEraser: appState.activeTool,
|
||||
});
|
||||
setCursor(app.canvas, CURSOR_TYPE.GRAB);
|
||||
}
|
||||
|
||||
return {
|
||||
appState: {
|
||||
...appState,
|
||||
selectedElementIds: {},
|
||||
selectedGroupIds: {},
|
||||
activeTool,
|
||||
},
|
||||
commitToHistory: true,
|
||||
};
|
||||
},
|
||||
keyTest: (event) => event.key === KEYS.H,
|
||||
});
|
||||
|
@ -145,7 +145,7 @@ export const actionFinalize = register({
|
||||
let activeTool: AppState["activeTool"];
|
||||
if (appState.activeTool.type === "eraser") {
|
||||
activeTool = updateActiveTool(appState, {
|
||||
...(appState.activeTool.lastActiveToolBeforeEraser || {
|
||||
...(appState.activeTool.lastActiveTool || {
|
||||
type: "selection",
|
||||
}),
|
||||
lastActiveToolBeforeEraser: null,
|
||||
|
@ -109,10 +109,11 @@ export type ActionName =
|
||||
| "decreaseFontSize"
|
||||
| "unbindText"
|
||||
| "hyperlink"
|
||||
| "eraser"
|
||||
| "bindText"
|
||||
| "toggleLock"
|
||||
| "toggleLinearEditor";
|
||||
| "toggleLinearEditor"
|
||||
| "toggleEraserTool"
|
||||
| "toggleHandTool";
|
||||
|
||||
export type PanelComponentProps = {
|
||||
elements: readonly ExcalidrawElement[];
|
||||
|
@ -45,7 +45,7 @@ export const getDefaultAppState = (): Omit<
|
||||
type: "selection",
|
||||
customType: null,
|
||||
locked: false,
|
||||
lastActiveToolBeforeEraser: null,
|
||||
lastActiveTool: null,
|
||||
},
|
||||
penMode: false,
|
||||
penDetected: false,
|
||||
@ -228,3 +228,11 @@ export const isEraserActive = ({
|
||||
}: {
|
||||
activeTool: AppState["activeTool"];
|
||||
}) => activeTool.type === "eraser";
|
||||
|
||||
export const isHandToolActive = ({
|
||||
activeTool,
|
||||
}: {
|
||||
activeTool: AppState["activeTool"];
|
||||
}) => {
|
||||
return activeTool.type === "hand";
|
||||
};
|
||||
|
@ -219,9 +219,10 @@ export const ShapesSwitcher = ({
|
||||
<>
|
||||
{SHAPES.map(({ value, icon, key, numericKey, fillable }, index) => {
|
||||
const label = t(`toolBar.${value}`);
|
||||
const letter = key && (typeof key === "string" ? key : key[0]);
|
||||
const letter =
|
||||
key && capitalizeString(typeof key === "string" ? key : key[0]);
|
||||
const shortcut = letter
|
||||
? `${capitalizeString(letter)} ${t("helpDialog.or")} ${numericKey}`
|
||||
? `${letter} ${t("helpDialog.or")} ${numericKey}`
|
||||
: `${numericKey}`;
|
||||
return (
|
||||
<ToolButton
|
||||
@ -232,7 +233,7 @@ export const ShapesSwitcher = ({
|
||||
checked={activeTool.type === value}
|
||||
name="editor-current-shape"
|
||||
title={`${capitalizeString(label)} — ${shortcut}`}
|
||||
keyBindingLabel={numericKey}
|
||||
keyBindingLabel={numericKey || letter}
|
||||
aria-label={capitalizeString(label)}
|
||||
aria-keyshortcuts={shortcut}
|
||||
data-testid={`toolbar-${value}`}
|
||||
|
@ -41,7 +41,11 @@ import { ActionManager } from "../actions/manager";
|
||||
import { actions } from "../actions/register";
|
||||
import { ActionResult } from "../actions/types";
|
||||
import { trackEvent } from "../analytics";
|
||||
import { getDefaultAppState, isEraserActive } from "../appState";
|
||||
import {
|
||||
getDefaultAppState,
|
||||
isEraserActive,
|
||||
isHandToolActive,
|
||||
} from "../appState";
|
||||
import { parseClipboard } from "../clipboard";
|
||||
import {
|
||||
APP_NAME,
|
||||
@ -274,6 +278,7 @@ import {
|
||||
import { shouldShowBoundingBox } from "../element/transformHandles";
|
||||
import { Fonts } from "../scene/Fonts";
|
||||
import { actionPaste } from "../actions/actionClipboard";
|
||||
import { actionToggleHandTool } from "../actions/actionCanvas";
|
||||
|
||||
const deviceContextInitialValue = {
|
||||
isSmScreen: false,
|
||||
@ -575,6 +580,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||
elements={this.scene.getNonDeletedElements()}
|
||||
onLockToggle={this.toggleLock}
|
||||
onPenModeToggle={this.togglePenMode}
|
||||
onHandToolToggle={this.onHandToolToggle}
|
||||
onInsertElements={(elements) =>
|
||||
this.addElementsFromPasteOrLibrary({
|
||||
elements,
|
||||
@ -1812,6 +1818,10 @@ class App extends React.Component<AppProps, AppState> {
|
||||
});
|
||||
};
|
||||
|
||||
onHandToolToggle = () => {
|
||||
this.actionManager.executeAction(actionToggleHandTool);
|
||||
};
|
||||
|
||||
scrollToContent = (
|
||||
target:
|
||||
| ExcalidrawElement
|
||||
@ -2229,11 +2239,13 @@ class App extends React.Component<AppProps, AppState> {
|
||||
|
||||
private setActiveTool = (
|
||||
tool:
|
||||
| { type: typeof SHAPES[number]["value"] | "eraser" }
|
||||
| { type: typeof SHAPES[number]["value"] | "eraser" | "hand" }
|
||||
| { type: "custom"; customType: string },
|
||||
) => {
|
||||
const nextActiveTool = updateActiveTool(this.state, tool);
|
||||
if (!isHoldingSpace) {
|
||||
if (nextActiveTool.type === "hand") {
|
||||
setCursor(this.canvas, CURSOR_TYPE.GRAB);
|
||||
} else if (!isHoldingSpace) {
|
||||
setCursorForShape(this.canvas, this.state);
|
||||
}
|
||||
if (isToolIcon(document.activeElement)) {
|
||||
@ -2904,7 +2916,12 @@ class App extends React.Component<AppProps, AppState> {
|
||||
null;
|
||||
}
|
||||
|
||||
if (isHoldingSpace || isPanning || isDraggingScrollBar) {
|
||||
if (
|
||||
isHoldingSpace ||
|
||||
isPanning ||
|
||||
isDraggingScrollBar ||
|
||||
isHandToolActive(this.state)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -3496,7 +3513,10 @@ class App extends React.Component<AppProps, AppState> {
|
||||
);
|
||||
} else if (this.state.activeTool.type === "custom") {
|
||||
setCursor(this.canvas, CURSOR_TYPE.AUTO);
|
||||
} else if (this.state.activeTool.type !== "eraser") {
|
||||
} else if (
|
||||
this.state.activeTool.type !== "eraser" &&
|
||||
this.state.activeTool.type !== "hand"
|
||||
) {
|
||||
this.createGenericElementOnPointerDown(
|
||||
this.state.activeTool.type,
|
||||
pointerDownState,
|
||||
@ -3607,6 +3627,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||
gesture.pointers.size <= 1 &&
|
||||
(event.button === POINTER_BUTTON.WHEEL ||
|
||||
(event.button === POINTER_BUTTON.MAIN && isHoldingSpace) ||
|
||||
isHandToolActive(this.state) ||
|
||||
this.state.viewModeEnabled)
|
||||
) ||
|
||||
isTextElement(this.state.editingElement)
|
||||
|
32
src/components/HandButton.tsx
Normal file
32
src/components/HandButton.tsx
Normal file
@ -0,0 +1,32 @@
|
||||
import "./ToolIcon.scss";
|
||||
|
||||
import clsx from "clsx";
|
||||
import { ToolButton } from "./ToolButton";
|
||||
import { handIcon } from "./icons";
|
||||
import { KEYS } from "../keys";
|
||||
|
||||
type LockIconProps = {
|
||||
title?: string;
|
||||
name?: string;
|
||||
checked: boolean;
|
||||
onChange?(): void;
|
||||
isMobile?: boolean;
|
||||
};
|
||||
|
||||
export const HandButton = (props: LockIconProps) => {
|
||||
return (
|
||||
<ToolButton
|
||||
className={clsx("Shape", { fillable: false })}
|
||||
type="radio"
|
||||
icon={handIcon}
|
||||
name="editor-current-shape"
|
||||
checked={props.checked}
|
||||
title={`${props.title} — H`}
|
||||
keyBindingLabel={!props.isMobile ? KEYS.H.toLocaleUpperCase() : undefined}
|
||||
aria-label={`${props.title} — H`}
|
||||
aria-keyshortcuts={KEYS.H}
|
||||
data-testid={`toolbar-hand`}
|
||||
onChange={() => props.onChange?.()}
|
||||
/>
|
||||
);
|
||||
};
|
@ -69,6 +69,10 @@ function* intersperse(as: JSX.Element[][], delim: string | null) {
|
||||
}
|
||||
}
|
||||
|
||||
const upperCaseSingleChars = (str: string) => {
|
||||
return str.replace(/\b[a-z]\b/, (c) => c.toUpperCase());
|
||||
};
|
||||
|
||||
const Shortcut = ({
|
||||
label,
|
||||
shortcuts,
|
||||
@ -83,7 +87,9 @@ const Shortcut = ({
|
||||
? [...shortcut.slice(0, -2).split("+"), "+"]
|
||||
: shortcut.split("+");
|
||||
|
||||
return keys.map((key) => <ShortcutKey key={key}>{key}</ShortcutKey>);
|
||||
return keys.map((key) => (
|
||||
<ShortcutKey key={key}>{upperCaseSingleChars(key)}</ShortcutKey>
|
||||
));
|
||||
});
|
||||
|
||||
return (
|
||||
@ -120,6 +126,7 @@ export const HelpDialog = ({ onClose }: { onClose?: () => void }) => {
|
||||
className="HelpDialog__island--tools"
|
||||
caption={t("helpDialog.tools")}
|
||||
>
|
||||
<Shortcut label={t("toolBar.hand")} shortcuts={[KEYS.H]} />
|
||||
<Shortcut
|
||||
label={t("toolBar.selection")}
|
||||
shortcuts={[KEYS.V, KEYS["1"]]}
|
||||
|
@ -50,6 +50,8 @@ import { hostSidebarCountersAtom } from "./Sidebar/Sidebar";
|
||||
import { jotaiScope } from "../jotai";
|
||||
import { useAtom } from "jotai";
|
||||
import MainMenu from "./main-menu/MainMenu";
|
||||
import { HandButton } from "./HandButton";
|
||||
import { isHandToolActive } from "../appState";
|
||||
|
||||
interface LayerUIProps {
|
||||
actionManager: ActionManager;
|
||||
@ -59,6 +61,7 @@ interface LayerUIProps {
|
||||
setAppState: React.Component<any, AppState>["setState"];
|
||||
elements: readonly NonDeletedExcalidrawElement[];
|
||||
onLockToggle: () => void;
|
||||
onHandToolToggle: () => void;
|
||||
onPenModeToggle: () => void;
|
||||
onInsertElements: (elements: readonly NonDeletedExcalidrawElement[]) => void;
|
||||
showExitZenModeBtn: boolean;
|
||||
@ -85,6 +88,7 @@ const LayerUI = ({
|
||||
elements,
|
||||
canvas,
|
||||
onLockToggle,
|
||||
onHandToolToggle,
|
||||
onPenModeToggle,
|
||||
onInsertElements,
|
||||
showExitZenModeBtn,
|
||||
@ -304,13 +308,20 @@ const LayerUI = ({
|
||||
penDetected={appState.penDetected}
|
||||
/>
|
||||
<LockButton
|
||||
zenModeEnabled={appState.zenModeEnabled}
|
||||
checked={appState.activeTool.locked}
|
||||
onChange={() => onLockToggle()}
|
||||
onChange={onLockToggle}
|
||||
title={t("toolBar.lock")}
|
||||
/>
|
||||
|
||||
<div className="App-toolbar__divider"></div>
|
||||
|
||||
<HandButton
|
||||
checked={isHandToolActive(appState)}
|
||||
onChange={() => onHandToolToggle()}
|
||||
title={t("toolBar.hand")}
|
||||
isMobile
|
||||
/>
|
||||
|
||||
<ShapesSwitcher
|
||||
appState={appState}
|
||||
canvas={canvas}
|
||||
@ -322,9 +333,6 @@ const LayerUI = ({
|
||||
});
|
||||
}}
|
||||
/>
|
||||
{/* {actionManager.renderAction("eraser", {
|
||||
// size: "small",
|
||||
})} */}
|
||||
</Stack.Row>
|
||||
</Island>
|
||||
</Stack.Row>
|
||||
@ -408,7 +416,8 @@ const LayerUI = ({
|
||||
renderJSONExportDialog={renderJSONExportDialog}
|
||||
renderImageExportDialog={renderImageExportDialog}
|
||||
setAppState={setAppState}
|
||||
onLockToggle={() => onLockToggle()}
|
||||
onLockToggle={onLockToggle}
|
||||
onHandToolToggle={onHandToolToggle}
|
||||
onPenModeToggle={onPenModeToggle}
|
||||
canvas={canvas}
|
||||
onImageAction={onImageAction}
|
||||
|
@ -9,7 +9,6 @@ type LockIconProps = {
|
||||
name?: string;
|
||||
checked: boolean;
|
||||
onChange?(): void;
|
||||
zenModeEnabled?: boolean;
|
||||
isMobile?: boolean;
|
||||
};
|
||||
|
||||
|
@ -22,6 +22,8 @@ import { LibraryButton } from "./LibraryButton";
|
||||
import { PenModeButton } from "./PenModeButton";
|
||||
import { Stats } from "./Stats";
|
||||
import { actionToggleStats } from "../actions";
|
||||
import { HandButton } from "./HandButton";
|
||||
import { isHandToolActive } from "../appState";
|
||||
|
||||
type MobileMenuProps = {
|
||||
appState: AppState;
|
||||
@ -31,6 +33,7 @@ type MobileMenuProps = {
|
||||
setAppState: React.Component<any, AppState>["setState"];
|
||||
elements: readonly NonDeletedExcalidrawElement[];
|
||||
onLockToggle: () => void;
|
||||
onHandToolToggle: () => void;
|
||||
onPenModeToggle: () => void;
|
||||
canvas: HTMLCanvasElement | null;
|
||||
|
||||
@ -52,6 +55,7 @@ export const MobileMenu = ({
|
||||
actionManager,
|
||||
setAppState,
|
||||
onLockToggle,
|
||||
onHandToolToggle,
|
||||
onPenModeToggle,
|
||||
canvas,
|
||||
onImageAction,
|
||||
@ -88,6 +92,13 @@ export const MobileMenu = ({
|
||||
</Island>
|
||||
{renderTopRightUI && renderTopRightUI(true, appState)}
|
||||
<div className="mobile-misc-tools-container">
|
||||
{!appState.viewModeEnabled && (
|
||||
<LibraryButton
|
||||
appState={appState}
|
||||
setAppState={setAppState}
|
||||
isMobile
|
||||
/>
|
||||
)}
|
||||
<PenModeButton
|
||||
checked={appState.penMode}
|
||||
onChange={onPenModeToggle}
|
||||
@ -101,13 +112,12 @@ export const MobileMenu = ({
|
||||
title={t("toolBar.lock")}
|
||||
isMobile
|
||||
/>
|
||||
{!appState.viewModeEnabled && (
|
||||
<LibraryButton
|
||||
appState={appState}
|
||||
setAppState={setAppState}
|
||||
isMobile
|
||||
/>
|
||||
)}
|
||||
<HandButton
|
||||
checked={isHandToolActive(appState)}
|
||||
onChange={() => onHandToolToggle()}
|
||||
title={t("toolBar.hand")}
|
||||
isMobile
|
||||
/>
|
||||
</div>
|
||||
</Stack.Row>
|
||||
</Stack.Col>
|
||||
|
@ -19,7 +19,7 @@ type ToolButtonBaseProps = {
|
||||
name?: string;
|
||||
id?: string;
|
||||
size?: ToolButtonSize;
|
||||
keyBindingLabel?: string;
|
||||
keyBindingLabel?: string | null;
|
||||
showAriaLabel?: boolean;
|
||||
hidden?: boolean;
|
||||
visible?: boolean;
|
||||
|
@ -1532,3 +1532,14 @@ export const publishIcon = createIcon(
|
||||
export const eraser = createIcon(
|
||||
<path d="M480 416C497.7 416 512 430.3 512 448C512 465.7 497.7 480 480 480H150.6C133.7 480 117.4 473.3 105.4 461.3L25.37 381.3C.3786 356.3 .3786 315.7 25.37 290.7L258.7 57.37C283.7 32.38 324.3 32.38 349.3 57.37L486.6 194.7C511.6 219.7 511.6 260.3 486.6 285.3L355.9 416H480zM265.4 416L332.7 348.7L195.3 211.3L70.63 336L150.6 416L265.4 416z" />,
|
||||
);
|
||||
|
||||
export const handIcon = createIcon(
|
||||
<g strokeWidth={1.25}>
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
|
||||
<path d="M8 13v-7.5a1.5 1.5 0 0 1 3 0v6.5"></path>
|
||||
<path d="M11 5.5v-2a1.5 1.5 0 1 1 3 0v8.5"></path>
|
||||
<path d="M14 5.5a1.5 1.5 0 0 1 3 0v6.5"></path>
|
||||
<path d="M17 7.5a1.5 1.5 0 0 1 3 0v8.5a6 6 0 0 1 -6 6h-2h.208a6 6 0 0 1 -5.012 -2.7a69.74 69.74 0 0 1 -.196 -.3c-.312 -.479 -1.407 -2.388 -3.286 -5.728a1.5 1.5 0 0 1 .536 -2.022a1.867 1.867 0 0 1 2.28 .28l1.47 1.47"></path>
|
||||
</g>,
|
||||
tablerIconProps,
|
||||
);
|
||||
|
@ -549,6 +549,7 @@
|
||||
border-top-left-radius: var(--border-radius-lg);
|
||||
border-bottom-left-radius: var(--border-radius-lg);
|
||||
border-right: 0;
|
||||
overflow: hidden;
|
||||
|
||||
background-color: var(--island-bg-color);
|
||||
|
||||
|
@ -55,6 +55,7 @@ export const AllowedExcalidrawActiveTools: Record<
|
||||
freedraw: true,
|
||||
eraser: false,
|
||||
custom: true,
|
||||
hand: true,
|
||||
};
|
||||
|
||||
export type RestoredDataState = {
|
||||
@ -465,7 +466,7 @@ export const restoreAppState = (
|
||||
? nextAppState.activeTool
|
||||
: { type: "selection" },
|
||||
),
|
||||
lastActiveToolBeforeEraser: null,
|
||||
lastActiveTool: null,
|
||||
locked: nextAppState.activeTool.locked ?? false,
|
||||
},
|
||||
// Migrates from previous version where appState.zoom was a number
|
||||
|
@ -11,6 +11,7 @@ export const showSelectedShapeActions = (
|
||||
appState.activeTool.type !== "custom" &&
|
||||
(appState.editingElement ||
|
||||
(appState.activeTool.type !== "selection" &&
|
||||
appState.activeTool.type !== "eraser"))) ||
|
||||
appState.activeTool.type !== "eraser" &&
|
||||
appState.activeTool.type !== "hand"))) ||
|
||||
getSelectedElements(elements, appState).length,
|
||||
);
|
||||
|
@ -220,7 +220,8 @@
|
||||
"lock": "Keep selected tool active after drawing",
|
||||
"penMode": "Pen mode - prevent touch",
|
||||
"link": "Add/ Update link for a selected shape",
|
||||
"eraser": "Eraser"
|
||||
"eraser": "Eraser",
|
||||
"hand": "Hand (panning tool)"
|
||||
},
|
||||
"headings": {
|
||||
"canvasActions": "Canvas actions",
|
||||
@ -228,7 +229,7 @@
|
||||
"shapes": "Shapes"
|
||||
},
|
||||
"hints": {
|
||||
"canvasPanning": "To move canvas, hold mouse wheel or spacebar while dragging",
|
||||
"canvasPanning": "To move canvas, hold mouse wheel or spacebar while dragging, or use the hand tool",
|
||||
"linearElement": "Click to start multiple points, drag for single line",
|
||||
"freeDraw": "Click and drag, release when you're finished",
|
||||
"text": "Tip: you can also add text by double-clicking anywhere with the selection tool",
|
||||
|
@ -4,7 +4,7 @@ exports[`contextMenu element right-clicking on a group should select whole group
|
||||
Object {
|
||||
"activeTool": Object {
|
||||
"customType": null,
|
||||
"lastActiveToolBeforeEraser": null,
|
||||
"lastActiveTool": null,
|
||||
"locked": false,
|
||||
"type": "selection",
|
||||
},
|
||||
@ -434,7 +434,7 @@ exports[`contextMenu element selecting 'Add to library' in context menu adds ele
|
||||
Object {
|
||||
"activeTool": Object {
|
||||
"customType": null,
|
||||
"lastActiveToolBeforeEraser": null,
|
||||
"lastActiveTool": null,
|
||||
"locked": false,
|
||||
"type": "selection",
|
||||
},
|
||||
@ -620,7 +620,7 @@ exports[`contextMenu element selecting 'Bring forward' in context menu brings el
|
||||
Object {
|
||||
"activeTool": Object {
|
||||
"customType": null,
|
||||
"lastActiveToolBeforeEraser": null,
|
||||
"lastActiveTool": null,
|
||||
"locked": false,
|
||||
"type": "selection",
|
||||
},
|
||||
@ -975,7 +975,7 @@ exports[`contextMenu element selecting 'Bring to front' in context menu brings e
|
||||
Object {
|
||||
"activeTool": Object {
|
||||
"customType": null,
|
||||
"lastActiveToolBeforeEraser": null,
|
||||
"lastActiveTool": null,
|
||||
"locked": false,
|
||||
"type": "selection",
|
||||
},
|
||||
@ -1330,7 +1330,7 @@ exports[`contextMenu element selecting 'Copy styles' in context menu copies styl
|
||||
Object {
|
||||
"activeTool": Object {
|
||||
"customType": null,
|
||||
"lastActiveToolBeforeEraser": null,
|
||||
"lastActiveTool": null,
|
||||
"locked": false,
|
||||
"type": "selection",
|
||||
},
|
||||
@ -1516,7 +1516,7 @@ exports[`contextMenu element selecting 'Delete' in context menu deletes element:
|
||||
Object {
|
||||
"activeTool": Object {
|
||||
"customType": null,
|
||||
"lastActiveToolBeforeEraser": null,
|
||||
"lastActiveTool": null,
|
||||
"locked": false,
|
||||
"type": "selection",
|
||||
},
|
||||
@ -1738,7 +1738,7 @@ exports[`contextMenu element selecting 'Duplicate' in context menu duplicates el
|
||||
Object {
|
||||
"activeTool": Object {
|
||||
"customType": null,
|
||||
"lastActiveToolBeforeEraser": null,
|
||||
"lastActiveTool": null,
|
||||
"locked": false,
|
||||
"type": "selection",
|
||||
},
|
||||
@ -2023,7 +2023,7 @@ exports[`contextMenu element selecting 'Group selection' in context menu groups
|
||||
Object {
|
||||
"activeTool": Object {
|
||||
"customType": null,
|
||||
"lastActiveToolBeforeEraser": null,
|
||||
"lastActiveTool": null,
|
||||
"locked": false,
|
||||
"type": "selection",
|
||||
},
|
||||
@ -2396,7 +2396,7 @@ exports[`contextMenu element selecting 'Paste styles' in context menu pastes sty
|
||||
Object {
|
||||
"activeTool": Object {
|
||||
"customType": null,
|
||||
"lastActiveToolBeforeEraser": null,
|
||||
"lastActiveTool": null,
|
||||
"locked": false,
|
||||
"type": "selection",
|
||||
},
|
||||
@ -3243,7 +3243,7 @@ exports[`contextMenu element selecting 'Send backward' in context menu sends ele
|
||||
Object {
|
||||
"activeTool": Object {
|
||||
"customType": null,
|
||||
"lastActiveToolBeforeEraser": null,
|
||||
"lastActiveTool": null,
|
||||
"locked": false,
|
||||
"type": "selection",
|
||||
},
|
||||
@ -3598,7 +3598,7 @@ exports[`contextMenu element selecting 'Send to back' in context menu sends elem
|
||||
Object {
|
||||
"activeTool": Object {
|
||||
"customType": null,
|
||||
"lastActiveToolBeforeEraser": null,
|
||||
"lastActiveTool": null,
|
||||
"locked": false,
|
||||
"type": "selection",
|
||||
},
|
||||
@ -3953,7 +3953,7 @@ exports[`contextMenu element selecting 'Ungroup selection' in context menu ungro
|
||||
Object {
|
||||
"activeTool": Object {
|
||||
"customType": null,
|
||||
"lastActiveToolBeforeEraser": null,
|
||||
"lastActiveTool": null,
|
||||
"locked": false,
|
||||
"type": "selection",
|
||||
},
|
||||
@ -4392,7 +4392,7 @@ exports[`contextMenu element shows 'Group selection' in context menu for multipl
|
||||
Object {
|
||||
"activeTool": Object {
|
||||
"customType": null,
|
||||
"lastActiveToolBeforeEraser": null,
|
||||
"lastActiveTool": null,
|
||||
"locked": false,
|
||||
"type": "selection",
|
||||
},
|
||||
@ -4933,7 +4933,7 @@ exports[`contextMenu element shows 'Ungroup selection' in context menu for group
|
||||
Object {
|
||||
"activeTool": Object {
|
||||
"customType": null,
|
||||
"lastActiveToolBeforeEraser": null,
|
||||
"lastActiveTool": null,
|
||||
"locked": false,
|
||||
"type": "selection",
|
||||
},
|
||||
@ -5559,7 +5559,7 @@ exports[`contextMenu element shows context menu for canvas: [end of test] appSta
|
||||
Object {
|
||||
"activeTool": Object {
|
||||
"customType": null,
|
||||
"lastActiveToolBeforeEraser": null,
|
||||
"lastActiveTool": null,
|
||||
"locked": false,
|
||||
"type": "selection",
|
||||
},
|
||||
@ -5773,7 +5773,7 @@ exports[`contextMenu element shows context menu for element: [end of test] appSt
|
||||
Object {
|
||||
"activeTool": Object {
|
||||
"customType": null,
|
||||
"lastActiveToolBeforeEraser": null,
|
||||
"lastActiveTool": null,
|
||||
"locked": false,
|
||||
"type": "selection",
|
||||
},
|
||||
@ -6110,7 +6110,7 @@ exports[`contextMenu element shows context menu for element: [end of test] appSt
|
||||
Object {
|
||||
"activeTool": Object {
|
||||
"customType": null,
|
||||
"lastActiveToolBeforeEraser": null,
|
||||
"lastActiveTool": null,
|
||||
"locked": false,
|
||||
"type": "selection",
|
||||
},
|
||||
|
@ -4,7 +4,7 @@ exports[`given element A and group of elements B and given both are selected whe
|
||||
Object {
|
||||
"activeTool": Object {
|
||||
"customType": null,
|
||||
"lastActiveToolBeforeEraser": null,
|
||||
"lastActiveTool": null,
|
||||
"locked": false,
|
||||
"type": "selection",
|
||||
},
|
||||
@ -540,7 +540,7 @@ exports[`given element A and group of elements B and given both are selected whe
|
||||
Object {
|
||||
"activeTool": Object {
|
||||
"customType": null,
|
||||
"lastActiveToolBeforeEraser": null,
|
||||
"lastActiveTool": null,
|
||||
"locked": false,
|
||||
"type": "selection",
|
||||
},
|
||||
@ -1082,7 +1082,7 @@ exports[`regression tests Cmd/Ctrl-click exclusively select element under pointe
|
||||
Object {
|
||||
"activeTool": Object {
|
||||
"customType": null,
|
||||
"lastActiveToolBeforeEraser": null,
|
||||
"lastActiveTool": null,
|
||||
"locked": false,
|
||||
"type": "selection",
|
||||
},
|
||||
@ -1989,7 +1989,7 @@ exports[`regression tests Drags selected element when hitting only bounding box
|
||||
Object {
|
||||
"activeTool": Object {
|
||||
"customType": null,
|
||||
"lastActiveToolBeforeEraser": null,
|
||||
"lastActiveTool": null,
|
||||
"locked": false,
|
||||
"type": "selection",
|
||||
},
|
||||
@ -2219,7 +2219,7 @@ exports[`regression tests adjusts z order when grouping: [end of test] appState
|
||||
Object {
|
||||
"activeTool": Object {
|
||||
"customType": null,
|
||||
"lastActiveToolBeforeEraser": null,
|
||||
"lastActiveTool": null,
|
||||
"locked": false,
|
||||
"type": "selection",
|
||||
},
|
||||
@ -2752,7 +2752,7 @@ exports[`regression tests alt-drag duplicates an element: [end of test] appState
|
||||
Object {
|
||||
"activeTool": Object {
|
||||
"customType": null,
|
||||
"lastActiveToolBeforeEraser": null,
|
||||
"lastActiveTool": null,
|
||||
"locked": false,
|
||||
"type": "selection",
|
||||
},
|
||||
@ -3041,7 +3041,7 @@ exports[`regression tests arrow keys: [end of test] appState 1`] = `
|
||||
Object {
|
||||
"activeTool": Object {
|
||||
"customType": null,
|
||||
"lastActiveToolBeforeEraser": null,
|
||||
"lastActiveTool": null,
|
||||
"locked": false,
|
||||
"type": "selection",
|
||||
},
|
||||
@ -3225,7 +3225,7 @@ exports[`regression tests can drag element that covers another element, while an
|
||||
Object {
|
||||
"activeTool": Object {
|
||||
"customType": null,
|
||||
"lastActiveToolBeforeEraser": null,
|
||||
"lastActiveTool": null,
|
||||
"locked": false,
|
||||
"type": "selection",
|
||||
},
|
||||
@ -3741,7 +3741,7 @@ exports[`regression tests change the properties of a shape: [end of test] appSta
|
||||
Object {
|
||||
"activeTool": Object {
|
||||
"customType": null,
|
||||
"lastActiveToolBeforeEraser": null,
|
||||
"lastActiveTool": null,
|
||||
"locked": false,
|
||||
"type": "selection",
|
||||
},
|
||||
@ -4009,7 +4009,7 @@ exports[`regression tests click on an element and drag it: [dragged] appState 1`
|
||||
Object {
|
||||
"activeTool": Object {
|
||||
"customType": null,
|
||||
"lastActiveToolBeforeEraser": null,
|
||||
"lastActiveTool": null,
|
||||
"locked": false,
|
||||
"type": "selection",
|
||||
},
|
||||
@ -4239,7 +4239,7 @@ exports[`regression tests click on an element and drag it: [end of test] appStat
|
||||
Object {
|
||||
"activeTool": Object {
|
||||
"customType": null,
|
||||
"lastActiveToolBeforeEraser": null,
|
||||
"lastActiveTool": null,
|
||||
"locked": false,
|
||||
"type": "selection",
|
||||
},
|
||||
@ -4515,7 +4515,7 @@ exports[`regression tests click to select a shape: [end of test] appState 1`] =
|
||||
Object {
|
||||
"activeTool": Object {
|
||||
"customType": null,
|
||||
"lastActiveToolBeforeEraser": null,
|
||||
"lastActiveTool": null,
|
||||
"locked": false,
|
||||
"type": "selection",
|
||||
},
|
||||
@ -4803,7 +4803,7 @@ exports[`regression tests click-drag to select a group: [end of test] appState 1
|
||||
Object {
|
||||
"activeTool": Object {
|
||||
"customType": null,
|
||||
"lastActiveToolBeforeEraser": null,
|
||||
"lastActiveTool": null,
|
||||
"locked": false,
|
||||
"type": "selection",
|
||||
},
|
||||
@ -5221,7 +5221,7 @@ exports[`regression tests deselects group of selected elements on pointer down w
|
||||
Object {
|
||||
"activeTool": Object {
|
||||
"customType": null,
|
||||
"lastActiveToolBeforeEraser": null,
|
||||
"lastActiveTool": null,
|
||||
"locked": false,
|
||||
"type": "selection",
|
||||
},
|
||||
@ -5562,7 +5562,7 @@ exports[`regression tests deselects group of selected elements on pointer up whe
|
||||
Object {
|
||||
"activeTool": Object {
|
||||
"customType": null,
|
||||
"lastActiveToolBeforeEraser": null,
|
||||
"lastActiveTool": null,
|
||||
"locked": false,
|
||||
"type": "selection",
|
||||
},
|
||||
@ -5876,7 +5876,7 @@ exports[`regression tests deselects selected element on pointer down when pointe
|
||||
Object {
|
||||
"activeTool": Object {
|
||||
"customType": null,
|
||||
"lastActiveToolBeforeEraser": null,
|
||||
"lastActiveTool": null,
|
||||
"locked": false,
|
||||
"type": "selection",
|
||||
},
|
||||
@ -6114,7 +6114,7 @@ exports[`regression tests deselects selected element, on pointer up, when click
|
||||
Object {
|
||||
"activeTool": Object {
|
||||
"customType": null,
|
||||
"lastActiveToolBeforeEraser": null,
|
||||
"lastActiveTool": null,
|
||||
"locked": false,
|
||||
"type": "selection",
|
||||
},
|
||||
@ -6300,7 +6300,7 @@ exports[`regression tests double click to edit a group: [end of test] appState 1
|
||||
Object {
|
||||
"activeTool": Object {
|
||||
"customType": null,
|
||||
"lastActiveToolBeforeEraser": null,
|
||||
"lastActiveTool": null,
|
||||
"locked": false,
|
||||
"type": "selection",
|
||||
},
|
||||
@ -6828,7 +6828,7 @@ exports[`regression tests drags selected elements from point inside common bound
|
||||
Object {
|
||||
"activeTool": Object {
|
||||
"customType": null,
|
||||
"lastActiveToolBeforeEraser": null,
|
||||
"lastActiveTool": null,
|
||||
"locked": false,
|
||||
"type": "selection",
|
||||
},
|
||||
@ -7193,7 +7193,7 @@ exports[`regression tests draw every type of shape: [end of test] appState 1`] =
|
||||
Object {
|
||||
"activeTool": Object {
|
||||
"customType": null,
|
||||
"lastActiveToolBeforeEraser": null,
|
||||
"lastActiveTool": null,
|
||||
"locked": false,
|
||||
"type": "freedraw",
|
||||
},
|
||||
@ -9545,7 +9545,7 @@ exports[`regression tests given a group of selected elements with an element tha
|
||||
Object {
|
||||
"activeTool": Object {
|
||||
"customType": null,
|
||||
"lastActiveToolBeforeEraser": null,
|
||||
"lastActiveTool": null,
|
||||
"locked": false,
|
||||
"type": "selection",
|
||||
},
|
||||
@ -9964,7 +9964,7 @@ exports[`regression tests given a selected element A and a not selected element
|
||||
Object {
|
||||
"activeTool": Object {
|
||||
"customType": null,
|
||||
"lastActiveToolBeforeEraser": null,
|
||||
"lastActiveTool": null,
|
||||
"locked": false,
|
||||
"type": "selection",
|
||||
},
|
||||
@ -10253,7 +10253,7 @@ exports[`regression tests given selected element A with lower z-index than unsel
|
||||
Object {
|
||||
"activeTool": Object {
|
||||
"customType": null,
|
||||
"lastActiveToolBeforeEraser": null,
|
||||
"lastActiveTool": null,
|
||||
"locked": false,
|
||||
"type": "selection",
|
||||
},
|
||||
@ -10501,7 +10501,7 @@ exports[`regression tests given selected element A with lower z-index than unsel
|
||||
Object {
|
||||
"activeTool": Object {
|
||||
"customType": null,
|
||||
"lastActiveToolBeforeEraser": null,
|
||||
"lastActiveTool": null,
|
||||
"locked": false,
|
||||
"type": "selection",
|
||||
},
|
||||
@ -10822,7 +10822,7 @@ exports[`regression tests key 2 selects rectangle tool: [end of test] appState 1
|
||||
Object {
|
||||
"activeTool": Object {
|
||||
"customType": null,
|
||||
"lastActiveToolBeforeEraser": null,
|
||||
"lastActiveTool": null,
|
||||
"locked": false,
|
||||
"type": "selection",
|
||||
},
|
||||
@ -11006,7 +11006,7 @@ exports[`regression tests key 3 selects diamond tool: [end of test] appState 1`]
|
||||
Object {
|
||||
"activeTool": Object {
|
||||
"customType": null,
|
||||
"lastActiveToolBeforeEraser": null,
|
||||
"lastActiveTool": null,
|
||||
"locked": false,
|
||||
"type": "selection",
|
||||
},
|
||||
@ -11190,7 +11190,7 @@ exports[`regression tests key 4 selects ellipse tool: [end of test] appState 1`]
|
||||
Object {
|
||||
"activeTool": Object {
|
||||
"customType": null,
|
||||
"lastActiveToolBeforeEraser": null,
|
||||
"lastActiveTool": null,
|
||||
"locked": false,
|
||||
"type": "selection",
|
||||
},
|
||||
@ -11374,7 +11374,7 @@ exports[`regression tests key 5 selects arrow tool: [end of test] appState 1`] =
|
||||
Object {
|
||||
"activeTool": Object {
|
||||
"customType": null,
|
||||
"lastActiveToolBeforeEraser": null,
|
||||
"lastActiveTool": null,
|
||||
"locked": false,
|
||||
"type": "selection",
|
||||
},
|
||||
@ -11611,7 +11611,7 @@ exports[`regression tests key 6 selects line tool: [end of test] appState 1`] =
|
||||
Object {
|
||||
"activeTool": Object {
|
||||
"customType": null,
|
||||
"lastActiveToolBeforeEraser": null,
|
||||
"lastActiveTool": null,
|
||||
"locked": false,
|
||||
"type": "selection",
|
||||
},
|
||||
@ -11848,7 +11848,7 @@ exports[`regression tests key 7 selects freedraw tool: [end of test] appState 1`
|
||||
Object {
|
||||
"activeTool": Object {
|
||||
"customType": null,
|
||||
"lastActiveToolBeforeEraser": null,
|
||||
"lastActiveTool": null,
|
||||
"locked": false,
|
||||
"type": "freedraw",
|
||||
},
|
||||
@ -12076,7 +12076,7 @@ exports[`regression tests key a selects arrow tool: [end of test] appState 1`] =
|
||||
Object {
|
||||
"activeTool": Object {
|
||||
"customType": null,
|
||||
"lastActiveToolBeforeEraser": null,
|
||||
"lastActiveTool": null,
|
||||
"locked": false,
|
||||
"type": "selection",
|
||||
},
|
||||
@ -12313,7 +12313,7 @@ exports[`regression tests key d selects diamond tool: [end of test] appState 1`]
|
||||
Object {
|
||||
"activeTool": Object {
|
||||
"customType": null,
|
||||
"lastActiveToolBeforeEraser": null,
|
||||
"lastActiveTool": null,
|
||||
"locked": false,
|
||||
"type": "selection",
|
||||
},
|
||||
@ -12497,7 +12497,7 @@ exports[`regression tests key l selects line tool: [end of test] appState 1`] =
|
||||
Object {
|
||||
"activeTool": Object {
|
||||
"customType": null,
|
||||
"lastActiveToolBeforeEraser": null,
|
||||
"lastActiveTool": null,
|
||||
"locked": false,
|
||||
"type": "selection",
|
||||
},
|
||||
@ -12734,7 +12734,7 @@ exports[`regression tests key o selects ellipse tool: [end of test] appState 1`]
|
||||
Object {
|
||||
"activeTool": Object {
|
||||
"customType": null,
|
||||
"lastActiveToolBeforeEraser": null,
|
||||
"lastActiveTool": null,
|
||||
"locked": false,
|
||||
"type": "selection",
|
||||
},
|
||||
@ -12918,7 +12918,7 @@ exports[`regression tests key p selects freedraw tool: [end of test] appState 1`
|
||||
Object {
|
||||
"activeTool": Object {
|
||||
"customType": null,
|
||||
"lastActiveToolBeforeEraser": null,
|
||||
"lastActiveTool": null,
|
||||
"locked": false,
|
||||
"type": "freedraw",
|
||||
},
|
||||
@ -13146,7 +13146,7 @@ exports[`regression tests key r selects rectangle tool: [end of test] appState 1
|
||||
Object {
|
||||
"activeTool": Object {
|
||||
"customType": null,
|
||||
"lastActiveToolBeforeEraser": null,
|
||||
"lastActiveTool": null,
|
||||
"locked": false,
|
||||
"type": "selection",
|
||||
},
|
||||
@ -13330,7 +13330,7 @@ exports[`regression tests make a group and duplicate it: [end of test] appState
|
||||
Object {
|
||||
"activeTool": Object {
|
||||
"customType": null,
|
||||
"lastActiveToolBeforeEraser": null,
|
||||
"lastActiveTool": null,
|
||||
"locked": false,
|
||||
"type": "selection",
|
||||
},
|
||||
@ -14169,7 +14169,7 @@ exports[`regression tests noop interaction after undo shouldn't create history e
|
||||
Object {
|
||||
"activeTool": Object {
|
||||
"customType": null,
|
||||
"lastActiveToolBeforeEraser": null,
|
||||
"lastActiveTool": null,
|
||||
"locked": false,
|
||||
"type": "selection",
|
||||
},
|
||||
@ -14458,7 +14458,7 @@ exports[`regression tests pinch-to-zoom works: [end of test] appState 1`] = `
|
||||
Object {
|
||||
"activeTool": Object {
|
||||
"customType": null,
|
||||
"lastActiveToolBeforeEraser": null,
|
||||
"lastActiveTool": null,
|
||||
"locked": false,
|
||||
"type": "selection",
|
||||
},
|
||||
@ -14569,7 +14569,7 @@ exports[`regression tests rerenders UI on language change: [end of test] appStat
|
||||
Object {
|
||||
"activeTool": Object {
|
||||
"customType": null,
|
||||
"lastActiveToolBeforeEraser": null,
|
||||
"lastActiveTool": null,
|
||||
"locked": false,
|
||||
"type": "rectangle",
|
||||
},
|
||||
@ -14678,7 +14678,7 @@ exports[`regression tests shift click on selected element should deselect it on
|
||||
Object {
|
||||
"activeTool": Object {
|
||||
"customType": null,
|
||||
"lastActiveToolBeforeEraser": null,
|
||||
"lastActiveTool": null,
|
||||
"locked": false,
|
||||
"type": "selection",
|
||||
},
|
||||
@ -14865,7 +14865,7 @@ exports[`regression tests shift-click to multiselect, then drag: [end of test] a
|
||||
Object {
|
||||
"activeTool": Object {
|
||||
"customType": null,
|
||||
"lastActiveToolBeforeEraser": null,
|
||||
"lastActiveTool": null,
|
||||
"locked": false,
|
||||
"type": "selection",
|
||||
},
|
||||
@ -15233,7 +15233,7 @@ exports[`regression tests should group elements and ungroup them: [end of test]
|
||||
Object {
|
||||
"activeTool": Object {
|
||||
"customType": null,
|
||||
"lastActiveToolBeforeEraser": null,
|
||||
"lastActiveTool": null,
|
||||
"locked": false,
|
||||
"type": "selection",
|
||||
},
|
||||
@ -15864,7 +15864,7 @@ exports[`regression tests should show fill icons when element has non transparen
|
||||
Object {
|
||||
"activeTool": Object {
|
||||
"customType": null,
|
||||
"lastActiveToolBeforeEraser": null,
|
||||
"lastActiveTool": null,
|
||||
"locked": false,
|
||||
"type": "selection",
|
||||
},
|
||||
@ -16090,7 +16090,7 @@ exports[`regression tests single-clicking on a subgroup of a selected group shou
|
||||
Object {
|
||||
"activeTool": Object {
|
||||
"customType": null,
|
||||
"lastActiveToolBeforeEraser": null,
|
||||
"lastActiveTool": null,
|
||||
"locked": false,
|
||||
"type": "selection",
|
||||
},
|
||||
@ -17053,7 +17053,7 @@ exports[`regression tests spacebar + drag scrolls the canvas: [end of test] appS
|
||||
Object {
|
||||
"activeTool": Object {
|
||||
"customType": null,
|
||||
"lastActiveToolBeforeEraser": null,
|
||||
"lastActiveTool": null,
|
||||
"locked": false,
|
||||
"type": "selection",
|
||||
},
|
||||
@ -17162,7 +17162,7 @@ exports[`regression tests supports nested groups: [end of test] appState 1`] = `
|
||||
Object {
|
||||
"activeTool": Object {
|
||||
"customType": null,
|
||||
"lastActiveToolBeforeEraser": null,
|
||||
"lastActiveTool": null,
|
||||
"locked": false,
|
||||
"type": "selection",
|
||||
},
|
||||
@ -18021,7 +18021,7 @@ exports[`regression tests switches from group of selected elements to another el
|
||||
Object {
|
||||
"activeTool": Object {
|
||||
"customType": null,
|
||||
"lastActiveToolBeforeEraser": null,
|
||||
"lastActiveTool": null,
|
||||
"locked": false,
|
||||
"type": "selection",
|
||||
},
|
||||
@ -18493,7 +18493,7 @@ exports[`regression tests switches selected element on pointer down: [end of tes
|
||||
Object {
|
||||
"activeTool": Object {
|
||||
"customType": null,
|
||||
"lastActiveToolBeforeEraser": null,
|
||||
"lastActiveTool": null,
|
||||
"locked": false,
|
||||
"type": "selection",
|
||||
},
|
||||
@ -18834,7 +18834,7 @@ exports[`regression tests two-finger scroll works: [end of test] appState 1`] =
|
||||
Object {
|
||||
"activeTool": Object {
|
||||
"customType": null,
|
||||
"lastActiveToolBeforeEraser": null,
|
||||
"lastActiveTool": null,
|
||||
"locked": false,
|
||||
"type": "selection",
|
||||
},
|
||||
@ -18945,7 +18945,7 @@ exports[`regression tests undo/redo drawing an element: [end of test] appState 1
|
||||
Object {
|
||||
"activeTool": Object {
|
||||
"customType": null,
|
||||
"lastActiveToolBeforeEraser": null,
|
||||
"lastActiveTool": null,
|
||||
"locked": false,
|
||||
"type": "selection",
|
||||
},
|
||||
@ -19516,7 +19516,7 @@ exports[`regression tests updates fontSize & fontFamily appState: [end of test]
|
||||
Object {
|
||||
"activeTool": Object {
|
||||
"customType": null,
|
||||
"lastActiveToolBeforeEraser": null,
|
||||
"lastActiveTool": null,
|
||||
"locked": false,
|
||||
"type": "text",
|
||||
},
|
||||
@ -19625,7 +19625,7 @@ exports[`regression tests zoom hotkeys: [end of test] appState 1`] = `
|
||||
Object {
|
||||
"activeTool": Object {
|
||||
"customType": null,
|
||||
"lastActiveToolBeforeEraser": null,
|
||||
"lastActiveTool": null,
|
||||
"locked": false,
|
||||
"type": "selection",
|
||||
},
|
||||
|
@ -4,7 +4,7 @@ exports[`exportToSvg with default arguments 1`] = `
|
||||
Object {
|
||||
"activeTool": Object {
|
||||
"customType": null,
|
||||
"lastActiveToolBeforeEraser": null,
|
||||
"lastActiveTool": null,
|
||||
"locked": false,
|
||||
"type": "selection",
|
||||
},
|
||||
|
22
src/types.ts
22
src/types.ts
@ -81,9 +81,9 @@ export type BinaryFileMetadata = Omit<BinaryFileData, "dataURL">;
|
||||
|
||||
export type BinaryFiles = Record<ExcalidrawElement["id"], BinaryFileData>;
|
||||
|
||||
export type LastActiveToolBeforeEraser =
|
||||
export type LastActiveTool =
|
||||
| {
|
||||
type: typeof SHAPES[number]["value"] | "eraser";
|
||||
type: typeof SHAPES[number]["value"] | "eraser" | "hand";
|
||||
customType: null;
|
||||
}
|
||||
| {
|
||||
@ -112,19 +112,23 @@ export type AppState = {
|
||||
// (e.g. text element when typing into the input)
|
||||
editingElement: NonDeletedExcalidrawElement | null;
|
||||
editingLinearElement: LinearElementEditor | null;
|
||||
activeTool:
|
||||
activeTool: {
|
||||
/**
|
||||
* indicates a previous tool we should revert back to if we deselect the
|
||||
* currently active tool. At the moment applies to `eraser` and `hand` tool.
|
||||
*/
|
||||
lastActiveTool: LastActiveTool;
|
||||
locked: boolean;
|
||||
} & (
|
||||
| {
|
||||
type: typeof SHAPES[number]["value"] | "eraser";
|
||||
lastActiveToolBeforeEraser: LastActiveToolBeforeEraser;
|
||||
locked: boolean;
|
||||
type: typeof SHAPES[number]["value"] | "eraser" | "hand";
|
||||
customType: null;
|
||||
}
|
||||
| {
|
||||
type: "custom";
|
||||
customType: string;
|
||||
lastActiveToolBeforeEraser: LastActiveToolBeforeEraser;
|
||||
locked: boolean;
|
||||
};
|
||||
}
|
||||
);
|
||||
penMode: boolean;
|
||||
penDetected: boolean;
|
||||
exportBackground: boolean;
|
||||
|
15
src/utils.ts
15
src/utils.ts
@ -12,10 +12,11 @@ import {
|
||||
WINDOWS_EMOJI_FALLBACK_FONT,
|
||||
} from "./constants";
|
||||
import { FontFamilyValues, FontString } from "./element/types";
|
||||
import { AppState, DataURL, LastActiveToolBeforeEraser, Zoom } from "./types";
|
||||
import { AppState, DataURL, LastActiveTool, Zoom } from "./types";
|
||||
import { unstable_batchedUpdates } from "react-dom";
|
||||
import { SHAPES } from "./shapes";
|
||||
import React from "react";
|
||||
import { isEraserActive, isHandToolActive } from "./appState";
|
||||
|
||||
let mockDateTime: string | null = null;
|
||||
|
||||
@ -219,9 +220,9 @@ export const distance = (x: number, y: number) => Math.abs(x - y);
|
||||
export const updateActiveTool = (
|
||||
appState: Pick<AppState, "activeTool">,
|
||||
data: (
|
||||
| { type: typeof SHAPES[number]["value"] | "eraser" }
|
||||
| { type: typeof SHAPES[number]["value"] | "eraser" | "hand" }
|
||||
| { type: "custom"; customType: string }
|
||||
) & { lastActiveToolBeforeEraser?: LastActiveToolBeforeEraser },
|
||||
) & { lastActiveToolBeforeEraser?: LastActiveTool },
|
||||
): AppState["activeTool"] => {
|
||||
if (data.type === "custom") {
|
||||
return {
|
||||
@ -233,9 +234,9 @@ export const updateActiveTool = (
|
||||
|
||||
return {
|
||||
...appState.activeTool,
|
||||
lastActiveToolBeforeEraser:
|
||||
lastActiveTool:
|
||||
data.lastActiveToolBeforeEraser === undefined
|
||||
? appState.activeTool.lastActiveToolBeforeEraser
|
||||
? appState.activeTool.lastActiveTool
|
||||
: data.lastActiveToolBeforeEraser,
|
||||
type: data.type,
|
||||
customType: null,
|
||||
@ -305,7 +306,9 @@ export const setCursorForShape = (
|
||||
}
|
||||
if (appState.activeTool.type === "selection") {
|
||||
resetCursor(canvas);
|
||||
} else if (appState.activeTool.type === "eraser") {
|
||||
} else if (isHandToolActive(appState)) {
|
||||
canvas.style.cursor = CURSOR_TYPE.GRAB;
|
||||
} else if (isEraserActive(appState)) {
|
||||
setEraserCursor(canvas, appState.theme);
|
||||
// do nothing if image tool is selected which suggests there's
|
||||
// a image-preview set as the cursor
|
||||
|
Loading…
x
Reference in New Issue
Block a user