mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-08-02 09:59:55 +08:00
fix(editor): connector not added as frame child (#12611)
Co-authored-by: L-Sun <zover.v@gmail.com>
This commit is contained in:
@@ -241,20 +241,35 @@ export class EdgelessFrameManager extends GfxExtension {
|
|||||||
surfaceModel.elementAdded.subscribe(({ id, local }) => {
|
surfaceModel.elementAdded.subscribe(({ id, local }) => {
|
||||||
const element = surfaceModel.getElementById(id);
|
const element = surfaceModel.getElementById(id);
|
||||||
if (element && local) {
|
if (element && local) {
|
||||||
const frame = this.getFrameFromPoint(element.elementBound.center);
|
// The entire frame detection logic must be in microtask for timing reasons:
|
||||||
|
//
|
||||||
// if the container created with a frame, skip it.
|
// 1. For connectors: When elementAdded fires, connectors have invalid bounds [0,0,0,0]
|
||||||
if (
|
// because their path/bounds are calculated in a separate microtask of updateConnectorPath by connector-watcher.
|
||||||
isGfxGroupCompatibleModel(element) &&
|
// We need to wait for that calculation to complete before frame detection.
|
||||||
frame &&
|
//
|
||||||
element.hasChild(frame)
|
// 2. For shapes: Although they have valid bounds immediately, processing them in microtask
|
||||||
) {
|
// ensures consistent timing and allows other initialization to complete first.
|
||||||
return;
|
//
|
||||||
}
|
// 3. Group compatibility: Some elements may need to establish their group relationships
|
||||||
|
// before being considered for frame membership.
|
||||||
// new element may intended to be added to other group
|
//
|
||||||
// so we need to wait for the next microtask to check if the element can be added to the frame
|
// By embedding the entire logic in microtask, we ensure:
|
||||||
|
// - Connectors have proper bounds calculated (not [0,0,0,0])
|
||||||
|
// - getFrameFromPoint() works correctly with valid element centers
|
||||||
|
// - All element initialization is complete before frame detection
|
||||||
queueMicrotask(() => {
|
queueMicrotask(() => {
|
||||||
|
const frame = this.getFrameFromPoint(element.elementBound.center);
|
||||||
|
|
||||||
|
// if the container created with a frame, skip it.
|
||||||
|
if (
|
||||||
|
isGfxGroupCompatibleModel(element) &&
|
||||||
|
frame &&
|
||||||
|
element.hasChild(frame)
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only add elements that aren't already grouped and have a valid frame
|
||||||
if (!element.group && frame) {
|
if (!element.group && frame) {
|
||||||
this.addElementsToFrame(frame, [element]);
|
this.addElementsToFrame(frame, [element]);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ describe('basic', () => {
|
|||||||
xywh: '[100, 0, 100, 100]',
|
xywh: '[100, 0, 100, 100]',
|
||||||
index: service.generateIndex(),
|
index: service.generateIndex(),
|
||||||
})!;
|
})!;
|
||||||
|
await wait(0); // wait next frame
|
||||||
frameId = service.crud.addBlock(
|
frameId = service.crud.addBlock(
|
||||||
'affine:frame',
|
'affine:frame',
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -478,3 +478,46 @@ test('undo/redo should work when change frame background', async ({ page }) => {
|
|||||||
expect(await getFrameBackground()).not.toBe(prevBackground);
|
expect(await getFrameBackground()).not.toBe(prevBackground);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('connector and shape created simultaneously with edgeless-auto-complete should both be added to frame', async ({
|
||||||
|
page,
|
||||||
|
}) => {
|
||||||
|
// Create a larger frame to ensure everything fits
|
||||||
|
const frameId = await createFrame(page, [50, 50], [650, 650]);
|
||||||
|
|
||||||
|
// Create first shape inside the frame (well within bounds)
|
||||||
|
const shape1Id = await createShapeElement(
|
||||||
|
page,
|
||||||
|
[150, 150],
|
||||||
|
[250, 250],
|
||||||
|
Shape.Square
|
||||||
|
);
|
||||||
|
|
||||||
|
// Click on the existing shape to start connection
|
||||||
|
await clickView(page, [200, 200]);
|
||||||
|
|
||||||
|
const autoComplete = page.locator('edgeless-auto-complete');
|
||||||
|
const rightArrowButton = autoComplete
|
||||||
|
.locator('.edgeless-auto-complete-arrow')
|
||||||
|
.nth(0);
|
||||||
|
|
||||||
|
await rightArrowButton.click();
|
||||||
|
|
||||||
|
// Wait for async processing
|
||||||
|
await waitNextFrame(page);
|
||||||
|
|
||||||
|
// Verify that the frame contains 3 children: original shape + new shape + connector
|
||||||
|
await assertContainerChildCount(page, frameId, 3);
|
||||||
|
|
||||||
|
// Verify by moving the frame - all elements should move together
|
||||||
|
const frameTitle = page.locator('affine-frame-title');
|
||||||
|
await frameTitle.click();
|
||||||
|
await dragBetweenViewCoords(page, [60, 60], [110, 110]);
|
||||||
|
|
||||||
|
// Check that the original shape moved with the frame
|
||||||
|
await assertEdgelessElementBound(page, shape1Id, [200, 200, 100, 100]);
|
||||||
|
await assertEdgelessElementBound(page, frameId, [100, 100, 600, 600]);
|
||||||
|
|
||||||
|
// Frame should still contain all 3 elements after the move
|
||||||
|
await assertContainerChildCount(page, frameId, 3);
|
||||||
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user