浏览代码

Revert "Feature: Multi Point Arrows (#338)" (#634)

This reverts commit 16263e942b7b690bb3e97340383009016129d489.
David Luzar 5 年之前
父节点
当前提交
3d2e59bfed

+ 1 - 2
src/actions/actionDeleteSelected.tsx

@@ -4,10 +4,9 @@ import { KEYS } from "../keys";
 
 export const actionDeleteSelected: Action = {
   name: "deleteSelectedElements",
-  perform: (elements, appState) => {
+  perform: elements => {
     return {
       elements: deleteSelectedElements(elements),
-      appState: { ...appState, elementType: "selection", multiElement: null },
     };
   },
   contextItemLabel: "labels.delete",

+ 0 - 27
src/actions/actionFinalize.tsx

@@ -1,27 +0,0 @@
-import { Action } from "./types";
-import { KEYS } from "../keys";
-import { clearSelection } from "../scene";
-
-export const actionFinalize: Action = {
-  name: "finalize",
-  perform: (elements, appState) => {
-    if (window.document.activeElement instanceof HTMLElement) {
-      window.document.activeElement.blur();
-    }
-    return {
-      elements: clearSelection(elements),
-      appState: {
-        ...appState,
-        elementType: "selection",
-        draggingElement: null,
-        multiElement: null,
-      },
-    };
-  },
-  keyTest: (event, appState) =>
-    (event.key === KEYS.ESCAPE &&
-      !appState.draggingElement &&
-      appState.multiElement === null) ||
-    ((event.key === KEYS.ESCAPE || event.key === KEYS.ENTER) &&
-      appState.multiElement !== null),
-};

+ 0 - 2
src/actions/index.ts

@@ -23,8 +23,6 @@ export {
   actionClearCanvas,
 } from "./actionCanvas";
 
-export { actionFinalize } from "./actionFinalize";
-
 export {
   actionChangeProjectName,
   actionChangeExportBackground,

+ 1 - 1
src/actions/manager.tsx

@@ -34,7 +34,7 @@ export class ActionManager implements ActionsManagerInterface {
     const data = Object.values(this.actions)
       .sort((a, b) => (b.keyPriority || 0) - (a.keyPriority || 0))
       .filter(
-        action => action.keyTest && action.keyTest(event, appState, elements),
+        action => action.keyTest && action.keyTest(event, elements, appState),
       );
 
     if (data.length === 0) return null;

+ 2 - 2
src/actions/types.ts

@@ -29,8 +29,8 @@ export interface Action {
   keyPriority?: number;
   keyTest?: (
     event: KeyboardEvent,
-    appState: AppState,
-    elements: readonly ExcalidrawElement[],
+    elements?: readonly ExcalidrawElement[],
+    appState?: AppState,
   ) => boolean;
   contextItemLabel?: string;
   contextMenuOrder?: number;

+ 0 - 7
src/appState.ts

@@ -7,7 +7,6 @@ export function getDefaultAppState(): AppState {
   return {
     draggingElement: null,
     resizingElement: null,
-    multiElement: null,
     editingElement: null,
     elementType: "selection",
     elementLocked: false,
@@ -27,9 +26,3 @@ export function getDefaultAppState(): AppState {
     name: DEFAULT_PROJECT_NAME,
   };
 }
-
-export function cleanAppStateForExport(appState: AppState) {
-  return {
-    viewBackgroundColor: appState.viewBackgroundColor,
-  };
-}

+ 5 - 94
src/element/bounds.ts

@@ -1,16 +1,11 @@
 import { ExcalidrawElement } from "./types";
 import { rotate } from "../math";
-import { Drawable } from "roughjs/bin/core";
-import { Point } from "roughjs/bin/geometry";
 
 // If the element is created from right to left, the width is going to be negative
 // This set of functions retrieves the absolute position of the 4 points.
 // We can't just always normalize it since we need to remember the fact that an arrow
 // is pointing left or right.
 export function getElementAbsoluteCoords(element: ExcalidrawElement) {
-  if (element.type === "arrow") {
-    return getArrowAbsoluteBounds(element);
-  }
   return [
     element.width >= 0 ? element.x : element.x + element.width, // x1
     element.height >= 0 ? element.y : element.y + element.height, // y1
@@ -34,95 +29,11 @@ export function getDiamondPoints(element: ExcalidrawElement) {
   return [topX, topY, rightX, rightY, bottomX, bottomY, leftX, leftY];
 }
 
-export function getArrowAbsoluteBounds(element: ExcalidrawElement) {
-  if (element.points.length < 2 || !element.shape) {
-    const { minX, minY, maxX, maxY } = element.points.reduce(
-      (limits, [x, y]) => {
-        limits.minY = Math.min(limits.minY, y);
-        limits.minX = Math.min(limits.minX, x);
-
-        limits.maxX = Math.max(limits.maxX, x);
-        limits.maxY = Math.max(limits.maxY, y);
-
-        return limits;
-      },
-      { minX: Infinity, minY: Infinity, maxX: -Infinity, maxY: -Infinity },
-    );
-    return [
-      minX + element.x,
-      minY + element.y,
-      maxX + element.x,
-      maxY + element.y,
-    ];
-  }
-
-  const shape = element.shape as Drawable[];
-
-  const ops = shape[1].sets[0].ops;
-
-  let currentP: Point = [0, 0];
-
-  const { minX, minY, maxX, maxY } = ops.reduce(
-    (limits, { op, data }) => {
-      // There are only four operation types:
-      // move, bcurveTo, lineTo, and curveTo
-      if (op === "move") {
-        // change starting point
-        currentP = data as Point;
-        // move operation does not draw anything; so, it always
-        // returns false
-      } else if (op === "bcurveTo") {
-        // create points from bezier curve
-        // bezier curve stores data as a flattened array of three positions
-        // [x1, y1, x2, y2, x3, y3]
-        const p1 = [data[0], data[1]] as Point;
-        const p2 = [data[2], data[3]] as Point;
-        const p3 = [data[4], data[5]] as Point;
-
-        const p0 = currentP;
-        currentP = p3;
-
-        const equation = (t: number, idx: number) =>
-          Math.pow(1 - t, 3) * p3[idx] +
-          3 * t * Math.pow(1 - t, 2) * p2[idx] +
-          3 * Math.pow(t, 2) * (1 - t) * p1[idx] +
-          p0[idx] * Math.pow(t, 3);
-
-        let t = 0;
-        while (t <= 1.0) {
-          const x = equation(t, 0);
-          const y = equation(t, 1);
-
-          limits.minY = Math.min(limits.minY, y);
-          limits.minX = Math.min(limits.minX, x);
-
-          limits.maxX = Math.max(limits.maxX, x);
-          limits.maxY = Math.max(limits.maxY, y);
-
-          t += 0.1;
-        }
-      } else if (op === "lineTo") {
-        // TODO: Implement this
-      } else if (op === "qcurveTo") {
-        // TODO: Implement this
-      }
-      return limits;
-    },
-    { minX: Infinity, minY: Infinity, maxX: -Infinity, maxY: -Infinity },
-  );
-
-  return [
-    minX + element.x,
-    minY + element.y,
-    maxX + element.x,
-    maxY + element.y,
-  ];
-}
-
 export function getArrowPoints(element: ExcalidrawElement) {
-  const points = element.points;
-  const [x1, y1] = points.length >= 2 ? points[points.length - 2] : [0, 0];
-  const [x2, y2] = points[points.length - 1];
+  const x1 = 0;
+  const y1 = 0;
+  const x2 = element.width;
+  const y2 = element.height;
 
   const size = 30; // pixels
   const distance = Math.hypot(x2 - x1, y2 - y1);
@@ -135,7 +46,7 @@ export function getArrowPoints(element: ExcalidrawElement) {
   const [x3, y3] = rotate(xs, ys, x2, y2, (-angle * Math.PI) / 180);
   const [x4, y4] = rotate(xs, ys, x2, y2, (angle * Math.PI) / 180);
 
-  return [x2, y2, x3, y3, x4, y4];
+  return [x1, y1, x2, y2, x3, y3, x4, y4];
 }
 
 export function getLinePoints(element: ExcalidrawElement) {

+ 11 - 99
src/element/collision.ts

@@ -2,13 +2,11 @@ import { distanceBetweenPointAndSegment } from "../math";
 
 import { ExcalidrawElement } from "./types";
 import {
+  getArrowPoints,
   getDiamondPoints,
   getElementAbsoluteCoords,
   getLinePoints,
-  getArrowAbsoluteBounds,
 } from "./bounds";
-import { Point } from "roughjs/bin/geometry";
-import { Drawable, OpSet } from "roughjs/bin/core";
 
 function isElementDraggableFromInside(element: ExcalidrawElement): boolean {
   return element.backgroundColor !== "transparent" || element.isSelected;
@@ -147,25 +145,18 @@ export function hitTest(
         lineThreshold
     );
   } else if (element.type === "arrow") {
-    if (!element.shape) {
-      return false;
-    }
-    const shape = element.shape as Drawable[];
-    // If shape does not consist of curve and two line segments
-    // for arrow shape, return false
-    if (shape.length < 3) return false;
-
-    const [x1, y1, x2, y2] = getArrowAbsoluteBounds(element);
-    if (x < x1 || y < y1 - 10 || x > x2 || y > y2 + 10) return false;
-
-    const relX = x - element.x;
-    const relY = y - element.y;
+    let [x1, y1, x2, y2, x3, y3, x4, y4] = getArrowPoints(element);
+    // The computation is done at the origin, we need to add a translation
+    x -= element.x;
+    y -= element.y;
 
-    // hit test curve and lien segments for arrow
     return (
-      hitTestRoughShape(shape[0].sets, relX, relY) ||
-      hitTestRoughShape(shape[1].sets, relX, relY) ||
-      hitTestRoughShape(shape[2].sets, relX, relY)
+      //    \
+      distanceBetweenPointAndSegment(x, y, x3, y3, x2, y2) < lineThreshold ||
+      // -----
+      distanceBetweenPointAndSegment(x, y, x1, y1, x2, y2) < lineThreshold ||
+      //    /
+      distanceBetweenPointAndSegment(x, y, x4, y4, x2, y2) < lineThreshold
     );
   } else if (element.type === "line") {
     const [x1, y1, x2, y2] = getLinePoints(element);
@@ -185,82 +176,3 @@ export function hitTest(
     throw new Error("Unimplemented type " + element.type);
   }
 }
-
-const pointInBezierEquation = (
-  p0: Point,
-  p1: Point,
-  p2: Point,
-  p3: Point,
-  [mx, my]: Point,
-) => {
-  // B(t) = p0 * (1-t)^3 + 3p1 * t * (1-t)^2 + 3p2 * t^2 * (1-t) + p3 * t^3
-  const equation = (t: number, idx: number) =>
-    Math.pow(1 - t, 3) * p3[idx] +
-    3 * t * Math.pow(1 - t, 2) * p2[idx] +
-    3 * Math.pow(t, 2) * (1 - t) * p1[idx] +
-    p0[idx] * Math.pow(t, 3);
-
-  const epsilon = 20;
-  // go through t in increments of 0.01
-  let t = 0;
-  while (t <= 1.0) {
-    const tx = equation(t, 0);
-    const ty = equation(t, 1);
-
-    const diff = Math.sqrt(Math.pow(tx - mx, 2) + Math.pow(ty - my, 2));
-
-    if (diff < epsilon) {
-      return true;
-    }
-
-    t += 0.01;
-  }
-
-  return false;
-};
-
-const hitTestRoughShape = (opSet: OpSet[], x: number, y: number) => {
-  // read operations from first opSet
-  const ops = opSet[0].ops;
-
-  // set start position as (0,0) just in case
-  // move operation does not exist (unlikely but it is worth safekeeping it)
-  let currentP: Point = [0, 0];
-
-  return ops.some(({ op, data }, idx) => {
-    // There are only four operation types:
-    // move, bcurveTo, lineTo, and curveTo
-    if (op === "move") {
-      // change starting point
-      currentP = data as Point;
-      // move operation does not draw anything; so, it always
-      // returns false
-    } else if (op === "bcurveTo") {
-      // create points from bezier curve
-      // bezier curve stores data as a flattened array of three positions
-      // [x1, y1, x2, y2, x3, y3]
-      const p1 = [data[0], data[1]] as Point;
-      const p2 = [data[2], data[3]] as Point;
-      const p3 = [data[4], data[5]] as Point;
-
-      const p0 = currentP;
-      currentP = p3;
-
-      // check if points are on the curve
-      // cubic bezier curves require four parameters
-      // the first parameter is the last stored position (p0)
-      let retVal = pointInBezierEquation(p0, p1, p2, p3, [x, y]);
-
-      // set end point of bezier curve as the new starting point for
-      // upcoming operations as each operation is based on the last drawn
-      // position of the previous operation
-      return retVal;
-    } else if (op === "lineTo") {
-      // TODO: Implement this
-    } else if (op === "qcurveTo") {
-      // TODO: Implement this
-    }
-
-    return false;
-  });
-};

+ 9 - 70
src/element/handlerRectangles.ts

@@ -1,6 +1,5 @@
 import { ExcalidrawElement } from "./types";
 import { SceneScroll } from "../scene/types";
-import { getArrowAbsoluteBounds } from "./bounds";
 
 type Sides = "n" | "s" | "w" | "e" | "nw" | "ne" | "sw" | "se";
 
@@ -8,31 +7,18 @@ export function handlerRectangles(
   element: ExcalidrawElement,
   { scrollX, scrollY }: SceneScroll,
 ) {
-  let elementX2 = 0;
-  let elementY2 = 0;
-  let elementX1 = Infinity;
-  let elementY1 = Infinity;
-  let marginX = -8;
-  let marginY = -8;
-
-  let minimumSize = 40;
-  if (element.type === "arrow") {
-    [elementX1, elementY1, elementX2, elementY2] = getArrowAbsoluteBounds(
-      element,
-    );
-  } else {
-    elementX1 = element.x;
-    elementX2 = element.x + element.width;
-    elementY1 = element.y;
-    elementY2 = element.y + element.height;
-
-    marginX = element.width < 0 ? 8 : -8;
-    marginY = element.height < 0 ? 8 : -8;
-  }
+  const elementX1 = element.x;
+  const elementX2 = element.x + element.width;
+  const elementY1 = element.y;
+  const elementY2 = element.y + element.height;
 
   const margin = 4;
+  const minimumSize = 40;
   const handlers = {} as { [T in Sides]: number[] };
 
+  const marginX = element.width < 0 ? 8 : -8;
+  const marginY = element.height < 0 ? 8 : -8;
+
   if (Math.abs(elementX2 - elementX1) > minimumSize) {
     handlers["n"] = [
       elementX1 + (elementX2 - elementX1) / 2 + scrollX - 4,
@@ -90,58 +76,11 @@ export function handlerRectangles(
     8,
   ]; // se
 
-  if (element.type === "line") {
+  if (element.type === "arrow" || element.type === "line") {
     return {
       nw: handlers.nw,
       se: handlers.se,
     } as typeof handlers;
-  } else if (element.type === "arrow") {
-    if (element.points.length === 2) {
-      // only check the last point because starting point is always (0,0)
-      const [, p1] = element.points;
-
-      if (p1[0] === 0 || p1[1] === 0) {
-        return {
-          nw: handlers.nw,
-          se: handlers.se,
-        } as typeof handlers;
-      }
-
-      if (p1[0] > 0 && p1[1] < 0) {
-        return {
-          ne: handlers.ne,
-          sw: handlers.sw,
-        } as typeof handlers;
-      }
-
-      if (p1[0] > 0 && p1[1] > 0) {
-        return {
-          nw: handlers.nw,
-          se: handlers.se,
-        } as typeof handlers;
-      }
-
-      if (p1[0] < 0 && p1[1] > 0) {
-        return {
-          ne: handlers.ne,
-          sw: handlers.sw,
-        } as typeof handlers;
-      }
-
-      if (p1[0] < 0 && p1[1] < 0) {
-        return {
-          nw: handlers.nw,
-          se: handlers.se,
-        } as typeof handlers;
-      }
-    }
-
-    return {
-      n: handlers.n,
-      s: handlers.s,
-      w: handlers.w,
-      e: handlers.e,
-    } as typeof handlers;
   }
 
   return handlers;

+ 0 - 1
src/element/index.ts

@@ -5,7 +5,6 @@ export {
   getDiamondPoints,
   getArrowPoints,
   getLinePoints,
-  getArrowAbsoluteBounds,
 } from "./bounds";
 
 export { handlerRectangles } from "./handlerRectangles";

+ 0 - 2
src/element/newElement.ts

@@ -1,7 +1,6 @@
 import { randomSeed } from "roughjs/bin/math";
 import nanoid from "nanoid";
 import { Drawable } from "roughjs/bin/core";
-import { Point } from "roughjs/bin/geometry";
 
 import { ExcalidrawElement, ExcalidrawTextElement } from "../element/types";
 import { measureText } from "../utils";
@@ -35,7 +34,6 @@ export function newElement(
     isSelected: false,
     seed: randomSeed(),
     shape: null as Drawable | Drawable[] | null,
-    points: [] as Point[],
   };
   return element;
 }

+ 0 - 1
src/element/resizeTest.ts

@@ -17,7 +17,6 @@ export function resizeTest(
 
   const filter = Object.keys(handlers).filter(key => {
     const handler = handlers[key as HandlerRectanglesRet]!;
-    if (!handler) return false;
 
     return (
       x + scrollX >= handler[0] &&

+ 60 - 356
src/index.tsx

@@ -42,13 +42,7 @@ import { renderScene } from "./renderer";
 import { AppState } from "./types";
 import { ExcalidrawElement } from "./element/types";
 
-import {
-  isInputLike,
-  debounce,
-  capitalizeString,
-  distance,
-  distance2d,
-} from "./utils";
+import { isInputLike, debounce, capitalizeString, distance } from "./utils";
 import { KEYS, isArrowKey } from "./keys";
 
 import { findShapeByKey, shapesShortcutKeys, SHAPES } from "./shapes";
@@ -82,7 +76,6 @@ import {
   actionSaveScene,
   actionCopyStyles,
   actionPasteStyles,
-  actionFinalize,
 } from "./actions";
 import { Action, ActionResult } from "./actions/types";
 import { getDefaultAppState } from "./appState";
@@ -95,7 +88,6 @@ import { ExportDialog } from "./components/ExportDialog";
 import { withTranslation } from "react-i18next";
 import { LanguageList } from "./components/LanguageList";
 import i18n, { languages, parseDetectedLang } from "./i18n";
-import { Point } from "roughjs/bin/geometry";
 import { StoredScenesList } from "./components/StoredScenesList";
 
 let { elements } = createScene();
@@ -117,7 +109,6 @@ function setCursorForShape(shape: string) {
   }
 }
 
-const DRAGGING_THRESHOLD = 10; // 10px
 const ELEMENT_SHIFT_TRANSLATE_AMOUNT = 5;
 const ELEMENT_TRANSLATE_AMOUNT = 1;
 const TEXT_TO_CENTER_SNAP_THRESHOLD = 30;
@@ -177,7 +168,6 @@ export class App extends React.Component<any, AppState> {
   canvasOnlyActions: Array<Action>;
   constructor(props: any) {
     super(props);
-    this.actionManager.registerAction(actionFinalize);
     this.actionManager.registerAction(actionDeleteSelected);
     this.actionManager.registerAction(actionSendToBack);
     this.actionManager.registerAction(actionBringToFront);
@@ -338,7 +328,17 @@ export class App extends React.Component<any, AppState> {
   };
 
   private onKeyDown = (event: KeyboardEvent) => {
-    if (isInputLike(event.target) && event.key !== KEYS.ESCAPE) return;
+    if (event.key === KEYS.ESCAPE && !this.state.draggingElement) {
+      elements = clearSelection(elements);
+      this.setState({});
+      this.setState({ elementType: "selection" });
+      if (window.document.activeElement instanceof HTMLElement) {
+        window.document.activeElement.blur();
+      }
+      event.preventDefault();
+      return;
+    }
+    if (isInputLike(event.target)) return;
 
     const actionResult = this.actionManager.handleKeyDown(
       event,
@@ -387,27 +387,19 @@ export class App extends React.Component<any, AppState> {
     } else if (event[KEYS.META] && event.code === "KeyZ") {
       event.preventDefault();
 
-      if (
-        this.state.resizingElement ||
-        this.state.multiElement ||
-        this.state.editingElement
-      ) {
-        return;
-      }
-
       if (event.shiftKey) {
         // Redo action
         const data = history.redoOnce();
         if (data !== null) {
           elements = data.elements;
-          this.setState({ ...data.appState });
+          this.setState(data.appState);
         }
       } else {
         // undo action
         const data = history.undoOnce();
         if (data !== null) {
           elements = data.elements;
-          this.setState({ ...data.appState });
+          this.setState(data.appState);
         }
       }
     } else if (event.key === KEYS.SPACE && !isHoldingMouseButton) {
@@ -578,7 +570,7 @@ export class App extends React.Component<any, AppState> {
               aria-label={capitalizeString(label)}
               aria-keyshortcuts={`${label[0]} ${index + 1}`}
               onChange={() => {
-                this.setState({ elementType: value, multiElement: null });
+                this.setState({ elementType: value });
                 elements = clearSelection(elements);
                 document.documentElement.style.cursor =
                   value === "text" ? CURSOR_TYPE.TEXT : CURSOR_TYPE.CROSSHAIR;
@@ -1044,28 +1036,11 @@ export class App extends React.Component<any, AppState> {
                   editingElement: element,
                 });
                 return;
-              } else if (this.state.elementType === "arrow") {
-                if (this.state.multiElement) {
-                  const { multiElement } = this.state;
-                  const { x: rx, y: ry } = multiElement;
-                  multiElement.isSelected = true;
-                  multiElement.points.push([x - rx, y - ry]);
-                  multiElement.shape = null;
-                  this.setState({ draggingElement: multiElement });
-                } else {
-                  element.isSelected = false;
-                  element.points.push([0, 0]);
-                  element.shape = null;
-                  elements = [...elements, element];
-                  this.setState({
-                    draggingElement: element,
-                  });
-                }
-              } else {
-                elements = [...elements, element];
-                this.setState({ multiElement: null, draggingElement: element });
               }
 
+              elements = [...elements, element];
+              this.setState({ draggingElement: element });
+
               let lastX = x;
               let lastY = y;
 
@@ -1074,75 +1049,6 @@ export class App extends React.Component<any, AppState> {
                 lastY = e.clientY - CANVAS_WINDOW_OFFSET_TOP;
               }
 
-              let resizeArrowFn:
-                | ((
-                    element: ExcalidrawElement,
-                    p1: Point,
-                    deltaX: number,
-                    deltaY: number,
-                    mouseX: number,
-                    mouseY: number,
-                    perfect: boolean,
-                  ) => void)
-                | null = null;
-
-              const arrowResizeOrigin = (
-                element: ExcalidrawElement,
-                p1: Point,
-                deltaX: number,
-                deltaY: number,
-                mouseX: number,
-                mouseY: number,
-                perfect: boolean,
-              ) => {
-                // TODO: Implement perfect sizing for origin
-                if (perfect) {
-                  const absPx = p1[0] + element.x;
-                  const absPy = p1[1] + element.y;
-
-                  let { width, height } = getPerfectElementSize(
-                    "arrow",
-                    mouseX - element.x - p1[0],
-                    mouseY - element.y - p1[1],
-                  );
-
-                  const dx = element.x + width + p1[0];
-                  const dy = element.y + height + p1[1];
-                  element.x = dx;
-                  element.y = dy;
-                  p1[0] = absPx - element.x;
-                  p1[1] = absPy - element.y;
-                } else {
-                  element.x += deltaX;
-                  element.y += deltaY;
-                  p1[0] -= deltaX;
-                  p1[1] -= deltaY;
-                }
-              };
-
-              const arrowResizeEnd = (
-                element: ExcalidrawElement,
-                p1: Point,
-                deltaX: number,
-                deltaY: number,
-                mouseX: number,
-                mouseY: number,
-                perfect: boolean,
-              ) => {
-                if (perfect) {
-                  const { width, height } = getPerfectElementSize(
-                    "arrow",
-                    mouseX - element.x,
-                    mouseY - element.y,
-                  );
-                  p1[0] = width;
-                  p1[1] = height;
-                } else {
-                  p1[0] += deltaX;
-                  p1[1] += deltaY;
-                }
-              };
-
               const onMouseMove = (e: MouseEvent) => {
                 const target = e.target;
                 if (!(target instanceof HTMLElement)) {
@@ -1169,16 +1075,6 @@ export class App extends React.Component<any, AppState> {
                   return;
                 }
 
-                // for arrows, don't start dragging until a given threshold
-                //  to ensure we don't create a 2-point arrow by mistake when
-                //  user clicks mouse in a way that it moves a tiny bit (thus
-                //  triggering mousemove)
-                if (!draggingOccurred && this.state.elementType === "arrow") {
-                  const { x, y } = viewportCoordsToSceneCoords(e, this.state);
-                  if (distance2d(x, y, originX, originY) < DRAGGING_THRESHOLD)
-                    return;
-                }
-
                 if (isResizingElements && this.state.resizingElement) {
                   const el = this.state.resizingElement;
                   const selectedElements = elements.filter(el => el.isSelected);
@@ -1191,217 +1087,73 @@ export class App extends React.Component<any, AppState> {
                       element.type === "line" || element.type === "arrow";
                     switch (resizeHandle) {
                       case "nw":
-                        if (
-                          element.type === "arrow" &&
-                          element.points.length === 2
-                        ) {
-                          const [, p1] = element.points;
-
-                          if (!resizeArrowFn) {
-                            if (p1[0] < 0 || p1[1] < 0) {
-                              resizeArrowFn = arrowResizeEnd;
-                            } else {
-                              resizeArrowFn = arrowResizeOrigin;
-                            }
-                          }
-                          resizeArrowFn(
-                            element,
-                            p1,
-                            deltaX,
-                            deltaY,
-                            x,
-                            y,
-                            e.shiftKey,
-                          );
-                        } else {
-                          element.width -= deltaX;
-                          element.x += deltaX;
-
-                          if (e.shiftKey) {
-                            if (isLinear) {
-                              resizePerfectLineForNWHandler(element, x, y);
-                            } else {
-                              element.y += element.height - element.width;
-                              element.height = element.width;
-                            }
+                        element.width -= deltaX;
+                        element.x += deltaX;
+
+                        if (e.shiftKey) {
+                          if (isLinear) {
+                            resizePerfectLineForNWHandler(element, x, y);
                           } else {
-                            element.height -= deltaY;
-                            element.y += deltaY;
+                            element.y += element.height - element.width;
+                            element.height = element.width;
                           }
+                        } else {
+                          element.height -= deltaY;
+                          element.y += deltaY;
                         }
                         break;
                       case "ne":
-                        if (
-                          element.type === "arrow" &&
-                          element.points.length === 2
-                        ) {
-                          const [, p1] = element.points;
-                          if (!resizeArrowFn) {
-                            if (p1[0] >= 0) {
-                              resizeArrowFn = arrowResizeEnd;
-                            } else {
-                              resizeArrowFn = arrowResizeOrigin;
-                            }
-                          }
-                          resizeArrowFn(
-                            element,
-                            p1,
-                            deltaX,
-                            deltaY,
-                            x,
-                            y,
-                            e.shiftKey,
-                          );
+                        element.width += deltaX;
+                        if (e.shiftKey) {
+                          element.y += element.height - element.width;
+                          element.height = element.width;
                         } else {
-                          element.width += deltaX;
-                          if (e.shiftKey) {
-                            element.y += element.height - element.width;
-                            element.height = element.width;
-                          } else {
-                            element.height -= deltaY;
-                            element.y += deltaY;
-                          }
+                          element.height -= deltaY;
+                          element.y += deltaY;
                         }
                         break;
                       case "sw":
-                        if (
-                          element.type === "arrow" &&
-                          element.points.length === 2
-                        ) {
-                          const [, p1] = element.points;
-                          if (!resizeArrowFn) {
-                            if (p1[0] <= 0) {
-                              resizeArrowFn = arrowResizeEnd;
-                            } else {
-                              resizeArrowFn = arrowResizeOrigin;
-                            }
-                          }
-                          resizeArrowFn(
-                            element,
-                            p1,
-                            deltaX,
-                            deltaY,
-                            x,
-                            y,
-                            e.shiftKey,
-                          );
+                        element.width -= deltaX;
+                        element.x += deltaX;
+                        if (e.shiftKey) {
+                          element.height = element.width;
                         } else {
-                          element.width -= deltaX;
-                          element.x += deltaX;
-                          if (e.shiftKey) {
-                            element.height = element.width;
-                          } else {
-                            element.height += deltaY;
-                          }
+                          element.height += deltaY;
                         }
                         break;
                       case "se":
-                        if (
-                          element.type === "arrow" &&
-                          element.points.length === 2
-                        ) {
-                          const [, p1] = element.points;
-                          if (!resizeArrowFn) {
-                            if (p1[0] > 0 || p1[1] > 0) {
-                              resizeArrowFn = arrowResizeEnd;
-                            } else {
-                              resizeArrowFn = arrowResizeOrigin;
-                            }
-                          }
-                          resizeArrowFn(
-                            element,
-                            p1,
-                            deltaX,
-                            deltaY,
-                            x,
-                            y,
-                            e.shiftKey,
-                          );
-                        } else {
-                          if (e.shiftKey) {
-                            if (isLinear) {
-                              const { width, height } = getPerfectElementSize(
-                                element.type,
-                                x - element.x,
-                                y - element.y,
-                              );
-                              element.width = width;
-                              element.height = height;
-                            } else {
-                              element.width += deltaX;
-                              element.height = element.width;
-                            }
+                        if (e.shiftKey) {
+                          if (isLinear) {
+                            const { width, height } = getPerfectElementSize(
+                              element.type,
+                              x - element.x,
+                              y - element.y,
+                            );
+                            element.width = width;
+                            element.height = height;
                           } else {
                             element.width += deltaX;
-                            element.height += deltaY;
+                            element.height = element.width;
                           }
+                        } else {
+                          element.width += deltaX;
+                          element.height += deltaY;
                         }
                         break;
-                      case "n": {
+                      case "n":
                         element.height -= deltaY;
                         element.y += deltaY;
-
-                        if (element.points.length > 0) {
-                          const len = element.points.length;
-
-                          const points = [...element.points].sort(
-                            (a, b) => a[1] - b[1],
-                          );
-
-                          for (let i = 1; i < points.length; ++i) {
-                            const pnt = points[i];
-                            pnt[1] -= deltaY / (len - i);
-                          }
-                        }
                         break;
-                      }
-                      case "w": {
+                      case "w":
                         element.width -= deltaX;
                         element.x += deltaX;
-
-                        if (element.points.length > 0) {
-                          const len = element.points.length;
-                          const points = [...element.points].sort(
-                            (a, b) => a[0] - b[0],
-                          );
-
-                          for (let i = 0; i < points.length; ++i) {
-                            const pnt = points[i];
-                            pnt[0] -= deltaX / (len - i);
-                          }
-                        }
                         break;
-                      }
-                      case "s": {
+                      case "s":
                         element.height += deltaY;
-                        if (element.points.length > 0) {
-                          const len = element.points.length;
-                          const points = [...element.points].sort(
-                            (a, b) => a[1] - b[1],
-                          );
-
-                          for (let i = 1; i < points.length; ++i) {
-                            const pnt = points[i];
-                            pnt[1] += deltaY / (len - i);
-                          }
-                        }
                         break;
-                      }
-                      case "e": {
+                      case "e":
                         element.width += deltaX;
-                        if (element.points.length > 0) {
-                          const len = element.points.length;
-                          const points = [...element.points].sort(
-                            (a, b) => a[0] - b[0],
-                          );
-
-                          for (let i = 1; i < points.length; ++i) {
-                            const pnt = points[i];
-                            pnt[0] += deltaX / (len - i);
-                          }
-                        }
                         break;
-                      }
                     }
 
                     if (resizeHandle) {
@@ -1483,30 +1235,6 @@ export class App extends React.Component<any, AppState> {
 
                 draggingElement.width = width;
                 draggingElement.height = height;
-
-                if (this.state.elementType === "arrow") {
-                  draggingOccurred = true;
-                  const points = draggingElement.points;
-                  let dx = x - draggingElement.x;
-                  let dy = y - draggingElement.y;
-
-                  if (e.shiftKey && points.length === 2) {
-                    ({ width: dx, height: dy } = getPerfectElementSize(
-                      this.state.elementType,
-                      dx,
-                      dy,
-                    ));
-                  }
-
-                  if (points.length === 1) {
-                    points.push([dx, dy]);
-                  } else if (points.length > 1) {
-                    const pnt = points[points.length - 1];
-                    pnt[0] = dx;
-                    pnt[1] = dy;
-                  }
-                }
-
                 draggingElement.shape = null;
 
                 if (this.state.elementType === "selection") {
@@ -1530,33 +1258,15 @@ export class App extends React.Component<any, AppState> {
                 const {
                   draggingElement,
                   resizingElement,
-                  multiElement,
                   elementType,
                   elementLocked,
                 } = this.state;
 
-                resizeArrowFn = null;
                 lastMouseUp = null;
                 isHoldingMouseButton = false;
                 window.removeEventListener("mousemove", onMouseMove);
                 window.removeEventListener("mouseup", onMouseUp);
 
-                if (elementType === "arrow") {
-                  if (draggingElement!.points.length > 1) {
-                    history.resumeRecording();
-                  }
-                  if (!draggingOccurred && !multiElement) {
-                    this.setState({ multiElement: this.state.draggingElement });
-                  } else if (draggingOccurred && !multiElement) {
-                    this.state.draggingElement!.isSelected = true;
-                    this.setState({
-                      draggingElement: null,
-                      elementType: "selection",
-                    });
-                  }
-                  return;
-                }
-
                 if (
                   elementType !== "selection" &&
                   draggingElement &&
@@ -1641,15 +1351,9 @@ export class App extends React.Component<any, AppState> {
               window.addEventListener("mousemove", onMouseMove);
               window.addEventListener("mouseup", onMouseUp);
 
-              if (
-                !this.state.multiElement ||
-                (this.state.multiElement &&
-                  this.state.multiElement.points.length < 2)
-              ) {
-                // We don't want to save history on mouseDown, only on mouseUp when it's fully configured
-                history.skipRecording();
-                this.setState({});
-              }
+              // We don't want to save history on mouseDown, only on mouseUp when it's fully configured
+              history.skipRecording();
+              this.setState({});
             }}
             onDoubleClick={e => {
               const { x, y } = viewportCoordsToSceneCoords(e, this.state);

+ 0 - 65
src/math.ts

@@ -1,5 +1,3 @@
-import { Point } from "roughjs/bin/geometry";
-
 // https://stackoverflow.com/a/6853926/232122
 export function distanceBetweenPointAndSegment(
   x: number,
@@ -54,66 +52,3 @@ export function rotate(
     (x1 - x2) * Math.sin(angle) + (y1 - y2) * Math.cos(angle) + y2,
   ];
 }
-
-export const getPointOnAPath = (point: Point, path: Point[]) => {
-  const [px, py] = point;
-  const [start, ...other] = path;
-  let [lastX, lastY] = start;
-  let kLine: number = 0;
-  let idx: number = 0;
-
-  // if any item in the array is true, it means that a point is
-  // on some segment of a line based path
-  const retVal = other.some(([x2, y2], i) => {
-    // we always take a line when dealing with line segments
-    const x1 = lastX;
-    const y1 = lastY;
-
-    lastX = x2;
-    lastY = y2;
-
-    // if a point is not within the domain of the line segment
-    // it is not on the line segment
-    if (px < x1 || px > x2) {
-      return false;
-    }
-
-    // check if all points lie on the same line
-    // y1 = kx1 + b, y2 = kx2 + b
-    // y2 - y1 = k(x2 - x2) -> k = (y2 - y1) / (x2 - x1)
-
-    // coefficient for the line (p0, p1)
-    const kL = (y2 - y1) / (x2 - x1);
-
-    // coefficient for the line segment (p0, point)
-    const kP1 = (py - y1) / (px - x1);
-
-    // coefficient for the line segment (point, p1)
-    const kP2 = (py - y2) / (px - x2);
-
-    // because we are basing both lines from the same starting point
-    // the only option for collinearity is having same coefficients
-
-    // using it for floating point comparisons
-    const epsilon = 0.3;
-
-    // if coefficient is more than an arbitrary epsilon,
-    // these lines are nor collinear
-    if (Math.abs(kP1 - kL) > epsilon && Math.abs(kP2 - kL) > epsilon) {
-      return false;
-    }
-
-    // store the coefficient because we are goint to need it
-    kLine = kL;
-    idx = i;
-
-    return true;
-  });
-
-  // Return a coordinate that is always on the line segment
-  if (retVal === true) {
-    return { x: point[0], y: kLine * point[0], segment: idx };
-  }
-
-  return null;
-};

+ 3 - 8
src/renderer/renderElement.ts

@@ -7,7 +7,6 @@ import {
 } from "../element/bounds";
 import { RoughCanvas } from "roughjs/bin/canvas";
 import { Drawable } from "roughjs/bin/core";
-import { Point } from "roughjs/bin/geometry";
 import { RoughSVG } from "roughjs/bin/svg";
 import { RoughGenerator } from "roughjs/bin/generator";
 import { SVG_NS } from "../utils";
@@ -90,23 +89,18 @@ function generateElement(
         );
         break;
       case "arrow": {
-        const [x2, y2, x3, y3, x4, y4] = getArrowPoints(element);
+        const [x1, y1, x2, y2, x3, y3, x4, y4] = getArrowPoints(element);
         const options = {
           stroke: element.strokeColor,
           strokeWidth: element.strokeWidth,
           roughness: element.roughness,
           seed: element.seed,
         };
-        // points array can be empty in the beginning, so it is important to add
-        // initial position to it
-        const points: Point[] = element.points.length
-          ? element.points
-          : [[0, 0]];
         element.shape = [
           //    \
           generator.line(x3, y3, x2, y2, options),
           // -----
-          generator.curve(points, options),
+          generator.line(x1, y1, x2, y2, options),
           //    /
           generator.line(x4, y4, x2, y2, options),
         ];
@@ -175,6 +169,7 @@ export function renderElement(
         context.fillStyle = fillStyle;
         context.font = font;
         context.globalAlpha = 1;
+        break;
       } else {
         throw new Error("Unimplemented type " + element.type);
       }

+ 12 - 20
src/renderer/renderScene.ts

@@ -76,7 +76,10 @@ export function renderScene(
       element.y + sceneState.scrollY,
     );
     renderElement(element, rc, context);
-    context.resetTransform();
+    context.translate(
+      -element.x - sceneState.scrollX,
+      -element.y - sceneState.scrollY,
+    );
   });
 
   if (renderSelection) {
@@ -104,11 +107,9 @@ export function renderScene(
 
     if (selectedElements.length === 1 && selectedElements[0].type !== "text") {
       const handlers = handlerRectangles(selectedElements[0], sceneState);
-      Object.values(handlers)
-        .filter(handler => handler !== undefined)
-        .forEach(handler => {
-          context.strokeRect(handler[0], handler[1], handler[2], handler[3]);
-        });
+      Object.values(handlers).forEach(handler => {
+        context.strokeRect(handler[0], handler[1], handler[2], handler[3]);
+      });
     }
   }
 
@@ -148,20 +149,11 @@ function isVisibleElement(
   canvasHeight: number,
 ) {
   let [x1, y1, x2, y2] = getElementAbsoluteCoords(element);
-  if (element.type !== "arrow") {
-    x1 += scrollX;
-    y1 += scrollY;
-    x2 += scrollX;
-    y2 += scrollY;
-    return x2 >= 0 && x1 <= canvasWidth && y2 >= 0 && y1 <= canvasHeight;
-  } else {
-    return (
-      x2 + scrollX >= 0 &&
-      x1 + scrollX <= canvasWidth &&
-      y2 + scrollY >= 0 &&
-      y1 + scrollY <= canvasHeight
-    );
-  }
+  x1 += scrollX;
+  y1 += scrollY;
+  x2 += scrollX;
+  y2 += scrollY;
+  return x2 >= 0 && x1 <= canvasWidth && y2 >= 0 && y1 <= canvasHeight;
 }
 
 // This should be only called for exporting purposes

+ 7 - 6
src/scene/data.ts

@@ -1,6 +1,6 @@
 import { ExcalidrawElement } from "../element/types";
 
-import { getDefaultAppState, cleanAppStateForExport } from "../appState";
+import { getDefaultAppState } from "../appState";
 
 import { AppState } from "../types";
 import { ExportType, PreviousScene } from "./types";
@@ -24,7 +24,7 @@ const BACKEND_GET = "https://json.excalidraw.com/api/v1/";
 
 interface DataState {
   elements: readonly ExcalidrawElement[];
-  appState: AppState | null;
+  appState: AppState;
   selectedId?: number;
 }
 
@@ -36,9 +36,10 @@ export function serializeAsJSON(
     {
       type: "excalidraw",
       version: 1,
-      source: window.location.origin,
+      appState: {
+        viewBackgroundColor: appState.viewBackgroundColor,
+      },
       elements: elements.map(({ shape, isSelected, ...el }) => el),
-      appState: cleanAppStateForExport(appState),
     },
     null,
     2,
@@ -254,7 +255,7 @@ export async function exportCanvas(
 
 function restore(
   savedElements: readonly ExcalidrawElement[],
-  savedState: AppState | null,
+  savedState: AppState,
 ): DataState {
   return {
     elements: savedElements.map(element => ({
@@ -290,7 +291,7 @@ export function restoreFromLocalStorage() {
   let appState = null;
   if (savedState) {
     try {
-      appState = JSON.parse(savedState) as AppState;
+      appState = JSON.parse(savedState);
     } catch (e) {
       // Do nothing because appState is already null
     }

+ 0 - 1
src/types.ts

@@ -3,7 +3,6 @@ import { ExcalidrawElement } from "./element/types";
 export type AppState = {
   draggingElement: ExcalidrawElement | null;
   resizingElement: ExcalidrawElement | null;
-  multiElement: ExcalidrawElement | null;
   // element being edited, but not necessarily added to elements array yet
   //  (e.g. text element when typing into the input)
   editingElement: ExcalidrawElement | null;

+ 0 - 6
src/utils.ts

@@ -103,9 +103,3 @@ export function removeSelection() {
 export function distance(x: number, y: number) {
   return Math.abs(x - y);
 }
-
-export function distance2d(x1: number, y1: number, x2: number, y2: number) {
-  const xd = x2 - x1;
-  const yd = y2 - y1;
-  return Math.sqrt(xd * xd + yd * yd);
-}