mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-25 02:13:00 +08:00
refactor(editor): improve the implementation of auto complete (#11135)
This commit is contained in:
@@ -1,7 +1,10 @@
|
||||
import { insertEdgelessTextCommand } from '@blocksuite/affine-block-edgeless-text';
|
||||
import { EdgelessFrameManagerIdentifier } from '@blocksuite/affine-block-frame';
|
||||
import {
|
||||
CanvasElementType,
|
||||
EdgelessCRUDIdentifier,
|
||||
getSurfaceBlock,
|
||||
getSurfaceComponent,
|
||||
} from '@blocksuite/affine-block-surface';
|
||||
import { FontFamilyIcon } from '@blocksuite/affine-components/icons';
|
||||
import {
|
||||
@@ -37,7 +40,11 @@ import {
|
||||
captureEventTarget,
|
||||
matchModels,
|
||||
} from '@blocksuite/affine-shared/utils';
|
||||
import { type BlockStdScope, stdContext } from '@blocksuite/block-std';
|
||||
import {
|
||||
type BlockComponent,
|
||||
type BlockStdScope,
|
||||
stdContext,
|
||||
} from '@blocksuite/block-std';
|
||||
import { GfxControllerIdentifier } from '@blocksuite/block-std/gfx';
|
||||
import type { XYWH } from '@blocksuite/global/gfx';
|
||||
import {
|
||||
@@ -58,7 +65,6 @@ import { repeat } from 'lit/directives/repeat.js';
|
||||
import { styleMap } from 'lit/directives/style-map.js';
|
||||
import * as Y from 'yjs';
|
||||
|
||||
import type { EdgelessRootBlockComponent } from '../../edgeless-root-block.js';
|
||||
import {
|
||||
type AUTO_COMPLETE_TARGET_TYPE,
|
||||
AutoCompleteFrameOverlay,
|
||||
@@ -125,7 +131,7 @@ export class EdgelessAutoCompletePanel extends WithDisposable(LitElement) {
|
||||
|
||||
constructor(
|
||||
position: [number, number],
|
||||
edgeless: EdgelessRootBlockComponent,
|
||||
edgeless: BlockComponent,
|
||||
currentSource: ShapeElementModel | NoteBlockModel,
|
||||
connector: ConnectorElementModel
|
||||
) {
|
||||
@@ -140,6 +146,10 @@ export class EdgelessAutoCompletePanel extends WithDisposable(LitElement) {
|
||||
return this.std.get(EdgelessCRUDIdentifier);
|
||||
}
|
||||
|
||||
get surface() {
|
||||
return getSurfaceComponent(this.std);
|
||||
}
|
||||
|
||||
private _addFrame() {
|
||||
const bound = this._generateTarget(this.connector)?.nextBound;
|
||||
if (!bound) return;
|
||||
@@ -152,9 +162,10 @@ export class EdgelessAutoCompletePanel extends WithDisposable(LitElement) {
|
||||
const { xywh, position } = target;
|
||||
|
||||
const edgeless = this.edgeless;
|
||||
const { service, surfaceBlockModel } = edgeless;
|
||||
const frameMgr = service.frame;
|
||||
const frameIndex = service.frames.length + 1;
|
||||
const surfaceBlockModel = getSurfaceBlock(this.std.store);
|
||||
if (!surfaceBlockModel) return;
|
||||
const frameMgr = this.std.get(EdgelessFrameManagerIdentifier);
|
||||
const frameIndex = frameMgr.frames.length + 1;
|
||||
const props = this.std.get(EditPropsStore).applyLastProps('affine:frame', {
|
||||
title: new Y.Text(`Frame ${frameIndex}`),
|
||||
xywh: serializeXYWH(...xywh),
|
||||
@@ -170,7 +181,7 @@ export class EdgelessAutoCompletePanel extends WithDisposable(LitElement) {
|
||||
position,
|
||||
};
|
||||
|
||||
edgeless.service.selection.set({
|
||||
this.gfx.selection.set({
|
||||
elements: [frame.id],
|
||||
editing: false,
|
||||
});
|
||||
@@ -209,7 +220,7 @@ export class EdgelessAutoCompletePanel extends WithDisposable(LitElement) {
|
||||
this.crud.updateElement(this.connector.id, {
|
||||
target: { id, position },
|
||||
});
|
||||
this.edgeless.service.selection.set({
|
||||
this.gfx.selection.set({
|
||||
elements: [id],
|
||||
editing: false,
|
||||
});
|
||||
@@ -395,7 +406,7 @@ export class EdgelessAutoCompletePanel extends WithDisposable(LitElement) {
|
||||
}
|
||||
|
||||
private _getPanelPosition() {
|
||||
const { viewport } = this.edgeless.service;
|
||||
const { viewport } = this.gfx;
|
||||
const { boundingClientRect: viewportRect, zoom } = viewport;
|
||||
const result = this._getTargetXYWH(PANEL_WIDTH / zoom, PANEL_HEIGHT / zoom);
|
||||
const pos = result ? result.xywh.slice(0, 2) : this.position;
|
||||
@@ -442,11 +453,14 @@ export class EdgelessAutoCompletePanel extends WithDisposable(LitElement) {
|
||||
}
|
||||
|
||||
private _removeOverlay() {
|
||||
if (this._overlay)
|
||||
this.edgeless.surface.renderer.removeOverlay(this._overlay);
|
||||
if (this._overlay && this.surface) {
|
||||
this.surface.renderer.removeOverlay(this._overlay);
|
||||
}
|
||||
}
|
||||
|
||||
private _showFrameOverlay() {
|
||||
if (!this.surface) return;
|
||||
|
||||
const bound = this._generateTarget(this.connector)?.nextBound;
|
||||
if (!bound) return;
|
||||
|
||||
@@ -459,7 +473,7 @@ export class EdgelessAutoCompletePanel extends WithDisposable(LitElement) {
|
||||
.get(ThemeProvider)
|
||||
.getCssVariableColor('--affine-black-30');
|
||||
this._overlay = new AutoCompleteFrameOverlay(this.gfx, xywh, strokeColor);
|
||||
this.edgeless.surface.renderer.addOverlay(this._overlay);
|
||||
this.surface.renderer.addOverlay(this._overlay);
|
||||
}
|
||||
|
||||
private _showNoteOverlay() {
|
||||
@@ -468,6 +482,7 @@ export class EdgelessAutoCompletePanel extends WithDisposable(LitElement) {
|
||||
DEFAULT_NOTE_OVERLAY_HEIGHT
|
||||
)?.xywh;
|
||||
if (!xywh) return;
|
||||
if (!this.surface) return;
|
||||
|
||||
const background = this.edgeless.std
|
||||
.get(ThemeProvider)
|
||||
@@ -478,12 +493,13 @@ export class EdgelessAutoCompletePanel extends WithDisposable(LitElement) {
|
||||
true
|
||||
);
|
||||
this._overlay = new AutoCompleteNoteOverlay(this.gfx, xywh, background);
|
||||
this.edgeless.surface.renderer.addOverlay(this._overlay);
|
||||
this.surface.renderer.addOverlay(this._overlay);
|
||||
}
|
||||
|
||||
private _showOverlay(targetType: AUTO_COMPLETE_TARGET_TYPE) {
|
||||
this._removeOverlay();
|
||||
if (!this._connectorExist()) return;
|
||||
if (!this.surface) return;
|
||||
|
||||
switch (targetType) {
|
||||
case 'text':
|
||||
@@ -499,12 +515,13 @@ export class EdgelessAutoCompletePanel extends WithDisposable(LitElement) {
|
||||
this._showShapeOverlay(targetType);
|
||||
}
|
||||
|
||||
this.edgeless.surface.refresh();
|
||||
this.surface.refresh();
|
||||
}
|
||||
|
||||
private _showShapeOverlay(targetType: TARGET_SHAPE_TYPE) {
|
||||
const bound = this._generateTarget(this.connector)?.nextBound;
|
||||
if (!bound) return;
|
||||
if (!this.surface) return;
|
||||
|
||||
const { x, y, w, h } = bound;
|
||||
const xywh = [x, y, w, h] as XYWH;
|
||||
@@ -537,7 +554,7 @@ export class EdgelessAutoCompletePanel extends WithDisposable(LitElement) {
|
||||
shapeStyle
|
||||
);
|
||||
|
||||
this.edgeless.surface.renderer.addOverlay(this._overlay);
|
||||
this.surface.renderer.addOverlay(this._overlay);
|
||||
}
|
||||
|
||||
private _showTextOverlay() {
|
||||
@@ -546,9 +563,10 @@ export class EdgelessAutoCompletePanel extends WithDisposable(LitElement) {
|
||||
DEFAULT_TEXT_HEIGHT
|
||||
)?.xywh;
|
||||
if (!xywh) return;
|
||||
if (!this.surface) return;
|
||||
|
||||
this._overlay = new AutoCompleteTextOverlay(this.gfx, xywh);
|
||||
this.edgeless.surface.renderer.addOverlay(this._overlay);
|
||||
this.surface.renderer.addOverlay(this._overlay);
|
||||
}
|
||||
|
||||
override connectedCallback() {
|
||||
@@ -568,9 +586,7 @@ export class EdgelessAutoCompletePanel extends WithDisposable(LitElement) {
|
||||
|
||||
override firstUpdated() {
|
||||
this.disposables.add(
|
||||
this.edgeless.service.viewport.viewportUpdated.subscribe(() =>
|
||||
this.requestUpdate()
|
||||
)
|
||||
this.gfx.viewport.viewportUpdated.subscribe(() => this.requestUpdate())
|
||||
);
|
||||
}
|
||||
|
||||
@@ -650,7 +666,7 @@ export class EdgelessAutoCompletePanel extends WithDisposable(LitElement) {
|
||||
accessor currentSource: ShapeElementModel | NoteBlockModel;
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor edgeless: EdgelessRootBlockComponent;
|
||||
accessor edgeless: BlockComponent;
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor position: [number, number];
|
||||
|
||||
@@ -3,6 +3,8 @@ import {
|
||||
type ConnectionOverlay,
|
||||
ConnectorPathGenerator,
|
||||
EdgelessCRUDIdentifier,
|
||||
getSurfaceBlock,
|
||||
getSurfaceComponent,
|
||||
isNoteBlock,
|
||||
Overlay,
|
||||
OverlayIdentifier,
|
||||
@@ -24,7 +26,11 @@ import {
|
||||
shapeMethods,
|
||||
} from '@blocksuite/affine-model';
|
||||
import { handleNativeRangeAtPoint } from '@blocksuite/affine-shared/utils';
|
||||
import { type BlockStdScope, stdContext } from '@blocksuite/block-std';
|
||||
import {
|
||||
type BlockComponent,
|
||||
type BlockStdScope,
|
||||
stdContext,
|
||||
} from '@blocksuite/block-std';
|
||||
import { GfxControllerIdentifier } from '@blocksuite/block-std/gfx';
|
||||
import { DisposableGroup } from '@blocksuite/global/disposable';
|
||||
import type { Bound, IVec } from '@blocksuite/global/gfx';
|
||||
@@ -42,7 +48,6 @@ import { property, state } from 'lit/decorators.js';
|
||||
import { classMap } from 'lit/directives/class-map.js';
|
||||
import { styleMap } from 'lit/directives/style-map.js';
|
||||
|
||||
import type { EdgelessRootBlockComponent } from '../../edgeless-root-block.js';
|
||||
import type { SelectedRect } from '../rects/edgeless-selected-rect.js';
|
||||
import { EdgelessAutoCompletePanel } from './auto-complete-panel.js';
|
||||
import {
|
||||
@@ -158,6 +163,10 @@ export class EdgelessAutoComplete extends WithDisposable(LitElement) {
|
||||
}
|
||||
`;
|
||||
|
||||
private get _surface() {
|
||||
return getSurfaceBlock(this.std.store);
|
||||
}
|
||||
|
||||
private _autoCompleteOverlay!: AutoCompleteOverlay;
|
||||
|
||||
private readonly _onPointerDown = (e: PointerEvent, type: Direction) => {
|
||||
@@ -167,7 +176,7 @@ export class EdgelessAutoComplete extends WithDisposable(LitElement) {
|
||||
e.clientY - viewportRect.top
|
||||
);
|
||||
|
||||
if (!this.edgeless.dispatcher) return;
|
||||
if (!this.edgeless.std.event) return;
|
||||
|
||||
let connector: ConnectorElementModel | null;
|
||||
|
||||
@@ -207,7 +216,7 @@ export class EdgelessAutoComplete extends WithDisposable(LitElement) {
|
||||
if (!this._isMoving) {
|
||||
this._generateElementOnClick(type);
|
||||
} else if (connector && !connector.target.id) {
|
||||
this.edgeless.service.selection.clear();
|
||||
this.gfx.selection.clear();
|
||||
this._createAutoCompletePanel(e, connector);
|
||||
}
|
||||
|
||||
@@ -343,10 +352,7 @@ export class EdgelessAutoComplete extends WithDisposable(LitElement) {
|
||||
) {
|
||||
if (!this.canShowAutoComplete) return;
|
||||
|
||||
const position = this.edgeless.service.viewport.toModelCoord(
|
||||
e.clientX,
|
||||
e.clientY
|
||||
);
|
||||
const position = this.gfx.viewport.toModelCoord(e.clientX, e.clientY);
|
||||
const autoCompletePanel = new EdgelessAutoCompletePanel(
|
||||
position,
|
||||
this.edgeless,
|
||||
@@ -358,7 +364,7 @@ export class EdgelessAutoComplete extends WithDisposable(LitElement) {
|
||||
}
|
||||
|
||||
private _generateElementOnClick(type: Direction) {
|
||||
const { doc, service } = this.edgeless;
|
||||
const { doc } = this.edgeless;
|
||||
const bound = this._computeNextBound(type);
|
||||
const id = createEdgelessElement(this.edgeless, this.current, bound);
|
||||
if (!id) return;
|
||||
@@ -384,7 +390,7 @@ export class EdgelessAutoComplete extends WithDisposable(LitElement) {
|
||||
if (!model) {
|
||||
return;
|
||||
}
|
||||
const [x, y] = service.viewport.toViewCoord(
|
||||
const [x, y] = this.gfx.viewport.toViewCoord(
|
||||
bound.center[0],
|
||||
bound.y + DEFAULT_NOTE_HEIGHT / 2
|
||||
);
|
||||
@@ -393,7 +399,7 @@ export class EdgelessAutoComplete extends WithDisposable(LitElement) {
|
||||
});
|
||||
}
|
||||
|
||||
this.edgeless.service.selection.set({
|
||||
this.gfx.selection.set({
|
||||
elements: [id],
|
||||
editing: true,
|
||||
});
|
||||
@@ -401,9 +407,9 @@ export class EdgelessAutoComplete extends WithDisposable(LitElement) {
|
||||
}
|
||||
|
||||
private _getConnectedElements(element: ShapeElementModel) {
|
||||
const service = this.edgeless.service;
|
||||
if (!this._surface) return [];
|
||||
|
||||
return service.getConnectors(element.id).reduce((prev, current) => {
|
||||
return this._surface.getConnectors(element.id).reduce((prev, current) => {
|
||||
if (current.target.id === element.id && current.source.id) {
|
||||
prev.push(
|
||||
this.crud.getElementById(current.source.id) as ShapeElementModel
|
||||
@@ -469,7 +475,8 @@ export class EdgelessAutoComplete extends WithDisposable(LitElement) {
|
||||
}
|
||||
|
||||
private _initOverlay() {
|
||||
const { surface } = this.edgeless;
|
||||
const surface = getSurfaceComponent(this.std);
|
||||
if (!surface) return;
|
||||
this._autoCompleteOverlay = new AutoCompleteOverlay(this.gfx);
|
||||
surface.renderer.addOverlay(this._autoCompleteOverlay);
|
||||
}
|
||||
@@ -477,7 +484,7 @@ export class EdgelessAutoComplete extends WithDisposable(LitElement) {
|
||||
private _renderArrow() {
|
||||
const isShape = this.current instanceof ShapeElementModel;
|
||||
const { selectedRect } = this;
|
||||
const { zoom } = this.edgeless.service.viewport;
|
||||
const { zoom } = this.gfx.viewport;
|
||||
const width = 72;
|
||||
const height = 44;
|
||||
|
||||
@@ -572,7 +579,7 @@ export class EdgelessAutoComplete extends WithDisposable(LitElement) {
|
||||
}
|
||||
|
||||
const { selectedRect } = this;
|
||||
const { zoom } = this.edgeless.service.viewport;
|
||||
const { zoom } = this.gfx.viewport;
|
||||
const size = 26;
|
||||
const buttonMargin =
|
||||
(mindmapButtons.mindmapNode?.children.length ?? 0) > 0
|
||||
@@ -644,7 +651,8 @@ export class EdgelessAutoComplete extends WithDisposable(LitElement) {
|
||||
path: IVec[],
|
||||
targetType: ShapeType
|
||||
) {
|
||||
const { surface } = this.edgeless;
|
||||
const surface = getSurfaceComponent(this.std);
|
||||
if (!surface) return;
|
||||
|
||||
this._autoCompleteOverlay.stroke = surface.renderer.getColorValue(
|
||||
current.strokeColor,
|
||||
@@ -667,7 +675,7 @@ export class EdgelessAutoComplete extends WithDisposable(LitElement) {
|
||||
}
|
||||
|
||||
override firstUpdated() {
|
||||
const { _disposables, edgeless } = this;
|
||||
const { _disposables, edgeless, gfx } = this;
|
||||
|
||||
_disposables.add(
|
||||
this.gfx.selection.slots.updated.subscribe(() => {
|
||||
@@ -681,8 +689,8 @@ export class EdgelessAutoComplete extends WithDisposable(LitElement) {
|
||||
_disposables.add(
|
||||
edgeless.host.event.add('pointerMove', ctx => {
|
||||
const evt = ctx.get('pointerState');
|
||||
const [x, y] = edgeless.gfx.viewport.toModelCoord(evt.x, evt.y);
|
||||
const elm = edgeless.gfx.getElementByPoint(x, y);
|
||||
const [x, y] = gfx.viewport.toModelCoord(evt.x, evt.y);
|
||||
const elm = gfx.getElementByPoint(x, y);
|
||||
|
||||
if (!elm) {
|
||||
this._isHover = false;
|
||||
@@ -703,7 +711,9 @@ export class EdgelessAutoComplete extends WithDisposable(LitElement) {
|
||||
|
||||
removeOverlay() {
|
||||
this._timer && clearTimeout(this._timer);
|
||||
this.edgeless.surface.renderer.removeOverlay(this._autoCompleteOverlay);
|
||||
const surface = getSurfaceComponent(this.std);
|
||||
if (!surface) return;
|
||||
surface.renderer.removeOverlay(this._autoCompleteOverlay);
|
||||
}
|
||||
|
||||
override render() {
|
||||
@@ -740,7 +750,7 @@ export class EdgelessAutoComplete extends WithDisposable(LitElement) {
|
||||
accessor current!: ShapeElementModel | NoteBlockModel;
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor edgeless!: EdgelessRootBlockComponent;
|
||||
accessor edgeless!: BlockComponent;
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor selectedRect!: SelectedRect;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import {
|
||||
EdgelessCRUDIdentifier,
|
||||
type Options,
|
||||
Overlay,
|
||||
type RoughCanvas,
|
||||
@@ -14,14 +15,13 @@ import {
|
||||
type ShapeName,
|
||||
type ShapeStyle,
|
||||
} from '@blocksuite/affine-model';
|
||||
import type { BlockComponent } from '@blocksuite/block-std';
|
||||
import type { GfxController, GfxModel } from '@blocksuite/block-std/gfx';
|
||||
import { BlockSuiteError, ErrorCode } from '@blocksuite/global/exceptions';
|
||||
import { Bound, normalizeDegAngle, type XYWH } from '@blocksuite/global/gfx';
|
||||
import { assertType } from '@blocksuite/global/utils';
|
||||
import * as Y from 'yjs';
|
||||
|
||||
import type { EdgelessRootBlockComponent } from '../../edgeless-root-block.js';
|
||||
|
||||
export enum Direction {
|
||||
Right,
|
||||
Bottom,
|
||||
@@ -270,14 +270,13 @@ export function capitalizeFirstLetter(str: string) {
|
||||
}
|
||||
|
||||
export function createEdgelessElement(
|
||||
edgeless: EdgelessRootBlockComponent,
|
||||
edgeless: BlockComponent,
|
||||
current: ShapeElementModel | NoteBlockModel,
|
||||
bound: Bound
|
||||
) {
|
||||
let id;
|
||||
const { service } = edgeless;
|
||||
const { crud } = service;
|
||||
const crud = edgeless.std.get(EdgelessCRUDIdentifier);
|
||||
|
||||
let id;
|
||||
let element: GfxModel | null = null;
|
||||
|
||||
if (isShape(current)) {
|
||||
@@ -331,11 +330,11 @@ export function createEdgelessElement(
|
||||
}
|
||||
|
||||
export function createShapeElement(
|
||||
edgeless: EdgelessRootBlockComponent,
|
||||
edgeless: BlockComponent,
|
||||
current: ShapeElementModel | NoteBlockModel,
|
||||
targetType: TARGET_SHAPE_TYPE
|
||||
) {
|
||||
const { crud } = edgeless.service;
|
||||
const crud = edgeless.std.get(EdgelessCRUDIdentifier);
|
||||
const id = crud.addElement('shape', {
|
||||
shapeType: getShapeType(targetType),
|
||||
radius: getShapeRadius(targetType),
|
||||
|
||||
Reference in New Issue
Block a user