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: 仅用作类型标注,切勿作为实体使用 // NOTE: 仅用作类型标注,切勿作为实体使用
import { SimulatorRenderer } from '../renderer/renderer'; import { SimulatorRenderer } from '../renderer/renderer';
import { SimulatorHost } from './host'; 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> { export function createSimulator(host: SimulatorHost, iframe: HTMLIFrameElement, vendors: AssetList = []): Promise<SimulatorRenderer> {
const win: any = iframe.contentWindow; const win: any = iframe.contentWindow;

View File

@ -12,7 +12,7 @@ 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 { setNativeSelection } from '../../../designer/helper/navtive-selection';
import cursor from '../../../designer/helper/cursor'; import cursor from '../../../designer/helper/cursor';
export interface SimulatorProps { export interface SimulatorProps {

View File

@ -4,66 +4,14 @@ import { host } from './host';
import SimulatorRendererView from './renderer-view'; import SimulatorRendererView from './renderer-view';
import { computed, obx } from '@recore/obx'; import { computed, obx } from '@recore/obx';
import { RootSchema, NpmInfo } from '../../../designer/schema'; 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 { 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 { reactFindDOMNodes } from '../utils/react-find-dom-nodes';
import { isESModule } from '../../../utils/is-es-module'; import { isESModule } from '../../../utils/is-es-module';
import { NodeInstance } from '../../../designer/simulator'; import { NodeInstance } from '../../../designer/simulator';
import { isElement } from '../../../utils/is-element';
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 class SimulatorRenderer { export class SimulatorRenderer {
readonly isSimulatorRenderer = true; readonly isSimulatorRenderer = true;
@ -205,7 +153,7 @@ export class SimulatorRenderer {
} }
findDOMNodes(instance: ReactInstance): Array<Element | Text> | null { findDOMNodes(instance: ReactInstance): Array<Element | Text> | null {
return findDOMNodes(instance); return reactFindDOMNodes(instance);
} }
getClientRects(element: Element | Text) { getClientRects(element: Element | Text) {
@ -297,4 +245,58 @@ function buildComponents(componentsMap: { [componentName: string]: ComponentDesc
return components; 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(); export default new SimulatorRenderer();

View File

@ -57,10 +57,6 @@ export function isAssetBundle(obj: any): obj is AssetBundle {
return obj && obj.type === AssetType.Bundle; 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 { export function assetBundle(assets?: Asset | AssetList | null, level?: AssetLevel): AssetBundle | null {
if (!assets) { if (!assets) {
return null; return null;

View File

@ -1,6 +1,7 @@
import { load, evaluate } from './script'; import { load, evaluate } from './script';
import StylePoint from './style'; 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) { function parseAssetList(scripts: any, styles: any, assets: AssetList, level?: AssetLevel) {
for (let asset of assets) { for (let asset of assets) {

View File

@ -1,5 +1,6 @@
import { ReactInstance } from 'react'; 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'; 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) { if (!elem) {
return null; return null;
} }

View File

@ -1,4 +1,4 @@
import { createDefer } from '../../../utils/create-defer'; import { createDefer } from './create-defer';
export function evaluate(script: string) { export function evaluate(script: string) {
const scriptEl = document.createElement('script'); 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 { export default class StylePoint {
private lastContent: string | undefined; private lastContent: string | undefined;

View File

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

View File

@ -7,7 +7,7 @@ import { ISimulator, ComponentInstance, Component, NodeInstance } from '../simul
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';
import { isElement } from '../../utils/dom'; import { isElement } from '../../utils/is-element';
export default class DocumentModel { export default class DocumentModel {
/** /**

View File

@ -6,7 +6,7 @@ import { NodeData } from '../schema';
import { ISimulator, isSimulator } 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 { setNativeSelection } from './navtive-selection';
import cursor from './cursor'; import cursor from './cursor';
export interface LocateEvent { export interface LocateEvent {
@ -135,8 +135,22 @@ export function setShaken(e: any) {
e.shaken = true; e.shaken = true;
} }
function isFromTopDocument(e: MouseEvent) { function getSourceSensor(dragObject: DragObject): ISimulator | null {
return e.view!.document === document; 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 { export default class Dragon {
@ -167,12 +181,12 @@ export default class Dragon {
} }
// Get a new node to be dragged // Get a new node to be dragged
const dragTarget = boost(e); const dragObject = boost(e);
if (!dragTarget) { if (!dragObject) {
return; return;
} }
this.boost(dragTarget, e); this.boost(dragObject, e);
}; };
shell.addEventListener('mousedown', mousedown as any); shell.addEventListener('mousedown', mousedown as any);
return () => { 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) { boost(dragObject: DragObject, boostEvent: MouseEvent) {
const doc = document; const doc = document;
const isFromTop = isFromTopDocument(boostEvent); const sourceDoc = boostEvent.view?.document;
const masterSensors = this.getMasterSensors(); const masterSensors = this.getMasterSensors();
const listenSimulators = !sourceDoc || sourceDoc === doc ? makeSimulatorListener(masterSensors) : null;
const alwaysListen = listenSimulators ? doc : sourceDoc!;
const designer = this.designer; const designer = this.designer;
const newBie = dragObject.type !== DragObjectType.Node; const newBie = dragObject.type !== DragObjectType.Node;
let lastSensor: ISensor | undefined; let lastSensor: ISensor | undefined;
this._dragging = false; this._dragging = false;
// 禁用默认的文稿拖选 // 禁用默认的文稿拖选
this.setNativeSelection(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) => { const drag = (e: MouseEvent) => {
checkcopy(e); checkcopy(e);
@ -241,10 +245,12 @@ export default class Dragon {
} }
this.setDraggingState(true); this.setDraggingState(true);
// ESC cancel drag // ESC cancel drag
doc.addEventListener('keydown', checkesc, false); alwaysListen.addEventListener('keydown', checkesc, false);
if (isFromTop) { listenSimulators &&
// topDoc.addEventListener('keydown', checkesc, false); listenSimulators(sdoc => {
} sdoc.addEventListener('keydown', checkesc, false);
});
this.emitter.emit('dragstart', locateEvent); this.emitter.emit('dragstart', locateEvent);
}; };
@ -281,26 +287,21 @@ export default class Dragon {
this.clearState(); this.clearState();
if (isFromTop) { alwaysListen.removeEventListener('mousemove', move, true);
doc.removeEventListener('mousemove', move, true); alwaysListen.removeEventListener('mouseup', over, true);
doc.removeEventListener('mouseup', over, true); alwaysListen.removeEventListener('mousedown', over, true);
doc.removeEventListener('mousedown', over, true); alwaysListen.removeEventListener('keydown', checkesc, false);
doc.removeEventListener('keydown', checkesc, false); alwaysListen.removeEventListener('keydown', checkcopy as any, false);
doc.removeEventListener('keydown', checkcopy as any, false); alwaysListen.removeEventListener('keyup', checkcopy as any, false);
doc.removeEventListener('keyup', checkcopy as any, false); listenSimulators &&
} else { listenSimulators(sdoc => {
masterSensors.forEach(item => { sdoc.removeEventListener('mousemove', move, true);
const odoc = item.contentDocument; sdoc.removeEventListener('mouseup', over, true);
if (odoc) { sdoc.removeEventListener('mousedown', over, true);
odoc.removeEventListener('mousemove', move, true); sdoc.removeEventListener('keydown', checkesc, false);
odoc.removeEventListener('mouseup', over, true); sdoc.removeEventListener('keydown', checkcopy as any, false);
odoc.removeEventListener('mousedown', over, true); sdoc.removeEventListener('keyup', checkcopy as any, false);
odoc.removeEventListener('keydown', checkesc, false);
odoc.removeEventListener('keydown', checkcopy as any, false);
odoc.removeEventListener('keyup', checkcopy as any, false);
}
}); });
}
if (exception) { if (exception) {
throw exception; throw exception;
} }
@ -348,13 +349,6 @@ export default class Dragon {
return evt; 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 sourceSensor = getSourceSensor(dragObject);
const sensors: ISensor[] = (masterSensors as ISensor[]).concat(this.sensors); const sensors: ISensor[] = (masterSensors as ISensor[]).concat(this.sensors);
const chooseSensor = (e: LocateEvent) => { const chooseSensor = (e: LocateEvent) => {
@ -380,27 +374,42 @@ export default class Dragon {
return sensor; return sensor;
}; };
doc.addEventListener('mousemove', move, true); alwaysListen.addEventListener('mousemove', move, true);
doc.addEventListener('mouseup', over, true); alwaysListen.addEventListener('mouseup', over, true);
doc.addEventListener('mousedown', over, true); alwaysListen.addEventListener('mousedown', over, true);
if (isFromTop) {/* listenSimulators &&
topDoc.addEventListener('mousemove', move, true); listenSimulators(sdoc => {
topDoc.addEventListener('mouseup', over, true); // alwaysListen = global document
topDoc.addEventListener('mousedown', over, true); // 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) { if (!newBie) {
doc.addEventListener('keydown', checkcopy as any, false); alwaysListen.addEventListener('keydown', checkcopy as any, false);
doc.addEventListener('keyup', checkcopy as any, false); alwaysListen.addEventListener('keyup', checkcopy as any, false);
if (isFromTop) {/* listenSimulators &&
topDoc.addEventListener('keydown', checkcopy as any, false); listenSimulators(sdoc => {
topDoc.addEventListener('keyup', checkcopy as any, false); 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 ============ // #region ======== drag and drop helpers ============
private setNativeSelection(enableFlag: boolean) { private setNativeSelection(enableFlag: boolean) {
setNativeSelection(enableFlag); setNativeSelection(enableFlag);
@ -447,7 +456,6 @@ export default class Dragon {
} }
// #endregion // #endregion
/** /**
* *
*/ */

View File

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

View File

@ -1,10 +1,4 @@
export function isDOMNode(node: any): node is Element | Text { import { isElement } from './is-element';
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;
}
// a range for test TextNode clientRect // a range for test TextNode clientRect
const cycleRange = document.createRange(); 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;
}