mirror of
https://github.com/alibaba/lowcode-engine.git
synced 2025-12-15 05:36:39 +00:00
sim 50%
This commit is contained in:
parent
a49ec4a171
commit
eb7a7f519f
@ -9,27 +9,30 @@
|
||||
"author": "",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@recore/core-obx": "^1.0.4",
|
||||
"@recore/obx": "^1.0.5",
|
||||
"@types/medium-editor": "^5.0.3",
|
||||
"@ali/lowcode-renderer": "0.9.0",
|
||||
"classnames": "^2.2.6",
|
||||
"react": "^16",
|
||||
"react-dom": "^16.7.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@recore/config": "^2.0.0",
|
||||
"@types/classnames": "^2.2.7",
|
||||
"@types/jest": "^24.0.16",
|
||||
"@types/node": "^13.7.1",
|
||||
"@types/react": "^16",
|
||||
"@types/react-dom": "^16",
|
||||
"@recore/config": "^2.0.0",
|
||||
"ts-jest": "^24.0.2",
|
||||
"ts-node": "^8.0.1",
|
||||
"eslint": "^6.5.1",
|
||||
"husky": "^1.1.2",
|
||||
"jest": "^23.4.1",
|
||||
"lint-staged": "^7.1.2",
|
||||
"prettier": "^1.18.2",
|
||||
"ts-jest": "^24.0.2",
|
||||
"ts-node": "^8.0.1",
|
||||
"tslib": "^1.9.3",
|
||||
"typescript": "^3.1.3",
|
||||
"prettier": "^1.18.2"
|
||||
"typescript": "^3.1.3"
|
||||
},
|
||||
"lint-staged": {
|
||||
"./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;
|
||||
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;
|
||||
onDragstart(): void;
|
||||
|
||||
@ -24,14 +24,18 @@ export default class DocumentContext {
|
||||
*/
|
||||
// TODO
|
||||
// readonly history: History = new History(this);
|
||||
/**
|
||||
* 模拟器
|
||||
*/
|
||||
simulator?: SimulatorInterface;
|
||||
|
||||
private nodesMap = new Map<string, Node>();
|
||||
private nodes = new Set<Node>();
|
||||
private seqId = 0;
|
||||
private _simulator?: SimulatorInterface;
|
||||
|
||||
/**
|
||||
* 模拟器
|
||||
*/
|
||||
get simulator(): SimulatorInterface | null {
|
||||
return this._simulator || null;
|
||||
}
|
||||
|
||||
get fileName() {
|
||||
return this.rootNode.extras.get('fileName')?.value as string;
|
||||
@ -179,6 +183,9 @@ export default class DocumentContext {
|
||||
// return !this.history.isSavePoint();
|
||||
}
|
||||
|
||||
/**
|
||||
* 提供给模拟器的参数
|
||||
*/
|
||||
@computed get simulatorProps(): object {
|
||||
let simulatorProps = this.project.simulatorProps;
|
||||
if (typeof simulatorProps === 'function') {
|
||||
@ -192,7 +199,7 @@ export default class DocumentContext {
|
||||
}
|
||||
|
||||
private mountSimulator(simulator: SimulatorInterface) {
|
||||
this.simulator = simulator;
|
||||
this._simulator = simulator;
|
||||
// TODO: emit simulator mounted
|
||||
}
|
||||
|
||||
@ -241,23 +248,53 @@ export default class DocumentContext {
|
||||
return this.simulator!.getCurrentComponent(componentName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 激活
|
||||
*/
|
||||
active(): void {}
|
||||
private _opened: boolean = true;
|
||||
private _suspensed: boolean = false;
|
||||
|
||||
/**
|
||||
* 不激活
|
||||
* 是否不是激活的
|
||||
*/
|
||||
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';
|
||||
|
||||
export interface LocationData {
|
||||
@ -15,7 +15,7 @@ export interface LocationChildrenDetail {
|
||||
type: LocationDetailType.Children;
|
||||
index: number;
|
||||
near?: {
|
||||
node: INode;
|
||||
node: ComponentNode;
|
||||
pos: 'before' | 'after';
|
||||
rect?: Rect;
|
||||
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 MasterBoard from '../document/master-board';
|
||||
import Location from '../document/location';
|
||||
import { INode } from '../document/node';
|
||||
import { NodeData } from '../document/document-data';
|
||||
import { getCurrentDocument } from './current';
|
||||
import { obx } from '@ali/recore';
|
||||
import { obx } from '@recore/obx';
|
||||
import Location from './document/location';
|
||||
import Project from './project';
|
||||
import DocumentContext from './document/document-context';
|
||||
import { NodeData } from './schema';
|
||||
import { SimulatorInterface } from './simulator-interface';
|
||||
import Node from './document/node/node';
|
||||
|
||||
export interface LocateEvent {
|
||||
readonly type: 'LocateEvent';
|
||||
readonly clientX: number;
|
||||
readonly clientY: number;
|
||||
/**
|
||||
* 浏览器窗口坐标系
|
||||
*/
|
||||
readonly globalX: number;
|
||||
readonly globalY: number;
|
||||
/**
|
||||
* 原始事件
|
||||
*/
|
||||
readonly originalEvent: MouseEvent;
|
||||
readonly dragTarget: DragTarget;
|
||||
/**
|
||||
* 拖拽对象
|
||||
*/
|
||||
readonly dragObject: DragObject;
|
||||
/**
|
||||
* 浏览器事件响应目标
|
||||
*/
|
||||
target: Element | null;
|
||||
/**
|
||||
* 当前激活文档画布坐标系
|
||||
*/
|
||||
canvasX?: number;
|
||||
canvasY?: number;
|
||||
/**
|
||||
* 激活或目标文档
|
||||
*/
|
||||
document?: DocumentContext;
|
||||
/**
|
||||
* 事件订正标识,初始构造时,从发起端构造,缺少 canvasX,canvasY, 需要经过订正才有
|
||||
*/
|
||||
fixed?: true;
|
||||
}
|
||||
|
||||
export type DragTarget = NodesDragTarget | NodeDatasDragTarget | AnyDragTarget;
|
||||
|
||||
export enum DragTargetType {
|
||||
Nodes = 'nodes',
|
||||
NodeDatas = 'nodedatas',
|
||||
/**
|
||||
* 拖拽敏感板
|
||||
*/
|
||||
export interface SensorInterface {
|
||||
/**
|
||||
* 是否可响应,比如面板被隐藏,可设置该值 false
|
||||
*/
|
||||
readonly sensorAvailable: boolean;
|
||||
/**
|
||||
* 给事件打补丁
|
||||
*/
|
||||
fixEvent(e: LocateEvent): LocateEvent;
|
||||
/**
|
||||
* 定位并激活
|
||||
*/
|
||||
locate(e: LocateEvent): Location | undefined;
|
||||
/**
|
||||
* 是否进入敏感板区域
|
||||
*/
|
||||
isEnter(e: LocateEvent): boolean;
|
||||
/**
|
||||
* 取消激活
|
||||
*/
|
||||
deactiveSensor(): void;
|
||||
}
|
||||
|
||||
export interface NodesDragTarget {
|
||||
type: DragTargetType.Nodes;
|
||||
nodes: INode[];
|
||||
export type DragObject = DragNodeObject | DragNodeDataObject | DragAnyObject;
|
||||
|
||||
export enum DragObjectType {
|
||||
Node = 'node',
|
||||
NodeData = 'nodedata',
|
||||
}
|
||||
|
||||
export function isNodesDragTarget(obj: any): obj is NodesDragTarget {
|
||||
return obj && obj.type === DragTargetType.Nodes;
|
||||
export interface DragNodeObject {
|
||||
type: DragObjectType.Node;
|
||||
node: Node | Node[];
|
||||
}
|
||||
|
||||
export interface NodeDatasDragTarget {
|
||||
type: DragTargetType.NodeDatas;
|
||||
data: NodeData[];
|
||||
export interface DragNodeDataObject {
|
||||
type: DragObjectType.NodeData;
|
||||
data: NodeData | NodeData[];
|
||||
maps?: { [tagName: string]: string };
|
||||
thumbnail?: string;
|
||||
description?: string;
|
||||
[extra: string]: any;
|
||||
}
|
||||
|
||||
export function isNodeDatasDragTarget(obj: any): obj is NodeDatasDragTarget {
|
||||
return obj && obj.type === DragTargetType.NodeDatas;
|
||||
}
|
||||
|
||||
export interface AnyDragTarget {
|
||||
export interface DragAnyObject {
|
||||
type: string;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
export function isAnyDragTarget(obj: any): obj is AnyDragTarget {
|
||||
return obj && obj.type !== DragTargetType.NodeDatas && obj.type !== DragTargetType.Nodes;
|
||||
export function isDragNodeObject(obj: any): obj is DragNodeObject {
|
||||
return obj && obj.type === DragObjectType.Node;
|
||||
}
|
||||
|
||||
export interface ISenseAble {
|
||||
id: string;
|
||||
sensitive: boolean;
|
||||
fixEvent(e: LocateEvent): LocateEvent;
|
||||
locate(e: LocateEvent): Location | undefined;
|
||||
isEnter(e: LocateEvent): boolean;
|
||||
inRange(e: LocateEvent): boolean;
|
||||
deactive(): void;
|
||||
export function isDragNodeDataObject(obj: any): obj is DragNodeDataObject {
|
||||
return obj && obj.type === DragObjectType.NodeData;
|
||||
}
|
||||
|
||||
export function isDragAnyObject(obj: any): obj is DragAnyObject {
|
||||
return obj && obj.type !== DragObjectType.NodeData && obj.type !== DragObjectType.Node;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
class Dragon {
|
||||
export default class Dragon {
|
||||
private sensors: ISenseAble[] = [];
|
||||
|
||||
/**
|
||||
@ -113,7 +151,9 @@ class Dragon {
|
||||
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) => {
|
||||
// ESC or RightClick
|
||||
if (e.which === 3 || e.button === 2) {
|
||||
@ -137,7 +177,7 @@ class Dragon {
|
||||
/**
|
||||
* dragTarget should be a INode | INode[] | NodeData | NodeData[]
|
||||
*/
|
||||
boost(dragTarget: DragTarget, boostEvent: MouseEvent) {
|
||||
boost(dragObject: DragObject, boostEvent: MouseEvent) {
|
||||
if (!this.master) {
|
||||
return;
|
||||
}
|
||||
@ -145,7 +185,7 @@ class Dragon {
|
||||
const doc = master.contentDocument;
|
||||
const viewport = master.document.viewport;
|
||||
const topDoc = getTopDocument(boostEvent, doc);
|
||||
const newBie = dragTarget.type !== DragTargetType.Nodes;
|
||||
const newBie = dragObject.type !== DragTargetType.Nodes;
|
||||
let lastLocation: any = null;
|
||||
let lastSensor: ISenseAble | undefined;
|
||||
this.dragging = false;
|
||||
@ -221,7 +261,7 @@ class Dragon {
|
||||
if (this.dragging) {
|
||||
this.dragging = false;
|
||||
try {
|
||||
this.emitter.emit('dragend', { dragTarget, copy: master.isCopy() }, lastLocation);
|
||||
this.emitter.emit('dragend', { dragTarget: dragObject, copy: master.isCopy() }, lastLocation);
|
||||
} catch (ex) {
|
||||
exception = ex;
|
||||
}
|
||||
@ -255,7 +295,7 @@ class Dragon {
|
||||
const evt: any = {
|
||||
type: 'LocateEvent',
|
||||
target: e.target,
|
||||
dragTarget,
|
||||
dragTarget: dragObject,
|
||||
originalEvent: e,
|
||||
};
|
||||
if (e.view!.document === document) {
|
||||
@ -274,17 +314,29 @@ class Dragon {
|
||||
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) => {
|
||||
let sensor;
|
||||
if (newBie && !lastLocation) {
|
||||
sensor = sensors.find(s => s.sensitive && s.isEnter(e));
|
||||
} else {
|
||||
sensor = sensors.find(s => s.sensitive && s.inRange(e)) || lastSensor;
|
||||
let sensor = sensors.find(s => s.sensorAvailable && s.isEnter(e));
|
||||
if (!sensor) {
|
||||
if (lastSensor) {
|
||||
sensor = lastSensor;
|
||||
} else if (sourceSensor) {
|
||||
sensor = sourceSensor;
|
||||
}
|
||||
}
|
||||
if (sensor !== lastSensor) {
|
||||
if (lastSensor) {
|
||||
lastSensor.deactive();
|
||||
lastSensor.deactiveSensor();
|
||||
}
|
||||
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);
|
||||
return () => {
|
||||
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 等信息,用于拖拽
|
||||
*/
|
||||
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