This commit is contained in:
kangwei 2020-02-19 09:03:53 +08:00
parent ada3fe12ec
commit 4d4cfab271
23 changed files with 561 additions and 476 deletions

View File

@ -1,22 +0,0 @@
辅助类
对齐线
插入指示 insertion 竖线 横线 插入块 禁止插入块
幽灵替身 ghost
聚焦编辑指示
插入指示 insertion 竖线 横线 插入块 禁止插入块
竖线:红色,绿色
横线:红色,绿色
插入块:透明绿色,透明红色
投放指示线
cover
轮廓服务
悬停指示线 xray mode?
选中指示线
投放指示线
透视线 x-ray

View File

@ -1,12 +0,0 @@
import { Component } from 'react';
import embedEditor from '../../globals/embed-editor';
export default class EmbedEditorToolbar extends Component {
shouldComponentUpdate() {
return false;
}
render() {
return <div className="embed-editor-toolbar" ref={shell => embedEditor.mount(shell)} />;
}
}

View File

@ -3,9 +3,10 @@ import { Component } from 'react';
import { OutlineHovering } from './outline-hovering';
import { SimulatorContext } from '../context';
import { SimulatorHost } from '../host';
import { OutlineSelecting } from './outline-selecting';
import { InsertionView } from './insertion';
import './auxiliary.less';
import './outlines.less';
import { OutlineSelecting } from './outline-selecting';
@observer
export class AuxiliaryView extends Component {
@ -22,6 +23,7 @@ export class AuxiliaryView extends Component {
<div className="lc-auxiliary" style={{ transform: `translate(${-scrollX * scale}px,${-scrollY * scale}px)` }}>
<OutlineHovering key="hovering" />
<OutlineSelecting key="selecting" />
<InsertionView key="insertion" />
</div>
);
}

View File

