From 41ce5a9937aa1f53c552f6269e9fbabfb0f44be3 Mon Sep 17 00:00:00 2001 From: L-Sun Date: Wed, 4 Dec 2024 08:57:29 +0000 Subject: [PATCH] refactor(edgeless): adjust logic of dragging selection (#8842) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Close [BS-2023](https://linear.app/affine-design/issue/BS-2032/[improvement]-框选行为) ### What Changed - Refined clicking selection on frames: - If a frame has a title, it can only be selected by clicking the title. - If a frame does not have a title, it can be selected by clicking anywhere within the frame area. - Refined dragging selection on frames: - If a frame is empty, it can be selected if the dragging rectangle intersects with the frame. - If a frame is not empty, it can be selected only if the dragging rectangle contains the entire frame. - Refined dragging selection on mindmaps: - A single node can be selected. - The entire mindmap can be selected only if the dragging rectangle contains the entire mindmap. - Tests updated https://github.com/user-attachments/assets/e3414945-93f9-411a-87f2-c8a3dc033c1e --- .../edgeless/gfx-tool/default-tool.ts | 29 +++++++++++--- tests/edgeless/align.spec.ts | 4 +- tests/edgeless/frame/clipboard.spec.ts | 16 +++++--- tests/edgeless/frame/frame-mindmap.spec.ts | 29 ++++++-------- tests/edgeless/frame/frame.spec.ts | 40 ++++++++++++++----- tests/edgeless/frame/selection.spec.ts | 20 +++++++++- 6 files changed, 97 insertions(+), 41 deletions(-) diff --git a/packages/blocks/src/root-block/edgeless/gfx-tool/default-tool.ts b/packages/blocks/src/root-block/edgeless/gfx-tool/default-tool.ts index 9daf67db5fc6..edc8cc1274d6 100644 --- a/packages/blocks/src/root-block/edgeless/gfx-tool/default-tool.ts +++ b/packages/blocks/src/root-block/edgeless/gfx-tool/default-tool.ts @@ -199,7 +199,17 @@ export class DefaultTool extends BaseTool { const { x, y, w, h } = this.controller.draggingArea$.peek(); const bound = new Bound(x, y, w, h); - const elements = getTopElements(gfx.getElementsByBound(bound)); + let elements = gfx.getElementsByBound(bound).filter(el => { + if (isFrameBlock(el)) { + return el.childElements.length === 0 || bound.contains(el.elementBound); + } + if (el instanceof MindmapElementModel) { + return bound.contains(el.elementBound); + } + return true; + }); + + elements = getTopElements(elements); const set = new Set( gfx.keyboard.shiftKey$.peek() @@ -484,9 +494,13 @@ export class DefaultTool extends BaseTool { if (frameByPickingTitle) return frameByPickingTitle; - const group = this.gfx.getElementInGroup(modelPos[0], modelPos[1], options); + const result = this.gfx.getElementInGroup( + modelPos[0], + modelPos[1], + options + ); - if (group instanceof MindmapElementModel) { + if (result instanceof MindmapElementModel) { const picked = this.gfx.getElementByPoint(modelPos[0], modelPos[1], { ...((options ?? {}) as PointTestOptions), all: true, @@ -496,7 +510,7 @@ export class DefaultTool extends BaseTool { while (pickedIdx >= 0) { const element = picked[pickedIdx]; - if (element === group) { + if (element === result) { pickedIdx -= 1; continue; } @@ -507,7 +521,12 @@ export class DefaultTool extends BaseTool { return picked[pickedIdx] ?? null; } - return group; + // if the frame has title, it only can be picked by clicking the title + if (isFrameBlock(result) && result.externalXYWH) { + return null; + } + + return result; } private _scheduleUpdate( diff --git a/tests/edgeless/align.spec.ts b/tests/edgeless/align.spec.ts index d5e2167a30ea..45b9a9936d80 100644 --- a/tests/edgeless/align.spec.ts +++ b/tests/edgeless/align.spec.ts @@ -164,7 +164,7 @@ test.describe('auto arrange align', () => { await page.mouse.click(0, 0); await page.mouse.move(75, 395); await page.mouse.down(); - await page.mouse.move(650, 880); + await page.mouse.move(900, 900); await page.mouse.up(); await assertEdgelessSelectedModelRect(page, [0, 0, 550, 450]); @@ -369,7 +369,7 @@ test.describe('auto resize align', () => { await page.mouse.click(0, 0); await page.mouse.move(75, 395); await page.mouse.down(); - await page.mouse.move(650, 880); + await page.mouse.move(900, 900); await page.mouse.up(); await assertEdgelessSelectedModelRect(page, [0, 0, 550, 450]); diff --git a/tests/edgeless/frame/clipboard.spec.ts b/tests/edgeless/frame/clipboard.spec.ts index 72dbdc60aec0..c229c919d82d 100644 --- a/tests/edgeless/frame/clipboard.spec.ts +++ b/tests/edgeless/frame/clipboard.spec.ts @@ -47,8 +47,10 @@ test.describe('frame copy and paste', () => { await createFrame(page, [50, 50], [450, 450]); await createShapeElement(page, [200, 200], [300, 300], Shape.Square); + const frameTitle = page.locator('affine-frame-title'); + await pressEscape(page); - await clickView(page, [60, 60]); + await frameTitle.click(); await copyByKeyboard(page); await deleteAll(page); await moveView(page, [500, 500]); // center copy @@ -68,18 +70,20 @@ test.describe('frame copy and paste', () => { await createShapeElement(page, [300, 300], [400, 400], Shape.Square); await pressEscape(page); + const frameTitles = page.locator('affine-frame-title'); + await shiftClickView(page, [260, 260]); await shiftClickView(page, [310, 310]); await triggerComponentToolbarAction(page, 'addGroup'); await pressEscape(page); - await clickView(page, [60, 60]); + await frameTitles.nth(0).click(); await page.keyboard.down('Alt'); await dragBetweenViewCoords(page, [60, 60], [460, 460]); await page.keyboard.up('Alt'); await pressEscape(page); - await shiftClickView(page, [60, 60]); + await frameTitles.nth(0).click({ modifiers: ['Shift'] }); await shiftClickView(page, [250, 250]); await shiftClickView(page, [350, 350]); await pressBackspace(page); // remove original elements @@ -101,12 +105,14 @@ test.describe('frame copy and paste', () => { await createShapeElement(page, [100, 100], [200, 200], Shape.Square); await pressEscape(page); - await clickView(page, [60, 60]); + const frameTitles = page.locator('affine-frame-title'); + + await frameTitles.nth(0).click(); await page.locator('edgeless-more-button').click(); await page.locator('editor-menu-action', { hasText: 'Duplicate' }).click(); await pressEscape(page); - await shiftClickView(page, [60, 60]); + await frameTitles.nth(0).click(); await shiftClickView(page, [150, 150]); await pressBackspace(page); // remove original elements diff --git a/tests/edgeless/frame/frame-mindmap.spec.ts b/tests/edgeless/frame/frame-mindmap.spec.ts index cdfa9e01338d..6777fd0eef30 100644 --- a/tests/edgeless/frame/frame-mindmap.spec.ts +++ b/tests/edgeless/frame/frame-mindmap.spec.ts @@ -132,16 +132,14 @@ test('drag whole mindmap into frame, then drag root node of mindmap out.', async // drag in { const mindmapBound = await getSelectedBound(page); - await dragBetweenViewCoords( - page, - [mindmapBound[0] - 10, mindmapBound[1] - 10], - [mindmapBound[0] + 10, mindmapBound[1] + 10] - ); - await dragBetweenViewCoords( - page, - [mindmapBound[0] + 10, mindmapBound[1] + 10], - [100, 100] - ); + const rootNodePos = [ + mindmapBound[0] + 10, + mindmapBound[1] + 0.5 * mindmapBound[3], + ]; + await dragBetweenViewCoords(page, rootNodePos, [ + rootNodePos[0] - 20, + rootNodePos[1] + 200, + ]); } await assertContainerOfElements(page, [mindmapId], frameId); @@ -149,15 +147,12 @@ test('drag whole mindmap into frame, then drag root node of mindmap out.', async // drag out { const mindmapBound = await getSelectedBound(page); - await clickView(page, [ + const rootNodePos = [ mindmapBound[0] + 10, mindmapBound[1] + 0.5 * mindmapBound[3], - ]); - await dragBetweenViewCoords( - page, - [mindmapBound[0] + 10, mindmapBound[1] + 0.5 * mindmapBound[3]], - [0, 0] - ); + ]; + + await dragBetweenViewCoords(page, rootNodePos, [-100, -100]); } await assertContainerOfElements(page, [mindmapId], null); diff --git a/tests/edgeless/frame/frame.spec.ts b/tests/edgeless/frame/frame.spec.ts index 59f77cd065a8..9699a7c43c38 100644 --- a/tests/edgeless/frame/frame.spec.ts +++ b/tests/edgeless/frame/frame.spec.ts @@ -142,9 +142,11 @@ test.describe('add element to frame and then move frame', () => { const noteCoord = await toViewCoord(page, [200, 200]); const noteId = await addNote(page, '', noteCoord[0], noteCoord[1]); + const frameTitle = page.locator('affine-frame-title'); + await pressEscape(page); - await clickView(page, [60, 60]); + await frameTitle.click(); await dragBetweenViewCoords(page, [60, 60], [110, 110]); await assertEdgelessElementBound(page, shapeId, [150, 150, 100, 100]); @@ -169,7 +171,9 @@ test.describe('add element to frame and then move frame', () => { ); await pressEscape(page); - await clickView(page, [60, 60]); + const frameTitle = page.locator('affine-frame-title'); + + await frameTitle.click(); await dragBetweenViewCoords(page, [60, 60], [110, 110]); await assertEdgelessElementBound(page, shapeId, [500, 500, 100, 100]); @@ -195,13 +199,15 @@ test.describe('add element to frame and then move frame', () => { ]; await pressEscape(page); + const frameTitle = page.locator('affine-frame-title'); + await shiftClickView(page, [110, 110]); await shiftClickView(page, [160, 160]); await page.keyboard.press(`${SHORT_KEY}+g`); const groupId = (await getSelectedIds(page))[0]; await pressEscape(page); - await clickView(page, [60, 60]); + await frameTitle.click(); await dragBetweenViewCoords(page, [60, 60], [110, 110]); await assertEdgelessElementBound(page, shapeIds[0], [150, 150, 100, 100]); @@ -220,13 +226,15 @@ test.describe('add element to frame and then move frame', () => { ]; await pressEscape(page); + const frameTitle = page.locator('affine-frame-title'); + await shiftClickView(page, [460, 460]); await shiftClickView(page, [510, 510]); await page.keyboard.press(`${SHORT_KEY}+g`); const groupId = (await getSelectedIds(page))[0]; await pressEscape(page); - await clickView(page, [60, 60]); + await frameTitle.click(); await dragBetweenViewCoords(page, [60, 60], [110, 110]); await assertEdgelessElementBound(page, shapeIds[0], [500, 500, 100, 100]); @@ -247,7 +255,9 @@ test.describe('add element to frame and then move frame', () => { ]; await pressEscape(page); - await clickView(page, [60, 60]); + const frameTitles = page.locator('affine-frame-title'); + + await frameTitles.nth(0).click(); await dragBetweenViewCoords(page, [60, 60], [110, 110]); await assertEdgelessElementBound(page, shapeId, [200, 200, 100, 100]); @@ -265,7 +275,9 @@ test.describe('add element to frame and then move frame', () => { ]; await pressEscape(page); - await clickView(page, [60, 60]); + const frameTitles = page.locator('affine-frame-title'); + + await frameTitles.nth(0).click(); await dragBetweenViewCoords(page, [60, 60], [110, 110]); await assertEdgelessElementBound(page, shapeId, [600, 600, 50, 50]); @@ -282,7 +294,9 @@ test.describe('add element to frame and then move frame', () => { await createShapeElement(page, [550, 550], [600, 600], Shape.Square), ]; - await clickView(page, [60, 60]); + const frameTitles = page.locator('affine-frame-title'); + + await frameTitles.nth(0).click(); await dragBetweenViewCoords(page, [60, 60], [110, 110]); await assertEdgelessElementBound(page, shapeId, [600, 600, 50, 50]); @@ -300,7 +314,9 @@ test.describe('resize frame then move ', () => { ]; await pressEscape(page); - await clickView(page, [60, 60]); + const frameTitle = page.locator('affine-frame-title'); + + await frameTitle.click(); await dragBetweenViewCoords(page, [150, 150], [450, 450]); await dragBetweenViewCoords(page, [60, 60], [110, 110]); @@ -316,7 +332,9 @@ test.describe('resize frame then move ', () => { ]; await pressEscape(page); - await clickView(page, [60, 60]); + const frameTitle = page.locator('affine-frame-title'); + + await frameTitle.click(); await dragBetweenViewCoords(page, [450, 450], [150, 150]); await dragBetweenViewCoords(page, [60, 60], [110, 110]); @@ -331,7 +349,9 @@ test('delete frame', async ({ page }) => { await createShapeElement(page, [200, 200], [300, 300], Shape.Square); await pressEscape(page); - await clickView(page, [60, 60]); + const frameTitle = page.locator('affine-frame-title'); + + await frameTitle.click(); await pressBackspace(page); await expect(page.locator('affine-frame')).toHaveCount(0); diff --git a/tests/edgeless/frame/selection.spec.ts b/tests/edgeless/frame/selection.spec.ts index 12674e5bbef6..1d4576ec90bd 100644 --- a/tests/edgeless/frame/selection.spec.ts +++ b/tests/edgeless/frame/selection.spec.ts @@ -15,6 +15,8 @@ import { zoomResetByKeyboard, } from 'utils/actions/edgeless.js'; import { + pressBackspace, + pressEnter, pressEscape, selectAllByKeyboard, type, @@ -44,16 +46,30 @@ test.beforeEach(async ({ page }) => { }); test.describe('frame selection', () => { - test('frame can be selected by click blank area of frame', async ({ + test('frame can not be selected by click blank area of frame if it has title', async ({ page, }) => { await createFrame(page, [50, 50], [150, 150]); await pressEscape(page); expect(await getSelectedBoundCount(page)).toBe(0); + await clickView(page, [100, 100]); + expect(await getSelectedBoundCount(page)).toBe(0); + }); + + test('frame can selected by click blank area of frame if it has not title', async ({ + page, + }) => { + await createFrame(page, [50, 50], [150, 150]); + await pressEscape(page); + expect(await getSelectedBoundCount(page)).toBe(0); + + await page.locator('affine-frame-title').dblclick(); + await pressBackspace(page); + await pressEnter(page); + await clickView(page, [100, 100]); expect(await getSelectedBoundCount(page)).toBe(1); - await assertSelectedBound(page, [50, 50, 100, 100]); }); test('frame can be selected by click frame title', async ({ page }) => {