mirror of
https://github.com/excalidraw/excalidraw.git
synced 2024-11-10 11:35:52 +01:00
feat: add flipping for multiple elements (#5578)
* feat: add flipping when resizing multiple elements * fix: image elements not flipping its content * test: fix accidental resizing in grouping test * fix: angles not flipping vertically when resizing * feat: add flipping multiple elements with a command * revert: image elements not flipping its content This reverts commit cb989a6c66e62a02a8c04ce41f12507806c8d0a0. * fix: add special cases for flipping text & images * fix: a few corner cases for flipping * fix: remove angle flip * fix: bound text scaling when resizing * fix: linear elements drifting away after multiple flips * revert: fix linear elements drifting away after multiple flips This reverts commit bffc33dd3ffe56c72029eee6aca843d992bac7ab. * fix: linear elements unstable bounds * revert: linear elements unstable bounds This reverts commit 22ae9b02c4a49f0ed6448c27abe1969cf6abb1e3. * fix: hand-drawn lines shift after flipping * test: fix flipping tests * test: fix the number of context menu items * fix: incorrect scaling due to ignoring bound text when finding selection bounds * fix: bound text coordinates not being updated * fix: lines bound text rotation * fix: incorrect placement of bound lines on flip * remove redundant predicates in actionFlip * update test * refactor resizeElement with some renaming and comments * fix grouped bounded text elements not being flipped correctly * combine mutation for bounded text element * remove incorrect return * fix: linear elements bindings after flipping * revert: remove incorrect return This reverts commit e6b205ca900b504fe982e4ac1b3b19dcfca246b8. * fix: minimum size for all elements in selection --------- Co-authored-by: Ryan Di <ryan.weihao.di@gmail.com>
This commit is contained in:
parent
75bea48b54
commit
6459ccda6a
@ -1,42 +1,17 @@
|
|||||||
import { register } from "./register";
|
import { register } from "./register";
|
||||||
import { getSelectedElements } from "../scene";
|
import { getSelectedElements } from "../scene";
|
||||||
import { getNonDeletedElements } from "../element";
|
import { getNonDeletedElements } from "../element";
|
||||||
import { mutateElement } from "../element/mutateElement";
|
|
||||||
import { ExcalidrawElement, NonDeleted } from "../element/types";
|
import { ExcalidrawElement, NonDeleted } from "../element/types";
|
||||||
import { normalizeAngle, resizeSingleElement } from "../element/resizeElements";
|
import { resizeMultipleElements } from "../element/resizeElements";
|
||||||
import { AppState } from "../types";
|
import { AppState, PointerDownState } from "../types";
|
||||||
import { getTransformHandles } from "../element/transformHandles";
|
|
||||||
import { updateBoundElements } from "../element/binding";
|
|
||||||
import { arrayToMap } from "../utils";
|
import { arrayToMap } from "../utils";
|
||||||
import {
|
|
||||||
getElementAbsoluteCoords,
|
|
||||||
getElementPointsCoords,
|
|
||||||
} from "../element/bounds";
|
|
||||||
import { isLinearElement } from "../element/typeChecks";
|
|
||||||
import { LinearElementEditor } from "../element/linearElementEditor";
|
|
||||||
import { CODES, KEYS } from "../keys";
|
import { CODES, KEYS } from "../keys";
|
||||||
|
import { getCommonBoundingBox } from "../element/bounds";
|
||||||
const enableActionFlipHorizontal = (
|
import {
|
||||||
elements: readonly ExcalidrawElement[],
|
bindOrUnbindSelectedElements,
|
||||||
appState: AppState,
|
isBindingEnabled,
|
||||||
) => {
|
unbindLinearElements,
|
||||||
const eligibleElements = getSelectedElements(
|
} from "../element/binding";
|
||||||
getNonDeletedElements(elements),
|
|
||||||
appState,
|
|
||||||
);
|
|
||||||
return eligibleElements.length === 1 && eligibleElements[0].type !== "text";
|
|
||||||
};
|
|
||||||
|
|
||||||
const enableActionFlipVertical = (
|
|
||||||
elements: readonly ExcalidrawElement[],
|
|
||||||
appState: AppState,
|
|
||||||
) => {
|
|
||||||
const eligibleElements = getSelectedElements(
|
|
||||||
getNonDeletedElements(elements),
|
|
||||||
appState,
|
|
||||||
);
|
|
||||||
return eligibleElements.length === 1;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const actionFlipHorizontal = register({
|
export const actionFlipHorizontal = register({
|
||||||
name: "flipHorizontal",
|
name: "flipHorizontal",
|
||||||
@ -50,8 +25,6 @@ export const actionFlipHorizontal = register({
|
|||||||
},
|
},
|
||||||
keyTest: (event) => event.shiftKey && event.code === CODES.H,
|
keyTest: (event) => event.shiftKey && event.code === CODES.H,
|
||||||
contextItemLabel: "labels.flipHorizontal",
|
contextItemLabel: "labels.flipHorizontal",
|
||||||
predicate: (elements, appState) =>
|
|
||||||
enableActionFlipHorizontal(elements, appState),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export const actionFlipVertical = register({
|
export const actionFlipVertical = register({
|
||||||
@ -67,8 +40,6 @@ export const actionFlipVertical = register({
|
|||||||
keyTest: (event) =>
|
keyTest: (event) =>
|
||||||
event.shiftKey && event.code === CODES.V && !event[KEYS.CTRL_OR_CMD],
|
event.shiftKey && event.code === CODES.V && !event[KEYS.CTRL_OR_CMD],
|
||||||
contextItemLabel: "labels.flipVertical",
|
contextItemLabel: "labels.flipVertical",
|
||||||
predicate: (elements, appState) =>
|
|
||||||
enableActionFlipVertical(elements, appState),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const flipSelectedElements = (
|
const flipSelectedElements = (
|
||||||
@ -81,11 +52,6 @@ const flipSelectedElements = (
|
|||||||
appState,
|
appState,
|
||||||
);
|
);
|
||||||
|
|
||||||
// remove once we allow for groups of elements to be flipped
|
|
||||||
if (selectedElements.length > 1) {
|
|
||||||
return elements;
|
|
||||||
}
|
|
||||||
|
|
||||||
const updatedElements = flipElements(
|
const updatedElements = flipElements(
|
||||||
selectedElements,
|
selectedElements,
|
||||||
appState,
|
appState,
|
||||||
@ -104,144 +70,20 @@ const flipElements = (
|
|||||||
appState: AppState,
|
appState: AppState,
|
||||||
flipDirection: "horizontal" | "vertical",
|
flipDirection: "horizontal" | "vertical",
|
||||||
): ExcalidrawElement[] => {
|
): ExcalidrawElement[] => {
|
||||||
elements.forEach((element) => {
|
const { minX, minY, maxX, maxY } = getCommonBoundingBox(elements);
|
||||||
flipElement(element, appState);
|
|
||||||
// If vertical flip, rotate an extra 180
|
resizeMultipleElements(
|
||||||
if (flipDirection === "vertical") {
|
{ originalElements: arrayToMap(elements) } as PointerDownState,
|
||||||
rotateElement(element, Math.PI);
|
elements,
|
||||||
}
|
"nw",
|
||||||
});
|
true,
|
||||||
|
flipDirection === "horizontal" ? maxX : minX,
|
||||||
|
flipDirection === "horizontal" ? minY : maxY,
|
||||||
|
);
|
||||||
|
|
||||||
|
(isBindingEnabled(appState)
|
||||||
|
? bindOrUnbindSelectedElements
|
||||||
|
: unbindLinearElements)(elements);
|
||||||
|
|
||||||
return elements;
|
return elements;
|
||||||
};
|
};
|
||||||
|
|
||||||
const flipElement = (
|
|
||||||
element: NonDeleted<ExcalidrawElement>,
|
|
||||||
appState: AppState,
|
|
||||||
) => {
|
|
||||||
const originalX = element.x;
|
|
||||||
const originalY = element.y;
|
|
||||||
const width = element.width;
|
|
||||||
const height = element.height;
|
|
||||||
const originalAngle = normalizeAngle(element.angle);
|
|
||||||
|
|
||||||
// Rotate back to zero, if necessary
|
|
||||||
mutateElement(element, {
|
|
||||||
angle: normalizeAngle(0),
|
|
||||||
});
|
|
||||||
// Flip unrotated by pulling TransformHandle to opposite side
|
|
||||||
const transformHandles = getTransformHandles(element, appState.zoom);
|
|
||||||
let usingNWHandle = true;
|
|
||||||
let nHandle = transformHandles.nw;
|
|
||||||
if (!nHandle) {
|
|
||||||
// Use ne handle instead
|
|
||||||
usingNWHandle = false;
|
|
||||||
nHandle = transformHandles.ne;
|
|
||||||
if (!nHandle) {
|
|
||||||
mutateElement(element, {
|
|
||||||
angle: originalAngle,
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let finalOffsetX = 0;
|
|
||||||
if (isLinearElement(element) && element.points.length < 3) {
|
|
||||||
finalOffsetX =
|
|
||||||
element.points.reduce((max, point) => Math.max(max, point[0]), 0) * 2 -
|
|
||||||
element.width;
|
|
||||||
}
|
|
||||||
|
|
||||||
let initialPointsCoords;
|
|
||||||
if (isLinearElement(element)) {
|
|
||||||
initialPointsCoords = getElementPointsCoords(element, element.points);
|
|
||||||
}
|
|
||||||
const initialElementAbsoluteCoords = getElementAbsoluteCoords(element);
|
|
||||||
|
|
||||||
if (isLinearElement(element) && element.points.length < 3) {
|
|
||||||
for (let index = 1; index < element.points.length; index++) {
|
|
||||||
LinearElementEditor.movePoints(element, [
|
|
||||||
{
|
|
||||||
index,
|
|
||||||
point: [-element.points[index][0], element.points[index][1]],
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
LinearElementEditor.normalizePoints(element);
|
|
||||||
} else {
|
|
||||||
const elWidth = initialPointsCoords
|
|
||||||
? initialPointsCoords[2] - initialPointsCoords[0]
|
|
||||||
: initialElementAbsoluteCoords[2] - initialElementAbsoluteCoords[0];
|
|
||||||
|
|
||||||
const startPoint = initialPointsCoords
|
|
||||||
? [initialPointsCoords[0], initialPointsCoords[1]]
|
|
||||||
: [initialElementAbsoluteCoords[0], initialElementAbsoluteCoords[1]];
|
|
||||||
|
|
||||||
resizeSingleElement(
|
|
||||||
new Map().set(element.id, element),
|
|
||||||
false,
|
|
||||||
element,
|
|
||||||
usingNWHandle ? "nw" : "ne",
|
|
||||||
true,
|
|
||||||
usingNWHandle ? startPoint[0] + elWidth : startPoint[0] - elWidth,
|
|
||||||
startPoint[1],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Rotate by (360 degrees - original angle)
|
|
||||||
let angle = normalizeAngle(2 * Math.PI - originalAngle);
|
|
||||||
if (angle < 0) {
|
|
||||||
// check, probably unnecessary
|
|
||||||
angle = normalizeAngle(angle + 2 * Math.PI);
|
|
||||||
}
|
|
||||||
mutateElement(element, {
|
|
||||||
angle,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Move back to original spot to appear "flipped in place"
|
|
||||||
mutateElement(element, {
|
|
||||||
x: originalX + finalOffsetX,
|
|
||||||
y: originalY,
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
});
|
|
||||||
|
|
||||||
updateBoundElements(element);
|
|
||||||
|
|
||||||
if (initialPointsCoords && isLinearElement(element)) {
|
|
||||||
// Adjusting origin because when a beizer curve path exceeds min/max points it offsets the origin.
|
|
||||||
// There's still room for improvement since when the line roughness is > 1
|
|
||||||
// we still have a small offset of the origin when fliipping the element.
|
|
||||||
const finalPointsCoords = getElementPointsCoords(element, element.points);
|
|
||||||
|
|
||||||
const topLeftCoordsDiff = initialPointsCoords[0] - finalPointsCoords[0];
|
|
||||||
const topRightCoordDiff = initialPointsCoords[2] - finalPointsCoords[2];
|
|
||||||
|
|
||||||
const coordsDiff = topLeftCoordsDiff + topRightCoordDiff;
|
|
||||||
|
|
||||||
mutateElement(element, {
|
|
||||||
x: element.x + coordsDiff * 0.5,
|
|
||||||
y: element.y,
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const rotateElement = (element: ExcalidrawElement, rotationAngle: number) => {
|
|
||||||
const originalX = element.x;
|
|
||||||
const originalY = element.y;
|
|
||||||
let angle = normalizeAngle(element.angle + rotationAngle);
|
|
||||||
if (angle < 0) {
|
|
||||||
// check, probably unnecessary
|
|
||||||
angle = normalizeAngle(2 * Math.PI + angle);
|
|
||||||
}
|
|
||||||
mutateElement(element, {
|
|
||||||
angle,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Move back to original spot
|
|
||||||
mutateElement(element, {
|
|
||||||
x: originalX,
|
|
||||||
y: originalY,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
@ -14,17 +14,21 @@ import {
|
|||||||
NonDeleted,
|
NonDeleted,
|
||||||
ExcalidrawElement,
|
ExcalidrawElement,
|
||||||
ExcalidrawTextElementWithContainer,
|
ExcalidrawTextElementWithContainer,
|
||||||
|
ExcalidrawImageElement,
|
||||||
} from "./types";
|
} from "./types";
|
||||||
|
import type { Mutable } from "../utility-types";
|
||||||
import {
|
import {
|
||||||
getElementAbsoluteCoords,
|
getElementAbsoluteCoords,
|
||||||
getCommonBounds,
|
getCommonBounds,
|
||||||
getResizedElementAbsoluteCoords,
|
getResizedElementAbsoluteCoords,
|
||||||
getCommonBoundingBox,
|
getCommonBoundingBox,
|
||||||
|
getElementPointsCoords,
|
||||||
} from "./bounds";
|
} from "./bounds";
|
||||||
import {
|
import {
|
||||||
isArrowElement,
|
isArrowElement,
|
||||||
isBoundToContainer,
|
isBoundToContainer,
|
||||||
isFreeDrawElement,
|
isFreeDrawElement,
|
||||||
|
isImageElement,
|
||||||
isLinearElement,
|
isLinearElement,
|
||||||
isTextElement,
|
isTextElement,
|
||||||
} from "./typeChecks";
|
} from "./typeChecks";
|
||||||
@ -49,8 +53,12 @@ import {
|
|||||||
measureText,
|
measureText,
|
||||||
getBoundTextMaxHeight,
|
getBoundTextMaxHeight,
|
||||||
} from "./textElement";
|
} from "./textElement";
|
||||||
|
import { LinearElementEditor } from "./linearElementEditor";
|
||||||
|
|
||||||
export const normalizeAngle = (angle: number): number => {
|
export const normalizeAngle = (angle: number): number => {
|
||||||
|
if (angle < 0) {
|
||||||
|
return angle + 2 * Math.PI;
|
||||||
|
}
|
||||||
if (angle >= 2 * Math.PI) {
|
if (angle >= 2 * Math.PI) {
|
||||||
return angle - 2 * Math.PI;
|
return angle - 2 * Math.PI;
|
||||||
}
|
}
|
||||||
@ -596,7 +604,7 @@ export const resizeSingleElement = (
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const resizeMultipleElements = (
|
export const resizeMultipleElements = (
|
||||||
pointerDownState: PointerDownState,
|
pointerDownState: PointerDownState,
|
||||||
selectedElements: readonly NonDeletedExcalidrawElement[],
|
selectedElements: readonly NonDeletedExcalidrawElement[],
|
||||||
transformHandleType: "nw" | "ne" | "sw" | "se",
|
transformHandleType: "nw" | "ne" | "sw" | "se",
|
||||||
@ -627,8 +635,28 @@ const resizeMultipleElements = (
|
|||||||
[],
|
[],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// getCommonBoundingBox() uses getBoundTextElement() which returns null for
|
||||||
|
// original elements from pointerDownState, so we have to find and add these
|
||||||
|
// bound text elements manually. Additionally, the coordinates of bound text
|
||||||
|
// elements aren't always up to date.
|
||||||
|
const boundTextElements = targetElements.reduce((acc, { orig }) => {
|
||||||
|
if (!isLinearElement(orig)) {
|
||||||
|
return acc;
|
||||||
|
}
|
||||||
|
const textId = getBoundTextElementId(orig);
|
||||||
|
if (!textId) {
|
||||||
|
return acc;
|
||||||
|
}
|
||||||
|
const text = pointerDownState.originalElements.get(textId) ?? null;
|
||||||
|
if (!isBoundToContainer(text)) {
|
||||||
|
return acc;
|
||||||
|
}
|
||||||
|
const xy = LinearElementEditor.getBoundTextElementPosition(orig, text);
|
||||||
|
return [...acc, { ...text, ...xy }];
|
||||||
|
}, [] as ExcalidrawTextElementWithContainer[]);
|
||||||
|
|
||||||
const { minX, minY, maxX, maxY, midX, midY } = getCommonBoundingBox(
|
const { minX, minY, maxX, maxY, midX, midY } = getCommonBoundingBox(
|
||||||
targetElements.map(({ orig }) => orig),
|
targetElements.map(({ orig }) => orig).concat(boundTextElements),
|
||||||
);
|
);
|
||||||
const direction = transformHandleType;
|
const direction = transformHandleType;
|
||||||
|
|
||||||
@ -640,12 +668,22 @@ const resizeMultipleElements = (
|
|||||||
};
|
};
|
||||||
|
|
||||||
// anchor point must be on the opposite side of the dragged selection handle
|
// anchor point must be on the opposite side of the dragged selection handle
|
||||||
// or be the center of the selection if alt is pressed
|
// or be the center of the selection if shouldResizeFromCenter
|
||||||
const [anchorX, anchorY]: Point = shouldResizeFromCenter
|
const [anchorX, anchorY]: Point = shouldResizeFromCenter
|
||||||
? [midX, midY]
|
? [midX, midY]
|
||||||
: mapDirectionsToAnchors[direction];
|
: mapDirectionsToAnchors[direction];
|
||||||
|
|
||||||
const mapDirectionsToPointerSides: Record<
|
const scale =
|
||||||
|
Math.max(
|
||||||
|
Math.abs(pointerX - anchorX) / (maxX - minX) || 0,
|
||||||
|
Math.abs(pointerY - anchorY) / (maxY - minY) || 0,
|
||||||
|
) * (shouldResizeFromCenter ? 2 : 1);
|
||||||
|
|
||||||
|
if (scale === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const mapDirectionsToPointerPositions: Record<
|
||||||
typeof direction,
|
typeof direction,
|
||||||
[x: boolean, y: boolean]
|
[x: boolean, y: boolean]
|
||||||
> = {
|
> = {
|
||||||
@ -655,68 +693,117 @@ const resizeMultipleElements = (
|
|||||||
nw: [pointerX <= anchorX, pointerY <= anchorY],
|
nw: [pointerX <= anchorX, pointerY <= anchorY],
|
||||||
};
|
};
|
||||||
|
|
||||||
// pointer side relative to anchor
|
/**
|
||||||
const [pointerSideX, pointerSideY] = mapDirectionsToPointerSides[
|
* to flip an element:
|
||||||
|
* 1. determine over which axis is the element being flipped
|
||||||
|
* (could be x, y, or both) indicated by `flipFactorX` & `flipFactorY`
|
||||||
|
* 2. shift element's position by the amount of width or height (or both) or
|
||||||
|
* mirror points in the case of linear & freedraw elemenets
|
||||||
|
* 3. adjust element angle
|
||||||
|
*/
|
||||||
|
const [flipFactorX, flipFactorY] = mapDirectionsToPointerPositions[
|
||||||
direction
|
direction
|
||||||
].map((condition) => (condition ? 1 : -1));
|
].map((condition) => (condition ? 1 : -1));
|
||||||
|
const isFlippedByX = flipFactorX < 0;
|
||||||
|
const isFlippedByY = flipFactorY < 0;
|
||||||
|
|
||||||
// stop resizing if a pointer is on the other side of selection
|
const elementsAndUpdates: {
|
||||||
if (pointerSideX < 0 && pointerSideY < 0) {
|
element: NonDeletedExcalidrawElement;
|
||||||
return;
|
update: Mutable<
|
||||||
|
Pick<ExcalidrawElement, "x" | "y" | "width" | "height" | "angle">
|
||||||
|
> & {
|
||||||
|
points?: ExcalidrawLinearElement["points"];
|
||||||
|
fontSize?: ExcalidrawTextElement["fontSize"];
|
||||||
|
baseline?: ExcalidrawTextElement["baseline"];
|
||||||
|
scale?: ExcalidrawImageElement["scale"];
|
||||||
|
};
|
||||||
|
boundText: {
|
||||||
|
element: ExcalidrawTextElementWithContainer;
|
||||||
|
fontSize: ExcalidrawTextElement["fontSize"];
|
||||||
|
baseline: ExcalidrawTextElement["baseline"];
|
||||||
|
} | null;
|
||||||
|
}[] = [];
|
||||||
|
|
||||||
|
for (const { orig, latest } of targetElements) {
|
||||||
|
// bounded text elements are updated along with their container elements
|
||||||
|
if (isTextElement(orig) && isBoundToContainer(orig)) {
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const scale =
|
const width = orig.width * scale;
|
||||||
Math.max(
|
const height = orig.height * scale;
|
||||||
(pointerSideX * Math.abs(pointerX - anchorX)) / (maxX - minX),
|
const angle = normalizeAngle(orig.angle * flipFactorX * flipFactorY);
|
||||||
(pointerSideY * Math.abs(pointerY - anchorY)) / (maxY - minY),
|
|
||||||
) * (shouldResizeFromCenter ? 2 : 1);
|
|
||||||
|
|
||||||
if (scale === 0) {
|
const isLinearOrFreeDraw = isLinearElement(orig) || isFreeDrawElement(orig);
|
||||||
return;
|
const offsetX = orig.x - anchorX;
|
||||||
}
|
const offsetY = orig.y - anchorY;
|
||||||
|
const shiftX = isFlippedByX && !isLinearOrFreeDraw ? width : 0;
|
||||||
|
const shiftY = isFlippedByY && !isLinearOrFreeDraw ? height : 0;
|
||||||
|
const x = anchorX + flipFactorX * (offsetX * scale + shiftX);
|
||||||
|
const y = anchorY + flipFactorY * (offsetY * scale + shiftY);
|
||||||
|
|
||||||
targetElements.forEach((element) => {
|
|
||||||
const width = element.orig.width * scale;
|
|
||||||
const height = element.orig.height * scale;
|
|
||||||
const x = anchorX + (element.orig.x - anchorX) * scale;
|
|
||||||
const y = anchorY + (element.orig.y - anchorY) * scale;
|
|
||||||
|
|
||||||
// readjust points for linear & free draw elements
|
|
||||||
const rescaledPoints = rescalePointsInElement(
|
const rescaledPoints = rescalePointsInElement(
|
||||||
element.orig,
|
orig,
|
||||||
width,
|
width * flipFactorX,
|
||||||
height,
|
height * flipFactorY,
|
||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
|
|
||||||
const update: {
|
const update: typeof elementsAndUpdates[0]["update"] = {
|
||||||
width: number;
|
|
||||||
height: number;
|
|
||||||
x: number;
|
|
||||||
y: number;
|
|
||||||
points?: Point[];
|
|
||||||
fontSize?: number;
|
|
||||||
baseline?: number;
|
|
||||||
} = {
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
x,
|
x,
|
||||||
y,
|
y,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
angle,
|
||||||
...rescaledPoints,
|
...rescaledPoints,
|
||||||
};
|
};
|
||||||
|
|
||||||
let boundTextUpdates: { fontSize: number; baseline: number } | null = null;
|
if (isImageElement(orig) && targetElements.length === 1) {
|
||||||
|
update.scale = [orig.scale[0] * flipFactorX, orig.scale[1] * flipFactorY];
|
||||||
|
}
|
||||||
|
|
||||||
const boundTextElement = getBoundTextElement(element.latest);
|
if (isLinearElement(orig) && (isFlippedByX || isFlippedByY)) {
|
||||||
|
const origBounds = getElementPointsCoords(orig, orig.points);
|
||||||
|
const newBounds = getElementPointsCoords(
|
||||||
|
{ ...orig, x, y },
|
||||||
|
rescaledPoints.points!,
|
||||||
|
);
|
||||||
|
const origXY = [orig.x, orig.y];
|
||||||
|
const newXY = [x, y];
|
||||||
|
|
||||||
if (boundTextElement || isTextElement(element.orig)) {
|
const linearShift = (axis: "x" | "y") => {
|
||||||
|
const i = axis === "x" ? 0 : 1;
|
||||||
|
return (
|
||||||
|
(newBounds[i + 2] -
|
||||||
|
newXY[i] -
|
||||||
|
(origXY[i] - origBounds[i]) * scale +
|
||||||
|
(origBounds[i + 2] - origXY[i]) * scale -
|
||||||
|
(newXY[i] - newBounds[i])) /
|
||||||
|
2
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (isFlippedByX) {
|
||||||
|
update.x -= linearShift("x");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isFlippedByY) {
|
||||||
|
update.y -= linearShift("y");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let boundText: typeof elementsAndUpdates[0]["boundText"] = null;
|
||||||
|
|
||||||
|
const boundTextElement = getBoundTextElement(latest);
|
||||||
|
|
||||||
|
if (boundTextElement || isTextElement(orig)) {
|
||||||
const updatedElement = {
|
const updatedElement = {
|
||||||
...element.latest,
|
...latest,
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
};
|
};
|
||||||
const metrics = measureFontSizeFromWidth(
|
const metrics = measureFontSizeFromWidth(
|
||||||
boundTextElement ?? (element.orig as ExcalidrawTextElement),
|
boundTextElement ?? (orig as ExcalidrawTextElement),
|
||||||
boundTextElement
|
boundTextElement
|
||||||
? getBoundTextMaxWidth(updatedElement)
|
? getBoundTextMaxWidth(updatedElement)
|
||||||
: updatedElement.width,
|
: updatedElement.width,
|
||||||
@ -729,29 +816,50 @@ const resizeMultipleElements = (
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isTextElement(element.orig)) {
|
if (isTextElement(orig)) {
|
||||||
update.fontSize = metrics.size;
|
update.fontSize = metrics.size;
|
||||||
update.baseline = metrics.baseline;
|
update.baseline = metrics.baseline;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (boundTextElement) {
|
if (boundTextElement) {
|
||||||
boundTextUpdates = {
|
boundText = {
|
||||||
|
element: boundTextElement,
|
||||||
fontSize: metrics.size,
|
fontSize: metrics.size,
|
||||||
baseline: metrics.baseline,
|
baseline: metrics.baseline,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
updateBoundElements(element.latest, { newSize: { width, height } });
|
elementsAndUpdates.push({ element: latest, update, boundText });
|
||||||
|
|
||||||
mutateElement(element.latest, update);
|
|
||||||
|
|
||||||
if (boundTextElement && boundTextUpdates) {
|
|
||||||
mutateElement(boundTextElement, boundTextUpdates);
|
|
||||||
|
|
||||||
handleBindTextResize(element.latest, transformHandleType);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const elementsToUpdate = elementsAndUpdates.map(({ element }) => element);
|
||||||
|
|
||||||
|
for (const { element, update, boundText } of elementsAndUpdates) {
|
||||||
|
const { width, height, angle } = update;
|
||||||
|
|
||||||
|
mutateElement(element, update, false);
|
||||||
|
|
||||||
|
updateBoundElements(element, {
|
||||||
|
simultaneouslyUpdated: elementsToUpdate,
|
||||||
|
newSize: { width, height },
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (boundText) {
|
||||||
|
const { element: boundTextElement, ...boundTextUpdates } = boundText;
|
||||||
|
mutateElement(
|
||||||
|
boundTextElement,
|
||||||
|
{
|
||||||
|
...boundTextUpdates,
|
||||||
|
angle: isLinearElement(element) ? undefined : angle,
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
handleBindTextResize(element, transformHandleType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Scene.getScene(elementsAndUpdates[0].element)?.informMutation();
|
||||||
};
|
};
|
||||||
|
|
||||||
const rotateMultipleElements = (
|
const rotateMultipleElements = (
|
||||||
|
@ -197,7 +197,6 @@ Object {
|
|||||||
"keyTest": [Function],
|
"keyTest": [Function],
|
||||||
"name": "flipHorizontal",
|
"name": "flipHorizontal",
|
||||||
"perform": [Function],
|
"perform": [Function],
|
||||||
"predicate": [Function],
|
|
||||||
"trackEvent": Object {
|
"trackEvent": Object {
|
||||||
"category": "element",
|
"category": "element",
|
||||||
},
|
},
|
||||||
@ -207,7 +206,6 @@ Object {
|
|||||||
"keyTest": [Function],
|
"keyTest": [Function],
|
||||||
"name": "flipVertical",
|
"name": "flipVertical",
|
||||||
"perform": [Function],
|
"perform": [Function],
|
||||||
"predicate": [Function],
|
|
||||||
"trackEvent": Object {
|
"trackEvent": Object {
|
||||||
"category": "element",
|
"category": "element",
|
||||||
},
|
},
|
||||||
@ -4594,7 +4592,6 @@ Object {
|
|||||||
"keyTest": [Function],
|
"keyTest": [Function],
|
||||||
"name": "flipHorizontal",
|
"name": "flipHorizontal",
|
||||||
"perform": [Function],
|
"perform": [Function],
|
||||||
"predicate": [Function],
|
|
||||||
"trackEvent": Object {
|
"trackEvent": Object {
|
||||||
"category": "element",
|
"category": "element",
|
||||||
},
|
},
|
||||||
@ -4604,7 +4601,6 @@ Object {
|
|||||||
"keyTest": [Function],
|
"keyTest": [Function],
|
||||||
"name": "flipVertical",
|
"name": "flipVertical",
|
||||||
"perform": [Function],
|
"perform": [Function],
|
||||||
"predicate": [Function],
|
|
||||||
"trackEvent": Object {
|
"trackEvent": Object {
|
||||||
"category": "element",
|
"category": "element",
|
||||||
},
|
},
|
||||||
@ -5144,7 +5140,6 @@ Object {
|
|||||||
"keyTest": [Function],
|
"keyTest": [Function],
|
||||||
"name": "flipHorizontal",
|
"name": "flipHorizontal",
|
||||||
"perform": [Function],
|
"perform": [Function],
|
||||||
"predicate": [Function],
|
|
||||||
"trackEvent": Object {
|
"trackEvent": Object {
|
||||||
"category": "element",
|
"category": "element",
|
||||||
},
|
},
|
||||||
@ -5154,7 +5149,6 @@ Object {
|
|||||||
"keyTest": [Function],
|
"keyTest": [Function],
|
||||||
"name": "flipVertical",
|
"name": "flipVertical",
|
||||||
"perform": [Function],
|
"perform": [Function],
|
||||||
"predicate": [Function],
|
|
||||||
"trackEvent": Object {
|
"trackEvent": Object {
|
||||||
"category": "element",
|
"category": "element",
|
||||||
},
|
},
|
||||||
@ -6003,7 +5997,6 @@ Object {
|
|||||||
"keyTest": [Function],
|
"keyTest": [Function],
|
||||||
"name": "flipHorizontal",
|
"name": "flipHorizontal",
|
||||||
"perform": [Function],
|
"perform": [Function],
|
||||||
"predicate": [Function],
|
|
||||||
"trackEvent": Object {
|
"trackEvent": Object {
|
||||||
"category": "element",
|
"category": "element",
|
||||||
},
|
},
|
||||||
@ -6013,7 +6006,6 @@ Object {
|
|||||||
"keyTest": [Function],
|
"keyTest": [Function],
|
||||||
"name": "flipVertical",
|
"name": "flipVertical",
|
||||||
"perform": [Function],
|
"perform": [Function],
|
||||||
"predicate": [Function],
|
|
||||||
"trackEvent": Object {
|
"trackEvent": Object {
|
||||||
"category": "element",
|
"category": "element",
|
||||||
},
|
},
|
||||||
@ -6349,7 +6341,6 @@ Object {
|
|||||||
"keyTest": [Function],
|
"keyTest": [Function],
|
||||||
"name": "flipHorizontal",
|
"name": "flipHorizontal",
|
||||||
"perform": [Function],
|
"perform": [Function],
|
||||||
"predicate": [Function],
|
|
||||||
"trackEvent": Object {
|
"trackEvent": Object {
|
||||||
"category": "element",
|
"category": "element",
|
||||||
},
|
},
|
||||||
@ -6359,7 +6350,6 @@ Object {
|
|||||||
"keyTest": [Function],
|
"keyTest": [Function],
|
||||||
"name": "flipVertical",
|
"name": "flipVertical",
|
||||||
"perform": [Function],
|
"perform": [Function],
|
||||||
"predicate": [Function],
|
|
||||||
"trackEvent": Object {
|
"trackEvent": Object {
|
||||||
"category": "element",
|
"category": "element",
|
||||||
},
|
},
|
||||||
|
@ -15332,7 +15332,10 @@ Object {
|
|||||||
"penMode": false,
|
"penMode": false,
|
||||||
"pendingImageElementId": null,
|
"pendingImageElementId": null,
|
||||||
"previousSelectedElementIds": Object {
|
"previousSelectedElementIds": Object {
|
||||||
|
"id0": true,
|
||||||
|
"id1": true,
|
||||||
"id2": true,
|
"id2": true,
|
||||||
|
"id3": true,
|
||||||
},
|
},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
@ -15342,7 +15345,6 @@ Object {
|
|||||||
"id0": true,
|
"id0": true,
|
||||||
"id1": true,
|
"id1": true,
|
||||||
"id2": true,
|
"id2": true,
|
||||||
"id3": true,
|
|
||||||
"id5": true,
|
"id5": true,
|
||||||
},
|
},
|
||||||
"selectedGroupIds": Object {},
|
"selectedGroupIds": Object {},
|
||||||
@ -15390,7 +15392,7 @@ Object {
|
|||||||
"type": "rectangle",
|
"type": "rectangle",
|
||||||
"updated": 1,
|
"updated": 1,
|
||||||
"version": 4,
|
"version": 4,
|
||||||
"versionNonce": 1505387817,
|
"versionNonce": 23633383,
|
||||||
"width": 10,
|
"width": 10,
|
||||||
"x": 10,
|
"x": 10,
|
||||||
"y": 10,
|
"y": 10,
|
||||||
@ -15421,7 +15423,7 @@ Object {
|
|||||||
"type": "rectangle",
|
"type": "rectangle",
|
||||||
"updated": 1,
|
"updated": 1,
|
||||||
"version": 4,
|
"version": 4,
|
||||||
"versionNonce": 23633383,
|
"versionNonce": 493213705,
|
||||||
"width": 10,
|
"width": 10,
|
||||||
"x": 30,
|
"x": 30,
|
||||||
"y": 10,
|
"y": 10,
|
||||||
@ -15452,7 +15454,7 @@ Object {
|
|||||||
"type": "rectangle",
|
"type": "rectangle",
|
||||||
"updated": 1,
|
"updated": 1,
|
||||||
"version": 4,
|
"version": 4,
|
||||||
"versionNonce": 493213705,
|
"versionNonce": 915032327,
|
||||||
"width": 10,
|
"width": 10,
|
||||||
"x": 50,
|
"x": 50,
|
||||||
"y": 10,
|
"y": 10,
|
||||||
@ -15803,7 +15805,6 @@ Object {
|
|||||||
"id0": true,
|
"id0": true,
|
||||||
"id1": true,
|
"id1": true,
|
||||||
"id2": true,
|
"id2": true,
|
||||||
"id3": true,
|
|
||||||
"id5": true,
|
"id5": true,
|
||||||
},
|
},
|
||||||
"selectedGroupIds": Object {},
|
"selectedGroupIds": Object {},
|
||||||
@ -15833,7 +15834,7 @@ Object {
|
|||||||
"type": "rectangle",
|
"type": "rectangle",
|
||||||
"updated": 1,
|
"updated": 1,
|
||||||
"version": 4,
|
"version": 4,
|
||||||
"versionNonce": 1505387817,
|
"versionNonce": 23633383,
|
||||||
"width": 10,
|
"width": 10,
|
||||||
"x": 10,
|
"x": 10,
|
||||||
"y": 10,
|
"y": 10,
|
||||||
@ -15861,7 +15862,7 @@ Object {
|
|||||||
"type": "rectangle",
|
"type": "rectangle",
|
||||||
"updated": 1,
|
"updated": 1,
|
||||||
"version": 4,
|
"version": 4,
|
||||||
"versionNonce": 23633383,
|
"versionNonce": 493213705,
|
||||||
"width": 10,
|
"width": 10,
|
||||||
"x": 30,
|
"x": 30,
|
||||||
"y": 10,
|
"y": 10,
|
||||||
@ -15889,7 +15890,7 @@ Object {
|
|||||||
"type": "rectangle",
|
"type": "rectangle",
|
||||||
"updated": 1,
|
"updated": 1,
|
||||||
"version": 4,
|
"version": 4,
|
||||||
"versionNonce": 493213705,
|
"versionNonce": 915032327,
|
||||||
"width": 10,
|
"width": 10,
|
||||||
"x": 50,
|
"x": 50,
|
||||||
"y": 10,
|
"y": 10,
|
||||||
|
@ -207,6 +207,8 @@ describe("contextMenu element", () => {
|
|||||||
"deleteSelectedElements",
|
"deleteSelectedElements",
|
||||||
"group",
|
"group",
|
||||||
"addToLibrary",
|
"addToLibrary",
|
||||||
|
"flipHorizontal",
|
||||||
|
"flipVertical",
|
||||||
"sendBackward",
|
"sendBackward",
|
||||||
"bringForward",
|
"bringForward",
|
||||||
"sendToBack",
|
"sendToBack",
|
||||||
@ -258,6 +260,8 @@ describe("contextMenu element", () => {
|
|||||||
"deleteSelectedElements",
|
"deleteSelectedElements",
|
||||||
"ungroup",
|
"ungroup",
|
||||||
"addToLibrary",
|
"addToLibrary",
|
||||||
|
"flipHorizontal",
|
||||||
|
"flipVertical",
|
||||||
"sendBackward",
|
"sendBackward",
|
||||||
"bringForward",
|
"bringForward",
|
||||||
"sendToBack",
|
"sendToBack",
|
||||||
|
@ -195,10 +195,8 @@ const checkElementsBoundingBox = async (
|
|||||||
debugger;
|
debugger;
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
// Check if width and height did not change
|
// Check if width and height did not change
|
||||||
expect(x1 - toleranceInPx <= x12 && x12 <= x1 + toleranceInPx).toBeTruthy();
|
expect(x2 - x1).toBeCloseTo(x22 - x12, -1);
|
||||||
expect(y1 - toleranceInPx <= y12 && y12 <= y1 + toleranceInPx).toBeTruthy();
|
expect(y2 - y1).toBeCloseTo(y22 - y12, -1);
|
||||||
expect(x2 - toleranceInPx <= x22 && x22 <= x2 + toleranceInPx).toBeTruthy();
|
|
||||||
expect(y2 - toleranceInPx <= y22 && y22 <= y2 + toleranceInPx).toBeTruthy();
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -216,14 +214,22 @@ const checkTwoPointsLineHorizontalFlip = async () => {
|
|||||||
h.app.actionManager.executeAction(actionFlipHorizontal);
|
h.app.actionManager.executeAction(actionFlipHorizontal);
|
||||||
const newElement = h.elements[0] as ExcalidrawLinearElement;
|
const newElement = h.elements[0] as ExcalidrawLinearElement;
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(originalElement.points[0][0]).toEqual(
|
expect(originalElement.points[0][0]).toBeCloseTo(
|
||||||
newElement.points[0][0] !== 0 ? -newElement.points[0][0] : 0,
|
-newElement.points[0][0],
|
||||||
|
5,
|
||||||
);
|
);
|
||||||
expect(originalElement.points[0][1]).toEqual(newElement.points[0][1]);
|
expect(originalElement.points[0][1]).toBeCloseTo(
|
||||||
expect(originalElement.points[1][0]).toEqual(
|
newElement.points[0][1],
|
||||||
newElement.points[1][0] !== 0 ? -newElement.points[1][0] : 0,
|
5,
|
||||||
|
);
|
||||||
|
expect(originalElement.points[1][0]).toBeCloseTo(
|
||||||
|
-newElement.points[1][0],
|
||||||
|
5,
|
||||||
|
);
|
||||||
|
expect(originalElement.points[1][1]).toBeCloseTo(
|
||||||
|
newElement.points[1][1],
|
||||||
|
5,
|
||||||
);
|
);
|
||||||
expect(originalElement.points[1][1]).toEqual(newElement.points[1][1]);
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -234,14 +240,22 @@ const checkTwoPointsLineVerticalFlip = async () => {
|
|||||||
h.app.actionManager.executeAction(actionFlipVertical);
|
h.app.actionManager.executeAction(actionFlipVertical);
|
||||||
const newElement = h.elements[0] as ExcalidrawLinearElement;
|
const newElement = h.elements[0] as ExcalidrawLinearElement;
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(originalElement.points[0][0]).toEqual(
|
expect(originalElement.points[0][0]).toBeCloseTo(
|
||||||
newElement.points[0][0] !== 0 ? -newElement.points[0][0] : 0,
|
newElement.points[0][0],
|
||||||
|
5,
|
||||||
);
|
);
|
||||||
expect(originalElement.points[0][1]).toEqual(newElement.points[0][1]);
|
expect(originalElement.points[0][1]).toBeCloseTo(
|
||||||
expect(originalElement.points[1][0]).toEqual(
|
-newElement.points[0][1],
|
||||||
newElement.points[1][0] !== 0 ? -newElement.points[1][0] : 0,
|
5,
|
||||||
|
);
|
||||||
|
expect(originalElement.points[1][0]).toBeCloseTo(
|
||||||
|
newElement.points[1][0],
|
||||||
|
5,
|
||||||
|
);
|
||||||
|
expect(originalElement.points[1][1]).toBeCloseTo(
|
||||||
|
-newElement.points[1][1],
|
||||||
|
5,
|
||||||
);
|
);
|
||||||
expect(originalElement.points[1][1]).toEqual(newElement.points[1][1]);
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -318,7 +332,7 @@ describe("rectangle", () => {
|
|||||||
|
|
||||||
it("flips a rotated rectangle vertically correctly", async () => {
|
it("flips a rotated rectangle vertically correctly", async () => {
|
||||||
const originalAngle = (3 * Math.PI) / 4;
|
const originalAngle = (3 * Math.PI) / 4;
|
||||||
const expectedAgnle = Math.PI / 4;
|
const expectedAgnle = (5 * Math.PI) / 4;
|
||||||
|
|
||||||
createAndSelectOneRectangle(originalAngle);
|
createAndSelectOneRectangle(originalAngle);
|
||||||
|
|
||||||
@ -351,7 +365,7 @@ describe("diamond", () => {
|
|||||||
|
|
||||||
it("flips a rotated diamond vertically correctly", async () => {
|
it("flips a rotated diamond vertically correctly", async () => {
|
||||||
const originalAngle = (5 * Math.PI) / 4;
|
const originalAngle = (5 * Math.PI) / 4;
|
||||||
const expectedAngle = (7 * Math.PI) / 4;
|
const expectedAngle = (3 * Math.PI) / 4;
|
||||||
|
|
||||||
createAndSelectOneDiamond(originalAngle);
|
createAndSelectOneDiamond(originalAngle);
|
||||||
|
|
||||||
@ -384,7 +398,7 @@ describe("ellipse", () => {
|
|||||||
|
|
||||||
it("flips a rotated ellipse vertically correctly", async () => {
|
it("flips a rotated ellipse vertically correctly", async () => {
|
||||||
const originalAngle = (7 * Math.PI) / 4;
|
const originalAngle = (7 * Math.PI) / 4;
|
||||||
const expectedAngle = (5 * Math.PI) / 4;
|
const expectedAngle = Math.PI / 4;
|
||||||
|
|
||||||
createAndSelectOneEllipse(originalAngle);
|
createAndSelectOneEllipse(originalAngle);
|
||||||
|
|
||||||
@ -429,7 +443,7 @@ describe("arrow", () => {
|
|||||||
|
|
||||||
it("flips a rotated arrow vertically with line inside min/max points bounds", async () => {
|
it("flips a rotated arrow vertically with line inside min/max points bounds", async () => {
|
||||||
const originalAngle = Math.PI / 4;
|
const originalAngle = Math.PI / 4;
|
||||||
const expectedAngle = (3 * Math.PI) / 4;
|
const expectedAngle = (7 * Math.PI) / 4;
|
||||||
const line = createLinearElementWithCurveInsideMinMaxPoints("arrow");
|
const line = createLinearElementWithCurveInsideMinMaxPoints("arrow");
|
||||||
h.app.scene.replaceAllElements([line]);
|
h.app.scene.replaceAllElements([line]);
|
||||||
h.app.state.selectedElementIds[line.id] = true;
|
h.app.state.selectedElementIds[line.id] = true;
|
||||||
@ -481,7 +495,7 @@ describe("arrow", () => {
|
|||||||
//TODO: elements with curve outside minMax points have a wrong bounding box!!!
|
//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 () => {
|
it.skip("flips a rotated arrow vertically with line outside min/max points bounds", async () => {
|
||||||
const originalAngle = Math.PI / 4;
|
const originalAngle = Math.PI / 4;
|
||||||
const expectedAngle = (3 * Math.PI) / 4;
|
const expectedAngle = (7 * Math.PI) / 4;
|
||||||
const line = createLinearElementsWithCurveOutsideMinMaxPoints("arrow");
|
const line = createLinearElementsWithCurveOutsideMinMaxPoints("arrow");
|
||||||
mutateElement(line, { angle: originalAngle });
|
mutateElement(line, { angle: originalAngle });
|
||||||
h.app.scene.replaceAllElements([line]);
|
h.app.scene.replaceAllElements([line]);
|
||||||
@ -512,7 +526,6 @@ describe("arrow", () => {
|
|||||||
|
|
||||||
it("flips a two points arrow vertically correctly", async () => {
|
it("flips a two points arrow vertically correctly", async () => {
|
||||||
createAndSelectOneArrow();
|
createAndSelectOneArrow();
|
||||||
|
|
||||||
await checkTwoPointsLineVerticalFlip();
|
await checkTwoPointsLineVerticalFlip();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -581,7 +594,7 @@ describe("line", () => {
|
|||||||
//TODO: elements with curve outside minMax points have a wrong bounding box
|
//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 () => {
|
it.skip("flips a rotated line vertically with line outside min/max points bounds", async () => {
|
||||||
const originalAngle = Math.PI / 4;
|
const originalAngle = Math.PI / 4;
|
||||||
const expectedAngle = (3 * Math.PI) / 4;
|
const expectedAngle = (7 * Math.PI) / 4;
|
||||||
const line = createLinearElementsWithCurveOutsideMinMaxPoints("line");
|
const line = createLinearElementsWithCurveOutsideMinMaxPoints("line");
|
||||||
mutateElement(line, { angle: originalAngle });
|
mutateElement(line, { angle: originalAngle });
|
||||||
h.app.scene.replaceAllElements([line]);
|
h.app.scene.replaceAllElements([line]);
|
||||||
@ -616,7 +629,7 @@ describe("line", () => {
|
|||||||
|
|
||||||
it("flips a rotated line vertically with line inside min/max points bounds", async () => {
|
it("flips a rotated line vertically with line inside min/max points bounds", async () => {
|
||||||
const originalAngle = Math.PI / 4;
|
const originalAngle = Math.PI / 4;
|
||||||
const expectedAngle = (3 * Math.PI) / 4;
|
const expectedAngle = (7 * Math.PI) / 4;
|
||||||
const line = createLinearElementWithCurveInsideMinMaxPoints("line");
|
const line = createLinearElementWithCurveInsideMinMaxPoints("line");
|
||||||
h.app.scene.replaceAllElements([line]);
|
h.app.scene.replaceAllElements([line]);
|
||||||
h.app.state.selectedElementIds[line.id] = true;
|
h.app.state.selectedElementIds[line.id] = true;
|
||||||
@ -670,7 +683,7 @@ describe("freedraw", () => {
|
|||||||
|
|
||||||
it("flips a rotated drawing vertically correctly", async () => {
|
it("flips a rotated drawing vertically correctly", async () => {
|
||||||
const originalAngle = Math.PI / 4;
|
const originalAngle = Math.PI / 4;
|
||||||
const expectedAngle = (3 * Math.PI) / 4;
|
const expectedAngle = (7 * Math.PI) / 4;
|
||||||
|
|
||||||
const draw = createAndReturnOneDraw(originalAngle);
|
const draw = createAndReturnOneDraw(originalAngle);
|
||||||
// select draw, since not done automatically
|
// select draw, since not done automatically
|
||||||
@ -718,8 +731,8 @@ describe("image", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
await checkVerticalFlip();
|
await checkVerticalFlip();
|
||||||
expect((h.elements[0] as ExcalidrawImageElement).scale).toEqual([-1, 1]);
|
expect((h.elements[0] as ExcalidrawImageElement).scale).toEqual([1, -1]);
|
||||||
expect(h.elements[0].angle).toBeCloseTo(Math.PI);
|
expect(h.elements[0].angle).toBeCloseTo(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("flips an rotated image horizontally correctly", async () => {
|
it("flips an rotated image horizontally correctly", async () => {
|
||||||
@ -742,7 +755,7 @@ describe("image", () => {
|
|||||||
|
|
||||||
it("flips an rotated image vertically correctly", async () => {
|
it("flips an rotated image vertically correctly", async () => {
|
||||||
const originalAngle = Math.PI / 4;
|
const originalAngle = Math.PI / 4;
|
||||||
const expectedAngle = (3 * Math.PI) / 4;
|
const expectedAngle = (7 * Math.PI) / 4;
|
||||||
//paste image
|
//paste image
|
||||||
await createImage();
|
await createImage();
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
@ -757,7 +770,7 @@ describe("image", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
await checkRotatedVerticalFlip(expectedAngle);
|
await checkRotatedVerticalFlip(expectedAngle);
|
||||||
expect((h.elements[0] as ExcalidrawImageElement).scale).toEqual([-1, 1]);
|
expect((h.elements[0] as ExcalidrawImageElement).scale).toEqual([1, -1]);
|
||||||
expect(h.elements[0].angle).toBeCloseTo(expectedAngle);
|
expect(h.elements[0].angle).toBeCloseTo(expectedAngle);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -772,7 +785,7 @@ describe("image", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
await checkVerticalHorizontalFlip();
|
await checkVerticalHorizontalFlip();
|
||||||
expect((h.elements[0] as ExcalidrawImageElement).scale).toEqual([1, 1]);
|
expect((h.elements[0] as ExcalidrawImageElement).scale).toEqual([-1, -1]);
|
||||||
expect(h.elements[0].angle).toBeCloseTo(Math.PI);
|
expect(h.elements[0].angle).toBeCloseTo(0);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -542,7 +542,7 @@ describe("regression tests", () => {
|
|||||||
expect(element.groupIds.length).toBe(1);
|
expect(element.groupIds.length).toBe(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
mouse.reset();
|
mouse.moveTo(-10, -10); // the NW resizing handle is at [0, 0], so moving further
|
||||||
mouse.down();
|
mouse.down();
|
||||||
mouse.restorePosition(...end);
|
mouse.restorePosition(...end);
|
||||||
mouse.up();
|
mouse.up();
|
||||||
|
Loading…
Reference in New Issue
Block a user