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
This commit is contained in:
doouding
2025-03-07 04:40:05 +00:00
parent 0a234fa263
commit 460e088873
5 changed files with 187 additions and 143 deletions

View File

@@ -38,18 +38,22 @@ export class GfxController extends LifeCycleWatcher {
private readonly _disposables: DisposableGroup = new DisposableGroup(); private readonly _disposables: DisposableGroup = new DisposableGroup();
private _surface: SurfaceBlockModel | null = null; private readonly _surface$ = new Signal<SurfaceBlockModel | null>(null);
readonly cursor$ = new Signal<CursorType>(); readonly cursor$ = new Signal<CursorType>();
readonly grid: GridManager;
readonly keyboard: KeyboardController; readonly keyboard: KeyboardController;
readonly layer: LayerManager;
readonly viewport: Viewport = new Viewport(); readonly viewport: Viewport = new Viewport();
get grid() {
return this.std.get(GridManager);
}
get layer() {
return this.std.get(LayerManager);
}
get doc() { get doc() {
return this.std.store; return this.std.store;
} }
@@ -62,8 +66,12 @@ export class GfxController extends LifeCycleWatcher {
return [...this.layer.blocks, ...this.layer.canvasElements]; return [...this.layer.blocks, ...this.layer.canvasElements];
} }
get surface$() {
return this._surface$;
}
get surface() { get surface() {
return this._surface; return this._surface$.peek();
} }
get surfaceComponent(): BlockComponent | null { get surfaceComponent(): BlockComponent | null {
@@ -75,22 +83,13 @@ export class GfxController extends LifeCycleWatcher {
constructor(std: BlockStdScope) { constructor(std: BlockStdScope) {
super(std); super(std);
this.grid = new GridManager();
this.layer = new LayerManager(this.doc, null);
this.keyboard = new KeyboardController(std); this.keyboard = new KeyboardController(std);
this._disposables.add( this._disposables.add(
onSurfaceAdded(this.doc, surface => { onSurfaceAdded(this.doc, surface => {
this._surface = surface; this._surface$.value = surface;
if (surface) {
this._disposables.add(this.grid.watch({ surface }));
this.layer.watch({ surface });
}
}) })
); );
this._disposables.add(this.grid.watch({ doc: this.doc }));
this._disposables.add(this.layer);
this._disposables.add(this.viewport); this._disposables.add(this.viewport);
this._disposables.add(this.keyboard); this._disposables.add(this.keyboard);

View File

@@ -4,9 +4,11 @@ import {
getBoundWithRotation, getBoundWithRotation,
intersects, intersects,
} from '@blocksuite/global/gfx'; } 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 { compare } from '../utils/layer.js';
import { GfxExtension } from './extension.js';
import { GfxBlockElementModel } from './model/gfx-block-model.js'; import { GfxBlockElementModel } from './model/gfx-block-model.js';
import type { GfxModel } from './model/model.js'; import type { GfxModel } from './model/model.js';
import { GfxPrimitiveElementModel } from './model/surface/element-model.js'; import { GfxPrimitiveElementModel } from './model/surface/element-model.js';
@@ -65,7 +67,9 @@ const typeFilters = {
type FilterFunc = (model: GfxModel | GfxLocalElementModel) => boolean; type FilterFunc = (model: GfxModel | GfxLocalElementModel) => boolean;
export class GridManager { export class GridManager extends GfxExtension {
static override key = 'grid';
private readonly _elementToGrids = new Map< private readonly _elementToGrids = new Map<
GfxModel | GfxLocalElementModel, GfxModel | GfxLocalElementModel,
Set<Set<GfxModel | GfxLocalElementModel>> Set<Set<GfxModel | GfxLocalElementModel>>
@@ -361,10 +365,16 @@ export class GridManager {
this.add(element); this.add(element);
} }
watch(blocks: { doc?: Store; surface?: SurfaceBlockModel | null }) { private readonly _disposables = new DisposableGroup();
const disposables: { dispose: () => void }[] = [];
const { doc, surface } = blocks; override unmounted(): void {
const isRenderableBlock = ( this._disposables.dispose();
}
override mounted() {
const disposables = this._disposables;
const { store } = this.std;
const canBeRenderedAsGfxBlock = (
block: BlockModel block: BlockModel
): block is GfxBlockElementModel => { ): block is GfxBlockElementModel => {
return ( return (
@@ -374,63 +384,75 @@ export class GridManager {
); );
}; };
if (doc) { disposables.add(
disposables.push( store.slots.blockUpdated.on(payload => {
doc.slots.blockUpdated.on(payload => { if (payload.type === 'add' && canBeRenderedAsGfxBlock(payload.model)) {
if (payload.type === 'add' && isRenderableBlock(payload.model)) { this.add(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') { if (payload.props.key === 'xywh' && canBeRenderedAsGfxBlock(model)) {
const model = doc.getBlock(payload.id) this.update(
?.model as GfxBlockElementModel; 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; return;
} }
});
if (this._elementToGrids.has(model) && !isRenderableBlock(model)) { lastChildMap.forEach((_, id) => {
this.remove(model as GfxBlockElementModel); const block = store.getBlock(id);
} else if ( if (block?.model) {
payload.props.key === 'xywh' && this.remove(block.model as GfxBlockElementModel);
isRenderableBlock(model)
) {
this.update(
doc.getBlock(payload.id)?.model as GfxBlockElementModel
);
} }
} });
lastChildMap = new Map(val);
if (
payload.type === 'delete' &&
payload.model instanceof GfxBlockElementModel
) {
this.remove(payload.model);
}
}) })
); );
Object.values(doc.blocks.peek()).forEach(block => { disposables.add(
if (isRenderableBlock(block.model)) {
this.add(block.model);
}
});
}
if (surface) {
disposables.push(
surface.elementAdded.on(payload => { surface.elementAdded.on(payload => {
this.add(surface.getElementById(payload.id)!); this.add(surface.getElementById(payload.id)!);
}) })
); );
disposables.push( disposables.add(
surface.elementRemoved.on(payload => { surface.elementRemoved.on(payload => {
this.remove(payload.model); this.remove(payload.model);
}) })
); );
disposables.push( disposables.add(
surface.elementUpdated.on(payload => { surface.elementUpdated.on(payload => {
if ( if (
payload.props['xywh'] || payload.props['xywh'] ||
@@ -442,13 +464,13 @@ export class GridManager {
}) })
); );
disposables.push( disposables.add(
surface.localElementAdded.on(elm => { surface.localElementAdded.on(elm => {
this.add(elm); this.add(elm);
}) })
); );
disposables.push( disposables.add(
surface.localElementUpdated.on(payload => { surface.localElementUpdated.on(payload => {
if (payload.props['xywh'] || payload.props['responseExtension']) { if (payload.props['xywh'] || payload.props['responseExtension']) {
this.update(payload.model); this.update(payload.model);
@@ -456,7 +478,7 @@ export class GridManager {
}) })
); );
disposables.push( disposables.add(
surface.localElementDeleted.on(elm => { surface.localElementDeleted.on(elm => {
this.remove(elm); this.remove(elm);
}) })
@@ -468,10 +490,18 @@ export class GridManager {
surface.localElementModels.forEach(model => { surface.localElementModels.forEach(model => {
this.add(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);
}
})
);
}
} }
} }

View File

@@ -1,7 +1,6 @@
import { Bound } from '@blocksuite/global/gfx'; import { Bound } from '@blocksuite/global/gfx';
import { DisposableGroup, Slot } from '@blocksuite/global/slot'; import { DisposableGroup, Slot } from '@blocksuite/global/slot';
import { assertType } from '@blocksuite/global/utils'; import { assertType } from '@blocksuite/global/utils';
import type { Store } from '@blocksuite/store';
import { generateKeyBetween } from 'fractional-indexing'; import { generateKeyBetween } from 'fractional-indexing';
import last from 'lodash-es/last'; import last from 'lodash-es/last';
@@ -16,6 +15,8 @@ import {
ungroupIndex, ungroupIndex,
updateLayersZIndex, updateLayersZIndex,
} from '../utils/layer.js'; } from '../utils/layer.js';
import { GfxExtension } from './extension.js';
import type { GfxController } from './index.js';
import { import {
type GfxGroupCompatibleInterface, type GfxGroupCompatibleInterface,
isGfxGroupCompatibleModel, isGfxGroupCompatibleModel,
@@ -65,11 +66,21 @@ export type CanvasLayer = BaseLayer<GfxPrimitiveElementModel> & {
export type Layer = BlockLayer | CanvasLayer; export type Layer = BlockLayer | CanvasLayer;
export class LayerManager { export class LayerManager extends GfxExtension {
static override key = 'layerManager';
static INITIAL_INDEX = 'a0'; static INITIAL_INDEX = 'a0';
private readonly _disposable = new DisposableGroup(); private readonly _disposable = new DisposableGroup();
private get _doc() {
return this.std.store;
}
private get _surface() {
return this.gfx.surface;
}
blocks: GfxBlockElementModel[] = []; blocks: GfxBlockElementModel[] = [];
canvasElements: GfxPrimitiveElementModel[] = []; canvasElements: GfxPrimitiveElementModel[] = [];
@@ -96,21 +107,9 @@ export class LayerManager {
}>(), }>(),
}; };
constructor( constructor(gfx: GfxController) {
private readonly _doc: Store, super(gfx);
private _surface: SurfaceBlockModel | null,
options: {
watch: boolean;
} = { watch: true }
) {
this._reset(); this._reset();
if (options?.watch) {
this.watch({
doc: _doc,
surface: _surface,
});
}
} }
private _buildCanvasLayers() { private _buildCanvasLayers() {
@@ -614,9 +613,8 @@ export class LayerManager {
* @returns * @returns
*/ */
createIndexGenerator() { createIndexGenerator() {
const manager = new LayerManager(this._doc, this._surface, { const manager = new LayerManager(this.gfx);
watch: false, manager._reset();
});
return () => { return () => {
const idx = manager.generateIndex(); const idx = manager.generateIndex();
@@ -681,8 +679,9 @@ export class LayerManager {
}); });
} }
dispose() { override unmounted() {
this.slots.layerUpdated.dispose(); this.slots.layerUpdated.dispose();
this._disposable.dispose();
} }
/** /**
@@ -792,62 +791,67 @@ export class LayerManager {
} }
} }
watch(blocks: { doc?: Store; surface: SurfaceBlockModel | null }) { override mounted() {
const { doc, surface } = blocks; 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( this._disposable.add(
doc.slots.blockUpdated.on(payload => { surface.childMap.subscribe(val => {
if (payload.type === 'add') { val.forEach((_, id) => {
const block = doc.getBlockById(payload.id)!; if (lastChildMap.has(id)) {
lastChildMap.delete(id);
if ( return;
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') { lastChildMap.forEach((_, id) => {
const block = doc.getBlockById(payload.id)!; const block = this._doc.getBlock(id);
if (block?.model) {
if ( this.delete(block.model as GfxBlockElementModel);
(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);
} }
} });
if (payload.type === 'delete') { lastChildMap = new Map(val);
const block = doc.getBlockById(payload.id);
if (block instanceof GfxBlockElementModel) {
this.delete(block as GfxBlockElementModel);
}
}
}) })
); );
}
if (surface) {
if (this._surface !== surface) {
this._surface = surface;
}
this._disposable.add( this._disposable.add(
surface.elementAdded.on(payload => surface.elementAdded.on(payload =>
@@ -870,7 +874,7 @@ export class LayerManager {
}) })
); );
this._disposable.add( this._disposable.add(
this._surface.localElementUpdated.on(payload => { surface.localElementUpdated.on(payload => {
if (payload.props['index'] || payload.props['groupId']) { if (payload.props['index'] || payload.props['groupId']) {
this.update(payload.model, payload.props); this.update(payload.model, payload.props);
} }
@@ -881,8 +885,18 @@ export class LayerManager {
this.delete(elm); 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);
}
})
);
} }
} }
} }

View File

@@ -12,6 +12,7 @@ import { UIEventDispatcher } from '../event/index.js';
import { DndController } from '../extension/dnd/index.js'; import { DndController } from '../extension/dnd/index.js';
import { EditorLifeCycleExtension } from '../extension/editor-life-cycle.js'; import { EditorLifeCycleExtension } from '../extension/editor-life-cycle.js';
import { GfxController } from '../gfx/controller.js'; import { GfxController } from '../gfx/controller.js';
import { GridManager, LayerManager } from '../gfx/index.js';
import { GfxSelectionManager } from '../gfx/selection.js'; import { GfxSelectionManager } from '../gfx/selection.js';
import { SurfaceMiddlewareExtension } from '../gfx/surface-middleware.js'; import { SurfaceMiddlewareExtension } from '../gfx/surface-middleware.js';
import { ViewManager } from '../gfx/view/view-manager.js'; import { ViewManager } from '../gfx/view/view-manager.js';
@@ -39,6 +40,8 @@ const internalExtensions = [
Clipboard, Clipboard,
GfxController, GfxController,
GfxSelectionManager, GfxSelectionManager,
GridManager,
LayerManager,
SurfaceMiddlewareExtension, SurfaceMiddlewareExtension,
ViewManager, ViewManager,
DndController, DndController,

View File

@@ -1,5 +1,5 @@
import type { EditorHost } from '@blocksuite/affine/block-std'; 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 { import {
getSurfaceBlock, getSurfaceBlock,
TemplateJob, TemplateJob,
@@ -14,9 +14,7 @@ export function createTemplateJob(host: EditorHost) {
} }
const middlewares: ((job: TemplateJob) => void)[] = []; const middlewares: ((job: TemplateJob) => void)[] = [];
const layer = new LayerManager(host.doc, surface, { const layer = new LayerManager(host.std.get(GfxController));
watch: false,
});
const bounds = [...layer.blocks, ...layer.canvasElements].map(i => const bounds = [...layer.blocks, ...layer.canvasElements].map(i =>
Bound.deserialize(i.xywh) Bound.deserialize(i.xywh)
); );