drag&drop logic 80%

This commit is contained in:
kangwei 2020-02-19 02:40:11 +08:00
parent f7c69af6c4
commit 59818ae3b6
10 changed files with 286 additions and 155 deletions

View File

@ -1,5 +1,5 @@
import { obx, autorun, computed } from '@recore/obx'; import { obx, autorun, computed } from '@recore/obx';
import { ISimulator, ComponentInstance, Component } from '../../../designer/simulator'; import { ISimulator, ComponentInstance, Component, NodeInstance } from '../../../designer/simulator';
import Viewport from './viewport'; import Viewport from './viewport';
import { createSimulator } from './create-simulator'; import { createSimulator } from './create-simulator';
import { SimulatorRenderer } from '../renderer/renderer'; import { SimulatorRenderer } from '../renderer/renderer';
@ -12,6 +12,8 @@ import { LocationData } from '../../../designer/helper/location';
import { NodeData } from '../../../designer/schema'; import { NodeData } from '../../../designer/schema';
import { ComponentDescriptionSpec } from '../../../designer/component-config'; import { ComponentDescriptionSpec } from '../../../designer/component-config';
import { ReactInstance } from 'react'; import { ReactInstance } from 'react';
import { setNativeSelection } from '../../../utils/navtive-selection';
import cursor from '../../../designer/helper/cursor';
export interface SimulatorProps { export interface SimulatorProps {
// 从 documentModel 上获取 // 从 documentModel 上获取
@ -50,6 +52,7 @@ const defaultDepends = [
]; ];
export class SimulatorHost implements ISimulator<SimulatorProps> { export class SimulatorHost implements ISimulator<SimulatorProps> {
readonly isSimulator = true;
constructor(readonly document: DocumentModel) {} constructor(readonly document: DocumentModel) {}
readonly designer = this.document.designer; readonly designer = this.document.designer;
@ -126,6 +129,7 @@ export class SimulatorHost implements ISimulator<SimulatorProps> {
get contentWindow() { get contentWindow() {
return this._contentWindow; return this._contentWindow;
} }
@obx.ref private _contentDocument?: Document; @obx.ref private _contentDocument?: Document;
get contentDocument() { get contentDocument() {
return this._contentDocument; return this._contentDocument;
@ -192,8 +196,8 @@ export class SimulatorHost implements ISimulator<SimulatorProps> {
// TODO: think of lock when edit a node // TODO: think of lock when edit a node
// 事件路由 // 事件路由
doc.addEventListener('mousedown', (downEvent: MouseEvent) => { doc.addEventListener('mousedown', (downEvent: MouseEvent) => {
const target = documentModel.getNodeFromElement(downEvent.target as Element); const nodeInst = documentModel.getNodeInstanceFromElement(downEvent.target as Element);
if (!target) { if (!nodeInst?.node) {
selection.clear(); selection.clear();
return; return;
} }
@ -202,7 +206,7 @@ export class SimulatorHost implements ISimulator<SimulatorProps> {
const isLeftButton = downEvent.which === 1 || downEvent.button === 0; const isLeftButton = downEvent.which === 1 || downEvent.button === 0;
if (isLeftButton) { if (isLeftButton) {
let node: Node = target; let node: Node = nodeInst.node;
let nodes: Node[] = [node]; let nodes: Node[] = [node];
let ignoreUpSelected = false; let ignoreUpSelected = false;
if (isMulti) { if (isMulti) {
@ -214,7 +218,7 @@ export class SimulatorHost implements ISimulator<SimulatorProps> {
} }
// 获得顶层 nodes // 获得顶层 nodes
nodes = selection.getTopNodes(); nodes = selection.getTopNodes();
} else if (selection.containsNode(target)) { } else if (selection.containsNode(node)) {
nodes = selection.getTopNodes(); nodes = selection.getTopNodes();
} else { } else {
// will clear current selection & select dragment in dragstart // will clear current selection & select dragment in dragstart
@ -236,7 +240,7 @@ export class SimulatorHost implements ISimulator<SimulatorProps> {
doc.removeEventListener('mouseup', checkSelect, true); doc.removeEventListener('mouseup', checkSelect, true);
if (!isShaken(downEvent, e)) { if (!isShaken(downEvent, e)) {
// const node = hasConditionFlow(target) ? target.conditionFlow : target; // const node = hasConditionFlow(target) ? target.conditionFlow : target;
const node = target; const node = nodeInst.node!;
const id = node.id; const id = node.id;
designer.activeTracker.track(node); designer.activeTracker.track(node);
if (isMulti && selection.has(id)) { if (isMulti && selection.has(id)) {
@ -252,6 +256,7 @@ export class SimulatorHost implements ISimulator<SimulatorProps> {
// cause edit // cause edit
doc.addEventListener('dblclick', (e: MouseEvent) => { doc.addEventListener('dblclick', (e: MouseEvent) => {
// TODO: // TODO:
}); });
} }
@ -266,9 +271,9 @@ export class SimulatorHost implements ISimulator<SimulatorProps> {
if (!hovering.enable) { if (!hovering.enable) {
return; return;
} }
const node = this.document.getNodeFromElement(e.target as Element); const nodeInst = this.document.getNodeInstanceFromElement(e.target as Element);
// TODO: enhance only hover one instance // TODO: enhance only hover one instance
hovering.hover(node); hovering.hover(nodeInst?.node || null);
e.stopPropagation(); e.stopPropagation();
}; };
const leave = () => hovering.leave(this.document); const leave = () => hovering.leave(this.document);
@ -293,15 +298,6 @@ export class SimulatorHost implements ISimulator<SimulatorProps> {
}; };
} }
setDraggingState(state: boolean): void {
throw new Error('Method not implemented.');
}
isDraggingState(): boolean {
throw new Error('Method not implemented.');
}
setCopyState(state: boolean): void {
throw new Error('Method not implemented.');
}
setSuspense(suspended: boolean) { setSuspense(suspended: boolean) {
if (suspended) { if (suspended) {
if (this.disableHovering) { if (this.disableHovering) {
@ -315,15 +311,10 @@ export class SimulatorHost implements ISimulator<SimulatorProps> {
} }
} }
} }
setDesignMode(mode: string): void { setDesignMode(mode: string): void {
throw new Error('Method not implemented.'); throw new Error('Method not implemented.');
} }
isCopyState(): boolean {
throw new Error('Method not implemented.');
}
clearState(): void {
throw new Error('Method not implemented.');
}
describeComponent(component: Component): ComponentDescriptionSpec { describeComponent(component: Component): ComponentDescriptionSpec {
throw new Error('Method not implemented.'); throw new Error('Method not implemented.');
@ -345,8 +336,8 @@ export class SimulatorHost implements ISimulator<SimulatorProps> {
throw new Error('Method not implemented.'); throw new Error('Method not implemented.');
} }
getClosestNodeId(elem: Element): string | null { getClosestNodeInstance(elem: Element): NodeInstance | null {
return this.renderer?.getClosestNodeId(elem) || null; return this.renderer?.getClosestNodeInstance(elem) || null;
} }
computeComponentInstanceRect(instance: ReactInstance): DOMRect | null { computeComponentInstanceRect(instance: ReactInstance): DOMRect | null {
@ -459,6 +450,20 @@ export class SimulatorHost implements ISimulator<SimulatorProps> {
} }
} }
// #region ========= drag and drop helpers =============
setNativeSelection(enableFlag: boolean) {
setNativeSelection(enableFlag);
}
setDraggingState(state: boolean) {
cursor.setDragging(state);
}
setCopyState(state: boolean) {
cursor.setCopy(state);
}
clearState() {
cursor.release();
}
fixEvent(e: LocateEvent): LocateEvent { fixEvent(e: LocateEvent): LocateEvent {
/* /*
if (e.fixed) { if (e.fixed) {
@ -483,7 +488,7 @@ export class SimulatorHost implements ISimulator<SimulatorProps> {
this.scroller.cancel(); this.scroller.cancel();
} }
//#region drag locate logic // ========= drag location logic start ==========
getDropTarget(e: LocateEvent): NodeParent | LocationData | null { getDropTarget(e: LocateEvent): NodeParent | LocationData | null {
/* /*
const { target, dragTarget } = e; const { target, dragTarget } = e;
@ -841,5 +846,5 @@ export class SimulatorHost implements ISimulator<SimulatorProps> {
}*/ }*/
return false; return false;
} }
//#endregion // #endregion
} }

View File

@ -9,6 +9,8 @@ import { Asset } from '../utils/asset';
import loader from '../utils/loader'; import loader from '../utils/loader';
import { ComponentDescriptionSpec } from '../../../designer/component-config'; import { ComponentDescriptionSpec } from '../../../designer/component-config';
import { findDOMNodes } from '../utils/react'; import { findDOMNodes } from '../utils/react';
import { isESModule } from '../../../utils/is-es-module';
import { NodeInstance } from '../../../designer/simulator';
let REACT_KEY = ''; let REACT_KEY = '';
function cacheReactKey(el: Element): Element { function cacheReactKey(el: Element): Element {
@ -24,28 +26,36 @@ function cacheReactKey(el: Element): Element {
const SYMBOL_VNID = Symbol('_LCNodeId'); const SYMBOL_VNID = Symbol('_LCNodeId');
function getClosestNodeId(element: Element): string | null { function getClosestNodeInstance(element: Element): NodeInstance | null {
let el: any = element; let el: any = element;
if (el) { if (el) {
el = cacheReactKey(el); el = cacheReactKey(el);
} }
while (el) { while (el) {
if (SYMBOL_VNID in el) { if (SYMBOL_VNID in el) {
return el[SYMBOL_VNID]; return {
nodeId: el[SYMBOL_VNID],
instance: el,
};
} }
// get fiberNode from element
if (el[REACT_KEY]) { if (el[REACT_KEY]) {
return getNodeId(el[REACT_KEY]); return getNodeInstance(el[REACT_KEY]);
} }
el = el.parentElement; el = el.parentElement;
} }
return null; return null;
} }
function getNodeId(instance: any): string { function getNodeInstance(fiberNode: any): NodeInstance | null {
if (instance.stateNode && SYMBOL_VNID in instance.stateNode) { const instance = fiberNode.stateNode;
return instance.stateNode[SYMBOL_VNID]; if (instance && SYMBOL_VNID in instance) {
return {
nodeId: instance[SYMBOL_VNID],
instance,
};
} }
return getNodeId(instance.return); return getNodeInstance(fiberNode.return);
} }
function checkInstanceMounted(instance: any): boolean { function checkInstanceMounted(instance: any): boolean {
@ -190,8 +200,8 @@ export class SimulatorRenderer {
return this.instancesMap.get(id) || null; return this.instancesMap.get(id) || null;
} }
getClosestNodeId(element: Element): string | null { getClosestNodeInstance(element: Element): NodeInstance | null {
return getClosestNodeId(element); return getClosestNodeInstance(element);
} }
findDOMNodes(instance: ReactInstance): Array<Element | Text> | null { findDOMNodes(instance: ReactInstance): Array<Element | Text> | null {
@ -228,22 +238,31 @@ function accessLibrary(library: string | object) {
return (window as any)[library]; return (window as any)[library];
} }
function getSubComponent(component: any, paths: string[]) { function getSubComponent(library: any, paths: string[]) {
const l = paths.length; const l = paths.length;
if (l < 1) { if (l < 1 || !library) {
return component; return library;
} }
let i = 0; let i = 0;
let component: any;
while (i < l) { while (i < l) {
const key = paths[i]!; const key = paths[i]!;
let ex: any;
try { try {
component = (component as any)[key]; component = library[key];
} catch (e) { } catch (e) {
ex = e;
component = null;
}
if (i === 0 && component == null && key === 'default') {
if (ex) {
return l === 1 ? library : null;
}
component = library;
} else if (component == null) {
return null; return null;
} }
if (!component) { library = component;
return null;
}
i++; i++;
} }
return component; return component;
@ -253,13 +272,21 @@ function findComponent(componentName: string, npm?: NpmInfo) {
if (!npm) { if (!npm) {
return accessLibrary(componentName); return accessLibrary(componentName);
} }
// libraryName the key access to global
// export { exportName } from xxx exportName === global.libraryName.exportName
// export exportName from xxx exportName === global.libraryName.default || global.libraryName
// export { exportName as componentName } from package
// if exportName == null exportName === componentName;
// const componentName = exportName.subName, if exportName empty subName donot use
const libraryName = npm.exportName || npm.componentName || componentName; const libraryName = npm.exportName || npm.componentName || componentName;
const component = accessLibrary(libraryName); const library = accessLibrary(libraryName);
const paths = npm.subName ? npm.subName.split('.') : []; const paths = npm.exportName && npm.subName ? npm.subName.split('.') : [];
if (npm.destructuring) { if (npm.destructuring) {
paths.unshift(libraryName); paths.unshift(libraryName);
} else if (isESModule(library)) {
paths.unshift('default');
} }
return getSubComponent(component, paths); return getSubComponent(library, paths);
} }
function buildComponents(componentsMap: { [componentName: string]: ComponentDescriptionSpec }) { function buildComponents(componentsMap: { [componentName: string]: ComponentDescriptionSpec }) {

View File

@ -1,15 +0,0 @@
html.my-cursor-dragging, html.my-cursor-dragging * {
cursor: move !important
}
html.my-cursor-x-resizing, html.my-cursor-x-resizing * {
cursor: col-resize;
}
html.my-cursor-y-resizing, html.my-cursor-y-resizing * {
cursor: row-resize;
}
html.my-cursor-copy, html.my-cursor-copy * {
cursor: copy !important
}

View File

@ -12,7 +12,7 @@ import Node, { insertChildren } from './document/node/node';
import { isRootNode } from './document/node/root-node'; import { isRootNode } from './document/node/root-node';
import { ComponentDescriptionSpec, ComponentConfig } from './component-config'; import { ComponentDescriptionSpec, ComponentConfig } from './component-config';
import Scroller, { IScrollable } from './helper/scroller'; import Scroller, { IScrollable } from './helper/scroller';
import { INodeInstance } from './simulator'; import { INodeSelector } from './simulator';
import OffsetObserver, { createOffsetObserver } from './helper/offset-observer'; import OffsetObserver, { createOffsetObserver } from './helper/offset-observer';
export interface DesignerProps { export interface DesignerProps {
@ -96,7 +96,7 @@ export default class Designer {
private _dropLocation?: Location; private _dropLocation?: Location;
/** /**
* * dragon
*/ */
createLocation(locationData: LocationData): Location { createLocation(locationData: LocationData): Location {
const loc = new Location(locationData); const loc = new Location(locationData);
@ -109,7 +109,7 @@ export default class Designer {
/** /**
* *
*/ */
private clearLocation() { clearLocation() {
if (this._dropLocation) { if (this._dropLocation) {
this._dropLocation.document.internalSetDropLocation(null); this._dropLocation.document.internalSetDropLocation(null);
} }
@ -120,7 +120,7 @@ export default class Designer {
return new Scroller(scrollable); return new Scroller(scrollable);
} }
createOffsetObserver(nodeInstance: INodeInstance): OffsetObserver | null { createOffsetObserver(nodeInstance: INodeSelector): OffsetObserver | null {
return createOffsetObserver(nodeInstance); return createOffsetObserver(nodeInstance);
} }

View File

@ -3,7 +3,7 @@ import { RootSchema, NodeData, isDOMText, isJSExpression, NodeSchema } from '../
import Node, { isNodeParent, insertChildren, insertChild, NodeParent } from './node/node'; import Node, { isNodeParent, insertChildren, insertChild, NodeParent } from './node/node';
import { Selection } from './selection'; import { Selection } from './selection';
import RootNode from './node/root-node'; import RootNode from './node/root-node';
import { ISimulator, ComponentInstance, Component } from '../simulator'; import { ISimulator, ComponentInstance, Component, NodeInstance } from '../simulator';
import { computed, obx } from '@recore/obx'; import { computed, obx } from '@recore/obx';
import Location from '../helper/location'; import Location from '../helper/location';
import { ComponentConfig } from '../component-config'; import { ComponentConfig } from '../component-config';
@ -245,16 +245,20 @@ export default class DocumentModel {
/** /**
* DOM simulator * DOM simulator
*/ */
getNodeFromElement(target: Element | null): Node | null { getNodeInstanceFromElement(target: Element | null): NodeInstance | null {
if (!this.simulator || !target) { if (!this.simulator || !target) {
return null; return null;
} }
const id = this.simulator.getClosestNodeId(target); const nodeIntance = this.simulator.getClosestNodeInstance(target);
if (!id) { if (!nodeIntance) {
return null; return null;
} }
return this.getNode(id) as Node; const node = this.getNode(nodeIntance.nodeId);
return {
...nodeIntance,
node,
};
} }
/** /**

View File

@ -0,0 +1,15 @@
html.lc-cursor-dragging, html.lc-cursor-dragging * {
cursor: move !important
}
html.lc-cursor-x-resizing, html.lc-cursor-x-resizing * {
cursor: col-resize;
}
html.lc-cursor-y-resizing, html.lc-cursor-y-resizing * {
cursor: row-resize;
}
html.lc-cursor-copy, html.lc-cursor-copy * {
cursor: copy !important
}

View File

@ -45,14 +45,14 @@ export class Cursor {
private addState(state: string) { private addState(state: string) {
if (!this.states.has(state)) { if (!this.states.has(state)) {
this.states.add(state); this.states.add(state);
document.documentElement.classList.add(`my-cursor-${state}`); document.documentElement.classList.add(`lc-cursor-${state}`);
} }
} }
private removeState(state: string) { private removeState(state: string) {
if (this.states.has(state)) { if (this.states.has(state)) {
this.states.delete(state); this.states.delete(state);
document.documentElement.classList.remove(`my-cursor-${state}`); document.documentElement.classList.remove(`lc-cursor-${state}`);
} }
} }
} }

View File

@ -3,9 +3,11 @@ import { obx } from '@recore/obx';
import Location from './location'; import Location from './location';
import DocumentModel from '../document/document-model'; import DocumentModel from '../document/document-model';
import { NodeData } from '../schema'; import { NodeData } from '../schema';
import { ISimulator } from '../simulator'; import { ISimulator, isSimulator } from '../simulator';
import Node from '../document/node/node'; import Node from '../document/node/node';
import Designer from '../designer'; import Designer from '../designer';
import { setNativeSelection } from '../../utils/navtive-selection';
import cursor from './cursor';
export interface LocateEvent { export interface LocateEvent {
readonly type: 'LocateEvent'; readonly type: 'LocateEvent';
@ -22,10 +24,17 @@ export interface LocateEvent {
* *
*/ */
readonly dragObject: DragObject; readonly dragObject: DragObject;
/**
*
*/
sensor?: ISensor;
// ======= 以下是 激活的 sensor 将填充的值 ========
/** /**
* *
*/ */
target: Element | null; target?: Element | null;
/** /**
* *
*/ */
@ -134,14 +143,18 @@ export default class Dragon {
private sensors: ISensor[] = []; private sensors: ISensor[] = [];
/** /**
* current actived sensor * current actived sensor,
*/ */
private _activeSensor: ISensor | undefined; @obx.ref private _activeSensor: ISensor | undefined;
get activeSensor(): ISensor | undefined { get activeSensor(): ISensor | undefined {
return this._activeSensor; return this._activeSensor;
} }
@obx.ref dragging = false; @obx.ref private _dragging: boolean = false;
get dragging(): boolean {
return this._dragging;
}
private emitter = new EventEmitter(); private emitter = new EventEmitter();
constructor(readonly designer: Designer) {} constructor(readonly designer: Designer) {}
@ -168,66 +181,69 @@ export default class Dragon {
} }
getMasterSensors(): ISimulator[] { getMasterSensors(): ISimulator[] {
return this.designer.project.documents.map(doc => (doc.actived && doc.simulator) || null).filter(Boolean) as any; return this.designer.project.documents.map(doc => {
if (doc.actived && doc.simulator?.sensorAvailable) {
return doc.simulator;
}
return null;
}).filter(Boolean) as any;
} }
/**
* dragTarget should be a INode | INode[] | NodeData | NodeData[]
*/
boost(dragObject: DragObject, boostEvent: MouseEvent) { boost(dragObject: DragObject, boostEvent: MouseEvent) {
/*
const doc = document; const doc = document;
const fromTop = isFromTopDocument(boostEvent); const isFromTop = isFromTopDocument(boostEvent);
let lastLocation: any = null;
let lastSensor: ISensor | undefined;
this.dragging = false;
const masterSensors = this.getMasterSensors(); const masterSensors = this.getMasterSensors();
masterSensors.forEach((sensor) => { const designer = this.designer;
sensor.setNativeSelection(false); const newBie = dragObject.type !== DragObjectType.Node;
}); let lastSensor: ISensor | undefined;
//
this._dragging = false;
// 禁用默认的文稿拖选
this.setNativeSelection(false);
const checkesc = (e: KeyboardEvent) => { const checkesc = (e: KeyboardEvent) => {
if (e.keyCode === 27) { if (e.keyCode === 27) {
lastLocation = null; designer.clearLocation();
master.document.clearLocation();
over(); over();
} }
}; };
const checkcopy = (e: MouseEvent) => { const checkcopy = (e: MouseEvent) => {
if (newBie || e.altKey || e.ctrlKey) { if (newBie || e.altKey || e.ctrlKey) {
master.setCopy(true); this.setCopyState(true);
} else { } else {
master.setCopy(false); this.setCopyState(false);
} }
}; };
// period one fix:
// get evt source-sensor
// get globalX and globalY source-sensor
const drag = (e: MouseEvent) => { const drag = (e: MouseEvent) => {
checkcopy(e); checkcopy(e);
const locateEvent = fixEvent(e); const locateEvent = createLocateEvent(e);
const sensor = chooseSensor(locateEvent); const sensor = chooseSensor(locateEvent);
if (sensor) { if (sensor) {
sensor.fixEvent(locateEvent); sensor.fixEvent(locateEvent);
lastLocation = sensor.locate(locateEvent); sensor.locate(locateEvent);
} else { } else {
master.document.clearLocation(); designer.clearLocation();
lastLocation = null;
} }
this.emitter.emit('drag', locateEvent, lastLocation); this.emitter.emit('drag', locateEvent);
}; };
const dragstart = () => { const dragstart = () => {
const locateEvent = fixEvent(boostEvent); const locateEvent = createLocateEvent(boostEvent);
if (!newBie) { if (!newBie) {
chooseSensor(locateEvent); chooseSensor(locateEvent);
} }
master.setDragging(true); this.setDraggingState(true);
// ESC cancel drag // ESC cancel drag
doc.addEventListener('keydown', checkesc, false); doc.addEventListener('keydown', checkesc, false);
if (topDoc) { if (isFromTop) {
topDoc.addEventListener('keydown', checkesc, false); // topDoc.addEventListener('keydown', checkesc, false);
} }
this.emitter.emit('dragstart', locateEvent); this.emitter.emit('dragstart', locateEvent);
}; };
@ -239,7 +255,7 @@ export default class Dragon {
} }
if (isShaken(boostEvent, e)) { if (isShaken(boostEvent, e)) {
this.dragging = true; this._dragging = true;
setShaken(boostEvent); setShaken(boostEvent);
dragstart(); dragstart();
@ -249,23 +265,23 @@ export default class Dragon {
const over = (e?: any) => { const over = (e?: any) => {
if (lastSensor) { if (lastSensor) {
lastSensor.deactive(); lastSensor.deactiveSensor();
} }
master.setNativeSelection(true); this.setNativeSelection(true);
let exception; let exception;
if (this.dragging) { if (this._dragging) {
this.dragging = false; this._dragging = false;
try { try {
this.emitter.emit('dragend', { dragTarget: dragObject, copy: master.isCopy() }, lastLocation); this.emitter.emit('dragend', { dragTarget: dragObject, copy: this.isCopyState() });
} catch (ex) { } catch (ex) {
exception = ex; exception = ex;
} }
} }
master.releaseCursor(); this.clearState();
if (fromTop) { if (isFromTop) {
doc.removeEventListener('mousemove', move, true); doc.removeEventListener('mousemove', move, true);
doc.removeEventListener('mouseup', over, true); doc.removeEventListener('mouseup', over, true);
doc.removeEventListener('mousedown', over, true); doc.removeEventListener('mousedown', over, true);
@ -290,28 +306,44 @@ export default class Dragon {
} }
}; };
const fixEvent = (e: MouseEvent): LocateEvent => { const createLocateEvent = (e: MouseEvent): LocateEvent => {
if (isLocateEvent(e)) { if (isLocateEvent(e)) {
return e; return e;
} }
const evt: any = { const evt: any = {
type: 'LocateEvent', type: 'LocateEvent',
dragObject,
target: e.target, target: e.target,
dragTarget: dragObject,
originalEvent: e, originalEvent: e,
}; };
if (e.view!.document === document) {
const l = viewport.toLocalPoint(e); const sourceDocument = e.view?.document;
evt.clientX = l.clientX;
evt.clientY = l.clientY; if (!sourceDocument || sourceDocument === document) {
evt.globalX = e.clientX; evt.globalX = e.clientX;
evt.globalY = e.clientY; evt.globalY = e.clientY;
} else { } else {
const g = viewport.toGlobalPoint(e); let srcSim: ISimulator | undefined;
evt.clientX = e.clientX; let lastSim = lastSensor && isSimulator(lastSensor) ? lastSensor : null;
evt.clientY = e.clientY; if (lastSim && lastSim.contentDocument === sourceDocument) {
evt.globalX = g.clientX; srcSim = lastSim;
evt.globalY = g.clientY; } else {
srcSim = masterSensors.find(sim => sim.contentDocument === sourceDocument);
if (!srcSim && lastSim) {
srcSim = lastSim;
}
}
if (srcSim) {
const g = srcSim.viewport.toGlobalPoint(e);
evt.globalX = g.clientX;
evt.globalY = g.clientY;
evt.sensor = srcSim;
} else {
// this condition will not happen, just make sure ts ok
evt.globalX = e.clientX;
evt.globalY = e.clientY;
}
} }
return evt; return evt;
}; };
@ -320,15 +352,13 @@ export default class Dragon {
if (!isDragNodeObject(dragObject)) { if (!isDragNodeObject(dragObject)) {
return null; return null;
} }
return (Array.isArray(dragObject.nodes) ? dragObject.nodes[0] : dragObject.nodes)?.document.simulator || null; return dragObject.nodes[0]?.document.simulator || null;
} }
const simSensors = this.project.documents.map(doc => (doc.actived && doc.simulator) || null).filter(Boolean);
const sourceSensor = getSourceSensor(dragObject); const sourceSensor = getSourceSensor(dragObject);
// check simulator is empty const sensors: ISensor[] = (masterSensors as ISensor[]).concat(this.sensors);
const sensors: ISensor[] = simSensors.concat(this.sensors);
const chooseSensor = (e: LocateEvent) => { const chooseSensor = (e: LocateEvent) => {
let sensor = sensors.find(s => s.sensorAvailable && s.isEnter(e)); let sensor = e.sensor || sensors.find(s => s.sensorAvailable && s.isEnter(e));
if (!sensor) { if (!sensor) {
if (lastSensor) { if (lastSensor) {
sensor = lastSensor; sensor = lastSensor;
@ -343,6 +373,7 @@ export default class Dragon {
lastSensor = sensor; lastSensor = sensor;
} }
if (sensor) { if (sensor) {
e.sensor = sensor;
sensor.fixEvent(e); sensor.fixEvent(e);
} }
this._activeSensor = sensor; this._activeSensor = sensor;
@ -352,26 +383,81 @@ export default class Dragon {
doc.addEventListener('mousemove', move, true); doc.addEventListener('mousemove', move, true);
doc.addEventListener('mouseup', over, true); doc.addEventListener('mouseup', over, true);
doc.addEventListener('mousedown', over, true); doc.addEventListener('mousedown', over, true);
if (topDoc) { if (isFromTop) {/*
topDoc.addEventListener('mousemove', move, true); topDoc.addEventListener('mousemove', move, true);
topDoc.addEventListener('mouseup', over, true); topDoc.addEventListener('mouseup', over, true);
topDoc.addEventListener('mousedown', over, true); topDoc.addEventListener('mousedown', over, true);
*/
} }
if (!newBie) { if (!newBie) {
doc.addEventListener('keydown', checkcopy as any, false); doc.addEventListener('keydown', checkcopy as any, false);
doc.addEventListener('keyup', checkcopy as any, false); doc.addEventListener('keyup', checkcopy as any, false);
if (topDoc) { if (isFromTop) {/*
topDoc.addEventListener('keydown', checkcopy as any, false); topDoc.addEventListener('keydown', checkcopy as any, false);
topDoc.addEventListener('keyup', checkcopy as any, false); topDoc.addEventListener('keyup', checkcopy as any, false);
*/
} }
} }
*/
} }
// #region ======== drag and drop helpers ============
private setNativeSelection(enableFlag: boolean) {
setNativeSelection(enableFlag);
this.designer.project.documents.forEach(doc => {
doc.simulator?.setNativeSelection(enableFlag);
});
}
/**
*
*/
private setDraggingState(state: boolean) {
cursor.setDragging(state);
this.designer.project.documents.forEach(doc => {
doc.simulator?.setDraggingState(state);
});
}
/**
*
*/
private setCopyState(state: boolean) {
cursor.setCopy(state);
this.designer.project.documents.forEach(doc => {
doc.simulator?.setCopyState(state);
});
}
/**
*
*/
private isCopyState(): boolean {
return cursor.isCopy();
}
/**
*
*/
private clearState() {
cursor.release();
this.designer.project.documents.forEach(doc => {
doc.simulator?.clearState();
});
}
// #endregion
/**
*
*/
addSensor(sensor: any) { addSensor(sensor: any) {
this.sensors.push(sensor); this.sensors.push(sensor);
} }
/**
*
*/
removeSensor(sensor: any) { removeSensor(sensor: any) {
const i = this.sensors.indexOf(sensor); const i = this.sensors.indexOf(sensor);
if (i > -1) { if (i > -1) {
@ -386,14 +472,14 @@ export default class Dragon {
}; };
} }
onDrag(func: (e: LocateEvent, location: Location) => any) { onDrag(func: (e: LocateEvent) => any) {
this.emitter.on('drag', func); this.emitter.on('drag', func);
return () => { return () => {
this.emitter.removeListener('drag', func); this.emitter.removeListener('drag', func);
}; };
} }
onDragend(func: (x: { dragObject: DragObject; copy: boolean }, location: Location) => any) { onDragend(func: (x: { dragObject: DragObject; copy: boolean }) => any) {
this.emitter.on('dragend', func); this.emitter.on('dragend', func);
return () => { return () => {
this.emitter.removeListener('dragend', func); this.emitter.removeListener('dragend', func);

View File

@ -1,5 +1,5 @@
import { obx, computed } from '@recore/obx'; import { obx, computed } from '@recore/obx';
import { INodeInstance, IViewport } from '../simulator'; import { INodeSelector, IViewport } from '../simulator';
import Viewport from '../../builtins/simulator/host/viewport'; import Viewport from '../../builtins/simulator/host/viewport';
export default class OffsetObserver { export default class OffsetObserver {
@ -24,7 +24,7 @@ export default class OffsetObserver {
private pid: number | undefined; private pid: number | undefined;
private viewport: IViewport; private viewport: IViewport;
constructor(readonly nodeInstance: INodeInstance) { constructor(readonly nodeInstance: INodeSelector) {
const { node, instance } = nodeInstance; const { node, instance } = nodeInstance;
const doc = node.document; const doc = node.document;
const host = doc.simulator!; const host = doc.simulator!;
@ -67,7 +67,7 @@ export default class OffsetObserver {
} }
} }
export function createOffsetObserver(nodeInstance: INodeInstance): OffsetObserver | null { export function createOffsetObserver(nodeInstance: INodeSelector): OffsetObserver | null {
if (!nodeInstance.instance) { if (!nodeInstance.instance) {
return null; return null;
} }

View File

@ -59,6 +59,7 @@ export interface IViewport extends IScrollable {
* *
*/ */
export interface ISimulator<P = object> extends ISensor { export interface ISimulator<P = object> extends ISensor {
readonly isSimulator: true;
/** /**
* *
*/ */
@ -77,26 +78,23 @@ export interface ISimulator<P = object> extends ISensor {
// 获取区块代码, 通过 components 传递,可异步获取 // 获取区块代码, 通过 components 传递,可异步获取
setProps(props: P): void; setProps(props: P): void;
setSuspense(suspensed: boolean): void;
// #region ========= drag and drop helpers =============
/**
*
*/
setNativeSelection(enableFlag: boolean): void;
/** /**
* *
*/ */
setDraggingState(state: boolean): void; setDraggingState(state: boolean): void;
/**
*
*/
isDraggingState(): boolean;
/** /**
* *
*/ */
setCopyState(state: boolean): void; setCopyState(state: boolean): void;
/**
*
*/
isCopyState(): boolean;
/** /**
* *
*/ */
@ -107,16 +105,18 @@ export interface ISimulator<P = object> extends ISensor {
*/ */
locate(e: LocateEvent): any; locate(e: LocateEvent): any;
/**
*
*/
scrollToNode(node: Node, detail?: any): void;
/** /**
* event canvasX, globalX * event canvasX, globalX
*/ */
fixEvent(e: LocateEvent): LocateEvent; fixEvent(e: LocateEvent): LocateEvent;
// #endregion
/**
*
*/
scrollToNode(node: Node, detail?: any): void;
/** /**
* *
*/ */
@ -134,19 +134,28 @@ export interface ISimulator<P = object> extends ISensor {
*/ */
getComponentContext(node: Node): object | null; getComponentContext(node: Node): object | null;
getClosestNodeId(elem: Element): string | null; getClosestNodeInstance(elem: Element): NodeInstance | null;
computeComponentInstanceRect(instance: ComponentInstance): DOMRect | null; computeComponentInstanceRect(instance: ComponentInstance): DOMRect | null;
findDOMNodes(instance: ComponentInstance): Array<Element | Text> | null; findDOMNodes(instance: ComponentInstance): Array<Element | Text> | null;
setSuspense(suspensed: boolean): void;
/** /**
* *
*/ */
purge(): void; purge(): void;
} }
export function isSimulator(obj: any): obj is ISimulator {
return obj && obj.isSimulator;
}
export interface NodeInstance {
nodeId: string;
instance: ComponentInstance;
node?: Node | null;
}
/** /**
* *
*/ */
@ -157,7 +166,7 @@ export type Component = ComponentType<any> | object;
*/ */
export type ComponentInstance = Element | ReactComponent<any> | object; export type ComponentInstance = Element | ReactComponent<any> | object;
export interface INodeInstance { export interface INodeSelector {
node: Node; node: Node;
instance?: ComponentInstance; instance?: ComponentInstance;
} }