Skip to content

Commit

Permalink
refactor(edgeless): adjust logic of dragging selection (#8842)
Browse files Browse the repository at this point in the history
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
  • Loading branch information
L-Sun committed Dec 4, 2024
1 parent 87e0271 commit 41ce5a9
Show file tree
Hide file tree
Showing 6 changed files with 97 additions and 41 deletions.
29 changes: 24 additions & 5 deletions packages/blocks/src/root-block/edgeless/gfx-tool/default-tool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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,
Expand All @@ -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;
}
Expand All @@ -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(
Expand Down
4 changes: 2 additions & 2 deletions tests/edgeless/align.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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]);

Expand Down Expand Up @@ -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]);

Expand Down
16 changes: 11 additions & 5 deletions tests/edgeless/frame/clipboard.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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

Expand Down
29 changes: 12 additions & 17 deletions tests/edgeless/frame/frame-mindmap.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,32 +132,27 @@ 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);

// 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);
Expand Down
40 changes: 30 additions & 10 deletions tests/edgeless/frame/frame.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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]);
Expand All @@ -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]);
Expand All @@ -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]);
Expand All @@ -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]);
Expand All @@ -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]);
Expand All @@ -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]);
Expand All @@ -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]);
Expand All @@ -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]);
Expand All @@ -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]);
Expand All @@ -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);

Expand Down
20 changes: 18 additions & 2 deletions tests/edgeless/frame/selection.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import {
zoomResetByKeyboard,
} from 'utils/actions/edgeless.js';
import {
pressBackspace,
pressEnter,
pressEscape,
selectAllByKeyboard,
type,
Expand Down Expand Up @@ -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 }) => {
Expand Down

0 comments on commit 41ce5a9

Please sign in to comment.