@ -1,4 +1,4 @@
.my-insertion {
.lc-insertion {
position: absolute;
top: -1.5px;
left: 0;

View File

@ -1,34 +1,35 @@
import { Component } from 'react';
import { observer } from '@ali/recore';
import { getCurrentDocument } from '../../globals';
import { computed } from '@recore/obx';
import { observer } from '@recore/core-obx';
import { SimulatorContext } from '../context';
import { SimulatorHost } from '../host';
import Location, { Rect, isLocationChildrenDetail, LocationChildrenDetail, isVertical } from '../../../../designer/helper/location';
import { ISimulator } from '../../../../designer/simulator';
import { NodeParent } from '../../../../designer/document/node/node';
import './insertion.less';
import Location, { isLocationChildrenDetail, isVertical, LocationChildrenDetail, Rect } from '../../document/location';
import { isConditionFlow } from '../../document/node/condition-flow';
import { getChildAt, INodeParent } from '../../document/node';
import DocumentContext from '../../document/document-context';
// eslint-disable-next-line @typescript-eslint/no-unused-vars
function processPropDetail() {
// return { insertType: 'cover', coverEdge: ? };
}
interface InsertionData {
edge?: Rect;
edge?: DOMRect;
insertType?: string;
vertical?: boolean;
nearRect?: Rect;
coverRect?: Rect;
coverRect?: DOMRect;
}
/**
* (INode)
*/
function processChildrenDetail(
doc: DocumentContext,
target: INodeParent,
sim: ISimulator,
target: NodeParent,
detail: LocationChildrenDetail,
): InsertionData {
const edge = doc.computeRect(target);
let edge = detail.edge || null;
if (edge) {
edge = sim.computeRect(target);
}
if (!edge) {
return {};
}
@ -38,15 +39,9 @@ function processChildrenDetail(
insertType: 'before',
};
if (isConditionFlow(target)) {
ret.insertType = 'cover';
ret.coverRect = edge;
return ret;
}
if (detail.near) {
const { node, pos, rect, align } = detail.near;
ret.nearRect = rect || doc.computeRect(node);
ret.nearRect = rect || sim.computeRect(node);
ret.vertical = align ? align === 'V' : isVertical(ret.nearRect);
ret.insertType = pos;
return ret;
@ -55,10 +50,10 @@ function processChildrenDetail(
// from outline-tree: has index, but no near
// TODO: think of shadowNode & ConditionFlow
const { index } = detail;
let nearNode = getChildAt(target, index);
let nearNode = target.children.get(index);
if (!nearNode) {
// index = 0, eg. nochild,
nearNode = getChildAt(target, index > 0 ? index - 1 : 0);
nearNode = target.children.get(index > 0 ? index - 1 : 0);
if (!nearNode) {
ret.insertType = 'cover';
ret.coverRect = edge;
@ -67,7 +62,7 @@ function processChildrenDetail(
ret.insertType = 'after';
}
if (nearNode) {
ret.nearRect = doc.computeRect(nearNode);
ret.nearRect = sim.computeRect(nearNode);
ret.vertical = isVertical(ret.nearRect);
}
return ret;
@ -77,44 +72,56 @@ function processChildrenDetail(
* detail "坐标"
*/
function processDetail({ target, detail, document }: Location): InsertionData {
const sim = document.simulator;
if (!sim) {
return {};
}
if (isLocationChildrenDetail(detail)) {
return processChildrenDetail(document, target, detail);
return processChildrenDetail(sim, target, detail);
} else {
// TODO: others...
const edge = document.computeRect(target);
const instances = sim.getComponentInstances(target);
if (!instances) {
return {};
}
const edge = sim.computeComponentInstanceRect(instances[0]);
return edge ? { edge, insertType: 'cover', coverRect: edge } : {};
}
}
@observer
export class InsertionView extends Component {
static contextType = SimulatorContext;
@computed get host(): SimulatorHost {
return this.context;
}
shouldComponentUpdate() {
return false;
}
render() {
const doc = getCurrentDocument();
if (!doc || !doc.dropLocation) {
const loc = this.host.document.dropLocation;
if (!loc) {
return null;
}
const { scale, scrollTarget } = doc.viewport;
const sx = scrollTarget!.left;
const sy = scrollTarget!.top;
const { scale, scrollX, scrollY } = this.host.viewport;
const { edge, insertType, coverRect, nearRect, vertical } = processDetail(loc);
const { edge, insertType, coverRect, nearRect, vertical } = processDetail(doc.dropLocation);
if (!edge) {
return null;
}
let className = 'my-insertion';
let className = 'lc-insertion';
const style: any = {};
let x: number;
let y: number;
if (insertType === 'cover') {
className += ' cover';
x = (coverRect!.left + sx) * scale;
y = (coverRect!.top + sy) * scale;
x = (coverRect!.left + scrollX) * scale;
y = (coverRect!.top + scrollY) * scale;
style.width = coverRect!.width * scale;
style.height = coverRect!.height * scale;
} else {
@ -123,12 +130,12 @@ export class InsertionView extends Component {
}
if (vertical) {
className += ' vertical';
x = ((insertType === 'before' ? nearRect.left : nearRect.right) + sx) * scale;
y = (nearRect.top + sy) * scale;
x = ((insertType === 'before' ? nearRect.left : nearRect.right) + scrollX) * scale;
y = (nearRect.top + scrollY) * scale;
style.height = nearRect!.height * scale;
} else {
x = (nearRect.left + sx) * scale;
y = ((insertType === 'before' ? nearRect.top : nearRect.bottom) + sy) * scale;
x = (nearRect.left + scrollX) * scale;
y = ((insertType === 'before' ? nearRect.top : nearRect.bottom) + scrollY) * scale;
style.width = nearRect.width * scale;
}
}

View File

@ -75,7 +75,7 @@ export class OutlineHovering extends Component {
if (!current) {
return <Fragment />;
}
const instances = host.getComponentInstance(current);
const instances = host.getComponentInstances(current);
if (!instances || instances.length < 1) {
return <Fragment />;
}
@ -83,7 +83,7 @@ export class OutlineHovering extends Component {
if (instances.length === 1) {
return (
<OutlineHoveringInstance
key="line-s"
key="line-h"
title={current.title}
scale={this.scale}
scrollX={this.scrollX}
@ -96,7 +96,7 @@ export class OutlineHovering extends Component {
<Fragment>
{instances.map((inst, i) => (
<OutlineHoveringInstance
key={`line-${i}`}
key={`line-h-${i}`}
title={current.title}
scale={this.scale}
scrollX={this.scrollX}

View File

@ -70,7 +70,7 @@ export class OutlineSelecting extends Component {
return (
<Fragment>
{selecting.map(node => {
const instances = this.host.getComponentInstance(node);
const instances = this.host.getComponentInstances(node);
if (!instances || instances.length < 1) {
return null;
}

View File

@ -1,19 +1,38 @@
import { obx, autorun, computed } from '@recore/obx';
import { ISimulator, ComponentInstance, Component, NodeInstance } from '../../../designer/simulator';
import { ISimulator, Component, NodeInstance } from '../../../designer/simulator';
import Viewport from './viewport';
import { createSimulator } from './create-simulator';
import { SimulatorRenderer } from '../renderer/renderer';
import Node, { NodeParent } from '../../../designer/document/node/node';
import Node, { NodeParent, isNodeParent, isNode, contains } from '../../../designer/document/node/node';
import DocumentModel from '../../../designer/document/document-model';
import ResourceConsumer from './resource-consumer';
import { AssetLevel, Asset, assetBundle, assetItem, AssetType } from '../utils/asset';
import { DragObjectType, isShaken, LocateEvent, DragNodeObject, DragNodeDataObject } from '../../../designer/helper/dragon';
import { LocationData } from '../../../designer/helper/location';
import { NodeData } from '../../../designer/schema';
import {
DragObjectType,
isShaken,
LocateEvent,
DragNodeObject,
DragNodeDataObject,
isDragAnyObject,
isDragNodeObject,
} from '../../../designer/helper/dragon';
import {
LocationData,
isLocationData,
LocationChildrenDetail,
LocationDetailType,
isChildInline,
isRowContainer,
getRectTarget,
Rect,
CanvasPoint,
} from '../../../designer/helper/location';
import { isNodeSchema, NodeSchema } from '../../../designer/schema';
import { ComponentDescriptionSpec } from '../../../designer/component-config';
import { ReactInstance } from 'react';
import { setNativeSelection } from '../../../designer/helper/navtive-selection';
import cursor from '../../../designer/helper/cursor';
import { isRootNode } from '../../../designer/document/node/root-node';
export interface SimulatorProps {
// 从 documentModel 上获取
@ -53,15 +72,11 @@ const defaultDepends = [
export class SimulatorHost implements ISimulator<SimulatorProps> {
readonly isSimulator = true;
constructor(readonly document: DocumentModel) {}
readonly designer = this.document.designer;
private _sensorAvailable: boolean = true;
get sensorAvailable(): boolean {
return this._sensorAvailable;
}
@computed get device(): string | undefined {
// 根据 device 不同来做画布外框样式变化 渲染时可选择不同组件
// renderer 依赖
@ -92,6 +107,9 @@ export class SimulatorHost implements ISimulator<SimulatorProps> {
}
@obx.ref _props: SimulatorProps = {};
/**
* @see ISimulator
*/
setProps(props: SimulatorProps) {
this._props = props;
}
@ -196,7 +214,7 @@ export class SimulatorHost implements ISimulator<SimulatorProps> {
// TODO: think of lock when edit a node
// 事件路由
doc.addEventListener('mousedown', (downEvent: MouseEvent) => {
const nodeInst = documentModel.getNodeInstanceFromElement(downEvent.target as Element);
const nodeInst = this.getNodeInstanceFromElement(downEvent.target as Element);
if (!nodeInst?.node) {
selection.clear();
return;
@ -256,7 +274,6 @@ export class SimulatorHost implements ISimulator<SimulatorProps> {
// cause edit
doc.addEventListener('dblclick', (e: MouseEvent) => {
// TODO:
});
}
@ -271,8 +288,9 @@ export class SimulatorHost implements ISimulator<SimulatorProps> {
if (!hovering.enable) {
return;
}
const nodeInst = this.document.getNodeInstanceFromElement(e.target as Element);
const nodeInst = this.getNodeInstanceFromElement(e.target as Element);
// TODO: enhance only hover one instance
console.info(nodeInst);
hovering.hover(nodeInst?.node || null);
e.stopPropagation();
};
@ -298,6 +316,9 @@ export class SimulatorHost implements ISimulator<SimulatorProps> {
};
}
/**
* @see ISimulator
*/
setSuspense(suspended: boolean) {
if (suspended) {
if (this.disableHovering) {
@ -312,35 +333,61 @@ export class SimulatorHost implements ISimulator<SimulatorProps> {
}
}
setDesignMode(mode: string): void {
throw new Error('Method not implemented.');
}
/**
* @see ISimulator
*/
describeComponent(component: Component): ComponentDescriptionSpec {
throw new Error('Method not implemented.');
}
getComponent(componentName: string): Component {
throw new Error('Method not implemented.');
/**
* @see ISimulator
*/
getComponent(componentName: string): Component | null {
return null;
}
getComponentInstance(node: Node): ReactInstance[] | null {
return this._renderer?.getComponentInstance(node.id) || null;
/**
* @see ISimulator
*/
getComponentInstances(node: Node): ReactInstance[] | null {
return this._renderer?.getComponentInstances(node.id) || null;
}
getComponentInstanceId(instance: ReactInstance) {
}
/**
* @see ISimulator
*/
getComponentInstanceId(instance: ReactInstance) {}
/**
* @see ISimulator
*/
getComponentContext(node: Node): object {
throw new Error('Method not implemented.');
}
getClosestNodeInstance(elem: Element): NodeInstance | null {
return this.renderer?.getClosestNodeInstance(elem) || null;
/**
* @see ISimulator
*/
getClosestNodeInstance(from: ReactInstance, specId?: string): NodeInstance<ReactInstance> | null {
return this.renderer?.getClosestNodeInstance(from, specId) || null;
}
computeComponentInstanceRect(instance: ReactInstance): DOMRect | null {
/**
* @see ISimulator
*/
computeRect(node: Node): Rect | null {
const instances = this.getComponentInstances(node);
if (!instances) {
return null;
}
return this.computeComponentInstanceRect(instances[0]);
}
/**
* @see ISimulator
*/
computeComponentInstanceRect(instance: ReactInstance): Rect | null {
const renderer = this.renderer!;
const elements = renderer.findDOMNodes(instance);
if (!elements) {
@ -348,10 +395,12 @@ export class SimulatorHost implements ISimulator<SimulatorProps> {
}
let rects: DOMRect[] | undefined;
let last: { x: number; y: number; r: number; b: number; } | undefined;
let last: { x: number; y: number; r: number; b: number } | undefined;
let computed = false;
const elems = elements.slice();
while (true) {
if (!rects || rects.length < 1) {
const elem = elements.pop();
const elem = elems.pop();
if (!elem) {
break;
}
@ -372,30 +421,62 @@ export class SimulatorHost implements ISimulator<SimulatorProps> {
}
if (rect.left < last.x) {
last.x = rect.left;
computed = true;
}
if (rect.top < last.y) {
last.y = rect.top;
computed = true;
}
if (rect.right > last.r) {
last.r = rect.right;
computed = true;
}
if (rect.bottom > last.b) {
last.b = rect.bottom;
computed = true;
}
}
if (last) {
return new DOMRect(last.x, last.y, last.r - last.x, last.b - last.y);
const r: any = new DOMRect(last.x, last.y, last.r - last.x, last.b - last.y);
r.elements = elements;
r.computed = computed;
return r;
}
return null;
}
/**
* @see ISimulator
*/
findDOMNodes(instance: ReactInstance): Array<Element | Text> | null {
return this._renderer?.findDOMNodes(instance) || null;
}
/**
* DOM simulator
*/
getNodeInstanceFromElement(target: Element | null): NodeInstance | null {
if (!target) {
return null;
}
const nodeIntance = this.getClosestNodeInstance(target);
if (!nodeIntance) {
return null;
}
const node = this.document.getNode(nodeIntance.nodeId);
return {
...nodeIntance,
node,
};
}
private tryScrollAgain: number | null = null;
/**
* @see ISimulator
*/
scrollToNode(node: Node, detail?: any, tryTimes = 0) {
this.tryScrollAgain = null;
if (this.sensing) {
@ -451,80 +532,251 @@ export class SimulatorHost implements ISimulator<SimulatorProps> {
}
// #region ========= drag and drop helpers =============
/**
* @see ISimulator
*/
setNativeSelection(enableFlag: boolean) {
setNativeSelection(enableFlag);
this.renderer?.setNativeSelection(enableFlag);
}
/**
* @see ISimulator
*/
setDraggingState(state: boolean) {
cursor.setDragging(state);
this.renderer?.setDraggingState(state);
}
/**
* @see ISimulator
*/
setCopyState(state: boolean) {
cursor.setCopy(state);
this.renderer?.setCopyState(state);
}
/**
* @see ISimulator
*/
clearState() {
cursor.release();
this.renderer?.clearState();
}
private _sensorAvailable: boolean = true;
/**
* @see ISensor
*/
get sensorAvailable(): boolean {
return this._sensorAvailable;
}
/**
* @see ISensor
*/
fixEvent(e: LocateEvent): LocateEvent {
/*
if (e.fixed) {
return e;
}
if (!e.target || e.originalEvent.view!.document !== this.contentDocument) {
e.target = this.contentDocument!.elementFromPoint(e.canvasX, e.canvasY);
}*/
const notMyEvent = e.originalEvent.view?.document !== this.contentDocument;
// fix canvasX canvasY : 当前激活文档画布坐标系
if (notMyEvent || !('canvasX' in e) || !('canvasY' in e)) {
const l = this.viewport.toLocalPoint({
clientX: e.globalX,
clientY: e.globalY,
});
e.canvasX = l.clientX;
e.canvasY = l.clientY;
}
// fix target : 浏览器事件响应目标
if (!e.target || notMyEvent) {
e.target = this.contentDocument!.elementFromPoint(e.canvasX!, e.canvasY!);
}
// documentModel : 目标文档
e.documentModel = this.document;
// 事件已订正
e.fixed = true;
return e;
}
/**
* @see ISensor
*/
isEnter(e: LocateEvent): boolean {
return false; /*
const rect = this.bounds;
const rect = this.viewport.bounds;
return e.globalY >= rect.top && e.globalY <= rect.bottom && e.globalX >= rect.left && e.globalX <= rect.right;
*/
}
private sensing: boolean = false;
/**
* @see ISensor
*/
deactiveSensor() {
this.sensing = false;
this.scroller.cancel();
}
// ========= drag location logic start ==========
// ========= drag location logic: hepler for locate ==========
/**
* @see ISensor
*/
locate(e: LocateEvent): any {
this.sensing = true;
this.scroller.scrolling(e);
const dropTarget = this.getDropTarget(e);
console.info('aa', dropTarget);
if (!dropTarget) {
return null;
}
if (isLocationData(dropTarget)) {
return this.designer.createLocation(dropTarget);
}
const target = dropTarget;
const targetInstance = e.targetInstance as ReactInstance;
const parentInstance = this.getClosestNodeInstance(targetInstance, target.id);
const edge = this.computeComponentInstanceRect(parentInstance?.instance as any);
if (!edge) {
return null;
}
const children = target.children;
const detail: LocationChildrenDetail = {
type: LocationDetailType.Children,
index: 0,
edge,
};
const locationData = {
target,
detail,
};
if (!children || children.size < 1 || !edge) {
return this.designer.createLocation(locationData);
}
let nearRect = null;
let nearIndex = 0;
let nearNode = null;
let nearDistance = null;
let minTop = null;
let maxBottom = null;
for (let i = 0, l = children.size; i < l; i++) {
let node = children.get(i)!;
let index = i;
const instances = this.getComponentInstances(node);
const inst = instances
? instances.length > 1
? instances.find(inst => {
return this.getClosestNodeInstance(inst, target.id)?.instance === targetInstance;
})
: instances[0]
: null;
const rect = inst ? this.computeComponentInstanceRect(inst) : null;
if (!rect) {
continue;
}
const distance = isPointInRect(e as any, rect) ? 0 : distanceToRect(e as any, rect);
if (distance === 0) {
nearDistance = distance;
nearNode = node;
nearIndex = index;
nearRect = rect;
break;
}
// 标记子节点最顶
if (minTop === null || rect.top < minTop) {
minTop = rect.top;
}
// 标记子节点最底
if (maxBottom === null || rect.bottom > maxBottom) {
maxBottom = 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 as any, nearRect, vertical)) {
near.pos = 'after';
detail.index = nearIndex + 1;
}
if (!row && nearDistance !== 0) {
const edgeDistance = distanceToEdge(e as any, edge);
if (edgeDistance.distance < nearDistance!) {
const nearAfter = edgeDistance.nearAfter;
if (minTop == null) {
minTop = edge.top;
}
if (maxBottom == null) {
maxBottom = edge.bottom;
}
near.rect = new DOMRect(edge.left, minTop, edge.width, maxBottom - minTop);
near.align = 'H';
near.pos = nearAfter ? 'after' : 'before';
detail.index = nearAfter ? children.size : 0;
}
}
}
return this.designer.createLocation(locationData);
}
getDropTarget(e: LocateEvent): NodeParent | LocationData | null {
/*
const { target, dragTarget } = e;
const isAny = isAnyDragTarget(dragTarget);
const { target, dragObject } = e;
const isAny = isDragAnyObject(dragObject);
let container: any;
if (target) {
const ref = this.document.getNodeFromElement(target as Element);
if (ref) {
container = ref;
const ref = this.getNodeInstanceFromElement(target);
if (ref?.node) {
e.targetInstance = ref.instance;
e.targetNode = ref.node;
container = ref.node;
} else if (isAny) {
return null;
} else {
container = this.document.view;
container = this.document.rootNode;
}
} else if (isAny) {
return null;
} else {
container = this.document.view;
container = this.document.rootNode;
}
if (!isElementNode(container) && !isRootNode(container)) {
if (!isNodeParent(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;
}
// TODO: use spec container to accept specialData
return null;
}
@ -546,7 +798,8 @@ export class SimulatorHost implements ISimulator<SimulatorProps> {
} else {
container = container.parent;
}
} else if (res === AT_CHILD) {
}
/* else if (res === AT_CHILD) {
if (!upward) {
upward = container.parent;
}
@ -555,31 +808,30 @@ export class SimulatorHost implements ISimulator<SimulatorProps> {
container = upward;
upward = null;
}
} else if (isNode(res)) {
}*/
else if (isNode(res)) {
console.info('res', res);
container = res;
upward = null;
}
}*/
}
return null;
}
acceptNodes(container: Node, e: LocateEvent) {
/*
const { dragTarget } = e;
acceptNodes(container: NodeParent, e: LocateEvent) {
const { dragObject } = e;
if (isRootNode(container)) {
return this.checkDropTarget(container, dragTarget as any);
return this.checkDropTarget(container, dragObject as any);
}
const proto = container.prototype;
const config = container.componentConfig;
const acceptable: boolean = this.isAcceptable(container);
if (!proto.isContainer && !acceptable) {
if (!config.isContainer) {
return false;
}
// check is contains, get common parent
if (isNodesDragTarget(dragTarget)) {
const nodes = dragTarget.nodes;
if (isDragNodeObject(dragObject)) {
const nodes = dragObject.nodes;
let i = nodes.length;
let p: any = container;
while (i-- > 0) {
@ -588,41 +840,16 @@ export class SimulatorHost implements ISimulator<SimulatorProps> {
}
}
if (p !== container) {
return p || this.document.view;
return p || this.document.rootNode;
}
}
// 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);
*/
return this.checkNesting(container, dragObject as any);
}
/*
getNearByContainer(container: NodeParent, e: LocateEvent) {
/*
const children = container.children;
if (!children || children.length < 1) {
return null;
@ -656,195 +883,76 @@ export class SimulatorHost implements ISimulator<SimulatorProps> {
}
}
return nearBy;*/
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);
*/
}
isAcceptable(container: NodeParent): boolean {
return false;
/*
const proto = container.prototype;
const view: any = this.getComponentInstance(container);
if (view && '$accept' in view) {
return true;
}
return proto.acceptable;*/
}
acceptAnyData(container: Node, 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: Node, dragTarget: DragNodeObject | DragNodeDataObject): boolean {
return false;
/*
const items: Array<INode | NodeData> = dragTarget.nodes || (dragTarget as NodeDatasDragTarget).data;
checkNesting(dropTarget: NodeParent, dragObject: DragNodeObject | DragNodeDataObject): boolean {
const items: Array<Node | NodeSchema> = dragObject.nodes || (dragObject as DragNodeDataObject).data;
return items.every(item => this.checkNestingDown(dropTarget, item));
*/
}
checkDropTarget(dropTarget: Node, dragTarget: DragNodeObject | DragNodeDataObject): boolean {
return false;
/*
const items: Array<INode | NodeData> = dragTarget.nodes || (dragTarget as NodeDatasDragTarget).data;
checkDropTarget(dropTarget: NodeParent, dragObject: DragNodeObject | DragNodeDataObject): boolean {
const items: Array<Node | NodeSchema> = dragObject.nodes || (dragObject as DragNodeDataObject).data;
return items.every(item => this.checkNestingUp(dropTarget, item));
*/
}
checkNestingUp(parent: NodeParent, target: NodeData | Node): 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);
checkNestingUp(parent: NodeParent, target: NodeSchema | Node): boolean {
if (isNode(target) || isNodeSchema(target)) {
const config = isNode(target) ? target.componentConfig : this.designer.getComponentConfig(target.componentName);
if (config) {
return config.checkNestingUp(target, parent);
}
}*/
}
return true;
}
checkNestingDown(parent: NodeParent, target: NodeData | Node): 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);
}*/
return false;
checkNestingDown(parent: NodeParent, target: NodeSchema | Node): boolean {
const config = parent.componentConfig;
return config.checkNestingDown(parent, target) && this.checkNestingUp(parent, target);
}
// #endregion
}
function isPointInRect(point: CanvasPoint, rect: Rect) {
return (
point.canvasY >= rect.top &&
point.canvasY <= rect.bottom &&
(point.canvasX >= rect.left && point.canvasX <= rect.right)
);
}
function distanceToRect(point: CanvasPoint, rect: Rect) {
let minX = Math.min(Math.abs(point.canvasX - rect.left), Math.abs(point.canvasX - rect.right));
let minY = Math.min(Math.abs(point.canvasY - rect.top), Math.abs(point.canvasY - rect.bottom));
if (point.canvasX >= rect.left && point.canvasX <= rect.right) {
minX = 0;
}
if (point.canvasY >= rect.top && point.canvasY <= rect.bottom) {
minY = 0;
}
return Math.sqrt(minX ** 2 + minY ** 2);
}
function distanceToEdge(point: CanvasPoint, rect: Rect) {
const distanceTop = Math.abs(point.canvasY - rect.top);
const distanceBottom = Math.abs(point.canvasY - rect.bottom);
return {
distance: Math.min(distanceTop, distanceBottom),
nearAfter: distanceBottom < distanceTop,
};
}
function isNearAfter(point: CanvasPoint, rect: Rect, inline: boolean) {
if (inline) {
return (
Math.abs(point.canvasX - rect.left) + Math.abs(point.canvasY - rect.top) >
Math.abs(point.canvasX - rect.right) + Math.abs(point.canvasY - rect.bottom)
);
}
return Math.abs(point.canvasY - rect.top) > Math.abs(point.canvasY - rect.bottom);
}

View File

@ -8,10 +8,12 @@ import { getClientRects } from '../../../utils/get-client-rects';
import { Asset } from '../utils/asset';
import loader from '../utils/loader';
import { ComponentDescriptionSpec } from '../../../designer/component-config';
import { reactFindDOMNodes } from '../utils/react-find-dom-nodes';
import { reactFindDOMNodes, FIBER_KEY } from '../utils/react-find-dom-nodes';
import { isESModule } from '../../../utils/is-es-module';
import { NodeInstance } from '../../../designer/simulator';
import { isElement } from '../../../utils/is-element';
import cursor from '../../../designer/helper/cursor';
import { setNativeSelection } from '../../../designer/helper/navtive-selection';
export class SimulatorRenderer {
readonly isSimulatorRenderer = true;
@ -133,7 +135,9 @@ export class SimulatorRenderer {
let instances = this.instancesMap.get(id);
if (instances) {
instances = instances.filter(checkInstanceMounted);
instances.push(instance);
if (!instances.includes(instance)) {
instances.push(instance);
}
instancesMap.set(id, instances);
} else {
instancesMap.set(id, [instance]);
@ -144,12 +148,12 @@ export class SimulatorRenderer {
this.ctxMap.set(id, ctx);
}
getComponentInstance(id: string): ReactInstance[] | null {
getComponentInstances(id: string): ReactInstance[] | null {
return this.instancesMap.get(id) || null;
}
getClosestNodeInstance(element: Element): NodeInstance | null {
return getClosestNodeInstance(element);
getClosestNodeInstance(from: ReactInstance, nodeId?: string): NodeInstance<ReactInstance> | null {
return getClosestNodeInstance(from, nodeId);
}
findDOMNodes(instance: ReactInstance): Array<Element | Text> | null {
@ -160,6 +164,28 @@ export class SimulatorRenderer {
return getClientRects(element);
}
setNativeSelection(enableFlag: boolean) {
setNativeSelection(enableFlag);
}
/**
* @see ISimulator
*/
setDraggingState(state: boolean) {
cursor.setDragging(state);
}
/**
* @see ISimulator
*/
setCopyState(state: boolean) {
cursor.setCopy(state);
}
/**
* @see ISimulator
*/
clearState() {
cursor.release();
}
private _running: boolean = false;
run() {
if (this._running) {
@ -260,34 +286,44 @@ function cacheReactKey(el: Element): Element {
const SYMBOL_VNID = Symbol('_LCNodeId');
function getClosestNodeInstance(element: Element): NodeInstance | null {
let el: any = element;
function getClosestNodeInstance(from: ReactInstance, specId?: string): NodeInstance<ReactInstance> | null {
let el: any = from;
if (el) {
el = cacheReactKey(el);
if (isElement(el)) {
el = cacheReactKey(el);
} else {
return getNodeInstance(el[FIBER_KEY], specId);
}
}
while (el) {
if (SYMBOL_VNID in el) {
return {
nodeId: el[SYMBOL_VNID],
instance: el,
};
const nodeId = el[SYMBOL_VNID];
if (!specId || specId === nodeId) {
return {
nodeId: nodeId,
instance: el,
};
}
}
// get fiberNode from element
if (el[REACT_KEY]) {
return getNodeInstance(el[REACT_KEY]);
return getNodeInstance(el[REACT_KEY], specId);
}
el = el.parentElement;
}
return null;
}
function getNodeInstance(fiberNode: any): NodeInstance | null {
function getNodeInstance(fiberNode: any, specId?: string): NodeInstance<ReactInstance> | null {
const instance = fiberNode.stateNode;
if (instance && SYMBOL_VNID in instance) {
return {
nodeId: instance[SYMBOL_VNID],
instance,
};
const nodeId = instance[SYMBOL_VNID];
if (!specId || specId === nodeId) {
return {
nodeId: nodeId,
instance: instance,
};
}
}
return getNodeInstance(fiberNode.return);
}

View File

@ -2,7 +2,7 @@ import { ReactInstance } from 'react';
import { isElement } from '../../../utils/is-element';
import { isDOMNode } from '../../../utils/is-dom-node';
const FIBER_KEY = '_reactInternalFiber';
export const FIBER_KEY = '_reactInternalFiber';
function elementsFromFiber(fiber: any, elements: Array<Element | Text>) {
if (fiber) {

View File

@ -280,7 +280,7 @@ export class ComponentConfig {
}
private _isContainer?: boolean;
get isContainer(): boolean {
return this._isContainer!;
return this._isContainer! || this.isRootComponent();
}
private _isModal?: boolean;
get isModal(): boolean {
@ -354,6 +354,10 @@ export class ComponentConfig {
}
}
isRootComponent() {
return this.componentName === 'Page' || this.componentName === 'Block' || this.componentName === 'Component';
}
set spec(spec: ComponentDescriptionSpec) {
this._spec = spec;
this.parseSpec(spec);

View File

@ -56,7 +56,7 @@ export default class Designer {
});
this.dragon.onDrag(e => {
console.info(e);
console.info('dropLocation', this._dropLocation);
if (this.props?.onDrag) {
this.props.onDrag(e);
}
@ -247,12 +247,9 @@ export default class Designer {
return this._componentConfigsMap.get(componentName)!;
}
const config = new ComponentConfig({
return new ComponentConfig({
componentName,
});
this._componentConfigsMap.set(componentName, config);
return config;
}
get componentsMap(): { [key: string]: ComponentDescriptionSpec } {

View File

@ -223,59 +223,16 @@ export default class DocumentModel {
// TODO: emit simulator mounted
}
/**
* simulator
*/
getComponentInstance(node: Node): ComponentInstance[] | null {
if (this.simulator) {
this.simulator.getComponentInstance(node);
}
return null;
}
getComponent(componentName: string): any {
return this.simulator!.getComponent(componentName);
}
getComponentConfig(component: Component, componentName: string): ComponentConfig {
getComponentConfig(componentName: string, component?: Component | null): ComponentConfig {
// TODO: guess componentConfig from component by simulator
return this.designer.getComponentConfig(componentName);
}
/**
* DOM simulator
*/
getNodeInstanceFromElement(target: Element | null): NodeInstance | null {
if (!this.simulator || !target) {
return null;
}
const nodeIntance = this.simulator.getClosestNodeInstance(target);
if (!nodeIntance) {
return null;
}
const node = this.getNode(nodeIntance.nodeId);
return {
...nodeIntance,
node,
};
}
/**
*
* DOM simulator
*/
getDOMNodes(instance: ComponentInstance): Array<Element | Text> | null {
if (!this.simulator) {
return null;
}
if (isElement(instance)) {
return [instance];
}
return this.simulator.findDOMNodes(instance);
}
private _opened: boolean = true;
private _suspensed: boolean = false;

View File

@ -1,8 +1,9 @@
import Node, { NodeParent } from './node';
import { NodeData } from '../../schema';
import { obx, computed } from '@recore/obx';
export default class NodeChildren {
private children: Node[];
@obx.val private children: Node[];
constructor(readonly owner: NodeParent, childrenData: NodeData | NodeData[]) {
this.children = (Array.isArray(childrenData) ? childrenData : [childrenData]).map(child => {
const node = this.owner.document.createNode(child);
@ -22,14 +23,14 @@ export default class NodeChildren {
/**
*
*/
get size(): number {
@computed get size(): number {
return this.children.length;
}
/**
*
*/
isEmpty() {
@computed isEmpty() {
return this.size < 1;
}

View File

@ -1,10 +1,10 @@
import { obx } from '@recore/obx';
import { obx, computed } from '@recore/obx';
import { JSExpression, isJSExpression } from '../../schema';
export default class NodeContent {
@obx.ref private _value: string | JSExpression = '';
get value(): string | JSExpression {
@computed get value(): string | JSExpression {
return this._value;
}
@ -15,7 +15,7 @@ export default class NodeContent {
/**
*
*/
get code() {
@computed get code() {
if (isJSExpression(this._value)) {
return this._value.value;
}
@ -70,14 +70,14 @@ export default class NodeContent {
/**
*
*/
isJSExpression(): boolean {
@computed isJSExpression(): boolean {
return isJSExpression(this._value);
}
/**
*
*/
isEmpty() {
@computed isEmpty() {
if (isJSExpression(this._value)) {
return this._value.value === '';
}

View File

@ -54,8 +54,8 @@ export default class Node {
protected _directives?: Props<Node>;
protected _extras?: Props<Node>;
protected _children: NodeChildren | NodeContent;
private _parent: NodeParent | null = null;
private _zLevel = 0;
@obx.ref private _parent: NodeParent | null = null;
@obx.ref private _zLevel = 0;
get props(): Props<Node> | undefined {
return this._props;
}
@ -165,15 +165,18 @@ export default class Node {
/**
*
*/
@obx.ref get component(): Component {
return this.document.getComponent(this.componentName);
@obx.ref get component(): Component | null {
if (this.isNodeParent) {
return this.document.getComponent(this.componentName);
}
return null;
}
/**
*
*/
@obx.ref get componentConfig(): ComponentConfig {
return this.document.getComponentConfig(this.component, this.componentName);
return this.document.getComponentConfig(this.componentName, this.component);
}
@obx.ref get propsData(): PropsMap | PropsList | null {
@ -258,7 +261,7 @@ export default class Node {
/**
*
*/
get index(): number {
@computed get index(): number {
if (!this.parent) {
return -1;
}

View File

@ -125,7 +125,7 @@ export default class Prop implements IPropParent {
*
* JSExpresion | JSSlot
*/
isContainJSExpression(): boolean {
@computed isContainJSExpression(): boolean {
const type = this._type;
if (type === 'expression') {
return true;
@ -142,12 +142,12 @@ export default class Prop implements IPropParent {
/**
* JSON
*/
isJSON() {
@computed isJSON() {
return !this.isContainJSExpression();
}
private _items: Prop[] | null = null;
private _maps: Map<string, Prop> | null = null;
@obx.val private _items: Prop[] | null = null;
@obx.val private _maps: Map<string, Prop> | null = null;
@computed private get items(): Prop[] | null {
let _items: any;
untracked(() => {

View File

@ -11,7 +11,7 @@ export type UNSET = typeof UNSET;
export default class Props<O = any> implements IPropParent {
readonly id = uniqueId('props');
@obx.val private items: Prop[] = [];
@obx.ref private get maps(): Map<string, Prop> {
@computed private get maps(): Map<string, Prop> {
const maps = new Map();
if (this.items.length > 0) {
this.items.forEach(prop => {
@ -36,7 +36,7 @@ export default class Props<O = any> implements IPropParent {
/**
*
*/
get size() {
@computed get size() {
return this.items.length;
}

View File

@ -1,10 +1,10 @@
import { obx, autorun, untracked } from '@recore/obx';
import { obx, autorun, untracked, computed } from '@recore/obx';
import Prop, { IPropParent } from './prop';
export type PendingItem = Prop[];
export default class StashSpace implements IPropParent {
@obx.val private space: Set<Prop> = new Set();
@obx.ref private get maps(): Map<string, Prop> {
@computed private get maps(): Map<string, Prop> {
const maps = new Map();
if (this.space.size > 0) {
this.space.forEach(prop => {

View File

@ -2,8 +2,8 @@ import { EventEmitter } from 'events';
import { obx } from '@recore/obx';
import Location from './location';
import DocumentModel from '../document/document-model';
import { NodeData } from '../schema';
import { ISimulator, isSimulator } from '../simulator';
import { NodeData, NodeSchema } from '../schema';
import { ISimulator, isSimulator, ComponentInstance } from '../simulator';
import Node from '../document/node/node';
import Designer from '../designer';
import { setNativeSelection } from './navtive-selection';
@ -43,11 +43,14 @@ export interface LocateEvent {
/**
*
*/
document?: DocumentModel;
documentModel?: DocumentModel;
/**
* canvasX,canvasY,
*/
fixed?: true;
targetNode?: Node;
targetInstance?: ComponentInstance;
}
/**
@ -89,8 +92,8 @@ export interface DragNodeObject {
}
export interface DragNodeDataObject {
type: DragObjectType.NodeData;
data: NodeData | NodeData[];
maps?: { [tagName: string]: string };
data: NodeSchema | NodeSchema[];
maps?: { [componentName: string]: string };
thumbnail?: string;
description?: string;
[extra: string]: any;
@ -279,7 +282,7 @@ export default class Dragon {
if (this._dragging) {
this._dragging = false;
try {
this.emitter.emit('dragend', { dragTarget: dragObject, copy: this.isCopyState() });
this.emitter.emit('dragend', { dragObject, copy: this.isCopyState() });
} catch (ex) {
exception = ex;
}
@ -339,6 +342,8 @@ export default class Dragon {
const g = srcSim.viewport.toGlobalPoint(e);
evt.globalX = g.clientX;
evt.globalY = g.clientY;
evt.canvasX = e.clientX;
evt.canvasY = e.clientY;
evt.sensor = srcSim;
} else {
// this condition will not happen, just make sure ts ok
@ -352,10 +357,12 @@ export default class Dragon {
const sourceSensor = getSourceSensor(dragObject);
const sensors: ISensor[] = (masterSensors as ISensor[]).concat(this.sensors);
const chooseSensor = (e: LocateEvent) => {
let sensor = e.sensor || sensors.find(s => s.sensorAvailable && s.isEnter(e));
let sensor = e.sensor && e.sensor.isEnter(e) ? e.sensor : sensors.find(s => s.sensorAvailable && s.isEnter(e));
if (!sensor) {
if (lastSensor) {
sensor = lastSensor;
} else if (e.sensor) {
sensor = e.sensor;
} else if (sourceSensor) {
sensor = sourceSensor;
}

View File

@ -21,7 +21,6 @@ export default class Hovering {
}
hover(node: Node | null) {
console.info(node);
this._current = node;
}

View File

@ -14,6 +14,7 @@ export enum LocationDetailType {
export interface LocationChildrenDetail {
type: LocationDetailType.Children;
index: number;
edge?: DOMRect;
near?: {
node: ComponentNode;
pos: 'before' | 'after';
@ -36,11 +37,16 @@ export interface Point {
clientY: number;
}
export type Rects = Array<ClientRect | DOMRect> & {
export interface CanvasPoint {
canvasX: number;
canvasY: number;
}
export type Rects = DOMRect[] & {
elements: Array<Element | Text>;
};
export type Rect = (ClientRect | DOMRect) & {
export type Rect = DOMRect & {
elements: Array<Element | Text>;
computed?: boolean;
};

View File

@ -100,16 +100,6 @@ export interface ISimulator<P = object> extends ISensor {
*/
clearState(): void;
/**
*
*/
locate(e: LocateEvent): any;
/**
* event canvasX, globalX
*/
fixEvent(e: LocateEvent): LocateEvent;
// #endregion
/**
@ -128,13 +118,15 @@ export interface ISimulator<P = object> extends ISensor {
/**
*
*/
getComponentInstance(node: Node): ComponentInstance[] | null;
getComponentInstances(node: Node): ComponentInstance[] | null;
/**
*
*/
getComponentContext(node: Node): object | null;
getClosestNodeInstance(elem: Element): NodeInstance | null;
getClosestNodeInstance(from: ComponentInstance, specId?: string): NodeInstance | null;
computeRect(node: Node): DOMRect | null;
computeComponentInstanceRect(instance: ComponentInstance): DOMRect | null;
@ -150,9 +142,9 @@ export function isSimulator(obj: any): obj is ISimulator {
return obj && obj.isSimulator;
}
export interface NodeInstance {
export interface NodeInstance<T = ComponentInstance> {
nodeId: string;
instance: ComponentInstance;
instance: T;
node?: Node | null;
}