refactor: rewrite dblclick and selection logic of default-tool (#11036)

continue #10824

### Changed
- Moved double-click-to-edit behavior from the default tool to individual model views
- Introduced `onSelected` callback interface in gfx view components to allows developers to override default selection logic
This commit is contained in:
doouding
2025-03-26 07:32:43 +00:00
parent 61c0d01da3
commit ace5d44a61
38 changed files with 779 additions and 353 deletions

View File

@@ -1,17 +1,23 @@
import {
EdgelessCRUDIdentifier,
getSurfaceBlock,
TextUtils,
} from '@blocksuite/affine-block-surface';
import type { ConnectorElementModel } from '@blocksuite/affine-model';
import type { RichText } from '@blocksuite/affine-rich-text';
import { ThemeProvider } from '@blocksuite/affine-shared/services';
import { almostEqual } from '@blocksuite/affine-shared/utils';
import { type BlockComponent, ShadowlessElement } from '@blocksuite/block-std';
import {
type BlockComponent,
type BlockStdScope,
ShadowlessElement,
stdContext,
} from '@blocksuite/block-std';
import { GfxControllerIdentifier } from '@blocksuite/block-std/gfx';
import { RANGE_SYNC_EXCLUDE_ATTR } from '@blocksuite/block-std/inline';
import { Bound, Vec } from '@blocksuite/global/gfx';
import { BlockSuiteError, ErrorCode } from '@blocksuite/global/exceptions';
import { Bound, type IVec, Vec } from '@blocksuite/global/gfx';
import { WithDisposable } from '@blocksuite/global/lit';
import { consume } from '@lit/context';
import { css, html, nothing } from 'lit';
import { property, query } from 'lit/decorators.js';
import { styleMap } from 'lit/directives/style-map.js';
@@ -21,6 +27,60 @@ const HORIZONTAL_PADDING = 2;
const VERTICAL_PADDING = 2;
const BORDER_WIDTH = 1;
export function mountConnectorLabelEditor(
connector: ConnectorElementModel,
edgeless: BlockComponent,
point?: IVec
) {
const mountElm = edgeless.querySelector('.edgeless-mount-point');
if (!mountElm) {
throw new BlockSuiteError(
ErrorCode.ValueNotExists,
"edgeless block's mount point does not exist"
);
}
const gfx = edgeless.std.get(GfxControllerIdentifier);
// @ts-expect-error default tool should be migrated to std
gfx.tool.setTool('default');
gfx.selection.set({
elements: [connector.id],
editing: true,
});
if (!connector.text) {
const text = new Y.Text();
const labelOffset = connector.labelOffset;
let labelXYWH = connector.labelXYWH ?? [0, 0, 16, 16];
if (point) {
const center = connector.getNearestPoint(point);
const distance = connector.getOffsetDistanceByPoint(center as IVec);
const bounds = Bound.fromXYWH(labelXYWH);
bounds.center = center;
labelOffset.distance = distance;
labelXYWH = bounds.toXYWH();
}
edgeless.std.get(EdgelessCRUDIdentifier).updateElement(connector.id, {
text,
labelXYWH,
labelOffset: { ...labelOffset },
});
}
const editor = new EdgelessConnectorLabelEditor();
editor.connector = connector;
mountElm.append(editor);
editor.updateComplete
.then(() => {
editor.inlineEditor?.focusEnd();
})
.catch(console.error);
}
export class EdgelessConnectorLabelEditor extends WithDisposable(
ShadowlessElement
) {
@@ -58,11 +118,11 @@ export class EdgelessConnectorLabelEditor extends WithDisposable(
`;
get crud() {
return this.edgeless.std.get(EdgelessCRUDIdentifier);
return this.std.get(EdgelessCRUDIdentifier);
}
get gfx() {
return this.edgeless.std.get(GfxControllerIdentifier);
return this.std.get(GfxControllerIdentifier);
}
get selection() {
@@ -76,8 +136,8 @@ export class EdgelessConnectorLabelEditor extends WithDisposable(
private _resizeObserver: ResizeObserver | null = null;
private readonly _updateLabelRect = () => {
const { connector, edgeless } = this;
if (!connector || !edgeless) return;
const { connector, isConnected } = this;
if (!connector || !isConnected) return;
if (!this.inlineEditorContainer) return;
@@ -119,9 +179,8 @@ export class EdgelessConnectorLabelEditor extends WithDisposable(
}
override firstUpdated() {
const { edgeless, connector, selection } = this;
const dispatcher = edgeless.std.event;
const store = edgeless.std.store;
const { connector, selection, std } = this;
const dispatcher = std.event;
this._resizeObserver = new ResizeObserver(() => {
this._updateLabelRect();
@@ -159,7 +218,7 @@ export class EdgelessConnectorLabelEditor extends WithDisposable(
})
);
const surface = getSurfaceBlock(store);
const surface = this.gfx.surface;
if (surface) {
this.disposables.add(
@@ -275,7 +334,7 @@ export class EdgelessConnectorLabelEditor extends WithDisposable(
];
const isEmpty = !connector.text?.length && !this._isComposition;
const color = this.edgeless.std
const color = this.std
.get(ThemeProvider)
.generateColorProperty(labelColor, '#000000');
@@ -326,8 +385,10 @@ export class EdgelessConnectorLabelEditor extends WithDisposable(
@property({ attribute: false })
accessor connector!: ConnectorElementModel;
@property({ attribute: false })
accessor edgeless!: BlockComponent;
@consume({
context: stdContext,
})
accessor std!: BlockStdScope;
@query('rich-text')
accessor richText!: RichText;

View File

@@ -54,7 +54,6 @@ export function mountConnectorLabelEditor(
const editor = new EdgelessConnectorLabelEditor();
editor.connector = connector;
editor.edgeless = edgeless;
mountElm.append(editor);
editor.updateComplete

View File

@@ -6,6 +6,8 @@ import {
GfxElementModelView,
} from '@blocksuite/block-std/gfx';
import { mountConnectorLabelEditor } from '../text/edgeless-connector-label-editor';
export class ConnectorElementView extends GfxElementModelView<ConnectorElementModel> {
static override type = 'connector';
@@ -24,4 +26,24 @@ export class ConnectorElementView extends GfxElementModelView<ConnectorElementMo
this.model.moveTo(currentBound.moveDelta(dx, dy));
};
override onCreated(): void {
super.onCreated();
this._initDblClickToEdit();
}
private _initDblClickToEdit(): void {
this.on('dblclick', evt => {
const edgeless = this.std.view.getBlock(this.std.store.root!.id);
if (edgeless && !this.model.isLocked()) {
mountConnectorLabelEditor(
this.model,
edgeless,
this.gfx.viewport.toModelCoord(evt.x, evt.y)
);
}
});
}
}