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:
Alex Kim
2023-05-25 19:27:41 +05:00
committed by GitHub
parent 75bea48b54
commit 6459ccda6a
7 changed files with 240 additions and 282 deletions

View File

@ -195,10 +195,8 @@ const checkElementsBoundingBox = async (
debugger;
await waitFor(() => {
// Check if width and height did not change
expect(x1 - toleranceInPx <= x12 && x12 <= x1 + toleranceInPx).toBeTruthy();
expect(y1 - toleranceInPx <= y12 && y12 <= y1 + toleranceInPx).toBeTruthy();
expect(x2 - toleranceInPx <= x22 && x22 <= x2 + toleranceInPx).toBeTruthy();
expect(y2 - toleranceInPx <= y22 && y22 <= y2 + toleranceInPx).toBeTruthy();
expect(x2 - x1).toBeCloseTo(x22 - x12, -1);
expect(y2 - y1).toBeCloseTo(y22 - y12, -1);
});
};
@ -216,14 +214,22 @@ const checkTwoPointsLineHorizontalFlip = async () => {
h.app.actionManager.executeAction(actionFlipHorizontal);
const newElement = h.elements[0] as ExcalidrawLinearElement;
await waitFor(() => {
expect(originalElement.points[0][0]).toEqual(
newElement.points[0][0] !== 0 ? -newElement.points[0][0] : 0,
expect(originalElement.points[0][0]).toBeCloseTo(
-newElement.points[0][0],
5,
);
expect(originalElement.points[0][1]).toEqual(newElement.points[0][1]);
expect(originalElement.points[1][0]).toEqual(
newElement.points[1][0] !== 0 ? -newElement.points[1][0] : 0,
expect(originalElement.points[0][1]).toBeCloseTo(
newElement.points[0][1],
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);
const newElement = h.elements[0] as ExcalidrawLinearElement;
await waitFor(() => {
expect(originalElement.points[0][0]).toEqual(
newElement.points[0][0] !== 0 ? -newElement.points[0][0] : 0,
expect(originalElement.points[0][0]).toBeCloseTo(
newElement.points[0][0],
5,
);
expect(originalElement.points[0][1]).toEqual(newElement.points[0][1]);
expect(originalElement.points[1][0]).toEqual(
newElement.points[1][0] !== 0 ? -newElement.points[1][0] : 0,
expect(originalElement.points[0][1]).toBeCloseTo(
-newElement.points[0][1],
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 () => {
const originalAngle = (3 * Math.PI) / 4;
const expectedAgnle = Math.PI / 4;
const expectedAgnle = (5 * Math.PI) / 4;
createAndSelectOneRectangle(originalAngle);
@ -351,7 +365,7 @@ describe("diamond", () => {
it("flips a rotated diamond vertically correctly", async () => {
const originalAngle = (5 * Math.PI) / 4;
const expectedAngle = (7 * Math.PI) / 4;
const expectedAngle = (3 * Math.PI) / 4;
createAndSelectOneDiamond(originalAngle);
@ -384,7 +398,7 @@ describe("ellipse", () => {
it("flips a rotated ellipse vertically correctly", async () => {
const originalAngle = (7 * Math.PI) / 4;
const expectedAngle = (5 * Math.PI) / 4;
const expectedAngle = Math.PI / 4;
createAndSelectOneEllipse(originalAngle);
@ -429,7 +443,7 @@ describe("arrow", () => {
it("flips a rotated arrow vertically with line inside min/max points bounds", async () => {
const originalAngle = Math.PI / 4;
const expectedAngle = (3 * Math.PI) / 4;
const expectedAngle = (7 * Math.PI) / 4;
const line = createLinearElementWithCurveInsideMinMaxPoints("arrow");
h.app.scene.replaceAllElements([line]);
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!!!
it.skip("flips a rotated arrow vertically with line outside min/max points bounds", async () => {
const originalAngle = Math.PI / 4;
const expectedAngle = (3 * Math.PI) / 4;
const expectedAngle = (7 * Math.PI) / 4;
const line = createLinearElementsWithCurveOutsideMinMaxPoints("arrow");
mutateElement(line, { angle: originalAngle });
h.app.scene.replaceAllElements([line]);
@ -512,7 +526,6 @@ describe("arrow", () => {
it("flips a two points arrow vertically correctly", async () => {
createAndSelectOneArrow();
await checkTwoPointsLineVerticalFlip();
});
});
@ -581,7 +594,7 @@ describe("line", () => {
//TODO: elements with curve outside minMax points have a wrong bounding box
it.skip("flips a rotated line vertically with line outside min/max points bounds", async () => {
const originalAngle = Math.PI / 4;
const expectedAngle = (3 * Math.PI) / 4;
const expectedAngle = (7 * Math.PI) / 4;
const line = createLinearElementsWithCurveOutsideMinMaxPoints("line");
mutateElement(line, { angle: originalAngle });
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 () => {
const originalAngle = Math.PI / 4;
const expectedAngle = (3 * Math.PI) / 4;
const expectedAngle = (7 * Math.PI) / 4;
const line = createLinearElementWithCurveInsideMinMaxPoints("line");
h.app.scene.replaceAllElements([line]);
h.app.state.selectedElementIds[line.id] = true;
@ -670,7 +683,7 @@ describe("freedraw", () => {
it("flips a rotated drawing vertically correctly", async () => {
const originalAngle = Math.PI / 4;
const expectedAngle = (3 * Math.PI) / 4;
const expectedAngle = (7 * Math.PI) / 4;
const draw = createAndReturnOneDraw(originalAngle);
// select draw, since not done automatically
@ -718,8 +731,8 @@ describe("image", () => {
});
await checkVerticalFlip();
expect((h.elements[0] as ExcalidrawImageElement).scale).toEqual([-1, 1]);
expect(h.elements[0].angle).toBeCloseTo(Math.PI);
expect((h.elements[0] as ExcalidrawImageElement).scale).toEqual([1, -1]);
expect(h.elements[0].angle).toBeCloseTo(0);
});
it("flips an rotated image horizontally correctly", async () => {
@ -742,7 +755,7 @@ describe("image", () => {
it("flips an rotated image vertically correctly", async () => {
const originalAngle = Math.PI / 4;
const expectedAngle = (3 * Math.PI) / 4;
const expectedAngle = (7 * Math.PI) / 4;
//paste image
await createImage();
await waitFor(() => {
@ -757,7 +770,7 @@ describe("image", () => {
});
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);
});
@ -772,7 +785,7 @@ describe("image", () => {
});
await checkVerticalHorizontalFlip();
expect((h.elements[0] as ExcalidrawImageElement).scale).toEqual([1, 1]);
expect(h.elements[0].angle).toBeCloseTo(Math.PI);
expect((h.elements[0] as ExcalidrawImageElement).scale).toEqual([-1, -1]);
expect(h.elements[0].angle).toBeCloseTo(0);
});
});