From e1e5e8fc14b7df66dec82d0df4c4c36d2e3def49 Mon Sep 17 00:00:00 2001 From: doodlewind <7312949+doodlewind@users.noreply.github.com> Date: Fri, 11 Apr 2025 10:16:07 +0000 Subject: [PATCH] feat(editor): add basic image support in turbo renderer (#11620) This PR adds basic support for image block: ![image.png](https://graphite-user-uploaded-assets-prod.s3.amazonaws.com/lEGcysB4lFTEbCwZ8jMv/4785fc76-fe09-4002-b3fb-aafa9cac34bb.png) --- blocksuite/affine/blocks/image/package.json | 4 +- blocksuite/affine/blocks/image/src/index.ts | 2 + .../image/src/turbo/image-layout-handler.ts | 69 +++++++++++++++++++ .../image/src/turbo/image-painter.worker.ts | 56 +++++++++++++++ blocksuite/affine/blocks/image/tsconfig.json | 1 + .../src/__tests__/utils/renderer-entry.ts | 2 + .../__tests__/utils/turbo-painter.worker.ts | 2 + .../extensions/turbo-painter.worker.ts | 2 + .../blocksuite/extensions/turbo-renderer.ts | 2 + tools/utils/src/workspace.gen.ts | 1 + yarn.lock | 1 + 11 files changed, 141 insertions(+), 1 deletion(-) create mode 100644 blocksuite/affine/blocks/image/src/turbo/image-layout-handler.ts create mode 100644 blocksuite/affine/blocks/image/src/turbo/image-painter.worker.ts diff --git a/blocksuite/affine/blocks/image/package.json b/blocksuite/affine/blocks/image/package.json index 659b6cc6ab..21b1f274b2 100644 --- a/blocksuite/affine/blocks/image/package.json +++ b/blocksuite/affine/blocks/image/package.json @@ -13,6 +13,7 @@ "@blocksuite/affine-block-note": "workspace:*", "@blocksuite/affine-block-surface": "workspace:*", "@blocksuite/affine-components": "workspace:*", + "@blocksuite/affine-gfx-turbo-renderer": "workspace:*", "@blocksuite/affine-model": "workspace:*", "@blocksuite/affine-shared": "workspace:*", "@blocksuite/affine-widget-slash-menu": "workspace:*", @@ -32,7 +33,8 @@ }, "exports": { ".": "./src/index.ts", - "./effects": "./src/effects.ts" + "./effects": "./src/effects.ts", + "./turbo-painter": "./src/turbo/image-painter.worker.ts" }, "files": [ "src", diff --git a/blocksuite/affine/blocks/image/src/index.ts b/blocksuite/affine/blocks/image/src/index.ts index 04d685796d..ad38a3cea3 100644 --- a/blocksuite/affine/blocks/image/src/index.ts +++ b/blocksuite/affine/blocks/image/src/index.ts @@ -7,5 +7,7 @@ export { ImageProxyService } from './image-proxy-service'; export * from './image-service'; export * from './image-spec'; export * from './styles'; +export * from './turbo/image-layout-handler'; +export * from './turbo/image-painter.worker'; export { addImages, downloadImageBlob, uploadBlobForImage } from './utils'; export { ImageSelection } from '@blocksuite/affine-shared/selection'; diff --git a/blocksuite/affine/blocks/image/src/turbo/image-layout-handler.ts b/blocksuite/affine/blocks/image/src/turbo/image-layout-handler.ts new file mode 100644 index 0000000000..2a14965417 --- /dev/null +++ b/blocksuite/affine/blocks/image/src/turbo/image-layout-handler.ts @@ -0,0 +1,69 @@ +import type { Rect } from '@blocksuite/affine-gfx-turbo-renderer'; +import { + BlockLayoutHandlerExtension, + BlockLayoutHandlersIdentifier, +} from '@blocksuite/affine-gfx-turbo-renderer'; +import type { Container } from '@blocksuite/global/di'; +import type { EditorHost, GfxBlockComponent } from '@blocksuite/std'; +import { clientToModelCoord, type ViewportRecord } from '@blocksuite/std/gfx'; +import type { BlockModel } from '@blocksuite/store'; + +import type { ImageLayout } from './image-painter.worker'; + +export class ImageLayoutHandlerExtension extends BlockLayoutHandlerExtension { + readonly blockType = 'affine:image'; + + static override setup(di: Container) { + di.addImpl( + BlockLayoutHandlersIdentifier('image'), + ImageLayoutHandlerExtension + ); + } + + override queryLayout( + model: BlockModel, + host: EditorHost, + viewportRecord: ViewportRecord + ): ImageLayout | null { + const component = host.std.view.getBlock(model.id) as GfxBlockComponent; + if (!component) return null; + + const imageContainer = component.querySelector('.affine-image-container'); + if (!imageContainer) return null; + + const resizableImg = component.querySelector( + '.resizable-img' + ) as HTMLElement; + if (!resizableImg) return null; + + const { zoom, viewScale } = viewportRecord; + const rect = resizableImg.getBoundingClientRect(); + + const [modelX, modelY] = clientToModelCoord(viewportRecord, [ + rect.x, + rect.y, + ]); + + const imageLayout: ImageLayout = { + type: 'affine:image', + blockId: model.id, + rect: { + x: modelX, + y: modelY, + w: rect.width / zoom / viewScale, + h: rect.height / zoom / viewScale, + }, + }; + + return imageLayout; + } + + calculateBound(layout: ImageLayout) { + const rect: Rect = layout.rect; + + return { + rect, + subRects: [rect], + }; + } +} diff --git a/blocksuite/affine/blocks/image/src/turbo/image-painter.worker.ts b/blocksuite/affine/blocks/image/src/turbo/image-painter.worker.ts new file mode 100644 index 0000000000..5d6e2bf55a --- /dev/null +++ b/blocksuite/affine/blocks/image/src/turbo/image-painter.worker.ts @@ -0,0 +1,56 @@ +import type { + BlockLayout, + BlockLayoutPainter, +} from '@blocksuite/affine-gfx-turbo-renderer'; +import { BlockLayoutPainterExtension } from '@blocksuite/affine-gfx-turbo-renderer/painter'; + +export interface ImageLayout extends BlockLayout { + type: 'affine:image'; + rect: { + x: number; + y: number; + w: number; + h: number; + }; +} + +function isImageLayout(layout: BlockLayout): layout is ImageLayout { + return layout.type === 'affine:image'; +} + +class ImageLayoutPainter implements BlockLayoutPainter { + paint( + ctx: OffscreenCanvasRenderingContext2D, + layout: BlockLayout, + layoutBaseX: number, + layoutBaseY: number + ): void { + if (!isImageLayout(layout)) { + console.warn( + 'Expected image layout but received different format:', + layout + ); + return; + } + + // For now, just paint a white rectangle + const x = layout.rect.x - layoutBaseX; + const y = layout.rect.y - layoutBaseY; + const width = layout.rect.w; + const height = layout.rect.h; + + // Draw a white rectangle with border + ctx.fillStyle = 'white'; + ctx.fillRect(x, y, width, height); + + // Add a border + ctx.strokeStyle = '#e0e0e0'; + ctx.lineWidth = 1; + ctx.strokeRect(x, y, width, height); + } +} + +export const ImageLayoutPainterExtension = BlockLayoutPainterExtension( + 'affine:image', + ImageLayoutPainter +); diff --git a/blocksuite/affine/blocks/image/tsconfig.json b/blocksuite/affine/blocks/image/tsconfig.json index df7d01cd6a..d0be8cddb5 100644 --- a/blocksuite/affine/blocks/image/tsconfig.json +++ b/blocksuite/affine/blocks/image/tsconfig.json @@ -10,6 +10,7 @@ { "path": "../note" }, { "path": "../surface" }, { "path": "../../components" }, + { "path": "../../gfx/turbo-renderer" }, { "path": "../../model" }, { "path": "../../shared" }, { "path": "../../widgets/slash-menu" }, diff --git a/blocksuite/integration-test/src/__tests__/utils/renderer-entry.ts b/blocksuite/integration-test/src/__tests__/utils/renderer-entry.ts index a39a6e6ae5..d8ceea9bbe 100644 --- a/blocksuite/integration-test/src/__tests__/utils/renderer-entry.ts +++ b/blocksuite/integration-test/src/__tests__/utils/renderer-entry.ts @@ -1,3 +1,4 @@ +import { ImageLayoutHandlerExtension } from '@blocksuite/affine/blocks/image'; import { ListLayoutHandlerExtension } from '@blocksuite/affine/blocks/list'; import { ParagraphLayoutHandlerExtension } from '@blocksuite/affine/blocks/paragraph'; import { @@ -13,6 +14,7 @@ async function init() { setupEditor('edgeless', [ ParagraphLayoutHandlerExtension, ListLayoutHandlerExtension, + ImageLayoutHandlerExtension, TurboRendererConfigFactory({ painterWorkerEntry: createPainterWorker, }), diff --git a/blocksuite/integration-test/src/__tests__/utils/turbo-painter.worker.ts b/blocksuite/integration-test/src/__tests__/utils/turbo-painter.worker.ts index 0c3b975493..08b49680be 100644 --- a/blocksuite/integration-test/src/__tests__/utils/turbo-painter.worker.ts +++ b/blocksuite/integration-test/src/__tests__/utils/turbo-painter.worker.ts @@ -1,3 +1,4 @@ +import { ImageLayoutPainterExtension } from '@blocksuite/affine-block-image/turbo-painter'; import { ListLayoutPainterExtension } from '@blocksuite/affine-block-list/turbo-painter'; import { NoteLayoutPainterExtension } from '@blocksuite/affine-block-note/turbo-painter'; import { ParagraphLayoutPainterExtension } from '@blocksuite/affine-block-paragraph/turbo-painter'; @@ -7,4 +8,5 @@ new ViewportLayoutPainter([ ParagraphLayoutPainterExtension, ListLayoutPainterExtension, NoteLayoutPainterExtension, + ImageLayoutPainterExtension, ]); diff --git a/packages/frontend/core/src/blocksuite/extensions/turbo-painter.worker.ts b/packages/frontend/core/src/blocksuite/extensions/turbo-painter.worker.ts index 4ee5a1dd0b..e4791bc6d9 100644 --- a/packages/frontend/core/src/blocksuite/extensions/turbo-painter.worker.ts +++ b/packages/frontend/core/src/blocksuite/extensions/turbo-painter.worker.ts @@ -1,4 +1,5 @@ import { CodeLayoutPainterExtension } from '@blocksuite/affine/blocks/code'; +import { ImageLayoutPainterExtension } from '@blocksuite/affine/blocks/image'; import { ListLayoutPainterExtension } from '@blocksuite/affine/blocks/list'; import { NoteLayoutPainterExtension } from '@blocksuite/affine/blocks/note'; import { ParagraphLayoutPainterExtension } from '@blocksuite/affine/blocks/paragraph'; @@ -9,4 +10,5 @@ new ViewportLayoutPainter([ ListLayoutPainterExtension, NoteLayoutPainterExtension, CodeLayoutPainterExtension, + ImageLayoutPainterExtension, ]); diff --git a/packages/frontend/core/src/blocksuite/extensions/turbo-renderer.ts b/packages/frontend/core/src/blocksuite/extensions/turbo-renderer.ts index 4b27de2669..533d791cea 100644 --- a/packages/frontend/core/src/blocksuite/extensions/turbo-renderer.ts +++ b/packages/frontend/core/src/blocksuite/extensions/turbo-renderer.ts @@ -1,5 +1,6 @@ import { getWorkerUrl } from '@affine/env/worker'; import { CodeLayoutHandlerExtension } from '@blocksuite/affine/blocks/code'; +import { ImageLayoutHandlerExtension } from '@blocksuite/affine/blocks/image'; import { ListLayoutHandlerExtension } from '@blocksuite/affine/blocks/list'; import { NoteLayoutHandlerExtension } from '@blocksuite/affine/blocks/note'; import { ParagraphLayoutHandlerExtension } from '@blocksuite/affine/blocks/paragraph'; @@ -19,6 +20,7 @@ export function patchTurboRendererExtension() { ListLayoutHandlerExtension, NoteLayoutHandlerExtension, CodeLayoutHandlerExtension, + ImageLayoutHandlerExtension, TurboRendererConfigFactory({ options: { zoomThreshold: 1, diff --git a/tools/utils/src/workspace.gen.ts b/tools/utils/src/workspace.gen.ts index c79f9bb2b7..522340b02b 100644 --- a/tools/utils/src/workspace.gen.ts +++ b/tools/utils/src/workspace.gen.ts @@ -225,6 +225,7 @@ export const PackageList = [ 'blocksuite/affine/blocks/note', 'blocksuite/affine/blocks/surface', 'blocksuite/affine/components', + 'blocksuite/affine/gfx/turbo-renderer', 'blocksuite/affine/model', 'blocksuite/affine/shared', 'blocksuite/affine/widgets/slash-menu', diff --git a/yarn.lock b/yarn.lock index 635c69c7ee..482cfbc1d1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2646,6 +2646,7 @@ __metadata: "@blocksuite/affine-block-note": "workspace:*" "@blocksuite/affine-block-surface": "workspace:*" "@blocksuite/affine-components": "workspace:*" + "@blocksuite/affine-gfx-turbo-renderer": "workspace:*" "@blocksuite/affine-model": "workspace:*" "@blocksuite/affine-shared": "workspace:*" "@blocksuite/affine-widget-slash-menu": "workspace:*"