mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-13 04:48:53 +00:00
refactor: redesign element transform manager interface (#11679)
### Change - Rename `ElementTransformManager` -> `InteractivityManager` - Now you can `event.on` and `action.onXXX` method to extend interactivity behaviour. The old approach of overriding methods directly is deprecated.
This commit is contained in:
@@ -12,29 +12,26 @@ export {
|
||||
} from '../utils/tree.js';
|
||||
export { GfxController } from './controller.js';
|
||||
export type { CursorType, StandardCursor } from './cursor.js';
|
||||
export type {
|
||||
DragExtensionInitializeContext,
|
||||
DragInitializationOption,
|
||||
ExtensionDragEndContext,
|
||||
ExtensionDragMoveContext,
|
||||
ExtensionDragStartContext,
|
||||
} from './element-transform/drag.js';
|
||||
export { CanvasEventHandler } from './element-transform/extension/canvas-event-handler.js';
|
||||
export {
|
||||
ElementTransformManager,
|
||||
TransformExtension,
|
||||
TransformExtensionIdentifier,
|
||||
TransformManagerIdentifier,
|
||||
} from './element-transform/transform-manager.js';
|
||||
export type {
|
||||
DragEndContext,
|
||||
DragMoveContext,
|
||||
DragStartContext,
|
||||
} from './element-transform/view-transform.js';
|
||||
export { type SelectedContext } from './element-transform/view-transform.js';
|
||||
export { GfxExtension, GfxExtensionIdentifier } from './extension.js';
|
||||
export { GridManager } from './grid.js';
|
||||
export { GfxControllerIdentifier } from './identifiers.js';
|
||||
export type {
|
||||
DragEndContext,
|
||||
DragExtensionInitializeContext,
|
||||
DragInitializationOption,
|
||||
DragMoveContext,
|
||||
DragStartContext,
|
||||
ExtensionDragEndContext,
|
||||
ExtensionDragMoveContext,
|
||||
ExtensionDragStartContext,
|
||||
SelectedContext,
|
||||
} from './interactivity/index.js';
|
||||
export {
|
||||
GfxViewEventManager,
|
||||
InteractivityExtension,
|
||||
InteractivityIdentifier,
|
||||
InteractivityManager,
|
||||
} from './interactivity/index.js';
|
||||
export { LayerManager, type ReorderingDirection } from './layer.js';
|
||||
export type {
|
||||
GfxCompatibleInterface,
|
||||
|
||||
8
blocksuite/framework/std/src/gfx/interactivity/event.ts
Normal file
8
blocksuite/framework/std/src/gfx/interactivity/event.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
export type SupportedEvents =
|
||||
| 'click'
|
||||
| 'dblclick'
|
||||
| 'pointerdown'
|
||||
| 'pointerenter'
|
||||
| 'pointerleave'
|
||||
| 'pointermove'
|
||||
| 'pointerup';
|
||||
141
blocksuite/framework/std/src/gfx/interactivity/extension/base.ts
Normal file
141
blocksuite/framework/std/src/gfx/interactivity/extension/base.ts
Normal file
@@ -0,0 +1,141 @@
|
||||
import { type Container, createIdentifier } from '@blocksuite/global/di';
|
||||
import { BlockSuiteError, ErrorCode } from '@blocksuite/global/exceptions';
|
||||
import { Extension } from '@blocksuite/store';
|
||||
|
||||
import type { PointerEventState } from '../../../event/index.js';
|
||||
import type { GfxController } from '../../controller.js';
|
||||
import { GfxControllerIdentifier } from '../../identifiers.js';
|
||||
import type { SupportedEvents } from '../event.js';
|
||||
import type {
|
||||
DragExtensionInitializeContext,
|
||||
ExtensionDragEndContext,
|
||||
ExtensionDragMoveContext,
|
||||
ExtensionDragStartContext,
|
||||
} from '../types/drag.js';
|
||||
|
||||
export const InteractivityExtensionIdentifier =
|
||||
createIdentifier<InteractivityExtension>('interactivity-extension');
|
||||
|
||||
export class InteractivityExtension extends Extension {
|
||||
static key: string;
|
||||
|
||||
get std() {
|
||||
return this.gfx.std;
|
||||
}
|
||||
|
||||
event: Omit<InteractivityEventAPI, 'emit'> = new InteractivityEventAPI();
|
||||
|
||||
action: Omit<InteractivityActionAPI, 'emit'> = new InteractivityActionAPI();
|
||||
|
||||
constructor(protected readonly gfx: GfxController) {
|
||||
super();
|
||||
}
|
||||
|
||||
mounted() {}
|
||||
|
||||
/**
|
||||
* Override this method should call `super.unmounted()`
|
||||
*/
|
||||
unmounted() {
|
||||
this.event.destroy();
|
||||
this.action.destroy();
|
||||
}
|
||||
|
||||
static override setup(di: Container) {
|
||||
if (!this.key) {
|
||||
throw new BlockSuiteError(
|
||||
ErrorCode.ValueNotExists,
|
||||
'key is not defined in the InteractivityExtension'
|
||||
);
|
||||
}
|
||||
|
||||
di.add(
|
||||
this as unknown as { new (gfx: GfxController): InteractivityExtension },
|
||||
[GfxControllerIdentifier]
|
||||
);
|
||||
di.addImpl(InteractivityExtensionIdentifier(this.key), provider =>
|
||||
provider.get(this)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export class InteractivityEventAPI {
|
||||
private readonly _handlersMap = new Map<
|
||||
SupportedEvents,
|
||||
((evt: PointerEventState) => void)[]
|
||||
>();
|
||||
|
||||
on(eventName: SupportedEvents, handler: (evt: PointerEventState) => void) {
|
||||
const handlers = this._handlersMap.get(eventName) ?? [];
|
||||
handlers.push(handler);
|
||||
this._handlersMap.set(eventName, handlers);
|
||||
|
||||
return () => {
|
||||
const idx = handlers.indexOf(handler);
|
||||
|
||||
if (idx > -1) {
|
||||
handlers.splice(idx, 1);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
emit(eventName: SupportedEvents, evt: PointerEventState) {
|
||||
const handlers = this._handlersMap.get(eventName);
|
||||
if (!handlers) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const handler of handlers) {
|
||||
handler(evt);
|
||||
}
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this._handlersMap.clear();
|
||||
}
|
||||
}
|
||||
|
||||
type ActionContextMap = {
|
||||
dragInitialize: {
|
||||
context: DragExtensionInitializeContext;
|
||||
returnType: {
|
||||
onDragStart?: (context: ExtensionDragStartContext) => void;
|
||||
onDragMove?: (context: ExtensionDragMoveContext) => void;
|
||||
onDragEnd?: (context: ExtensionDragEndContext) => void;
|
||||
clear?: () => void;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
export class InteractivityActionAPI {
|
||||
private readonly _handlers: Partial<{
|
||||
dragInitialize: Parameters<InteractivityActionAPI['onDragInitialize']>[0];
|
||||
}> = {};
|
||||
|
||||
onDragInitialize(
|
||||
handler: (
|
||||
ctx: ActionContextMap['dragInitialize']['context']
|
||||
) => ActionContextMap['dragInitialize']['returnType']
|
||||
) {
|
||||
this._handlers['dragInitialize'] = handler;
|
||||
|
||||
return () => {
|
||||
delete this._handlers['dragInitialize'];
|
||||
};
|
||||
}
|
||||
|
||||
emit<K extends keyof ActionContextMap>(
|
||||
event: K,
|
||||
context: ActionContextMap[K]['context']
|
||||
): ActionContextMap[K]['returnType'] | undefined {
|
||||
const handler = this._handlers[event];
|
||||
|
||||
return handler?.(context);
|
||||
}
|
||||
|
||||
destroy() {
|
||||
for (const key in this._handlers) {
|
||||
delete this._handlers[key as keyof typeof this._handlers];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,11 @@
|
||||
import { Bound } from '@blocksuite/global/gfx';
|
||||
import last from 'lodash-es/last';
|
||||
|
||||
import type { PointerEventState } from '../../../event';
|
||||
import type { GfxController } from '../..';
|
||||
import type { GfxElementModelView } from '../../view/view';
|
||||
import type { PointerEventState } from '../../event';
|
||||
import type { GfxController } from '../controller.js';
|
||||
import type { GfxElementModelView } from '../view/view.js';
|
||||
|
||||
export class CanvasEventHandler {
|
||||
export class GfxViewEventManager {
|
||||
private _currentStackedElm: GfxElementModelView[] = [];
|
||||
|
||||
private _callInReverseOrder(
|
||||
17
blocksuite/framework/std/src/gfx/interactivity/index.ts
Normal file
17
blocksuite/framework/std/src/gfx/interactivity/index.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
export { InteractivityExtension } from './extension/base.js';
|
||||
export { GfxViewEventManager } from './gfx-view-event-handler.js';
|
||||
export { InteractivityIdentifier, InteractivityManager } from './manager.js';
|
||||
export type {
|
||||
DragExtensionInitializeContext,
|
||||
DragInitializationOption,
|
||||
ExtensionDragEndContext,
|
||||
ExtensionDragMoveContext,
|
||||
ExtensionDragStartContext,
|
||||
} from './types/drag.js';
|
||||
export type {
|
||||
DragEndContext,
|
||||
DragMoveContext,
|
||||
DragStartContext,
|
||||
GfxViewTransformInterface,
|
||||
SelectedContext,
|
||||
} from './types/view.js';
|
||||
@@ -1,39 +1,36 @@
|
||||
import {
|
||||
type Container,
|
||||
createIdentifier,
|
||||
type ServiceIdentifier,
|
||||
} from '@blocksuite/global/di';
|
||||
import { type ServiceIdentifier } from '@blocksuite/global/di';
|
||||
import { DisposableGroup } from '@blocksuite/global/disposable';
|
||||
import { BlockSuiteError, ErrorCode } from '@blocksuite/global/exceptions';
|
||||
import { Bound, Point } from '@blocksuite/global/gfx';
|
||||
import { Extension } from '@blocksuite/store';
|
||||
|
||||
import type { PointerEventState } from '../../event/state/pointer.js';
|
||||
import { type GfxController } from '../controller.js';
|
||||
import { GfxExtension, GfxExtensionIdentifier } from '../extension.js';
|
||||
import { GfxControllerIdentifier } from '../identifiers.js';
|
||||
import type { GfxModel } from '../model/model.js';
|
||||
import { type SupportedEvent } from '../view/view.js';
|
||||
import type { SupportedEvents } from './event.js';
|
||||
import {
|
||||
type InteractivityActionAPI,
|
||||
type InteractivityEventAPI,
|
||||
InteractivityExtensionIdentifier,
|
||||
} from './extension/base.js';
|
||||
import { GfxViewEventManager } from './gfx-view-event-handler.js';
|
||||
import type {
|
||||
DragExtensionInitializeContext,
|
||||
DragInitializationOption,
|
||||
ExtensionDragEndContext,
|
||||
ExtensionDragMoveContext,
|
||||
ExtensionDragStartContext,
|
||||
} from './drag.js';
|
||||
import { CanvasEventHandler } from './extension/canvas-event-handler.js';
|
||||
} from './types/drag.js';
|
||||
|
||||
type ExtensionPointerHandler = Exclude<
|
||||
SupportedEvent,
|
||||
SupportedEvents,
|
||||
'pointerleave' | 'pointerenter'
|
||||
>;
|
||||
|
||||
export const TransformManagerIdentifier = GfxExtensionIdentifier(
|
||||
'element-transform-manager'
|
||||
) as ServiceIdentifier<ElementTransformManager>;
|
||||
export const InteractivityIdentifier = GfxExtensionIdentifier(
|
||||
'interactivity-manager'
|
||||
) as ServiceIdentifier<InteractivityManager>;
|
||||
|
||||
const CAMEL_CASE_MAP: {
|
||||
[key in ExtensionPointerHandler]: keyof CanvasEventHandler;
|
||||
[key in ExtensionPointerHandler]: keyof GfxViewEventManager;
|
||||
} = {
|
||||
click: 'click',
|
||||
dblclick: 'dblClick',
|
||||
@@ -42,23 +39,29 @@ const CAMEL_CASE_MAP: {
|
||||
pointerup: 'pointerUp',
|
||||
};
|
||||
|
||||
export class ElementTransformManager extends GfxExtension {
|
||||
static override key = 'element-transform-manager';
|
||||
export class InteractivityManager extends GfxExtension {
|
||||
static override key = 'interactivity-manager';
|
||||
|
||||
private readonly _disposable = new DisposableGroup();
|
||||
|
||||
private canvasEventHandler = new CanvasEventHandler(this.gfx);
|
||||
private canvasEventHandler = new GfxViewEventManager(this.gfx);
|
||||
|
||||
override mounted(): void {
|
||||
this.canvasEventHandler = new CanvasEventHandler(this.gfx);
|
||||
this.canvasEventHandler = new GfxViewEventManager(this.gfx);
|
||||
this.interactExtensions.forEach(ext => {
|
||||
ext.mounted();
|
||||
});
|
||||
}
|
||||
|
||||
override unmounted(): void {
|
||||
this._disposable.dispose();
|
||||
this.interactExtensions.forEach(ext => {
|
||||
ext.unmounted();
|
||||
});
|
||||
}
|
||||
|
||||
get transformExtensions() {
|
||||
return this.std.provider.getAll(TransformExtensionIdentifier);
|
||||
get interactExtensions() {
|
||||
return this.std.provider.getAll(InteractivityExtensionIdentifier);
|
||||
}
|
||||
|
||||
get keyboard() {
|
||||
@@ -83,10 +86,10 @@ export class ElementTransformManager extends GfxExtension {
|
||||
|
||||
this.canvasEventHandler[handlerName](evt);
|
||||
|
||||
const extension = this.transformExtensions;
|
||||
const extensions = this.interactExtensions;
|
||||
|
||||
extension.forEach(ext => {
|
||||
ext[handlerName]?.(evt);
|
||||
extensions.forEach(ext => {
|
||||
(ext.event as InteractivityEventAPI).emit(eventName, evt);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -142,15 +145,18 @@ export class ElementTransformManager extends GfxExtension {
|
||||
])
|
||||
),
|
||||
};
|
||||
const extension = this.transformExtensions;
|
||||
const extension = this.interactExtensions;
|
||||
const activeExtensionHandlers = Array.from(
|
||||
extension.values().map(ext => {
|
||||
return ext.onDragInitialize(context);
|
||||
return (ext.action as InteractivityActionAPI).emit(
|
||||
'dragInitialize',
|
||||
context
|
||||
);
|
||||
})
|
||||
);
|
||||
|
||||
if (cancelledByExt) {
|
||||
activeExtensionHandlers.forEach(handler => handler.clear?.());
|
||||
activeExtensionHandlers.forEach(handler => handler?.clear?.());
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -198,7 +204,7 @@ export class ElementTransformManager extends GfxExtension {
|
||||
|
||||
this._safeExecute(() => {
|
||||
activeExtensionHandlers.forEach(handler =>
|
||||
handler.onDragMove?.(moveContext)
|
||||
handler?.onDragMove?.(moveContext)
|
||||
);
|
||||
}, 'Error while executing extension `onDragMove`');
|
||||
|
||||
@@ -231,7 +237,7 @@ export class ElementTransformManager extends GfxExtension {
|
||||
|
||||
this._safeExecute(() => {
|
||||
activeExtensionHandlers.forEach(handler =>
|
||||
handler.onDragEnd?.(endContext)
|
||||
handler?.onDragEnd?.(endContext)
|
||||
);
|
||||
}, 'Error while executing extension `onDragEnd` handler');
|
||||
|
||||
@@ -249,7 +255,7 @@ export class ElementTransformManager extends GfxExtension {
|
||||
});
|
||||
|
||||
this._safeExecute(() => {
|
||||
activeExtensionHandlers.forEach(handler => handler.clear?.());
|
||||
activeExtensionHandlers.forEach(handler => handler?.clear?.());
|
||||
}, 'Error while executing extension `clear` handler');
|
||||
|
||||
options.onDragEnd?.();
|
||||
@@ -274,7 +280,7 @@ export class ElementTransformManager extends GfxExtension {
|
||||
|
||||
this._safeExecute(() => {
|
||||
activeExtensionHandlers.forEach(handler =>
|
||||
handler.onDragStart?.(dragStartContext)
|
||||
handler?.onDragStart?.(dragStartContext)
|
||||
);
|
||||
}, 'Error while executing extension `onDragStart` handler');
|
||||
};
|
||||
@@ -283,58 +289,3 @@ export class ElementTransformManager extends GfxExtension {
|
||||
dragStart();
|
||||
}
|
||||
}
|
||||
|
||||
export const TransformExtensionIdentifier =
|
||||
createIdentifier<TransformExtension>('element-transform-extension');
|
||||
|
||||
export class TransformExtension extends Extension {
|
||||
static key: string;
|
||||
|
||||
get std() {
|
||||
return this.gfx.std;
|
||||
}
|
||||
|
||||
constructor(protected readonly gfx: GfxController) {
|
||||
super();
|
||||
}
|
||||
|
||||
mounted() {}
|
||||
|
||||
unmounted() {}
|
||||
|
||||
click(_: PointerEventState) {}
|
||||
|
||||
dblClick(_: PointerEventState) {}
|
||||
|
||||
pointerDown(_: PointerEventState) {}
|
||||
|
||||
pointerMove(_: PointerEventState) {}
|
||||
|
||||
pointerUp(_: PointerEventState) {}
|
||||
|
||||
onDragInitialize(_: DragExtensionInitializeContext): {
|
||||
onDragStart?: (context: ExtensionDragStartContext) => void;
|
||||
onDragMove?: (context: ExtensionDragMoveContext) => void;
|
||||
onDragEnd?: (context: ExtensionDragEndContext) => void;
|
||||
clear?: () => void;
|
||||
} {
|
||||
return {};
|
||||
}
|
||||
|
||||
static override setup(di: Container) {
|
||||
if (!this.key) {
|
||||
throw new BlockSuiteError(
|
||||
ErrorCode.ValueNotExists,
|
||||
'key is not defined in the TransformExtension'
|
||||
);
|
||||
}
|
||||
|
||||
di.add(
|
||||
this as unknown as { new (gfx: GfxController): TransformExtension },
|
||||
[GfxControllerIdentifier]
|
||||
);
|
||||
di.addImpl(TransformExtensionIdentifier(this.key), provider =>
|
||||
provider.get(this)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
import type { Bound } from '@blocksuite/global/gfx';
|
||||
|
||||
import type { GfxBlockComponent } from '../../view';
|
||||
import type { GfxModel } from '../model/model';
|
||||
import type { GfxElementModelView } from '../view/view';
|
||||
import type { GfxBlockComponent } from '../../../view';
|
||||
import type { GfxModel } from '../../model/model';
|
||||
import type { GfxElementModelView } from '../../view/view';
|
||||
|
||||
export type DragInitializationOption = {
|
||||
movingElements: GfxModel[];
|
||||
@@ -1,8 +1,8 @@
|
||||
import type { Bound, IPoint } from '@blocksuite/global/gfx';
|
||||
|
||||
import type { GfxBlockComponent } from '../../view';
|
||||
import type { GfxModel } from '../model/model';
|
||||
import type { GfxElementModelView } from '../view/view';
|
||||
import type { GfxBlockComponent } from '../../../view/element/gfx-block-component.js';
|
||||
import type { GfxModel } from '../../model/model.js';
|
||||
import type { GfxElementModelView } from '../../view/view.js';
|
||||
|
||||
export type DragStartContext = {
|
||||
/**
|
||||
@@ -6,14 +6,14 @@ import type { Extension } from '@blocksuite/store';
|
||||
|
||||
import type { PointerEventState } from '../../event/index.js';
|
||||
import type { EditorHost } from '../../view/index.js';
|
||||
import type { GfxController } from '../index.js';
|
||||
import type {
|
||||
DragEndContext,
|
||||
DragMoveContext,
|
||||
DragStartContext,
|
||||
GfxViewTransformInterface,
|
||||
SelectedContext,
|
||||
} from '../element-transform/view-transform.js';
|
||||
import type { GfxController } from '../index.js';
|
||||
} from '../interactivity/index.js';
|
||||
import type { GfxElementGeometry, PointTestOptions } from '../model/base.js';
|
||||
import { GfxPrimitiveElementModel } from '../model/surface/element-model.js';
|
||||
import type { GfxLocalElementModel } from '../model/surface/local-element-model.js';
|
||||
|
||||
@@ -4,12 +4,12 @@ import { computed, effect, signal } from '@preact/signals-core';
|
||||
import { nothing } from 'lit';
|
||||
|
||||
import type { BlockService } from '../../extension/index.js';
|
||||
import { GfxControllerIdentifier } from '../../gfx/identifiers.js';
|
||||
import type {
|
||||
DragMoveContext,
|
||||
GfxViewTransformInterface,
|
||||
SelectedContext,
|
||||
} from '../../gfx/element-transform/view-transform.js';
|
||||
import { GfxControllerIdentifier } from '../../gfx/identifiers.js';
|
||||
} from '../../gfx/interactivity/index.js';
|
||||
import { type GfxBlockElementModel } from '../../gfx/model/gfx-block-model.js';
|
||||
import { SurfaceSelection } from '../../selection/index.js';
|
||||
import { BlockComponent } from './block-component.js';
|
||||
|
||||
Reference in New Issue
Block a user