chore: Unify math types, utils and functions (#8389)

Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
This commit is contained in:
Márk Tolmács 2024-09-03 00:23:38 +02:00 committed by GitHub
parent e3d1dee9d0
commit f4dd23fc31
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
98 changed files with 4291 additions and 3661 deletions

View file

@ -7,6 +7,7 @@ import { API } from "./helpers/api";
import { KEYS } from "../keys";
import { actionWrapTextInContainer } from "../actions/actionBoundText";
import { arrayToMap } from "../utils";
import { point } from "../../math";
const { h } = window;
@ -31,12 +32,7 @@ describe("element binding", () => {
y: 0,
width: 100,
height: 1,
points: [
[0, 0],
[0, 0],
[100, 0],
[100, 0],
],
points: [point(0, 0), point(0, 0), point(100, 0), point(100, 0)],
});
API.setElements([rect, arrow]);
expect(arrow.startBinding).toBe(null);
@ -314,10 +310,7 @@ describe("element binding", () => {
const arrow1 = API.createElement({
type: "arrow",
id: "arrow1",
points: [
[0, 0],
[0, -87.45777932247563],
],
points: [point(0, 0), point(0, -87.45777932247563)],
startBinding: {
elementId: "rectangle1",
focus: 0.2,
@ -335,10 +328,7 @@ describe("element binding", () => {
const arrow2 = API.createElement({
type: "arrow",
id: "arrow2",
points: [
[0, 0],
[0, -87.45777932247563],
],
points: [point(0, 0), point(0, -87.45777932247563)],
startBinding: {
elementId: "text1",
focus: 0.2,

View file

@ -1,3 +1,4 @@
import type { Radians } from "../../../math";
import { DEFAULT_FONT_FAMILY } from "../../constants";
import type { ExcalidrawElement } from "../../element/types";
@ -7,7 +8,7 @@ const elementBase: Omit<ExcalidrawElement, "type"> = {
y: 237,
width: 214,
height: 214,
angle: 0,
angle: 0 as Radians,
strokeColor: "#000000",
backgroundColor: "#15aabf",
fillStyle: "hachure",

View file

@ -28,6 +28,8 @@ import { KEYS } from "../keys";
import { getBoundTextElementPosition } from "../element/textElement";
import { createPasteEvent } from "../clipboard";
import { arrayToMap, cloneJSON } from "../utils";
import type { LocalPoint } from "../../math";
import { point, type Radians } from "../../math";
const { h } = window;
const mouse = new Pointer("mouse");
@ -131,7 +133,7 @@ const createLinearElementWithCurveInsideMinMaxPoints = (
y: -2412.5069664197654,
width: 1750.4888916015625,
height: 410.51605224609375,
angle: 0,
angle: 0 as Radians,
strokeColor: "#000000",
backgroundColor: "#fa5252",
fillStyle: "hachure",
@ -145,9 +147,9 @@ const createLinearElementWithCurveInsideMinMaxPoints = (
link: null,
locked: false,
points: [
[0, 0],
[-922.4761962890625, 300.3277587890625],
[828.0126953125, 410.51605224609375],
point<LocalPoint>(0, 0),
point<LocalPoint>(-922.4761962890625, 300.3277587890625),
point<LocalPoint>(828.0126953125, 410.51605224609375),
],
});
};
@ -423,8 +425,8 @@ describe("arrow", () => {
});
it("flips a rotated arrow horizontally with line inside min/max points bounds", async () => {
const originalAngle = Math.PI / 4;
const expectedAngle = (7 * Math.PI) / 4;
const originalAngle = (Math.PI / 4) as Radians;
const expectedAngle = ((7 * Math.PI) / 4) as Radians;
const line = createLinearElementWithCurveInsideMinMaxPoints("arrow");
API.setElements([line]);
API.setAppState({
@ -444,8 +446,8 @@ describe("arrow", () => {
});
it("flips a rotated arrow vertically with line inside min/max points bounds", async () => {
const originalAngle = Math.PI / 4;
const expectedAngle = (7 * Math.PI) / 4;
const originalAngle = (Math.PI / 4) as Radians;
const expectedAngle = ((7 * Math.PI) / 4) as Radians;
const line = createLinearElementWithCurveInsideMinMaxPoints("arrow");
API.setElements([line]);
API.setAppState({
@ -477,8 +479,8 @@ describe("arrow", () => {
//TODO: elements with curve outside minMax points have a wrong bounding box!!!
it.skip("flips a rotated arrow horizontally with line outside min/max points bounds", async () => {
const originalAngle = Math.PI / 4;
const expectedAngle = (7 * Math.PI) / 4;
const originalAngle = (Math.PI / 4) as Radians;
const expectedAngle = ((7 * Math.PI) / 4) as Radians;
const line = createLinearElementsWithCurveOutsideMinMaxPoints("arrow");
API.updateElement(line, { angle: originalAngle });
API.setElements([line]);
@ -501,8 +503,8 @@ describe("arrow", () => {
//TODO: elements with curve outside minMax points have a wrong bounding box!!!
it.skip("flips a rotated arrow vertically with line outside min/max points bounds", async () => {
const originalAngle = Math.PI / 4;
const expectedAngle = (7 * Math.PI) / 4;
const originalAngle = (Math.PI / 4) as Radians;
const expectedAngle = ((7 * Math.PI) / 4) as Radians;
const line = createLinearElementsWithCurveOutsideMinMaxPoints("arrow");
API.updateElement(line, { angle: originalAngle });
API.setElements([line]);
@ -585,8 +587,8 @@ describe("line", () => {
//TODO: elements with curve outside minMax points have a wrong bounding box
it.skip("flips a rotated line horizontally with line outside min/max points bounds", async () => {
const originalAngle = Math.PI / 4;
const expectedAngle = (7 * Math.PI) / 4;
const originalAngle = (Math.PI / 4) as Radians;
const expectedAngle = ((7 * Math.PI) / 4) as Radians;
const line = createLinearElementsWithCurveOutsideMinMaxPoints("line");
API.updateElement(line, { angle: originalAngle });
API.setElements([line]);
@ -600,8 +602,8 @@ describe("line", () => {
//TODO: elements with curve outside minMax points have a wrong bounding box
it.skip("flips a rotated line vertically with line outside min/max points bounds", async () => {
const originalAngle = Math.PI / 4;
const expectedAngle = (7 * Math.PI) / 4;
const originalAngle = (Math.PI / 4) as Radians;
const expectedAngle = ((7 * Math.PI) / 4) as Radians;
const line = createLinearElementsWithCurveOutsideMinMaxPoints("line");
API.updateElement(line, { angle: originalAngle });
API.setElements([line]);
@ -619,8 +621,8 @@ describe("line", () => {
});
it("flips a rotated line horizontally with line inside min/max points bounds", async () => {
const originalAngle = Math.PI / 4;
const expectedAngle = (7 * Math.PI) / 4;
const originalAngle = (Math.PI / 4) as Radians;
const expectedAngle = ((7 * Math.PI) / 4) as Radians;
const line = createLinearElementWithCurveInsideMinMaxPoints("line");
API.setElements([line]);
API.setAppState({
@ -640,8 +642,8 @@ describe("line", () => {
});
it("flips a rotated line vertically with line inside min/max points bounds", async () => {
const originalAngle = Math.PI / 4;
const expectedAngle = (7 * Math.PI) / 4;
const originalAngle = (Math.PI / 4) as Radians;
const expectedAngle = ((7 * Math.PI) / 4) as Radians;
const line = createLinearElementWithCurveInsideMinMaxPoints("line");
API.setElements([line]);
API.setAppState({
@ -772,8 +774,8 @@ describe("image", () => {
});
it("flips an rotated image horizontally correctly", async () => {
const originalAngle = Math.PI / 4;
const expectedAngle = (7 * Math.PI) / 4;
const originalAngle = (Math.PI / 4) as Radians;
const expectedAngle = ((7 * Math.PI) / 4) as Radians;
//paste image
await createImage();
await waitFor(() => {
@ -790,8 +792,8 @@ describe("image", () => {
});
it("flips an rotated image vertically correctly", async () => {
const originalAngle = Math.PI / 4;
const expectedAngle = (7 * Math.PI) / 4;
const originalAngle = (Math.PI / 4) as Radians;
const expectedAngle = ((7 * Math.PI) / 4) as Radians;
//paste image
await createImage();
await waitFor(() => {

View file

@ -1,70 +0,0 @@
import * as GA from "../ga";
import { point, toString, direction, offset } from "../ga";
import * as GAPoint from "../gapoints";
import * as GALine from "../galines";
import * as GATransform from "../gatransforms";
describe("geometric algebra", () => {
describe("points", () => {
it("distanceToLine", () => {
const point = GA.point(3, 3);
const line = GALine.equation(0, 1, -1);
expect(GAPoint.distanceToLine(point, line)).toEqual(2);
});
it("distanceToLine neg", () => {
const point = GA.point(-3, -3);
const line = GALine.equation(0, 1, -1);
expect(GAPoint.distanceToLine(point, line)).toEqual(-4);
});
});
describe("lines", () => {
it("through", () => {
const a = GA.point(0, 0);
const b = GA.point(2, 0);
expect(toString(GALine.through(a, b))).toEqual(
toString(GALine.equation(0, 2, 0)),
);
});
it("parallel", () => {
const point = GA.point(3, 3);
const line = GALine.equation(0, 1, -1);
const parallel = GALine.parallel(line, 2);
expect(GAPoint.distanceToLine(point, parallel)).toEqual(0);
});
});
describe("translation", () => {
it("points", () => {
const start = point(2, 2);
const move = GATransform.translation(direction(0, 1));
const end = GATransform.apply(move, start);
expect(toString(end)).toEqual(toString(point(2, 3)));
});
it("points 2", () => {
const start = point(2, 2);
const move = GATransform.translation(offset(3, 4));
const end = GATransform.apply(move, start);
expect(toString(end)).toEqual(toString(point(5, 6)));
});
it("lines", () => {
const original = GALine.through(point(2, 2), point(3, 4));
const move = GATransform.translation(offset(3, 4));
const parallel = GATransform.apply(move, original);
expect(toString(parallel)).toEqual(
toString(GALine.through(point(5, 6), point(6, 8))),
);
});
});
describe("rotation", () => {
it("points", () => {
const start = point(2, 2);
const pivot = point(1, 1);
const rotate = GATransform.rotation(pivot, Math.PI / 2);
const end = GATransform.apply(rotate, start);
expect(toString(end)).toEqual(toString(point(2, 0)));
});
});
});

View file

@ -27,7 +27,7 @@ import {
newImageElement,
newMagicFrameElement,
} from "../../element/newElement";
import type { AppState, Point } from "../../types";
import type { AppState } from "../../types";
import { getSelectedElements } from "../../scene/selection";
import { isLinearElementType } from "../../element/typeChecks";
import type { Mutable } from "../../utility-types";
@ -36,6 +36,7 @@ import type App from "../../components/App";
import { createTestHook } from "../../components/App";
import type { Action } from "../../actions/types";
import { mutateElement } from "../../element/mutateElement";
import { point, type LocalPoint, type Radians } from "../../../math";
const readFile = util.promisify(fs.readFile);
// so that window.h is available when App.tsx is not imported as well.
@ -171,7 +172,7 @@ export class API {
containerId?: T extends "text"
? ExcalidrawTextElement["containerId"]
: never;
points?: T extends "arrow" | "line" ? readonly Point[] : never;
points?: T extends "arrow" | "line" ? readonly LocalPoint[] : never;
locked?: boolean;
fileId?: T extends "image" ? string : never;
scale?: T extends "image" ? ExcalidrawImageElement["scale"] : never;
@ -218,7 +219,7 @@ export class API {
y,
frameId: rest.frameId ?? null,
index: rest.index ?? null,
angle: rest.angle ?? 0,
angle: (rest.angle ?? 0) as Radians,
strokeColor: rest.strokeColor ?? appState.currentItemStrokeColor,
backgroundColor:
rest.backgroundColor ?? appState.currentItemBackgroundColor,
@ -293,8 +294,8 @@ export class API {
height,
type,
points: rest.points ?? [
[0, 0],
[100, 100],
point<LocalPoint>(0, 0),
point<LocalPoint>(100, 100),
],
elbowed: rest.elbowed ?? false,
});
@ -306,8 +307,8 @@ export class API {
height,
type,
points: rest.points ?? [
[0, 0],
[100, 100],
point<LocalPoint>(0, 0),
point<LocalPoint>(100, 100),
],
});
break;

View file

@ -1,4 +1,4 @@
import type { Point, ToolType } from "../../types";
import type { ToolType } from "../../types";
import type {
ExcalidrawElement,
ExcalidrawLinearElement,
@ -30,10 +30,11 @@ import {
isFrameLikeElement,
} from "../../element/typeChecks";
import { getCommonBounds, getElementPointsCoords } from "../../element/bounds";
import { rotatePoint } from "../../math";
import { getTextEditor } from "../queries/dom";
import { arrayToMap } from "../../utils";
import { createTestHook } from "../../components/App";
import type { GlobalPoint, LocalPoint, Radians } from "../../../math";
import { point, pointRotateRads } from "../../../math";
// so that window.h is available when App.tsx is not imported as well.
createTestHook();
@ -131,27 +132,29 @@ export class Keyboard {
};
}
const getElementPointForSelection = (element: ExcalidrawElement): Point => {
const getElementPointForSelection = (
element: ExcalidrawElement,
): GlobalPoint => {
const { x, y, width, height, angle } = element;
const target: Point = [
const target = point<GlobalPoint>(
x +
(isLinearElement(element) || isFreeDrawElement(element) ? 0 : width / 2),
y,
];
let center: Point;
);
let center: GlobalPoint;
if (isLinearElement(element)) {
const bounds = getElementPointsCoords(element, element.points);
center = [(bounds[0] + bounds[2]) / 2, (bounds[1] + bounds[3]) / 2];
center = point((bounds[0] + bounds[2]) / 2, (bounds[1] + bounds[3]) / 2);
} else {
center = [x + width / 2, y + height / 2];
center = point(x + width / 2, y + height / 2);
}
if (isTextElement(element)) {
return center;
}
return rotatePoint(target, center, angle);
return pointRotateRads(target, center, angle);
};
export class Pointer {
@ -328,7 +331,7 @@ const transform = (
const isFrameSelected = elements.some(isFrameLikeElement);
const transformHandles = getTransformHandlesFromCoords(
[x1, y1, x2, y2, (x1 + x2) / 2, (y1 + y2) / 2],
0,
0 as Radians,
h.state.zoom,
"mouse",
isFrameSelected ? OMIT_SIDES_FOR_FRAME : OMIT_SIDES_FOR_MULTIPLE_ELEMENTS,
@ -450,7 +453,7 @@ export class UI {
width?: number;
height?: number;
angle?: number;
points?: T extends "line" | "arrow" | "freedraw" ? Point[] : never;
points?: T extends "line" | "arrow" | "freedraw" ? LocalPoint[] : never;
} = {},
): Element<T> & {
/** Returns the actual, current element from the elements array, instead
@ -459,9 +462,9 @@ export class UI {
} {
const width = initialWidth ?? initialHeight ?? size;
const height = initialHeight ?? size;
const points: Point[] = initialPoints ?? [
[0, 0],
[width, height],
const points: LocalPoint[] = initialPoints ?? [
point(0, 0),
point(width, height),
];
UI.clickTool(type);

View file

@ -44,6 +44,8 @@ import { queryByText } from "@testing-library/react";
import { HistoryEntry } from "../history";
import { AppStateChange, ElementsChange } from "../change";
import { Snapshot, StoreAction } from "../store";
import type { LocalPoint, Radians } from "../../math";
import { point } from "../../math";
const { h } = window;
@ -2038,9 +2040,9 @@ describe("history", () => {
width: 178.9000000000001,
height: 236.10000000000002,
points: [
[0, 0],
[178.9000000000001, 0],
[178.9000000000001, 236.10000000000002],
point(0, 0),
point(178.9000000000001, 0),
point(178.9000000000001, 236.10000000000002),
],
startBinding: {
elementId: "KPrBI4g_v9qUB1XxYLgSz",
@ -2156,12 +2158,12 @@ describe("history", () => {
elements: [
newElementWith(h.elements[0] as ExcalidrawLinearElement, {
points: [
[0, 0],
[5, 5],
[10, 10],
[15, 15],
[20, 20],
],
point(0, 0),
point(5, 5),
point(10, 10),
point(15, 15),
point(20, 20),
] as LocalPoint[],
}),
],
storeAction: StoreAction.UPDATE,
@ -4003,7 +4005,7 @@ describe("history", () => {
newElementWith(h.elements[0], {
x: 200,
y: 200,
angle: 90,
angle: 90 as Radians,
}),
],
storeAction: StoreAction.CAPTURE,
@ -4121,7 +4123,7 @@ describe("history", () => {
newElementWith(h.elements[0], {
x: 205,
y: 205,
angle: 90,
angle: 90 as Radians,
}),
],
storeAction: StoreAction.CAPTURE,

View file

@ -8,7 +8,6 @@ import type {
SceneElementsMap,
} from "../element/types";
import { Excalidraw, mutateElement } from "../index";
import { centerPoint } from "../math";
import { reseed } from "../random";
import * as StaticScene from "../renderer/staticScene";
import * as InteractiveCanvas from "../renderer/interactiveScene";
@ -16,7 +15,6 @@ import * as InteractiveCanvas from "../renderer/interactiveScene";
import { Keyboard, Pointer, UI } from "./helpers/ui";
import { screen, render, fireEvent, GlobalTestState } from "./test-utils";
import { API } from "../tests/helpers/api";
import type { Point } from "../types";
import { KEYS } from "../keys";
import { LinearElementEditor } from "../element/linearElementEditor";
import { act, queryByTestId, queryByText } from "@testing-library/react";
@ -29,6 +27,8 @@ import * as textElementUtils from "../element/textElement";
import { ROUNDNESS, VERTICAL_ALIGN } from "../constants";
import { vi } from "vitest";
import { arrayToMap } from "../utils";
import type { GlobalPoint } from "../../math";
import { pointCenter, point } from "../../math";
const renderInteractiveScene = vi.spyOn(
InteractiveCanvas,
@ -57,9 +57,9 @@ describe("Test Linear Elements", () => {
interactiveCanvas = container.querySelector("canvas.interactive")!;
});
const p1: Point = [20, 20];
const p2: Point = [60, 20];
const midpoint = centerPoint(p1, p2);
const p1 = point<GlobalPoint>(20, 20);
const p2 = point<GlobalPoint>(60, 20);
const midpoint = pointCenter<GlobalPoint>(p1, p2);
const delta = 50;
const mouse = new Pointer("mouse");
@ -75,10 +75,7 @@ describe("Test Linear Elements", () => {
height: 0,
type,
roughness,
points: [
[0, 0],
[p2[0] - p1[0], p2[1] - p1[1]],
],
points: [point(0, 0), point(p2[0] - p1[0], p2[1] - p1[1])],
roundness,
});
API.setElements([line]);
@ -102,9 +99,9 @@ describe("Test Linear Elements", () => {
type,
roughness,
points: [
[0, 0],
[p3[0], p3[1]],
[p2[0] - p1[0], p2[1] - p1[1]],
point(0, 0),
point(p3[0], p3[1]),
point(p2[0] - p1[0], p2[1] - p1[1]),
],
roundness,
});
@ -129,7 +126,7 @@ describe("Test Linear Elements", () => {
expect(h.state.editingLinearElement?.elementId).toEqual(line.id);
};
const drag = (startPoint: Point, endPoint: Point) => {
const drag = (startPoint: GlobalPoint, endPoint: GlobalPoint) => {
fireEvent.pointerDown(interactiveCanvas, {
clientX: startPoint[0],
clientY: startPoint[1],
@ -144,7 +141,7 @@ describe("Test Linear Elements", () => {
});
};
const deletePoint = (point: Point) => {
const deletePoint = (point: GlobalPoint) => {
fireEvent.pointerDown(interactiveCanvas, {
clientX: point[0],
clientY: point[1],
@ -164,7 +161,7 @@ describe("Test Linear Elements", () => {
expect(line.points.length).toEqual(2);
mouse.clickAt(midpoint[0], midpoint[1]);
drag(midpoint, [midpoint[0] + 1, midpoint[1] + 1]);
drag(midpoint, point(midpoint[0] + 1, midpoint[1] + 1));
expect(line.points.length).toEqual(2);
@ -172,7 +169,7 @@ describe("Test Linear Elements", () => {
expect(line.y).toBe(originalY);
expect(line.points.length).toEqual(2);
drag(midpoint, [midpoint[0] + delta, midpoint[1] + delta]);
drag(midpoint, point(midpoint[0] + delta, midpoint[1] + delta));
expect(line.x).toBe(originalX);
expect(line.y).toBe(originalY);
expect(line.points.length).toEqual(3);
@ -187,7 +184,7 @@ describe("Test Linear Elements", () => {
expect((h.elements[0] as ExcalidrawLinearElement).points.length).toEqual(2);
// drag line from midpoint
drag(midpoint, [midpoint[0] + delta, midpoint[1] + delta]);
drag(midpoint, point(midpoint[0] + delta, midpoint[1] + delta));
expect(renderInteractiveScene.mock.calls.length).toMatchInlineSnapshot(`9`);
expect(renderStaticScene.mock.calls.length).toMatchInlineSnapshot(`7`);
expect(line.points.length).toEqual(3);
@ -251,7 +248,7 @@ describe("Test Linear Elements", () => {
mouse.clickAt(midpoint[0], midpoint[1]);
expect(line.points.length).toEqual(2);
drag(midpoint, [midpoint[0] + 1, midpoint[1] + 1]);
drag(midpoint, point(midpoint[0] + 1, midpoint[1] + 1));
expect(line.x).toBe(originalX);
expect(line.y).toBe(originalY);
expect(line.points.length).toEqual(3);
@ -264,7 +261,7 @@ describe("Test Linear Elements", () => {
enterLineEditingMode(line);
// drag line from midpoint
drag(midpoint, [midpoint[0] + delta, midpoint[1] + delta]);
drag(midpoint, point(midpoint[0] + delta, midpoint[1] + delta));
expect(renderInteractiveScene.mock.calls.length).toMatchInlineSnapshot(
`12`,
);
@ -356,10 +353,13 @@ describe("Test Linear Elements", () => {
h.state,
);
const startPoint = centerPoint(points[0], midPoints[0] as Point);
const startPoint = pointCenter(points[0], midPoints[0]!);
const deltaX = 50;
const deltaY = 20;
const endPoint: Point = [startPoint[0] + deltaX, startPoint[1] + deltaY];
const endPoint = point<GlobalPoint>(
startPoint[0] + deltaX,
startPoint[1] + deltaY,
);
// Move the element
drag(startPoint, endPoint);
@ -399,8 +399,8 @@ describe("Test Linear Elements", () => {
// This is the expected midpoint for line with round edge
// hence hardcoding it so if later some bug is introduced
// this will fail and we can fix it
const firstSegmentMidpoint: Point = [55, 45];
const lastSegmentMidpoint: Point = [75, 40];
const firstSegmentMidpoint = point<GlobalPoint>(55, 45);
const lastSegmentMidpoint = point<GlobalPoint>(75, 40);
let line: ExcalidrawLinearElement;
@ -414,17 +414,20 @@ describe("Test Linear Elements", () => {
it("should allow dragging lines from midpoints in between segments", async () => {
// drag line via first segment midpoint
drag(firstSegmentMidpoint, [
firstSegmentMidpoint[0] + delta,
firstSegmentMidpoint[1] + delta,
]);
drag(
firstSegmentMidpoint,
point(
firstSegmentMidpoint[0] + delta,
firstSegmentMidpoint[1] + delta,
),
);
expect(line.points.length).toEqual(4);
// drag line from last segment midpoint
drag(lastSegmentMidpoint, [
lastSegmentMidpoint[0] + delta,
lastSegmentMidpoint[1] + delta,
]);
drag(
lastSegmentMidpoint,
point(lastSegmentMidpoint[0] + delta, lastSegmentMidpoint[1] + delta),
);
expect(renderInteractiveScene.mock.calls.length).toMatchInlineSnapshot(
`16`,
@ -472,10 +475,10 @@ describe("Test Linear Elements", () => {
h.state,
);
const hitCoords: Point = [points[0][0], points[0][1]];
const hitCoords = point<GlobalPoint>(points[0][0], points[0][1]);
// Drag from first point
drag(hitCoords, [hitCoords[0] - delta, hitCoords[1] - delta]);
drag(hitCoords, point(hitCoords[0] - delta, hitCoords[1] - delta));
expect(renderInteractiveScene.mock.calls.length).toMatchInlineSnapshot(
`12`,
@ -513,10 +516,10 @@ describe("Test Linear Elements", () => {
h.state,
);
const hitCoords: Point = [points[0][0], points[0][1]];
const hitCoords = point<GlobalPoint>(points[0][0], points[0][1]);
// Drag from first point
drag(hitCoords, [hitCoords[0] + delta, hitCoords[1] + delta]);
drag(hitCoords, point(hitCoords[0] + delta, hitCoords[1] + delta));
expect(renderInteractiveScene.mock.calls.length).toMatchInlineSnapshot(
`12`,
@ -551,10 +554,10 @@ describe("Test Linear Elements", () => {
);
// dragging line from last segment midpoint
drag(lastSegmentMidpoint, [
lastSegmentMidpoint[0] + 50,
lastSegmentMidpoint[1] + 50,
]);
drag(
lastSegmentMidpoint,
point(lastSegmentMidpoint[0] + 50, lastSegmentMidpoint[1] + 50),
);
expect(line.points.length).toEqual(4);
const midPoints = LinearElementEditor.getEditorMidPoints(
@ -586,12 +589,14 @@ describe("Test Linear Elements", () => {
// This is the expected midpoint for line with round edge
// hence hardcoding it so if later some bug is introduced
// this will fail and we can fix it
const firstSegmentMidpoint: Point = [
55.9697848965255, 47.442326230998205,
];
const lastSegmentMidpoint: Point = [
76.08587175006699, 43.294165939653226,
];
const firstSegmentMidpoint = point<GlobalPoint>(
55.9697848965255,
47.442326230998205,
);
const lastSegmentMidpoint = point<GlobalPoint>(
76.08587175006699,
43.294165939653226,
);
let line: ExcalidrawLinearElement;
beforeEach(() => {
@ -605,17 +610,20 @@ describe("Test Linear Elements", () => {
it("should allow dragging lines from midpoints in between segments", async () => {
// drag line from first segment midpoint
drag(firstSegmentMidpoint, [
firstSegmentMidpoint[0] + delta,
firstSegmentMidpoint[1] + delta,
]);
drag(
firstSegmentMidpoint,
point(
firstSegmentMidpoint[0] + delta,
firstSegmentMidpoint[1] + delta,
),
);
expect(line.points.length).toEqual(4);
// drag line from last segment midpoint
drag(lastSegmentMidpoint, [
lastSegmentMidpoint[0] + delta,
lastSegmentMidpoint[1] + delta,
]);
drag(
lastSegmentMidpoint,
point(lastSegmentMidpoint[0] + delta, lastSegmentMidpoint[1] + delta),
);
expect(renderInteractiveScene.mock.calls.length).toMatchInlineSnapshot(
`16`,
);
@ -661,10 +669,10 @@ describe("Test Linear Elements", () => {
h.state,
);
const hitCoords: Point = [points[0][0], points[0][1]];
const hitCoords = point<GlobalPoint>(points[0][0], points[0][1]);
// Drag from first point
drag(hitCoords, [hitCoords[0] - delta, hitCoords[1] - delta]);
drag(hitCoords, point(hitCoords[0] - delta, hitCoords[1] - delta));
const newPoints = LinearElementEditor.getPointsGlobalCoordinates(
line,
@ -709,10 +717,10 @@ describe("Test Linear Elements", () => {
h.state,
);
const hitCoords: Point = [points[0][0], points[0][1]];
const hitCoords = point<GlobalPoint>(points[0][0], points[0][1]);
// Drag from first point
drag(hitCoords, [hitCoords[0] + delta, hitCoords[1] + delta]);
drag(hitCoords, point(hitCoords[0] + delta, hitCoords[1] + delta));
expect(renderInteractiveScene.mock.calls.length).toMatchInlineSnapshot(
`12`,
@ -741,10 +749,10 @@ describe("Test Linear Elements", () => {
it("should update all the midpoints when a point is deleted", async () => {
const elementsMap = arrayToMap(h.elements);
drag(lastSegmentMidpoint, [
lastSegmentMidpoint[0] + delta,
lastSegmentMidpoint[1] + delta,
]);
drag(
lastSegmentMidpoint,
point(lastSegmentMidpoint[0] + delta, lastSegmentMidpoint[1] + delta),
);
expect(line.points.length).toEqual(4);
const midPoints = LinearElementEditor.getEditorMidPoints(
@ -803,8 +811,11 @@ describe("Test Linear Elements", () => {
API.setSelectedElements([line]);
enterLineEditingMode(line, true);
drag(
[line.points[0][0] + line.x, line.points[0][1] + line.y],
[dragEndPositionOffset[0] + line.x, dragEndPositionOffset[1] + line.y],
point(line.points[0][0] + line.x, line.points[0][1] + line.y),
point(
dragEndPositionOffset[0] + line.x,
dragEndPositionOffset[1] + line.y,
),
);
expect(line.points).toMatchInlineSnapshot(`
[
@ -916,14 +927,18 @@ describe("Test Linear Elements", () => {
// This is the expected midpoint for line with round edge
// hence hardcoding it so if later some bug is introduced
// this will fail and we can fix it
const firstSegmentMidpoint: Point = [
55.9697848965255, 47.442326230998205,
];
const firstSegmentMidpoint = point<GlobalPoint>(
55.9697848965255,
47.442326230998205,
);
// drag line from first segment midpoint
drag(firstSegmentMidpoint, [
firstSegmentMidpoint[0] + delta,
firstSegmentMidpoint[1] + delta,
]);
drag(
firstSegmentMidpoint,
point(
firstSegmentMidpoint[0] + delta,
firstSegmentMidpoint[1] + delta,
),
);
const position = LinearElementEditor.getBoundTextElementPosition(
container,
@ -1136,7 +1151,7 @@ describe("Test Linear Elements", () => {
);
// Drag from last point
drag(points[1], [points[1][0] + 300, points[1][1]]);
drag(points[1], point(points[1][0] + 300, points[1][1]));
expect({ width: container.width, height: container.height })
.toMatchInlineSnapshot(`
@ -1335,14 +1350,14 @@ describe("Test Linear Elements", () => {
[
{
index: 0,
point: [line.points[0][0] + 10, line.points[0][1] + 10],
point: point(line.points[0][0] + 10, line.points[0][1] + 10),
},
{
index: line.points.length - 1,
point: [
point: point(
line.points[line.points.length - 1][0] - 10,
line.points[line.points.length - 1][1] - 10,
],
),
},
],
new Map() as SceneElementsMap,

View file

@ -7,7 +7,6 @@ import type {
ExcalidrawFreeDrawElement,
ExcalidrawLinearElement,
} from "../element/types";
import type { Point } from "../types";
import type { Bounds } from "../element/bounds";
import { getElementPointsCoords } from "../element/bounds";
import { Excalidraw } from "../index";
@ -16,6 +15,8 @@ import { KEYS } from "../keys";
import { isLinearElement } from "../element/typeChecks";
import { LinearElementEditor } from "../element/linearElementEditor";
import { arrayToMap } from "../utils";
import type { LocalPoint } from "../../math";
import { point } from "../../math";
ReactDOM.unmountComponentAtNode(document.getElementById("root")!);
@ -217,18 +218,13 @@ describe("generic element", () => {
});
describe.each(["line", "freedraw"] as const)("%s element", (type) => {
const points: Record<typeof type, Point[]> = {
line: [
[0, 0],
[60, -20],
[20, 40],
[-40, 0],
],
const points: Record<typeof type, LocalPoint[]> = {
line: [point(0, 0), point(60, -20), point(20, 40), point(-40, 0)],
freedraw: [
[0, 0],
[-2.474600807561444, 41.021700699972],
[3.6627956000014024, 47.84174560617245],
[40.495224145598115, 47.15909710753482],
point(0, 0),
point(-2.474600807561444, 41.021700699972),
point(3.6627956000014024, 47.84174560617245),
point(40.495224145598115, 47.15909710753482),
],
};
@ -296,11 +292,11 @@ describe("arrow element", () => {
it("resizes with a label", async () => {
const arrow = UI.createElement("arrow", {
points: [
[0, 0],
[40, 140],
[80, 60], // label's anchor
[180, 20],
[200, 120],
point(0, 0),
point(40, 140),
point(80, 60), // label's anchor
point(180, 20),
point(200, 120),
],
});
const label = await UI.editText(arrow, "Hello");
@ -694,24 +690,24 @@ describe("multiple selection", () => {
x: 60,
y: 40,
points: [
[0, 0],
[-40, 40],
[-60, 0],
[0, -40],
[40, 20],
[0, 40],
point(0, 0),
point(-40, 40),
point(-60, 0),
point(0, -40),
point(40, 20),
point(0, 40),
],
});
const freedraw = UI.createElement("freedraw", {
x: 63.56072661326618,
y: 100,
points: [
[0, 0],
[-43.56072661326618, 18.15048126846341],
[-43.56072661326618, 29.041198460587566],
[-38.115368017204105, 42.652452795512204],
[-19.964886748740696, 66.24829266003775],
[19.056612930986716, 77.1390098521619],
point(0, 0),
point(-43.56072661326618, 18.15048126846341),
point(-43.56072661326618, 29.041198460587566),
point(-38.115368017204105, 42.652452795512204),
point(-19.964886748740696, 66.24829266003775),
point(19.056612930986716, 77.1390098521619),
],
});
@ -1050,13 +1046,13 @@ describe("multiple selection", () => {
x: 60,
y: 0,
points: [
[0, 0],
[-40, 40],
[-20, 60],
[20, 20],
[40, 40],
[-20, 100],
[-60, 60],
point(0, 0),
point(-40, 40),
point(-20, 60),
point(20, 20),
point(40, 40),
point(-20, 100),
point(-60, 60),
],
});