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

@@ -2,8 +2,6 @@ import { EdgelessFrameManagerIdentifier } from '@blocksuite/affine-block-frame';
import { import {
EdgelessCRUDIdentifier, EdgelessCRUDIdentifier,
EdgelessLegacySlotIdentifier, EdgelessLegacySlotIdentifier,
type ElementRenderer,
elementRenderers,
getSurfaceBlock, getSurfaceBlock,
type SurfaceBlockModel, type SurfaceBlockModel,
type SurfaceContext, type SurfaceContext,
@@ -48,8 +46,6 @@ export class EdgelessRootService extends RootService implements SurfaceContext {
private readonly _surface: SurfaceBlockModel; private readonly _surface: SurfaceBlockModel;
elementRenderers: Record<string, ElementRenderer> = elementRenderers;
TemplateJob = TemplateJob; TemplateJob = TemplateJob;
get blocks(): GfxBlockElementModel[] { get blocks(): GfxBlockElementModel[] {

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 './clipboard-config';
export * from './crud-extension'; export * from './crud-extension';
export * from './element-renderer';
export * from './export-manager'; export * from './export-manager';
export * from './legacy-slot-extension'; export * from './legacy-slot-extension';
export * from './query'; export * from './query';

View File

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

View File

@@ -1,6 +1,10 @@
import type { IBound } from '@blocksuite/global/gfx'; 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 { RoughCanvas } from '../../index.js';
import type { CanvasRenderer } from '../canvas-renderer.js'; import type { CanvasRenderer } from '../canvas-renderer.js';
import { brush } from './brush/index.js'; import { brush } from './brush/index.js';
@@ -13,7 +17,9 @@ import { text } from './text/index.js';
export { normalizeShapeBound } from './shape/utils.js'; export { normalizeShapeBound } from './shape/utils.js';
export type ElementRenderer< export type ElementRenderer<
T extends GfxPrimitiveElementModel = GfxPrimitiveElementModel, T extends
| GfxPrimitiveElementModel
| GfxLocalElementModel = GfxPrimitiveElementModel,
> = ( > = (
model: T, model: T,
ctx: CanvasRenderingContext2D, ctx: CanvasRenderingContext2D,
@@ -23,12 +29,47 @@ export type ElementRenderer<
viewportBound: IBound viewportBound: IBound
) => void; ) => void;
export const elementRenderers = { export const BrushElementRendererExtension = ElementRendererExtension(
brush, 'brush',
highlighter, brush
connector, );
group,
shape, export const HighlighterElementRendererExtension = ElementRendererExtension(
text, 'highlighter',
mindmap, highlighter
} as Record<string, ElementRenderer<any>>; );
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 { ConnectorElementModel } from './element-model/index.js';
import { CanvasRenderer } from './renderer/canvas-renderer.js'; import { CanvasRenderer } from './renderer/canvas-renderer.js';
import {
type ElementRenderer,
elementRenderers,
} from './renderer/elements/index.js';
import { OverlayIdentifier } from './renderer/overlay.js'; import { OverlayIdentifier } from './renderer/overlay.js';
import type { SurfaceBlockModel } from './surface-model.js'; import type { SurfaceBlockModel } from './surface-model.js';
export interface SurfaceContext { export interface SurfaceContext {
viewport: Viewport; viewport: Viewport;
host: EditorHost; host: EditorHost;
elementRenderers: Record<string, ElementRenderer>;
selection: { selection: {
selectedIds: string[]; selectedIds: string[];
slots: { slots: {
@@ -148,6 +143,7 @@ export class SurfaceBlockComponent extends BlockComponent<SurfaceBlockModel> {
const themeService = this.std.get(ThemeProvider); const themeService = this.std.get(ThemeProvider);
this._renderer = new CanvasRenderer({ this._renderer = new CanvasRenderer({
std: this.std,
viewport: gfx.viewport, viewport: gfx.viewport,
layerManager: gfx.layer, layerManager: gfx.layer,
gridManager: gfx.grid, gridManager: gfx.grid,
@@ -177,7 +173,6 @@ export class SurfaceBlockComponent extends BlockComponent<SurfaceBlockModel> {
onStackingCanvasCreated(canvas) { onStackingCanvasCreated(canvas) {
canvas.className = 'indexable-canvas'; canvas.className = 'indexable-canvas';
}, },
elementRenderers,
surfaceModel: this.model, surfaceModel: this.model,
}); });

View File

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

View File

@@ -1,7 +1,6 @@
/* oxlint-disable @typescript-eslint/no-non-null-assertion */ /* oxlint-disable @typescript-eslint/no-non-null-assertion */
import { import {
CanvasRenderer, CanvasRenderer,
elementRenderers,
fitContent, fitContent,
type SurfaceBlockModel, type SurfaceBlockModel,
} from '@blocksuite/affine/blocks/surface'; } from '@blocksuite/affine/blocks/surface';
@@ -98,6 +97,7 @@ export class MindmapSurfaceBlock extends BlockComponent<SurfaceBlockModel> {
const themeService = this.std.get(ThemeProvider); const themeService = this.std.get(ThemeProvider);
this.renderer = new CanvasRenderer({ this.renderer = new CanvasRenderer({
std: this.std,
viewport: this.viewport, viewport: this.viewport,
layerManager: this._layer, layerManager: this._layer,
gridManager: this._grid, gridManager: this._grid,
@@ -124,7 +124,6 @@ export class MindmapSurfaceBlock extends BlockComponent<SurfaceBlockModel> {
themeService.edgelessTheme themeService.edgelessTheme
), ),
}, },
elementRenderers,
surfaceModel: this.model, surfaceModel: this.model,
}); });
this._disposables.add(this.renderer); this._disposables.add(this.renderer);