feat: resize elements from the sides (#7855)

Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
This commit is contained in:
Ryan Di 2024-05-01 00:05:00 +08:00 committed by GitHub
parent 6e5aeb112d
commit 88812e0386
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
19 changed files with 3913 additions and 3741 deletions

View file

@ -2170,14 +2170,14 @@ exports[`contextMenu element > selecting 'Delete' in context menu deletes elemen
"roundness": {
"type": 3,
},
"seed": 1278240551,
"seed": 449462985,
"strokeColor": "#1e1e1e",
"strokeStyle": "solid",
"strokeWidth": 2,
"type": "rectangle",
"updated": 1,
"version": 4,
"versionNonce": 1150084233,
"versionNonce": 1014066025,
"width": 20,
"x": -10,
"y": 0,
@ -2404,14 +2404,14 @@ exports[`contextMenu element > selecting 'Duplicate' in context menu duplicates
"roundness": {
"type": 3,
},
"seed": 1278240551,
"seed": 449462985,
"strokeColor": "#1e1e1e",
"strokeStyle": "solid",
"strokeWidth": 2,
"type": "rectangle",
"updated": 1,
"version": 3,
"versionNonce": 401146281,
"versionNonce": 1150084233,
"width": 20,
"x": -10,
"y": 0,
@ -2438,14 +2438,14 @@ exports[`contextMenu element > selecting 'Duplicate' in context menu duplicates
"roundness": {
"type": 3,
},
"seed": 1150084233,
"seed": 1014066025,
"strokeColor": "#1e1e1e",
"strokeStyle": "solid",
"strokeWidth": 2,
"type": "rectangle",
"updated": 1,
"version": 4,
"versionNonce": 1116226695,
"versionNonce": 238820263,
"width": 20,
"x": 0,
"y": 10,
@ -2704,14 +2704,14 @@ exports[`contextMenu element > selecting 'Group selection' in context menu group
"roundness": {
"type": 3,
},
"seed": 1278240551,
"seed": 449462985,
"strokeColor": "#1e1e1e",
"strokeStyle": "solid",
"strokeWidth": 2,
"type": "rectangle",
"updated": 1,
"version": 4,
"versionNonce": 1505387817,
"versionNonce": 493213705,
"width": 20,
"x": -10,
"y": 0,
@ -2740,14 +2740,14 @@ exports[`contextMenu element > selecting 'Group selection' in context menu group
"roundness": {
"type": 3,
},
"seed": 1150084233,
"seed": 1014066025,
"strokeColor": "#1e1e1e",
"strokeStyle": "solid",
"strokeWidth": 2,
"type": "rectangle",
"updated": 1,
"version": 4,
"versionNonce": 23633383,
"versionNonce": 915032327,
"width": 20,
"x": 20,
"y": 30,
@ -3060,14 +3060,14 @@ exports[`contextMenu element > selecting 'Paste styles' in context menu pastes s
"roundness": {
"type": 3,
},
"seed": 1278240551,
"seed": 449462985,
"strokeColor": "#e03131",
"strokeStyle": "dotted",
"strokeWidth": 2,
"type": "rectangle",
"updated": 1,
"version": 4,
"versionNonce": 640725609,
"versionNonce": 941653321,
"width": 20,
"x": -10,
"y": 0,
@ -3094,14 +3094,14 @@ exports[`contextMenu element > selecting 'Paste styles' in context menu pastes s
"roundness": {
"type": 3,
},
"seed": 760410951,
"seed": 289600103,
"strokeColor": "#e03131",
"strokeStyle": "dotted",
"strokeWidth": 2,
"type": "rectangle",
"updated": 1,
"version": 9,
"versionNonce": 1315507081,
"versionNonce": 640725609,
"width": 20,
"x": 20,
"y": 30,
@ -3840,14 +3840,14 @@ exports[`contextMenu element > selecting 'Send to back' in context menu sends el
"roundness": {
"type": 3,
},
"seed": 1150084233,
"seed": 1014066025,
"strokeColor": "#1e1e1e",
"strokeStyle": "solid",
"strokeWidth": 2,
"type": "rectangle",
"updated": 1,
"version": 4,
"versionNonce": 1604849351,
"versionNonce": 23633383,
"width": 20,
"x": 20,
"y": 30,
@ -3874,14 +3874,14 @@ exports[`contextMenu element > selecting 'Send to back' in context menu sends el
"roundness": {
"type": 3,
},
"seed": 1278240551,
"seed": 449462985,
"strokeColor": "#1e1e1e",
"strokeStyle": "solid",
"strokeWidth": 2,
"type": "rectangle",
"updated": 1,
"version": 3,
"versionNonce": 401146281,
"versionNonce": 1150084233,
"width": 20,
"x": -10,
"y": 0,
@ -5224,8 +5224,8 @@ exports[`contextMenu element > shows 'Group selection' in context menu for multi
},
},
],
"left": -19,
"top": -9,
"left": -17,
"top": -7,
},
"currentChartType": "bar",
"currentItemBackgroundColor": "transparent",
@ -5342,14 +5342,14 @@ exports[`contextMenu element > shows 'Group selection' in context menu for multi
"roundness": {
"type": 3,
},
"seed": 449462985,
"seed": 453191,
"strokeColor": "#1e1e1e",
"strokeStyle": "solid",
"strokeWidth": 2,
"type": "rectangle",
"updated": 1,
"version": 3,
"versionNonce": 1150084233,
"versionNonce": 1014066025,
"width": 10,
"x": -10,
"y": 0,
@ -5376,16 +5376,16 @@ exports[`contextMenu element > shows 'Group selection' in context menu for multi
"roundness": {
"type": 3,
},
"seed": 1014066025,
"seed": 400692809,
"strokeColor": "#1e1e1e",
"strokeStyle": "solid",
"strokeWidth": 2,
"type": "rectangle",
"updated": 1,
"version": 3,
"versionNonce": 1604849351,
"versionNonce": 23633383,
"width": 10,
"x": 10,
"x": 12,
"y": 0,
}
`;
@ -5493,7 +5493,7 @@ History {
"strokeWidth": 2,
"type": "rectangle",
"width": 10,
"x": 10,
"x": 12,
"y": 0,
},
"inserted": {
@ -6349,8 +6349,8 @@ exports[`contextMenu element > shows 'Ungroup selection' in context menu for gro
},
},
],
"left": -19,
"top": -9,
"left": -17,
"top": -7,
},
"currentChartType": "bar",
"currentItemBackgroundColor": "transparent",
@ -6516,7 +6516,7 @@ exports[`contextMenu element > shows 'Ungroup selection' in context menu for gro
"version": 4,
"versionNonce": 747212839,
"width": 10,
"x": 10,
"x": 12,
"y": 0,
}
`;
@ -6624,7 +6624,7 @@ History {
"strokeWidth": 2,
"type": "rectangle",
"width": 10,
"x": 10,
"x": 12,
"y": 0,
},
"inserted": {
@ -8181,8 +8181,8 @@ exports[`contextMenu element > shows context menu for element > [end of test] ap
},
},
],
"left": -19,
"top": -9,
"left": -17,
"top": -7,
},
"currentChartType": "bar",
"currentItemBackgroundColor": "transparent",

View file

@ -1400,9 +1400,7 @@ exports[`regression tests > Drags selected element when hitting only bounding bo
"penDetected": false,
"penMode": false,
"pendingImageElementId": null,
"previousSelectedElementIds": {
"id0": true,
},
"previousSelectedElementIds": {},
"resizingElement": null,
"scrollX": 0,
"scrollY": 0,
@ -1522,7 +1520,7 @@ History {
exports[`regression tests > Drags selected element when hitting only bounding box and keeps element selected > [end of test] number of elements 1`] = `0`;
exports[`regression tests > Drags selected element when hitting only bounding box and keeps element selected > [end of test] number of renders 1`] = `9`;
exports[`regression tests > Drags selected element when hitting only bounding box and keeps element selected > [end of test] number of renders 1`] = `11`;
exports[`regression tests > adjusts z order when grouping > [end of test] appState 1`] = `
{

View file

@ -45,6 +45,7 @@ describe("element binding", () => {
mouse.downAt(100, 0);
mouse.moveTo(55, 0);
mouse.up(0, 0);
expect(API.getSelectedElements()).toEqual([arrow]);
expect(arrow.startBinding).toEqual({
elementId: rect.id,
focus: expect.toBeNonNaNNumber(),

View file

@ -108,8 +108,8 @@ describe("contextMenu element", () => {
fireEvent.contextMenu(GlobalTestState.interactiveCanvas, {
button: 2,
clientX: 1,
clientY: 1,
clientX: 3,
clientY: 3,
});
const contextMenu = UI.queryContextMenu();
const contextMenuOptions =
@ -188,19 +188,19 @@ describe("contextMenu element", () => {
mouse.up(10, 10);
UI.clickTool("rectangle");
mouse.down(10, -10);
mouse.down(12, -10);
mouse.up(10, 10);
mouse.reset();
mouse.click(10, 10);
Keyboard.withModifierKeys({ shift: true }, () => {
mouse.click(20, 0);
mouse.click(22, 0);
});
fireEvent.contextMenu(GlobalTestState.interactiveCanvas, {
button: 2,
clientX: 1,
clientY: 1,
clientX: 3,
clientY: 3,
});
const contextMenu = UI.queryContextMenu();
@ -240,13 +240,13 @@ describe("contextMenu element", () => {
mouse.up(10, 10);
UI.clickTool("rectangle");
mouse.down(10, -10);
mouse.down(12, -10);
mouse.up(10, 10);
mouse.reset();
mouse.click(10, 10);
Keyboard.withModifierKeys({ shift: true }, () => {
mouse.click(20, 0);
mouse.click(22, 0);
});
Keyboard.withModifierKeys({ ctrl: true }, () => {
@ -255,8 +255,8 @@ describe("contextMenu element", () => {
fireEvent.contextMenu(GlobalTestState.interactiveCanvas, {
button: 2,
clientX: 1,
clientY: 1,
clientX: 3,
clientY: 3,
});
const contextMenu = UI.queryContextMenu();
@ -297,8 +297,8 @@ describe("contextMenu element", () => {
fireEvent.contextMenu(GlobalTestState.interactiveCanvas, {
button: 2,
clientX: 1,
clientY: 1,
clientX: 3,
clientY: 3,
});
const contextMenu = UI.queryContextMenu();
expect(copiedStyles).toBe("{}");
@ -382,8 +382,8 @@ describe("contextMenu element", () => {
fireEvent.contextMenu(GlobalTestState.interactiveCanvas, {
button: 2,
clientX: 1,
clientY: 1,
clientX: 3,
clientY: 3,
});
const contextMenu = UI.queryContextMenu();
fireEvent.click(queryAllByText(contextMenu!, "Delete")[0]);
@ -398,8 +398,8 @@ describe("contextMenu element", () => {
fireEvent.contextMenu(GlobalTestState.interactiveCanvas, {
button: 2,
clientX: 1,
clientY: 1,
clientX: 3,
clientY: 3,
});
const contextMenu = UI.queryContextMenu();
fireEvent.click(queryByText(contextMenu!, "Add to library")!);
@ -417,8 +417,8 @@ describe("contextMenu element", () => {
fireEvent.contextMenu(GlobalTestState.interactiveCanvas, {
button: 2,
clientX: 1,
clientY: 1,
clientX: 3,
clientY: 3,
});
const contextMenu = UI.queryContextMenu();
fireEvent.click(queryByText(contextMenu!, "Duplicate")!);
@ -548,8 +548,8 @@ describe("contextMenu element", () => {
fireEvent.contextMenu(GlobalTestState.interactiveCanvas, {
button: 2,
clientX: 1,
clientY: 1,
clientX: 3,
clientY: 3,
});
const contextMenu = UI.queryContextMenu();
fireEvent.click(queryByText(contextMenu!, "Group selection")!);
@ -578,8 +578,8 @@ describe("contextMenu element", () => {
fireEvent.contextMenu(GlobalTestState.interactiveCanvas, {
button: 2,
clientX: 1,
clientY: 1,
clientX: 3,
clientY: 3,
});
const contextMenu = UI.queryContextMenu();

View file

@ -315,6 +315,7 @@ const transform = (
h.state.zoom,
arrayToMap(h.elements),
"mouse",
{},
)[handle];
} else {
const [x1, y1, x2, y2] = getCommonBounds(elements);

View file

@ -199,7 +199,6 @@ describe("regression tests", () => {
expect(
h.elements.filter((element) => element.type === "rectangle").length,
).toBe(1);
Keyboard.withModifierKeys({ alt: true }, () => {
mouse.down(-8, -8);
mouse.up(10, 10);
@ -725,7 +724,7 @@ describe("regression tests", () => {
mouse.up(10, 10);
const { x: prevX, y: prevY } = API.getSelectedElement();
API.clearSelection();
// drag element from point on bounding box that doesn't hit element
mouse.reset();
mouse.down(8, 8);
@ -1015,12 +1014,22 @@ describe("regression tests", () => {
});
it("single-clicking on a subgroup of a selected group should not alter selection", () => {
const rect1 = UI.createElement("rectangle", { x: 10 });
const rect2 = UI.createElement("rectangle", { x: 50 });
const rect1 = UI.createElement("rectangle", {
x: 10,
});
const rect2 = UI.createElement("rectangle", {
x: 50,
});
UI.group([rect1, rect2]);
const rect3 = UI.createElement("rectangle", { x: 10, y: 50 });
const rect4 = UI.createElement("rectangle", { x: 50, y: 50 });
const rect3 = UI.createElement("rectangle", {
x: 10,
y: 50,
});
const rect4 = UI.createElement("rectangle", {
x: 50,
y: 50,
});
UI.group([rect3, rect4]);
Keyboard.withModifierKeys({ ctrl: true }, () => {
@ -1079,8 +1088,9 @@ describe("regression tests", () => {
UI.group([rect1, rect3]);
assertSelectedElements(rect1, rect2, rect3);
mouse.reset();
Keyboard.withModifierKeys({ ctrl: true }, () => {
mouse.clickOn(rect1);
mouse.click(10, 5);
});
assertSelectedElements(rect1);

View file

@ -544,7 +544,9 @@ describe("multiple selection", () => {
1 + move[1] / selectionHeight,
);
UI.resize([rectangle, diamond, ellipse], "se", move);
UI.resize([rectangle, diamond, ellipse], "se", move, {
shift: true,
});
expect(rectangle.x).toBeCloseTo(0);
expect(rectangle.y).toBeCloseTo(0);
@ -613,7 +615,9 @@ describe("multiple selection", () => {
1 + move[1] / selectionHeight,
);
UI.resize([line, freedraw], "se", move);
UI.resize([line, freedraw], "se", move, {
shift: true,
});
expect(line.x).toBeCloseTo(60 * scale);
expect(line.y).toBeCloseTo(40 * scale);
@ -653,7 +657,9 @@ describe("multiple selection", () => {
1 - move[1] / selectionHeight,
);
UI.resize([horizLine, vertLine, diagLine], "nw", move);
UI.resize([horizLine, vertLine, diagLine], "nw", move, {
shift: true,
});
expect(horizLine.x).toBeCloseTo(selectionWidth * (1 - scale));
expect(horizLine.y).toBeCloseTo(selectionHeight * (1 - scale));
@ -703,7 +709,9 @@ describe("multiple selection", () => {
const rightArrowBinding = { ...rightBoundArrow.endBinding };
delete rightArrowBinding.gap;
UI.resize([rectangle, rightBoundArrow], "nw", move);
UI.resize([rectangle, rightBoundArrow], "nw", move, {
shift: true,
});
expect(leftBoundArrow.x).toBeCloseTo(-110);
expect(leftBoundArrow.y).toBeCloseTo(50);
@ -751,7 +759,9 @@ describe("multiple selection", () => {
const move = [80, 0] as [number, number];
const scale = move[0] / selectionWidth + 1;
const elementsMap = arrayToMap(h.elements);
UI.resize([topArrow.get(), bottomArrow.get()], "se", move);
UI.resize([topArrow.get(), bottomArrow.get()], "se", move, {
shift: true,
});
const topArrowLabelPos = LinearElementEditor.getBoundTextElementPosition(
topArrow,
topArrowLabel,
@ -815,7 +825,7 @@ describe("multiple selection", () => {
1 - move[1] / selectionHeight,
);
UI.resize([topText, bottomText], "ne", move);
UI.resize([topText, bottomText], "ne", move, { shift: true });
expect(topText.x).toBeCloseTo(0);
expect(topText.y).toBeCloseTo(-selectionHeight * (scale - 1));
@ -828,7 +838,7 @@ describe("multiple selection", () => {
expect(bottomText.angle).toEqual(0);
});
it("resizes with images", () => {
it("resizes with images (proportional)", () => {
const topImage = API.createElement({
type: "image",
x: 0,
@ -891,7 +901,7 @@ describe("multiple selection", () => {
1 + (2 * move[1]) / selectionHeight,
);
UI.resize([rectangle, ellipse], "se", move, { alt: true });
UI.resize([rectangle, ellipse], "se", move, { shift: true, alt: true });
expect(rectangle.x).toBeCloseTo(-200 * scale);
expect(rectangle.y).toBeCloseTo(-140 * scale);
@ -954,7 +964,9 @@ describe("multiple selection", () => {
const scaleY = -scaleX;
const lineOrigBounds = getBoundsFromPoints(line);
const elementsMap = arrayToMap(h.elements);
UI.resize([line, image, rectangle, boundArrow], "se", move);
UI.resize([line, image, rectangle, boundArrow], "se", move, {
shift: true,
});
const lineNewBounds = getBoundsFromPoints(line);
const arrowLabelPos = LinearElementEditor.getBoundTextElementPosition(
boundArrow,
@ -979,7 +991,7 @@ describe("multiple selection", () => {
expect(image.width).toBeCloseTo(100 * -scaleX);
expect(image.height).toBeCloseTo(100 * scaleY);
expect(image.angle).toBeCloseTo((Math.PI * 5) / 6);
expect(image.scale).toEqual([1, 1]);
expect(image.scale).toEqual([-1, 1]);
expect(rectangle.x).toBeCloseTo((180 + 160) * scaleX);
expect(rectangle.y).toBeCloseTo(60 * scaleY);