fix: cursor being leaked outside of canvas (#3161)

pull/3165/head
David Luzar 4 years ago committed by GitHub
parent f295ba98c5
commit c77c9ce65a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -18,7 +18,7 @@ import { isBindingElement } from "../element/typeChecks";
export const actionFinalize = register({ export const actionFinalize = register({
name: "finalize", name: "finalize",
perform: (elements, appState) => { perform: (elements, appState, _, { canvas }) => {
if (appState.editingLinearElement) { if (appState.editingLinearElement) {
const { const {
elementId, elementId,
@ -126,7 +126,7 @@ export const actionFinalize = register({
(!appState.elementLocked && appState.elementType !== "draw") || (!appState.elementLocked && appState.elementType !== "draw") ||
!multiPointElement !multiPointElement
) { ) {
resetCursor(); resetCursor(canvas);
} }
return { return {
elements: newElements, elements: newElements,

@ -151,10 +151,12 @@ const LIBRARY_ICON = (
); );
export const ShapesSwitcher = ({ export const ShapesSwitcher = ({
canvas,
elementType, elementType,
setAppState, setAppState,
isLibraryOpen, isLibraryOpen,
}: { }: {
canvas: HTMLCanvasElement | null;
elementType: ExcalidrawElement["type"]; elementType: ExcalidrawElement["type"];
setAppState: React.Component<any, AppState>["setState"]; setAppState: React.Component<any, AppState>["setState"];
isLibraryOpen: boolean; isLibraryOpen: boolean;
@ -185,7 +187,7 @@ export const ShapesSwitcher = ({
multiElement: null, multiElement: null,
selectedElementIds: {}, selectedElementIds: {},
}); });
setCursorForShape(value); setCursorForShape(canvas, value);
setAppState({}); setAppState({});
}} }}
/> />

@ -172,6 +172,7 @@ import {
ResolvablePromise, ResolvablePromise,
resolvablePromise, resolvablePromise,
sceneCoordsToViewportCoords, sceneCoordsToViewportCoords,
setCursor,
setCursorForShape, setCursorForShape,
tupleToCoors, tupleToCoors,
viewportCoordsToSceneCoords, viewportCoordsToSceneCoords,
@ -1440,16 +1441,16 @@ class App extends React.Component<ExcalidrawProps, AppState> {
} }
if (event.key === KEYS.SPACE && gesture.pointers.size === 0) { if (event.key === KEYS.SPACE && gesture.pointers.size === 0) {
isHoldingSpace = true; isHoldingSpace = true;
document.documentElement.style.cursor = CURSOR_TYPE.GRABBING; setCursor(this.canvas, CURSOR_TYPE.GRABBING);
} }
}); });
private onKeyUp = withBatchedUpdates((event: KeyboardEvent) => { private onKeyUp = withBatchedUpdates((event: KeyboardEvent) => {
if (event.key === KEYS.SPACE) { if (event.key === KEYS.SPACE) {
if (this.state.elementType === "selection") { if (this.state.elementType === "selection") {
resetCursor(); resetCursor(this.canvas);
} else { } else {
setCursorForShape(this.state.elementType); setCursorForShape(this.canvas, this.state.elementType);
this.setState({ this.setState({
selectedElementIds: {}, selectedElementIds: {},
selectedGroupIds: {}, selectedGroupIds: {},
@ -1475,7 +1476,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
private selectShapeTool(elementType: AppState["elementType"]) { private selectShapeTool(elementType: AppState["elementType"]) {
if (!isHoldingSpace) { if (!isHoldingSpace) {
setCursorForShape(elementType); setCursorForShape(this.canvas, elementType);
} }
if (isToolIcon(document.activeElement)) { if (isToolIcon(document.activeElement)) {
document.activeElement.blur(); document.activeElement.blur();
@ -1601,7 +1602,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
editingElement: null, editingElement: null,
}); });
if (this.state.elementLocked) { if (this.state.elementLocked) {
setCursorForShape(this.state.elementType); setCursorForShape(this.canvas, this.state.elementType);
} }
}), }),
element, element,
@ -1782,7 +1783,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
return; return;
} }
resetCursor(); resetCursor(this.canvas);
const { x: sceneX, y: sceneY } = viewportCoordsToSceneCoords( const { x: sceneX, y: sceneY } = viewportCoordsToSceneCoords(
event, event,
@ -1814,7 +1815,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
} }
} }
resetCursor(); resetCursor(this.canvas);
if (!event[KEYS.CTRL_OR_CMD]) { if (!event[KEYS.CTRL_OR_CMD]) {
this.startTextEditing({ this.startTextEditing({
@ -1880,9 +1881,9 @@ class App extends React.Component<ExcalidrawProps, AppState> {
const isOverScrollBar = isPointerOverScrollBars.isOverEither; const isOverScrollBar = isPointerOverScrollBars.isOverEither;
if (!this.state.draggingElement && !this.state.multiElement) { if (!this.state.draggingElement && !this.state.multiElement) {
if (isOverScrollBar) { if (isOverScrollBar) {
resetCursor(); resetCursor(this.canvas);
} else { } else {
setCursorForShape(this.state.elementType); setCursorForShape(this.canvas, this.state.elementType);
} }
} }
@ -1933,7 +1934,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
const { points, lastCommittedPoint } = multiElement; const { points, lastCommittedPoint } = multiElement;
const lastPoint = points[points.length - 1]; const lastPoint = points[points.length - 1];
setCursorForShape(this.state.elementType); setCursorForShape(this.canvas, this.state.elementType);
if (lastPoint === lastCommittedPoint) { if (lastPoint === lastCommittedPoint) {
// if we haven't yet created a temp point and we're beyond commit-zone // if we haven't yet created a temp point and we're beyond commit-zone
@ -1950,7 +1951,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
points: [...points, [scenePointerX - rx, scenePointerY - ry]], points: [...points, [scenePointerX - rx, scenePointerY - ry]],
}); });
} else { } else {
document.documentElement.style.cursor = CURSOR_TYPE.POINTER; setCursor(this.canvas, CURSOR_TYPE.POINTER);
// in this branch, we're inside the commit zone, and no uncommitted // in this branch, we're inside the commit zone, and no uncommitted
// point exists. Thus do nothing (don't add/remove points). // point exists. Thus do nothing (don't add/remove points).
} }
@ -1964,13 +1965,13 @@ class App extends React.Component<ExcalidrawProps, AppState> {
lastCommittedPoint[1], lastCommittedPoint[1],
) < LINE_CONFIRM_THRESHOLD ) < LINE_CONFIRM_THRESHOLD
) { ) {
document.documentElement.style.cursor = CURSOR_TYPE.POINTER; setCursor(this.canvas, CURSOR_TYPE.POINTER);
mutateElement(multiElement, { mutateElement(multiElement, {
points: points.slice(0, -1), points: points.slice(0, -1),
}); });
} else { } else {
if (isPathALoop(points, this.state.zoom.value)) { if (isPathALoop(points, this.state.zoom.value)) {
document.documentElement.style.cursor = CURSOR_TYPE.POINTER; setCursor(this.canvas, CURSOR_TYPE.POINTER);
} }
// update last uncommitted point // update last uncommitted point
mutateElement(multiElement, { mutateElement(multiElement, {
@ -2013,8 +2014,9 @@ class App extends React.Component<ExcalidrawProps, AppState> {
elementWithTransformHandleType && elementWithTransformHandleType &&
elementWithTransformHandleType.transformHandleType elementWithTransformHandleType.transformHandleType
) { ) {
document.documentElement.style.cursor = getCursorForResizingElement( setCursor(
elementWithTransformHandleType, this.canvas,
getCursorForResizingElement(elementWithTransformHandleType),
); );
return; return;
} }
@ -2027,9 +2029,12 @@ class App extends React.Component<ExcalidrawProps, AppState> {
event.pointerType, event.pointerType,
); );
if (transformHandleType) { if (transformHandleType) {
document.documentElement.style.cursor = getCursorForResizingElement({ setCursor(
transformHandleType, this.canvas,
}); getCursorForResizingElement({
transformHandleType,
}),
);
return; return;
} }
} }
@ -2039,11 +2044,12 @@ class App extends React.Component<ExcalidrawProps, AppState> {
scenePointer.y, scenePointer.y,
); );
if (this.state.elementType === "text") { if (this.state.elementType === "text") {
document.documentElement.style.cursor = isTextElement(hitElement) setCursor(
? CURSOR_TYPE.TEXT this.canvas,
: CURSOR_TYPE.CROSSHAIR; isTextElement(hitElement) ? CURSOR_TYPE.TEXT : CURSOR_TYPE.CROSSHAIR,
);
} else if (isOverScrollBar) { } else if (isOverScrollBar) {
document.documentElement.style.cursor = CURSOR_TYPE.AUTO; setCursor(this.canvas, CURSOR_TYPE.AUTO);
} else if ( } else if (
hitElement || hitElement ||
this.isHittingCommonBoundingBoxOfSelectedElements( this.isHittingCommonBoundingBoxOfSelectedElements(
@ -2051,9 +2057,9 @@ class App extends React.Component<ExcalidrawProps, AppState> {
selectedElements, selectedElements,
) )
) { ) {
document.documentElement.style.cursor = CURSOR_TYPE.MOVE; setCursor(this.canvas, CURSOR_TYPE.MOVE);
} else { } else {
document.documentElement.style.cursor = CURSOR_TYPE.AUTO; setCursor(this.canvas, CURSOR_TYPE.AUTO);
} }
}; };
@ -2226,7 +2232,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
let nextPastePrevented = false; let nextPastePrevented = false;
const isLinux = /Linux/.test(window.navigator.platform); const isLinux = /Linux/.test(window.navigator.platform);
document.documentElement.style.cursor = CURSOR_TYPE.GRABBING; setCursor(this.canvas, CURSOR_TYPE.GRABBING);
let { clientX: lastX, clientY: lastY } = event; let { clientX: lastX, clientY: lastY } = event;
const onPointerMove = withBatchedUpdates((event: PointerEvent) => { const onPointerMove = withBatchedUpdates((event: PointerEvent) => {
const deltaX = lastX - event.clientX; const deltaX = lastX - event.clientX;
@ -2278,7 +2284,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
lastPointerUp = null; lastPointerUp = null;
isPanning = false; isPanning = false;
if (!isHoldingSpace) { if (!isHoldingSpace) {
setCursorForShape(this.state.elementType); setCursorForShape(this.canvas, this.state.elementType);
} }
this.setState({ this.setState({
cursorButton: "up", cursorButton: "up",
@ -2394,7 +2400,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
const onPointerUp = withBatchedUpdates(() => { const onPointerUp = withBatchedUpdates(() => {
isDraggingScrollBar = false; isDraggingScrollBar = false;
setCursorForShape(this.state.elementType); setCursorForShape(this.canvas, this.state.elementType);
lastPointerUp = null; lastPointerUp = null;
this.setState({ this.setState({
cursorButton: "up", cursorButton: "up",
@ -2457,9 +2463,12 @@ class App extends React.Component<ExcalidrawProps, AppState> {
); );
} }
if (pointerDownState.resize.handleType) { if (pointerDownState.resize.handleType) {
document.documentElement.style.cursor = getCursorForResizingElement({ setCursor(
transformHandleType: pointerDownState.resize.handleType, this.canvas,
}); getCursorForResizingElement({
transformHandleType: pointerDownState.resize.handleType,
}),
);
pointerDownState.resize.isResizing = true; pointerDownState.resize.isResizing = true;
pointerDownState.resize.offset = tupleToCoors( pointerDownState.resize.offset = tupleToCoors(
getResizeOffsetXY( getResizeOffsetXY(
@ -2624,7 +2633,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
insertAtParentCenter: !event.altKey, insertAtParentCenter: !event.altKey,
}); });
resetCursor(); resetCursor(this.canvas);
if (!this.state.elementLocked) { if (!this.state.elementLocked) {
this.setState({ this.setState({
elementType: "selection", elementType: "selection",
@ -2681,7 +2690,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
mutateElement(multiElement, { mutateElement(multiElement, {
lastCommittedPoint: multiElement.points[multiElement.points.length - 1], lastCommittedPoint: multiElement.points[multiElement.points.length - 1],
}); });
document.documentElement.style.cursor = CURSOR_TYPE.POINTER; setCursor(this.canvas, CURSOR_TYPE.POINTER);
} else { } else {
const [gridX, gridY] = getGridPoint( const [gridX, gridY] = getGridPoint(
pointerDownState.origin.x, pointerDownState.origin.x,
@ -3216,7 +3225,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
} }
this.setState({ suggestedBindings: [], startBoundElement: null }); this.setState({ suggestedBindings: [], startBoundElement: null });
if (!elementLocked && elementType !== "draw") { if (!elementLocked && elementType !== "draw") {
resetCursor(); resetCursor(this.canvas);
this.setState((prevState) => ({ this.setState((prevState) => ({
draggingElement: null, draggingElement: null,
elementType: "selection", elementType: "selection",
@ -3387,7 +3396,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
} }
if (!elementLocked && elementType !== "draw") { if (!elementLocked && elementType !== "draw") {
resetCursor(); resetCursor(this.canvas);
this.setState({ this.setState({
draggingElement: null, draggingElement: null,
suggestedBindings: [], suggestedBindings: [],

@ -516,6 +516,7 @@ const LayerUI = ({
{heading} {heading}
<Stack.Row gap={1}> <Stack.Row gap={1}>
<ShapesSwitcher <ShapesSwitcher
canvas={canvas}
elementType={appState.elementType} elementType={appState.elementType}
setAppState={setAppState} setAppState={setAppState}
isLibraryOpen={appState.isLibraryOpen} isLibraryOpen={appState.isLibraryOpen}

@ -57,6 +57,7 @@ export const MobileMenu = ({
{heading} {heading}
<Stack.Row gap={1}> <Stack.Row gap={1}>
<ShapesSwitcher <ShapesSwitcher
canvas={canvas}
elementType={appState.elementType} elementType={appState.elementType}
setAppState={setAppState} setAppState={setAppState}
isLibraryOpen={appState.isLibraryOpen} isLibraryOpen={appState.isLibraryOpen}

@ -1,6 +1,5 @@
import React from "react"; import React from "react";
import * as Sentry from "@sentry/browser"; import * as Sentry from "@sentry/browser";
import { resetCursor } from "../utils";
import { t } from "../i18n"; import { t } from "../i18n";
interface TopErrorBoundaryState { interface TopErrorBoundaryState {
@ -24,7 +23,6 @@ export class TopErrorBoundary extends React.Component<
} }
componentDidCatch(error: Error, errorInfo: any) { componentDidCatch(error: Error, errorInfo: any) {
resetCursor();
const _localStorage: any = {}; const _localStorage: any = {};
for (const [key, value] of Object.entries({ ...localStorage })) { for (const [key, value] of Object.entries({ ...localStorage })) {
try { try {

@ -21,7 +21,6 @@ import {
import { isLinearElement, isTextElement } from "./typeChecks"; import { isLinearElement, isTextElement } from "./typeChecks";
import { mutateElement } from "./mutateElement"; import { mutateElement } from "./mutateElement";
import { getPerfectElementSize } from "./sizeHelpers"; import { getPerfectElementSize } from "./sizeHelpers";
import { getCursorForResizingElement } from "./resizeTest";
import { measureText, getFontString } from "../utils"; import { measureText, getFontString } from "../utils";
import { updateBoundElements } from "./binding"; import { updateBoundElements } from "./binding";
import { import {
@ -105,13 +104,6 @@ export const transformElements = (
); );
} }
// update cursor
// FIXME it is not very nice to have this here
document.documentElement.style.cursor = getCursorForResizingElement({
element,
transformHandleType,
});
return true; return true;
} else if (selectedElements.length > 1) { } else if (selectedElements.length > 1) {
if (transformHandleType === "rotation") { if (transformHandleType === "rotation") {

@ -160,15 +160,29 @@ export const removeSelection = () => {
export const distance = (x: number, y: number) => Math.abs(x - y); export const distance = (x: number, y: number) => Math.abs(x - y);
export const resetCursor = () => { export const resetCursor = (canvas: HTMLCanvasElement | null) => {
document.documentElement.style.cursor = ""; if (canvas) {
canvas.style.cursor = "";
}
};
export const setCursor = (canvas: HTMLCanvasElement | null, cursor: string) => {
if (canvas) {
canvas.style.cursor = cursor;
}
}; };
export const setCursorForShape = (shape: string) => { export const setCursorForShape = (
canvas: HTMLCanvasElement | null,
shape: string,
) => {
if (!canvas) {
return;
}
if (shape === "selection") { if (shape === "selection") {
resetCursor(); resetCursor(canvas);
} else { } else {
document.documentElement.style.cursor = CURSOR_TYPE.CROSSHAIR; canvas.style.cursor = CURSOR_TYPE.CROSSHAIR;
} }
}; };

Loading…
Cancel
Save