mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-12 12:28:42 +00:00
refactor: moving connector label to connector view (#11738)
### Changed Moved connector label moving logic from `default-tool` to connector view. #### Other infrastructure changes: - Gfx element view now can handles drag events - Added `context.preventDefault()` support to bypass built-in interactions in extension - Handle the pointer events in element view will bypass the built-in interactions automatically > The built-in interactions include element dragging, click selection, drag-to-scale operations, etc.
This commit is contained in:
@@ -24,6 +24,7 @@ export type {
|
||||
ExtensionDragEndContext,
|
||||
ExtensionDragMoveContext,
|
||||
ExtensionDragStartContext,
|
||||
GfxInteractivityContext,
|
||||
SelectedContext,
|
||||
} from './interactivity/index.js';
|
||||
export {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import type { PointerEventState, UIEventState } from '../../event';
|
||||
|
||||
export type SupportedEvents =
|
||||
| 'click'
|
||||
| 'dblclick'
|
||||
@@ -5,4 +7,42 @@ export type SupportedEvents =
|
||||
| 'pointerenter'
|
||||
| 'pointerleave'
|
||||
| 'pointermove'
|
||||
| 'pointerup';
|
||||
| 'pointerup'
|
||||
| 'dragstart'
|
||||
| 'dragmove'
|
||||
| 'dragend';
|
||||
|
||||
export type GfxInteractivityContext<
|
||||
EventState extends UIEventState = PointerEventState,
|
||||
RawEvent extends Event = EventState['event'],
|
||||
> = {
|
||||
event: EventState;
|
||||
|
||||
/**
|
||||
* The raw dom event.
|
||||
*/
|
||||
raw: RawEvent;
|
||||
|
||||
/**
|
||||
* Prevent the default gfx interaction
|
||||
*/
|
||||
preventDefault: () => void;
|
||||
};
|
||||
|
||||
export const createInteractionContext = (event: PointerEventState) => {
|
||||
let preventDefaultState = false;
|
||||
|
||||
return {
|
||||
context: {
|
||||
event,
|
||||
raw: event.raw,
|
||||
preventDefault: () => {
|
||||
preventDefaultState = true;
|
||||
event.raw.preventDefault();
|
||||
},
|
||||
},
|
||||
get preventDefaultState() {
|
||||
return preventDefaultState;
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
@@ -2,11 +2,10 @@ 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 { GfxModel } from '../../model/model.js';
|
||||
import type { SupportedEvents } from '../event.js';
|
||||
import type { GfxInteractivityContext, SupportedEvents } from '../event.js';
|
||||
import type { ExtensionElementsCloneContext } from '../types/clone.js';
|
||||
import type {
|
||||
DragExtensionInitializeContext,
|
||||
@@ -64,10 +63,13 @@ export class InteractivityExtension extends Extension {
|
||||
export class InteractivityEventAPI {
|
||||
private readonly _handlersMap = new Map<
|
||||
SupportedEvents,
|
||||
((evt: PointerEventState) => void)[]
|
||||
((evt: GfxInteractivityContext) => void)[]
|
||||
>();
|
||||
|
||||
on(eventName: SupportedEvents, handler: (evt: PointerEventState) => void) {
|
||||
on(
|
||||
eventName: SupportedEvents,
|
||||
handler: (evt: GfxInteractivityContext) => void
|
||||
) {
|
||||
const handlers = this._handlersMap.get(eventName) ?? [];
|
||||
handlers.push(handler);
|
||||
this._handlersMap.set(eventName, handlers);
|
||||
@@ -81,7 +83,7 @@ export class InteractivityEventAPI {
|
||||
};
|
||||
}
|
||||
|
||||
emit(eventName: SupportedEvents, evt: PointerEventState) {
|
||||
emit(eventName: SupportedEvents, evt: GfxInteractivityContext) {
|
||||
const handlers = this._handlersMap.get(eventName);
|
||||
if (!handlers) {
|
||||
return;
|
||||
|
||||
@@ -3,14 +3,15 @@ import last from 'lodash-es/last';
|
||||
|
||||
import type { PointerEventState } from '../../event';
|
||||
import type { GfxController } from '../controller.js';
|
||||
import type { GfxElementModelView } from '../view/view.js';
|
||||
import type { GfxElementModelView, SupportedEvent } from '../view/view.js';
|
||||
|
||||
export class GfxViewEventManager {
|
||||
private _currentStackedElm: GfxElementModelView[] = [];
|
||||
private _hoveredElementsStack: GfxElementModelView[] = [];
|
||||
private _draggingElement: GfxElementModelView | null = null;
|
||||
|
||||
private _callInReverseOrder(
|
||||
callback: (view: GfxElementModelView) => void,
|
||||
arr = this._currentStackedElm
|
||||
arr = this._hoveredElementsStack
|
||||
) {
|
||||
for (let i = arr.length - 1; i >= 0; i--) {
|
||||
const view = arr[i];
|
||||
@@ -21,26 +22,52 @@ export class GfxViewEventManager {
|
||||
|
||||
constructor(private readonly gfx: GfxController) {}
|
||||
|
||||
click(_evt: PointerEventState): void {
|
||||
last(this._currentStackedElm)?.dispatch('click', _evt);
|
||||
dispatch(eventName: SupportedEvent, evt: PointerEventState) {
|
||||
if (eventName === 'pointermove') {
|
||||
this._handlePointerMove(evt);
|
||||
return false;
|
||||
} else if (eventName.startsWith('drag')) {
|
||||
return this._handleDrag(
|
||||
eventName as 'dragstart' | 'dragend' | 'dragmove',
|
||||
evt
|
||||
);
|
||||
} else {
|
||||
return last(this._hoveredElementsStack)?.dispatch(eventName, evt);
|
||||
}
|
||||
}
|
||||
|
||||
dblClick(_evt: PointerEventState): void {
|
||||
last(this._currentStackedElm)?.dispatch('dblclick', _evt);
|
||||
private _handleDrag(
|
||||
evtName: 'dragstart' | 'dragend' | 'dragmove',
|
||||
_evt: PointerEventState
|
||||
): boolean {
|
||||
switch (evtName) {
|
||||
case 'dragstart': {
|
||||
if (this._draggingElement) {
|
||||
this._draggingElement.dispatch('dragend', _evt);
|
||||
}
|
||||
this._draggingElement = last(this._hoveredElementsStack) ?? null;
|
||||
return this._draggingElement?.dispatch('dragstart', _evt) ?? false;
|
||||
}
|
||||
case 'dragmove': {
|
||||
return this._draggingElement?.dispatch('dragmove', _evt) ?? false;
|
||||
}
|
||||
case 'dragend': {
|
||||
const dispatched =
|
||||
this._draggingElement?.dispatch('dragend', _evt) ?? false;
|
||||
this._draggingElement = null;
|
||||
return dispatched;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pointerDown(_evt: PointerEventState): void {
|
||||
last(this._currentStackedElm)?.dispatch('pointerdown', _evt);
|
||||
}
|
||||
|
||||
pointerMove(_evt: PointerEventState): void {
|
||||
private _handlePointerMove(_evt: PointerEventState): void {
|
||||
const [x, y] = this.gfx.viewport.toModelCoord(_evt.x, _evt.y);
|
||||
const hoveredElmViews = this.gfx.grid
|
||||
.search(new Bound(x - 5, y - 5, 10, 10), {
|
||||
filter: ['canvas', 'local'],
|
||||
})
|
||||
.map(model => this.gfx.view.get(model)) as GfxElementModelView[];
|
||||
const currentStackedViews = new Set(this._currentStackedElm);
|
||||
const currentStackedViews = new Set(this._hoveredElementsStack);
|
||||
const visited = new Set<GfxElementModelView>();
|
||||
|
||||
this._callInReverseOrder(view => {
|
||||
@@ -54,10 +81,6 @@ export class GfxViewEventManager {
|
||||
this._callInReverseOrder(
|
||||
view => !visited.has(view) && view.dispatch('pointerleave', _evt)
|
||||
);
|
||||
this._currentStackedElm = hoveredElmViews;
|
||||
}
|
||||
|
||||
pointerUp(_evt: PointerEventState): void {
|
||||
last(this._currentStackedElm)?.dispatch('pointerup', _evt);
|
||||
this._hoveredElementsStack = hoveredElmViews;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
export type { GfxInteractivityContext } from './event.js';
|
||||
export { InteractivityExtension } from './extension/base.js';
|
||||
export { GfxViewEventManager } from './gfx-view-event-handler.js';
|
||||
export { InteractivityIdentifier, InteractivityManager } from './manager.js';
|
||||
|
||||
@@ -5,7 +5,7 @@ import { Bound, Point } from '@blocksuite/global/gfx';
|
||||
import type { PointerEventState } from '../../event/state/pointer.js';
|
||||
import { GfxExtension, GfxExtensionIdentifier } from '../extension.js';
|
||||
import type { GfxModel } from '../model/model.js';
|
||||
import type { SupportedEvents } from './event.js';
|
||||
import { createInteractionContext, type SupportedEvents } from './event.js';
|
||||
import {
|
||||
type InteractivityActionAPI,
|
||||
type InteractivityEventAPI,
|
||||
@@ -30,16 +30,6 @@ export const InteractivityIdentifier = GfxExtensionIdentifier(
|
||||
'interactivity-manager'
|
||||
) as ServiceIdentifier<InteractivityManager>;
|
||||
|
||||
const CAMEL_CASE_MAP: {
|
||||
[key in ExtensionPointerHandler]: keyof GfxViewEventManager;
|
||||
} = {
|
||||
click: 'click',
|
||||
dblclick: 'dblClick',
|
||||
pointerdown: 'pointerDown',
|
||||
pointermove: 'pointerMove',
|
||||
pointerup: 'pointerUp',
|
||||
};
|
||||
|
||||
export class InteractivityManager extends GfxExtension {
|
||||
static override key = 'interactivity-manager';
|
||||
|
||||
@@ -78,20 +68,26 @@ export class InteractivityManager extends GfxExtension {
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatch the event to canvas elements
|
||||
* Dispatch event to extensions and gfx view.
|
||||
* @param eventName
|
||||
* @param evt
|
||||
* @returns
|
||||
*/
|
||||
dispatch(eventName: ExtensionPointerHandler, evt: PointerEventState) {
|
||||
const handlerName = CAMEL_CASE_MAP[eventName];
|
||||
|
||||
this.canvasEventHandler[handlerName](evt);
|
||||
|
||||
dispatchEvent(eventName: ExtensionPointerHandler, evt: PointerEventState) {
|
||||
const { context, preventDefaultState } = createInteractionContext(evt);
|
||||
const extensions = this.interactExtensions;
|
||||
|
||||
extensions.forEach(ext => {
|
||||
(ext.event as InteractivityEventAPI).emit(eventName, evt);
|
||||
(ext.event as InteractivityEventAPI).emit(eventName, context);
|
||||
});
|
||||
|
||||
const handledByView =
|
||||
this.canvasEventHandler.dispatch(eventName, evt) ?? false;
|
||||
|
||||
return {
|
||||
preventDefaultState,
|
||||
handledByView,
|
||||
};
|
||||
}
|
||||
|
||||
dispatchOnSelected(evt: PointerEventState) {
|
||||
@@ -126,6 +122,14 @@ export class InteractivityManager extends GfxExtension {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize drag operation for elements.
|
||||
* Handles drag start, move and end events automatically.
|
||||
* Note: Call this when mouse is already down.
|
||||
*
|
||||
* @param options
|
||||
* @returns
|
||||
*/
|
||||
initializeDrag(options: DragInitializationOption) {
|
||||
let cancelledByExt = false;
|
||||
|
||||
|
||||
@@ -26,6 +26,9 @@ export type EventsHandlerMap = {
|
||||
pointerleave: PointerEventState;
|
||||
pointermove: PointerEventState;
|
||||
pointerup: PointerEventState;
|
||||
dragstart: PointerEventState;
|
||||
dragmove: PointerEventState;
|
||||
dragend: PointerEventState;
|
||||
};
|
||||
|
||||
export type SupportedEvent = keyof EventsHandlerMap;
|
||||
@@ -97,11 +100,24 @@ export class GfxElementModelView<
|
||||
return this.model.containsBound(bounds);
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatches an event to the view.
|
||||
* @param event
|
||||
* @param evt
|
||||
* @returns Whether the event view has any handlers for the event.
|
||||
*/
|
||||
dispatch<K extends keyof EventsHandlerMap>(
|
||||
event: K,
|
||||
evt: EventsHandlerMap[K]
|
||||
) {
|
||||
this._handlers.get(event)?.forEach(callback => callback(evt));
|
||||
const handlers = this._handlers.get(event);
|
||||
|
||||
if (handlers?.length) {
|
||||
handlers.forEach(callback => callback(evt));
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
getLineIntersections(start: IVec, end: IVec) {
|
||||
|
||||
Reference in New Issue
Block a user