mirror of
https://github.com/alibaba/lowcode-engine.git
synced 2025-12-15 14:00:35 +00:00
sim 20%
This commit is contained in:
parent
b566524466
commit
a49ec4a171
@ -1,8 +1,16 @@
|
||||
import Project from '../project';
|
||||
import { RootSchema, NodeData, isDOMText, isJSExpression } from '../schema';
|
||||
import Node from './node/node';
|
||||
import { RootSchema, NodeData, isDOMText, isJSExpression, NodeSchema } from '../schema';
|
||||
import Node, { isNodeParent, insertChildren, insertChild, NodeParent } from './node/node';
|
||||
import { Selection } from './selection';
|
||||
import RootNode from './node/root-node';
|
||||
import { SimulatorInterface } from '../simulator-interface';
|
||||
import { computed } from '@recore/obx';
|
||||
|
||||
export default class DocumentContext {
|
||||
/**
|
||||
* 根节点 类型有:Page/Component/Block
|
||||
*/
|
||||
readonly rootNode: RootNode;
|
||||
/**
|
||||
* 文档编号
|
||||
*/
|
||||
@ -14,23 +22,35 @@ export default class DocumentContext {
|
||||
/**
|
||||
* 操作记录控制
|
||||
*/
|
||||
readonly history: History = new History(this);
|
||||
/**
|
||||
* 根节点 类型有:Page/Component/Block
|
||||
*/
|
||||
readonly root: Root;
|
||||
// TODO
|
||||
// readonly history: History = new History(this);
|
||||
/**
|
||||
* 模拟器
|
||||
*/
|
||||
simulator?: SimulatorInterface;
|
||||
|
||||
private nodesMap = new Map<string, INode>();
|
||||
private nodes = new Set<INode>();
|
||||
private nodesMap = new Map<string, Node>();
|
||||
private nodes = new Set<Node>();
|
||||
private seqId = 0;
|
||||
|
||||
get fileName() {
|
||||
return this.rootNode.extras.get('fileName')?.value as string;
|
||||
}
|
||||
|
||||
set fileName(fileName: string) {
|
||||
this.rootNode.extras.get('fileName', true).value = fileName;
|
||||
}
|
||||
|
||||
constructor(readonly project: Project, schema: RootSchema) {
|
||||
this.id = uniqueId('doc');
|
||||
this.root = new Root(this, viewData);
|
||||
this.rootNode = new RootNode(this, schema);
|
||||
this.id = this.rootNode.id;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成唯一id
|
||||
*/
|
||||
nextId() {
|
||||
return (++this.seqId).toString(36).toLocaleLowerCase();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -40,19 +60,22 @@ export default class DocumentContext {
|
||||
return this.nodesMap.get(id) || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否存在节点
|
||||
*/
|
||||
hasNode(id: string): boolean {
|
||||
const node = this.getNode(id);
|
||||
return node ? !node.isPurged : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据 schema 创建一个节点
|
||||
*/
|
||||
createNode(data: NodeData): Node {
|
||||
let schema: any;
|
||||
if (isDOMText(data)) {
|
||||
if (isDOMText(data) || isJSExpression(data)) {
|
||||
schema = {
|
||||
componentName: '#text',
|
||||
children: data,
|
||||
};
|
||||
} else if (isJSExpression(data)) {
|
||||
schema = {
|
||||
componentName: '#expression',
|
||||
componentName: '#frag',
|
||||
children: data,
|
||||
};
|
||||
} else {
|
||||
@ -63,12 +86,21 @@ export default class DocumentContext {
|
||||
this.nodes.add(node);
|
||||
return node;
|
||||
}
|
||||
|
||||
/**
|
||||
* 插入一个节点
|
||||
*/
|
||||
insertNode(parent: Node, thing: Node | Schema, at?: number | null, copy?: boolean): Node {
|
||||
|
||||
insertNode(parent: NodeParent, thing: Node | NodeData, at?: number | null, copy?: boolean): Node {
|
||||
return insertChild(parent, thing, at, copy);
|
||||
}
|
||||
|
||||
/**
|
||||
* 插入多个节点
|
||||
*/
|
||||
insertNodes(parent: NodeParent, thing: Node[] | NodeData[], at?: number | null, copy?: boolean) {
|
||||
return insertChildren(parent, thing, at, copy);
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除一个节点
|
||||
*/
|
||||
@ -82,30 +114,88 @@ export default class DocumentContext {
|
||||
node = idOrNode;
|
||||
id = node.id;
|
||||
}
|
||||
if (!node || !node.parent) {
|
||||
if (!node) {
|
||||
return;
|
||||
}
|
||||
// TODO: 考虑留着缓存
|
||||
this.nodesMap.delete(id);
|
||||
this.nodes.delete(node);
|
||||
node.parent.removeChild(node);
|
||||
this.internalRemoveAndPurgeNode(node);
|
||||
}
|
||||
|
||||
/**
|
||||
* 内部方法,请勿调用
|
||||
*/
|
||||
internalRemoveAndPurgeNode(node: Node) {
|
||||
if (!this.nodes.has(node)) {
|
||||
return;
|
||||
}
|
||||
this.nodesMap.delete(node.id);
|
||||
this.nodes.delete(node);
|
||||
node.remove();
|
||||
}
|
||||
|
||||
/**
|
||||
* 包裹当前选区中的节点
|
||||
*/
|
||||
wrapWith(schema: NodeSchema): Node | null {
|
||||
const nodes = this.selection.getTopNodes();
|
||||
if (nodes.length < 1) {
|
||||
return null;
|
||||
}
|
||||
const wrapper = this.createNode(schema);
|
||||
if (isNodeParent(wrapper)) {
|
||||
const first = nodes[0];
|
||||
// TODO: check nesting rules x 2
|
||||
insertChild(first.parent!, wrapper, first.index);
|
||||
insertChildren(wrapper, nodes);
|
||||
this.selection.select(wrapper.id);
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
this.removeNode(wrapper);
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出 schema 数据
|
||||
*/
|
||||
getSchema(): Schema {
|
||||
return this.root.getSchema();
|
||||
get schema(): NodeSchema {
|
||||
return this.rootNode.schema;
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出节点 Schema
|
||||
* 导出节点数据
|
||||
*/
|
||||
getNodeSchema(id: string): Schema | null {
|
||||
getNodeSchema(id: string): NodeData | null {
|
||||
const node = this.getNode(id);
|
||||
if (node) {
|
||||
return node.getSchema();
|
||||
return node.schema;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否已修改
|
||||
*/
|
||||
isModified() {
|
||||
// return !this.history.isSavePoint();
|
||||
}
|
||||
|
||||
@computed get simulatorProps(): object {
|
||||
let simulatorProps = this.project.simulatorProps;
|
||||
if (typeof simulatorProps === 'function') {
|
||||
simulatorProps = simulatorProps(this);
|
||||
}
|
||||
return {
|
||||
...simulatorProps,
|
||||
documentContext: this,
|
||||
onMount: this.mountSimulator.bind(this),
|
||||
};
|
||||
}
|
||||
|
||||
private mountSimulator(simulator: SimulatorInterface) {
|
||||
this.simulator = simulator;
|
||||
// TODO: emit simulator mounted
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据节点取得视图实例,在循环等场景会有多个,依赖 simulator 的接口
|
||||
*/
|
||||
@ -115,6 +205,7 @@ export default class DocumentContext {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过 DOM 节点获取节点,依赖 simulator 的接口
|
||||
*/
|
||||
@ -129,6 +220,7 @@ export default class DocumentContext {
|
||||
}
|
||||
return this.getNode(id) as Node;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得到的结果是一个数组
|
||||
* 表示一个实例对应多个外层 DOM 节点,依赖 simulator 的接口
|
||||
@ -144,34 +236,28 @@ export default class DocumentContext {
|
||||
|
||||
return this.simulator.findDOMNodes(viewInstance);
|
||||
}
|
||||
|
||||
getComponent(componentName: string): any {
|
||||
return this.simulator!.getCurrentComponent(componentName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 激活当前文档
|
||||
* 激活
|
||||
*/
|
||||
active(): void {}
|
||||
|
||||
/**
|
||||
* 不激活
|
||||
*/
|
||||
suspense(): void {}
|
||||
/**
|
||||
* 销毁
|
||||
*/
|
||||
destroy(): void {}
|
||||
|
||||
/**
|
||||
* 是否已修改
|
||||
* 开启
|
||||
*/
|
||||
isModified() {
|
||||
return !this.history.isSavePoint();
|
||||
}
|
||||
open(): void {}
|
||||
|
||||
/**
|
||||
* 生成唯一id
|
||||
* 关闭
|
||||
*/
|
||||
nextId() {
|
||||
return (++this.seqId).toString(36).toLocaleLowerCase();
|
||||
}
|
||||
|
||||
getComponent(tagName: string): any {
|
||||
return this.simulator!.getCurrentComponent(tagName);
|
||||
}
|
||||
close(): void {}
|
||||
}
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
import { INode, INodeParent } from './node/node';
|
||||
import { INode, NodeParent } from './node/node';
|
||||
import DocumentContext from './document-context';
|
||||
|
||||
export interface LocationData {
|
||||
target: INodeParent; // shadowNode | ConditionFlow | ElementNode | RootNode
|
||||
target: NodeParent; // shadowNode | ConditionFlow | ElementNode | RootNode
|
||||
detail: LocationDetail;
|
||||
}
|
||||
|
||||
@ -113,7 +113,7 @@ export function getWindow(elem: Element | Document): Window {
|
||||
}
|
||||
|
||||
export default class Location {
|
||||
readonly target: INodeParent;
|
||||
readonly target: NodeParent;
|
||||
readonly detail: LocationDetail;
|
||||
|
||||
constructor(readonly document: DocumentContext, { target, detail }: LocationData) {
|
||||
|
||||
@ -1,733 +0,0 @@
|
||||
import RootNode from './root-node';
|
||||
import { flags, panes } from '../globals';
|
||||
import {
|
||||
dragon,
|
||||
ISenseAble,
|
||||
isShaken,
|
||||
LocateEvent,
|
||||
isNodesDragTarget,
|
||||
NodesDragTarget,
|
||||
NodeDatasDragTarget,
|
||||
isNodeDatasDragTarget,
|
||||
DragTargetType,
|
||||
isAnyDragTarget,
|
||||
} from '../globals/dragon';
|
||||
import cursor from '../utils/cursor';
|
||||
import {
|
||||
INode,
|
||||
isElementNode,
|
||||
isNode,
|
||||
INodeParent,
|
||||
insertChildren,
|
||||
hasConditionFlow,
|
||||
contains,
|
||||
isRootNode,
|
||||
isConfettiNode,
|
||||
} from './node/node';
|
||||
import {
|
||||
Point,
|
||||
Rect,
|
||||
getRectTarget,
|
||||
isChildInline,
|
||||
isRowContainer,
|
||||
LocationDetailType,
|
||||
LocationChildrenDetail,
|
||||
isLocationChildrenDetail,
|
||||
LocationData,
|
||||
isLocationData,
|
||||
} from './location';
|
||||
import { isConditionFlow } from './node/condition-flow';
|
||||
import { isElementData, NodeData } from './document-data';
|
||||
import ElementNode from './node/element-node';
|
||||
import { AT_CHILD } from '../prototype/prototype';
|
||||
import Scroller from './scroller';
|
||||
import { isShadowNode } from './node/shadow-node';
|
||||
import { activeTracker } from '../globals/active-tracker';
|
||||
import { edging } from '../globals/edging';
|
||||
import { setNativeSelection } from '../utils/navtive-selection';
|
||||
import DocumentContext from './document-context';
|
||||
import Simulator from '../adaptors/simulator';
|
||||
import { focusing } from '../globals/focusing';
|
||||
import embedEditor from '../globals/embed-editor';
|
||||
|
||||
export const MasterBoardID = 'master-board';
|
||||
export default class MasterBoard implements ISenseAble {
|
||||
id = MasterBoardID;
|
||||
sensitive = true;
|
||||
readonly contentDocument: Document;
|
||||
|
||||
private simulator: Simulator<any, any>;
|
||||
private sensing = false;
|
||||
private scroller: Scroller;
|
||||
|
||||
get bounds() {
|
||||
const vw = this.document.viewport;
|
||||
const bounds = vw.bounds;
|
||||
const innerBounds = vw.innerBounds;
|
||||
const doe = this.contentDocument.documentElement;
|
||||
return {
|
||||
top: bounds.top,
|
||||
left: bounds.left,
|
||||
right: bounds.right,
|
||||
bottom: bounds.bottom,
|
||||
width: bounds.width,
|
||||
height: bounds.height,
|
||||
innerBounds,
|
||||
scale: vw.scale,
|
||||
scrollHeight: doe.scrollHeight,
|
||||
scrollWidth: doe.scrollWidth,
|
||||
};
|
||||
}
|
||||
|
||||
constructor(readonly document: DocumentContext, frame: HTMLIFrameElement) {
|
||||
this.simulator = document.simulator!;
|
||||
this.contentDocument = frame.contentDocument!;
|
||||
this.scroller = new Scroller(this, document.viewport.scrollTarget!);
|
||||
const doc = this.contentDocument;
|
||||
const selection = document.selection;
|
||||
|
||||
// TODO: think of lock when edit a node
|
||||
// 事件路由
|
||||
doc.addEventListener('mousedown', (downEvent: MouseEvent) => {
|
||||
/*
|
||||
if (embedEditor.editing) {
|
||||
return;
|
||||
}
|
||||
*/
|
||||
const target = document.getNodeFromElement(downEvent.target as Element);
|
||||
panes.dockingStation.visible = false;
|
||||
focusing.focus('canvas');
|
||||
if (!target) {
|
||||
selection.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
const isMulti = downEvent.metaKey || downEvent.ctrlKey;
|
||||
const isLeftButton = downEvent.which === 1 || downEvent.button === 0;
|
||||
|
||||
if (isLeftButton) {
|
||||
let node: INode = target;
|
||||
if (hasConditionFlow(node)) {
|
||||
node = node.conditionFlow;
|
||||
}
|
||||
let nodes: INode[] = [node];
|
||||
let ignoreUpSelected = false;
|
||||
if (isMulti) {
|
||||
// multi select mode, directily add
|
||||
if (!selection.has(node.id)) {
|
||||
activeTracker.track(node);
|
||||
selection.add(node.id);
|
||||
ignoreUpSelected = true;
|
||||
}
|
||||
// 获得顶层 nodes
|
||||
nodes = selection.getTopNodes();
|
||||
} else if (selection.containsNode(target)) {
|
||||
nodes = selection.getTopNodes();
|
||||
} else {
|
||||
// will clear current selection & select dragment in dragstart
|
||||
}
|
||||
dragon.boost(
|
||||
{
|
||||
type: DragTargetType.Nodes,
|
||||
nodes,
|
||||
},
|
||||
downEvent,
|
||||
);
|
||||
if (ignoreUpSelected) {
|
||||
// multi select mode has add selected, should return
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const checkSelect = (e: MouseEvent) => {
|
||||
doc.removeEventListener('mouseup', checkSelect, true);
|
||||
if (!isShaken(downEvent, e)) {
|
||||
// const node = hasConditionFlow(target) ? target.conditionFlow : target;
|
||||
const node = target;
|
||||
const id = node.id;
|
||||
activeTracker.track(node);
|
||||
if (isMulti && selection.has(id)) {
|
||||
selection.del(id);
|
||||
} else {
|
||||
selection.select(id);
|
||||
}
|
||||
}
|
||||
};
|
||||
doc.addEventListener('mouseup', checkSelect, true);
|
||||
});
|
||||
|
||||
dragon.onDragstart(({ dragTarget }) => {
|
||||
if (this.disableEdging) {
|
||||
this.disableEdging();
|
||||
}
|
||||
if (isNodesDragTarget(dragTarget) && dragTarget.nodes.length === 1) {
|
||||
// ensure current selecting
|
||||
selection.select(dragTarget.nodes[0].id);
|
||||
}
|
||||
flags.setDragComponentsMode(true);
|
||||
});
|
||||
|
||||
dragon.onDragend(({ dragTarget, copy }) => {
|
||||
const loc = this.document.dropLocation;
|
||||
flags.setDragComponentsMode(false);
|
||||
if (loc) {
|
||||
if (!isConditionFlow(loc.target)) {
|
||||
if (isLocationChildrenDetail(loc.detail)) {
|
||||
let nodes: INode[] | undefined;
|
||||
if (isNodesDragTarget(dragTarget)) {
|
||||
nodes = insertChildren(loc.target, dragTarget.nodes, loc.detail.index, copy);
|
||||
} else if (isNodeDatasDragTarget(dragTarget)) {
|
||||
// process nodeData
|
||||
const nodesData = this.document.processDocumentData(dragTarget.data, dragTarget.maps);
|
||||
nodes = insertChildren(loc.target, nodesData, loc.detail.index);
|
||||
}
|
||||
if (nodes) {
|
||||
this.document.selection.selectAll(nodes.map(o => o.id));
|
||||
setTimeout(() => activeTracker.track(nodes![0]), 10);
|
||||
}
|
||||
}
|
||||
// TODO: others...
|
||||
}
|
||||
}
|
||||
this.document.clearLocation();
|
||||
this.enableEdging();
|
||||
});
|
||||
|
||||
// cause edit
|
||||
doc.addEventListener('dblclick', (e: MouseEvent) => {
|
||||
// TODO: refactor
|
||||
let target = document.getNodeFromElement(e.target as Element)!;
|
||||
if (target && isElementNode(target)) {
|
||||
if (isShadowNode(target)) {
|
||||
target = target.origin;
|
||||
}
|
||||
if (target.children.length === 1 && isConfettiNode(target.children[0])) {
|
||||
// test
|
||||
// embedEditor.edit(target as any, 'children', document.getDOMNodes(target) as any);
|
||||
|
||||
activeTracker.track(target.children[0]);
|
||||
selection.select(target.children[0].id);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
activeTracker.onChange(({ node, detail }) => {
|
||||
this.scrollToNode(node, detail);
|
||||
});
|
||||
|
||||
this.enableEdging();
|
||||
}
|
||||
|
||||
private disableEdging: (() => void) | undefined;
|
||||
|
||||
enableEdging() {
|
||||
const edgingWatch = (e: Event) => {
|
||||
const node = this.document.getNodeFromElement(e.target as Element);
|
||||
edging.watch(node);
|
||||
e.stopPropagation();
|
||||
};
|
||||
const leave = () => edging.watch(null);
|
||||
|
||||
this.contentDocument.addEventListener('mouseover', edgingWatch, true);
|
||||
this.contentDocument.addEventListener('mouseleave', leave, false);
|
||||
|
||||
// TODO: refactor this line, contains click, mousedown, mousemove
|
||||
this.contentDocument.addEventListener(
|
||||
'mousemove',
|
||||
(e: Event) => {
|
||||
e.stopPropagation();
|
||||
},
|
||||
true,
|
||||
);
|
||||
|
||||
this.disableEdging = () => {
|
||||
edging.watch(null);
|
||||
this.contentDocument.removeEventListener('mouseover', edgingWatch, true);
|
||||
this.contentDocument.removeEventListener('mouseleave', leave, false);
|
||||
};
|
||||
}
|
||||
|
||||
setNativeSelection(enableFlag: boolean) {
|
||||
setNativeSelection(enableFlag);
|
||||
this.simulator.utils.setNativeSelection(enableFlag);
|
||||
}
|
||||
|
||||
setDragging(flag: boolean) {
|
||||
cursor.setDragging(flag);
|
||||
this.simulator.utils.cursor.setDragging(flag);
|
||||
}
|
||||
|
||||
setCopy(flag: boolean) {
|
||||
cursor.setCopy(flag);
|
||||
this.simulator.utils.cursor.setCopy(flag);
|
||||
}
|
||||
|
||||
isCopy(): boolean {
|
||||
return this.simulator.utils.cursor.isCopy();
|
||||
}
|
||||
|
||||
releaseCursor() {
|
||||
cursor.release();
|
||||
this.simulator.utils.cursor.release();
|
||||
}
|
||||
|
||||
getDropTarget(e: LocateEvent): INodeParent | LocationData | null {
|
||||
const { target, dragTarget } = e;
|
||||
const isAny = isAnyDragTarget(dragTarget);
|
||||
let container: any;
|
||||
if (target) {
|
||||
const ref = this.document.getNodeFromElement(target as Element);
|
||||
if (ref) {
|
||||
container = ref;
|
||||
} else if (isAny) {
|
||||
return null;
|
||||
} else {
|
||||
container = this.document.view;
|
||||
}
|
||||
} else if (isAny) {
|
||||
return null;
|
||||
} else {
|
||||
container = this.document.view;
|
||||
}
|
||||
|
||||
if (!isElementNode(container) && !isRootNode(container)) {
|
||||
container = container.parent;
|
||||
}
|
||||
|
||||
// use spec container to accept specialData
|
||||
if (isAny) {
|
||||
while (container) {
|
||||
if (isRootNode(container)) {
|
||||
return null;
|
||||
}
|
||||
const locationData = this.acceptAnyData(container, e);
|
||||
if (locationData) {
|
||||
return locationData;
|
||||
}
|
||||
container = container.parent;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
let res: any;
|
||||
let upward: any;
|
||||
// TODO: improve AT_CHILD logic, mark has checked
|
||||
while (container) {
|
||||
res = this.acceptNodes(container, e);
|
||||
if (isLocationData(res)) {
|
||||
return res;
|
||||
}
|
||||
if (res === true) {
|
||||
return container;
|
||||
}
|
||||
if (!res) {
|
||||
if (upward) {
|
||||
container = upward;
|
||||
upward = null;
|
||||
} else {
|
||||
container = container.parent;
|
||||
}
|
||||
} else if (res === AT_CHILD) {
|
||||
if (!upward) {
|
||||
upward = container.parent;
|
||||
}
|
||||
container = this.getNearByContainer(container, e);
|
||||
if (!container) {
|
||||
container = upward;
|
||||
upward = null;
|
||||
}
|
||||
} else if (isNode(res)) {
|
||||
container = res;
|
||||
upward = null;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
acceptNodes(container: RootNode | ElementNode, e: LocateEvent) {
|
||||
const { dragTarget } = e;
|
||||
if (isRootNode(container)) {
|
||||
return this.checkDropTarget(container, dragTarget as any);
|
||||
}
|
||||
|
||||
const proto = container.prototype;
|
||||
|
||||
const acceptable: boolean = this.isAcceptable(container);
|
||||
if (!proto.isContainer && !acceptable) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// check is contains, get common parent
|
||||
if (isNodesDragTarget(dragTarget)) {
|
||||
const nodes = dragTarget.nodes;
|
||||
let i = nodes.length;
|
||||
let p: any = container;
|
||||
while (i-- > 0) {
|
||||
if (contains(nodes[i], p)) {
|
||||
p = nodes[i].parent;
|
||||
}
|
||||
}
|
||||
if (p !== container) {
|
||||
return p || this.document.view;
|
||||
}
|
||||
}
|
||||
|
||||
// first use accept
|
||||
if (acceptable) {
|
||||
const view: any = this.document.getView(container);
|
||||
if (view && '$accept' in view) {
|
||||
if (view.$accept === false) {
|
||||
return false;
|
||||
}
|
||||
if (view.$accept === AT_CHILD || view.$accept === '@CHILD') {
|
||||
return AT_CHILD;
|
||||
}
|
||||
if (typeof view.$accept === 'function') {
|
||||
const ret = view.$accept(container, e);
|
||||
if (ret || ret === false) {
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (proto.acceptable) {
|
||||
const ret = proto.accept(container, e);
|
||||
if (ret || ret === false) {
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return this.checkNesting(container, dragTarget as any);
|
||||
}
|
||||
|
||||
getNearByContainer(container: INodeParent, e: LocateEvent): INodeParent | null {
|
||||
const children = container.children;
|
||||
if (!children || children.length < 1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let nearDistance: any = null;
|
||||
let nearBy: any = null;
|
||||
for (let i = 0, l = children.length; i < l; i++) {
|
||||
let child: any = children[i];
|
||||
if (!isElementNode(child)) {
|
||||
continue;
|
||||
}
|
||||
if (hasConditionFlow(child)) {
|
||||
const bn = child.conditionFlow;
|
||||
i = bn.index + bn.length - 1;
|
||||
child = bn.visibleNode;
|
||||
}
|
||||
const rect = this.document.computeRect(child);
|
||||
if (!rect) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (isPointInRect(e, rect)) {
|
||||
return child;
|
||||
}
|
||||
|
||||
const distance = distanceToRect(e, rect);
|
||||
if (nearDistance === null || distance < nearDistance) {
|
||||
nearDistance = distance;
|
||||
nearBy = child;
|
||||
}
|
||||
}
|
||||
|
||||
return nearBy;
|
||||
}
|
||||
|
||||
locate(e: LocateEvent): any {
|
||||
this.sensing = true;
|
||||
this.scroller.scrolling(e);
|
||||
const dropTarget = this.getDropTarget(e);
|
||||
if (!dropTarget) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (isLocationData(dropTarget)) {
|
||||
return this.document.createLocation(dropTarget);
|
||||
}
|
||||
|
||||
const target = dropTarget;
|
||||
|
||||
const edge = this.document.computeRect(target);
|
||||
|
||||
const children = target.children;
|
||||
|
||||
const detail: LocationChildrenDetail = {
|
||||
type: LocationDetailType.Children,
|
||||
index: 0,
|
||||
};
|
||||
|
||||
const locationData = {
|
||||
target,
|
||||
detail,
|
||||
};
|
||||
|
||||
if (!children || children.length < 1 || !edge) {
|
||||
return this.document.createLocation(locationData);
|
||||
}
|
||||
|
||||
let nearRect = null;
|
||||
let nearIndex = 0;
|
||||
let nearNode = null;
|
||||
let nearDistance = null;
|
||||
let top = null;
|
||||
let bottom = null;
|
||||
|
||||
for (let i = 0, l = children.length; i < l; i++) {
|
||||
let node = children[i];
|
||||
let index = i;
|
||||
if (hasConditionFlow(node)) {
|
||||
node = node.conditionFlow;
|
||||
index = node.index;
|
||||
// skip flow items
|
||||
i = index + (node as any).length - 1;
|
||||
}
|
||||
const rect = this.document.computeRect(node);
|
||||
|
||||
if (!rect) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const distance = isPointInRect(e, rect) ? 0 : distanceToRect(e, rect);
|
||||
|
||||
if (distance === 0) {
|
||||
nearDistance = distance;
|
||||
nearNode = node;
|
||||
nearIndex = index;
|
||||
nearRect = rect;
|
||||
break;
|
||||
}
|
||||
|
||||
// TODO: 忘记为什么这么处理了,记得添加注释
|
||||
if (top === null || rect.top < top) {
|
||||
top = rect.top;
|
||||
}
|
||||
if (bottom === null || rect.bottom > bottom) {
|
||||
bottom = rect.bottom;
|
||||
}
|
||||
|
||||
if (nearDistance === null || distance < nearDistance) {
|
||||
nearDistance = distance;
|
||||
nearNode = node;
|
||||
nearIndex = index;
|
||||
nearRect = rect;
|
||||
}
|
||||
}
|
||||
|
||||
detail.index = nearIndex;
|
||||
|
||||
if (nearNode && nearRect) {
|
||||
const el = getRectTarget(nearRect);
|
||||
const inline = el ? isChildInline(el) : false;
|
||||
const row = el ? isRowContainer(el.parentElement!) : false;
|
||||
const vertical = inline || row;
|
||||
// TODO: fix type
|
||||
const near: any = {
|
||||
node: nearNode,
|
||||
pos: 'before',
|
||||
align: vertical ? 'V' : 'H',
|
||||
};
|
||||
detail.near = near;
|
||||
if (isNearAfter(e, nearRect, vertical)) {
|
||||
near.pos = 'after';
|
||||
detail.index = nearIndex + (isConditionFlow(nearNode) ? nearNode.length : 1);
|
||||
}
|
||||
if (!row && nearDistance !== 0) {
|
||||
const edgeDistance = distanceToEdge(e, edge);
|
||||
if (edgeDistance.distance < nearDistance!) {
|
||||
const nearAfter = edgeDistance.nearAfter;
|
||||
if (top == null) {
|
||||
top = edge.top;
|
||||
}
|
||||
if (bottom == null) {
|
||||
bottom = edge.bottom;
|
||||
}
|
||||
near.rect = new DOMRect(edge.left, top, edge.width, bottom - top);
|
||||
near.align = 'H';
|
||||
near.pos = nearAfter ? 'after' : 'before';
|
||||
detail.index = nearAfter ? children.length : 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return this.document.createLocation(locationData);
|
||||
}
|
||||
|
||||
private tryScrollAgain: number | null = null;
|
||||
scrollToNode(node: INode, detail?: any, tryTimes = 0) {
|
||||
this.tryScrollAgain = null;
|
||||
if (this.sensing) {
|
||||
// actived sensor
|
||||
return;
|
||||
}
|
||||
|
||||
const opt: any = {};
|
||||
let scroll = false;
|
||||
|
||||
if (detail) {
|
||||
// TODO:
|
||||
/*
|
||||
const rect = insertion ? insertion.getNearRect() : node.getRect();
|
||||
let y;
|
||||
let scroll = false;
|
||||
if (insertion && rect) {
|
||||
y = insertion.isNearAfter() ? rect.bottom : rect.top;
|
||||
|
||||
if (y < bounds.top || y > bounds.bottom) {
|
||||
scroll = true;
|
||||
}
|
||||
}*/
|
||||
} else {
|
||||
const rect = this.document.computeRect(node);
|
||||
if (!rect || rect.width === 0 || rect.height === 0) {
|
||||
if (!this.tryScrollAgain && tryTimes < 3) {
|
||||
this.tryScrollAgain = requestAnimationFrame(() => this.scrollToNode(node, null, tryTimes + 1));
|
||||
}
|
||||
return;
|
||||
}
|
||||
const scrollTarget = this.document.viewport.scrollTarget!;
|
||||
const st = scrollTarget.top;
|
||||
const sl = scrollTarget.left;
|
||||
const { innerBounds, scrollHeight, scrollWidth } = this.bounds;
|
||||
const { height, width, top, bottom, left, right } = innerBounds;
|
||||
|
||||
if (rect.height > height ? rect.top > bottom || rect.bottom < top : rect.top < top || rect.bottom > bottom) {
|
||||
opt.top = Math.min(rect.top + rect.height / 2 + st - top - height / 2, scrollHeight - height);
|
||||
scroll = true;
|
||||
}
|
||||
|
||||
if (rect.width > width ? rect.left > right || rect.right < left : rect.left < left || rect.right > right) {
|
||||
opt.left = Math.min(rect.left + rect.width / 2 + sl - left - width / 2, scrollWidth - width);
|
||||
scroll = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (scroll && this.scroller) {
|
||||
this.scroller.scrollTo(opt);
|
||||
}
|
||||
}
|
||||
|
||||
fixEvent(e: LocateEvent): LocateEvent {
|
||||
if (e.fixed) {
|
||||
return e;
|
||||
}
|
||||
if (!e.target || e.originalEvent.view!.document !== this.contentDocument) {
|
||||
e.target = this.contentDocument.elementFromPoint(e.clientX, e.clientY);
|
||||
}
|
||||
return e;
|
||||
}
|
||||
|
||||
isEnter(e: LocateEvent): boolean {
|
||||
const rect = this.bounds;
|
||||
return e.globalY >= rect.top && e.globalY <= rect.bottom && e.globalX >= rect.left && e.globalX <= rect.right;
|
||||
}
|
||||
|
||||
inRange(e: LocateEvent): boolean {
|
||||
return e.globalX <= this.bounds.right;
|
||||
}
|
||||
|
||||
deactive() {
|
||||
this.sensing = false;
|
||||
this.scroller.cancel();
|
||||
}
|
||||
|
||||
isAcceptable(container: ElementNode): boolean {
|
||||
const proto = container.prototype;
|
||||
const view: any = this.document.getView(container);
|
||||
if (view && '$accept' in view) {
|
||||
return true;
|
||||
}
|
||||
return proto.acceptable;
|
||||
}
|
||||
|
||||
acceptAnyData(container: ElementNode, e: LocateEvent | MouseEvent | KeyboardEvent) {
|
||||
const proto = container.prototype;
|
||||
const view: any = this.document.getView(container);
|
||||
// use view instance method: $accept
|
||||
if (view && typeof view.$accept === 'function') {
|
||||
// should return LocationData
|
||||
return view.$accept(container, e);
|
||||
}
|
||||
// use prototype method: accept
|
||||
return proto.accept(container, e);
|
||||
}
|
||||
|
||||
checkNesting(dropTarget: ElementNode, dragTarget: NodesDragTarget | NodeDatasDragTarget): boolean {
|
||||
const items: Array<INode | NodeData> = dragTarget.nodes || (dragTarget as NodeDatasDragTarget).data;
|
||||
return items.every(item => this.checkNestingDown(dropTarget, item));
|
||||
}
|
||||
|
||||
checkDropTarget(dropTarget: RootNode | ElementNode, dragTarget: NodesDragTarget | NodeDatasDragTarget): boolean {
|
||||
const items: Array<INode | NodeData> = dragTarget.nodes || (dragTarget as NodeDatasDragTarget).data;
|
||||
return items.every(item => this.checkNestingUp(dropTarget, item));
|
||||
}
|
||||
|
||||
checkNestingUp(parent: RootNode | ElementNode, target: NodeData | INode): boolean {
|
||||
if (isElementNode(target) || isElementData(target)) {
|
||||
const proto = isElementNode(target)
|
||||
? target.prototype
|
||||
: this.document.getPrototypeByTagNameOrURI(target.tagName, target.uri);
|
||||
if (proto) {
|
||||
return proto.checkNestingUp(target, parent);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
checkNestingDown(parent: ElementNode, target: NodeData | INode): boolean {
|
||||
const proto = parent.prototype;
|
||||
if (isConditionFlow(parent)) {
|
||||
return parent.children.every(
|
||||
child => proto.checkNestingDown(parent, child) && this.checkNestingUp(parent, child),
|
||||
);
|
||||
} else {
|
||||
return proto.checkNestingDown(parent, target) && this.checkNestingUp(parent, target);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function isPointInRect(point: Point, rect: Rect) {
|
||||
return (
|
||||
point.clientY >= rect.top &&
|
||||
point.clientY <= rect.bottom &&
|
||||
point.clientX >= rect.left &&
|
||||
point.clientX <= rect.right
|
||||
);
|
||||
}
|
||||
|
||||
function distanceToRect(point: Point, rect: Rect) {
|
||||
let minX = Math.min(Math.abs(point.clientX - rect.left), Math.abs(point.clientX - rect.right));
|
||||
let minY = Math.min(Math.abs(point.clientY - rect.top), Math.abs(point.clientY - rect.bottom));
|
||||
if (point.clientX >= rect.left && point.clientX <= rect.right) {
|
||||
minX = 0;
|
||||
}
|
||||
if (point.clientY >= rect.top && point.clientY <= rect.bottom) {
|
||||
minY = 0;
|
||||
}
|
||||
|
||||
return Math.sqrt(minX ** 2 + minY ** 2);
|
||||
}
|
||||
|
||||
function distanceToEdge(point: Point, rect: Rect) {
|
||||
const distanceTop = Math.abs(point.clientY - rect.top);
|
||||
const distanceBottom = Math.abs(point.clientY - rect.bottom);
|
||||
|
||||
return {
|
||||
distance: Math.min(distanceTop, distanceBottom),
|
||||
nearAfter: distanceBottom < distanceTop,
|
||||
};
|
||||
}
|
||||
|
||||
function isNearAfter(point: Point, rect: Rect, inline: boolean) {
|
||||
if (inline) {
|
||||
return (
|
||||
Math.abs(point.clientX - rect.left) + Math.abs(point.clientY - rect.top) >
|
||||
Math.abs(point.clientX - rect.right) + Math.abs(point.clientY - rect.bottom)
|
||||
);
|
||||
}
|
||||
return Math.abs(point.clientY - rect.top) > Math.abs(point.clientY - rect.bottom);
|
||||
}
|
||||
@ -1,134 +0,0 @@
|
||||
export class ScrollTarget {
|
||||
get left() {
|
||||
return 'scrollX' in this.target ? this.target.scrollX : this.target.scrollLeft;
|
||||
}
|
||||
get top() {
|
||||
return 'scrollY' in this.target ? this.target.scrollY : this.target.scrollTop;
|
||||
}
|
||||
scrollTo(options: { left?: number; top?: number }) {
|
||||
this.target.scrollTo(options);
|
||||
}
|
||||
|
||||
scrollToXY(x: number, y: number) {
|
||||
this.target.scrollTo(x, y);
|
||||
}
|
||||
|
||||
constructor(private target: Window | Element) {}
|
||||
}
|
||||
|
||||
function easing(n: number) {
|
||||
return Math.sin((n * Math.PI) / 2);
|
||||
}
|
||||
|
||||
const SCROLL_ACCURCY = 30;
|
||||
|
||||
export default class Scroller {
|
||||
private pid: number | undefined;
|
||||
|
||||
constructor(private board: { bounds: any }, private scrollTarget: ScrollTarget) {}
|
||||
|
||||
scrollTo(options: { left?: number; top?: number }) {
|
||||
this.cancel();
|
||||
|
||||
let pid: number;
|
||||
const left = this.scrollTarget.left;
|
||||
const top = this.scrollTarget.top;
|
||||
const end = () => {
|
||||
this.cancel();
|
||||
};
|
||||
|
||||
if ((left === options.left || options.left == null) && top === options.top) {
|
||||
end();
|
||||
return;
|
||||
}
|
||||
|
||||
const duration = 200;
|
||||
const start = +new Date();
|
||||
|
||||
const animate = () => {
|
||||
if (pid !== this.pid) {
|
||||
return;
|
||||
}
|
||||
|
||||
const now = +new Date();
|
||||
const time = Math.min(1, (now - start) / duration);
|
||||
const eased = easing(time);
|
||||
const opt: any = {};
|
||||
if (options.left != null) {
|
||||
opt.left = eased * (options.left - left) + left;
|
||||
}
|
||||
if (options.top != null) {
|
||||
opt.top = eased * (options.top - top) + top;
|
||||
}
|
||||
|
||||
this.scrollTarget.scrollTo(opt);
|
||||
|
||||
if (time < 1) {
|
||||
this.pid = pid = requestAnimationFrame(animate);
|
||||
} else {
|
||||
end();
|
||||
}
|
||||
};
|
||||
|
||||
this.pid = pid = requestAnimationFrame(animate);
|
||||
}
|
||||
|
||||
scrolling(point: { globalX: number; globalY: number }) {
|
||||
this.cancel();
|
||||
|
||||
const x = point.globalX;
|
||||
const y = point.globalY;
|
||||
const bounds = this.board.bounds;
|
||||
const scale = bounds.scale;
|
||||
|
||||
const maxScrollHeight = bounds.scrollHeight - bounds.height / scale;
|
||||
const maxScrollWidth = bounds.scrollWidth - bounds.width / scale;
|
||||
let sx = this.scrollTarget.left;
|
||||
let sy = this.scrollTarget.top;
|
||||
let ax = 0;
|
||||
let ay = 0;
|
||||
if (y < bounds.top + SCROLL_ACCURCY) {
|
||||
ay = -Math.min(Math.max(bounds.top + SCROLL_ACCURCY - y, 10), 50) / scale;
|
||||
} else if (y > bounds.bottom - SCROLL_ACCURCY) {
|
||||
ay = Math.min(Math.max(y + SCROLL_ACCURCY - bounds.bottom, 10), 50) / scale;
|
||||
}
|
||||
if (x < bounds.left + SCROLL_ACCURCY) {
|
||||
ax = -Math.min(Math.max(bounds.top + SCROLL_ACCURCY - y, 10), 50) / scale;
|
||||
} else if (x > bounds.right - SCROLL_ACCURCY) {
|
||||
ax = Math.min(Math.max(x + SCROLL_ACCURCY - bounds.right, 10), 50) / scale;
|
||||
}
|
||||
|
||||
if (!ax && !ay) {
|
||||
return;
|
||||
}
|
||||
|
||||
const animate = () => {
|
||||
let scroll = false;
|
||||
if ((ay > 0 && sy < maxScrollHeight) || (ay < 0 && sy > 0)) {
|
||||
sy += ay;
|
||||
sy = Math.min(Math.max(sy, 0), maxScrollHeight);
|
||||
scroll = true;
|
||||
}
|
||||
if ((ax > 0 && sx < maxScrollWidth) || (ax < 0 && sx > 0)) {
|
||||
sx += ax;
|
||||
sx = Math.min(Math.max(sx, 0), maxScrollWidth);
|
||||
scroll = true;
|
||||
}
|
||||
if (!scroll) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.scrollTarget.scrollTo({ left: sx, top: sy });
|
||||
this.pid = requestAnimationFrame(animate);
|
||||
};
|
||||
|
||||
animate();
|
||||
}
|
||||
|
||||
cancel() {
|
||||
if (this.pid) {
|
||||
cancelAnimationFrame(this.pid);
|
||||
}
|
||||
this.pid = undefined;
|
||||
}
|
||||
}
|
||||
@ -1,5 +1,5 @@
|
||||
import { INode, contains, isNode, comparePosition } from './node/node';
|
||||
import { obx } from '@ali/recore';
|
||||
import Node, { comparePosition } from './node/node';
|
||||
import { obx } from '@recore/obx';
|
||||
import DocumentContext from './document-context';
|
||||
|
||||
export class Selection {
|
||||
@ -7,6 +7,9 @@ export class Selection {
|
||||
|
||||
constructor(private doc: DocumentContext) {}
|
||||
|
||||
/**
|
||||
* 选中
|
||||
*/
|
||||
select(id: string) {
|
||||
if (this.selected.length === 1 && this.selected.indexOf(id) > -1) {
|
||||
// avoid cause reaction
|
||||
@ -16,76 +19,83 @@ export class Selection {
|
||||
this.selected = [id];
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量选中
|
||||
*/
|
||||
selectAll(ids: string[]) {
|
||||
this.selected = ids;
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除选中
|
||||
*/
|
||||
clear() {
|
||||
this.selected = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* 整理选中
|
||||
*/
|
||||
dispose() {
|
||||
let i = this.selected.length;
|
||||
while (i-- > 0) {
|
||||
const id = this.selected[i];
|
||||
const node = this.doc.getNode(id, true);
|
||||
if (!node) {
|
||||
if (!this.doc.hasNode(id)) {
|
||||
this.selected.splice(i, 1);
|
||||
} else if (node.id !== id) {
|
||||
} else {
|
||||
this.selected[i] = id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加选中
|
||||
*/
|
||||
add(id: string) {
|
||||
if (this.selected.indexOf(id) > -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
const i = this.findIndex(id);
|
||||
if (i > -1) {
|
||||
this.selected.splice(i, 1);
|
||||
}
|
||||
|
||||
this.selected.push(id);
|
||||
}
|
||||
|
||||
private findIndex(id: string): number {
|
||||
const ns = getNamespace(id);
|
||||
const nsx = `${ns}:`;
|
||||
return this.selected.findIndex(idx => {
|
||||
return idx === ns || idx.startsWith(nsx);
|
||||
});
|
||||
/**
|
||||
* 是否选中
|
||||
*/
|
||||
has(id: string) {
|
||||
return this.selected.indexOf(id) > -1;
|
||||
}
|
||||
|
||||
has(id: string, variant = false) {
|
||||
return this.selected.indexOf(id) > -1 || (variant && this.findIndex(id) > -1);
|
||||
}
|
||||
|
||||
del(id: string, variant = false) {
|
||||
/**
|
||||
* 移除选中
|
||||
*/
|
||||
remove(id: string) {
|
||||
let i = this.selected.indexOf(id);
|
||||
if (i > -1) {
|
||||
this.selected.splice(i, 1);
|
||||
} else if (variant) {
|
||||
i = this.findIndex(id);
|
||||
this.selected.splice(i, 1);
|
||||
}
|
||||
}
|
||||
|
||||
containsNode(node: INode) {
|
||||
/**
|
||||
* 选区是否包含节点
|
||||
*/
|
||||
containsNode(node: Node) {
|
||||
for (const id of this.selected) {
|
||||
const parent = this.doc.getNode(id);
|
||||
if (parent && contains(parent, node)) {
|
||||
if (parent?.contains(node)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取选中的节点
|
||||
*/
|
||||
getNodes() {
|
||||
const nodes = [];
|
||||
for (const id of this.selected) {
|
||||
const node = this.doc.getNode(id, true);
|
||||
const node = this.doc.getNode(id);
|
||||
if (node) {
|
||||
nodes.push(node);
|
||||
}
|
||||
@ -93,24 +103,13 @@ export class Selection {
|
||||
return nodes;
|
||||
}
|
||||
|
||||
getOriginNodes(): INode[] {
|
||||
const nodes: any[] = [];
|
||||
for (const id of this.selected) {
|
||||
const node = this.doc.getOriginNode(id);
|
||||
if (node && !nodes.includes(node)) {
|
||||
nodes.push(node);
|
||||
}
|
||||
}
|
||||
return nodes;
|
||||
}
|
||||
|
||||
/**
|
||||
* get union items that at top level
|
||||
* 获取顶层选区节点, 场景:拖拽时,建立蒙层,只蒙在最上层
|
||||
*/
|
||||
getTopNodes(origin?: boolean) {
|
||||
getTopNodes() {
|
||||
const nodes = [];
|
||||
for (const id of this.selected) {
|
||||
const node = origin ? this.doc.getOriginNode(id) : this.doc.getNode(id);
|
||||
const node = this.doc.getNode(id);
|
||||
if (!node) {
|
||||
continue;
|
||||
}
|
||||
@ -136,16 +135,3 @@ export class Selection {
|
||||
return nodes;
|
||||
}
|
||||
}
|
||||
|
||||
function getNamespace(id: string) {
|
||||
const i = id.indexOf(':');
|
||||
if (i < 0) {
|
||||
return id;
|
||||
}
|
||||
|
||||
return id.substring(0, i);
|
||||
}
|
||||
|
||||
export function isSelectable(obj: any): obj is INode {
|
||||
return isNode(obj);
|
||||
}
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { obx } from '@ali/recore';
|
||||
import { screen } from '../globals/screen';
|
||||
import { Point } from './location';
|
||||
import { ScrollTarget } from './scroller';
|
||||
import { ScrollTarget } from '../../builtins/simulator/scroller';
|
||||
|
||||
export type AutoFit = '100%';
|
||||
export const AutoFit = '100%';
|
||||
|
||||
@ -4,9 +4,12 @@ import { EventEmitter } from 'events';
|
||||
|
||||
export default class Project {
|
||||
@obx documents: DocumentContext[];
|
||||
displayMode: 'exclusive' | 'split'; // P2
|
||||
displayMode: 'exclusive' | 'tabbed' | 'split'; // P2
|
||||
private emitter = new EventEmitter();
|
||||
private data: ProjectSchema = {};
|
||||
|
||||
// 考虑项目级别 History
|
||||
|
||||
constructor(schema: ProjectSchema) {
|
||||
this.data = { ...schema };
|
||||
}
|
||||
|
||||
@ -0,0 +1,74 @@
|
||||
export interface SimulatorInterface<P = object> {
|
||||
/**
|
||||
* 获得边界维度等信息
|
||||
*/
|
||||
readonly bounds: object;
|
||||
|
||||
// containerAssets // like react jQuery lodash
|
||||
// vendorsAssets // antd/fusion
|
||||
// themeAssets
|
||||
// componentAssets
|
||||
// simulatorUrls //
|
||||
// layout: ComponentName
|
||||
// utils, dataSource, constants 模拟
|
||||
// 获取区块代码, 通过 components 传递,可异步获取
|
||||
setProps(props: P): void;
|
||||
|
||||
/**
|
||||
* 设置编辑模式
|
||||
*/
|
||||
setDesignMode(mode: "live" | "design" | string): void;
|
||||
|
||||
/**
|
||||
* 设置拖拽态
|
||||
*/
|
||||
setDraggingState(state: boolean): void;
|
||||
|
||||
/**
|
||||
* 是否拖拽态
|
||||
*/
|
||||
isDraggingState(): boolean;
|
||||
|
||||
/**
|
||||
* 设置拷贝态
|
||||
*/
|
||||
setCopyState(state: boolean): void;
|
||||
|
||||
/**
|
||||
* 是否拷贝态
|
||||
*/
|
||||
isCopyState(): boolean;
|
||||
|
||||
/**
|
||||
* 清除所有态:拖拽态、拷贝态
|
||||
*/
|
||||
clearState(): void;
|
||||
|
||||
/**
|
||||
* 在模拟器拖拽定位
|
||||
*/
|
||||
locate(e: LocateEvent): any;
|
||||
|
||||
/**
|
||||
* 滚动视口到节点
|
||||
*/
|
||||
scrollToNode(node: INode, detail?: any): void;
|
||||
|
||||
/**
|
||||
* 给 event 打补丁,添加 canvasX, globalX 等信息,用于拖拽
|
||||
*/
|
||||
fixEvent(e: LocateEvent): LocateEvent;
|
||||
|
||||
getComponent(npmInfo: object): ReactComponent | any;
|
||||
getViewInstance(node: Node): ViewInstance[] | null;
|
||||
|
||||
/**
|
||||
* 设置挂起
|
||||
*/
|
||||
setSuspense(suspended: boolean): void;
|
||||
|
||||
/**
|
||||
* 销毁
|
||||
*/
|
||||
destroy(): void;
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user