mirror of
https://github.com/alibaba/lowcode-engine.git
synced 2025-12-19 00:22:50 +00:00
sim 60%
This commit is contained in:
parent
eb7a7f519f
commit
6cb06d3369
@ -1,105 +0,0 @@
|
||||
import { Component } from 'react';
|
||||
import { observer, obx } from '@ali/recore';
|
||||
import { dragon } from '../../globals/dragon';
|
||||
|
||||
import './ghost.less';
|
||||
import { OutlineBoardID } from '../builtin-panes/outline-pane/outline-board';
|
||||
// import { INode } from '../../document/node';
|
||||
|
||||
type offBinding = () => any;
|
||||
|
||||
@observer
|
||||
export default class Ghost extends Component {
|
||||
private dispose: offBinding[] = [];
|
||||
@obx.ref private dragment: any = null;
|
||||
@obx.ref private x = 0;
|
||||
@obx.ref private y = 0;
|
||||
|
||||
componentWillMount() {
|
||||
this.dispose = [
|
||||
dragon.onDragstart(e => {
|
||||
this.dragment = e.dragTarget;
|
||||
this.x = e.clientX;
|
||||
this.y = e.clientY;
|
||||
}),
|
||||
dragon.onDrag(e => {
|
||||
this.x = e.clientX;
|
||||
this.y = e.clientY;
|
||||
}),
|
||||
dragon.onDragend(() => {
|
||||
this.dragment = null;
|
||||
this.x = 0;
|
||||
this.y = 0;
|
||||
}),
|
||||
];
|
||||
}
|
||||
|
||||
shouldComponentUpdate() {
|
||||
return false;
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
if (this.dispose) {
|
||||
this.dispose.forEach(off => off());
|
||||
}
|
||||
}
|
||||
|
||||
renderGhostGroup() {
|
||||
const dragment = this.dragment;
|
||||
if (Array.isArray(dragment)) {
|
||||
return dragment.map((node: any, index: number) => {
|
||||
const ghost = (
|
||||
<div className="my-ghost" key={`ghost-${index}`}>
|
||||
<div className="my-ghost-title">{node.tagName}</div>
|
||||
</div>
|
||||
);
|
||||
return ghost;
|
||||
});
|
||||
} else {
|
||||
return (
|
||||
<div className="my-ghost">
|
||||
<div className="my-ghost-title">{dragment.tagName}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
if (!this.dragment) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// let x = this.x;
|
||||
// let y = this.y;
|
||||
|
||||
// todo: 考虑多个图标、title、不同 sensor 区域的形态
|
||||
if (dragon.activeSensor && dragon.activeSensor.id === OutlineBoardID) {
|
||||
// const nodeId = (this.dragment as INode).id;
|
||||
// const elt = document.querySelector(`[data-id="${nodeId}"`) as HTMLDivElement;
|
||||
//
|
||||
// if (elt) {
|
||||
// // do something
|
||||
// // const target = elt.cloneNode(true) as HTMLDivElement;
|
||||
// console.log('>>> target', elt);
|
||||
// elt.classList.remove('hidden');
|
||||
// elt.classList.add('dragging');
|
||||
// elt.style.transform = `translate(${this.x}px, ${this.y}px)`;
|
||||
// }
|
||||
//
|
||||
// return null;
|
||||
// x -= 30;
|
||||
// y += 30;
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className="my-ghost-group"
|
||||
style={{
|
||||
transform: `translate(${this.x}px, ${this.y}px)`,
|
||||
}}
|
||||
>
|
||||
{this.renderGhostGroup()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1,47 +0,0 @@
|
||||
.my-canvas {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
margin: 10px;
|
||||
box-shadow: 0 2px 10px 0 rgba(31,56,88,.15);
|
||||
}
|
||||
|
||||
html.my-show-topbar .my-canvas {
|
||||
top: var(--topbar-height);
|
||||
}
|
||||
html.my-show-toolbar .my-canvas {
|
||||
top: var(--toolbar-height);
|
||||
}
|
||||
html.my-show-topbar.my-show-toolbar .my-canvas {
|
||||
top: calc(var(--topbar-height) + var(--topbar-height));
|
||||
}
|
||||
|
||||
.my-screen {
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
left: 0;
|
||||
position: absolute;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.my-doc-shell {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
.my-doc-frame {
|
||||
border: none;
|
||||
transform-origin: 0 0;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.my-drag-pane-mode .my-doc-shell {
|
||||
pointer-events: none;
|
||||
}
|
||||
@ -1,76 +0,0 @@
|
||||
import { Component } from 'react';
|
||||
import { observer } from '@ali/recore';
|
||||
import { getCurrentDocument, screen, progressing } from '../../globals';
|
||||
import { AutoFit } from '../../document/viewport';
|
||||
import { AuxiliaryView } from '../auxiliary';
|
||||
import { PreLoaderView } from '../widgets/pre-loader';
|
||||
import DocumentContext from '../../document/document-context';
|
||||
import FocusingArea from '../widgets/focusing-area';
|
||||
import './canvas.less';
|
||||
|
||||
const Canvas = () => (
|
||||
<FocusingArea
|
||||
className="my-canvas"
|
||||
id="canvas"
|
||||
onEsc={() => {
|
||||
const doc = getCurrentDocument();
|
||||
if (doc) {
|
||||
doc.selection.clear();
|
||||
}
|
||||
return false;
|
||||
}}
|
||||
>
|
||||
<Screen />
|
||||
</FocusingArea>
|
||||
);
|
||||
|
||||
export default Canvas;
|
||||
|
||||
@observer
|
||||
class Screen extends Component {
|
||||
render() {
|
||||
const doc = getCurrentDocument();
|
||||
// TODO: thinkof multi documents
|
||||
return (
|
||||
<div ref={elmt => screen.mount(elmt)} className="my-screen">
|
||||
{progressing.visible ? <PreLoaderView /> : null}
|
||||
<AuxiliaryView />
|
||||
{doc ? <DocumentView key={doc.id} doc={doc} /> : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@observer
|
||||
class DocumentView extends Component<{ doc: DocumentContext }> {
|
||||
componentWillUnmount() {
|
||||
this.props.doc.sleep();
|
||||
}
|
||||
|
||||
render() {
|
||||
const { doc } = this.props;
|
||||
const viewport = doc.viewport;
|
||||
let shellStyle = {};
|
||||
let frameStyle = {};
|
||||
if (viewport.width !== AutoFit && viewport.height !== AutoFit) {
|
||||
const shellWidth = viewport.width * viewport.scale;
|
||||
const screenWidth = screen.width;
|
||||
const shellLeft = shellWidth < screenWidth ? `calc((100% - ${shellWidth}px) / 2)` : 0;
|
||||
shellStyle = {
|
||||
width: shellWidth,
|
||||
left: shellLeft,
|
||||
};
|
||||
frameStyle = {
|
||||
transform: `scale(${viewport.scale})`,
|
||||
height: viewport.height,
|
||||
width: viewport.width,
|
||||
};
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="my-doc-shell" style={shellStyle}>
|
||||
<iframe className="my-doc-frame" style={frameStyle} ref={frame => doc.mountRuntimeFrame(frame)} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1,17 +1,149 @@
|
||||
class Designer {
|
||||
id: string = guid();
|
||||
hotkey: Hotkey;
|
||||
import Dragon, { isDragNodeObject, isDragNodeDataObject } from "./dragon";
|
||||
import Project from './project';
|
||||
import { ProjectSchema } from './schema';
|
||||
import DocumentModel from './document/document-model';
|
||||
import BuiltinSimulatorView from '../builtins/simulator/master';
|
||||
import { Component } from 'react';
|
||||
import { obx, computed } from '@recore/obx';
|
||||
import ActiveTracker from './active-tracker';
|
||||
import Location, { LocationData, isLocationChildrenDetail } from './location';
|
||||
import Node, { insertChildren } from './document/node/node';
|
||||
|
||||
constructor(options: BuilderOptions): Builder;
|
||||
export interface DesignerProps {
|
||||
className?: string;
|
||||
style?: object;
|
||||
defaultSchema?: ProjectSchema;
|
||||
hotkeys?: object;
|
||||
simulatorProps?: object | ((document: DocumentModel) => object);
|
||||
simulatorComponent?: Component<any>;
|
||||
dragGhostComponent?: Component<any>;
|
||||
suspensed?: boolean;
|
||||
onMount?: (designer: Designer) => void;
|
||||
onDragstart?: (designer: Designer) => void;
|
||||
onDrag?: (designer: Designer) => void;
|
||||
onDragend?: (designer: Designer) => void;
|
||||
// TODO: ...add other events support
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
getValue(): ProjectSchema;
|
||||
setValue(schema: ProjectSchema): void;
|
||||
project: Project;
|
||||
dragboost(locateEvent: LocateEvent): void;
|
||||
addDropSensor(dropSensor: DropSensor): void;
|
||||
export default class Designer {
|
||||
// readonly hotkey: Hotkey;
|
||||
readonly dragon = new Dragon(this);
|
||||
readonly activeTracker = new ActiveTracker();
|
||||
readonly project: Project;
|
||||
|
||||
constructor(props: DesignerProps) {
|
||||
this.project = new Project(this, props.defaultSchema);
|
||||
|
||||
private _suspensed: boolean = false;
|
||||
this.dragon.onDragstart(({ dragObject }) => {
|
||||
if (isDragNodeObject(dragObject) && dragObject.nodes.length === 1) {
|
||||
// ensure current selecting
|
||||
dragObject.nodes[0].select();
|
||||
}
|
||||
});
|
||||
|
||||
this.dragon.onDragend(({ dragObject, copy }) => {
|
||||
const loc = this._dropLocation;
|
||||
if (loc) {
|
||||
if (isLocationChildrenDetail(loc.detail)) {
|
||||
let nodes: Node[] | undefined;
|
||||
if (isDragNodeObject(dragObject)) {
|
||||
nodes = insertChildren(loc.target, dragObject.nodes, loc.detail.index, copy);
|
||||
} else if (isDragNodeDataObject(dragObject)) {
|
||||
// process nodeData
|
||||
const nodeData = Array.isArray(dragObject.data) ? dragObject.data : [dragObject.data];
|
||||
nodes = insertChildren(loc.target, nodeData, loc.detail.index);
|
||||
}
|
||||
if (nodes) {
|
||||
loc.document.selection.selectAll(nodes.map(o => o.id));
|
||||
setTimeout(() => this.activeTracker.track(nodes![0]), 10);
|
||||
}
|
||||
}
|
||||
}
|
||||
this.clearLocation();
|
||||
// this.enableEdging();
|
||||
});
|
||||
|
||||
this.activeTracker.onChange(({ node, detail }) => {
|
||||
node.document.simulator?.scrollToNode(node, detail);
|
||||
});
|
||||
|
||||
this.setProps(props);
|
||||
}
|
||||
|
||||
private _dropLocation?: Location;
|
||||
/**
|
||||
* 创建插入位置
|
||||
*/
|
||||
createLocation(locationData: LocationData): Location {
|
||||
const loc = new Location(locationData);
|
||||
this._dropLocation = loc;
|
||||
loc.document.internalSetDropLocation(loc);
|
||||
this.activeTracker.track({ node: loc.target, detail: loc.detail });
|
||||
return loc;
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除插入位置
|
||||
*/
|
||||
private clearLocation() {
|
||||
if (this._dropLocation) {
|
||||
this._dropLocation.document.internalSetDropLocation(null);
|
||||
}
|
||||
this._dropLocation = undefined;
|
||||
}
|
||||
|
||||
private props?: DesignerProps;
|
||||
setProps(props: DesignerProps) {
|
||||
if (this.props) {
|
||||
// check hotkeys
|
||||
// TODO:
|
||||
// check simulatorConfig
|
||||
if (props.simulatorComponent !== this.props.simulatorComponent) {
|
||||
this._simulatorComponent = props.simulatorComponent;
|
||||
}
|
||||
if (props.simulatorProps !== this.props.simulatorProps) {
|
||||
this._simulatorProps = props.simulatorProps;
|
||||
}
|
||||
if (props.suspensed !== this.props.suspensed && props.suspensed != null) {
|
||||
this.suspensed = props.suspensed;
|
||||
}
|
||||
} else {
|
||||
// init hotkeys
|
||||
// todo:
|
||||
// init simulatorConfig
|
||||
if (props.simulatorComponent) {
|
||||
this._simulatorComponent = props.simulatorComponent;
|
||||
}
|
||||
if (props.simulatorProps) {
|
||||
this._simulatorProps = props.simulatorProps;
|
||||
}
|
||||
// init suspensed
|
||||
if (props.suspensed != null) {
|
||||
this.suspensed = props.suspensed;
|
||||
}
|
||||
}
|
||||
this.props = props;
|
||||
}
|
||||
|
||||
get(key: string): any {
|
||||
return this.props ? this.props[key] : null;
|
||||
}
|
||||
|
||||
@obx.ref private _simulatorComponent?: Component<any>;
|
||||
@obx.ref private _simulatorProps?: object;
|
||||
@computed get simulatorConfig(): {
|
||||
Component: Component<any>;
|
||||
props: object;
|
||||
} {
|
||||
const config: any = {
|
||||
Component: this._simulatorComponent || BuiltinSimulatorView,
|
||||
props: this._simulatorProps || {},
|
||||
};
|
||||
return config;
|
||||
}
|
||||
|
||||
@obx.ref private _suspensed: boolean = false;
|
||||
|
||||
get suspensed(): boolean {
|
||||
return this._suspensed;
|
||||
@ -25,9 +157,15 @@ class Designer {
|
||||
}
|
||||
}
|
||||
|
||||
// 事件 & 消息
|
||||
onActiveChange(): () => void;
|
||||
onDragstart(): void;
|
||||
onDragend(): void;
|
||||
//....
|
||||
get schema(): ProjectSchema {
|
||||
return this.project.schema;
|
||||
}
|
||||
|
||||
set schema(schema: ProjectSchema) {
|
||||
// todo:
|
||||
}
|
||||
|
||||
purge() {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,300 +0,0 @@
|
||||
import Project from '../project';
|
||||
import { RootSchema, NodeData, isDOMText, isJSExpression, NodeSchema } from '../schema';
|
||||
import Node, { isNodeParent, insertChildren, insertChild, NodeParent } from './node/node';
|
||||
import { Selection } from './selection';
|
||||
import RootNode from './node/root-node';
|
||||
import { SimulatorInterface } from '../simulator-interface';
|
||||
import { computed } from '@recore/obx';
|
||||
|
||||
export default class DocumentContext {
|
||||
/**
|
||||
* 根节点 类型有:Page/Component/Block
|
||||
*/
|
||||
readonly rootNode: RootNode;
|
||||
/**
|
||||
* 文档编号
|
||||
*/
|
||||
readonly id: string;
|
||||
/**
|
||||
* 选区控制
|
||||
*/
|
||||
readonly selection: Selection = new Selection(this);
|
||||
/**
|
||||
* 操作记录控制
|
||||
*/
|
||||
// TODO
|
||||
// readonly history: History = new History(this);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
set fileName(fileName: string) {
|
||||
this.rootNode.extras.get('fileName', true).value = fileName;
|
||||
}
|
||||
|
||||
constructor(readonly project: Project, schema: RootSchema) {
|
||||
this.rootNode = new RootNode(this, schema);
|
||||
this.id = this.rootNode.id;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成唯一id
|
||||
*/
|
||||
nextId() {
|
||||
return (++this.seqId).toString(36).toLocaleLowerCase();
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据 id 获取节点
|
||||
*/
|
||||
getNode(id: string): Node | null {
|
||||
return this.nodesMap.get(id) || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否存在节点
|
||||
*/
|
||||
hasNode(id: string): boolean {
|
||||
const node = this.getNode(id);
|
||||
return node ? !node.isPurged : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据 schema 创建一个节点
|
||||
*/
|
||||
createNode(data: NodeData): Node {
|
||||
let schema: any;
|
||||
if (isDOMText(data) || isJSExpression(data)) {
|
||||
schema = {
|
||||
componentName: '#frag',
|
||||
children: data,
|
||||
};
|
||||
} else {
|
||||
schema = data;
|
||||
}
|
||||
const node = new Node(this, schema);
|
||||
this.nodesMap.set(node.id, node);
|
||||
this.nodes.add(node);
|
||||
return node;
|
||||
}
|
||||
|
||||
/**
|
||||
* 插入一个节点
|
||||
*/
|
||||
insertNode(parent: NodeParent, thing: Node | NodeData, at?: number | null, copy?: boolean): Node {
|
||||
return insertChild(parent, thing, at, copy);
|
||||
}
|
||||
|
||||
/**
|
||||
* 插入多个节点
|
||||
*/
|
||||
insertNodes(parent: NodeParent, thing: Node[] | NodeData[], at?: number | null, copy?: boolean) {
|
||||
return insertChildren(parent, thing, at, copy);
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除一个节点
|
||||
*/
|
||||
removeNode(idOrNode: string | Node) {
|
||||
let id: string;
|
||||
let node: Node | null;
|
||||
if (typeof idOrNode === 'string') {
|
||||
id = idOrNode;
|
||||
node = this.getNode(id);
|
||||
} else {
|
||||
node = idOrNode;
|
||||
id = node.id;
|
||||
}
|
||||
if (!node) {
|
||||
return;
|
||||
}
|
||||
this.internalRemoveAndPurgeNode(node);
|
||||
}
|
||||
|
||||
/**
|
||||
* 内部方法,请勿调用
|
||||
*/
|
||||
internalRemoveAndPurgeNode(node: Node) {
|
||||
if (!this.nodes.has(node)) {
|
||||
return;
|
||||
}
|
||||
this.nodesMap.delete(node.id);
|
||||
this.nodes.delete(node);
|
||||
node.remove();
|
||||
}
|
||||
|
||||
/**
|
||||
* 包裹当前选区中的节点
|
||||
*/
|
||||
wrapWith(schema: NodeSchema): Node | null {
|
||||
const nodes = this.selection.getTopNodes();
|
||||
if (nodes.length < 1) {
|
||||
return null;
|
||||
}
|
||||
const wrapper = this.createNode(schema);
|
||||
if (isNodeParent(wrapper)) {
|
||||
const first = nodes[0];
|
||||
// TODO: check nesting rules x 2
|
||||
insertChild(first.parent!, wrapper, first.index);
|
||||
insertChildren(wrapper, nodes);
|
||||
this.selection.select(wrapper.id);
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
this.removeNode(wrapper);
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出 schema 数据
|
||||
*/
|
||||
get schema(): NodeSchema {
|
||||
return this.rootNode.schema;
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出节点数据
|
||||
*/
|
||||
getNodeSchema(id: string): NodeData | null {
|
||||
const node = this.getNode(id);
|
||||
if (node) {
|
||||
return node.schema;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否已修改
|
||||
*/
|
||||
isModified() {
|
||||
// return !this.history.isSavePoint();
|
||||
}
|
||||
|
||||
/**
|
||||
* 提供给模拟器的参数
|
||||
*/
|
||||
@computed get simulatorProps(): object {
|
||||
let simulatorProps = this.project.simulatorProps;
|
||||
if (typeof simulatorProps === 'function') {
|
||||
simulatorProps = simulatorProps(this);
|
||||
}
|
||||
return {
|
||||
...simulatorProps,
|
||||
documentContext: this,
|
||||
onMount: this.mountSimulator.bind(this),
|
||||
};
|
||||
}
|
||||
|
||||
private mountSimulator(simulator: SimulatorInterface) {
|
||||
this._simulator = simulator;
|
||||
// TODO: emit simulator mounted
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据节点取得视图实例,在循环等场景会有多个,依赖 simulator 的接口
|
||||
*/
|
||||
getViewInstance(node: Node): ViewInstance[] | null {
|
||||
if (this.simulator) {
|
||||
this.simulator.getViewInstance(node.id);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过 DOM 节点获取节点,依赖 simulator 的接口
|
||||
*/
|
||||
getNodeFromElement(target: Element | null): Node | null {
|
||||
if (!this.simulator || !target) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const id = this.simulator.getClosestNodeId(target);
|
||||
if (!id) {
|
||||
return null;
|
||||
}
|
||||
return this.getNode(id) as Node;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得到的结果是一个数组
|
||||
* 表示一个实例对应多个外层 DOM 节点,依赖 simulator 的接口
|
||||
*/
|
||||
getDOMNodes(viewInstance: ViewInstance): Array<Element | Text> | null {
|
||||
if (!this.simulator) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (isElement(viewInstance)) {
|
||||
return [viewInstance];
|
||||
}
|
||||
|
||||
return this.simulator.findDOMNodes(viewInstance);
|
||||
}
|
||||
|
||||
getComponent(componentName: string): any {
|
||||
return this.simulator!.getCurrentComponent(componentName);
|
||||
}
|
||||
|
||||
private _opened: boolean = true;
|
||||
private _suspensed: boolean = false;
|
||||
|
||||
/**
|
||||
* 是否不是激活的
|
||||
*/
|
||||
get suspensed(): boolean {
|
||||
return this._suspensed;
|
||||
}
|
||||
|
||||
/**
|
||||
* 与 suspensed 相反,是否是激活的,这个函数可能用的更多一点
|
||||
*/
|
||||
get actived(): boolean {
|
||||
return !this._suspensed;
|
||||
}
|
||||
|
||||
/**
|
||||
* 切换激活,只有打开的才能激活
|
||||
* 不激活,打开之后切换到另外一个时发生,比如 tab 视图,切换到另外一个标签页
|
||||
*/
|
||||
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() {
|
||||
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,27 @@
|
||||
import { Component, createContext } from 'react';
|
||||
import DocumentModel from './document-model';
|
||||
|
||||
export const DocumentContext = createContext<DocumentModel>(null as any);
|
||||
|
||||
export default class DocumentView extends Component<{ documentModel: DocumentModel }> {
|
||||
render() {
|
||||
const { documentModel } = this.props;
|
||||
return (
|
||||
<div className="lc-document">
|
||||
<DocumentContext.Provider value={documentModel}>
|
||||
{/* 这一层将来做缩放用途 */}
|
||||
<div className="lc-simulator-shell">
|
||||
|
||||
</div>
|
||||
<DocumentInfoView />
|
||||
</DocumentContext.Provider>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
class DocumentInfoView extends Component {
|
||||
render() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@ -1,123 +0,0 @@
|
||||
import ComponentNode, { NodeParent } from './node/node';
|
||||
import DocumentContext from './document-context';
|
||||
|
||||
export interface LocationData {
|
||||
target: NodeParent; // shadowNode | ConditionFlow | ElementNode | RootNode
|
||||
detail: LocationDetail;
|
||||
}
|
||||
|
||||
export enum LocationDetailType {
|
||||
Children = 'Children',
|
||||
Prop = 'Prop',
|
||||
}
|
||||
|
||||
export interface LocationChildrenDetail {
|
||||
type: LocationDetailType.Children;
|
||||
index: number;
|
||||
near?: {
|
||||
node: ComponentNode;
|
||||
pos: 'before' | 'after';
|
||||
rect?: Rect;
|
||||
align?: 'V' | 'H';
|
||||
};
|
||||
}
|
||||
|
||||
export interface LocationPropDetail {
|
||||
// cover 形态,高亮 domNode,如果 domNode 为空,取 container 的值
|
||||
type: LocationDetailType.Prop;
|
||||
name: string;
|
||||
domNode?: HTMLElement;
|
||||
}
|
||||
|
||||
export type LocationDetail = LocationChildrenDetail | LocationPropDetail | { type: string; [key: string]: any };
|
||||
|
||||
export interface Point {
|
||||
clientX: number;
|
||||
clientY: number;
|
||||
}
|
||||
|
||||
export type Rects = Array<ClientRect | DOMRect> & {
|
||||
elements: Array<Element | Text>;
|
||||
};
|
||||
|
||||
export type Rect = (ClientRect | DOMRect) & {
|
||||
elements: Array<Element | Text>;
|
||||
computed?: boolean;
|
||||
};
|
||||
|
||||
export function isLocationData(obj: any): obj is LocationData {
|
||||
return obj && obj.target && obj.detail;
|
||||
}
|
||||
|
||||
export function isLocationChildrenDetail(obj: any): obj is LocationChildrenDetail {
|
||||
return obj && obj.type === LocationDetailType.Children;
|
||||
}
|
||||
|
||||
export function isRowContainer(container: Element | Text, win?: Window) {
|
||||
if (isText(container)) {
|
||||
return true;
|
||||
}
|
||||
const style = (win || getWindow(container)).getComputedStyle(container);
|
||||
const display = style.getPropertyValue('display');
|
||||
if (/flex$/.test(display)) {
|
||||
const direction = style.getPropertyValue('flex-direction') || 'row';
|
||||
if (direction === 'row' || direction === 'row-reverse') {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export function isChildInline(child: Element | Text, win?: Window) {
|
||||
if (isText(child)) {
|
||||
return true;
|
||||
}
|
||||
const style = (win || getWindow(child)).getComputedStyle(child);
|
||||
return /^inline/.test(style.getPropertyValue('display'));
|
||||
}
|
||||
|
||||
export function getRectTarget(rect: Rect | null) {
|
||||
if (!rect || rect.computed) {
|
||||
return null;
|
||||
}
|
||||
const els = rect.elements;
|
||||
return els && els.length > 0 ? els[0]! : null;
|
||||
}
|
||||
|
||||
export function isVerticalContainer(rect: Rect | null) {
|
||||
const el = getRectTarget(rect);
|
||||
if (!el) {
|
||||
return false;
|
||||
}
|
||||
return isRowContainer(el);
|
||||
}
|
||||
|
||||
export function isVertical(rect: Rect | null) {
|
||||
const el = getRectTarget(rect);
|
||||
if (!el) {
|
||||
return false;
|
||||
}
|
||||
return isChildInline(el) || (el.parentElement ? isRowContainer(el.parentElement) : false);
|
||||
}
|
||||
|
||||
function isText(elem: any): elem is Text {
|
||||
return elem.nodeType === Node.TEXT_NODE;
|
||||
}
|
||||
|
||||
function isDocument(elem: any): elem is Document {
|
||||
return elem.nodeType === Node.DOCUMENT_NODE;
|
||||
}
|
||||
|
||||
export function getWindow(elem: Element | Document): Window {
|
||||
return (isDocument(elem) ? elem : elem.ownerDocument!).defaultView!;
|
||||
}
|
||||
|
||||
export default class Location {
|
||||
readonly target: NodeParent;
|
||||
readonly detail: LocationDetail;
|
||||
|
||||
constructor(readonly document: DocumentContext, { target, detail }: LocationData) {
|
||||
this.target = target;
|
||||
this.detail = detail;
|
||||
}
|
||||
}
|
||||
@ -1,11 +1,11 @@
|
||||
import Node, { comparePosition } from './node/node';
|
||||
import { obx } from '@recore/obx';
|
||||
import DocumentContext from './document-context';
|
||||
import DocumentModel from './document-model';
|
||||
|
||||
export class Selection {
|
||||
@obx.val private selected: string[] = [];
|
||||
|
||||
constructor(private doc: DocumentContext) {}
|
||||
constructor(private doc: DocumentModel) {}
|
||||
|
||||
/**
|
||||
* 选中
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
import { EventEmitter } from 'events';
|
||||
import { obx } from '@recore/obx';
|
||||
import Location from './document/location';
|
||||
import Project from './project';
|
||||
import DocumentContext from './document/document-context';
|
||||
import Location from './location';
|
||||
import DocumentModel from './document/document-model';
|
||||
import { NodeData } from './schema';
|
||||
import { SimulatorInterface } from './simulator-interface';
|
||||
import Node from './document/node/node';
|
||||
import Designer from './designer';
|
||||
|
||||
export interface LocateEvent {
|
||||
readonly type: 'LocateEvent';
|
||||
@ -34,7 +34,7 @@ export interface LocateEvent {
|
||||
/**
|
||||
* 激活或目标文档
|
||||
*/
|
||||
document?: DocumentContext;
|
||||
document?: DocumentModel;
|
||||
/**
|
||||
* 事件订正标识,初始构造时,从发起端构造,缺少 canvasX,canvasY, 需要经过订正才有
|
||||
*/
|
||||
@ -44,7 +44,7 @@ export interface LocateEvent {
|
||||
/**
|
||||
* 拖拽敏感板
|
||||
*/
|
||||
export interface SensorInterface {
|
||||
export interface ISensor {
|
||||
/**
|
||||
* 是否可响应,比如面板被隐藏,可设置该值 false
|
||||
*/
|
||||
@ -76,7 +76,7 @@ export enum DragObjectType {
|
||||
|
||||
export interface DragNodeObject {
|
||||
type: DragObjectType.Node;
|
||||
node: Node | Node[];
|
||||
nodes: Node[];
|
||||
}
|
||||
export interface DragNodeDataObject {
|
||||
type: DragObjectType.NodeData;
|
||||
@ -126,32 +126,25 @@ export function setShaken(e: any) {
|
||||
e.shaken = true;
|
||||
}
|
||||
|
||||
function getTopDocument(e: MouseEvent, local: Document) {
|
||||
return e.view!.document === local ? null : document;
|
||||
function isFromTopDocument(e: MouseEvent) {
|
||||
return e.view!.document === document;
|
||||
}
|
||||
|
||||
export default class Dragon {
|
||||
private sensors: ISenseAble[] = [];
|
||||
private sensors: ISensor[] = [];
|
||||
|
||||
/**
|
||||
* current actived sensor
|
||||
*/
|
||||
private _activeSensor: ISenseAble | undefined;
|
||||
get activeSensor(): ISenseAble | undefined {
|
||||
private _activeSensor: ISensor | undefined;
|
||||
get activeSensor(): ISensor | undefined {
|
||||
return this._activeSensor;
|
||||
}
|
||||
|
||||
@obx.ref dragging = false;
|
||||
private emitter = new EventEmitter();
|
||||
private get master(): MasterBoard | undefined {
|
||||
const doc = getCurrentDocument();
|
||||
if (!doc) {
|
||||
return undefined;
|
||||
}
|
||||
return doc.masterBoard;
|
||||
}
|
||||
|
||||
constructor(readonly project: Project) {}
|
||||
constructor(readonly designer: Designer) {}
|
||||
|
||||
from(shell: Element, boost: (e: MouseEvent) => DragObject | null) {
|
||||
const mousedown = (e: MouseEvent) => {
|
||||
@ -174,22 +167,28 @@ export default class Dragon {
|
||||
};
|
||||
}
|
||||
|
||||
getMasterSensors(): SimulatorInterface[] {
|
||||
return this.designer.project.documents.map(doc => (doc.actived && doc.simulator) || null).filter(Boolean);
|
||||
}
|
||||
|
||||
get master(): DocumentModel {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* dragTarget should be a INode | INode[] | NodeData | NodeData[]
|
||||
*/
|
||||
boost(dragObject: DragObject, boostEvent: MouseEvent) {
|
||||
if (!this.master) {
|
||||
return;
|
||||
}
|
||||
const master = this.master;
|
||||
const doc = master.contentDocument;
|
||||
const viewport = master.document.viewport;
|
||||
const topDoc = getTopDocument(boostEvent, doc);
|
||||
const newBie = dragObject.type !== DragTargetType.Nodes;
|
||||
const doc = document;
|
||||
const fromTop = isFromTopDocument(boostEvent);
|
||||
let lastLocation: any = null;
|
||||
let lastSensor: ISenseAble | undefined;
|
||||
let lastSensor: ISensor | undefined;
|
||||
this.dragging = false;
|
||||
master.setNativeSelection(false);
|
||||
const masterSensors = this.getMasterSensors();
|
||||
masterSensors.forEach((sensor) => {
|
||||
sensor.setNativeSelection(false);
|
||||
});
|
||||
//
|
||||
|
||||
const checkesc = (e: KeyboardEvent) => {
|
||||
if (e.keyCode === 27) {
|
||||
@ -269,19 +268,25 @@ export default class Dragon {
|
||||
|
||||
master.releaseCursor();
|
||||
|
||||
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);
|
||||
if (topDoc) {
|
||||
topDoc.removeEventListener('mousemove', move, true);
|
||||
topDoc.removeEventListener('mouseup', over, true);
|
||||
topDoc.removeEventListener('mousedown', over, true);
|
||||
topDoc.removeEventListener('keydown', checkesc, false);
|
||||
topDoc.removeEventListener('keydown', checkcopy as any, false);
|
||||
topDoc.removeEventListener('keyup', checkcopy as any, false);
|
||||
if (fromTop) {
|
||||
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.ownerDocument;
|
||||
if (odoc && odoc !== doc) {
|
||||
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);
|
||||
}
|
||||
});
|
||||
}
|
||||
if (exception) {
|
||||
throw exception;
|
||||
@ -318,13 +323,13 @@ export default class Dragon {
|
||||
if (!isDragNodeObject(dragObject)) {
|
||||
return null;
|
||||
}
|
||||
return (Array.isArray(dragObject.node) ? dragObject.node[0] : dragObject.node)?.document.simulator || null;
|
||||
return (Array.isArray(dragObject.nodes) ? dragObject.nodes[0] : dragObject.nodes)?.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 sensors: ISensor[] = simSensors.concat(this.sensors);
|
||||
const chooseSensor = (e: LocateEvent) => {
|
||||
let sensor = sensors.find(s => s.sensorAvailable && s.isEnter(e));
|
||||
if (!sensor) {
|
||||
@ -390,7 +395,7 @@ export default class Dragon {
|
||||
};
|
||||
}
|
||||
|
||||
onDragend(func: (x: { dragTarget: DragObject; copy: boolean }, location: Location) => any) {
|
||||
onDragend(func: (x: { dragObject: DragObject; copy: boolean }, location: Location) => any) {
|
||||
this.emitter.on('dragend', func);
|
||||
return () => {
|
||||
this.emitter.removeListener('dragend', func);
|
||||
|
||||
@ -1,17 +1,25 @@
|
||||
import { obx } from '@recore/obx';
|
||||
import { DocumentSchema, ProjectSchema } from './schema';
|
||||
import { ProjectSchema } from './schema';
|
||||
import { EventEmitter } from 'events';
|
||||
import Designer from './designer';
|
||||
import DocumentModel from './document/document-model';
|
||||
|
||||
export default class Project {
|
||||
@obx documents: DocumentContext[];
|
||||
displayMode: 'exclusive' | 'tabbed' | 'split'; // P2
|
||||
private emitter = new EventEmitter();
|
||||
@obx.val readonly documents: DocumentModel[] = [];
|
||||
private data: ProjectSchema = {};
|
||||
|
||||
@obx.ref displayMode: 'exclusive' | 'tabbed' | 'split' = 'exclusive';
|
||||
|
||||
// 考虑项目级别 History
|
||||
|
||||
constructor(schema: ProjectSchema) {
|
||||
this.data = { ...schema };
|
||||
constructor(readonly designer: Designer, schema?: ProjectSchema) {
|
||||
this.data = {
|
||||
version: '1.0.0',
|
||||
componentsMap: [],
|
||||
componentsTree: [],
|
||||
...schema
|
||||
};
|
||||
}
|
||||
|
||||
getDocument(fileName: string): DocumentContext {}
|
||||
@ -23,16 +31,19 @@ export default class Project {
|
||||
/**
|
||||
* 获取项目整体 schema
|
||||
*/
|
||||
getSchema(): ProjectSchema {
|
||||
get schema(): ProjectSchema {
|
||||
return {
|
||||
...this.data,
|
||||
componentsTree: this.documents.map(doc => doc.getSchema()),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 整体设置项目 schema
|
||||
*/
|
||||
setSchema(schema: ProjectSchema): void {}
|
||||
set schema(schema: ProjectSchema) {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 分字段设置储存数据,不记录操作记录
|
||||
|
||||
@ -1,9 +1,10 @@
|
||||
import { NpmInfo } from './schema';
|
||||
import { ComponentClass as ReactComponentClass, Component } from 'react';
|
||||
import { LocateEvent, SensorInterface } from './dragon';
|
||||
import { Point } from './document/location';
|
||||
import { LocateEvent, ISensor } from './dragon';
|
||||
import { Point } from './location';
|
||||
import Node from './document/node/node';
|
||||
|
||||
export interface SimulatorInterface<P = object> extends SensorInterface {
|
||||
export interface SimulatorInterface<P = object> extends ISensor {
|
||||
/**
|
||||
* 获得边界维度等信息
|
||||
*/
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user