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 _surface: SurfaceBlockModel | null = null;
private readonly _surface$ = new Signal<SurfaceBlockModel | null>(null);
readonly cursor$ = new Signal<CursorType>();
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);

View File

@@ -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<Set<GfxModel | GfxLocalElementModel>>
@@ -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);
}
})
);
}
}
}

View File

@@ -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<GfxPrimitiveElementModel> & {
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);
}
})
);
}
}
}

View File

@@ -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,