mirror of
https://github.com/alibaba/lowcode-engine.git
synced 2025-12-16 06:42:53 +00:00
sim 50%
This commit is contained in:
parent
a49ec4a171
commit
eb7a7f519f
@ -9,27 +9,30 @@
|
|||||||
"author": "",
|
"author": "",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@recore/core-obx": "^1.0.4",
|
||||||
"@recore/obx": "^1.0.5",
|
"@recore/obx": "^1.0.5",
|
||||||
"@types/medium-editor": "^5.0.3",
|
"@types/medium-editor": "^5.0.3",
|
||||||
|
"@ali/lowcode-renderer": "0.9.0",
|
||||||
"classnames": "^2.2.6",
|
"classnames": "^2.2.6",
|
||||||
"react": "^16",
|
"react": "^16",
|
||||||
"react-dom": "^16.7.0"
|
"react-dom": "^16.7.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@recore/config": "^2.0.0",
|
||||||
"@types/classnames": "^2.2.7",
|
"@types/classnames": "^2.2.7",
|
||||||
"@types/jest": "^24.0.16",
|
"@types/jest": "^24.0.16",
|
||||||
|
"@types/node": "^13.7.1",
|
||||||
"@types/react": "^16",
|
"@types/react": "^16",
|
||||||
"@types/react-dom": "^16",
|
"@types/react-dom": "^16",
|
||||||
"@recore/config": "^2.0.0",
|
|
||||||
"ts-jest": "^24.0.2",
|
|
||||||
"ts-node": "^8.0.1",
|
|
||||||
"eslint": "^6.5.1",
|
"eslint": "^6.5.1",
|
||||||
"husky": "^1.1.2",
|
"husky": "^1.1.2",
|
||||||
"jest": "^23.4.1",
|
"jest": "^23.4.1",
|
||||||
"lint-staged": "^7.1.2",
|
"lint-staged": "^7.1.2",
|
||||||
|
"prettier": "^1.18.2",
|
||||||
|
"ts-jest": "^24.0.2",
|
||||||
|
"ts-node": "^8.0.1",
|
||||||
"tslib": "^1.9.3",
|
"tslib": "^1.9.3",
|
||||||
"typescript": "^3.1.3",
|
"typescript": "^3.1.3"
|
||||||
"prettier": "^1.18.2"
|
|
||||||
},
|
},
|
||||||
"lint-staged": {
|
"lint-staged": {
|
||||||
"./src/**/*.{ts,tsx}": [
|
"./src/**/*.{ts,tsx}": [
|
||||||
|
|||||||
@ -1,13 +0,0 @@
|
|||||||
// outline
|
|
||||||
// insertion
|
|
||||||
/*
|
|
||||||
// 插入指示 insertion 竖线 横线 插入块 禁止插入块
|
|
||||||
|
|
||||||
竖线:红色,绿色
|
|
||||||
横线:红色,绿色
|
|
||||||
插入块:透明绿色,透明红色
|
|
||||||
|
|
||||||
投放指示线
|
|
||||||
|
|
||||||
cover
|
|
||||||
*/
|
|
||||||
@ -1,80 +0,0 @@
|
|||||||
import { getCurrentAdaptor } from '../globals';
|
|
||||||
import Simulator from '../adaptors/simulator';
|
|
||||||
import { isCSSUrl } from '../utils/is-css-url';
|
|
||||||
|
|
||||||
export interface AssetMap {
|
|
||||||
jsUrl?: string;
|
|
||||||
cssUrl?: string;
|
|
||||||
jsText?: string;
|
|
||||||
cssText?: string;
|
|
||||||
}
|
|
||||||
export type AssetList = string[];
|
|
||||||
export type Assets = AssetMap[] | AssetList;
|
|
||||||
|
|
||||||
function isAssetMap(obj: any): obj is AssetMap {
|
|
||||||
return obj && typeof obj === 'object';
|
|
||||||
}
|
|
||||||
|
|
||||||
export function createSimulator<T, I>(iframe: HTMLIFrameElement, vendors: Assets = []): Promise<Simulator<T, I>> {
|
|
||||||
const currentAdaptor = getCurrentAdaptor();
|
|
||||||
const win: any = iframe.contentWindow;
|
|
||||||
const doc = iframe.contentDocument!;
|
|
||||||
|
|
||||||
const styles: string[] = [];
|
|
||||||
let scripts: string[] = [];
|
|
||||||
const afterScripts: string[] = [];
|
|
||||||
|
|
||||||
vendors.forEach((asset: any) => {
|
|
||||||
if (!isAssetMap(asset)) {
|
|
||||||
if (isCSSUrl(asset)) {
|
|
||||||
asset = { cssUrl: asset };
|
|
||||||
} else {
|
|
||||||
if (asset.startsWith('!')) {
|
|
||||||
afterScripts.push(`<script src="${asset.slice(1)}"></script>`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
asset = { jsUrl: asset };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (asset.jsText) {
|
|
||||||
scripts.push(`<script>${asset.jsText}</script>`);
|
|
||||||
}
|
|
||||||
if (asset.jsUrl) {
|
|
||||||
scripts.push(`<script src="${asset.jsUrl}"></script>`);
|
|
||||||
}
|
|
||||||
if (asset.cssUrl) {
|
|
||||||
styles.push(`<link rel="stylesheet" href="${asset.cssUrl}" />`);
|
|
||||||
}
|
|
||||||
if (asset.cssText) {
|
|
||||||
styles.push(`<style type="text/css">${asset.cssText}</style>`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
currentAdaptor.simulatorUrls.forEach(url => {
|
|
||||||
if (isCSSUrl(url)) {
|
|
||||||
styles.push(`<link rel="stylesheet" href="${url}" />`);
|
|
||||||
} else {
|
|
||||||
scripts.push(`<script src="${url}"></script>`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
scripts = scripts.concat(afterScripts);
|
|
||||||
|
|
||||||
doc.open();
|
|
||||||
doc.write(`<!doctype html><html><head><meta charset="utf-8"/>
|
|
||||||
${styles.join('\n')}
|
|
||||||
<style base-point></style>
|
|
||||||
${scripts.join('\n')}
|
|
||||||
</head></html>`);
|
|
||||||
doc.close();
|
|
||||||
|
|
||||||
return new Promise(resolve => {
|
|
||||||
if (win.VisionSimulator) {
|
|
||||||
return resolve(win.VisionSimulator);
|
|
||||||
}
|
|
||||||
const loaded = () => {
|
|
||||||
resolve(win.VisionSimulator);
|
|
||||||
win.removeEventListener('load', loaded);
|
|
||||||
};
|
|
||||||
win.addEventListener('load', loaded);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@ -10,6 +10,21 @@ class Designer {
|
|||||||
dragboost(locateEvent: LocateEvent): void;
|
dragboost(locateEvent: LocateEvent): void;
|
||||||
addDropSensor(dropSensor: DropSensor): void;
|
addDropSensor(dropSensor: DropSensor): void;
|
||||||
|
|
||||||
|
|
||||||
|
private _suspensed: boolean = false;
|
||||||
|
|
||||||
|
get suspensed(): boolean {
|
||||||
|
return this._suspensed;
|
||||||
|
}
|
||||||
|
|
||||||
|
set suspensed(flag: boolean) {
|
||||||
|
this._suspensed = flag;
|
||||||
|
// Todo afterwards...
|
||||||
|
if (flag) {
|
||||||
|
// this.project.suspensed = true?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 事件 & 消息
|
// 事件 & 消息
|
||||||
onActiveChange(): () => void;
|
onActiveChange(): () => void;
|
||||||
onDragstart(): void;
|
onDragstart(): void;
|
||||||
|
|||||||
@ -24,14 +24,18 @@ export default class DocumentContext {
|
|||||||
*/
|
*/
|
||||||
// TODO
|
// TODO
|
||||||
// readonly history: History = new History(this);
|
// readonly history: History = new History(this);
|
||||||
/**
|
|
||||||
* 模拟器
|
|
||||||
*/
|
|
||||||
simulator?: SimulatorInterface;
|
|
||||||
|
|
||||||
private nodesMap = new Map<string, Node>();
|
private nodesMap = new Map<string, Node>();
|
||||||
private nodes = new Set<Node>();
|
private nodes = new Set<Node>();
|
||||||
private seqId = 0;
|
private seqId = 0;
|
||||||
|
private _simulator?: SimulatorInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 模拟器
|
||||||
|
*/
|
||||||
|
get simulator(): SimulatorInterface | null {
|
||||||
|
return this._simulator || null;
|
||||||
|
}
|
||||||
|
|
||||||
get fileName() {
|
get fileName() {
|
||||||
return this.rootNode.extras.get('fileName')?.value as string;
|
return this.rootNode.extras.get('fileName')?.value as string;
|
||||||
@ -179,6 +183,9 @@ export default class DocumentContext {
|
|||||||
// return !this.history.isSavePoint();
|
// return !this.history.isSavePoint();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 提供给模拟器的参数
|
||||||
|
*/
|
||||||
@computed get simulatorProps(): object {
|
@computed get simulatorProps(): object {
|
||||||
let simulatorProps = this.project.simulatorProps;
|
let simulatorProps = this.project.simulatorProps;
|
||||||
if (typeof simulatorProps === 'function') {
|
if (typeof simulatorProps === 'function') {
|
||||||
@ -192,7 +199,7 @@ export default class DocumentContext {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private mountSimulator(simulator: SimulatorInterface) {
|
private mountSimulator(simulator: SimulatorInterface) {
|
||||||
this.simulator = simulator;
|
this._simulator = simulator;
|
||||||
// TODO: emit simulator mounted
|
// TODO: emit simulator mounted
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -241,23 +248,53 @@ export default class DocumentContext {
|
|||||||
return this.simulator!.getCurrentComponent(componentName);
|
return this.simulator!.getCurrentComponent(componentName);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private _opened: boolean = true;
|
||||||
* 激活
|
private _suspensed: boolean = false;
|
||||||
*/
|
|
||||||
active(): void {}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 不激活
|
* 是否不是激活的
|
||||||
*/
|
*/
|
||||||
suspense(): void {}
|
get suspensed(): boolean {
|
||||||
|
return this._suspensed;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 开启
|
* 与 suspensed 相反,是否是激活的,这个函数可能用的更多一点
|
||||||
*/
|
*/
|
||||||
open(): void {}
|
get actived(): boolean {
|
||||||
|
return !this._suspensed;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 关闭
|
* 切换激活,只有打开的才能激活
|
||||||
|
* 不激活,打开之后切换到另外一个时发生,比如 tab 视图,切换到另外一个标签页
|
||||||
*/
|
*/
|
||||||
close(): void {}
|
set suspensed(flag: boolean) {
|
||||||
|
if (!this._opened && !flag) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this._suspensed = flag;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 打开,已载入,默认建立时就打开状态,除非手动关闭
|
||||||
|
*/
|
||||||
|
open(): void {
|
||||||
|
this._opened = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 关闭,相当于 sleep,仍然缓存,停止一切响应,如果有发生的变更没被保存,仍然需要去取数据保存
|
||||||
|
*/
|
||||||
|
close(): void {
|
||||||
|
this.suspensed = true;
|
||||||
|
this._opened = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从项目中移除
|
||||||
|
*/
|
||||||
|
remove() {
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { INode, NodeParent } from './node/node';
|
import ComponentNode, { NodeParent } from './node/node';
|
||||||
import DocumentContext from './document-context';
|
import DocumentContext from './document-context';
|
||||||
|
|
||||||
export interface LocationData {
|
export interface LocationData {
|
||||||
@ -15,7 +15,7 @@ export interface LocationChildrenDetail {
|
|||||||
type: LocationDetailType.Children;
|
type: LocationDetailType.Children;
|
||||||
index: number;
|
index: number;
|
||||||
near?: {
|
near?: {
|
||||||
node: INode;
|
node: ComponentNode;
|
||||||
pos: 'before' | 'after';
|
pos: 'before' | 'after';
|
||||||
rect?: Rect;
|
rect?: Rect;
|
||||||
align?: 'V' | 'H';
|
align?: 'V' | 'H';
|
||||||
|
|||||||
@ -1,96 +0,0 @@
|
|||||||
import { obx } from '@ali/recore';
|
|
||||||
import { screen } from '../globals/screen';
|
|
||||||
import { Point } from './location';
|
|
||||||
import { ScrollTarget } from '../../builtins/simulator/scroller';
|
|
||||||
|
|
||||||
export type AutoFit = '100%';
|
|
||||||
export const AutoFit = '100%';
|
|
||||||
|
|
||||||
export default class Viewport {
|
|
||||||
private shell: HTMLDivElement | undefined;
|
|
||||||
scrollTarget: ScrollTarget | undefined;
|
|
||||||
|
|
||||||
get scale(): number {
|
|
||||||
if (this.width === AutoFit) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
return screen.width / this.width;
|
|
||||||
}
|
|
||||||
|
|
||||||
get height(): number | AutoFit {
|
|
||||||
if (this.scale === 1) {
|
|
||||||
return AutoFit;
|
|
||||||
}
|
|
||||||
return screen.height / this.scale;
|
|
||||||
}
|
|
||||||
|
|
||||||
private _bounds: ClientRect | DOMRect | null = null;
|
|
||||||
get bounds(): ClientRect | DOMRect {
|
|
||||||
if (this._bounds) {
|
|
||||||
return this._bounds;
|
|
||||||
}
|
|
||||||
this._bounds = this.shell!.getBoundingClientRect();
|
|
||||||
requestAnimationFrame(() => {
|
|
||||||
this._bounds = null;
|
|
||||||
});
|
|
||||||
return this._bounds;
|
|
||||||
}
|
|
||||||
|
|
||||||
get innerBounds(): ClientRect | DOMRect {
|
|
||||||
const bounds = this.bounds;
|
|
||||||
const scale = this.scale;
|
|
||||||
const ret: any = {
|
|
||||||
top: 0,
|
|
||||||
left: 0,
|
|
||||||
x: 0,
|
|
||||||
y: 0,
|
|
||||||
width: bounds.width / scale,
|
|
||||||
height: bounds.height / scale,
|
|
||||||
};
|
|
||||||
ret.right = ret.width;
|
|
||||||
ret.bottom = ret.height;
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
@obx.ref width: number | AutoFit = AutoFit;
|
|
||||||
@obx.ref scrollX = 0;
|
|
||||||
@obx.ref scrollY = 0;
|
|
||||||
|
|
||||||
setShell(shell: HTMLDivElement) {
|
|
||||||
this.shell = shell;
|
|
||||||
}
|
|
||||||
|
|
||||||
setScrollTarget(target: Window) {
|
|
||||||
this.scrollTarget = new ScrollTarget(target);
|
|
||||||
this.scrollX = this.scrollTarget.left;
|
|
||||||
this.scrollY = this.scrollTarget.top;
|
|
||||||
target.onscroll = () => {
|
|
||||||
this.scrollX = this.scrollTarget!.left;
|
|
||||||
this.scrollY = this.scrollTarget!.top;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
toGlobalPoint(point: Point): Point {
|
|
||||||
if (!this.shell) {
|
|
||||||
return point;
|
|
||||||
}
|
|
||||||
|
|
||||||
const rect = this.shell.getBoundingClientRect();
|
|
||||||
return {
|
|
||||||
clientX: point.clientX * this.scale + rect.left,
|
|
||||||
clientY: point.clientY * this.scale + rect.top,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
toLocalPoint(point: Point): Point {
|
|
||||||
if (!this.shell) {
|
|
||||||
return point;
|
|
||||||
}
|
|
||||||
|
|
||||||
const rect = this.shell.getBoundingClientRect();
|
|
||||||
return {
|
|
||||||
clientX: (point.clientX - rect.left) / this.scale,
|
|
||||||
clientY: (point.clientY - rect.top) / this.scale,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,69 +1,107 @@
|
|||||||
import { EventEmitter } from 'events';
|
import { EventEmitter } from 'events';
|
||||||
import MasterBoard from '../document/master-board';
|
import { obx } from '@recore/obx';
|
||||||
import Location from '../document/location';
|
import Location from './document/location';
|
||||||
import { INode } from '../document/node';
|
import Project from './project';
|
||||||
import { NodeData } from '../document/document-data';
|
import DocumentContext from './document/document-context';
|
||||||
import { getCurrentDocument } from './current';
|
import { NodeData } from './schema';
|
||||||
import { obx } from '@ali/recore';
|
import { SimulatorInterface } from './simulator-interface';
|
||||||
|
import Node from './document/node/node';
|
||||||
|
|
||||||
export interface LocateEvent {
|
export interface LocateEvent {
|
||||||
readonly type: 'LocateEvent';
|
readonly type: 'LocateEvent';
|
||||||
readonly clientX: number;
|
/**
|
||||||
readonly clientY: number;
|
* 浏览器窗口坐标系
|
||||||
|
*/
|
||||||
readonly globalX: number;
|
readonly globalX: number;
|
||||||
readonly globalY: number;
|
readonly globalY: number;
|
||||||
|
/**
|
||||||
|
* 原始事件
|
||||||
|
*/
|
||||||
readonly originalEvent: MouseEvent;
|
readonly originalEvent: MouseEvent;
|
||||||
readonly dragTarget: DragTarget;
|
/**
|
||||||
|
* 拖拽对象
|
||||||
|
*/
|
||||||
|
readonly dragObject: DragObject;
|
||||||
|
/**
|
||||||
|
* 浏览器事件响应目标
|
||||||
|
*/
|
||||||
target: Element | null;
|
target: Element | null;
|
||||||
|
/**
|
||||||
|
* 当前激活文档画布坐标系
|
||||||
|
*/
|
||||||
|
canvasX?: number;
|
||||||
|
canvasY?: number;
|
||||||
|
/**
|
||||||
|
* 激活或目标文档
|
||||||
|
*/
|
||||||
|
document?: DocumentContext;
|
||||||
|
/**
|
||||||
|
* 事件订正标识,初始构造时,从发起端构造,缺少 canvasX,canvasY, 需要经过订正才有
|
||||||
|
*/
|
||||||
fixed?: true;
|
fixed?: true;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type DragTarget = NodesDragTarget | NodeDatasDragTarget | AnyDragTarget;
|
/**
|
||||||
|
* 拖拽敏感板
|
||||||
export enum DragTargetType {
|
*/
|
||||||
Nodes = 'nodes',
|
export interface SensorInterface {
|
||||||
NodeDatas = 'nodedatas',
|
/**
|
||||||
|
* 是否可响应,比如面板被隐藏,可设置该值 false
|
||||||
|
*/
|
||||||
|
readonly sensorAvailable: boolean;
|
||||||
|
/**
|
||||||
|
* 给事件打补丁
|
||||||
|
*/
|
||||||
|
fixEvent(e: LocateEvent): LocateEvent;
|
||||||
|
/**
|
||||||
|
* 定位并激活
|
||||||
|
*/
|
||||||
|
locate(e: LocateEvent): Location | undefined;
|
||||||
|
/**
|
||||||
|
* 是否进入敏感板区域
|
||||||
|
*/
|
||||||
|
isEnter(e: LocateEvent): boolean;
|
||||||
|
/**
|
||||||
|
* 取消激活
|
||||||
|
*/
|
||||||
|
deactiveSensor(): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface NodesDragTarget {
|
export type DragObject = DragNodeObject | DragNodeDataObject | DragAnyObject;
|
||||||
type: DragTargetType.Nodes;
|
|
||||||
nodes: INode[];
|
export enum DragObjectType {
|
||||||
|
Node = 'node',
|
||||||
|
NodeData = 'nodedata',
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isNodesDragTarget(obj: any): obj is NodesDragTarget {
|
export interface DragNodeObject {
|
||||||
return obj && obj.type === DragTargetType.Nodes;
|
type: DragObjectType.Node;
|
||||||
|
node: Node | Node[];
|
||||||
}
|
}
|
||||||
|
export interface DragNodeDataObject {
|
||||||
export interface NodeDatasDragTarget {
|
type: DragObjectType.NodeData;
|
||||||
type: DragTargetType.NodeDatas;
|
data: NodeData | NodeData[];
|
||||||
data: NodeData[];
|
|
||||||
maps?: { [tagName: string]: string };
|
maps?: { [tagName: string]: string };
|
||||||
thumbnail?: string;
|
thumbnail?: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
[extra: string]: any;
|
[extra: string]: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isNodeDatasDragTarget(obj: any): obj is NodeDatasDragTarget {
|
export interface DragAnyObject {
|
||||||
return obj && obj.type === DragTargetType.NodeDatas;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface AnyDragTarget {
|
|
||||||
type: string;
|
type: string;
|
||||||
[key: string]: any;
|
[key: string]: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isAnyDragTarget(obj: any): obj is AnyDragTarget {
|
export function isDragNodeObject(obj: any): obj is DragNodeObject {
|
||||||
return obj && obj.type !== DragTargetType.NodeDatas && obj.type !== DragTargetType.Nodes;
|
return obj && obj.type === DragObjectType.Node;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ISenseAble {
|
export function isDragNodeDataObject(obj: any): obj is DragNodeDataObject {
|
||||||
id: string;
|
return obj && obj.type === DragObjectType.NodeData;
|
||||||
sensitive: boolean;
|
}
|
||||||
fixEvent(e: LocateEvent): LocateEvent;
|
|
||||||
locate(e: LocateEvent): Location | undefined;
|
export function isDragAnyObject(obj: any): obj is DragAnyObject {
|
||||||
isEnter(e: LocateEvent): boolean;
|
return obj && obj.type !== DragObjectType.NodeData && obj.type !== DragObjectType.Node;
|
||||||
inRange(e: LocateEvent): boolean;
|
|
||||||
deactive(): void;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isLocateEvent(e: any): e is LocateEvent {
|
export function isLocateEvent(e: any): e is LocateEvent {
|
||||||
@ -92,7 +130,7 @@ function getTopDocument(e: MouseEvent, local: Document) {
|
|||||||
return e.view!.document === local ? null : document;
|
return e.view!.document === local ? null : document;
|
||||||
}
|
}
|
||||||
|
|
||||||
class Dragon {
|
export default class Dragon {
|
||||||
private sensors: ISenseAble[] = [];
|
private sensors: ISenseAble[] = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -113,7 +151,9 @@ class Dragon {
|
|||||||
return doc.masterBoard;
|
return doc.masterBoard;
|
||||||
}
|
}
|
||||||
|
|
||||||
from(shell: Element, boost: (e: MouseEvent) => DragTarget | null) {
|
constructor(readonly project: Project) {}
|
||||||
|
|
||||||
|
from(shell: Element, boost: (e: MouseEvent) => DragObject | null) {
|
||||||
const mousedown = (e: MouseEvent) => {
|
const mousedown = (e: MouseEvent) => {
|
||||||
// ESC or RightClick
|
// ESC or RightClick
|
||||||
if (e.which === 3 || e.button === 2) {
|
if (e.which === 3 || e.button === 2) {
|
||||||
@ -137,7 +177,7 @@ class Dragon {
|
|||||||
/**
|
/**
|
||||||
* dragTarget should be a INode | INode[] | NodeData | NodeData[]
|
* dragTarget should be a INode | INode[] | NodeData | NodeData[]
|
||||||
*/
|
*/
|
||||||
boost(dragTarget: DragTarget, boostEvent: MouseEvent) {
|
boost(dragObject: DragObject, boostEvent: MouseEvent) {
|
||||||
if (!this.master) {
|
if (!this.master) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -145,7 +185,7 @@ class Dragon {
|
|||||||
const doc = master.contentDocument;
|
const doc = master.contentDocument;
|
||||||
const viewport = master.document.viewport;
|
const viewport = master.document.viewport;
|
||||||
const topDoc = getTopDocument(boostEvent, doc);
|
const topDoc = getTopDocument(boostEvent, doc);
|
||||||
const newBie = dragTarget.type !== DragTargetType.Nodes;
|
const newBie = dragObject.type !== DragTargetType.Nodes;
|
||||||
let lastLocation: any = null;
|
let lastLocation: any = null;
|
||||||
let lastSensor: ISenseAble | undefined;
|
let lastSensor: ISenseAble | undefined;
|
||||||
this.dragging = false;
|
this.dragging = false;
|
||||||
@ -221,7 +261,7 @@ class Dragon {
|
|||||||
if (this.dragging) {
|
if (this.dragging) {
|
||||||
this.dragging = false;
|
this.dragging = false;
|
||||||
try {
|
try {
|
||||||
this.emitter.emit('dragend', { dragTarget, copy: master.isCopy() }, lastLocation);
|
this.emitter.emit('dragend', { dragTarget: dragObject, copy: master.isCopy() }, lastLocation);
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
exception = ex;
|
exception = ex;
|
||||||
}
|
}
|
||||||
@ -255,7 +295,7 @@ class Dragon {
|
|||||||
const evt: any = {
|
const evt: any = {
|
||||||
type: 'LocateEvent',
|
type: 'LocateEvent',
|
||||||
target: e.target,
|
target: e.target,
|
||||||
dragTarget,
|
dragTarget: dragObject,
|
||||||
originalEvent: e,
|
originalEvent: e,
|
||||||
};
|
};
|
||||||
if (e.view!.document === document) {
|
if (e.view!.document === document) {
|
||||||
@ -274,17 +314,29 @@ class Dragon {
|
|||||||
return evt;
|
return evt;
|
||||||
};
|
};
|
||||||
|
|
||||||
const sensors: ISenseAble[] = ([master] as any).concat(this.sensors);
|
function getSourceSensor(dragObject: DragObject): SimulatorInterface | null {
|
||||||
|
if (!isDragNodeObject(dragObject)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return (Array.isArray(dragObject.node) ? dragObject.node[0] : dragObject.node)?.document.simulator || null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const simSensors = this.project.documents.map(doc => (doc.actived && doc.simulator) || null).filter(Boolean);
|
||||||
|
const sourceSensor = getSourceSensor(dragObject);
|
||||||
|
// check simulator is empty
|
||||||
|
const sensors: SensorInterface[] = simSensors.concat(this.sensors);
|
||||||
const chooseSensor = (e: LocateEvent) => {
|
const chooseSensor = (e: LocateEvent) => {
|
||||||
let sensor;
|
let sensor = sensors.find(s => s.sensorAvailable && s.isEnter(e));
|
||||||
if (newBie && !lastLocation) {
|
if (!sensor) {
|
||||||
sensor = sensors.find(s => s.sensitive && s.isEnter(e));
|
if (lastSensor) {
|
||||||
} else {
|
sensor = lastSensor;
|
||||||
sensor = sensors.find(s => s.sensitive && s.inRange(e)) || lastSensor;
|
} else if (sourceSensor) {
|
||||||
|
sensor = sourceSensor;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (sensor !== lastSensor) {
|
if (sensor !== lastSensor) {
|
||||||
if (lastSensor) {
|
if (lastSensor) {
|
||||||
lastSensor.deactive();
|
lastSensor.deactiveSensor();
|
||||||
}
|
}
|
||||||
lastSensor = sensor;
|
lastSensor = sensor;
|
||||||
}
|
}
|
||||||
@ -338,12 +390,10 @@ class Dragon {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
onDragend(func: (x: { dragTarget: DragTarget; copy: boolean }, location: Location) => any) {
|
onDragend(func: (x: { dragTarget: DragObject; copy: boolean }, location: Location) => any) {
|
||||||
this.emitter.on('dragend', func);
|
this.emitter.on('dragend', func);
|
||||||
return () => {
|
return () => {
|
||||||
this.emitter.removeListener('dragend', func);
|
this.emitter.removeListener('dragend', func);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const dragon = new Dragon();
|
|
||||||
|
|||||||
@ -1,114 +0,0 @@
|
|||||||
import Hotkey, { isFormEvent } from '../utils/hotkey';
|
|
||||||
import { getCurrentDocument, getCurrentAdaptor } from './current';
|
|
||||||
import { isShadowNode } from '../document/node/shadow-node';
|
|
||||||
import { focusing } from './focusing';
|
|
||||||
import { INode, isElementNode, insertChildren } from '../document/node';
|
|
||||||
import { activeTracker } from './active-tracker';
|
|
||||||
import clipboard from '../utils/clipboard';
|
|
||||||
|
|
||||||
export const hotkey = new Hotkey();
|
|
||||||
|
|
||||||
// hotkey binding
|
|
||||||
hotkey.bind(['backspace', 'del'], (e: KeyboardEvent) => {
|
|
||||||
const doc = getCurrentDocument();
|
|
||||||
if (isFormEvent(e) || !doc) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
e.preventDefault();
|
|
||||||
|
|
||||||
const sel = doc.selection;
|
|
||||||
const topItems = sel.getTopNodes();
|
|
||||||
topItems.forEach(node => {
|
|
||||||
if (isShadowNode(node)) {
|
|
||||||
doc.removeNode(node.origin);
|
|
||||||
} else {
|
|
||||||
doc.removeNode(node);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
sel.clear();
|
|
||||||
});
|
|
||||||
|
|
||||||
hotkey.bind('escape', (e: KeyboardEvent) => {
|
|
||||||
const currentFocus = focusing.current;
|
|
||||||
if (isFormEvent(e) || !currentFocus) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
e.preventDefault();
|
|
||||||
|
|
||||||
currentFocus.esc();
|
|
||||||
});
|
|
||||||
|
|
||||||
function isHTMLTag(name: string) {
|
|
||||||
return /^[a-z]\w*$/.test(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
function isIgnore(uri: string) {
|
|
||||||
return /^(\.|@(builtins|html|imported):)/.test(uri);
|
|
||||||
}
|
|
||||||
|
|
||||||
function generateMaps(node: INode | INode[], maps: any = {}) {
|
|
||||||
if (Array.isArray(node)) {
|
|
||||||
node.forEach(n => generateMaps(n, maps));
|
|
||||||
return maps;
|
|
||||||
}
|
|
||||||
if (isElementNode(node)) {
|
|
||||||
const { uri, tagName } = node;
|
|
||||||
if (uri && !isHTMLTag(tagName) && !isIgnore(uri)) {
|
|
||||||
maps[tagName] = uri;
|
|
||||||
}
|
|
||||||
generateMaps(node.children, maps);
|
|
||||||
}
|
|
||||||
return maps;
|
|
||||||
}
|
|
||||||
|
|
||||||
// command + c copy command + x cut
|
|
||||||
hotkey.bind(['command+c', 'ctrl+c', 'command+x', 'ctrl+x'], (e, action) => {
|
|
||||||
const doc = getCurrentDocument();
|
|
||||||
if (isFormEvent(e) || !doc || !(focusing.id === 'outline' || focusing.id === 'canvas')) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
e.preventDefault();
|
|
||||||
|
|
||||||
const selected = doc.selection.getTopNodes(true);
|
|
||||||
if (!selected || selected.length < 1) return;
|
|
||||||
|
|
||||||
const maps = generateMaps(selected);
|
|
||||||
const nodesData = selected.map(item => item.nodeData);
|
|
||||||
const code = getCurrentAdaptor().viewDataToSource({
|
|
||||||
file: '',
|
|
||||||
children: nodesData as any,
|
|
||||||
});
|
|
||||||
|
|
||||||
clipboard.setData({ code, maps });
|
|
||||||
/*
|
|
||||||
const cutMode = action.indexOf('x') > 0;
|
|
||||||
if (cutMode) {
|
|
||||||
const parentNode = selected.getParent();
|
|
||||||
parentNode.select();
|
|
||||||
selected.remove();
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
});
|
|
||||||
|
|
||||||
// command + v paste
|
|
||||||
hotkey.bind(['command+v', 'ctrl+v'], e => {
|
|
||||||
const doc = getCurrentDocument();
|
|
||||||
if (isFormEvent(e) || !doc) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
clipboard.waitPasteData(e, data => {
|
|
||||||
if (data.code && data.maps) {
|
|
||||||
const adaptor = getCurrentAdaptor();
|
|
||||||
let nodesData = adaptor.parseToViewData(data.code, data.maps).children;
|
|
||||||
nodesData = doc.processDocumentData(nodesData, data.maps);
|
|
||||||
const { target, index } = doc.getSuitableInsertion();
|
|
||||||
const nodes = insertChildren(target, nodesData, index);
|
|
||||||
if (nodes) {
|
|
||||||
doc.selection.selectAll(nodes.map(o => o.id));
|
|
||||||
setTimeout(() => activeTracker.track(nodes[0]), 10);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
hotkey.mount(window);
|
|
||||||
@ -76,4 +76,7 @@ export default class Project {
|
|||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
// 通知标记删除,需要告知服务端
|
||||||
|
// 项目角度编辑不是全量打开所有文档,是按需加载,哪个更新就通知更新谁,
|
||||||
|
// 哪个删除就
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,9 @@
|
|||||||
export interface SimulatorInterface<P = object> {
|
import { NpmInfo } from './schema';
|
||||||
|
import { ComponentClass as ReactComponentClass, Component } from 'react';
|
||||||
|
import { LocateEvent, SensorInterface } from './dragon';
|
||||||
|
import { Point } from './document/location';
|
||||||
|
|
||||||
|
export interface SimulatorInterface<P = object> extends SensorInterface {
|
||||||
/**
|
/**
|
||||||
* 获得边界维度等信息
|
* 获得边界维度等信息
|
||||||
*/
|
*/
|
||||||
@ -52,15 +57,35 @@ export interface SimulatorInterface<P = object> {
|
|||||||
/**
|
/**
|
||||||
* 滚动视口到节点
|
* 滚动视口到节点
|
||||||
*/
|
*/
|
||||||
scrollToNode(node: INode, detail?: any): void;
|
scrollToNode(node: Node, detail?: any): void;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 给 event 打补丁,添加 canvasX, globalX 等信息,用于拖拽
|
* 给 event 打补丁,添加 canvasX, globalX 等信息,用于拖拽
|
||||||
*/
|
*/
|
||||||
fixEvent(e: LocateEvent): LocateEvent;
|
fixEvent(e: LocateEvent): LocateEvent;
|
||||||
|
|
||||||
getComponent(npmInfo: object): ReactComponent | any;
|
/**
|
||||||
getViewInstance(node: Node): ViewInstance[] | null;
|
* 全局坐标系转化为本地坐标系
|
||||||
|
*/
|
||||||
|
toLocalPoint(point: Point): Point;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 本地坐标系转化为全局坐标系
|
||||||
|
*/
|
||||||
|
toGlobalPoint(point: Point): Point;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据组件信息获取组件类
|
||||||
|
*/
|
||||||
|
getComponent(npmInfo: NpmInfo): ComponentClass | any;
|
||||||
|
/**
|
||||||
|
* 根据节点获取节点的组件实例
|
||||||
|
*/
|
||||||
|
getComponentInstance(node: Node): ComponentInstance[] | null;
|
||||||
|
/**
|
||||||
|
* 根据节点获取节点的组件运行上下文
|
||||||
|
*/
|
||||||
|
getComponentContext(node: Node): object;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 设置挂起
|
* 设置挂起
|
||||||
@ -70,5 +95,14 @@ export interface SimulatorInterface<P = object> {
|
|||||||
/**
|
/**
|
||||||
* 销毁
|
* 销毁
|
||||||
*/
|
*/
|
||||||
destroy(): void;
|
purge(): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 组件类定义
|
||||||
|
*/
|
||||||
|
export type ComponentClass = ReactComponentClass | object;
|
||||||
|
/**
|
||||||
|
* 组件实例定义
|
||||||
|
*/
|
||||||
|
export type ComponentInstance = Element | Component<any, any> | object;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user