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:
L-Sun
2025-03-31 12:35:02 +00:00
parent 587fea02b8
commit fec698fd8b
4 changed files with 107 additions and 28 deletions

View File

@@ -10,6 +10,8 @@ import {
type ConnectorElementModel,
GroupElementModel,
MindmapElementModel,
NoteBlockModel,
NoteDisplayMode,
} from '@blocksuite/affine-model';
import { resetNativeSelection } from '@blocksuite/affine-shared/utils';
import { DisposableGroup } from '@blocksuite/global/disposable';
@@ -141,6 +143,12 @@ export class DefaultTool extends BaseTool {
if (el instanceof MindmapElementModel) {
return bound.contains(el.elementBound);
}
if (
el instanceof NoteBlockModel &&
el.props.displayMode === NoteDisplayMode.DocOnly
) {
return false;
}
return true;
});

View File

@@ -12,7 +12,13 @@ import {
import { GfxControllerIdentifier } from '@blocksuite/std/gfx';
import type { BlockModel } from '@blocksuite/store';
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 { query } from 'lit/decorators.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 _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 = () => {};
@@ -136,7 +157,7 @@ export class OutlinePanelBody extends SignalWatcher(
if (!this.doc.root) return;
const pageVisibleNotes = this._pageVisibleNotes$.peek();
const selected = this._selectedNotes$.peek();
const selected = this._allSelectedNotes$.peek();
const children = this.doc.root.children.slice();
const noteIndex = new Map<NoteBlockModel, number>();
@@ -203,23 +224,39 @@ export class OutlinePanelBody extends SignalWatcher(
const note = this.doc.getBlock(id)?.model;
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) {
selectedNotes = selectedNotes.filter(_note => _note !== note);
} else if (multiselect) {
selectedNotes = [...selectedNotes, note];
if (multiselect) {
selectedNotes[note.props.displayMode] = selected
? [...selectedNotes[note.props.displayMode], note]
: selectedNotes[note.props.displayMode].filter(_note => _note !== note);
} 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') {
gfx.selection.set({
elements: selectedNotes.map(({ id }) => id),
elements: [...selectedNotes.both, ...selectedNotes.edgeless].map(
({ id }) => id
),
editing: false,
});
this._selectedNotes$.doc.value = selectedNotes.doc;
} 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]);
});
const preSelected = this._selectedNotes$.peek();
if (
preSelected.length !== currSelectedNotes.length ||
preSelected.some(note => !currSelectedNotes.includes(note))
) {
this._selectedNotes$.value = currSelectedNotes;
}
// update selected notes from edgeless selection
batch(() => {
[NoteDisplayMode.DocAndEdgeless, NoteDisplayMode.EdgelessOnly].forEach(
mode => {
this._selectedNotes$[mode].value = currSelectedNotes.filter(
note => note.props.displayMode === mode
);
}
);
});
});
}
@@ -280,11 +320,6 @@ export class OutlinePanelBody extends SignalWatcher(
std.dnd.monitor<NoteCardEntity, NoteDropPayload>({
onDragStart: () => {
this._dragging$.value = true;
this._selectedNotes$.value = this._selectedNotes$
.peek()
.filter(note => {
return this._pageVisibleNotes$.value.includes(note);
});
},
onDrag: data => {
const target = data.location.current.dropTargets[0];
@@ -377,7 +412,7 @@ export class OutlinePanelBody extends SignalWatcher(
index=${index}
.note=${note}
.activeHeadingId=${this._activeHeadingId$.value}
.status=${this._selectedNotes$.value.includes(note)
.status=${this._allSelectedNotes$.value.includes(note)
? this._dragging$.value
? 'dragging'
: 'selected'