Merge branch 'master' into cleanipp
This commit is contained in:
commit
e4a0254c47
@ -3587,9 +3587,6 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
transformElements(
|
transformElements(
|
||||||
pointerDownState,
|
pointerDownState,
|
||||||
transformHandleType,
|
transformHandleType,
|
||||||
(newTransformHandle) => {
|
|
||||||
pointerDownState.resize.handleType = newTransformHandle;
|
|
||||||
},
|
|
||||||
selectedElements,
|
selectedElements,
|
||||||
pointerDownState.resize.arrowDirection,
|
pointerDownState.resize.arrowDirection,
|
||||||
getRotateWithDiscreteAngleKey(event),
|
getRotateWithDiscreteAngleKey(event),
|
||||||
|
@ -90,3 +90,4 @@ export const TAP_TWICE_TIMEOUT = 300;
|
|||||||
export const TITLE_TIMEOUT = 10000;
|
export const TITLE_TIMEOUT = 10000;
|
||||||
export const TOAST_TIMEOUT = 5000;
|
export const TOAST_TIMEOUT = 5000;
|
||||||
export const TOUCH_CTX_MENU_TIMEOUT = 500;
|
export const TOUCH_CTX_MENU_TIMEOUT = 500;
|
||||||
|
export const VERSION_TIMEOUT = 15000;
|
||||||
|
@ -34,7 +34,6 @@ export {
|
|||||||
export {
|
export {
|
||||||
resizeTest,
|
resizeTest,
|
||||||
getCursorForResizingElement,
|
getCursorForResizingElement,
|
||||||
normalizeTransformHandleType,
|
|
||||||
getElementWithTransformHandleType,
|
getElementWithTransformHandleType,
|
||||||
getTransformHandleTypeFromCoords,
|
getTransformHandleTypeFromCoords,
|
||||||
} from "./resizeTest";
|
} from "./resizeTest";
|
||||||
|
@ -4,7 +4,6 @@ import { rescalePoints } from "../points";
|
|||||||
import {
|
import {
|
||||||
rotate,
|
rotate,
|
||||||
adjustXYWithRotation,
|
adjustXYWithRotation,
|
||||||
getFlipAdjustment,
|
|
||||||
centerPoint,
|
centerPoint,
|
||||||
rotatePoint,
|
rotatePoint,
|
||||||
} from "../math";
|
} from "../math";
|
||||||
@ -13,21 +12,16 @@ import {
|
|||||||
ExcalidrawTextElement,
|
ExcalidrawTextElement,
|
||||||
NonDeletedExcalidrawElement,
|
NonDeletedExcalidrawElement,
|
||||||
NonDeleted,
|
NonDeleted,
|
||||||
ExcalidrawGenericElement,
|
|
||||||
ExcalidrawElement,
|
|
||||||
} from "./types";
|
} from "./types";
|
||||||
import {
|
import {
|
||||||
getElementAbsoluteCoords,
|
getElementAbsoluteCoords,
|
||||||
getCommonBounds,
|
getCommonBounds,
|
||||||
getResizedElementAbsoluteCoords,
|
getResizedElementAbsoluteCoords,
|
||||||
} from "./bounds";
|
} from "./bounds";
|
||||||
import { isGenericElement, isLinearElement, isTextElement } from "./typeChecks";
|
import { isLinearElement, isTextElement } from "./typeChecks";
|
||||||
import { mutateElement } from "./mutateElement";
|
import { mutateElement } from "./mutateElement";
|
||||||
import { getPerfectElementSize } from "./sizeHelpers";
|
import { getPerfectElementSize } from "./sizeHelpers";
|
||||||
import {
|
import { getCursorForResizingElement } from "./resizeTest";
|
||||||
getCursorForResizingElement,
|
|
||||||
normalizeTransformHandleType,
|
|
||||||
} from "./resizeTest";
|
|
||||||
import { measureText, getFontString } from "../utils";
|
import { measureText, getFontString } from "../utils";
|
||||||
import { updateBoundElements } from "./binding";
|
import { updateBoundElements } from "./binding";
|
||||||
import {
|
import {
|
||||||
@ -49,7 +43,6 @@ const normalizeAngle = (angle: number): number => {
|
|||||||
export const transformElements = (
|
export const transformElements = (
|
||||||
pointerDownState: PointerDownState,
|
pointerDownState: PointerDownState,
|
||||||
transformHandleType: MaybeTransformHandleType,
|
transformHandleType: MaybeTransformHandleType,
|
||||||
setTransformHandle: (nextTransformHandle: MaybeTransformHandleType) => void,
|
|
||||||
selectedElements: readonly NonDeletedExcalidrawElement[],
|
selectedElements: readonly NonDeletedExcalidrawElement[],
|
||||||
resizeArrowDirection: "origin" | "end",
|
resizeArrowDirection: "origin" | "end",
|
||||||
isRotateWithDiscreteAngle: boolean,
|
isRotateWithDiscreteAngle: boolean,
|
||||||
@ -101,36 +94,15 @@ export const transformElements = (
|
|||||||
);
|
);
|
||||||
updateBoundElements(element);
|
updateBoundElements(element);
|
||||||
} else if (transformHandleType) {
|
} else if (transformHandleType) {
|
||||||
if (isGenericElement(element)) {
|
resizeSingleElement(
|
||||||
resizeSingleGenericElement(
|
pointerDownState.originalElements.get(element.id) as typeof element,
|
||||||
pointerDownState.originalElements.get(element.id) as typeof element,
|
shouldKeepSidesRatio,
|
||||||
shouldKeepSidesRatio,
|
element,
|
||||||
element,
|
transformHandleType,
|
||||||
transformHandleType,
|
isResizeCenterPoint,
|
||||||
isResizeCenterPoint,
|
pointerX,
|
||||||
pointerX,
|
pointerY,
|
||||||
pointerY,
|
);
|
||||||
);
|
|
||||||
} else {
|
|
||||||
const keepSquareAspectRatio = shouldKeepSidesRatio;
|
|
||||||
resizeSingleNonGenericElement(
|
|
||||||
element,
|
|
||||||
transformHandleType,
|
|
||||||
isResizeCenterPoint,
|
|
||||||
keepSquareAspectRatio,
|
|
||||||
pointerX,
|
|
||||||
pointerY,
|
|
||||||
);
|
|
||||||
setTransformHandle(
|
|
||||||
normalizeTransformHandleType(element, transformHandleType),
|
|
||||||
);
|
|
||||||
if (element.width < 0) {
|
|
||||||
mutateElement(element, { width: -element.width });
|
|
||||||
}
|
|
||||||
if (element.height < 0) {
|
|
||||||
mutateElement(element, { height: -element.height });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// update cursor
|
// update cursor
|
||||||
@ -414,8 +386,8 @@ const resizeSingleTextElement = (
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const resizeSingleGenericElement = (
|
const resizeSingleElement = (
|
||||||
stateAtResizeStart: NonDeleted<ExcalidrawGenericElement>,
|
stateAtResizeStart: NonDeletedExcalidrawElement,
|
||||||
shouldKeepSidesRatio: boolean,
|
shouldKeepSidesRatio: boolean,
|
||||||
element: NonDeletedExcalidrawElement,
|
element: NonDeletedExcalidrawElement,
|
||||||
transformHandleDirection: TransformHandleDirection,
|
transformHandleDirection: TransformHandleDirection,
|
||||||
@ -423,251 +395,184 @@ const resizeSingleGenericElement = (
|
|||||||
pointerX: number,
|
pointerX: number,
|
||||||
pointerY: number,
|
pointerY: number,
|
||||||
) => {
|
) => {
|
||||||
const [x1, y1, x2, y2] = getElementAbsoluteCoords(stateAtResizeStart);
|
// Gets bounds corners
|
||||||
|
const [x1, y1, x2, y2] = getResizedElementAbsoluteCoords(
|
||||||
|
stateAtResizeStart,
|
||||||
|
stateAtResizeStart.width,
|
||||||
|
stateAtResizeStart.height,
|
||||||
|
);
|
||||||
const startTopLeft: Point = [x1, y1];
|
const startTopLeft: Point = [x1, y1];
|
||||||
const startBottomRight: Point = [x2, y2];
|
const startBottomRight: Point = [x2, y2];
|
||||||
const startCenter: Point = centerPoint(startTopLeft, startBottomRight);
|
const startCenter: Point = centerPoint(startTopLeft, startBottomRight);
|
||||||
|
|
||||||
// Calculate new dimensions based on cursor position
|
// Calculate new dimensions based on cursor position
|
||||||
let newWidth = stateAtResizeStart.width;
|
|
||||||
let newHeight = stateAtResizeStart.height;
|
|
||||||
const rotatedPointer = rotatePoint(
|
const rotatedPointer = rotatePoint(
|
||||||
[pointerX, pointerY],
|
[pointerX, pointerY],
|
||||||
startCenter,
|
startCenter,
|
||||||
-stateAtResizeStart.angle,
|
-stateAtResizeStart.angle,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
//Get bounds corners rendered on screen
|
||||||
|
const [esx1, esy1, esx2, esy2] = getResizedElementAbsoluteCoords(
|
||||||
|
element,
|
||||||
|
element.width,
|
||||||
|
element.height,
|
||||||
|
);
|
||||||
|
const boundsCurrentWidth = esx2 - esx1;
|
||||||
|
const boundsCurrentHeight = esy2 - esy1;
|
||||||
|
|
||||||
|
// It's important we set the initial scale value based on the width and height at resize start,
|
||||||
|
// otherwise previous dimensions affected by modifiers will be taken into account.
|
||||||
|
const atStartBoundsWidth = startBottomRight[0] - startTopLeft[0];
|
||||||
|
const atStartBoundsHeight = startBottomRight[1] - startTopLeft[1];
|
||||||
|
let scaleX = atStartBoundsWidth / boundsCurrentWidth;
|
||||||
|
let scaleY = atStartBoundsHeight / boundsCurrentHeight;
|
||||||
|
|
||||||
if (transformHandleDirection.includes("e")) {
|
if (transformHandleDirection.includes("e")) {
|
||||||
newWidth = rotatedPointer[0] - startTopLeft[0];
|
scaleX = (rotatedPointer[0] - startTopLeft[0]) / boundsCurrentWidth;
|
||||||
}
|
}
|
||||||
if (transformHandleDirection.includes("s")) {
|
if (transformHandleDirection.includes("s")) {
|
||||||
newHeight = rotatedPointer[1] - startTopLeft[1];
|
scaleY = (rotatedPointer[1] - startTopLeft[1]) / boundsCurrentHeight;
|
||||||
}
|
}
|
||||||
if (transformHandleDirection.includes("w")) {
|
if (transformHandleDirection.includes("w")) {
|
||||||
newWidth = startBottomRight[0] - rotatedPointer[0];
|
scaleX = (startBottomRight[0] - rotatedPointer[0]) / boundsCurrentWidth;
|
||||||
}
|
}
|
||||||
if (transformHandleDirection.includes("n")) {
|
if (transformHandleDirection.includes("n")) {
|
||||||
newHeight = startBottomRight[1] - rotatedPointer[1];
|
scaleY = (startBottomRight[1] - rotatedPointer[1]) / boundsCurrentHeight;
|
||||||
}
|
}
|
||||||
|
// Linear elements dimensions differ from bounds dimensions
|
||||||
|
const eleInitialWidth = stateAtResizeStart.width;
|
||||||
|
const eleInitialHeight = stateAtResizeStart.height;
|
||||||
|
// We have to use dimensions of element on screen, otherwise the scaling of the
|
||||||
|
// dimensions won't match the cursor for linear elements.
|
||||||
|
let eleNewWidth = element.width * scaleX;
|
||||||
|
let eleNewHeight = element.height * scaleY;
|
||||||
|
|
||||||
// adjust dimensions for resizing from center
|
// adjust dimensions for resizing from center
|
||||||
if (isResizeFromCenter) {
|
if (isResizeFromCenter) {
|
||||||
newWidth = 2 * newWidth - stateAtResizeStart.width;
|
eleNewWidth = 2 * eleNewWidth - eleInitialWidth;
|
||||||
newHeight = 2 * newHeight - stateAtResizeStart.height;
|
eleNewHeight = 2 * eleNewHeight - eleInitialHeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
// adjust dimensions to keep sides ratio
|
// adjust dimensions to keep sides ratio
|
||||||
if (shouldKeepSidesRatio) {
|
if (shouldKeepSidesRatio) {
|
||||||
const widthRatio = Math.abs(newWidth) / stateAtResizeStart.width;
|
const widthRatio = Math.abs(eleNewWidth) / eleInitialWidth;
|
||||||
const heightRatio = Math.abs(newHeight) / stateAtResizeStart.height;
|
const heightRatio = Math.abs(eleNewHeight) / eleInitialHeight;
|
||||||
if (transformHandleDirection.length === 1) {
|
if (transformHandleDirection.length === 1) {
|
||||||
newHeight *= widthRatio;
|
eleNewHeight *= widthRatio;
|
||||||
newWidth *= heightRatio;
|
eleNewWidth *= heightRatio;
|
||||||
}
|
}
|
||||||
if (transformHandleDirection.length === 2) {
|
if (transformHandleDirection.length === 2) {
|
||||||
const ratio = Math.max(widthRatio, heightRatio);
|
const ratio = Math.max(widthRatio, heightRatio);
|
||||||
newWidth = stateAtResizeStart.width * ratio * Math.sign(newWidth);
|
eleNewWidth = eleInitialWidth * ratio * Math.sign(eleNewWidth);
|
||||||
newHeight = stateAtResizeStart.height * ratio * Math.sign(newHeight);
|
eleNewHeight = eleInitialHeight * ratio * Math.sign(eleNewHeight);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const [
|
||||||
|
newBoundsX1,
|
||||||
|
newBoundsY1,
|
||||||
|
newBoundsX2,
|
||||||
|
newBoundsY2,
|
||||||
|
] = getResizedElementAbsoluteCoords(
|
||||||
|
stateAtResizeStart,
|
||||||
|
eleNewWidth,
|
||||||
|
eleNewHeight,
|
||||||
|
);
|
||||||
|
const newBoundsWidth = newBoundsX2 - newBoundsX1;
|
||||||
|
const newBoundsHeight = newBoundsY2 - newBoundsY1;
|
||||||
|
|
||||||
// Calculate new topLeft based on fixed corner during resize
|
// Calculate new topLeft based on fixed corner during resize
|
||||||
let newTopLeft = startTopLeft as [number, number];
|
let newTopLeft = [...startTopLeft] as [number, number];
|
||||||
if (["n", "w", "nw"].includes(transformHandleDirection)) {
|
if (["n", "w", "nw"].includes(transformHandleDirection)) {
|
||||||
newTopLeft = [
|
newTopLeft = [
|
||||||
startBottomRight[0] - Math.abs(newWidth),
|
startBottomRight[0] - Math.abs(newBoundsWidth),
|
||||||
startBottomRight[1] - Math.abs(newHeight),
|
startBottomRight[1] - Math.abs(newBoundsHeight),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
if (transformHandleDirection === "ne") {
|
if (transformHandleDirection === "ne") {
|
||||||
const bottomLeft = [
|
const bottomLeft = [startTopLeft[0], startBottomRight[1]];
|
||||||
stateAtResizeStart.x,
|
newTopLeft = [bottomLeft[0], bottomLeft[1] - Math.abs(newBoundsHeight)];
|
||||||
stateAtResizeStart.y + stateAtResizeStart.height,
|
|
||||||
];
|
|
||||||
newTopLeft = [bottomLeft[0], bottomLeft[1] - Math.abs(newHeight)];
|
|
||||||
}
|
}
|
||||||
if (transformHandleDirection === "sw") {
|
if (transformHandleDirection === "sw") {
|
||||||
const topRight = [
|
const topRight = [startBottomRight[0], startTopLeft[1]];
|
||||||
stateAtResizeStart.x + stateAtResizeStart.width,
|
newTopLeft = [topRight[0] - Math.abs(newBoundsWidth), topRight[1]];
|
||||||
stateAtResizeStart.y,
|
|
||||||
];
|
|
||||||
newTopLeft = [topRight[0] - Math.abs(newWidth), topRight[1]];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Keeps opposite handle fixed during resize
|
// Keeps opposite handle fixed during resize
|
||||||
if (shouldKeepSidesRatio) {
|
if (shouldKeepSidesRatio) {
|
||||||
if (["s", "n"].includes(transformHandleDirection)) {
|
if (["s", "n"].includes(transformHandleDirection)) {
|
||||||
newTopLeft[0] = startCenter[0] - newWidth / 2;
|
newTopLeft[0] = startCenter[0] - newBoundsWidth / 2;
|
||||||
}
|
}
|
||||||
if (["e", "w"].includes(transformHandleDirection)) {
|
if (["e", "w"].includes(transformHandleDirection)) {
|
||||||
newTopLeft[1] = startCenter[1] - newHeight / 2;
|
newTopLeft[1] = startCenter[1] - newBoundsHeight / 2;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Flip horizontally
|
// Flip horizontally
|
||||||
if (newWidth < 0) {
|
if (eleNewWidth < 0) {
|
||||||
if (transformHandleDirection.includes("e")) {
|
if (transformHandleDirection.includes("e")) {
|
||||||
newTopLeft[0] -= Math.abs(newWidth);
|
newTopLeft[0] -= Math.abs(newBoundsWidth);
|
||||||
}
|
}
|
||||||
if (transformHandleDirection.includes("w")) {
|
if (transformHandleDirection.includes("w")) {
|
||||||
newTopLeft[0] += Math.abs(newWidth);
|
newTopLeft[0] += Math.abs(newBoundsWidth);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Flip vertically
|
// Flip vertically
|
||||||
if (newHeight < 0) {
|
if (eleNewHeight < 0) {
|
||||||
if (transformHandleDirection.includes("s")) {
|
if (transformHandleDirection.includes("s")) {
|
||||||
newTopLeft[1] -= Math.abs(newHeight);
|
newTopLeft[1] -= Math.abs(newBoundsHeight);
|
||||||
}
|
}
|
||||||
if (transformHandleDirection.includes("n")) {
|
if (transformHandleDirection.includes("n")) {
|
||||||
newTopLeft[1] += Math.abs(newHeight);
|
newTopLeft[1] += Math.abs(newBoundsHeight);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isResizeFromCenter) {
|
if (isResizeFromCenter) {
|
||||||
newTopLeft[0] = startCenter[0] - Math.abs(newWidth) / 2;
|
newTopLeft[0] = startCenter[0] - Math.abs(newBoundsWidth) / 2;
|
||||||
newTopLeft[1] = startCenter[1] - Math.abs(newHeight) / 2;
|
newTopLeft[1] = startCenter[1] - Math.abs(newBoundsHeight) / 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
// adjust topLeft to new rotation point
|
// adjust topLeft to new rotation point
|
||||||
const angle = stateAtResizeStart.angle;
|
const angle = stateAtResizeStart.angle;
|
||||||
const rotatedTopLeft = rotatePoint(newTopLeft, startCenter, angle);
|
const rotatedTopLeft = rotatePoint(newTopLeft, startCenter, angle);
|
||||||
const newCenter: Point = [
|
const newCenter: Point = [
|
||||||
newTopLeft[0] + Math.abs(newWidth) / 2,
|
newTopLeft[0] + Math.abs(newBoundsWidth) / 2,
|
||||||
newTopLeft[1] + Math.abs(newHeight) / 2,
|
newTopLeft[1] + Math.abs(newBoundsHeight) / 2,
|
||||||
];
|
];
|
||||||
const rotatedNewCenter = rotatePoint(newCenter, startCenter, angle);
|
const rotatedNewCenter = rotatePoint(newCenter, startCenter, angle);
|
||||||
newTopLeft = rotatePoint(rotatedTopLeft, rotatedNewCenter, -angle);
|
newTopLeft = rotatePoint(rotatedTopLeft, rotatedNewCenter, -angle);
|
||||||
|
|
||||||
|
// Readjust points for linear elements
|
||||||
|
const rescaledPoints = rescalePointsInElement(
|
||||||
|
stateAtResizeStart,
|
||||||
|
eleNewWidth,
|
||||||
|
eleNewHeight,
|
||||||
|
);
|
||||||
|
// For linear elements (x,y) are the coordinates of the first drawn point not the top-left corner
|
||||||
|
// So we need to readjust (x,y) to be where the first point should be
|
||||||
|
const newOrigin = [...newTopLeft];
|
||||||
|
newOrigin[0] += stateAtResizeStart.x - newBoundsX1;
|
||||||
|
newOrigin[1] += stateAtResizeStart.y - newBoundsY1;
|
||||||
|
|
||||||
const resizedElement = {
|
const resizedElement = {
|
||||||
width: Math.abs(newWidth),
|
width: Math.abs(eleNewWidth),
|
||||||
height: Math.abs(newHeight),
|
height: Math.abs(eleNewHeight),
|
||||||
x: newTopLeft[0],
|
x: newOrigin[0],
|
||||||
y: newTopLeft[1],
|
y: newOrigin[1],
|
||||||
|
...rescaledPoints,
|
||||||
};
|
};
|
||||||
updateBoundElements(element, {
|
|
||||||
newSize: { width: resizedElement.width, height: resizedElement.height },
|
|
||||||
});
|
|
||||||
mutateElement(element, resizedElement);
|
|
||||||
};
|
|
||||||
|
|
||||||
const resizeSingleNonGenericElement = (
|
|
||||||
element: NonDeleted<Exclude<ExcalidrawElement, ExcalidrawGenericElement>>,
|
|
||||||
transformHandleType: "n" | "s" | "w" | "e" | "nw" | "ne" | "sw" | "se",
|
|
||||||
isResizeFromCenter: boolean,
|
|
||||||
keepSquareAspectRatio: boolean,
|
|
||||||
pointerX: number,
|
|
||||||
pointerY: number,
|
|
||||||
) => {
|
|
||||||
const [x1, y1, x2, y2] = getElementAbsoluteCoords(element);
|
|
||||||
const cx = (x1 + x2) / 2;
|
|
||||||
const cy = (y1 + y2) / 2;
|
|
||||||
|
|
||||||
// rotation pointer with reverse angle
|
|
||||||
const [rotatedX, rotatedY] = rotate(
|
|
||||||
pointerX,
|
|
||||||
pointerY,
|
|
||||||
cx,
|
|
||||||
cy,
|
|
||||||
-element.angle,
|
|
||||||
);
|
|
||||||
|
|
||||||
let scaleX = 1;
|
|
||||||
let scaleY = 1;
|
|
||||||
if (
|
|
||||||
transformHandleType === "e" ||
|
|
||||||
transformHandleType === "ne" ||
|
|
||||||
transformHandleType === "se"
|
|
||||||
) {
|
|
||||||
scaleX = (rotatedX - x1) / (x2 - x1);
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
transformHandleType === "s" ||
|
|
||||||
transformHandleType === "sw" ||
|
|
||||||
transformHandleType === "se"
|
|
||||||
) {
|
|
||||||
scaleY = (rotatedY - y1) / (y2 - y1);
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
transformHandleType === "w" ||
|
|
||||||
transformHandleType === "nw" ||
|
|
||||||
transformHandleType === "sw"
|
|
||||||
) {
|
|
||||||
scaleX = (x2 - rotatedX) / (x2 - x1);
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
transformHandleType === "n" ||
|
|
||||||
transformHandleType === "nw" ||
|
|
||||||
transformHandleType === "ne"
|
|
||||||
) {
|
|
||||||
scaleY = (y2 - rotatedY) / (y2 - y1);
|
|
||||||
}
|
|
||||||
let nextWidth = element.width * scaleX;
|
|
||||||
let nextHeight = element.height * scaleY;
|
|
||||||
if (keepSquareAspectRatio) {
|
|
||||||
nextWidth = nextHeight = Math.max(nextWidth, nextHeight);
|
|
||||||
}
|
|
||||||
|
|
||||||
const [nextX1, nextY1, nextX2, nextY2] = getResizedElementAbsoluteCoords(
|
|
||||||
element,
|
|
||||||
nextWidth,
|
|
||||||
nextHeight,
|
|
||||||
);
|
|
||||||
const deltaX1 = (x1 - nextX1) / 2;
|
|
||||||
const deltaY1 = (y1 - nextY1) / 2;
|
|
||||||
const deltaX2 = (x2 - nextX2) / 2;
|
|
||||||
const deltaY2 = (y2 - nextY2) / 2;
|
|
||||||
|
|
||||||
const rescaledPoints = rescalePointsInElement(element, nextWidth, nextHeight);
|
|
||||||
|
|
||||||
updateBoundElements(element, {
|
|
||||||
newSize: { width: nextWidth, height: nextHeight },
|
|
||||||
});
|
|
||||||
const [finalX1, finalY1, finalX2, finalY2] = getResizedElementAbsoluteCoords(
|
|
||||||
{
|
|
||||||
...element,
|
|
||||||
...rescaledPoints,
|
|
||||||
},
|
|
||||||
Math.abs(nextWidth),
|
|
||||||
Math.abs(nextHeight),
|
|
||||||
);
|
|
||||||
const [flipDiffX, flipDiffY] = getFlipAdjustment(
|
|
||||||
transformHandleType,
|
|
||||||
nextWidth,
|
|
||||||
nextHeight,
|
|
||||||
nextX1,
|
|
||||||
nextY1,
|
|
||||||
nextX2,
|
|
||||||
nextY2,
|
|
||||||
finalX1,
|
|
||||||
finalY1,
|
|
||||||
finalX2,
|
|
||||||
finalY2,
|
|
||||||
isLinearElement(element),
|
|
||||||
element.angle,
|
|
||||||
);
|
|
||||||
const [nextElementX, nextElementY] = adjustXYWithRotation(
|
|
||||||
getSidesForTransformHandle(transformHandleType, isResizeFromCenter),
|
|
||||||
element.x - flipDiffX,
|
|
||||||
element.y - flipDiffY,
|
|
||||||
element.angle,
|
|
||||||
deltaX1,
|
|
||||||
deltaY1,
|
|
||||||
deltaX2,
|
|
||||||
deltaY2,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
nextWidth !== 0 &&
|
resizedElement.width !== 0 &&
|
||||||
nextHeight !== 0 &&
|
resizedElement.height !== 0 &&
|
||||||
Number.isFinite(nextElementX) &&
|
Number.isFinite(resizedElement.x) &&
|
||||||
Number.isFinite(nextElementY)
|
Number.isFinite(resizedElement.y)
|
||||||
) {
|
) {
|
||||||
mutateElement(element, {
|
updateBoundElements(element, {
|
||||||
width: nextWidth,
|
newSize: { width: resizedElement.width, height: resizedElement.height },
|
||||||
height: nextHeight,
|
|
||||||
x: nextElementX,
|
|
||||||
y: nextElementY,
|
|
||||||
...rescaledPoints,
|
|
||||||
});
|
});
|
||||||
|
mutateElement(element, resizedElement);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -173,57 +173,3 @@ export const getCursorForResizingElement = (resizingElement: {
|
|||||||
|
|
||||||
return cursor ? `${cursor}-resize` : "";
|
return cursor ? `${cursor}-resize` : "";
|
||||||
};
|
};
|
||||||
|
|
||||||
export const normalizeTransformHandleType = (
|
|
||||||
element: ExcalidrawElement,
|
|
||||||
transformHandleType: TransformHandleType,
|
|
||||||
): TransformHandleType => {
|
|
||||||
if (element.width >= 0 && element.height >= 0) {
|
|
||||||
return transformHandleType;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (element.width < 0 && element.height < 0) {
|
|
||||||
switch (transformHandleType) {
|
|
||||||
case "nw":
|
|
||||||
return "se";
|
|
||||||
case "ne":
|
|
||||||
return "sw";
|
|
||||||
case "se":
|
|
||||||
return "nw";
|
|
||||||
case "sw":
|
|
||||||
return "ne";
|
|
||||||
}
|
|
||||||
} else if (element.width < 0) {
|
|
||||||
switch (transformHandleType) {
|
|
||||||
case "nw":
|
|
||||||
return "ne";
|
|
||||||
case "ne":
|
|
||||||
return "nw";
|
|
||||||
case "se":
|
|
||||||
return "sw";
|
|
||||||
case "sw":
|
|
||||||
return "se";
|
|
||||||
case "e":
|
|
||||||
return "w";
|
|
||||||
case "w":
|
|
||||||
return "e";
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
switch (transformHandleType) {
|
|
||||||
case "nw":
|
|
||||||
return "sw";
|
|
||||||
case "ne":
|
|
||||||
return "se";
|
|
||||||
case "se":
|
|
||||||
return "ne";
|
|
||||||
case "sw":
|
|
||||||
return "nw";
|
|
||||||
case "n":
|
|
||||||
return "s";
|
|
||||||
case "s":
|
|
||||||
return "n";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return transformHandleType;
|
|
||||||
};
|
|
||||||
|
@ -11,7 +11,7 @@ import { getDefaultAppState } from "../appState";
|
|||||||
import { ExcalidrawImperativeAPI } from "../components/App";
|
import { ExcalidrawImperativeAPI } from "../components/App";
|
||||||
import { ErrorDialog } from "../components/ErrorDialog";
|
import { ErrorDialog } from "../components/ErrorDialog";
|
||||||
import { TopErrorBoundary } from "../components/TopErrorBoundary";
|
import { TopErrorBoundary } from "../components/TopErrorBoundary";
|
||||||
import { APP_NAME, EVENT, TITLE_TIMEOUT } from "../constants";
|
import { APP_NAME, EVENT, TITLE_TIMEOUT, VERSION_TIMEOUT } from "../constants";
|
||||||
import { ImportedDataState } from "../data/types";
|
import { ImportedDataState } from "../data/types";
|
||||||
import {
|
import {
|
||||||
ExcalidrawElement,
|
ExcalidrawElement,
|
||||||
@ -229,18 +229,10 @@ const ExcalidrawWrapper = (props: { collab: CollabAPI }) => {
|
|||||||
const { collab } = props;
|
const { collab } = props;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// delayed by 15 sec so that the app has a time to load the latest SW
|
// Delayed so that the app has a time to load the latest SW
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
const version = getVersion();
|
trackEvent("load", "version", getVersion());
|
||||||
const loggedVersion = window.localStorage.getItem(
|
}, VERSION_TIMEOUT);
|
||||||
"excalidraw-lastLoggedVersion",
|
|
||||||
);
|
|
||||||
// prevent logging on multiple visits
|
|
||||||
if (version && version !== loggedVersion) {
|
|
||||||
window.localStorage.setItem("excalidraw-lastLoggedVersion", version);
|
|
||||||
trackEvent("load", "version", version);
|
|
||||||
}
|
|
||||||
}, 15000);
|
|
||||||
|
|
||||||
excalidrawRef.current!.readyPromise.then((excalidrawApi) => {
|
excalidrawRef.current!.readyPromise.then((excalidrawApi) => {
|
||||||
initializeScene({
|
initializeScene({
|
||||||
|
58
src/math.ts
58
src/math.ts
@ -70,64 +70,6 @@ export const adjustXYWithRotation = (
|
|||||||
return [x, y];
|
return [x, y];
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getFlipAdjustment = (
|
|
||||||
side: "n" | "s" | "w" | "e" | "nw" | "ne" | "sw" | "se",
|
|
||||||
nextWidth: number,
|
|
||||||
nextHeight: number,
|
|
||||||
nextX1: number,
|
|
||||||
nextY1: number,
|
|
||||||
nextX2: number,
|
|
||||||
nextY2: number,
|
|
||||||
finalX1: number,
|
|
||||||
finalY1: number,
|
|
||||||
finalX2: number,
|
|
||||||
finalY2: number,
|
|
||||||
needsRotation: boolean,
|
|
||||||
angle: number,
|
|
||||||
): [number, number] => {
|
|
||||||
const cos = Math.cos(angle);
|
|
||||||
const sin = Math.sin(angle);
|
|
||||||
let flipDiffX = 0;
|
|
||||||
let flipDiffY = 0;
|
|
||||||
if (nextWidth < 0) {
|
|
||||||
if (side === "e" || side === "ne" || side === "se") {
|
|
||||||
if (needsRotation) {
|
|
||||||
flipDiffX += (finalX2 - nextX1) * cos;
|
|
||||||
flipDiffY += (finalX2 - nextX1) * sin;
|
|
||||||
} else {
|
|
||||||
flipDiffX += finalX2 - nextX1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (side === "w" || side === "nw" || side === "sw") {
|
|
||||||
if (needsRotation) {
|
|
||||||
flipDiffX += (finalX1 - nextX2) * cos;
|
|
||||||
flipDiffY += (finalX1 - nextX2) * sin;
|
|
||||||
} else {
|
|
||||||
flipDiffX += finalX1 - nextX2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (nextHeight < 0) {
|
|
||||||
if (side === "s" || side === "se" || side === "sw") {
|
|
||||||
if (needsRotation) {
|
|
||||||
flipDiffY += (finalY2 - nextY1) * cos;
|
|
||||||
flipDiffX += (finalY2 - nextY1) * -sin;
|
|
||||||
} else {
|
|
||||||
flipDiffY += finalY2 - nextY1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (side === "n" || side === "ne" || side === "nw") {
|
|
||||||
if (needsRotation) {
|
|
||||||
flipDiffY += (finalY1 - nextY2) * cos;
|
|
||||||
flipDiffX += (finalY1 - nextY2) * -sin;
|
|
||||||
} else {
|
|
||||||
flipDiffY += finalY1 - nextY2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return [flipDiffX, flipDiffY];
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getPointOnAPath = (point: Point, path: Point[]) => {
|
export const getPointOnAPath = (point: Point, path: Point[]) => {
|
||||||
const [px, py] = point;
|
const [px, py] = point;
|
||||||
const [start, ...other] = path;
|
const [start, ...other] = path;
|
||||||
|
@ -12,15 +12,21 @@ The change should be grouped under one of the below section and must contain PR
|
|||||||
Please add the latest change on the top under the correct section.
|
Please add the latest change on the top under the correct section.
|
||||||
-->
|
-->
|
||||||
|
|
||||||
## [Unreleased]
|
## 0.2.0
|
||||||
|
|
||||||
### Features
|
### Features
|
||||||
|
|
||||||
|
- Add toast [#2772](https://github.com/excalidraw/excalidraw/pull/2772)
|
||||||
- Add `cmd+o` shortcut to load scene [#2732](https://github.com/excalidraw/excalidraw/pull/2732)
|
- Add `cmd+o` shortcut to load scene [#2732](https://github.com/excalidraw/excalidraw/pull/2732)
|
||||||
- Remove language picker, and add `langCode`, `renderFooter` [#2644](https://github.com/excalidraw/excalidraw/pull/2644):
|
- Remove language picker, and add `langCode`, `renderFooter` [#2644](https://github.com/excalidraw/excalidraw/pull/2644):
|
||||||
- BREAKING: removed the language picker from UI. It is now the host app's responsibility to implement a language picker if desirable, using the newly added [`renderFooter`](https://github.com/excalidraw/excalidraw/blob/master/src/packages/excalidraw/README.md#renderFooter) prop. The reasoning is that the i18n should be controlled by the app itself, not by the nested Excalidraw component.
|
- BREAKING: removed the language picker from UI. It is now the host app's responsibility to implement a language picker if desirable, using the newly added [`renderFooter`](https://github.com/excalidraw/excalidraw/blob/master/src/packages/excalidraw/README.md#renderFooter) prop. The reasoning is that the i18n should be controlled by the app itself, not by the nested Excalidraw component.
|
||||||
- Added [`langCode`](https://github.com/excalidraw/excalidraw/blob/master/src/packages/excalidraw/README.md#langCode) prop to control the UI language.
|
- Added [`langCode`](https://github.com/excalidraw/excalidraw/blob/master/src/packages/excalidraw/README.md#langCode) prop to control the UI language.
|
||||||
- Add support for `exportToBackend` prop to allow host apps to implement shareable links [#2612](https://github.com/excalidraw/excalidraw/pull/2612/files)
|
- Add support for `exportToBackend` prop to allow host apps to implement shareable links [#2612](https://github.com/excalidraw/excalidraw/pull/2612/files)
|
||||||
|
- Require use of a preset dialog size; adjust dialog sizing [#2684](https://github.com/excalidraw/excalidraw/pull/2684)
|
||||||
|
- Add line chart and paste dialog selection [#2670](https://github.com/excalidraw/excalidraw/pull/2670)
|
||||||
|
- Tweak editing behavior [#2668](https://github.com/excalidraw/excalidraw/pull/2668)
|
||||||
|
- Change title to Excalidraw after a timeout
|
||||||
|
- Checkmark to toggle context-menu-items [#2645](https://github.com/excalidraw/excalidraw/pull/2645)
|
||||||
- Add zoom to selection [#2522](https://github.com/excalidraw/excalidraw/pull/2522)
|
- Add zoom to selection [#2522](https://github.com/excalidraw/excalidraw/pull/2522)
|
||||||
- Insert Library items in the middle of the screen [#2527](https://github.com/excalidraw/excalidraw/pull/2527)
|
- Insert Library items in the middle of the screen [#2527](https://github.com/excalidraw/excalidraw/pull/2527)
|
||||||
- Show shortcut context menu [#2501](https://github.com/excalidraw/excalidraw/pull/2501)
|
- Show shortcut context menu [#2501](https://github.com/excalidraw/excalidraw/pull/2501)
|
||||||
@ -31,7 +37,12 @@ Please add the latest change on the top under the correct section.
|
|||||||
|
|
||||||
### Fixes
|
### Fixes
|
||||||
|
|
||||||
|
- Fix typo for initialData and point all links to master [#2707](https://github.com/excalidraw/excalidraw/pull/2707)
|
||||||
|
- Fix compile error [#2685](https://github.com/excalidraw/excalidraw/pull/2685)
|
||||||
|
- Center zoom on iPhone and iPad [#2642](https://github.com/excalidraw/excalidraw/pull/2642)
|
||||||
- Allow text-selecting in dialogs & reset cursor [#2783](https://github.com/excalidraw/excalidraw/pull/2783)
|
- Allow text-selecting in dialogs & reset cursor [#2783](https://github.com/excalidraw/excalidraw/pull/2783)
|
||||||
|
- Don't render due to zoom after unmount [#2779](https://github.com/excalidraw/excalidraw/pull/2779)
|
||||||
|
- Track the chart type correctly [#2773](https://github.com/excalidraw/excalidraw/pull/2773)
|
||||||
- Fix late-render due to debounced zoom [#2779](https://github.com/excalidraw/excalidraw/pull/2779)
|
- Fix late-render due to debounced zoom [#2779](https://github.com/excalidraw/excalidraw/pull/2779)
|
||||||
- Fix initialization when browser tab not focused [#2677](https://github.com/excalidraw/excalidraw/pull/2677)
|
- Fix initialization when browser tab not focused [#2677](https://github.com/excalidraw/excalidraw/pull/2677)
|
||||||
- Consistent case for export locale strings [#2622](https://github.com/excalidraw/excalidraw/pull/2622)
|
- Consistent case for export locale strings [#2622](https://github.com/excalidraw/excalidraw/pull/2622)
|
||||||
@ -47,6 +58,11 @@ Please add the latest change on the top under the correct section.
|
|||||||
### Improvements
|
### Improvements
|
||||||
|
|
||||||
- Added Zen Mode to the context menu [#2734](https://github.com/excalidraw/excalidraw/pull/2734)
|
- Added Zen Mode to the context menu [#2734](https://github.com/excalidraw/excalidraw/pull/2734)
|
||||||
|
- Do not reset to selection for draw tool [#2721]((https://github.com/excalidraw/excalidraw/pull/2721)
|
||||||
|
- Make dialogs look more like dialogs [#2686](https://github.com/excalidraw/excalidraw/pull/2686)
|
||||||
|
- Browse libraries styles fixed [#2694](https://github.com/excalidraw/excalidraw/pull/2694)
|
||||||
|
- Change hint for 2-point lines on resize [#2655](https://github.com/excalidraw/excalidraw/pull/2655)
|
||||||
|
- Align items in context menu [#2640](https://github.com/excalidraw/excalidraw/pull/2640)
|
||||||
- Do not reset to selection when using the draw tool [#2721](https://github.com/excalidraw/excalidraw/pull/2721)
|
- Do not reset to selection when using the draw tool [#2721](https://github.com/excalidraw/excalidraw/pull/2721)
|
||||||
- Display proper tooltip for 2-point lines during resize, and normalize modifier key labels in hints [#2655](https://github.com/excalidraw/excalidraw/pull/2655)
|
- Display proper tooltip for 2-point lines during resize, and normalize modifier key labels in hints [#2655](https://github.com/excalidraw/excalidraw/pull/2655)
|
||||||
- Improve error message around importing images [#2619](https://github.com/excalidraw/excalidraw/pull/2619)
|
- Improve error message around importing images [#2619](https://github.com/excalidraw/excalidraw/pull/2619)
|
||||||
@ -56,6 +72,14 @@ Please add the latest change on the top under the correct section.
|
|||||||
- Hide shortcuts on pickers for mobile [#2508](https://github.com/excalidraw/excalidraw/pull/2508)
|
- Hide shortcuts on pickers for mobile [#2508](https://github.com/excalidraw/excalidraw/pull/2508)
|
||||||
- Hide stats and scrollToContent-button when mobile menus open [#2509](https://github.com/excalidraw/excalidraw/pull/2509)
|
- Hide stats and scrollToContent-button when mobile menus open [#2509](https://github.com/excalidraw/excalidraw/pull/2509)
|
||||||
|
|
||||||
|
### Refactor
|
||||||
|
|
||||||
|
- refactor: Converting span to kbd tag [#2774](https://github.com/excalidraw/excalidraw/pull/2774)
|
||||||
|
- Media queries [#2680](https://github.com/excalidraw/excalidraw/pull/2680)
|
||||||
|
- Remove duplicate entry from en.json[#2654](https://github.com/excalidraw/excalidraw/pull/2654)
|
||||||
|
- Remove the word toggle from labels [#2648](https://github.com/excalidraw/excalidraw/pull/2648)
|
||||||
|
-
|
||||||
|
|
||||||
### Chore
|
### Chore
|
||||||
|
|
||||||
- Bump ini from 1.3.5 to 1.3.7 in /src/packages/excalidraw [#2500](https://github.com/excalidraw/excalidraw/pull/2500)
|
- Bump ini from 1.3.5 to 1.3.7 in /src/packages/excalidraw [#2500](https://github.com/excalidraw/excalidraw/pull/2500)
|
||||||
|
@ -142,6 +142,51 @@ export default function App() {
|
|||||||
| [`langCode`](#langCode) | string | `en` | Language code string |
|
| [`langCode`](#langCode) | string | `en` | Language code string |
|
||||||
| [`renderFooter `](#renderFooter) | Function | | Function that renders custom UI footer |
|
| [`renderFooter `](#renderFooter) | Function | | Function that renders custom UI footer |
|
||||||
|
|
||||||
|
### `Extra API's`
|
||||||
|
|
||||||
|
#### `getSceneVersion`
|
||||||
|
|
||||||
|
**How to use**
|
||||||
|
|
||||||
|
<pre>
|
||||||
|
import { getSceneVersion } from "@excalidraw/excalidraw";
|
||||||
|
getSceneVersion(elements: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L78">ExcalidrawElement []</a>)
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
This function returns the current scene version.
|
||||||
|
|
||||||
|
#### `getSyncableElements`
|
||||||
|
|
||||||
|
**_Signature_**
|
||||||
|
|
||||||
|
<pre>
|
||||||
|
getSyncableElements(elements: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L78">ExcalidrawElement []</a>):<a href="https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L78">ExcalidrawElement []</a>
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
**How to use**
|
||||||
|
|
||||||
|
```js
|
||||||
|
import { getSyncableElements } from "@excalidraw/excalidraw";
|
||||||
|
```
|
||||||
|
|
||||||
|
This function returns all the deleted elements of the scene.
|
||||||
|
|
||||||
|
### `getElementMap`
|
||||||
|
|
||||||
|
**_Signature_**
|
||||||
|
|
||||||
|
<pre>
|
||||||
|
getElementsMap(elements: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L78">ExcalidrawElement []</a>): {[id: string]: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L78">ExcalidrawElement</a>}
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
**How to use**
|
||||||
|
|
||||||
|
```js
|
||||||
|
import { getElementsMap } from "@excalidraw/excalidraw";
|
||||||
|
```
|
||||||
|
|
||||||
|
This function returns an object where each element is mapped to its id.
|
||||||
|
|
||||||
#### `width`
|
#### `width`
|
||||||
|
|
||||||
This props defines the `width` of the Excalidraw component. Defaults to `window.innerWidth` if not passed.
|
This props defines the `width` of the Excalidraw component. Defaults to `window.innerWidth` if not passed.
|
||||||
|
2
src/packages/excalidraw/package-lock.json
generated
2
src/packages/excalidraw/package-lock.json
generated
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@excalidraw/excalidraw",
|
"name": "@excalidraw/excalidraw",
|
||||||
"version": "0.1.1",
|
"version": "0.2.0",
|
||||||
"lockfileVersion": 1,
|
"lockfileVersion": 1,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@excalidraw/excalidraw",
|
"name": "@excalidraw/excalidraw",
|
||||||
"version": "0.1.1",
|
"version": "0.2.0",
|
||||||
"main": "dist/excalidraw.min.js",
|
"main": "dist/excalidraw.min.js",
|
||||||
"files": [
|
"files": [
|
||||||
"dist/*"
|
"dist/*"
|
||||||
|
@ -41,7 +41,7 @@ describe("resize rectangle ellipses and diamond elements", () => {
|
|||||||
${"s"} | ${[_, 39]} | ${[100, 139]} | ${[elemData.x, elemData.x]}
|
${"s"} | ${[_, 39]} | ${[100, 139]} | ${[elemData.x, elemData.x]}
|
||||||
${"e"} | ${[-20, _]} | ${[80, 100]} | ${[elemData.x, elemData.y]}
|
${"e"} | ${[-20, _]} | ${[80, 100]} | ${[elemData.x, elemData.y]}
|
||||||
${"w"} | ${[-20, _]} | ${[120, 100]} | ${[-20, elemData.y]}
|
${"w"} | ${[-20, _]} | ${[120, 100]} | ${[-20, elemData.y]}
|
||||||
${"ne"} | ${[10, 55]} | ${[110, 45]} | ${[elemData.x, 55]}
|
${"ne"} | ${[5, 55]} | ${[105, 45]} | ${[elemData.x, 55]}
|
||||||
${"se"} | ${[-30, -10]} | ${[70, 90]} | ${[elemData.x, elemData.y]}
|
${"se"} | ${[-30, -10]} | ${[70, 90]} | ${[elemData.x, elemData.y]}
|
||||||
${"nw"} | ${[-300, -200]} | ${[400, 300]} | ${[-300, -200]}
|
${"nw"} | ${[-300, -200]} | ${[400, 300]} | ${[-300, -200]}
|
||||||
${"sw"} | ${[40, -20]} | ${[60, 80]} | ${[40, 0]}
|
${"sw"} | ${[40, -20]} | ${[60, 80]} | ${[40, 0]}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user