fix: connector should remain the same when its target or source switch to other

shape type (#11369)

Fixes [BS-1431](https://linear.app/affine-design/issue/BS-1431/改变-shape-形状后,connector-丢失)
This commit is contained in:
doouding
2025-04-02 05:10:30 +00:00
parent c20514f269
commit ef76c83184
4 changed files with 118 additions and 19 deletions
@@ -1319,27 +1319,31 @@ export class ConnectorPathGenerator extends PathGenerator {
if (source.id) {
const startTangentVertical = Vec.rot(startPoint.tangent, -Math.PI / 2);
startPoint.out = Vec.mul(
startTangentVertical,
Math.max(
100,
Math.abs(
Vec.pry(Vec.sub(endPoint, startPoint), startTangentVertical)
) / 3
)
);
startPoint.out = isVecZero(startTangentVertical)
? Vec.mul(Vec.per(Vec.normalize(Vec.sub(startPoint, endPoint))), 20)
: Vec.mul(
startTangentVertical,
Math.max(
100,
Math.abs(
Vec.pry(Vec.sub(endPoint, startPoint), startTangentVertical)
) / 3
)
);
}
if (target.id) {
const endTangentVertical = Vec.rot(endPoint.tangent, -Math.PI / 2);
endPoint.in = Vec.mul(
endTangentVertical,
Math.max(
100,
Math.abs(
Vec.pry(Vec.sub(startPoint, endPoint), endTangentVertical)
) / 3
)
);
endPoint.in = isVecZero(endTangentVertical)
? Vec.mul(Vec.per(Vec.normalize(Vec.sub(endPoint, startPoint))), 20)
: Vec.mul(
endTangentVertical,
Math.max(
100,
Math.abs(
Vec.pry(Vec.sub(startPoint, endPoint), endTangentVertical)
) / 3
)
);
}
return [startPoint, endPoint];
} else {
@@ -141,7 +141,7 @@ export const shapeToolbarConfig = {
return renderMenu({
icon: ShapeIcon(),
label: 'Switch type',
label: 'Switch shape type',
items: ShapeComponentConfig.map(item => ({
key: item.tooltip,
value: item.name,
@@ -9,12 +9,17 @@ import {
createShapeElement,
dragBetweenViewCoords,
edgelessCommonSetup as commonSetup,
getConnectorPath,
getConnectorPathWithInOut,
pickColorAtPoints,
rotateElementByHandle,
selectElementInEdgeless,
setEdgelessTool,
Shape,
toModelCoord,
toViewCoord,
triggerComponentToolbarAction,
triggerShapeSwitch,
} from '../../utils/actions/edgeless.js';
import { pressBackspace, waitNextFrame } from '../../utils/actions/index.js';
import {
@@ -320,4 +325,56 @@ test.describe('quick connect', () => {
await page.mouse.move(...point);
await assertConnectorPath(page, [[50, 100], endpoint]);
});
test('the triangle connectors should remain the same when switch to other shape', async ({
page,
}) => {
await commonSetup(page);
const shape1Id = await createShapeElement(
page,
[0, 0],
[100, 100],
Shape.Triangle
);
const shape2Id = await createShapeElement(
page,
[200, 0],
[300, 100],
Shape.Triangle
);
await setEdgelessTool(page, 'connector');
await dragBetweenViewCoords(page, [60, 50], [240, 50]);
{
const path = await getConnectorPath(page);
// make sure the connector is created
expect(path.length).toBeGreaterThan(0);
}
// switch to other shape
await selectElementInEdgeless(page, [shape1Id]);
await triggerShapeSwitch(page, 'Square');
await selectElementInEdgeless(page, [shape2Id]);
await triggerShapeSwitch(page, 'Square');
await dragBetweenViewCoords(page, [50, 50], [0, 0]);
await dragBetweenViewCoords(page, [250, 50], [300, 50]);
{
const path = await getConnectorPathWithInOut(page);
expect(path.length).toBeGreaterThan(0);
path.forEach(point => {
[0, 1].forEach(i => {
expect(point.in[i]).not.toBeNaN();
expect(point.out[i]).not.toBeNaN();
expect(point.point[i]).not.toBeNaN();
});
});
}
});
});
@@ -1082,6 +1082,19 @@ type Action =
| 'autoArrange'
| 'autoResize';
export async function triggerShapeSwitch(
page: Page,
type: 'Square' | 'Ellipse' | 'Diamond' | 'Triangle' | 'Rounded rectangle'
) {
const button = locatorComponentToolbar(page)
.getByLabel('Switch shape type')
.first();
await button.click();
const shapeButton = locatorComponentToolbar(page).getByLabel(type);
await shapeButton.click();
}
export async function triggerComponentToolbarAction(
page: Page,
action: Action
@@ -1598,6 +1611,31 @@ export async function getConnectorPath(page: Page, index = 0): Promise<IVec[]> {
);
}
export async function getConnectorPathWithInOut(
page: Page,
index = 0
): Promise<
{
point: IVec;
in: IVec;
out: IVec;
}[]
> {
return page.evaluate(
([index]) => {
const container = document.querySelector('affine-edgeless-root');
if (!container) throw new Error('container not found');
const connectors = container.service.crud.getElementsByType('connector');
return connectors[index].absolutePath.map(path => ({
point: [path[0], path[1]],
in: path.in,
out: path.out,
}));
},
[index]
);
}
export async function getEdgelessElementBound(
page: Page,
elementId: string