From 460e0888739ede06d204fede7baff93241bf1ed7 Mon Sep 17 00:00:00 2001 From: doouding Date: Fri, 7 Mar 2025 04:40:05 +0000 Subject: [PATCH] fix: block should not be selectable when dragged into note (#10664) ### Changed - Fixed the issue that block can still be selected when dragged into note - Rewrite grid and layer with extension infra --- .../framework/block-std/src/gfx/controller.ts | 31 ++-- .../framework/block-std/src/gfx/grid.ts | 132 +++++++++------ .../framework/block-std/src/gfx/layer.ts | 158 ++++++++++-------- .../block-std/src/scope/block-std-scope.ts | 3 + .../src/blocksuite/ai/utils/template-job.ts | 6 +- 5 files changed, 187 insertions(+), 143 deletions(-) diff --git a/blocksuite/framework/block-std/src/gfx/controller.ts b/blocksuite/framework/block-std/src/gfx/controller.ts index e2881fce05..7e2ae9a12d 100644 --- a/blocksuite/framework/block-std/src/gfx/controller.ts +++ b/blocksuite/framework/block-std/src/gfx/controller.ts @@ -38,18 +38,22 @@ export class GfxController extends LifeCycleWatcher { private readonly _disposables: DisposableGroup = new DisposableGroup(); - private _surface: SurfaceBlockModel | null = null; + private readonly _surface$ = new Signal(null); readonly cursor$ = new Signal(); - readonly grid: GridManager; - readonly keyboard: KeyboardController; - readonly layer: LayerManager; - readonly viewport: Viewport = new Viewport(); + get grid() { + return this.std.get(GridManager); + } + + get layer() { + return this.std.get(LayerManager); + } + get doc() { return this.std.store; } @@ -62,8 +66,12 @@ export class GfxController extends LifeCycleWatcher { return [...this.layer.blocks, ...this.layer.canvasElements]; } + get surface$() { + return this._surface$; + } + get surface() { - return this._surface; + return this._surface$.peek(); } get surfaceComponent(): BlockComponent | null { @@ -75,22 +83,13 @@ export class GfxController extends LifeCycleWatcher { constructor(std: BlockStdScope) { super(std); - this.grid = new GridManager(); - this.layer = new LayerManager(this.doc, null); this.keyboard = new KeyboardController(std); this._disposables.add( onSurfaceAdded(this.doc, surface => { - this._surface = surface; - - if (surface) { - this._disposables.add(this.grid.watch({ surface })); - this.layer.watch({ surface }); - } + this._surface$.value = surface; }) ); - this._disposables.add(this.grid.watch({ doc: this.doc })); - this._disposables.add(this.layer); this._disposables.add(this.viewport); this._disposables.add(this.keyboard); diff --git a/blocksuite/framework/block-std/src/gfx/grid.ts b/blocksuite/framework/block-std/src/gfx/grid.ts index 460aeec60b..87859c8dab 100644 --- a/blocksuite/framework/block-std/src/gfx/grid.ts +++ b/blocksuite/framework/block-std/src/gfx/grid.ts @@ -4,9 +4,11 @@ import { getBoundWithRotation, intersects, } from '@blocksuite/global/gfx'; -import type { BlockModel, Store } from '@blocksuite/store'; +import { DisposableGroup } from '@blocksuite/global/slot'; +import type { BlockModel } from '@blocksuite/store'; import { compare } from '../utils/layer.js'; +import { GfxExtension } from './extension.js'; import { GfxBlockElementModel } from './model/gfx-block-model.js'; import type { GfxModel } from './model/model.js'; import { GfxPrimitiveElementModel } from './model/surface/element-model.js'; @@ -65,7 +67,9 @@ const typeFilters = { type FilterFunc = (model: GfxModel | GfxLocalElementModel) => boolean; -export class GridManager { +export class GridManager extends GfxExtension { + static override key = 'grid'; + private readonly _elementToGrids = new Map< GfxModel | GfxLocalElementModel, Set> @@ -361,10 +365,16 @@ export class GridManager { this.add(element); } - watch(blocks: { doc?: Store; surface?: SurfaceBlockModel | null }) { - const disposables: { dispose: () => void }[] = []; - const { doc, surface } = blocks; - const isRenderableBlock = ( + private readonly _disposables = new DisposableGroup(); + + override unmounted(): void { + this._disposables.dispose(); + } + + override mounted() { + const disposables = this._disposables; + const { store } = this.std; + const canBeRenderedAsGfxBlock = ( block: BlockModel ): block is GfxBlockElementModel => { return ( @@ -374,63 +384,75 @@ export class GridManager { ); }; - if (doc) { - disposables.push( - doc.slots.blockUpdated.on(payload => { - if (payload.type === 'add' && isRenderableBlock(payload.model)) { - this.add(payload.model); + disposables.add( + store.slots.blockUpdated.on(payload => { + if (payload.type === 'add' && canBeRenderedAsGfxBlock(payload.model)) { + this.add(payload.model); + } + + if (payload.type === 'update') { + const model = store.getBlock(payload.id) + ?.model as GfxBlockElementModel; + + if (!model) { + return; } - if (payload.type === 'update') { - const model = doc.getBlock(payload.id) - ?.model as GfxBlockElementModel; + if (payload.props.key === 'xywh' && canBeRenderedAsGfxBlock(model)) { + this.update( + store.getBlock(payload.id)?.model as GfxBlockElementModel + ); + } + } - if (!model) { + if ( + payload.type === 'delete' && + payload.model instanceof GfxBlockElementModel + ) { + this.remove(payload.model); + } + }) + ); + + Object.values(store.blocks.peek()).forEach(block => { + if (canBeRenderedAsGfxBlock(block.model)) { + this.add(block.model); + } + }); + + const watchSurface = (surface: SurfaceBlockModel) => { + let lastChildMap = new Map(surface.childMap.peek()); + disposables.add( + surface.childMap.subscribe(val => { + val.forEach((_, id) => { + if (lastChildMap.has(id)) { + lastChildMap.delete(id); return; } - - if (this._elementToGrids.has(model) && !isRenderableBlock(model)) { - this.remove(model as GfxBlockElementModel); - } else if ( - payload.props.key === 'xywh' && - isRenderableBlock(model) - ) { - this.update( - doc.getBlock(payload.id)?.model as GfxBlockElementModel - ); + }); + lastChildMap.forEach((_, id) => { + const block = store.getBlock(id); + if (block?.model) { + this.remove(block.model as GfxBlockElementModel); } - } - - if ( - payload.type === 'delete' && - payload.model instanceof GfxBlockElementModel - ) { - this.remove(payload.model); - } + }); + lastChildMap = new Map(val); }) ); - Object.values(doc.blocks.peek()).forEach(block => { - if (isRenderableBlock(block.model)) { - this.add(block.model); - } - }); - } - - if (surface) { - disposables.push( + disposables.add( surface.elementAdded.on(payload => { this.add(surface.getElementById(payload.id)!); }) ); - disposables.push( + disposables.add( surface.elementRemoved.on(payload => { this.remove(payload.model); }) ); - disposables.push( + disposables.add( surface.elementUpdated.on(payload => { if ( payload.props['xywh'] || @@ -442,13 +464,13 @@ export class GridManager { }) ); - disposables.push( + disposables.add( surface.localElementAdded.on(elm => { this.add(elm); }) ); - disposables.push( + disposables.add( surface.localElementUpdated.on(payload => { if (payload.props['xywh'] || payload.props['responseExtension']) { this.update(payload.model); @@ -456,7 +478,7 @@ export class GridManager { }) ); - disposables.push( + disposables.add( surface.localElementDeleted.on(elm => { this.remove(elm); }) @@ -468,10 +490,18 @@ export class GridManager { surface.localElementModels.forEach(model => { this.add(model); }); - } - - return () => { - disposables.forEach(d => d.dispose()); }; + + if (this.gfx.surface) { + watchSurface(this.gfx.surface); + } else { + disposables.add( + this.gfx.surface$.subscribe(surface => { + if (surface) { + watchSurface(surface); + } + }) + ); + } } } diff --git a/blocksuite/framework/block-std/src/gfx/layer.ts b/blocksuite/framework/block-std/src/gfx/layer.ts index e0ae725b33..dd2db94bb2 100644 --- a/blocksuite/framework/block-std/src/gfx/layer.ts +++ b/blocksuite/framework/block-std/src/gfx/layer.ts @@ -1,7 +1,6 @@ import { Bound } from '@blocksuite/global/gfx'; import { DisposableGroup, Slot } from '@blocksuite/global/slot'; import { assertType } from '@blocksuite/global/utils'; -import type { Store } from '@blocksuite/store'; import { generateKeyBetween } from 'fractional-indexing'; import last from 'lodash-es/last'; @@ -16,6 +15,8 @@ import { ungroupIndex, updateLayersZIndex, } from '../utils/layer.js'; +import { GfxExtension } from './extension.js'; +import type { GfxController } from './index.js'; import { type GfxGroupCompatibleInterface, isGfxGroupCompatibleModel, @@ -65,11 +66,21 @@ export type CanvasLayer = BaseLayer & { export type Layer = BlockLayer | CanvasLayer; -export class LayerManager { +export class LayerManager extends GfxExtension { + static override key = 'layerManager'; + static INITIAL_INDEX = 'a0'; private readonly _disposable = new DisposableGroup(); + private get _doc() { + return this.std.store; + } + + private get _surface() { + return this.gfx.surface; + } + blocks: GfxBlockElementModel[] = []; canvasElements: GfxPrimitiveElementModel[] = []; @@ -96,21 +107,9 @@ export class LayerManager { }>(), }; - constructor( - private readonly _doc: Store, - private _surface: SurfaceBlockModel | null, - options: { - watch: boolean; - } = { watch: true } - ) { + constructor(gfx: GfxController) { + super(gfx); this._reset(); - - if (options?.watch) { - this.watch({ - doc: _doc, - surface: _surface, - }); - } } private _buildCanvasLayers() { @@ -614,9 +613,8 @@ export class LayerManager { * @returns */ createIndexGenerator() { - const manager = new LayerManager(this._doc, this._surface, { - watch: false, - }); + const manager = new LayerManager(this.gfx); + manager._reset(); return () => { const idx = manager.generateIndex(); @@ -681,8 +679,9 @@ export class LayerManager { }); } - dispose() { + override unmounted() { this.slots.layerUpdated.dispose(); + this._disposable.dispose(); } /** @@ -792,62 +791,67 @@ export class LayerManager { } } - watch(blocks: { doc?: Store; surface: SurfaceBlockModel | null }) { - const { doc, surface } = blocks; + override mounted() { + const store = this._doc; - if (doc) { + this._disposable.add( + store.slots.blockUpdated.on(payload => { + if (payload.type === 'add') { + const block = store.getBlockById(payload.id)!; + + if ( + block instanceof GfxBlockElementModel && + (block.parent instanceof SurfaceBlockModel || + block.parent?.role === 'root') && + this.blocks.indexOf(block) === -1 + ) { + this.add(block as GfxBlockElementModel); + } + } + if (payload.type === 'update') { + const block = store.getBlockById(payload.id)!; + + if ( + (payload.props.key === 'index' || + payload.props.key === 'childElementIds') && + block instanceof GfxBlockElementModel && + (block.parent instanceof SurfaceBlockModel || + block.parent?.role === 'root') + ) { + this.update(block as GfxBlockElementModel, { + [payload.props.key]: true, + }); + } + } + if (payload.type === 'delete') { + const block = store.getBlockById(payload.id); + + if (block instanceof GfxBlockElementModel) { + this.delete(block as GfxBlockElementModel); + } + } + }) + ); + + const watchSurface = (surface: SurfaceBlockModel) => { + let lastChildMap = new Map(surface.childMap.peek()); this._disposable.add( - doc.slots.blockUpdated.on(payload => { - if (payload.type === 'add') { - const block = doc.getBlockById(payload.id)!; - - if ( - block instanceof GfxBlockElementModel && - (block.parent instanceof SurfaceBlockModel || - block.parent?.role === 'root') && - this.blocks.indexOf(block) === -1 - ) { - this.add(block as GfxBlockElementModel); + surface.childMap.subscribe(val => { + val.forEach((_, id) => { + if (lastChildMap.has(id)) { + lastChildMap.delete(id); + return; } - } - if (payload.type === 'update') { - const block = doc.getBlockById(payload.id)!; - - if ( - (payload.props.key === 'index' || - payload.props.key === 'childElementIds') && - block instanceof GfxBlockElementModel && - (block.parent instanceof SurfaceBlockModel || - block.parent?.role === 'root') - ) { - this.update(block as GfxBlockElementModel, { - [payload.props.key]: true, - }); - } else if ( - this.blocks.includes(block as GfxBlockElementModel) && - !( - block.parent instanceof SurfaceBlockModel || - block.parent?.role === 'root' - ) - ) { - this.delete(block as GfxBlockElementModel); + }); + lastChildMap.forEach((_, id) => { + const block = this._doc.getBlock(id); + if (block?.model) { + this.delete(block.model as GfxBlockElementModel); } - } - if (payload.type === 'delete') { - const block = doc.getBlockById(payload.id); - - if (block instanceof GfxBlockElementModel) { - this.delete(block as GfxBlockElementModel); - } - } + }); + lastChildMap = new Map(val); }) ); - } - - if (surface) { - if (this._surface !== surface) { - this._surface = surface; - } this._disposable.add( surface.elementAdded.on(payload => @@ -870,7 +874,7 @@ export class LayerManager { }) ); this._disposable.add( - this._surface.localElementUpdated.on(payload => { + surface.localElementUpdated.on(payload => { if (payload.props['index'] || payload.props['groupId']) { this.update(payload.model, payload.props); } @@ -881,8 +885,18 @@ export class LayerManager { this.delete(elm); }) ); + }; - surface.elementModels.forEach(el => this.add(el)); + if (this.gfx.surface) { + watchSurface(this.gfx.surface); + } else { + this._disposable.add( + this.gfx.surface$.subscribe(surface => { + if (surface) { + watchSurface(surface); + } + }) + ); } } } diff --git a/blocksuite/framework/block-std/src/scope/block-std-scope.ts b/blocksuite/framework/block-std/src/scope/block-std-scope.ts index 130d5956ba..456f3145d2 100644 --- a/blocksuite/framework/block-std/src/scope/block-std-scope.ts +++ b/blocksuite/framework/block-std/src/scope/block-std-scope.ts @@ -12,6 +12,7 @@ import { UIEventDispatcher } from '../event/index.js'; import { DndController } from '../extension/dnd/index.js'; import { EditorLifeCycleExtension } from '../extension/editor-life-cycle.js'; import { GfxController } from '../gfx/controller.js'; +import { GridManager, LayerManager } from '../gfx/index.js'; import { GfxSelectionManager } from '../gfx/selection.js'; import { SurfaceMiddlewareExtension } from '../gfx/surface-middleware.js'; import { ViewManager } from '../gfx/view/view-manager.js'; @@ -39,6 +40,8 @@ const internalExtensions = [ Clipboard, GfxController, GfxSelectionManager, + GridManager, + LayerManager, SurfaceMiddlewareExtension, ViewManager, DndController, diff --git a/packages/frontend/core/src/blocksuite/ai/utils/template-job.ts b/packages/frontend/core/src/blocksuite/ai/utils/template-job.ts index 321b194764..288979c22d 100644 --- a/packages/frontend/core/src/blocksuite/ai/utils/template-job.ts +++ b/packages/frontend/core/src/blocksuite/ai/utils/template-job.ts @@ -1,5 +1,5 @@ import type { EditorHost } from '@blocksuite/affine/block-std'; -import { LayerManager } from '@blocksuite/affine/block-std/gfx'; +import { GfxController, LayerManager } from '@blocksuite/affine/block-std/gfx'; import { getSurfaceBlock, TemplateJob, @@ -14,9 +14,7 @@ export function createTemplateJob(host: EditorHost) { } const middlewares: ((job: TemplateJob) => void)[] = []; - const layer = new LayerManager(host.doc, surface, { - watch: false, - }); + const layer = new LayerManager(host.std.get(GfxController)); const bounds = [...layer.blocks, ...layer.canvasElements].map(i => Bound.deserialize(i.xywh) );