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:
@@ -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;
|
||||
|
||||
@@ -54,7 +54,6 @@ export function mountConnectorLabelEditor(
|
||||
|
||||
const editor = new EdgelessConnectorLabelEditor();
|
||||
editor.connector = connector;
|
||||
editor.edgeless = edgeless;
|
||||
|
||||
mountElm.append(editor);
|
||||
editor.updateComplete
|
||||
|
||||
@@ -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)
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
export * from './command';
|
||||
export * from './text/text';
|
||||
export * from './toolbar/config';
|
||||
export * from './view';
|
||||
|
||||
@@ -5,15 +5,49 @@ import {
|
||||
} from '@blocksuite/affine-block-surface';
|
||||
import type { GroupElementModel } from '@blocksuite/affine-model';
|
||||
import type { RichText } from '@blocksuite/affine-rich-text';
|
||||
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 { BlockSuiteError, ErrorCode } from '@blocksuite/global/exceptions';
|
||||
import { Bound } from '@blocksuite/global/gfx';
|
||||
import { WithDisposable } from '@blocksuite/global/lit';
|
||||
import { consume } from '@lit/context';
|
||||
import { html, nothing } from 'lit';
|
||||
import { property, query } from 'lit/decorators.js';
|
||||
import { styleMap } from 'lit/directives/style-map.js';
|
||||
|
||||
export function mountGroupTitleEditor(
|
||||
group: GroupElementModel,
|
||||
edgeless: BlockComponent
|
||||
) {
|
||||
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 FIXME: resolve after gfx tool refactor
|
||||
gfx.tool.setTool('default');
|
||||
gfx.selection.set({
|
||||
elements: [group.id],
|
||||
editing: true,
|
||||
});
|
||||
|
||||
const groupEditor = new EdgelessGroupTitleEditor();
|
||||
groupEditor.group = group;
|
||||
|
||||
mountElm.append(groupEditor);
|
||||
}
|
||||
|
||||
export class EdgelessGroupTitleEditor extends WithDisposable(
|
||||
ShadowlessElement
|
||||
) {
|
||||
@@ -26,7 +60,7 @@ export class EdgelessGroupTitleEditor extends WithDisposable(
|
||||
}
|
||||
|
||||
get gfx() {
|
||||
return this.edgeless.std.get(GfxControllerIdentifier);
|
||||
return this.std.get(GfxControllerIdentifier);
|
||||
}
|
||||
|
||||
get selection() {
|
||||
@@ -50,7 +84,7 @@ export class EdgelessGroupTitleEditor extends WithDisposable(
|
||||
}
|
||||
|
||||
override firstUpdated(): void {
|
||||
const dispatcher = this.edgeless.std.event;
|
||||
const dispatcher = this.std.event;
|
||||
|
||||
this.updateComplete
|
||||
.then(() => {
|
||||
@@ -141,12 +175,14 @@ export class EdgelessGroupTitleEditor extends WithDisposable(
|
||||
></rich-text>`;
|
||||
}
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor edgeless!: BlockComponent;
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor group!: GroupElementModel;
|
||||
|
||||
@consume({
|
||||
context: stdContext,
|
||||
})
|
||||
accessor std!: BlockStdScope;
|
||||
|
||||
@query('rich-text')
|
||||
accessor richText!: RichText;
|
||||
}
|
||||
|
||||
@@ -28,7 +28,6 @@ export function mountGroupTitleEditor(
|
||||
|
||||
const groupEditor = new EdgelessGroupTitleEditor();
|
||||
groupEditor.group = group;
|
||||
groupEditor.edgeless = edgeless;
|
||||
|
||||
mountElm.append(groupEditor);
|
||||
}
|
||||
|
||||
24
blocksuite/affine/gfx/group/src/view.ts
Normal file
24
blocksuite/affine/gfx/group/src/view.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import type { GroupElementModel } from '@blocksuite/affine-model';
|
||||
import { GfxElementModelView } from '@blocksuite/block-std/gfx';
|
||||
|
||||
import { mountGroupTitleEditor } from './text/edgeless-group-title-editor';
|
||||
|
||||
export class GroupElementView extends GfxElementModelView<GroupElementModel> {
|
||||
static override type: string = 'group';
|
||||
|
||||
override onCreated(): void {
|
||||
super.onCreated();
|
||||
|
||||
this._initDblClickToEdit();
|
||||
}
|
||||
|
||||
private _initDblClickToEdit(): void {
|
||||
this.on('dblclick', () => {
|
||||
const edgeless = this.std.view.getBlock(this.std.store.root!.id);
|
||||
|
||||
if (edgeless && !this.model.isLocked()) {
|
||||
mountGroupTitleEditor(this.model, edgeless);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -4,3 +4,4 @@ export * from './overlay';
|
||||
export * from './shape-tool';
|
||||
export * from './text';
|
||||
export * from './toolbar';
|
||||
export * from './view';
|
||||
|
||||
@@ -2,21 +2,73 @@ import {
|
||||
EdgelessCRUDIdentifier,
|
||||
TextUtils,
|
||||
} from '@blocksuite/affine-block-surface';
|
||||
import type { ShapeElementModel } from '@blocksuite/affine-model';
|
||||
import { MindmapElementModel, TextResizing } from '@blocksuite/affine-model';
|
||||
import {
|
||||
MindmapElementModel,
|
||||
ShapeElementModel,
|
||||
TextResizing,
|
||||
} from '@blocksuite/affine-model';
|
||||
import type { RichText } from '@blocksuite/affine-rich-text';
|
||||
import { ThemeProvider } from '@blocksuite/affine-shared/services';
|
||||
import { getSelectedRect } 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 { BlockSuiteError, ErrorCode } from '@blocksuite/global/exceptions';
|
||||
import { Bound, toRadian, Vec } from '@blocksuite/global/gfx';
|
||||
import { WithDisposable } from '@blocksuite/global/lit';
|
||||
import { consume } from '@lit/context';
|
||||
import { html, nothing } from 'lit';
|
||||
import { property, query } from 'lit/decorators.js';
|
||||
import { styleMap } from 'lit/directives/style-map.js';
|
||||
import * as Y from 'yjs';
|
||||
|
||||
export function mountShapeTextEditor(
|
||||
shapeElement: ShapeElementModel,
|
||||
edgeless: BlockComponent
|
||||
) {
|
||||
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);
|
||||
const crud = edgeless.std.get(EdgelessCRUDIdentifier);
|
||||
|
||||
const updatedElement = crud.getElementById(shapeElement.id);
|
||||
|
||||
if (!(updatedElement instanceof ShapeElementModel)) {
|
||||
console.error('Cannot mount text editor on a non-shape element');
|
||||
return;
|
||||
}
|
||||
|
||||
// @ts-expect-error FIXME: resolve after gfx tool refactor
|
||||
gfx.tool.setTool('default');
|
||||
gfx.selection.set({
|
||||
elements: [shapeElement.id],
|
||||
editing: true,
|
||||
});
|
||||
|
||||
if (!shapeElement.text) {
|
||||
const text = new Y.Text();
|
||||
edgeless.std
|
||||
.get(EdgelessCRUDIdentifier)
|
||||
.updateElement(shapeElement.id, { text });
|
||||
}
|
||||
|
||||
const shapeEditor = new EdgelessShapeTextEditor();
|
||||
shapeEditor.element = updatedElement;
|
||||
|
||||
mountElm.append(shapeEditor);
|
||||
}
|
||||
|
||||
export class EdgelessShapeTextEditor extends WithDisposable(ShadowlessElement) {
|
||||
private _keeping = false;
|
||||
|
||||
@@ -29,11 +81,11 @@ export class EdgelessShapeTextEditor extends WithDisposable(ShadowlessElement) {
|
||||
}
|
||||
|
||||
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() {
|
||||
@@ -179,7 +231,7 @@ export class EdgelessShapeTextEditor extends WithDisposable(ShadowlessElement) {
|
||||
}
|
||||
|
||||
override firstUpdated(): void {
|
||||
const dispatcher = this.edgeless.std.event;
|
||||
const dispatcher = this.std.event;
|
||||
|
||||
this.element.textDisplay = false;
|
||||
|
||||
@@ -273,7 +325,7 @@ export class EdgelessShapeTextEditor extends WithDisposable(ShadowlessElement) {
|
||||
);
|
||||
const [x, y] = this.gfx.viewport.toViewCoord(leftTopX, leftTopY);
|
||||
const autoWidth = textResizing === TextResizing.AUTO_WIDTH_AND_HEIGHT;
|
||||
const color = this.edgeless.std
|
||||
const color = this.std
|
||||
.get(ThemeProvider)
|
||||
.generateColorProperty(this.element.color, '#000000');
|
||||
|
||||
@@ -346,16 +398,13 @@ export class EdgelessShapeTextEditor extends WithDisposable(ShadowlessElement) {
|
||||
this._keeping = keeping;
|
||||
}
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor edgeless!: BlockComponent;
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor element!: ShapeElementModel;
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor mountEditor:
|
||||
| ((element: ShapeElementModel, edgeless: BlockComponent) => void)
|
||||
| undefined = undefined;
|
||||
@consume({
|
||||
context: stdContext,
|
||||
})
|
||||
accessor std!: BlockStdScope;
|
||||
|
||||
@query('rich-text')
|
||||
accessor richText!: RichText;
|
||||
|
||||
@@ -1 +1 @@
|
||||
export * from './text';
|
||||
export * from './edgeless-shape-text-editor';
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
import { EdgelessCRUDIdentifier } from '@blocksuite/affine-block-surface';
|
||||
import { ShapeElementModel } from '@blocksuite/affine-model';
|
||||
import type { BlockComponent } from '@blocksuite/block-std';
|
||||
import { GfxControllerIdentifier } from '@blocksuite/block-std/gfx';
|
||||
import { BlockSuiteError, ErrorCode } from '@blocksuite/global/exceptions';
|
||||
import * as Y from 'yjs';
|
||||
|
||||
import { EdgelessShapeTextEditor } from './edgeless-shape-text-editor';
|
||||
|
||||
export function mountShapeTextEditor(
|
||||
shapeElement: ShapeElementModel,
|
||||
edgeless: BlockComponent
|
||||
) {
|
||||
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);
|
||||
const crud = edgeless.std.get(EdgelessCRUDIdentifier);
|
||||
|
||||
const updatedElement = crud.getElementById(shapeElement.id);
|
||||
|
||||
if (!(updatedElement instanceof ShapeElementModel)) {
|
||||
console.error('Cannot mount text editor on a non-shape element');
|
||||
return;
|
||||
}
|
||||
|
||||
// @ts-expect-error FIXME: resolve after gfx tool refactor
|
||||
gfx.tool.setTool('default');
|
||||
gfx.selection.set({
|
||||
elements: [shapeElement.id],
|
||||
editing: true,
|
||||
});
|
||||
|
||||
if (!shapeElement.text) {
|
||||
const text = new Y.Text();
|
||||
edgeless.std
|
||||
.get(EdgelessCRUDIdentifier)
|
||||
.updateElement(shapeElement.id, { text });
|
||||
}
|
||||
|
||||
const shapeEditor = new EdgelessShapeTextEditor();
|
||||
shapeEditor.element = updatedElement;
|
||||
shapeEditor.edgeless = edgeless;
|
||||
shapeEditor.mountEditor = mountShapeTextEditor;
|
||||
|
||||
mountElm.append(shapeEditor);
|
||||
}
|
||||
@@ -47,7 +47,7 @@ import { html } from 'lit';
|
||||
import isEqual from 'lodash-es/isEqual';
|
||||
|
||||
import type { ShapeToolOption } from '../shape-tool';
|
||||
import { mountShapeTextEditor } from '../text';
|
||||
import { mountShapeTextEditor } from '../text/edgeless-shape-text-editor';
|
||||
import { ShapeComponentConfig } from './shape-menu-config';
|
||||
|
||||
export const shapeToolbarConfig = {
|
||||
|
||||
24
blocksuite/affine/gfx/shape/src/view.ts
Normal file
24
blocksuite/affine/gfx/shape/src/view.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import type { ShapeElementModel } from '@blocksuite/affine-model';
|
||||
import { GfxElementModelView } from '@blocksuite/block-std/gfx';
|
||||
|
||||
import { mountShapeTextEditor } from './text/edgeless-shape-text-editor';
|
||||
|
||||
export class ShapeElementView extends GfxElementModelView<ShapeElementModel> {
|
||||
static override type: string = 'shape';
|
||||
|
||||
override onCreated(): void {
|
||||
super.onCreated();
|
||||
|
||||
this._initDblClickToEdit();
|
||||
}
|
||||
|
||||
private _initDblClickToEdit(): void {
|
||||
const edgeless = this.std.view.getBlock(this.std.store.root!.id);
|
||||
|
||||
this.on('dblclick', () => {
|
||||
if (edgeless && !this.model.isLocked()) {
|
||||
mountShapeTextEditor(this.model, edgeless);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,18 @@
|
||||
import {
|
||||
CanvasElementType,
|
||||
EdgelessCRUDIdentifier,
|
||||
getSurfaceBlock,
|
||||
type IModelCoord,
|
||||
TextUtils,
|
||||
} from '@blocksuite/affine-block-surface';
|
||||
import type { TextElementModel } from '@blocksuite/affine-model';
|
||||
import { TextElementModel } from '@blocksuite/affine-model';
|
||||
import type { RichText } from '@blocksuite/affine-rich-text';
|
||||
import { ThemeProvider } from '@blocksuite/affine-shared/services';
|
||||
import { getSelectedRect } from '@blocksuite/affine-shared/utils';
|
||||
import {
|
||||
type BlockComponent,
|
||||
type BlockStdScope,
|
||||
type PointerEventState,
|
||||
ShadowlessElement,
|
||||
stdContext,
|
||||
} from '@blocksuite/block-std';
|
||||
@@ -20,6 +24,71 @@ 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';
|
||||
import * as Y from 'yjs';
|
||||
|
||||
export function mountTextElementEditor(
|
||||
textElement: TextElementModel,
|
||||
edgeless: BlockComponent,
|
||||
focusCoord?: IModelCoord
|
||||
) {
|
||||
let cursorIndex = textElement.text.length;
|
||||
if (focusCoord) {
|
||||
cursorIndex = Math.min(
|
||||
TextUtils.getCursorByCoord(textElement, focusCoord),
|
||||
cursorIndex
|
||||
);
|
||||
}
|
||||
|
||||
const textEditor = new EdgelessTextEditor();
|
||||
textEditor.element = textElement;
|
||||
|
||||
edgeless.append(textEditor);
|
||||
textEditor.updateComplete
|
||||
.then(() => {
|
||||
textEditor.inlineEditor?.focusIndex(cursorIndex);
|
||||
})
|
||||
.catch(console.error);
|
||||
|
||||
const gfx = edgeless.std.get(GfxControllerIdentifier);
|
||||
|
||||
// @ts-expect-error TODO: refactor gfx tool
|
||||
gfx.tool.setTool('default');
|
||||
gfx.selection.set({
|
||||
elements: [textElement.id],
|
||||
editing: true,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*
|
||||
* Canvas Text has been deprecated
|
||||
*/
|
||||
export function addText(edgeless: BlockComponent, event: PointerEventState) {
|
||||
const gfx = edgeless.std.get(GfxControllerIdentifier);
|
||||
const crud = edgeless.std.get(EdgelessCRUDIdentifier);
|
||||
const [x, y] = gfx.viewport.toModelCoord(event.x, event.y);
|
||||
const selected = gfx.getElementByPoint(x, y);
|
||||
|
||||
if (!selected) {
|
||||
const [modelX, modelY] = gfx.viewport.toModelCoord(event.x, event.y);
|
||||
|
||||
const id = edgeless.std
|
||||
.get(EdgelessCRUDIdentifier)
|
||||
.addElement(CanvasElementType.TEXT, {
|
||||
xywh: new Bound(modelX, modelY, 32, 32).serialize(),
|
||||
text: new Y.Text(),
|
||||
});
|
||||
if (!id) return;
|
||||
|
||||
edgeless.doc.captureSync();
|
||||
const textElement = crud.getElementById(id);
|
||||
if (!textElement) return;
|
||||
if (textElement instanceof TextElementModel) {
|
||||
mountTextElementEditor(textElement, edgeless);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class EdgelessTextEditor extends WithDisposable(ShadowlessElement) {
|
||||
get crud() {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
export * from './edgeless-text-editor';
|
||||
export * from './mount-text-editor';
|
||||
export * from './tool';
|
||||
export * from './toolbar';
|
||||
export * from './view';
|
||||
|
||||
28
blocksuite/affine/gfx/text/src/view.ts
Normal file
28
blocksuite/affine/gfx/text/src/view.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import type { TextElementModel } from '@blocksuite/affine-model';
|
||||
import { GfxElementModelView } from '@blocksuite/block-std/gfx';
|
||||
|
||||
import { mountTextElementEditor } from './edgeless-text-editor';
|
||||
|
||||
export class TextElementView extends GfxElementModelView<TextElementModel> {
|
||||
static override type: string = 'text';
|
||||
|
||||
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);
|
||||
const [x, y] = this.gfx.viewport.toModelCoord(evt.x, evt.y);
|
||||
|
||||
if (edgeless && !this.model.isLocked()) {
|
||||
mountTextElementEditor(this.model, edgeless, {
|
||||
x,
|
||||
y,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user