mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-07-13 16:16:46 +08:00
@@ -0,0 +1,139 @@
|
||||
import { DisposableGroup } from '@blocksuite/global/disposable';
|
||||
|
||||
import { onSurfaceAdded } from '../../utils/gfx.js';
|
||||
import {
|
||||
type GfxBlockComponent,
|
||||
isGfxBlockComponent,
|
||||
} from '../../view/index.js';
|
||||
import type { GfxController } from '../controller.js';
|
||||
import { GfxExtension, GfxExtensionIdentifier } from '../extension.js';
|
||||
import type { GfxModel } from '../model/model.js';
|
||||
import type { GfxPrimitiveElementModel } from '../model/surface/element-model.js';
|
||||
import type { GfxLocalElementModel } from '../model/surface/local-element-model.js';
|
||||
import type { SurfaceBlockModel } from '../model/surface/surface-model.js';
|
||||
import {
|
||||
GfxElementModelView,
|
||||
GfxElementModelViewExtIdentifier,
|
||||
} from './view.js';
|
||||
|
||||
export class ViewManager extends GfxExtension {
|
||||
static override key = 'viewManager';
|
||||
|
||||
private readonly _disposable = new DisposableGroup();
|
||||
|
||||
private readonly _viewCtorMap = new Map<string, typeof GfxElementModelView>();
|
||||
|
||||
private readonly _viewMap = new Map<string, GfxElementModelView>();
|
||||
|
||||
constructor(gfx: GfxController) {
|
||||
super(gfx);
|
||||
}
|
||||
|
||||
static override extendGfx(gfx: GfxController): void {
|
||||
Object.defineProperty(gfx, 'view', {
|
||||
get() {
|
||||
return this.std.get(GfxExtensionIdentifier('viewManager'));
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
get(
|
||||
model: GfxModel | GfxLocalElementModel | string
|
||||
): GfxElementModelView | GfxBlockComponent | null {
|
||||
model = typeof model === 'string' ? model : model.id;
|
||||
|
||||
if (this._viewMap.has(model)) {
|
||||
return this._viewMap.get(model)!;
|
||||
}
|
||||
|
||||
const blockView = this.std.view.getBlock(model);
|
||||
|
||||
if (blockView && isGfxBlockComponent(blockView)) {
|
||||
return blockView;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
override mounted(): void {
|
||||
this.std.provider
|
||||
.getAll(GfxElementModelViewExtIdentifier)
|
||||
.forEach(viewCtor => {
|
||||
this._viewCtorMap.set(viewCtor.type, viewCtor);
|
||||
});
|
||||
|
||||
const updateViewOnElementChange = (surface: SurfaceBlockModel) => {
|
||||
const createView = (
|
||||
model: GfxPrimitiveElementModel | GfxLocalElementModel
|
||||
) => {
|
||||
const ViewCtor =
|
||||
this._viewCtorMap.get(model.type) ?? GfxElementModelView;
|
||||
const view = new ViewCtor(model, this.gfx);
|
||||
|
||||
this._viewMap.set(model.id, view);
|
||||
view.onCreated();
|
||||
};
|
||||
|
||||
this._disposable.add(
|
||||
surface.elementAdded.subscribe(payload => {
|
||||
const model = surface.getElementById(payload.id)!;
|
||||
createView(model);
|
||||
})
|
||||
);
|
||||
|
||||
this._disposable.add(
|
||||
surface.elementRemoved.subscribe(elem => {
|
||||
const view = this._viewMap.get(elem.id);
|
||||
this._viewMap.delete(elem.id);
|
||||
view?.onDestroyed();
|
||||
})
|
||||
);
|
||||
|
||||
this._disposable.add(
|
||||
surface.localElementAdded.subscribe(model => {
|
||||
createView(model);
|
||||
})
|
||||
);
|
||||
|
||||
this._disposable.add(
|
||||
surface.localElementDeleted.subscribe(model => {
|
||||
const view = this._viewMap.get(model.id);
|
||||
this._viewMap.delete(model.id);
|
||||
view?.onDestroyed();
|
||||
})
|
||||
);
|
||||
|
||||
surface.localElementModels.forEach(model => {
|
||||
createView(model);
|
||||
});
|
||||
|
||||
surface.elementModels.forEach(model => {
|
||||
createView(model);
|
||||
});
|
||||
};
|
||||
|
||||
if (this.gfx.surface) {
|
||||
updateViewOnElementChange(this.gfx.surface);
|
||||
} else {
|
||||
this._disposable.add(
|
||||
onSurfaceAdded(this.std.store, surface => {
|
||||
if (surface) {
|
||||
updateViewOnElementChange(surface);
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
override unmounted(): void {
|
||||
this._disposable.dispose();
|
||||
this._viewMap.forEach(view => view.onDestroyed());
|
||||
this._viewMap.clear();
|
||||
}
|
||||
}
|
||||
|
||||
declare module '../controller.js' {
|
||||
interface GfxController {
|
||||
readonly view: ViewManager;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,221 @@
|
||||
import { type Container, createIdentifier } from '@blocksuite/global/di';
|
||||
import { DisposableGroup } from '@blocksuite/global/disposable';
|
||||
import { BlockSuiteError, ErrorCode } from '@blocksuite/global/exceptions';
|
||||
import type { Bound, IVec } from '@blocksuite/global/gfx';
|
||||
import type { Extension } from '@blocksuite/store';
|
||||
|
||||
import type { PointerEventState } from '../../event/index.js';
|
||||
import type { EditorHost } from '../../view/index.js';
|
||||
import type {
|
||||
DragEndContext,
|
||||
DragMoveContext,
|
||||
DragStartContext,
|
||||
GfxViewTransformInterface,
|
||||
SelectedContext,
|
||||
} from '../element-transform/view-transform.js';
|
||||
import type { GfxController } from '../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';
|
||||
|
||||
export type EventsHandlerMap = {
|
||||
click: PointerEventState;
|
||||
dblclick: PointerEventState;
|
||||
pointerdown: PointerEventState;
|
||||
pointerenter: PointerEventState;
|
||||
pointerleave: PointerEventState;
|
||||
pointermove: PointerEventState;
|
||||
pointerup: PointerEventState;
|
||||
};
|
||||
|
||||
export type SupportedEvent = keyof EventsHandlerMap;
|
||||
|
||||
export const GfxElementModelViewExtIdentifier = createIdentifier<
|
||||
typeof GfxElementModelView
|
||||
>('GfxElementModelView');
|
||||
|
||||
export class GfxElementModelView<
|
||||
T extends GfxLocalElementModel | GfxPrimitiveElementModel =
|
||||
| GfxPrimitiveElementModel
|
||||
| GfxLocalElementModel,
|
||||
RendererContext = object,
|
||||
>
|
||||
implements GfxElementGeometry, Extension, GfxViewTransformInterface
|
||||
{
|
||||
static type: string;
|
||||
|
||||
private readonly _handlers = new Map<
|
||||
keyof EventsHandlerMap,
|
||||
((evt: any) => void)[]
|
||||
>();
|
||||
|
||||
private _isConnected = true;
|
||||
|
||||
protected disposable = new DisposableGroup();
|
||||
|
||||
readonly model: T;
|
||||
|
||||
get isConnected() {
|
||||
return this._isConnected;
|
||||
}
|
||||
|
||||
get rotate() {
|
||||
return this.model.rotate;
|
||||
}
|
||||
|
||||
get surface() {
|
||||
return this.model.surface;
|
||||
}
|
||||
|
||||
get type() {
|
||||
return this.model.type;
|
||||
}
|
||||
|
||||
get std() {
|
||||
return this.gfx.std;
|
||||
}
|
||||
|
||||
constructor(
|
||||
model: T,
|
||||
readonly gfx: GfxController
|
||||
) {
|
||||
this.model = model;
|
||||
}
|
||||
|
||||
static setup(di: Container): void {
|
||||
if (!this.type) {
|
||||
throw new BlockSuiteError(
|
||||
ErrorCode.ValueNotExists,
|
||||
'The GfxElementModelView should have a static `type` property.'
|
||||
);
|
||||
}
|
||||
|
||||
di.addImpl(GfxElementModelViewExtIdentifier(this.type), () => this);
|
||||
}
|
||||
|
||||
containsBound(bounds: Bound): boolean {
|
||||
return this.model.containsBound(bounds);
|
||||
}
|
||||
|
||||
dispatch<K extends keyof EventsHandlerMap>(
|
||||
event: K,
|
||||
evt: EventsHandlerMap[K]
|
||||
) {
|
||||
this._handlers.get(event)?.forEach(callback => callback(evt));
|
||||
}
|
||||
|
||||
getLineIntersections(start: IVec, end: IVec) {
|
||||
return this.model.getLineIntersections(start, end);
|
||||
}
|
||||
|
||||
getNearestPoint(point: IVec) {
|
||||
return this.model.getNearestPoint(point);
|
||||
}
|
||||
|
||||
getRelativePointLocation(relativePoint: IVec) {
|
||||
return this.model.getRelativePointLocation(relativePoint);
|
||||
}
|
||||
|
||||
includesPoint(
|
||||
x: number,
|
||||
y: number,
|
||||
_: PointTestOptions,
|
||||
__: EditorHost
|
||||
): boolean {
|
||||
return this.model.includesPoint(x, y, _, __);
|
||||
}
|
||||
|
||||
intersectsBound(bound: Bound): boolean {
|
||||
return (
|
||||
this.containsBound(bound) ||
|
||||
bound.points.some((point, i, points) =>
|
||||
this.getLineIntersections(point, points[(i + 1) % points.length])
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
off<K extends keyof EventsHandlerMap>(
|
||||
event: K,
|
||||
callback: (evt: EventsHandlerMap[K]) => void
|
||||
) {
|
||||
if (!this._handlers.has(event)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const callbacks = this._handlers.get(event)!;
|
||||
const index = callbacks.indexOf(callback);
|
||||
|
||||
if (index !== -1) {
|
||||
callbacks.splice(index, 1);
|
||||
}
|
||||
}
|
||||
|
||||
on<K extends keyof EventsHandlerMap>(
|
||||
event: K,
|
||||
callback: (evt: EventsHandlerMap[K]) => void
|
||||
) {
|
||||
if (!this._handlers.has(event)) {
|
||||
this._handlers.set(event, []);
|
||||
}
|
||||
|
||||
this._handlers.get(event)!.push(callback);
|
||||
|
||||
return () => this.off(event, callback);
|
||||
}
|
||||
|
||||
once<K extends keyof EventsHandlerMap>(
|
||||
event: K,
|
||||
callback: (evt: EventsHandlerMap[K]) => void
|
||||
) {
|
||||
const off = this.on(event, evt => {
|
||||
off();
|
||||
callback(evt);
|
||||
});
|
||||
|
||||
return off;
|
||||
}
|
||||
|
||||
onCreated() {}
|
||||
|
||||
onDragStart(_: DragStartContext) {
|
||||
if (this.model instanceof GfxPrimitiveElementModel) {
|
||||
this.model.stash('xywh');
|
||||
}
|
||||
}
|
||||
|
||||
onDragEnd(_: DragEndContext) {
|
||||
if (this.model instanceof GfxPrimitiveElementModel) {
|
||||
this.model.pop('xywh');
|
||||
}
|
||||
}
|
||||
|
||||
onDragMove({ dx, dy, currentBound }: DragMoveContext) {
|
||||
this.model.xywh = currentBound.moveDelta(dx, dy).serialize();
|
||||
}
|
||||
|
||||
onSelected(context: SelectedContext) {
|
||||
if (this.model instanceof GfxPrimitiveElementModel) {
|
||||
if (context.multiSelect) {
|
||||
this.gfx.selection.toggle(this.model);
|
||||
} else {
|
||||
this.gfx.selection.set({ elements: [this.model.id] });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onResize = () => {};
|
||||
|
||||
onRotate = () => {};
|
||||
|
||||
/**
|
||||
* Called when the view is destroyed.
|
||||
* Override this method requires calling `super.onDestroyed()`.
|
||||
*/
|
||||
onDestroyed() {
|
||||
this._isConnected = false;
|
||||
this.disposable.dispose();
|
||||
this._handlers.clear();
|
||||
}
|
||||
|
||||
render(_: RendererContext) {}
|
||||
}
|
||||
Reference in New Issue
Block a user