dragon 100%

This commit is contained in:
kangwei 2020-02-19 04:02:29 +08:00
parent 59818ae3b6
commit ada3fe12ec
18 changed files with 153 additions and 143 deletions

View File

@ -1,7 +1,8 @@
// NOTE: 仅用作类型标注,切勿作为实体使用
import { SimulatorRenderer } from '../renderer/renderer';
import { SimulatorHost } from './host';
import { AssetLevel, AssetLevels, AssetList, isAssetBundle, isAssetItem, isCSSUrl, AssetType, assetItem } from '../utils/asset';
import { AssetLevel, AssetLevels, AssetList, isAssetBundle, isAssetItem, AssetType, assetItem } from '../utils/asset';
import { isCSSUrl } from '../../../utils/is-css-url';
export function createSimulator(host: SimulatorHost, iframe: HTMLIFrameElement, vendors: AssetList = []): Promise<SimulatorRenderer> {
const win: any = iframe.contentWindow;

View File

@ -12,7 +12,7 @@ import { LocationData } from '../../../designer/helper/location';
import { NodeData } from '../../../designer/schema';
import { ComponentDescriptionSpec } from '../../../designer/component-config';
import { ReactInstance } from 'react';
import { setNativeSelection } from '../../../utils/navtive-selection';
import { setNativeSelection } from '../../../designer/helper/navtive-selection';
import cursor from '../../../designer/helper/cursor';
export interface SimulatorProps {

View File

@ -4,66 +4,14 @@ import { host } from './host';
import SimulatorRendererView from './renderer-view';
import { computed, obx } from '@recore/obx';
import { RootSchema, NpmInfo } from '../../../designer/schema';
import { isElement, getClientRects } from '../../../utils/dom';
import { getClientRects } from '../../../utils/get-client-rects';
import { Asset } from '../utils/asset';
import loader from '../utils/loader';
import { ComponentDescriptionSpec } from '../../../designer/component-config';
import { findDOMNodes } from '../utils/react';
import { reactFindDOMNodes } from '../utils/react-find-dom-nodes';
import { isESModule } from '../../../utils/is-es-module';
import { NodeInstance } from '../../../designer/simulator';
let REACT_KEY = '';
function cacheReactKey(el: Element): Element {
if (REACT_KEY !== '') {
return el;
}
REACT_KEY = Object.keys(el).find(key => key.startsWith('__reactInternalInstance$')) || '';
if (!REACT_KEY && (el as HTMLElement).parentElement) {
return cacheReactKey((el as HTMLElement).parentElement!);
}
return el;
}
const SYMBOL_VNID = Symbol('_LCNodeId');
function getClosestNodeInstance(element: Element): NodeInstance | null {
let el: any = element;
if (el) {
el = cacheReactKey(el);
}
while (el) {
if (SYMBOL_VNID in el) {
return {
nodeId: el[SYMBOL_VNID],
instance: el,
};
}
// get fiberNode from element
if (el[REACT_KEY]) {
return getNodeInstance(el[REACT_KEY]);
}
el = el.parentElement;
}
return null;
}
function getNodeInstance(fiberNode: any): NodeInstance | null {
const instance = fiberNode.stateNode;
if (instance && SYMBOL_VNID in instance) {
return {
nodeId: instance[SYMBOL_VNID],
instance,
};
}
return getNodeInstance(fiberNode.return);
}
function checkInstanceMounted(instance: any): boolean {
if (isElement(instance)) {
return instance.parentElement != null;
}
return true;
}
import { isElement } from '../../../utils/is-element';
export class SimulatorRenderer {
readonly isSimulatorRenderer = true;
@ -205,7 +153,7 @@ export class SimulatorRenderer {
}
findDOMNodes(instance: ReactInstance): Array<Element | Text> | null {
return findDOMNodes(instance);
return reactFindDOMNodes(instance);
}
getClientRects(element: Element | Text) {
@ -297,4 +245,58 @@ function buildComponents(componentsMap: { [componentName: string]: ComponentDesc
return components;
}
let REACT_KEY = '';
function cacheReactKey(el: Element): Element {
if (REACT_KEY !== '') {
return el;
}
REACT_KEY = Object.keys(el).find(key => key.startsWith('__reactInternalInstance$')) || '';
if (!REACT_KEY && (el as HTMLElement).parentElement) {
return cacheReactKey((el as HTMLElement).parentElement!);
}
return el;
}
const SYMBOL_VNID = Symbol('_LCNodeId');
function getClosestNodeInstance(element: Element): NodeInstance | null {
let el: any = element;
if (el) {
el = cacheReactKey(el);
}
while (el) {
if (SYMBOL_VNID in el) {
return {
nodeId: el[SYMBOL_VNID],
instance: el,
};
}
// get fiberNode from element
if (el[REACT_KEY]) {
return getNodeInstance(el[REACT_KEY]);
}
el = el.parentElement;
}
return null;
}
function getNodeInstance(fiberNode: any): NodeInstance | null {
const instance = fiberNode.stateNode;
if (instance && SYMBOL_VNID in instance) {
return {
nodeId: instance[SYMBOL_VNID],
instance,
};
}
return getNodeInstance(fiberNode.return);
}
function checkInstanceMounted(instance: any): boolean {
if (isElement(instance)) {
return instance.parentElement != null;
}
return true;
}
export default new SimulatorRenderer();

View File

@ -57,10 +57,6 @@ export function isAssetBundle(obj: any): obj is AssetBundle {
return obj && obj.type === AssetType.Bundle;
}
export function isCSSUrl(url: string): boolean {
return /\.css$/.test(url);
}
export function assetBundle(assets?: Asset | AssetList | null, level?: AssetLevel): AssetBundle | null {
if (!assets) {
return null;

View File

@ -1,6 +1,7 @@
import { load, evaluate } from './script';
import StylePoint from './style';
import { Asset, AssetLevel, AssetLevels, AssetType, AssetList, isAssetBundle, isAssetItem, assetItem, isCSSUrl, AssetItem } from './asset';
import { Asset, AssetLevel, AssetLevels, AssetType, AssetList, isAssetBundle, isAssetItem, assetItem, AssetItem } from './asset';
import { isCSSUrl } from '../../../utils/is-css-url';
function parseAssetList(scripts: any, styles: any, assets: AssetList, level?: AssetLevel) {
for (let asset of assets) {

View File

@ -1,5 +1,6 @@
import { ReactInstance } from 'react';
import { isDOMNode, isElement } from '../../../utils/dom';
import { isElement } from '../../../utils/is-element';
import { isDOMNode } from '../../../utils/is-dom-node';
const FIBER_KEY = '_reactInternalFiber';
@ -18,7 +19,7 @@ function elementsFromFiber(fiber: any, elements: Array<Element | Text>) {
}
}
export function findDOMNodes(elem: ReactInstance | null): Array<Element | Text> | null {
export function reactFindDOMNodes(elem: ReactInstance | null): Array<Element | Text> | null {
if (!elem) {
return null;
}

View File

@ -1,4 +1,4 @@
import { createDefer } from '../../../utils/create-defer';
import { createDefer } from './create-defer';
export function evaluate(script: string) {
const scriptEl = document.createElement('script');

View File

@ -1,4 +1,4 @@
import { createDefer } from '../../../utils/create-defer';
import { createDefer } from './create-defer';
export default class StylePoint {
private lastContent: string | undefined;

View File

@ -56,6 +56,7 @@ export default class Designer {
});
this.dragon.onDrag(e => {
console.info(e);
if (this.props?.onDrag) {
this.props.onDrag(e);
}

View File

@ -7,7 +7,7 @@ import { ISimulator, ComponentInstance, Component, NodeInstance } from '../simul
import { computed, obx } from '@recore/obx';
import Location from '../helper/location';
import { ComponentConfig } from '../component-config';
import { isElement } from '../../utils/dom';
import { isElement } from '../../utils/is-element';
export default class DocumentModel {
/**

View File

@ -6,7 +6,7 @@ import { NodeData } from '../schema';
import { ISimulator, isSimulator } from '../simulator';
import Node from '../document/node/node';
import Designer from '../designer';
import { setNativeSelection } from '../../utils/navtive-selection';
import { setNativeSelection } from './navtive-selection';
import cursor from './cursor';
export interface LocateEvent {
@ -135,8 +135,22 @@ export function setShaken(e: any) {
e.shaken = true;
}
function isFromTopDocument(e: MouseEvent) {
return e.view!.document === document;
function getSourceSensor(dragObject: DragObject): ISimulator | null {
if (!isDragNodeObject(dragObject)) {
return null;
}
return dragObject.nodes[0]?.document.simulator || null;
}
function makeSimulatorListener(masterSensors: ISimulator[]): (fn: (sdoc: Document) => void) => void {
return (fn: (sdoc: Document) => void) => {
masterSensors.forEach(sim => {
const sdoc = sim.contentDocument;
if (sdoc) {
fn(sdoc);
}
});
};
}
export default class Dragon {
@ -167,12 +181,12 @@ export default class Dragon {
}
// Get a new node to be dragged
const dragTarget = boost(e);
if (!dragTarget) {
const dragObject = boost(e);
if (!dragObject) {
return;
}
this.boost(dragTarget, e);
this.boost(dragObject, e);
};
shell.addEventListener('mousedown', mousedown as any);
return () => {
@ -180,24 +194,18 @@ export default class Dragon {
};
}
getMasterSensors(): ISimulator[] {
return this.designer.project.documents.map(doc => {
if (doc.actived && doc.simulator?.sensorAvailable) {
return doc.simulator;
}
return null;
}).filter(Boolean) as any;
}
boost(dragObject: DragObject, boostEvent: MouseEvent) {
const doc = document;
const isFromTop = isFromTopDocument(boostEvent);
const sourceDoc = boostEvent.view?.document;
const masterSensors = this.getMasterSensors();
const listenSimulators = !sourceDoc || sourceDoc === doc ? makeSimulatorListener(masterSensors) : null;
const alwaysListen = listenSimulators ? doc : sourceDoc!;
const designer = this.designer;
const newBie = dragObject.type !== DragObjectType.Node;
let lastSensor: ISensor | undefined;
this._dragging = false;
// 禁用默认的文稿拖选
this.setNativeSelection(false);
@ -216,10 +224,6 @@ export default class Dragon {
}
};
// period one fix:
// get evt source-sensor
// get globalX and globalY source-sensor
const drag = (e: MouseEvent) => {
checkcopy(e);
@ -241,10 +245,12 @@ export default class Dragon {
}
this.setDraggingState(true);
// ESC cancel drag
doc.addEventListener('keydown', checkesc, false);
if (isFromTop) {
// topDoc.addEventListener('keydown', checkesc, false);
}
alwaysListen.addEventListener('keydown', checkesc, false);
listenSimulators &&
listenSimulators(sdoc => {
sdoc.addEventListener('keydown', checkesc, false);
});
this.emitter.emit('dragstart', locateEvent);
};
@ -281,26 +287,21 @@ export default class Dragon {
this.clearState();
if (isFromTop) {
doc.removeEventListener('mousemove', move, true);
doc.removeEventListener('mouseup', over, true);
doc.removeEventListener('mousedown', over, true);
doc.removeEventListener('keydown', checkesc, false);
doc.removeEventListener('keydown', checkcopy as any, false);
doc.removeEventListener('keyup', checkcopy as any, false);
} else {
masterSensors.forEach(item => {
const odoc = item.contentDocument;
if (odoc) {
odoc.removeEventListener('mousemove', move, true);
odoc.removeEventListener('mouseup', over, true);
odoc.removeEventListener('mousedown', over, true);
odoc.removeEventListener('keydown', checkesc, false);
odoc.removeEventListener('keydown', checkcopy as any, false);
odoc.removeEventListener('keyup', checkcopy as any, false);
}
alwaysListen.removeEventListener('mousemove', move, true);
alwaysListen.removeEventListener('mouseup', over, true);
alwaysListen.removeEventListener('mousedown', over, true);
alwaysListen.removeEventListener('keydown', checkesc, false);
alwaysListen.removeEventListener('keydown', checkcopy as any, false);
alwaysListen.removeEventListener('keyup', checkcopy as any, false);
listenSimulators &&
listenSimulators(sdoc => {
sdoc.removeEventListener('mousemove', move, true);
sdoc.removeEventListener('mouseup', over, true);
sdoc.removeEventListener('mousedown', over, true);
sdoc.removeEventListener('keydown', checkesc, false);
sdoc.removeEventListener('keydown', checkcopy as any, false);
sdoc.removeEventListener('keyup', checkcopy as any, false);
});
}
if (exception) {
throw exception;
}
@ -348,13 +349,6 @@ export default class Dragon {
return evt;
};
function getSourceSensor(dragObject: DragObject): ISimulator | null {
if (!isDragNodeObject(dragObject)) {
return null;
}
return dragObject.nodes[0]?.document.simulator || null;
}
const sourceSensor = getSourceSensor(dragObject);
const sensors: ISensor[] = (masterSensors as ISensor[]).concat(this.sensors);
const chooseSensor = (e: LocateEvent) => {
@ -380,27 +374,42 @@ export default class Dragon {
return sensor;
};
doc.addEventListener('mousemove', move, true);
doc.addEventListener('mouseup', over, true);
doc.addEventListener('mousedown', over, true);
if (isFromTop) {/*
topDoc.addEventListener('mousemove', move, true);
topDoc.addEventListener('mouseup', over, true);
topDoc.addEventListener('mousedown', over, true);
*/
}
alwaysListen.addEventListener('mousemove', move, true);
alwaysListen.addEventListener('mouseup', over, true);
alwaysListen.addEventListener('mousedown', over, true);
listenSimulators &&
listenSimulators(sdoc => {
// alwaysListen = global document
// listen others simulator iframe
sdoc.addEventListener('mousemove', move, true);
sdoc.addEventListener('mouseup', over, true);
sdoc.addEventListener('mousedown', over, true);
});
// future think: drag things from browser-out or a iframe-pane
if (!newBie) {
doc.addEventListener('keydown', checkcopy as any, false);
doc.addEventListener('keyup', checkcopy as any, false);
if (isFromTop) {/*
topDoc.addEventListener('keydown', checkcopy as any, false);
topDoc.addEventListener('keyup', checkcopy as any, false);
*/
}
alwaysListen.addEventListener('keydown', checkcopy as any, false);
alwaysListen.addEventListener('keyup', checkcopy as any, false);
listenSimulators &&
listenSimulators(sdoc => {
sdoc.addEventListener('keydown', checkcopy as any, false);
sdoc.addEventListener('keyup', checkcopy as any, false);
});
}
}
private getMasterSensors(): ISimulator[] {
return this.designer.project.documents
.map(doc => {
if (doc.actived && doc.simulator?.sensorAvailable) {
return doc.simulator;
}
return null;
})
.filter(Boolean) as any;
}
// #region ======== drag and drop helpers ============
private setNativeSelection(enableFlag: boolean) {
setNativeSelection(enableFlag);
@ -447,7 +456,6 @@ export default class Dragon {
}
// #endregion
/**
*
*/

View File

@ -1,4 +1,4 @@
import { isElement } from '../../utils/dom';
import { isElement } from '../../utils/is-element';
export class ScrollTarget {
get left() {

View File

@ -1,10 +1,4 @@
export function isDOMNode(node: any): node is Element | Text {
return node.nodeType && (node.nodeType === Node.ELEMENT_NODE || node.nodeType === Node.TEXT_NODE);
}
export function isElement(node: any): node is Element {
return node.nodeType === Node.ELEMENT_NODE;
}
import { isElement } from './is-element';
// a range for test TextNode clientRect
const cycleRange = document.createRange();

View File

@ -0,0 +1,3 @@
export function isDOMNode(node: any): node is Element | Text {
return node.nodeType && (node.nodeType === Node.ELEMENT_NODE || node.nodeType === Node.TEXT_NODE);
}

View File

@ -0,0 +1,3 @@
export function isElement(node: any): node is Element {
return node.nodeType === Node.ELEMENT_NODE;
}