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:
@@ -1319,27 +1319,31 @@ export class ConnectorPathGenerator extends PathGenerator {
|
|||||||
|
|
||||||
if (source.id) {
|
if (source.id) {
|
||||||
const startTangentVertical = Vec.rot(startPoint.tangent, -Math.PI / 2);
|
const startTangentVertical = Vec.rot(startPoint.tangent, -Math.PI / 2);
|
||||||
startPoint.out = Vec.mul(
|
startPoint.out = isVecZero(startTangentVertical)
|
||||||
startTangentVertical,
|
? Vec.mul(Vec.per(Vec.normalize(Vec.sub(startPoint, endPoint))), 20)
|
||||||
Math.max(
|
: Vec.mul(
|
||||||
100,
|
startTangentVertical,
|
||||||
Math.abs(
|
Math.max(
|
||||||
Vec.pry(Vec.sub(endPoint, startPoint), startTangentVertical)
|
100,
|
||||||
) / 3
|
Math.abs(
|
||||||
)
|
Vec.pry(Vec.sub(endPoint, startPoint), startTangentVertical)
|
||||||
);
|
) / 3
|
||||||
|
)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if (target.id) {
|
if (target.id) {
|
||||||
const endTangentVertical = Vec.rot(endPoint.tangent, -Math.PI / 2);
|
const endTangentVertical = Vec.rot(endPoint.tangent, -Math.PI / 2);
|
||||||
endPoint.in = Vec.mul(
|
endPoint.in = isVecZero(endTangentVertical)
|
||||||
endTangentVertical,
|
? Vec.mul(Vec.per(Vec.normalize(Vec.sub(endPoint, startPoint))), 20)
|
||||||
Math.max(
|
: Vec.mul(
|
||||||
100,
|
endTangentVertical,
|
||||||
Math.abs(
|
Math.max(
|
||||||
Vec.pry(Vec.sub(startPoint, endPoint), endTangentVertical)
|
100,
|
||||||
) / 3
|
Math.abs(
|
||||||
)
|
Vec.pry(Vec.sub(startPoint, endPoint), endTangentVertical)
|
||||||
);
|
) / 3
|
||||||
|
)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return [startPoint, endPoint];
|
return [startPoint, endPoint];
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -141,7 +141,7 @@ export const shapeToolbarConfig = {
|
|||||||
|
|
||||||
return renderMenu({
|
return renderMenu({
|
||||||
icon: ShapeIcon(),
|
icon: ShapeIcon(),
|
||||||
label: 'Switch type',
|
label: 'Switch shape type',
|
||||||
items: ShapeComponentConfig.map(item => ({
|
items: ShapeComponentConfig.map(item => ({
|
||||||
key: item.tooltip,
|
key: item.tooltip,
|
||||||
value: item.name,
|
value: item.name,
|
||||||
|
|||||||
@@ -9,12 +9,17 @@ import {
|
|||||||
createShapeElement,
|
createShapeElement,
|
||||||
dragBetweenViewCoords,
|
dragBetweenViewCoords,
|
||||||
edgelessCommonSetup as commonSetup,
|
edgelessCommonSetup as commonSetup,
|
||||||
|
getConnectorPath,
|
||||||
|
getConnectorPathWithInOut,
|
||||||
pickColorAtPoints,
|
pickColorAtPoints,
|
||||||
rotateElementByHandle,
|
rotateElementByHandle,
|
||||||
|
selectElementInEdgeless,
|
||||||
|
setEdgelessTool,
|
||||||
Shape,
|
Shape,
|
||||||
toModelCoord,
|
toModelCoord,
|
||||||
toViewCoord,
|
toViewCoord,
|
||||||
triggerComponentToolbarAction,
|
triggerComponentToolbarAction,
|
||||||
|
triggerShapeSwitch,
|
||||||
} from '../../utils/actions/edgeless.js';
|
} from '../../utils/actions/edgeless.js';
|
||||||
import { pressBackspace, waitNextFrame } from '../../utils/actions/index.js';
|
import { pressBackspace, waitNextFrame } from '../../utils/actions/index.js';
|
||||||
import {
|
import {
|
||||||
@@ -320,4 +325,56 @@ test.describe('quick connect', () => {
|
|||||||
await page.mouse.move(...point);
|
await page.mouse.move(...point);
|
||||||
await assertConnectorPath(page, [[50, 100], endpoint]);
|
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'
|
| 'autoArrange'
|
||||||
| 'autoResize';
|
| '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(
|
export async function triggerComponentToolbarAction(
|
||||||
page: Page,
|
page: Page,
|
||||||
action: Action
|
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(
|
export async function getEdgelessElementBound(
|
||||||
page: Page,
|
page: Page,
|
||||||
elementId: string
|
elementId: string
|
||||||
|
|||||||
Reference in New Issue
Block a user