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
|
@ -210,12 +210,6 @@ import {
|
|||
isElementCompletelyInViewport,
|
||||
isElementInViewport,
|
||||
} from "../element/sizeHelpers";
|
||||
import {
|
||||
distance2d,
|
||||
getCornerRadius,
|
||||
getGridPoint,
|
||||
isPathALoop,
|
||||
} from "../math";
|
||||
import {
|
||||
calculateScrollCenter,
|
||||
getElementsWithinSelection,
|
||||
|
@ -230,7 +224,13 @@ import type {
|
|||
ScrollBars,
|
||||
} from "../scene/types";
|
||||
import { getStateForZoom } from "../scene/zoom";
|
||||
import { findShapeByKey, getBoundTextShape, getElementShape } from "../shapes";
|
||||
import {
|
||||
findShapeByKey,
|
||||
getBoundTextShape,
|
||||
getCornerRadius,
|
||||
getElementShape,
|
||||
isPathALoop,
|
||||
} from "../shapes";
|
||||
import { getSelectionBoxShape } from "../../utils/geometry/shape";
|
||||
import { isPointInShape } from "../../utils/collision";
|
||||
import type {
|
||||
|
@ -386,6 +386,7 @@ import {
|
|||
getReferenceSnapPoints,
|
||||
SnapCache,
|
||||
isGridModeEnabled,
|
||||
getGridPoint,
|
||||
} from "../snapping";
|
||||
import { actionWrapTextInContainer } from "../actions/actionBoundText";
|
||||
import BraveMeasureTextError from "./BraveMeasureTextError";
|
||||
|
@ -439,6 +440,8 @@ import {
|
|||
FlowChartNavigator,
|
||||
getLinkDirectionFromKey,
|
||||
} from "../element/flowchart";
|
||||
import type { LocalPoint, Radians } from "../../math";
|
||||
import { point, pointDistance, vector } from "../../math";
|
||||
|
||||
const AppContext = React.createContext<AppClassProperties>(null!);
|
||||
const AppPropsContext = React.createContext<AppProps>(null!);
|
||||
|
@ -4844,7 +4847,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||
this.getElementHitThreshold(),
|
||||
);
|
||||
|
||||
return isPointInShape([x, y], selectionShape);
|
||||
return isPointInShape(point(x, y), selectionShape);
|
||||
}
|
||||
|
||||
// take bound text element into consideration for hit collision as well
|
||||
|
@ -5035,7 +5038,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||
containerId: shouldBindToContainer ? container?.id : undefined,
|
||||
groupIds: container?.groupIds ?? [],
|
||||
lineHeight,
|
||||
angle: container?.angle ?? 0,
|
||||
angle: container?.angle ?? (0 as Radians),
|
||||
frameId: topLayerFrame ? topLayerFrame.id : null,
|
||||
});
|
||||
|
||||
|
@ -5203,7 +5206,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||
element,
|
||||
this.scene.getNonDeletedElementsMap(),
|
||||
this.state,
|
||||
[scenePointer.x, scenePointer.y],
|
||||
point(scenePointer.x, scenePointer.y),
|
||||
this.device.editor.isMobile,
|
||||
)
|
||||
);
|
||||
|
@ -5214,11 +5217,12 @@ class App extends React.Component<AppProps, AppState> {
|
|||
event: React.PointerEvent<HTMLCanvasElement>,
|
||||
isTouchScreen: boolean,
|
||||
) => {
|
||||
const draggedDistance = distance2d(
|
||||
this.lastPointerDownEvent!.clientX,
|
||||
this.lastPointerDownEvent!.clientY,
|
||||
this.lastPointerUpEvent!.clientX,
|
||||
this.lastPointerUpEvent!.clientY,
|
||||
const draggedDistance = pointDistance(
|
||||
point(
|
||||
this.lastPointerDownEvent!.clientX,
|
||||
this.lastPointerDownEvent!.clientY,
|
||||
),
|
||||
point(this.lastPointerUpEvent!.clientX, this.lastPointerUpEvent!.clientY),
|
||||
);
|
||||
if (
|
||||
!this.hitLinkElement ||
|
||||
|
@ -5237,7 +5241,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||
this.hitLinkElement,
|
||||
elementsMap,
|
||||
this.state,
|
||||
[lastPointerDownCoords.x, lastPointerDownCoords.y],
|
||||
point(lastPointerDownCoords.x, lastPointerDownCoords.y),
|
||||
this.device.editor.isMobile,
|
||||
);
|
||||
const lastPointerUpCoords = viewportCoordsToSceneCoords(
|
||||
|
@ -5248,7 +5252,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||
this.hitLinkElement,
|
||||
elementsMap,
|
||||
this.state,
|
||||
[lastPointerUpCoords.x, lastPointerUpCoords.y],
|
||||
point(lastPointerUpCoords.x, lastPointerUpCoords.y),
|
||||
this.device.editor.isMobile,
|
||||
);
|
||||
if (lastPointerDownHittingLinkIcon && lastPointerUpHittingLinkIcon) {
|
||||
|
@ -5497,17 +5501,18 @@ class App extends React.Component<AppProps, AppState> {
|
|||
// if we haven't yet created a temp point and we're beyond commit-zone
|
||||
// threshold, add a point
|
||||
if (
|
||||
distance2d(
|
||||
scenePointerX - rx,
|
||||
scenePointerY - ry,
|
||||
lastPoint[0],
|
||||
lastPoint[1],
|
||||
pointDistance(
|
||||
point(scenePointerX - rx, scenePointerY - ry),
|
||||
lastPoint,
|
||||
) >= LINE_CONFIRM_THRESHOLD
|
||||
) {
|
||||
mutateElement(
|
||||
multiElement,
|
||||
{
|
||||
points: [...points, [scenePointerX - rx, scenePointerY - ry]],
|
||||
points: [
|
||||
...points,
|
||||
point<LocalPoint>(scenePointerX - rx, scenePointerY - ry),
|
||||
],
|
||||
},
|
||||
false,
|
||||
);
|
||||
|
@ -5519,11 +5524,9 @@ class App extends React.Component<AppProps, AppState> {
|
|||
} else if (
|
||||
points.length > 2 &&
|
||||
lastCommittedPoint &&
|
||||
distance2d(
|
||||
scenePointerX - rx,
|
||||
scenePointerY - ry,
|
||||
lastCommittedPoint[0],
|
||||
lastCommittedPoint[1],
|
||||
pointDistance(
|
||||
point(scenePointerX - rx, scenePointerY - ry),
|
||||
lastCommittedPoint,
|
||||
) < LINE_CONFIRM_THRESHOLD
|
||||
) {
|
||||
setCursor(this.interactiveCanvas, CURSOR_TYPE.POINTER);
|
||||
|
@ -5570,10 +5573,10 @@ class App extends React.Component<AppProps, AppState> {
|
|||
this.scene.getNonDeletedElementsMap(),
|
||||
[
|
||||
...points.slice(0, -1),
|
||||
[
|
||||
point<LocalPoint>(
|
||||
lastCommittedX + dxFromLastCommitted,
|
||||
lastCommittedY + dyFromLastCommitted,
|
||||
],
|
||||
),
|
||||
],
|
||||
undefined,
|
||||
undefined,
|
||||
|
@ -5589,10 +5592,10 @@ class App extends React.Component<AppProps, AppState> {
|
|||
{
|
||||
points: [
|
||||
...points.slice(0, -1),
|
||||
[
|
||||
point<LocalPoint>(
|
||||
lastCommittedX + dxFromLastCommitted,
|
||||
lastCommittedY + dyFromLastCommitted,
|
||||
],
|
||||
),
|
||||
],
|
||||
},
|
||||
false,
|
||||
|
@ -5817,17 +5820,15 @@ class App extends React.Component<AppProps, AppState> {
|
|||
}
|
||||
};
|
||||
|
||||
const distance = distance2d(
|
||||
pointerDownState.lastCoords.x,
|
||||
pointerDownState.lastCoords.y,
|
||||
scenePointer.x,
|
||||
scenePointer.y,
|
||||
const distance = pointDistance(
|
||||
point(pointerDownState.lastCoords.x, pointerDownState.lastCoords.y),
|
||||
point(scenePointer.x, scenePointer.y),
|
||||
);
|
||||
const threshold = this.getElementHitThreshold();
|
||||
const point = { ...pointerDownState.lastCoords };
|
||||
const p = { ...pointerDownState.lastCoords };
|
||||
let samplingInterval = 0;
|
||||
while (samplingInterval <= distance) {
|
||||
const hitElements = this.getElementsAtPosition(point.x, point.y);
|
||||
const hitElements = this.getElementsAtPosition(p.x, p.y);
|
||||
processElements(hitElements);
|
||||
|
||||
// Exit since we reached current point
|
||||
|
@ -5839,12 +5840,10 @@ class App extends React.Component<AppProps, AppState> {
|
|||
samplingInterval = Math.min(samplingInterval + threshold, distance);
|
||||
|
||||
const distanceRatio = samplingInterval / distance;
|
||||
const nextX =
|
||||
(1 - distanceRatio) * point.x + distanceRatio * scenePointer.x;
|
||||
const nextY =
|
||||
(1 - distanceRatio) * point.y + distanceRatio * scenePointer.y;
|
||||
point.x = nextX;
|
||||
point.y = nextY;
|
||||
const nextX = (1 - distanceRatio) * p.x + distanceRatio * scenePointer.x;
|
||||
const nextY = (1 - distanceRatio) * p.y + distanceRatio * scenePointer.y;
|
||||
p.x = nextX;
|
||||
p.y = nextY;
|
||||
}
|
||||
|
||||
pointerDownState.lastCoords.x = scenePointer.x;
|
||||
|
@ -6325,7 +6324,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||
this.hitLinkElement,
|
||||
this.scene.getNonDeletedElementsMap(),
|
||||
this.state,
|
||||
[scenePointer.x, scenePointer.y],
|
||||
point(scenePointer.x, scenePointer.y),
|
||||
)
|
||||
) {
|
||||
this.handleEmbeddableCenterClick(this.hitLinkElement);
|
||||
|
@ -7008,7 +7007,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||
simulatePressure,
|
||||
locked: false,
|
||||
frameId: topLayerFrame ? topLayerFrame.id : null,
|
||||
points: [[0, 0]],
|
||||
points: [point<LocalPoint>(0, 0)],
|
||||
pressures: simulatePressure ? [] : [event.pressure],
|
||||
});
|
||||
|
||||
|
@ -7216,11 +7215,9 @@ class App extends React.Component<AppProps, AppState> {
|
|||
if (
|
||||
multiElement.points.length > 1 &&
|
||||
lastCommittedPoint &&
|
||||
distance2d(
|
||||
pointerDownState.origin.x - rx,
|
||||
pointerDownState.origin.y - ry,
|
||||
lastCommittedPoint[0],
|
||||
lastCommittedPoint[1],
|
||||
pointDistance(
|
||||
point(pointerDownState.origin.x - rx, pointerDownState.origin.y - ry),
|
||||
lastCommittedPoint,
|
||||
) < LINE_CONFIRM_THRESHOLD
|
||||
) {
|
||||
this.actionManager.executeAction(actionFinalize);
|
||||
|
@ -7321,7 +7318,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||
};
|
||||
});
|
||||
mutateElement(element, {
|
||||
points: [...element.points, [0, 0]],
|
||||
points: [...element.points, point<LocalPoint>(0, 0)],
|
||||
});
|
||||
const boundElement = getHoveredElementForBinding(
|
||||
pointerDownState.origin,
|
||||
|
@ -7573,11 +7570,9 @@ class App extends React.Component<AppProps, AppState> {
|
|||
this.state.activeTool.type === "line")
|
||||
) {
|
||||
if (
|
||||
distance2d(
|
||||
pointerCoords.x,
|
||||
pointerCoords.y,
|
||||
pointerDownState.origin.x,
|
||||
pointerDownState.origin.y,
|
||||
pointDistance(
|
||||
point(pointerCoords.x, pointerCoords.y),
|
||||
point(pointerDownState.origin.x, pointerDownState.origin.y),
|
||||
) < DRAGGING_THRESHOLD
|
||||
) {
|
||||
return;
|
||||
|
@ -7926,7 +7921,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||
mutateElement(
|
||||
newElement,
|
||||
{
|
||||
points: [...points, [dx, dy]],
|
||||
points: [...points, point<LocalPoint>(dx, dy)],
|
||||
pressures,
|
||||
},
|
||||
false,
|
||||
|
@ -7955,7 +7950,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||
mutateElement(
|
||||
newElement,
|
||||
{
|
||||
points: [...points, [dx, dy]],
|
||||
points: [...points, point<LocalPoint>(dx, dy)],
|
||||
},
|
||||
false,
|
||||
);
|
||||
|
@ -7963,8 +7958,8 @@ class App extends React.Component<AppProps, AppState> {
|
|||
mutateElbowArrow(
|
||||
newElement,
|
||||
elementsMap,
|
||||
[...points.slice(0, -1), [dx, dy]],
|
||||
[0, 0],
|
||||
[...points.slice(0, -1), point<LocalPoint>(dx, dy)],
|
||||
vector(0, 0),
|
||||
undefined,
|
||||
{
|
||||
isDragging: true,
|
||||
|
@ -7975,7 +7970,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||
mutateElement(
|
||||
newElement,
|
||||
{
|
||||
points: [...points.slice(0, -1), [dx, dy]],
|
||||
points: [...points.slice(0, -1), point<LocalPoint>(dx, dy)],
|
||||
},
|
||||
false,
|
||||
);
|
||||
|
@ -8284,9 +8279,9 @@ class App extends React.Component<AppProps, AppState> {
|
|||
: [...newElement.pressures, childEvent.pressure];
|
||||
|
||||
mutateElement(newElement, {
|
||||
points: [...points, [dx, dy]],
|
||||
points: [...points, point<LocalPoint>(dx, dy)],
|
||||
pressures,
|
||||
lastCommittedPoint: [dx, dy],
|
||||
lastCommittedPoint: point<LocalPoint>(dx, dy),
|
||||
});
|
||||
|
||||
this.actionManager.executeAction(actionFinalize);
|
||||
|
@ -8333,7 +8328,10 @@ class App extends React.Component<AppProps, AppState> {
|
|||
mutateElement(newElement, {
|
||||
points: [
|
||||
...newElement.points,
|
||||
[pointerCoords.x - newElement.x, pointerCoords.y - newElement.y],
|
||||
point<LocalPoint>(
|
||||
pointerCoords.x - newElement.x,
|
||||
pointerCoords.y - newElement.y,
|
||||
),
|
||||
],
|
||||
});
|
||||
this.setState({
|
||||
|
@ -8643,11 +8641,9 @@ class App extends React.Component<AppProps, AppState> {
|
|||
if (isEraserActive(this.state) && pointerStart && pointerEnd) {
|
||||
this.eraserTrail.endPath();
|
||||
|
||||
const draggedDistance = distance2d(
|
||||
pointerStart.clientX,
|
||||
pointerStart.clientY,
|
||||
pointerEnd.clientX,
|
||||
pointerEnd.clientY,
|
||||
const draggedDistance = pointDistance(
|
||||
point(pointerStart.clientX, pointerStart.clientY),
|
||||
point(pointerEnd.clientX, pointerEnd.clientY),
|
||||
);
|
||||
|
||||
if (draggedDistance === 0) {
|
||||
|
|
|
@ -2,13 +2,14 @@ import { mutateElement } from "../../element/mutateElement";
|
|||
import { getBoundTextElement } from "../../element/textElement";
|
||||
import { isArrowElement, isElbowArrow } from "../../element/typeChecks";
|
||||
import type { ExcalidrawElement } from "../../element/types";
|
||||
import { degreeToRadian, radianToDegree } from "../../math";
|
||||
import { angleIcon } from "../icons";
|
||||
import DragInput from "./DragInput";
|
||||
import type { DragInputCallbackType } from "./DragInput";
|
||||
import { getStepSizedValue, isPropertyEditable, updateBindings } from "./utils";
|
||||
import type Scene from "../../scene/Scene";
|
||||
import type { AppState } from "../../types";
|
||||
import type { Degrees } from "../../../math";
|
||||
import { degreesToRadians, radiansToDegrees } from "../../../math";
|
||||
|
||||
interface AngleProps {
|
||||
element: ExcalidrawElement;
|
||||
|
@ -36,7 +37,7 @@ const handleDegreeChange: DragInputCallbackType<AngleProps["property"]> = ({
|
|||
}
|
||||
|
||||
if (nextValue !== undefined) {
|
||||
const nextAngle = degreeToRadian(nextValue);
|
||||
const nextAngle = degreesToRadians(nextValue as Degrees);
|
||||
mutateElement(latestElement, {
|
||||
angle: nextAngle,
|
||||
});
|
||||
|
@ -51,7 +52,7 @@ const handleDegreeChange: DragInputCallbackType<AngleProps["property"]> = ({
|
|||
}
|
||||
|
||||
const originalAngleInDegrees =
|
||||
Math.round(radianToDegree(origElement.angle) * 100) / 100;
|
||||
Math.round(radiansToDegrees(origElement.angle) * 100) / 100;
|
||||
const changeInDegrees = Math.round(accumulatedChange);
|
||||
let nextAngleInDegrees = (originalAngleInDegrees + changeInDegrees) % 360;
|
||||
if (shouldChangeByStepSize) {
|
||||
|
@ -61,7 +62,7 @@ const handleDegreeChange: DragInputCallbackType<AngleProps["property"]> = ({
|
|||
nextAngleInDegrees =
|
||||
nextAngleInDegrees < 0 ? nextAngleInDegrees + 360 : nextAngleInDegrees;
|
||||
|
||||
const nextAngle = degreeToRadian(nextAngleInDegrees);
|
||||
const nextAngle = degreesToRadians(nextAngleInDegrees as Degrees);
|
||||
|
||||
mutateElement(latestElement, {
|
||||
angle: nextAngle,
|
||||
|
@ -80,7 +81,7 @@ const Angle = ({ element, scene, appState, property }: AngleProps) => {
|
|||
<DragInput
|
||||
label="A"
|
||||
icon={angleIcon}
|
||||
value={Math.round((radianToDegree(element.angle) % 360) * 100) / 100}
|
||||
value={Math.round((radiansToDegrees(element.angle) % 360) * 100) / 100}
|
||||
elements={[element]}
|
||||
dragInputCallback={handleDegreeChange}
|
||||
editable={isPropertyEditable(element, "angle")}
|
||||
|
|
|
@ -3,13 +3,14 @@ import { getBoundTextElement } from "../../element/textElement";
|
|||
import { isArrowElement } from "../../element/typeChecks";
|
||||
import type { ExcalidrawElement } from "../../element/types";
|
||||
import { isInGroup } from "../../groups";
|
||||
import { degreeToRadian, radianToDegree } from "../../math";
|
||||
import type Scene from "../../scene/Scene";
|
||||
import { angleIcon } from "../icons";
|
||||
import DragInput from "./DragInput";
|
||||
import type { DragInputCallbackType } from "./DragInput";
|
||||
import { getStepSizedValue, isPropertyEditable } from "./utils";
|
||||
import type { AppState } from "../../types";
|
||||
import type { Degrees } from "../../../math";
|
||||
import { degreesToRadians, radiansToDegrees } from "../../../math";
|
||||
|
||||
interface MultiAngleProps {
|
||||
elements: readonly ExcalidrawElement[];
|
||||
|
@ -39,7 +40,7 @@ const handleDegreeChange: DragInputCallbackType<
|
|||
);
|
||||
|
||||
if (nextValue !== undefined) {
|
||||
const nextAngle = degreeToRadian(nextValue);
|
||||
const nextAngle = degreesToRadians(nextValue as Degrees);
|
||||
|
||||
for (const element of editableLatestIndividualElements) {
|
||||
if (!element) {
|
||||
|
@ -71,7 +72,7 @@ const handleDegreeChange: DragInputCallbackType<
|
|||
}
|
||||
const originalElement = editableOriginalIndividualElements[i];
|
||||
const originalAngleInDegrees =
|
||||
Math.round(radianToDegree(originalElement.angle) * 100) / 100;
|
||||
Math.round(radiansToDegrees(originalElement.angle) * 100) / 100;
|
||||
const changeInDegrees = Math.round(accumulatedChange);
|
||||
let nextAngleInDegrees = (originalAngleInDegrees + changeInDegrees) % 360;
|
||||
if (shouldChangeByStepSize) {
|
||||
|
@ -81,7 +82,7 @@ const handleDegreeChange: DragInputCallbackType<
|
|||
nextAngleInDegrees =
|
||||
nextAngleInDegrees < 0 ? nextAngleInDegrees + 360 : nextAngleInDegrees;
|
||||
|
||||
const nextAngle = degreeToRadian(nextAngleInDegrees);
|
||||
const nextAngle = degreesToRadians(nextAngleInDegrees as Degrees);
|
||||
|
||||
mutateElement(
|
||||
latestElement,
|
||||
|
@ -109,7 +110,7 @@ const MultiAngle = ({
|
|||
(el) => !isInGroup(el) && isPropertyEditable(el, "angle"),
|
||||
);
|
||||
const angles = editableLatestIndividualElements.map(
|
||||
(el) => Math.round((radianToDegree(el.angle) % 360) * 100) / 100,
|
||||
(el) => Math.round((radiansToDegrees(el.angle) % 360) * 100) / 100,
|
||||
);
|
||||
const value = new Set(angles).size === 1 ? angles[0] : "Mixed";
|
||||
|
||||
|
|
|
@ -13,13 +13,14 @@ import type {
|
|||
NonDeletedSceneElementsMap,
|
||||
} from "../../element/types";
|
||||
import type Scene from "../../scene/Scene";
|
||||
import type { AppState, Point } from "../../types";
|
||||
import type { AppState } from "../../types";
|
||||
import DragInput from "./DragInput";
|
||||
import type { DragInputCallbackType } from "./DragInput";
|
||||
import { getAtomicUnits, getStepSizedValue, isPropertyEditable } from "./utils";
|
||||
import { getElementsInAtomicUnit, resizeElement } from "./utils";
|
||||
import type { AtomicUnit } from "./utils";
|
||||
import { MIN_WIDTH_OR_HEIGHT } from "../../constants";
|
||||
import { point, type GlobalPoint } from "../../../math";
|
||||
|
||||
interface MultiDimensionProps {
|
||||
property: "width" | "height";
|
||||
|
@ -104,7 +105,7 @@ const resizeGroup = (
|
|||
nextHeight: number,
|
||||
initialHeight: number,
|
||||
aspectRatio: number,
|
||||
anchor: Point,
|
||||
anchor: GlobalPoint,
|
||||
property: MultiDimensionProps["property"],
|
||||
latestElements: ExcalidrawElement[],
|
||||
originalElements: ExcalidrawElement[],
|
||||
|
@ -181,7 +182,7 @@ const handleDimensionChange: DragInputCallbackType<
|
|||
nextHeight,
|
||||
initialHeight,
|
||||
aspectRatio,
|
||||
[x1, y1],
|
||||
point(x1, y1),
|
||||
property,
|
||||
latestElements,
|
||||
originalElements,
|
||||
|
@ -286,7 +287,7 @@ const handleDimensionChange: DragInputCallbackType<
|
|||
nextHeight,
|
||||
initialHeight,
|
||||
aspectRatio,
|
||||
[x1, y1],
|
||||
point(x1, y1),
|
||||
property,
|
||||
latestElements,
|
||||
originalElements,
|
||||
|
|
|
@ -4,7 +4,6 @@ import type {
|
|||
NonDeletedExcalidrawElement,
|
||||
NonDeletedSceneElementsMap,
|
||||
} from "../../element/types";
|
||||
import { rotate } from "../../math";
|
||||
import type Scene from "../../scene/Scene";
|
||||
import StatsDragInput from "./DragInput";
|
||||
import type { DragInputCallbackType } from "./DragInput";
|
||||
|
@ -14,6 +13,7 @@ import { useMemo } from "react";
|
|||
import { getElementsInAtomicUnit, moveElement } from "./utils";
|
||||
import type { AtomicUnit } from "./utils";
|
||||
import type { AppState } from "../../types";
|
||||
import { point, pointRotateRads } from "../../../math";
|
||||
|
||||
interface MultiPositionProps {
|
||||
property: "x" | "y";
|
||||
|
@ -43,11 +43,9 @@ const moveElements = (
|
|||
origElement.x + origElement.width / 2,
|
||||
origElement.y + origElement.height / 2,
|
||||
];
|
||||
const [topLeftX, topLeftY] = rotate(
|
||||
origElement.x,
|
||||
origElement.y,
|
||||
cx,
|
||||
cy,
|
||||
const [topLeftX, topLeftY] = pointRotateRads(
|
||||
point(origElement.x, origElement.y),
|
||||
point(cx, cy),
|
||||
origElement.angle,
|
||||
);
|
||||
|
||||
|
@ -98,11 +96,9 @@ const moveGroupTo = (
|
|||
latestElement.y + latestElement.height / 2,
|
||||
];
|
||||
|
||||
const [topLeftX, topLeftY] = rotate(
|
||||
latestElement.x,
|
||||
latestElement.y,
|
||||
cx,
|
||||
cy,
|
||||
const [topLeftX, topLeftY] = pointRotateRads(
|
||||
point(latestElement.x, latestElement.y),
|
||||
point(cx, cy),
|
||||
latestElement.angle,
|
||||
);
|
||||
|
||||
|
@ -174,11 +170,9 @@ const handlePositionChange: DragInputCallbackType<
|
|||
origElement.x + origElement.width / 2,
|
||||
origElement.y + origElement.height / 2,
|
||||
];
|
||||
const [topLeftX, topLeftY] = rotate(
|
||||
origElement.x,
|
||||
origElement.y,
|
||||
cx,
|
||||
cy,
|
||||
const [topLeftX, topLeftY] = pointRotateRads(
|
||||
point(origElement.x, origElement.y),
|
||||
point(cx, cy),
|
||||
origElement.angle,
|
||||
);
|
||||
|
||||
|
@ -246,7 +240,11 @@ const MultiPosition = ({
|
|||
const [el] = elementsInUnit;
|
||||
const [cx, cy] = [el.x + el.width / 2, el.y + el.height / 2];
|
||||
|
||||
const [topLeftX, topLeftY] = rotate(el.x, el.y, cx, cy, el.angle);
|
||||
const [topLeftX, topLeftY] = pointRotateRads(
|
||||
point(el.x, el.y),
|
||||
point(cx, cy),
|
||||
el.angle,
|
||||
);
|
||||
|
||||
return Math.round((property === "x" ? topLeftX : topLeftY) * 100) / 100;
|
||||
}),
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import type { ElementsMap, ExcalidrawElement } from "../../element/types";
|
||||
import { rotate } from "../../math";
|
||||
import StatsDragInput from "./DragInput";
|
||||
import type { DragInputCallbackType } from "./DragInput";
|
||||
import { getStepSizedValue, moveElement } from "./utils";
|
||||
import type Scene from "../../scene/Scene";
|
||||
import type { AppState } from "../../types";
|
||||
import { point, pointRotateRads } from "../../../math";
|
||||
|
||||
interface PositionProps {
|
||||
property: "x" | "y";
|
||||
|
@ -32,11 +32,9 @@ const handlePositionChange: DragInputCallbackType<"x" | "y"> = ({
|
|||
origElement.x + origElement.width / 2,
|
||||
origElement.y + origElement.height / 2,
|
||||
];
|
||||
const [topLeftX, topLeftY] = rotate(
|
||||
origElement.x,
|
||||
origElement.y,
|
||||
cx,
|
||||
cy,
|
||||
const [topLeftX, topLeftY] = pointRotateRads(
|
||||
point(origElement.x, origElement.y),
|
||||
point(cx, cy),
|
||||
origElement.angle,
|
||||
);
|
||||
|
||||
|
@ -94,11 +92,9 @@ const Position = ({
|
|||
scene,
|
||||
appState,
|
||||
}: PositionProps) => {
|
||||
const [topLeftX, topLeftY] = rotate(
|
||||
element.x,
|
||||
element.y,
|
||||
element.x + element.width / 2,
|
||||
element.y + element.height / 2,
|
||||
const [topLeftX, topLeftY] = pointRotateRads(
|
||||
point(element.x, element.y),
|
||||
point(element.x + element.width / 2, element.y + element.height / 2),
|
||||
element.angle,
|
||||
);
|
||||
const value =
|
||||
|
|
|
@ -19,12 +19,13 @@ import type {
|
|||
ExcalidrawLinearElement,
|
||||
ExcalidrawTextElement,
|
||||
} from "../../element/types";
|
||||
import { degreeToRadian, rotate } from "../../math";
|
||||
import { getTextEditor, updateTextEditor } from "../../tests/queries/dom";
|
||||
import { getCommonBounds, isTextElement } from "../../element";
|
||||
import { API } from "../../tests/helpers/api";
|
||||
import { actionGroup } from "../../actions";
|
||||
import { isInGroup } from "../../groups";
|
||||
import type { Degrees } from "../../../math";
|
||||
import { degreesToRadians, point, pointRotateRads } from "../../../math";
|
||||
|
||||
const { h } = window;
|
||||
const mouse = new Pointer("mouse");
|
||||
|
@ -46,7 +47,9 @@ const testInputProperty = (
|
|||
expect(input.value).toBe(initialValue.toString());
|
||||
UI.updateInput(input, String(nextValue));
|
||||
if (property === "angle") {
|
||||
expect(element[property]).toBe(degreeToRadian(Number(nextValue)));
|
||||
expect(element[property]).toBe(
|
||||
degreesToRadians(Number(nextValue) as Degrees),
|
||||
);
|
||||
} else if (property === "fontSize" && isTextElement(element)) {
|
||||
expect(element[property]).toBe(Number(nextValue));
|
||||
} else if (property !== "fontSize") {
|
||||
|
@ -260,11 +263,9 @@ describe("stats for a generic element", () => {
|
|||
rectangle.x + rectangle.width / 2,
|
||||
rectangle.y + rectangle.height / 2,
|
||||
];
|
||||
const [topLeftX, topLeftY] = rotate(
|
||||
rectangle.x,
|
||||
rectangle.y,
|
||||
cx,
|
||||
cy,
|
||||
const [topLeftX, topLeftY] = pointRotateRads(
|
||||
point(rectangle.x, rectangle.y),
|
||||
point(cx, cy),
|
||||
rectangle.angle,
|
||||
);
|
||||
|
||||
|
@ -281,11 +282,9 @@ describe("stats for a generic element", () => {
|
|||
|
||||
testInputProperty(rectangle, "angle", "A", 0, 45);
|
||||
|
||||
let [newTopLeftX, newTopLeftY] = rotate(
|
||||
rectangle.x,
|
||||
rectangle.y,
|
||||
cx,
|
||||
cy,
|
||||
let [newTopLeftX, newTopLeftY] = pointRotateRads(
|
||||
point(rectangle.x, rectangle.y),
|
||||
point(cx, cy),
|
||||
rectangle.angle,
|
||||
);
|
||||
|
||||
|
@ -294,11 +293,9 @@ describe("stats for a generic element", () => {
|
|||
|
||||
testInputProperty(rectangle, "angle", "A", 45, 66);
|
||||
|
||||
[newTopLeftX, newTopLeftY] = rotate(
|
||||
rectangle.x,
|
||||
rectangle.y,
|
||||
cx,
|
||||
cy,
|
||||
[newTopLeftX, newTopLeftY] = pointRotateRads(
|
||||
point(rectangle.x, rectangle.y),
|
||||
point(cx, cy),
|
||||
rectangle.angle,
|
||||
);
|
||||
expect(newTopLeftX.toString()).not.toEqual(xInput.value);
|
||||
|
@ -313,11 +310,9 @@ describe("stats for a generic element", () => {
|
|||
rectangle.x + rectangle.width / 2,
|
||||
rectangle.y + rectangle.height / 2,
|
||||
];
|
||||
const [topLeftX, topLeftY] = rotate(
|
||||
rectangle.x,
|
||||
rectangle.y,
|
||||
cx,
|
||||
cy,
|
||||
const [topLeftX, topLeftY] = pointRotateRads(
|
||||
point(rectangle.x, rectangle.y),
|
||||
point(cx, cy),
|
||||
rectangle.angle,
|
||||
);
|
||||
testInputProperty(rectangle, "width", "W", rectangle.width, 400);
|
||||
|
@ -325,11 +320,9 @@ describe("stats for a generic element", () => {
|
|||
rectangle.x + rectangle.width / 2,
|
||||
rectangle.y + rectangle.height / 2,
|
||||
];
|
||||
let [currentTopLeftX, currentTopLeftY] = rotate(
|
||||
rectangle.x,
|
||||
rectangle.y,
|
||||
cx,
|
||||
cy,
|
||||
let [currentTopLeftX, currentTopLeftY] = pointRotateRads(
|
||||
point(rectangle.x, rectangle.y),
|
||||
point(cx, cy),
|
||||
rectangle.angle,
|
||||
);
|
||||
expect(currentTopLeftX).toBeCloseTo(topLeftX, 4);
|
||||
|
@ -340,11 +333,9 @@ describe("stats for a generic element", () => {
|
|||
rectangle.x + rectangle.width / 2,
|
||||
rectangle.y + rectangle.height / 2,
|
||||
];
|
||||
[currentTopLeftX, currentTopLeftY] = rotate(
|
||||
rectangle.x,
|
||||
rectangle.y,
|
||||
cx,
|
||||
cy,
|
||||
[currentTopLeftX, currentTopLeftY] = pointRotateRads(
|
||||
point(rectangle.x, rectangle.y),
|
||||
point(cx, cy),
|
||||
rectangle.angle,
|
||||
);
|
||||
|
||||
|
@ -642,7 +633,7 @@ describe("stats for multiple elements", () => {
|
|||
|
||||
UI.updateInput(angle, "40");
|
||||
|
||||
const angleInRadian = degreeToRadian(40);
|
||||
const angleInRadian = degreesToRadians(40 as Degrees);
|
||||
expect(rectangle?.angle).toBeCloseTo(angleInRadian, 4);
|
||||
expect(text?.angle).toBeCloseTo(angleInRadian, 4);
|
||||
expect(frame.angle).toBe(0);
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import type { Radians } from "../../../math";
|
||||
import { point, pointRotateRads } from "../../../math";
|
||||
import {
|
||||
bindOrUnbindLinearElements,
|
||||
updateBoundElements,
|
||||
|
@ -30,7 +32,6 @@ import {
|
|||
getElementsInGroup,
|
||||
isInGroup,
|
||||
} from "../../groups";
|
||||
import { rotate } from "../../math";
|
||||
import type Scene from "../../scene/Scene";
|
||||
import type { AppState } from "../../types";
|
||||
import { getFontString } from "../../utils";
|
||||
|
@ -229,23 +230,19 @@ export const moveElement = (
|
|||
originalElement.x + originalElement.width / 2,
|
||||
originalElement.y + originalElement.height / 2,
|
||||
];
|
||||
const [topLeftX, topLeftY] = rotate(
|
||||
originalElement.x,
|
||||
originalElement.y,
|
||||
cx,
|
||||
cy,
|
||||
const [topLeftX, topLeftY] = pointRotateRads(
|
||||
point(originalElement.x, originalElement.y),
|
||||
point(cx, cy),
|
||||
originalElement.angle,
|
||||
);
|
||||
|
||||
const changeInX = newTopLeftX - topLeftX;
|
||||
const changeInY = newTopLeftY - topLeftY;
|
||||
|
||||
const [x, y] = rotate(
|
||||
newTopLeftX,
|
||||
newTopLeftY,
|
||||
cx + changeInX,
|
||||
cy + changeInY,
|
||||
-originalElement.angle,
|
||||
const [x, y] = pointRotateRads(
|
||||
point(newTopLeftX, newTopLeftY),
|
||||
point(cx + changeInX, cy + changeInY),
|
||||
-originalElement.angle as Radians,
|
||||
);
|
||||
|
||||
mutateElement(
|
||||
|
|
|
@ -25,11 +25,11 @@ import type { BinaryFiles } from "../../types";
|
|||
import { ArrowRightIcon } from "../icons";
|
||||
|
||||
import "./TTDDialog.scss";
|
||||
import { isFiniteNumber } from "../../utils";
|
||||
import { atom, useAtom } from "jotai";
|
||||
import { trackEvent } from "../../analytics";
|
||||
import { InlineIcon } from "../InlineIcon";
|
||||
import { TTDDialogSubmitShortcut } from "./TTDDialogSubmitShortcut";
|
||||
import { isFiniteNumber } from "../../../math";
|
||||
|
||||
const MIN_PROMPT_LENGTH = 3;
|
||||
const MAX_PROMPT_LENGTH = 1000;
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import type { AppState, ExcalidrawProps, Point, UIAppState } from "../../types";
|
||||
import type { AppState, ExcalidrawProps, UIAppState } from "../../types";
|
||||
import {
|
||||
sceneCoordsToViewportCoords,
|
||||
viewportCoordsToSceneCoords,
|
||||
|
@ -36,6 +36,7 @@ import { trackEvent } from "../../analytics";
|
|||
import { useAppProps, useExcalidrawAppState } from "../App";
|
||||
import { isEmbeddableElement } from "../../element/typeChecks";
|
||||
import { getLinkHandleFromCoords } from "./helpers";
|
||||
import { point, type GlobalPoint } from "../../../math";
|
||||
|
||||
const CONTAINER_WIDTH = 320;
|
||||
const SPACE_BOTTOM = 85;
|
||||
|
@ -176,10 +177,12 @@ export const Hyperlink = ({
|
|||
if (timeoutId) {
|
||||
clearTimeout(timeoutId);
|
||||
}
|
||||
const shouldHide = shouldHideLinkPopup(element, elementsMap, appState, [
|
||||
event.clientX,
|
||||
event.clientY,
|
||||
]) as boolean;
|
||||
const shouldHide = shouldHideLinkPopup(
|
||||
element,
|
||||
elementsMap,
|
||||
appState,
|
||||
point(event.clientX, event.clientY),
|
||||
) as boolean;
|
||||
if (shouldHide) {
|
||||
timeoutId = window.setTimeout(() => {
|
||||
setAppState({ showHyperlinkPopup: false });
|
||||
|
@ -416,7 +419,7 @@ const shouldHideLinkPopup = (
|
|||
element: NonDeletedExcalidrawElement,
|
||||
elementsMap: ElementsMap,
|
||||
appState: AppState,
|
||||
[clientX, clientY]: Point,
|
||||
[clientX, clientY]: GlobalPoint,
|
||||
): Boolean => {
|
||||
const { x: sceneX, y: sceneY } = viewportCoordsToSceneCoords(
|
||||
{ clientX, clientY },
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import type { GlobalPoint, Radians } from "../../../math";
|
||||
import { point, pointRotateRads } from "../../../math";
|
||||
import { MIME_TYPES } from "../../constants";
|
||||
import type { Bounds } from "../../element/bounds";
|
||||
import { getElementAbsoluteCoords } from "../../element/bounds";
|
||||
|
@ -6,9 +8,8 @@ import type {
|
|||
ElementsMap,
|
||||
NonDeletedExcalidrawElement,
|
||||
} from "../../element/types";
|
||||
import { rotate } from "../../math";
|
||||
import { DEFAULT_LINK_SIZE } from "../../renderer/renderElement";
|
||||
import type { AppState, Point, UIAppState } from "../../types";
|
||||
import type { AppState, UIAppState } from "../../types";
|
||||
|
||||
export const EXTERNAL_LINK_IMG = document.createElement("img");
|
||||
EXTERNAL_LINK_IMG.src = `data:${MIME_TYPES.svg}, ${encodeURIComponent(
|
||||
|
@ -17,7 +18,7 @@ EXTERNAL_LINK_IMG.src = `data:${MIME_TYPES.svg}, ${encodeURIComponent(
|
|||
|
||||
export const getLinkHandleFromCoords = (
|
||||
[x1, y1, x2, y2]: Bounds,
|
||||
angle: number,
|
||||
angle: Radians,
|
||||
appState: Pick<UIAppState, "zoom">,
|
||||
): Bounds => {
|
||||
const size = DEFAULT_LINK_SIZE;
|
||||
|
@ -33,11 +34,9 @@ export const getLinkHandleFromCoords = (
|
|||
const x = x2 + dashedLineMargin - centeringOffset;
|
||||
const y = y1 - dashedLineMargin - linkMarginY + centeringOffset;
|
||||
|
||||
const [rotatedX, rotatedY] = rotate(
|
||||
x + linkWidth / 2,
|
||||
y + linkHeight / 2,
|
||||
centerX,
|
||||
centerY,
|
||||
const [rotatedX, rotatedY] = pointRotateRads(
|
||||
point(x + linkWidth / 2, y + linkHeight / 2),
|
||||
point(centerX, centerY),
|
||||
angle,
|
||||
);
|
||||
return [
|
||||
|
@ -52,7 +51,7 @@ export const isPointHittingLinkIcon = (
|
|||
element: NonDeletedExcalidrawElement,
|
||||
elementsMap: ElementsMap,
|
||||
appState: AppState,
|
||||
[x, y]: Point,
|
||||
[x, y]: GlobalPoint,
|
||||
) => {
|
||||
const threshold = 4 / appState.zoom.value;
|
||||
const [x1, y1, x2, y2] = getElementAbsoluteCoords(element, elementsMap);
|
||||
|
@ -73,7 +72,7 @@ export const isPointHittingLink = (
|
|||
element: NonDeletedExcalidrawElement,
|
||||
elementsMap: ElementsMap,
|
||||
appState: AppState,
|
||||
[x, y]: Point,
|
||||
[x, y]: GlobalPoint,
|
||||
isMobile: boolean,
|
||||
) => {
|
||||
if (!element.link || appState.selectedElementIds[element.id]) {
|
||||
|
@ -86,5 +85,5 @@ export const isPointHittingLink = (
|
|||
) {
|
||||
return true;
|
||||
}
|
||||
return isPointHittingLinkIcon(element, elementsMap, appState, [x, y]);
|
||||
return isPointHittingLinkIcon(element, elementsMap, appState, point(x, y));
|
||||
};
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue