Skip to content

Commit

Permalink
refactor: migrating legacy tools (#8471)
Browse files Browse the repository at this point in the history
Continue #8438

There're some depended modules that need to be refactor also during migration. I'll list them here.

### GfxExtension
Used to extend the `GfxController`. The `GfxExtension` can perform tasks during the life cycle of `GfxController`.

```typescript
import { GfxExtension } from '@blocksuite/std/gfx';

export class ExampleExtension extends GfxExtension {
  // the key is required
  static override key = 'example';

  // you can access this.gfx inside the class context

  mounted() {
    // called when gfx has mounted
  }

  unmounted() {
    // called when gfx has unmounted
  }
}

// edgeless-spec.ts
export const edgelessSpec = [
  // append it on the spec list
  ExampleExtension
]
```

### KeyboardController
`KeyboardController` provide the pressing status of common key such as shift, space. So that you don't have to maintain thoese status everywhere. Now you can access it through `gfx.keyboard`.

### EdgelessSelectionManager
The legacy `EdgelessSelectionManager` has move to `block-std/gfx` and become a first-party supported module. Now you can access it through `gfx.selection`.

### OverlayExtension
The legacy overlay has to be instantiated and added manually, which is a bit of a hassle. The newly refactored overlay utilizes the new Extension infra to automatically instantiate and add itself to the overlay renderer.

```typescript
import { Overlay } from './renderer/overlay.js';

export class ExampleOverlay extends Overlay {
  static override overlayName = 'example';

  // the methods remain the same
}

// edgeless-spec.ts
import { ExampleOverlay } from './example-overlay.js'

export const edgelessSpec = [
  // append it on the spec list
 ExampleOverlay
]
```

When you need to get the instance of that overlay, just use the `std.get(OverlayIdentifier(theOverlayName)`.

```typescript
import { OverlayIdentifier } from './renderer/overlay.js';

const exampleOverlayInst = std.get(OverlayIdentifier('example');
```

Note that you don’t have to use the OverlayExtension if you only need a local overlay. You can still create and add it to the renderer manually.

### SurfaceMiddlewareExtesion
The new surface middleware is aimed to extend the functionality of `addElement`, `deleteElement` and `updateElement`. It's a pure local infra which is helpful when you need to do something when the previously mentioned methods are called.

You can load the middleware manually using `surfaceModel.applyMiddlewares` method. But we provide a more common way to extend the middleware using the Extension infra.

```typescript
import { type SurfaceMiddlewareBuilder } from '@blocksuite/block-std/gfx';

export const ExampleMiddlewareBuilder: SurfaceMiddlewareBuilder = (std: BlockStdScope) => {
  /**
   * you can access std here
   * the builder must return a middleware function which will be apply to surface model
   **/
  return (ctx: MiddlewareCtx) => {
    // this middleware function will be executed every time when the surface methods get called.
  }
}

// edgeless-spec.ts
import { SurfaceMiddlewareExtension } from '@blocksuite/block-std/gfx';
import { ExampleMiddlewareBuilder } from './middlewares.js'

export const edgelessSpec = [
  // ...
  // the surface builder should be wrapped in the `SurfaceMiddlewareExtension` function to get injected
  SurfaceMiddlewareExtension([ExampleMiddlewareBuilder])
]
```

### Review guide
This PR is a bit huge, but most of changes are just migration from legacy implementation. Here the list that worth attention.

- Every change under block-std package
- New extendable overlay under block-surface/src/renderer/overlay.ts
  • Loading branch information
doouding committed Oct 22, 2024
1 parent 231c950 commit 65fbde6
Show file tree
Hide file tree
Showing 154 changed files with 3,715 additions and 4,289 deletions.
9 changes: 2 additions & 7 deletions packages/affine/block-surface/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export {
ConnectorEndpointLocationsOnTriangle,
ConnectorPathGenerator,
} from './managers/connector-manager.js';
export { CanvasRenderer, Overlay } from './renderer/canvas-renderer.js';
export { CanvasRenderer } from './renderer/canvas-renderer.js';
export * from './renderer/elements/group/consts.js';
export type { ElementRenderer } from './renderer/elements/index.js';
export {
Expand All @@ -28,6 +28,7 @@ export {
} from './renderer/elements/index.js';
export { fitContent } from './renderer/elements/shape/utils.js';
export * from './renderer/elements/type.js';
export { Overlay, OverlayIdentifier } from './renderer/overlay.js';
import {
getCursorByCoord,
getLineHeight,
Expand Down Expand Up @@ -60,10 +61,7 @@ export type { Options } from './utils/rough/core.js';
import {
almostEqual,
clamp,
getBoundsWithRotation,
getPointFromBoundsWithRotation,
getPointsFromBoundsWithRotation,
getQuadBoundsWithRotation,
getStroke,
getSvgPathFromStroke,
intersects,
Expand Down Expand Up @@ -118,10 +116,7 @@ export const CommonUtils = {
clamp,
generateElementId,
generateKeyBetween,
getBoundsWithRotation,
getPointFromBoundsWithRotation,
getPointsFromBoundsWithRotation,
getQuadBoundsWithRotation,
getStroke,
getSvgPathFromStroke,
intersects,
Expand Down
28 changes: 14 additions & 14 deletions packages/affine/block-surface/src/managers/connector-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import {
getBezierCurveBoundingBox,
getBezierParameters,
getBoundFromPoints,
getBoundsWithRotation,
getBoundWithRotation,
getPointFromBoundsWithRotation,
isOverlap,
isVecZero,
Expand All @@ -32,7 +32,7 @@ import {
Vec,
} from '@blocksuite/global/utils';

import { Overlay } from '../renderer/canvas-renderer.js';
import { Overlay } from '../renderer/overlay.js';
import { AStarRunner } from '../utils/a-star.js';

export type Connectable = Exclude<
Expand Down Expand Up @@ -820,6 +820,8 @@ function renderRect(
}

export class ConnectionOverlay extends Overlay {
static override overlayName = 'connection';

highlightPoint: IVec | null = null;

points: IVec[] = [];
Expand All @@ -828,12 +830,12 @@ export class ConnectionOverlay extends Overlay {

targetBounds: IBound | null = null;

constructor(private _gfx: GfxController) {
super();
constructor(gfx: GfxController) {
super(gfx);
}

private _findConnectablesInViews() {
const gfx = this._gfx;
const gfx = this.gfx;
const bound = gfx.viewport.viewportBounds;
return gfx.getElementsByBound(bound).filter(ele => ele.connectable);
}
Expand All @@ -851,9 +853,9 @@ export class ConnectionOverlay extends Overlay {
}

override render(ctx: CanvasRenderingContext2D): void {
const zoom = this._gfx.viewport.zoom;
const zoom = this.gfx.viewport.zoom;
const radius = 5 / zoom;
const color = getComputedStyle(this._gfx.std.host).getPropertyValue(
const color = getComputedStyle(this.gfx.std.host).getPropertyValue(
'--affine-text-emphasis-color'
);

Expand Down Expand Up @@ -902,7 +904,7 @@ export class ConnectionOverlay extends Overlay {
*/
renderConnector(point: IVec, excludedIds: string[] = []) {
const connectables = this._findConnectablesInViews();
const context = this._gfx;
const context = this.gfx;
const target = [];

this._clearRect();
Expand All @@ -915,9 +917,7 @@ export class ConnectionOverlay extends Overlay {

// then check if in expanded bound
const bound = Bound.deserialize(connectable.xywh);
const rotateBound = Bound.from(
getBoundsWithRotation(rBound(connectable))
);
const rotateBound = Bound.from(getBoundWithRotation(rBound(connectable)));
// FIXME: the real path needs to be expanded: diamod, ellipse, trangle.
if (!rotateBound.expand(10).isPointInBound(point)) continue;

Expand Down Expand Up @@ -980,7 +980,7 @@ export class ConnectionOverlay extends Overlay {
{
ignoreTransparent: false,
},
this._gfx.std.host
this.gfx.std.host
)
) {
target.push(connectable);
Expand Down Expand Up @@ -1101,10 +1101,10 @@ export class ConnectorPathGenerator {
const [startPoint, endPoint] = this._computeStartEndPoint(connector);

const startBound = start
? Bound.from(getBoundsWithRotation(rBound(start)))
? Bound.from(getBoundWithRotation(rBound(start)))
: null;
const endBound = end
? Bound.from(getBoundsWithRotation(rBound(end)))
? Bound.from(getBoundWithRotation(rBound(end)))
: null;
const path = this.generateOrthogonalConnectorPath({
startPoint,
Expand Down
2 changes: 1 addition & 1 deletion packages/affine/block-surface/src/middlewares/group.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export const groupRelationMiddleware: SurfaceMiddleware = (
// remove the group if it has no children
if (element instanceof SurfaceGroupLikeModel && props['childIds']) {
if (element.childIds.length === 0) {
surface.removeElement(id);
surface.deleteElement(id);
}
}
}),
Expand Down
23 changes: 3 additions & 20 deletions packages/affine/block-surface/src/renderer/canvas-renderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,35 +9,18 @@ import { type Color, ColorScheme } from '@blocksuite/affine-model';
import { requestConnectedFrame } from '@blocksuite/affine-shared/utils';
import {
DisposableGroup,
getBoundsWithRotation,
getBoundWithRotation,
intersects,
last,
Slot,
} from '@blocksuite/global/utils';

import type { ElementRenderer } from './elements/index.js';
import type { Overlay } from './overlay.js';

import { SurfaceElementModel } from '../element-model/base.js';
import { RoughCanvas } from '../utils/rough/canvas.js';

/**
* An overlay is a layer covered on top of elements,
* can be used for rendering non-CRDT state indicators.
*/
export abstract class Overlay {
protected _renderer: CanvasRenderer | null = null;

constructor() {}

clear() {}

abstract render(ctx: CanvasRenderingContext2D, rc: RoughCanvas): void;

setRenderer(renderer: CanvasRenderer | null) {
this._renderer = renderer;
}
}

type EnvProvider = {
generateColorProperty: (color: Color, fallback: string) => string;
getColorScheme: () => ColorScheme;
Expand Down Expand Up @@ -295,7 +278,7 @@ export class CanvasRenderer {
ctx.save();

const display = element.display ?? true;
if (display && intersects(getBoundsWithRotation(element), bound)) {
if (display && intersects(getBoundWithRotation(element), bound)) {
const renderFn =
this.elementRenderers[
element.type as keyof typeof this.elementRenderers
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import type { Bound } from '@blocksuite/global/utils';
import type { Y } from '@blocksuite/store';

import {
getPointsFromBoundsWithRotation,
getPointsFromBoundWithRotation,
rotatePoints,
} from '@blocksuite/global/utils';
import { deltaInsertsToChunks } from '@blocksuite/inline';
Expand Down Expand Up @@ -235,7 +235,7 @@ export function getTextCursorPosition(
model: TextElementModel,
coord: { x: number; y: number }
) {
const leftTop = getPointsFromBoundsWithRotation(model)[0];
const leftTop = getPointsFromBoundWithRotation(model)[0];
const mousePos = rotatePoints(
[[coord.x, coord.y]],
leftTop,
Expand Down
47 changes: 47 additions & 0 deletions packages/affine/block-surface/src/renderer/overlay.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { Extension } from '@blocksuite/block-std';
import {
type GfxController,
GfxControllerIdentifier,
} from '@blocksuite/block-std/gfx';
import { type Container, createIdentifier } from '@blocksuite/global/di';
import { BlockSuiteError, ErrorCode } from '@blocksuite/global/exceptions';

import type { RoughCanvas } from '../utils/rough/canvas.js';
import type { CanvasRenderer } from './canvas-renderer.js';

/**
* An overlay is a layer covered on top of elements,
* can be used for rendering non-CRDT state indicators.
*/
export abstract class Overlay extends Extension {
static overlayName: string = '';

protected _renderer: CanvasRenderer | null = null;

constructor(protected gfx: GfxController) {
super();
}

static override setup(di: Container): void {
if (!this.overlayName) {
throw new BlockSuiteError(
ErrorCode.ValueNotExists,
`The overlay constructor '${this.name}' should have a static 'overlayName' property.`
);
}

di.addImpl(OverlayIdentifier(this.overlayName), this, [
GfxControllerIdentifier,
]);
}

clear() {}

abstract render(ctx: CanvasRenderingContext2D, rc: RoughCanvas): void;

setRenderer(renderer: CanvasRenderer | null) {
this._renderer = renderer;
}
}

export const OverlayIdentifier = createIdentifier<Overlay>('Overlay');
13 changes: 9 additions & 4 deletions packages/affine/block-surface/src/surface-block.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,22 +8,21 @@ import {
GfxControllerIdentifier,
type Viewport,
} from '@blocksuite/block-std/gfx';
import { Bound, values } from '@blocksuite/global/utils';
import { Bound } from '@blocksuite/global/utils';
import { css, html } from 'lit';
import { query } from 'lit/decorators.js';

import type { Overlay } from './renderer/canvas-renderer.js';
import type { ElementRenderer } from './renderer/elements/index.js';
import type { SurfaceBlockModel } from './surface-model.js';
import type { SurfaceBlockService } from './surface-service.js';

import { ConnectorElementModel } from './element-model/index.js';
import { CanvasRenderer } from './renderer/canvas-renderer.js';
import { OverlayIdentifier } from './renderer/overlay.js';

export interface SurfaceContext {
viewport: Viewport;
host: EditorHost;
overlays: Record<string, Overlay>;
elementRenderers: Record<string, ElementRenderer>;
selection: {
selectedIds: string[];
Expand Down Expand Up @@ -166,9 +165,15 @@ export class SurfaceBlockComponent extends BlockComponent<
}

private _initOverlays() {
values(this._edgelessService.overlays).forEach(overlay => {
this.std.provider.getAll(OverlayIdentifier).forEach(overlay => {
this._renderer.addOverlay(overlay);
});

this._disposables.add(() => {
this.std.provider.getAll(OverlayIdentifier).forEach(overlay => {
this._renderer.removeOverlay(overlay);
});
});
}

private _initRenderer() {
Expand Down
15 changes: 4 additions & 11 deletions packages/affine/block-surface/src/surface-model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -159,26 +159,19 @@ export const SurfaceBlockSchema = defineBlockSchema({
toModel: () => new SurfaceBlockModel(),
});

export type SurfaceMiddleware = (
surface: SurfaceBlockModel,
hooks: SurfaceBlockModel['hooks']
) => () => void;
export type SurfaceMiddleware = (surface: SurfaceBlockModel) => () => void;

export class SurfaceBlockModel extends BaseSurfaceModel {
private _disposables: DisposableGroup = new DisposableGroup();

override _init() {
[connectorMiddleware(this), groupRelationMiddleware(this)].forEach(
disposable => this._disposables.add(disposable)
);
this._extendElement(elementsCtorMap);
super._init();
}

override applyMiddlewares() {
[
connectorMiddleware(this, this.hooks),
groupRelationMiddleware(this, this.hooks),
].forEach(disposable => this._disposables.add(disposable));
}

getConnectors(id: string) {
const connectors = this.getElementsByType(
'connector'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,10 @@ export class AffineLatexNode extends SignalWatcher(
gap: 10px;
border-radius: 4px;
background: ${unsafeCSSVarV2('label/red')};
background: ${
// @ts-ignore
unsafeCSSVarV2('label/red')
};
color: ${unsafeCSSVarV2('text/highlight/fg/red')};
font-family: Inter;
Expand Down
10 changes: 5 additions & 5 deletions packages/affine/model/src/elements/brush/brush.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ import {
import {
Bound,
getBoundFromPoints,
getPointsFromBoundsWithRotation,
getQuadBoundsWithRotation,
getPointsFromBoundWithRotation,
getQuadBoundWithRotation,
getSolidStrokePoints,
getSvgPathFromStroke,
inflateBound,
Expand Down Expand Up @@ -69,17 +69,17 @@ export class BrushElementModel extends GfxPrimitiveElementModel<BrushProps> {
}

override containsBound(bounds: Bound) {
const points = getPointsFromBoundsWithRotation(this);
const points = getPointsFromBoundWithRotation(this);
return points.some(point => bounds.containsPoint(point));
}

override getLineIntersections(start: IVec, end: IVec) {
const tl = [this.x, this.y];
const points = getPointsFromBoundsWithRotation(this, _ =>
const points = getPointsFromBoundWithRotation(this, _ =>
this.points.map(point => Vec.add(point, tl))
);

const box = Bound.fromDOMRect(getQuadBoundsWithRotation(this));
const box = Bound.fromDOMRect(getQuadBoundWithRotation(this));

if (box.w < 8 && box.h < 8) {
return Vec.distanceToLineSegment(start, end, box.center) < 5 ? [] : null;
Expand Down
2 changes: 1 addition & 1 deletion packages/affine/model/src/elements/mindmap/mindmap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -668,7 +668,7 @@ export class MindmapElementModel extends GfxGroupLikeElementModel<MindmapElement
});

queueMicrotask(() => {
removedDescendants.forEach(id => surface.removeElement(id));
removedDescendants.forEach(id => surface.deleteElement(id));
});

// This transaction may not end
Expand Down
Loading

0 comments on commit 65fbde6

Please sign in to comment.