refactor(editor): extensionalize surface canvas renderer (#11456)

This commit is contained in:
Saul-Mirone
2025-04-04 13:09:45 +00:00
parent 3ecdc377fe
commit 138e7f60de
9 changed files with 97 additions and 34 deletions

View File

@@ -0,0 +1,31 @@
import {
createIdentifier,
type ServiceIdentifier,
} from '@blocksuite/global/di';
import type {
GfxLocalElementModel,
GfxPrimitiveElementModel,
} from '@blocksuite/std/gfx';
import type { ExtensionType } from '@blocksuite/store';
import type { ElementRenderer } from '../renderer/elements';
export const ElementRendererIdentifier =
createIdentifier<unknown>('element-renderer');
export const ElementRendererExtension = <
T extends GfxPrimitiveElementModel | GfxLocalElementModel,
>(
id: string,
renderer: ElementRenderer<T>
): ExtensionType & {
identifier: ServiceIdentifier<ElementRenderer<T>>;
} => {
const identifier = ElementRendererIdentifier<ElementRenderer<T>>(id);
return {
setup: di => {
di.addImpl(identifier, () => renderer);
},
identifier,
};
};

View File

@@ -1,5 +1,6 @@
export * from './clipboard-config';
export * from './crud-extension';
export * from './element-renderer';
export * from './export-manager';
export * from './legacy-slot-extension';
export * from './query';

View File

@@ -22,10 +22,7 @@ export {
export { CanvasRenderer } from './renderer/canvas-renderer.js';
export * from './renderer/elements/group/consts.js';
export type { ElementRenderer } from './renderer/elements/index.js';
export {
elementRenderers,
normalizeShapeBound,
} from './renderer/elements/index.js';
export { normalizeShapeBound } 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';

View File

@@ -3,6 +3,7 @@ import { requestConnectedFrame } from '@blocksuite/affine-shared/utils';
import { DisposableGroup } from '@blocksuite/global/disposable';
import type { IBound } from '@blocksuite/global/gfx';
import { getBoundWithRotation, intersects } from '@blocksuite/global/gfx';
import type { BlockStdScope } from '@blocksuite/std';
import type {
GridManager,
LayerManager,
@@ -13,6 +14,7 @@ import last from 'lodash-es/last';
import { Subject } from 'rxjs';
import type { SurfaceElementModel } from '../element-model/base.js';
import { ElementRendererIdentifier } from '../extensions/element-renderer.js';
import { RoughCanvas } from '../utils/rough/canvas.js';
import type { ElementRenderer } from './elements/index.js';
import type { Overlay } from './overlay.js';
@@ -26,12 +28,12 @@ type EnvProvider = {
};
type RendererOptions = {
std: BlockStdScope;
viewport: Viewport;
layerManager: LayerManager;
provider?: Partial<EnvProvider>;
enableStackingCanvas?: boolean;
onStackingCanvasCreated?: (canvas: HTMLCanvasElement) => void;
elementRenderers: Record<string, ElementRenderer>;
gridManager: GridManager;
surfaceModel: SurfaceBlockModel;
};
@@ -51,7 +53,7 @@ export class CanvasRenderer {
ctx: CanvasRenderingContext2D;
elementRenderers: Record<string, ElementRenderer>;
std: BlockStdScope;
grid: GridManager;
@@ -76,11 +78,11 @@ export class CanvasRenderer {
this.canvas = canvas;
this.ctx = this.canvas.getContext('2d') as CanvasRenderingContext2D;
this.std = options.std;
this.viewport = options.viewport;
this.layerManager = options.layerManager;
this.grid = options.gridManager;
this.provider = options.provider ?? {};
this.elementRenderers = options.elementRenderers;
this._initViewport();
options.enableStackingCanvas = options.enableStackingCanvas ?? false;
@@ -277,10 +279,9 @@ export class CanvasRenderer {
for (const element of elements) {
const display = (element.display ?? true) && !element.hidden;
if (display && intersects(getBoundWithRotation(element), bound)) {
const renderFn =
this.elementRenderers[
element.type as keyof typeof this.elementRenderers
];
const renderFn = this.std.getOptional<ElementRenderer>(
ElementRendererIdentifier(element.type)
);
if (!renderFn) {
console.warn(`Cannot find renderer for ${element.type}`);

View File

@@ -1,6 +1,10 @@
import type { IBound } from '@blocksuite/global/gfx';
import type { GfxPrimitiveElementModel } from '@blocksuite/std/gfx';
import type {
GfxLocalElementModel,
GfxPrimitiveElementModel,
} from '@blocksuite/std/gfx';
import { ElementRendererExtension } from '../../extensions/element-renderer.js';
import type { RoughCanvas } from '../../index.js';
import type { CanvasRenderer } from '../canvas-renderer.js';
import { brush } from './brush/index.js';
@@ -13,7 +17,9 @@ import { text } from './text/index.js';
export { normalizeShapeBound } from './shape/utils.js';
export type ElementRenderer<
T extends GfxPrimitiveElementModel = GfxPrimitiveElementModel,
T extends
| GfxPrimitiveElementModel
| GfxLocalElementModel = GfxPrimitiveElementModel,
> = (
model: T,
ctx: CanvasRenderingContext2D,
@@ -23,12 +29,47 @@ export type ElementRenderer<
viewportBound: IBound
) => void;
export const elementRenderers = {
brush,
highlighter,
connector,
group,
shape,
text,
mindmap,
} as Record<string, ElementRenderer<any>>;
export const BrushElementRendererExtension = ElementRendererExtension(
'brush',
brush
);
export const HighlighterElementRendererExtension = ElementRendererExtension(
'highlighter',
highlighter
);
export const ConnectorElementRendererExtension = ElementRendererExtension(
'connector',
connector
);
export const GroupElementRendererExtension = ElementRendererExtension(
'group',
group
);
export const ShapeElementRendererExtension = ElementRendererExtension(
'shape',
shape
);
export const TextElementRendererExtension = ElementRendererExtension(
'text',
text
);
export const MindmapElementRendererExtension = ElementRendererExtension(
'mindmap',
mindmap
);
export const elementRendererExtensions = [
BrushElementRendererExtension,
HighlighterElementRendererExtension,
ConnectorElementRendererExtension,
GroupElementRendererExtension,
ShapeElementRendererExtension,
TextElementRendererExtension,
MindmapElementRendererExtension,
];

View File

@@ -11,17 +11,12 @@ import type { Subject } from 'rxjs';
import { ConnectorElementModel } from './element-model/index.js';
import { CanvasRenderer } from './renderer/canvas-renderer.js';
import {
type ElementRenderer,
elementRenderers,
} from './renderer/elements/index.js';
import { OverlayIdentifier } from './renderer/overlay.js';
import type { SurfaceBlockModel } from './surface-model.js';
export interface SurfaceContext {
viewport: Viewport;
host: EditorHost;
elementRenderers: Record<string, ElementRenderer>;
selection: {
selectedIds: string[];
slots: {
@@ -148,6 +143,7 @@ export class SurfaceBlockComponent extends BlockComponent<SurfaceBlockModel> {
const themeService = this.std.get(ThemeProvider);
this._renderer = new CanvasRenderer({
std: this.std,
viewport: gfx.viewport,
layerManager: gfx.layer,
gridManager: gfx.grid,
@@ -177,7 +173,6 @@ export class SurfaceBlockComponent extends BlockComponent<SurfaceBlockModel> {
onStackingCanvasCreated(canvas) {
canvas.className = 'indexable-canvas';
},
elementRenderers,
surfaceModel: this.model,
});

View File

@@ -11,12 +11,14 @@ import {
EdgelessLegacySlotExtension,
} from './extensions';
import { ExportManagerExtension } from './extensions/export-manager/export-manager';
import { elementRendererExtensions } from './renderer/elements';
const CommonSurfaceBlockSpec: ExtensionType[] = [
FlavourExtension('affine:surface'),
EdgelessCRUDExtension,
EdgelessLegacySlotExtension,
ExportManagerExtension,
...elementRendererExtensions,
];
export const PageSurfaceBlockSpec: ExtensionType[] = [