mirror of
https://github.com/excalidraw/excalidraw.git
synced 2025-05-03 10:00:07 -04:00
chore: Unify math types, utils and functions (#8389)
Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
This commit is contained in:
parent
e3d1dee9d0
commit
f4dd23fc31
98 changed files with 4291 additions and 3661 deletions
|
@ -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,
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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(() => {
|
||||
|
|
|
@ -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)));
|
||||
});
|
||||
});
|
||||
});
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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),
|
||||
],
|
||||
});
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue