mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-23 09:17:06 +08:00
chore(editor): improve selection of doc in canvas (#11314)
Close [BS-2705](https://linear.app/affine-design/issue/BS-2705/[improvement]-通过-viability-control-选择-hide-in-edgeless) This PR disabled selecting operation of notes that are only shown in page mode.
This commit is contained in:
@@ -10,6 +10,8 @@ import {
|
|||||||
type ConnectorElementModel,
|
type ConnectorElementModel,
|
||||||
GroupElementModel,
|
GroupElementModel,
|
||||||
MindmapElementModel,
|
MindmapElementModel,
|
||||||
|
NoteBlockModel,
|
||||||
|
NoteDisplayMode,
|
||||||
} from '@blocksuite/affine-model';
|
} from '@blocksuite/affine-model';
|
||||||
import { resetNativeSelection } from '@blocksuite/affine-shared/utils';
|
import { resetNativeSelection } from '@blocksuite/affine-shared/utils';
|
||||||
import { DisposableGroup } from '@blocksuite/global/disposable';
|
import { DisposableGroup } from '@blocksuite/global/disposable';
|
||||||
@@ -141,6 +143,12 @@ export class DefaultTool extends BaseTool {
|
|||||||
if (el instanceof MindmapElementModel) {
|
if (el instanceof MindmapElementModel) {
|
||||||
return bound.contains(el.elementBound);
|
return bound.contains(el.elementBound);
|
||||||
}
|
}
|
||||||
|
if (
|
||||||
|
el instanceof NoteBlockModel &&
|
||||||
|
el.props.displayMode === NoteDisplayMode.DocOnly
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,13 @@ import {
|
|||||||
import { GfxControllerIdentifier } from '@blocksuite/std/gfx';
|
import { GfxControllerIdentifier } from '@blocksuite/std/gfx';
|
||||||
import type { BlockModel } from '@blocksuite/store';
|
import type { BlockModel } from '@blocksuite/store';
|
||||||
import { consume } from '@lit/context';
|
import { consume } from '@lit/context';
|
||||||
import { effect, signal } from '@preact/signals-core';
|
import {
|
||||||
|
batch,
|
||||||
|
computed,
|
||||||
|
effect,
|
||||||
|
type Signal,
|
||||||
|
signal,
|
||||||
|
} from '@preact/signals-core';
|
||||||
import { html, nothing } from 'lit';
|
import { html, nothing } from 'lit';
|
||||||
import { query } from 'lit/decorators.js';
|
import { query } from 'lit/decorators.js';
|
||||||
import { classMap } from 'lit/directives/class-map.js';
|
import { classMap } from 'lit/directives/class-map.js';
|
||||||
@@ -53,7 +59,22 @@ export class OutlinePanelBody extends SignalWatcher(
|
|||||||
|
|
||||||
private readonly _edgelessOnlyNotes$ = signal<NoteBlockModel[]>([]);
|
private readonly _edgelessOnlyNotes$ = signal<NoteBlockModel[]>([]);
|
||||||
|
|
||||||
private readonly _selectedNotes$ = signal<NoteBlockModel[]>([]);
|
private readonly _selectedNotes$: Record<
|
||||||
|
NoteDisplayMode,
|
||||||
|
Signal<NoteBlockModel[]>
|
||||||
|
> = {
|
||||||
|
[NoteDisplayMode.DocOnly]: signal<NoteBlockModel[]>([]),
|
||||||
|
[NoteDisplayMode.DocAndEdgeless]: signal<NoteBlockModel[]>([]),
|
||||||
|
[NoteDisplayMode.EdgelessOnly]: signal<NoteBlockModel[]>([]),
|
||||||
|
};
|
||||||
|
|
||||||
|
private readonly _allSelectedNotes$ = computed(() =>
|
||||||
|
[
|
||||||
|
NoteDisplayMode.DocAndEdgeless,
|
||||||
|
NoteDisplayMode.DocOnly,
|
||||||
|
NoteDisplayMode.EdgelessOnly,
|
||||||
|
].flatMap(mode => this._selectedNotes$[mode].value)
|
||||||
|
);
|
||||||
|
|
||||||
private _clearHighlightMask = () => {};
|
private _clearHighlightMask = () => {};
|
||||||
|
|
||||||
@@ -136,7 +157,7 @@ export class OutlinePanelBody extends SignalWatcher(
|
|||||||
if (!this.doc.root) return;
|
if (!this.doc.root) return;
|
||||||
|
|
||||||
const pageVisibleNotes = this._pageVisibleNotes$.peek();
|
const pageVisibleNotes = this._pageVisibleNotes$.peek();
|
||||||
const selected = this._selectedNotes$.peek();
|
const selected = this._allSelectedNotes$.peek();
|
||||||
const children = this.doc.root.children.slice();
|
const children = this.doc.root.children.slice();
|
||||||
|
|
||||||
const noteIndex = new Map<NoteBlockModel, number>();
|
const noteIndex = new Map<NoteBlockModel, number>();
|
||||||
@@ -203,23 +224,39 @@ export class OutlinePanelBody extends SignalWatcher(
|
|||||||
const note = this.doc.getBlock(id)?.model;
|
const note = this.doc.getBlock(id)?.model;
|
||||||
if (!note || !matchModels(note, [NoteBlockModel])) return;
|
if (!note || !matchModels(note, [NoteBlockModel])) return;
|
||||||
|
|
||||||
let selectedNotes = this._selectedNotes$.peek();
|
// map from signal to value
|
||||||
|
const selectedNotes = Object.fromEntries(
|
||||||
|
Object.entries(this._selectedNotes$).map(([k, v]) => [k, v.peek()])
|
||||||
|
) as Record<NoteDisplayMode, NoteBlockModel[]>;
|
||||||
|
|
||||||
if (!selected) {
|
if (multiselect) {
|
||||||
selectedNotes = selectedNotes.filter(_note => _note !== note);
|
selectedNotes[note.props.displayMode] = selected
|
||||||
} else if (multiselect) {
|
? [...selectedNotes[note.props.displayMode], note]
|
||||||
selectedNotes = [...selectedNotes, note];
|
: selectedNotes[note.props.displayMode].filter(_note => _note !== note);
|
||||||
} else {
|
} else {
|
||||||
selectedNotes = [note];
|
selectedNotes[note.props.displayMode] = selected ? [note] : [];
|
||||||
|
Object.keys(this._selectedNotes$).forEach(mode => {
|
||||||
|
if (mode !== note.props.displayMode) {
|
||||||
|
selectedNotes[mode as NoteDisplayMode] = [];
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// We use gfx.selection and effect to keep sync between canvas and outline panel
|
||||||
if (editorMode === 'edgeless') {
|
if (editorMode === 'edgeless') {
|
||||||
gfx.selection.set({
|
gfx.selection.set({
|
||||||
elements: selectedNotes.map(({ id }) => id),
|
elements: [...selectedNotes.both, ...selectedNotes.edgeless].map(
|
||||||
|
({ id }) => id
|
||||||
|
),
|
||||||
editing: false,
|
editing: false,
|
||||||
});
|
});
|
||||||
|
this._selectedNotes$.doc.value = selectedNotes.doc;
|
||||||
} else {
|
} else {
|
||||||
this._selectedNotes$.value = selectedNotes;
|
[NoteDisplayMode.DocOnly, NoteDisplayMode.DocAndEdgeless].forEach(
|
||||||
|
mode => {
|
||||||
|
this._selectedNotes$[mode].value = selectedNotes[mode];
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -237,13 +274,16 @@ export class OutlinePanelBody extends SignalWatcher(
|
|||||||
return !!model && matchModels(model, [NoteBlockModel]);
|
return !!model && matchModels(model, [NoteBlockModel]);
|
||||||
});
|
});
|
||||||
|
|
||||||
const preSelected = this._selectedNotes$.peek();
|
// update selected notes from edgeless selection
|
||||||
if (
|
batch(() => {
|
||||||
preSelected.length !== currSelectedNotes.length ||
|
[NoteDisplayMode.DocAndEdgeless, NoteDisplayMode.EdgelessOnly].forEach(
|
||||||
preSelected.some(note => !currSelectedNotes.includes(note))
|
mode => {
|
||||||
) {
|
this._selectedNotes$[mode].value = currSelectedNotes.filter(
|
||||||
this._selectedNotes$.value = currSelectedNotes;
|
note => note.props.displayMode === mode
|
||||||
}
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -280,11 +320,6 @@ export class OutlinePanelBody extends SignalWatcher(
|
|||||||
std.dnd.monitor<NoteCardEntity, NoteDropPayload>({
|
std.dnd.monitor<NoteCardEntity, NoteDropPayload>({
|
||||||
onDragStart: () => {
|
onDragStart: () => {
|
||||||
this._dragging$.value = true;
|
this._dragging$.value = true;
|
||||||
this._selectedNotes$.value = this._selectedNotes$
|
|
||||||
.peek()
|
|
||||||
.filter(note => {
|
|
||||||
return this._pageVisibleNotes$.value.includes(note);
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
onDrag: data => {
|
onDrag: data => {
|
||||||
const target = data.location.current.dropTargets[0];
|
const target = data.location.current.dropTargets[0];
|
||||||
@@ -377,7 +412,7 @@ export class OutlinePanelBody extends SignalWatcher(
|
|||||||
index=${index}
|
index=${index}
|
||||||
.note=${note}
|
.note=${note}
|
||||||
.activeHeadingId=${this._activeHeadingId$.value}
|
.activeHeadingId=${this._activeHeadingId$.value}
|
||||||
.status=${this._selectedNotes$.value.includes(note)
|
.status=${this._allSelectedNotes$.value.includes(note)
|
||||||
? this._dragging$.value
|
? this._dragging$.value
|
||||||
? 'dragging'
|
? 'dragging'
|
||||||
: 'selected'
|
: 'selected'
|
||||||
|
|||||||
@@ -320,9 +320,10 @@ test.describe('TOC and edgeless selection', () => {
|
|||||||
expect(await getEdgelessSelectedIds(page)).toHaveLength(0);
|
expect(await getEdgelessSelectedIds(page)).toHaveLength(0);
|
||||||
|
|
||||||
await cards.nth(1).click();
|
await cards.nth(1).click();
|
||||||
expect(await getEdgelessSelectedIds(page)).toHaveLength(1);
|
expect(
|
||||||
await cards.nth(1).click();
|
await getEdgelessSelectedIds(page),
|
||||||
expect(await getEdgelessSelectedIds(page)).toHaveLength(0);
|
'should not select doc only note'
|
||||||
|
).toHaveLength(0);
|
||||||
|
|
||||||
await cards.nth(2).click();
|
await cards.nth(2).click();
|
||||||
expect(await getEdgelessSelectedIds(page)).toHaveLength(1);
|
expect(await getEdgelessSelectedIds(page)).toHaveLength(1);
|
||||||
@@ -333,7 +334,7 @@ test.describe('TOC and edgeless selection', () => {
|
|||||||
await cards.nth(1).click({ modifiers: ['Shift'] });
|
await cards.nth(1).click({ modifiers: ['Shift'] });
|
||||||
await cards.nth(2).click({ modifiers: ['Shift'] });
|
await cards.nth(2).click({ modifiers: ['Shift'] });
|
||||||
|
|
||||||
expect(await getEdgelessSelectedIds(page)).toHaveLength(3);
|
expect(await getEdgelessSelectedIds(page)).toHaveLength(2);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should select note cards when select note blocks in canvas', async ({
|
test('should select note cards when select note blocks in canvas', async ({
|
||||||
|
|||||||
@@ -7,6 +7,9 @@ import {
|
|||||||
assertEdgelessTool,
|
assertEdgelessTool,
|
||||||
changeEdgelessNoteBackground,
|
changeEdgelessNoteBackground,
|
||||||
changeNoteDisplayMode,
|
changeNoteDisplayMode,
|
||||||
|
dragBetweenViewCoords,
|
||||||
|
getSelectedBound,
|
||||||
|
getSelectedBoundCount,
|
||||||
locatorComponentToolbar,
|
locatorComponentToolbar,
|
||||||
locatorEdgelessZoomToolButton,
|
locatorEdgelessZoomToolButton,
|
||||||
selectNoteInEdgeless,
|
selectNoteInEdgeless,
|
||||||
@@ -19,6 +22,7 @@ import {
|
|||||||
import {
|
import {
|
||||||
click,
|
click,
|
||||||
clickBlockById,
|
clickBlockById,
|
||||||
|
clickView,
|
||||||
dragBetweenCoords,
|
dragBetweenCoords,
|
||||||
dragBetweenIndices,
|
dragBetweenIndices,
|
||||||
enterPlaygroundRoom,
|
enterPlaygroundRoom,
|
||||||
@@ -31,6 +35,7 @@ import {
|
|||||||
pressBackspace,
|
pressBackspace,
|
||||||
pressEnter,
|
pressEnter,
|
||||||
pressTab,
|
pressTab,
|
||||||
|
selectAllByKeyboard,
|
||||||
type,
|
type,
|
||||||
undoByKeyboard,
|
undoByKeyboard,
|
||||||
waitForInlineEditorStateUpdated,
|
waitForInlineEditorStateUpdated,
|
||||||
@@ -541,3 +546,33 @@ test('select text cross blocks in edgeless note', async ({ page }) => {
|
|||||||
await pressBackspace(page);
|
await pressBackspace(page);
|
||||||
await assertRichTexts(page, ['ac']);
|
await assertRichTexts(page, ['ac']);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('should not select doc only note', async ({ page }) => {
|
||||||
|
await enterPlaygroundRoom(page);
|
||||||
|
await initEmptyEdgelessState(page);
|
||||||
|
await switchEditorMode(page);
|
||||||
|
await clickView(page, [0, 0]);
|
||||||
|
|
||||||
|
await selectAllByKeyboard(page);
|
||||||
|
const noteBound = await getSelectedBound(page);
|
||||||
|
|
||||||
|
await triggerComponentToolbarAction(page, 'changeNoteDisplayMode');
|
||||||
|
await waitNextFrame(page);
|
||||||
|
await changeNoteDisplayMode(page, NoteDisplayMode.DocOnly);
|
||||||
|
|
||||||
|
await selectAllByKeyboard(page);
|
||||||
|
expect(await getSelectedBoundCount(page)).toBe(0);
|
||||||
|
|
||||||
|
await clickView(page, [
|
||||||
|
0.5 * (noteBound[0] + noteBound[2]),
|
||||||
|
0.5 * (noteBound[1] + noteBound[3]),
|
||||||
|
]);
|
||||||
|
expect(await getSelectedBoundCount(page)).toBe(0);
|
||||||
|
|
||||||
|
await dragBetweenViewCoords(
|
||||||
|
page,
|
||||||
|
[noteBound[0] - 10, noteBound[1] - 10],
|
||||||
|
[noteBound[0] + noteBound[2] + 10, noteBound[1] + noteBound[3] + 10]
|
||||||
|
);
|
||||||
|
expect(await getSelectedBoundCount(page)).toBe(0);
|
||||||
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user