diff --git a/blocksuite/affine/blocks/block-surface/src/managers/connector-manager.ts b/blocksuite/affine/blocks/block-surface/src/managers/connector-manager.ts index 81c8320644..69b35c9b9c 100644 --- a/blocksuite/affine/blocks/block-surface/src/managers/connector-manager.ts +++ b/blocksuite/affine/blocks/block-surface/src/managers/connector-manager.ts @@ -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 { diff --git a/blocksuite/affine/gfx/shape/src/toolbar/config.ts b/blocksuite/affine/gfx/shape/src/toolbar/config.ts index 118470e712..2c769afb83 100644 --- a/blocksuite/affine/gfx/shape/src/toolbar/config.ts +++ b/blocksuite/affine/gfx/shape/src/toolbar/config.ts @@ -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, diff --git a/tests/blocksuite/e2e/edgeless/connector/connector.spec.ts b/tests/blocksuite/e2e/edgeless/connector/connector.spec.ts index 9205d0ee41..723069f02b 100644 --- a/tests/blocksuite/e2e/edgeless/connector/connector.spec.ts +++ b/tests/blocksuite/e2e/edgeless/connector/connector.spec.ts @@ -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(); + }); + }); + } + }); }); diff --git a/tests/blocksuite/e2e/utils/actions/edgeless.ts b/tests/blocksuite/e2e/utils/actions/edgeless.ts index 1a45bf2a72..2055734bb8 100644 --- a/tests/blocksuite/e2e/utils/actions/edgeless.ts +++ b/tests/blocksuite/e2e/utils/actions/edgeless.ts @@ -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 { ); } +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