mirror of
https://github.com/alibaba/lowcode-engine.git
synced 2025-12-16 15:01:15 +00:00
feat: history log
This commit is contained in:
parent
9d2967f612
commit
fbb3577bd4
@ -2,10 +2,8 @@ import { Component } from 'react';
|
|||||||
import { obx } from '@recore/obx';
|
import { obx } from '@recore/obx';
|
||||||
import { observer } from '@recore/core-obx';
|
import { observer } from '@recore/core-obx';
|
||||||
import Designer from '../../designer/designer';
|
import Designer from '../../designer/designer';
|
||||||
import './ghost.less';
|
|
||||||
import { NodeSchema } from '../../designer/schema';
|
|
||||||
import Node from '../../designer/document/node/node';
|
|
||||||
import { isDragNodeObject, DragObject, isDragNodeDataObject } from '../../designer/helper/dragon';
|
import { isDragNodeObject, DragObject, isDragNodeDataObject } from '../../designer/helper/dragon';
|
||||||
|
import './ghost.less';
|
||||||
|
|
||||||
type offBinding = () => any;
|
type offBinding = () => any;
|
||||||
|
|
||||||
|
|||||||
@ -72,12 +72,10 @@ export class OutlineHovering extends Component {
|
|||||||
render() {
|
render() {
|
||||||
const host = this.context as SimulatorHost;
|
const host = this.context as SimulatorHost;
|
||||||
const current = this.current;
|
const current = this.current;
|
||||||
console.info('current', current)
|
|
||||||
if (!current || host.viewport.scrolling) {
|
if (!current || host.viewport.scrolling) {
|
||||||
return <Fragment />;
|
return <Fragment />;
|
||||||
}
|
}
|
||||||
const instances = host.getComponentInstances(current);
|
const instances = host.getComponentInstances(current);
|
||||||
console.info('current instances', instances)
|
|
||||||
if (!instances || instances.length < 1) {
|
if (!instances || instances.length < 1) {
|
||||||
return <Fragment />;
|
return <Fragment />;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -290,8 +290,6 @@ export class SimulatorHost implements ISimulator<SimulatorProps> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const nodeInst = this.getNodeInstanceFromElement(e.target as Element);
|
const nodeInst = this.getNodeInstanceFromElement(e.target as Element);
|
||||||
// TODO: enhance only hover one instance
|
|
||||||
console.info(nodeInst);
|
|
||||||
hovering.hover(nodeInst?.node || null);
|
hovering.hover(nodeInst?.node || null);
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
};
|
};
|
||||||
@ -633,7 +631,6 @@ export class SimulatorHost implements ISimulator<SimulatorProps> {
|
|||||||
this.sensing = true;
|
this.sensing = true;
|
||||||
this.scroller.scrolling(e);
|
this.scroller.scrolling(e);
|
||||||
const dropTarget = this.getDropTarget(e);
|
const dropTarget = this.getDropTarget(e);
|
||||||
console.info('aa', dropTarget);
|
|
||||||
if (!dropTarget) {
|
if (!dropTarget) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -280,7 +280,7 @@ export class ComponentConfig {
|
|||||||
}
|
}
|
||||||
private _isContainer?: boolean;
|
private _isContainer?: boolean;
|
||||||
get isContainer(): boolean {
|
get isContainer(): boolean {
|
||||||
return this._isContainer! || this.isRootComponent();
|
return true; // this._isContainer! || this.isRootComponent();
|
||||||
}
|
}
|
||||||
private _isModal?: boolean;
|
private _isModal?: boolean;
|
||||||
get isModal(): boolean {
|
get isModal(): boolean {
|
||||||
|
|||||||
@ -56,7 +56,6 @@ export default class Designer {
|
|||||||
});
|
});
|
||||||
|
|
||||||
this.dragon.onDrag(e => {
|
this.dragon.onDrag(e => {
|
||||||
console.info('dropLocation', this._dropLocation);
|
|
||||||
if (this.props?.onDrag) {
|
if (this.props?.onDrag) {
|
||||||
this.props.onDrag(e);
|
this.props.onDrag(e);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,9 +4,11 @@ import Node, { isNodeParent, insertChildren, insertChild, NodeParent } from './n
|
|||||||
import { Selection } from './selection';
|
import { Selection } from './selection';
|
||||||
import RootNode from './node/root-node';
|
import RootNode from './node/root-node';
|
||||||
import { ISimulator, Component } from '../simulator';
|
import { ISimulator, Component } from '../simulator';
|
||||||
import { computed, obx } from '@recore/obx';
|
import { computed, obx, autorun } from '@recore/obx';
|
||||||
import Location from '../helper/location';
|
import Location from '../helper/location';
|
||||||
import { ComponentConfig } from '../component-config';
|
import { ComponentConfig } from '../component-config';
|
||||||
|
import History from '../helper/history';
|
||||||
|
import Prop from './node/props/prop';
|
||||||
|
|
||||||
export default class DocumentModel {
|
export default class DocumentModel {
|
||||||
/**
|
/**
|
||||||
@ -24,11 +26,10 @@ export default class DocumentModel {
|
|||||||
/**
|
/**
|
||||||
* 操作记录控制
|
* 操作记录控制
|
||||||
*/
|
*/
|
||||||
// TODO
|
readonly history: History;
|
||||||
// readonly history: History = new History(this);
|
|
||||||
|
|
||||||
private nodesMap = new Map<string, Node>();
|
private nodesMap = new Map<string, Node>();
|
||||||
private nodes = new Set<Node>();
|
@obx.val private nodes = new Set<Node>();
|
||||||
private seqId = 0;
|
private seqId = 0;
|
||||||
private _simulator?: ISimulator;
|
private _simulator?: ISimulator;
|
||||||
|
|
||||||
@ -48,8 +49,21 @@ export default class DocumentModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
constructor(readonly project: Project, schema: RootSchema) {
|
constructor(readonly project: Project, schema: RootSchema) {
|
||||||
this.rootNode = this.createNode(schema) as RootNode;
|
// todo: purge this autorun
|
||||||
|
/*
|
||||||
|
autorun(() => {
|
||||||
|
this.nodes.forEach(item => {
|
||||||
|
if (item.parent == null && item !== this.rootNode) {
|
||||||
|
// item.remove();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, true);*/
|
||||||
|
this.rootNode = this.createRootNode(schema);
|
||||||
this.id = this.rootNode.id;
|
this.id = this.rootNode.id;
|
||||||
|
this.history = new History(
|
||||||
|
() => this.schema,
|
||||||
|
(schema) => this.import(schema as RootSchema, true),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
readonly designer = this.project.designer;
|
readonly designer = this.project.designer;
|
||||||
@ -79,7 +93,7 @@ export default class DocumentModel {
|
|||||||
/**
|
/**
|
||||||
* 根据 schema 创建一个节点
|
* 根据 schema 创建一个节点
|
||||||
*/
|
*/
|
||||||
createNode(data: NodeData): Node {
|
createNode(data: NodeData, slotFor?: Prop): Node {
|
||||||
let schema: any;
|
let schema: any;
|
||||||
if (isDOMText(data) || isJSExpression(data)) {
|
if (isDOMText(data) || isJSExpression(data)) {
|
||||||
schema = {
|
schema = {
|
||||||
@ -89,7 +103,34 @@ export default class DocumentModel {
|
|||||||
} else {
|
} else {
|
||||||
schema = data;
|
schema = data;
|
||||||
}
|
}
|
||||||
const node = new Node(this, schema);
|
|
||||||
|
let node: Node | null = null;
|
||||||
|
if (schema.id) {
|
||||||
|
node = this.getNode(schema.id);
|
||||||
|
if (node && node.componentName === schema.componentName) {
|
||||||
|
node.internalSetParent(null);
|
||||||
|
node.internalSetSlotFor(slotFor);
|
||||||
|
node.import(schema, true);
|
||||||
|
} else if (node) {
|
||||||
|
node = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!node) {
|
||||||
|
node = new Node(this, schema, slotFor);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.nodesMap.has(node.id)) {
|
||||||
|
node.purge();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.nodesMap.set(node.id, node);
|
||||||
|
this.nodes.add(node);
|
||||||
|
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
private createRootNode(schema: RootSchema) {
|
||||||
|
const node = new RootNode(this, schema);
|
||||||
this.nodesMap.set(node.id, node);
|
this.nodesMap.set(node.id, node);
|
||||||
this.nodes.add(node);
|
this.nodes.add(node);
|
||||||
return node;
|
return node;
|
||||||
@ -184,6 +225,11 @@ export default class DocumentModel {
|
|||||||
return this.rootNode.schema as any;
|
return this.rootNode.schema as any;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
import(schema: RootSchema, checkId: boolean = false) {
|
||||||
|
this.rootNode.import(schema, checkId);
|
||||||
|
// todo: purge something
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 导出节点数据
|
* 导出节点数据
|
||||||
*/
|
*/
|
||||||
@ -231,8 +277,6 @@ export default class DocumentModel {
|
|||||||
return this.designer.getComponentConfig(componentName);
|
return this.designer.getComponentConfig(componentName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@obx.ref private _opened: boolean = true;
|
@obx.ref private _opened: boolean = true;
|
||||||
@obx.ref private _suspensed: boolean = false;
|
@obx.ref private _suspensed: boolean = false;
|
||||||
|
|
||||||
@ -303,7 +347,9 @@ export default class DocumentModel {
|
|||||||
/**
|
/**
|
||||||
* 从项目中移除
|
* 从项目中移除
|
||||||
*/
|
*/
|
||||||
remove() {}
|
remove() {
|
||||||
|
// todo:
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isDocumentModel(obj: any): obj is DocumentModel {
|
export function isDocumentModel(obj: any): obj is DocumentModel {
|
||||||
|
|||||||
@ -1,11 +1,11 @@
|
|||||||
import Node, { NodeParent } from './node';
|
import Node, { NodeParent } from './node';
|
||||||
import { NodeData } from '../../schema';
|
import { NodeData, isNodeSchema } from '../../schema';
|
||||||
import { obx, computed } from '@recore/obx';
|
import { obx, computed } from '@recore/obx';
|
||||||
|
|
||||||
export default class NodeChildren {
|
export default class NodeChildren {
|
||||||
@obx.val private children: Node[];
|
@obx.val private children: Node[];
|
||||||
constructor(readonly owner: NodeParent, childrenData: NodeData | NodeData[]) {
|
constructor(readonly owner: NodeParent, data: NodeData | NodeData[]) {
|
||||||
this.children = (Array.isArray(childrenData) ? childrenData : [childrenData]).map(child => {
|
this.children = (Array.isArray(data) ? data : [data]).map(child => {
|
||||||
const node = this.owner.document.createNode(child);
|
const node = this.owner.document.createNode(child);
|
||||||
node.internalSetParent(this.owner);
|
node.internalSetParent(this.owner);
|
||||||
return node;
|
return node;
|
||||||
@ -16,8 +16,33 @@ export default class NodeChildren {
|
|||||||
* 导出 schema
|
* 导出 schema
|
||||||
* @param serialize 序列化,加 id 标识符,用于储存为操作记录
|
* @param serialize 序列化,加 id 标识符,用于储存为操作记录
|
||||||
*/
|
*/
|
||||||
exportSchema(serialize = false): NodeData[] {
|
export(serialize = false): NodeData[] {
|
||||||
return this.children.map(node => node.exportSchema(serialize));
|
return this.children.map(node => node.export(serialize));
|
||||||
|
}
|
||||||
|
|
||||||
|
import(data?: NodeData | NodeData[], checkId: boolean = false) {
|
||||||
|
data = data ? (Array.isArray(data) ? data : [data]) : [];
|
||||||
|
|
||||||
|
const originChildren = this.children.slice();
|
||||||
|
this.children.forEach(child => child.internalSetParent(null));
|
||||||
|
|
||||||
|
const children = new Array<Node>(data.length);
|
||||||
|
for (let i = 0, l = data.length; i < l; i++) {
|
||||||
|
const child = originChildren[i];
|
||||||
|
const item = data[i];
|
||||||
|
|
||||||
|
let node: Node | undefined;
|
||||||
|
if (isNodeSchema(item) && !checkId && child && child.componentName === item.componentName) {
|
||||||
|
node = child;
|
||||||
|
node.import(item);
|
||||||
|
} else {
|
||||||
|
node = this.owner.document.createNode(item);
|
||||||
|
}
|
||||||
|
node.internalSetParent(this.owner);
|
||||||
|
children[i] = node;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.children = children;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -34,27 +59,6 @@ export default class NodeChildren {
|
|||||||
return this.size < 1;
|
return this.size < 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
// 用于数据重新灌入
|
|
||||||
merge() {
|
|
||||||
for (let i = 0, l = data.length; i < l; i++) {
|
|
||||||
const item = this.children[i];
|
|
||||||
if (item && isMergeable(item) && item.tagName === data[i].tagName) {
|
|
||||||
item.merge(data[i]);
|
|
||||||
} else {
|
|
||||||
if (item) {
|
|
||||||
item.purge();
|
|
||||||
}
|
|
||||||
this.children[i] = this.document.createNode(data[i]);
|
|
||||||
this.children[i].internalSetParent(this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (this.children.length > data.length) {
|
|
||||||
this.children.splice(data.length).forEach(child => child.purge());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 删除一个节点
|
* 删除一个节点
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -57,6 +57,10 @@ export default class NodeContent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
constructor(value: any) {
|
constructor(value: any) {
|
||||||
|
this.import(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
import(value: any) {
|
||||||
const type = typeof value;
|
const type = typeof value;
|
||||||
if (value == null) {
|
if (value == null) {
|
||||||
this._value = '';
|
this._value = '';
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { obx, computed } from '@recore/obx';
|
import { obx, computed, untracked } from '@recore/obx';
|
||||||
import { NodeSchema, NodeData, PropsMap, PropsList } from '../../schema';
|
import { NodeSchema, NodeData, PropsMap, PropsList } from '../../schema';
|
||||||
import Props from './props/props';
|
import Props from './props/props';
|
||||||
import DocumentModel from '../document-model';
|
import DocumentModel from '../document-model';
|
||||||
@ -50,19 +50,19 @@ export default class Node {
|
|||||||
* * Component 组件/元件
|
* * Component 组件/元件
|
||||||
*/
|
*/
|
||||||
readonly componentName: string;
|
readonly componentName: string;
|
||||||
protected _props?: Props<Node>;
|
protected _props?: Props;
|
||||||
protected _directives?: Props<Node>;
|
protected _directives?: Props;
|
||||||
protected _extras?: Props<Node>;
|
protected _extras?: Props;
|
||||||
protected _children: NodeChildren | NodeContent;
|
protected _children: NodeChildren | NodeContent;
|
||||||
@obx.ref private _parent: NodeParent | null = null;
|
@obx.ref private _parent: NodeParent | null = null;
|
||||||
@obx.ref private _zLevel = 0;
|
@obx.ref private _zLevel = 0;
|
||||||
get props(): Props<Node> | undefined {
|
get props(): Props | undefined {
|
||||||
return this._props;
|
return this._props;
|
||||||
}
|
}
|
||||||
get directives(): Props<Node> | undefined {
|
get directives(): Props | undefined {
|
||||||
return this._directives;
|
return this._directives;
|
||||||
}
|
}
|
||||||
get extras(): Props<Node> | undefined {
|
get extras(): Props | undefined {
|
||||||
return this._extras;
|
return this._extras;
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
@ -80,8 +80,11 @@ export default class Node {
|
|||||||
/**
|
/**
|
||||||
* 当前节点深度
|
* 当前节点深度
|
||||||
*/
|
*/
|
||||||
get zLevel(): number {
|
@computed get zLevel(): number {
|
||||||
return this._zLevel;
|
if (this._parent) {
|
||||||
|
return this._parent.zLevel + 1;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@computed get title(): string {
|
@computed get title(): string {
|
||||||
@ -98,11 +101,16 @@ export default class Node {
|
|||||||
return this.componentName;
|
return this.componentName;
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(readonly document: DocumentModel, nodeSchema: NodeSchema) {
|
get isSlotRoot(): boolean {
|
||||||
|
return this._slotFor != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(readonly document: DocumentModel, nodeSchema: NodeSchema, slotFor?: Prop) {
|
||||||
const { componentName, id, children, props, ...extras } = nodeSchema;
|
const { componentName, id, children, props, ...extras } = nodeSchema;
|
||||||
this.id = id || `node$${document.nextId()}`;
|
this.id = id || `node$${document.nextId()}`;
|
||||||
this.componentName = componentName;
|
this.componentName = componentName;
|
||||||
if (this.isNodeParent) {
|
this._slotFor = slotFor;
|
||||||
|
if (isNodeParent(this)) {
|
||||||
this._props = new Props(this, props);
|
this._props = new Props(this, props);
|
||||||
this._directives = new Props(this, {});
|
this._directives = new Props(this, {});
|
||||||
Object.keys(extras).forEach(key => {
|
Object.keys(extras).forEach(key => {
|
||||||
@ -127,30 +135,33 @@ export default class Node {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 内部方法,请勿使用
|
* 内部方法,请勿使用
|
||||||
*
|
|
||||||
* @ignore
|
|
||||||
*/
|
*/
|
||||||
internalSetParent(parent: NodeParent | null) {
|
internalSetParent(parent: NodeParent | null) {
|
||||||
if (this._parent === parent) {
|
if (this._parent === parent) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (this._parent) {
|
|
||||||
|
if (this._parent && !this.isSlotRoot) {
|
||||||
this._parent.children.delete(this);
|
this._parent.children.delete(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
this._parent = parent;
|
this._parent = parent;
|
||||||
if (parent) {
|
}
|
||||||
this._zLevel = parent.zLevel + 1;
|
|
||||||
} else {
|
private _slotFor?: Prop | null = null;
|
||||||
this._zLevel = -1;
|
internalSetSlotFor(slotFor: Prop | null | undefined) {
|
||||||
}
|
this._slotFor = slotFor;
|
||||||
|
}
|
||||||
|
|
||||||
|
get slotFor() {
|
||||||
|
return this._slotFor;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 移除当前节点
|
* 移除当前节点
|
||||||
*/
|
*/
|
||||||
remove() {
|
remove() {
|
||||||
if (this.parent) {
|
if (this.parent && !this.isSlotRoot) {
|
||||||
this.parent.children.delete(this, true);
|
this.parent.children.delete(this, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -231,25 +242,10 @@ export default class Node {
|
|||||||
}
|
}
|
||||||
|
|
||||||
replaceWith(schema: NodeSchema, migrate: boolean = true) {
|
replaceWith(schema: NodeSchema, migrate: boolean = true) {
|
||||||
|
// reuse the same id? or replaceSelection
|
||||||
//
|
//
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
// TODO
|
|
||||||
// 外部修改,merge 进来,产生一次可恢复的历史数据
|
|
||||||
merge(data: ElementData) {
|
|
||||||
this.elementData = data;
|
|
||||||
const { leadingComments } = data;
|
|
||||||
this.leadingComments = leadingComments ? leadingComments.slice() : [];
|
|
||||||
this.parse();
|
|
||||||
this.mergeChildren(data.children || []);
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: 再利用历史数据,不产生历史数据
|
|
||||||
reuse(timelineData: NodeSchema) {}
|
|
||||||
*/
|
|
||||||
|
|
||||||
getProp(path: string, useStash: boolean = true): Prop | null {
|
getProp(path: string, useStash: boolean = true): Prop | null {
|
||||||
return this.props?.query(path, useStash as any) || null;
|
return this.props?.query(path, useStash as any) || null;
|
||||||
}
|
}
|
||||||
@ -300,28 +296,51 @@ export default class Node {
|
|||||||
* 获取符合搭建协议-节点 schema 结构
|
* 获取符合搭建协议-节点 schema 结构
|
||||||
*/
|
*/
|
||||||
get schema(): NodeSchema {
|
get schema(): NodeSchema {
|
||||||
// TODO: ..
|
return this.export(true);
|
||||||
return this.exportSchema(true);
|
}
|
||||||
|
|
||||||
|
set schema(data: NodeSchema) {
|
||||||
|
this.import(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
import(data: NodeSchema, checkId: boolean = false) {
|
||||||
|
const { componentName, id, children, props, ...extras } = data;
|
||||||
|
|
||||||
|
if (isNodeParent(this)) {
|
||||||
|
const directives: any = {};
|
||||||
|
Object.keys(extras).forEach(key => {
|
||||||
|
if (DIRECTIVES.indexOf(key) > -1) {
|
||||||
|
directives[key] = (extras as any)[key];
|
||||||
|
delete (extras as any)[key];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this._props!.import(data.props);
|
||||||
|
this._directives!.import(directives);
|
||||||
|
this._extras!.import(extras as any);
|
||||||
|
this._children.import(children, checkId);
|
||||||
|
} else {
|
||||||
|
this._children.import(children);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 导出 schema
|
* 导出 schema
|
||||||
* @param serialize 序列化,加 id 标识符,用于储存为操作记录
|
* @param serialize 序列化,加 id 标识符,用于储存为操作记录
|
||||||
*/
|
*/
|
||||||
exportSchema(serialize = false): NodeSchema {
|
export(serialize = false): NodeSchema {
|
||||||
// TODO...
|
// TODO...
|
||||||
const schema: any = {
|
const schema: any = {
|
||||||
componentName: this.componentName,
|
componentName: this.componentName,
|
||||||
...this.extras?.value,
|
...this.extras?.export(serialize),
|
||||||
props: this.props?.value || {},
|
props: this.props?.export(serialize) || {},
|
||||||
...this.directives?.value,
|
...this.directives?.export(serialize),
|
||||||
};
|
};
|
||||||
if (serialize) {
|
if (serialize) {
|
||||||
schema.id = this.id;
|
schema.id = this.id;
|
||||||
}
|
}
|
||||||
if (isNodeParent(this)) {
|
if (isNodeParent(this)) {
|
||||||
if (this.children.size > 0) {
|
if (this.children.size > 0) {
|
||||||
schema.children = this.children.exportSchema(serialize);
|
schema.children = this.children.export(serialize);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
schema.children = (this.children as NodeContent).value;
|
schema.children = (this.children as NodeContent).value;
|
||||||
@ -387,9 +406,9 @@ export default class Node {
|
|||||||
|
|
||||||
export interface NodeParent extends Node {
|
export interface NodeParent extends Node {
|
||||||
readonly children: NodeChildren;
|
readonly children: NodeChildren;
|
||||||
readonly props: Props<Node>;
|
readonly props: Props;
|
||||||
readonly directives: Props<Node>;
|
readonly directives: Props;
|
||||||
readonly extras: Props<Node>;
|
readonly extras: Props;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isNode(node: any): node is Node {
|
export function isNode(node: any): node is Node {
|
||||||
@ -466,7 +485,7 @@ export function comparePosition(node1: Node, node2: Node): number {
|
|||||||
export function insertChild(container: NodeParent, thing: Node | NodeData, at?: number | null, copy?: boolean): Node {
|
export function insertChild(container: NodeParent, thing: Node | NodeData, at?: number | null, copy?: boolean): Node {
|
||||||
let node: Node;
|
let node: Node;
|
||||||
if (copy && isNode(thing)) {
|
if (copy && isNode(thing)) {
|
||||||
thing = thing.exportSchema(false);
|
thing = thing.export(false);
|
||||||
}
|
}
|
||||||
if (isNode(thing)) {
|
if (isNode(thing)) {
|
||||||
node = thing;
|
node = thing;
|
||||||
|
|||||||
@ -1,8 +1,9 @@
|
|||||||
import { obx, autorun, untracked, computed } from '@recore/obx';
|
import { obx, autorun, untracked, computed } from '@recore/obx';
|
||||||
import Prop, { IPropParent } from './prop';
|
import Prop, { IPropParent, UNSET } from './prop';
|
||||||
|
import Props from './props';
|
||||||
|
|
||||||
export type PendingItem = Prop[];
|
export type PendingItem = Prop[];
|
||||||
export default class StashSpace implements IPropParent {
|
export default class PropStash implements IPropParent {
|
||||||
@obx.val private space: Set<Prop> = new Set();
|
@obx.val private space: Set<Prop> = new Set();
|
||||||
@computed private get maps(): Map<string, Prop> {
|
@computed private get maps(): Map<string, Prop> {
|
||||||
const maps = new Map();
|
const maps = new Map();
|
||||||
@ -15,7 +16,7 @@ export default class StashSpace implements IPropParent {
|
|||||||
}
|
}
|
||||||
private willPurge: () => void;
|
private willPurge: () => void;
|
||||||
|
|
||||||
constructor(write: (item: Prop) => void, before: () => boolean) {
|
constructor(readonly props: Props, write: (item: Prop) => void) {
|
||||||
this.willPurge = autorun(() => {
|
this.willPurge = autorun(() => {
|
||||||
if (this.space.size < 1) {
|
if (this.space.size < 1) {
|
||||||
return;
|
return;
|
||||||
@ -28,11 +29,10 @@ export default class StashSpace implements IPropParent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (pending.length > 0) {
|
if (pending.length > 0) {
|
||||||
|
debugger;
|
||||||
untracked(() => {
|
untracked(() => {
|
||||||
if (before()) {
|
for (const item of pending) {
|
||||||
for (const item of pending) {
|
write(item);
|
||||||
write(item);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -42,7 +42,7 @@ export default class StashSpace implements IPropParent {
|
|||||||
get(key: string): Prop {
|
get(key: string): Prop {
|
||||||
let prop = this.maps.get(key);
|
let prop = this.maps.get(key);
|
||||||
if (!prop) {
|
if (!prop) {
|
||||||
prop = new Prop(this, null, key);
|
prop = new Prop(this, UNSET, key);
|
||||||
this.space.add(prop);
|
this.space.add(prop);
|
||||||
}
|
}
|
||||||
return prop;
|
return prop;
|
||||||
@ -1,16 +1,19 @@
|
|||||||
import { untracked, computed, obx } from '@recore/obx';
|
import { untracked, computed, obx } from '@recore/obx';
|
||||||
import { valueToSource } from '../../../../utils/value-to-source';
|
import { valueToSource } from '../../../../utils/value-to-source';
|
||||||
import { CompositeValue, isJSExpression } from '../../../schema';
|
import { CompositeValue, isJSExpression, isJSSlot, NodeSchema, NodeData, isNodeSchema } from '../../../schema';
|
||||||
import StashSpace from './stash-space';
|
import PropStash from './prop-stash';
|
||||||
import { uniqueId } from '../../../../utils/unique-id';
|
import { uniqueId } from '../../../../utils/unique-id';
|
||||||
import { isPlainObject } from '../../../../utils/is-plain-object';
|
import { isPlainObject } from '../../../../utils/is-plain-object';
|
||||||
import { hasOwnProperty } from '../../../../utils/has-own-property';
|
import { hasOwnProperty } from '../../../../utils/has-own-property';
|
||||||
|
import Props from './props';
|
||||||
|
import Node from '../node';
|
||||||
|
|
||||||
export const UNSET = Symbol.for('unset');
|
export const UNSET = Symbol.for('unset');
|
||||||
export type UNSET = typeof UNSET;
|
export type UNSET = typeof UNSET;
|
||||||
|
|
||||||
export interface IPropParent {
|
export interface IPropParent {
|
||||||
delete(prop: Prop): void;
|
delete(prop: Prop): void;
|
||||||
|
readonly props: Props;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class Prop implements IPropParent {
|
export default class Prop implements IPropParent {
|
||||||
@ -18,11 +21,11 @@ export default class Prop implements IPropParent {
|
|||||||
|
|
||||||
readonly id = uniqueId('prop$');
|
readonly id = uniqueId('prop$');
|
||||||
|
|
||||||
private _type: 'unset' | 'literal' | 'map' | 'list' | 'expression' = 'unset';
|
@obx.ref private _type: 'unset' | 'literal' | 'map' | 'list' | 'expression' | 'slot' = 'unset';
|
||||||
/**
|
/**
|
||||||
* 属性类型
|
* 属性类型
|
||||||
*/
|
*/
|
||||||
get type(): 'unset' | 'literal' | 'map' | 'list' | 'expression' {
|
get type(): 'unset' | 'literal' | 'map' | 'list' | 'expression' | 'slot' {
|
||||||
return this._type;
|
return this._type;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -32,15 +35,27 @@ export default class Prop implements IPropParent {
|
|||||||
* 属性值
|
* 属性值
|
||||||
*/
|
*/
|
||||||
@computed get value(): CompositeValue {
|
@computed get value(): CompositeValue {
|
||||||
if (this._type === 'unset') {
|
return this.export(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
export(serialize = false): CompositeValue {
|
||||||
|
const type = this._type;
|
||||||
|
|
||||||
|
if (type === 'unset') {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const type = this._type;
|
|
||||||
if (type === 'literal' || type === 'expression') {
|
if (type === 'literal' || type === 'expression') {
|
||||||
return this._value;
|
return this._value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (type === 'slot') {
|
||||||
|
return {
|
||||||
|
type: 'JSSlot',
|
||||||
|
value: this._slotNode!.export(serialize),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
if (type === 'map') {
|
if (type === 'map') {
|
||||||
if (!this._items) {
|
if (!this._items) {
|
||||||
return this._value;
|
return this._value;
|
||||||
@ -79,11 +94,14 @@ export default class Prop implements IPropParent {
|
|||||||
this._value = null;
|
this._value = null;
|
||||||
this._type = 'literal';
|
this._type = 'literal';
|
||||||
} else if (t === 'string' || t === 'number' || t === 'boolean') {
|
} else if (t === 'string' || t === 'number' || t === 'boolean') {
|
||||||
this._value = val;
|
|
||||||
this._type = 'literal';
|
this._type = 'literal';
|
||||||
} else if (Array.isArray(val)) {
|
} else if (Array.isArray(val)) {
|
||||||
this._type = 'list';
|
this._type = 'list';
|
||||||
} else if (isPlainObject(val)) {
|
} else if (isPlainObject(val)) {
|
||||||
|
if (isJSSlot(val)) {
|
||||||
|
this.setAsSlot(val.value);
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (isJSExpression(val)) {
|
if (isJSExpression(val)) {
|
||||||
this._type = 'expression';
|
this._type = 'expression';
|
||||||
} else {
|
} else {
|
||||||
@ -97,14 +115,42 @@ export default class Prop implements IPropParent {
|
|||||||
value: valueToSource(val),
|
value: valueToSource(val),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
if (untracked(() => this._items)) {
|
this.dispose();
|
||||||
this._items!.forEach(prop => prop.purge());
|
}
|
||||||
this._items = null;
|
|
||||||
|
private dispose() {
|
||||||
|
const items = untracked(() => this._items);
|
||||||
|
if (items) {
|
||||||
|
items.forEach(prop => prop.purge());
|
||||||
}
|
}
|
||||||
|
this._items = null;
|
||||||
this._maps = null;
|
this._maps = null;
|
||||||
if (this.stash) {
|
if (this.stash) {
|
||||||
this.stash.clear();
|
this.stash.clear();
|
||||||
}
|
}
|
||||||
|
if (this._type !== 'slot' && this._slotNode) {
|
||||||
|
this._slotNode.purge();
|
||||||
|
this._slotNode = undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private _slotNode?: Node;
|
||||||
|
setAsSlot(data: NodeData) {
|
||||||
|
this._type = 'slot';
|
||||||
|
if (
|
||||||
|
this._slotNode &&
|
||||||
|
isNodeSchema(data) &&
|
||||||
|
(!data.id || this._slotNode.id === data.id) &&
|
||||||
|
this._slotNode.componentName === data.componentName
|
||||||
|
) {
|
||||||
|
this._slotNode.import(data);
|
||||||
|
} else {
|
||||||
|
this._slotNode?.internalSetParent(null);
|
||||||
|
const owner = this.props.owner;
|
||||||
|
this._slotNode = owner.document.createNode(data, this);
|
||||||
|
this._slotNode.internalSetParent(owner);
|
||||||
|
}
|
||||||
|
this.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -122,19 +168,19 @@ export default class Prop implements IPropParent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 值是否包含表达式
|
* 值是否是带类型的 JS
|
||||||
* 包含 JSExpresion | JSSlot 等值
|
* 比如 JSExpresion | JSSlot 等值
|
||||||
*/
|
*/
|
||||||
@computed isContainJSExpression(): boolean {
|
@computed isTypedJS(): boolean {
|
||||||
const type = this._type;
|
const type = this._type;
|
||||||
if (type === 'expression') {
|
if (type === 'expression' || type === 'slot') {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (type === 'literal' || type === 'unset') {
|
if (type === 'literal' || type === 'unset') {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if ((type === 'list' || type === 'map') && this.items) {
|
if ((type === 'list' || type === 'map') && this.items) {
|
||||||
return this.items.some(item => item.isContainJSExpression());
|
return this.items.some(item => item.isTypedJS());
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -143,7 +189,7 @@ export default class Prop implements IPropParent {
|
|||||||
* 是否简单 JSON 数据
|
* 是否简单 JSON 数据
|
||||||
*/
|
*/
|
||||||
@computed isJSON() {
|
@computed isJSON() {
|
||||||
return !this.isContainJSExpression();
|
return !this.isTypedJS();
|
||||||
}
|
}
|
||||||
|
|
||||||
@obx.val private _items: Prop[] | null = null;
|
@obx.val private _items: Prop[] | null = null;
|
||||||
@ -189,7 +235,7 @@ export default class Prop implements IPropParent {
|
|||||||
return this._maps;
|
return this._maps;
|
||||||
}
|
}
|
||||||
|
|
||||||
private stash: StashSpace | undefined;
|
private stash: PropStash | undefined;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 键值
|
* 键值
|
||||||
@ -200,12 +246,15 @@ export default class Prop implements IPropParent {
|
|||||||
*/
|
*/
|
||||||
@obx spread: boolean;
|
@obx spread: boolean;
|
||||||
|
|
||||||
|
readonly props: Props;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
public parent: IPropParent,
|
public parent: IPropParent,
|
||||||
value: CompositeValue | UNSET = UNSET,
|
value: CompositeValue | UNSET = UNSET,
|
||||||
key?: string | number,
|
key?: string | number,
|
||||||
spread = false,
|
spread = false,
|
||||||
) {
|
) {
|
||||||
|
this.props = parent.props;
|
||||||
if (value !== UNSET) {
|
if (value !== UNSET) {
|
||||||
this.value = value;
|
this.value = value;
|
||||||
}
|
}
|
||||||
@ -257,16 +306,11 @@ export default class Prop implements IPropParent {
|
|||||||
|
|
||||||
if (stash) {
|
if (stash) {
|
||||||
if (!this.stash) {
|
if (!this.stash) {
|
||||||
this.stash = new StashSpace(
|
this.stash = new PropStash(this.props, item => {
|
||||||
item => {
|
// item take effect
|
||||||
// item take effect
|
this.set(String(item.key), item);
|
||||||
this.set(String(item.key), item);
|
item.parent = this;
|
||||||
item.parent = this;
|
});
|
||||||
},
|
|
||||||
() => {
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
prop = this.stash.get(entry);
|
prop = this.stash.get(entry);
|
||||||
if (nest) {
|
if (nest) {
|
||||||
@ -401,6 +445,9 @@ export default class Prop implements IPropParent {
|
|||||||
this._items.forEach(item => item.purge());
|
this._items.forEach(item => item.purge());
|
||||||
}
|
}
|
||||||
this._maps = null;
|
this._maps = null;
|
||||||
|
if (this._slotNode && this._slotNode.slotFor === this) {
|
||||||
|
this._slotNode.purge();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -1,14 +1,14 @@
|
|||||||
import { computed, obx } from '@recore/obx';
|
import { computed, obx } from '@recore/obx';
|
||||||
import { uniqueId } from '../../../../utils/unique-id';
|
import { uniqueId } from '../../../../utils/unique-id';
|
||||||
import { CompositeValue, PropsList, PropsMap } from '../../../schema';
|
import { CompositeValue, PropsList, PropsMap } from '../../../schema';
|
||||||
import StashSpace from './stash-space';
|
import PropStash from './prop-stash';
|
||||||
import Prop, { IPropParent } from './prop';
|
import Prop, { IPropParent } from './prop';
|
||||||
|
import { NodeParent } from '../node';
|
||||||
|
|
||||||
export const UNSET = Symbol.for('unset');
|
export const UNSET = Symbol.for('unset');
|
||||||
export type UNSET = typeof UNSET;
|
export type UNSET = typeof UNSET;
|
||||||
|
|
||||||
|
export default class Props implements IPropParent {
|
||||||
export default class Props<O = any> implements IPropParent {
|
|
||||||
readonly id = uniqueId('props');
|
readonly id = uniqueId('props');
|
||||||
@obx.val private items: Prop[] = [];
|
@obx.val private items: Prop[] = [];
|
||||||
@computed private get maps(): Map<string, Prop> {
|
@computed private get maps(): Map<string, Prop> {
|
||||||
@ -23,15 +23,14 @@ export default class Props<O = any> implements IPropParent {
|
|||||||
return maps;
|
return maps;
|
||||||
}
|
}
|
||||||
|
|
||||||
private stash = new StashSpace(
|
get props(): Props {
|
||||||
prop => {
|
return this;
|
||||||
this.items.push(prop);
|
}
|
||||||
prop.parent = this;
|
|
||||||
},
|
private stash = new PropStash(this, prop => {
|
||||||
() => {
|
this.items.push(prop);
|
||||||
return true;
|
prop.parent = this;
|
||||||
},
|
});
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 元素个数
|
* 元素个数
|
||||||
@ -41,6 +40,36 @@ export default class Props<O = any> implements IPropParent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@computed get value(): PropsMap | PropsList | null {
|
@computed get value(): PropsMap | PropsList | null {
|
||||||
|
return this.export(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@obx type: 'map' | 'list' = 'map';
|
||||||
|
|
||||||
|
constructor(readonly owner: NodeParent, value?: PropsMap | PropsList | null) {
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
this.type = 'list';
|
||||||
|
this.items = value.map(item => new Prop(this, item.value, item.name, item.spread));
|
||||||
|
} else if (value != null) {
|
||||||
|
this.items = Object.keys(value).map(key => new Prop(this, value[key], key));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
import(value?: PropsMap | PropsList | null) {
|
||||||
|
this.stash.clear();
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
this.type = 'list';
|
||||||
|
this.items = value.map(item => new Prop(this, item.value, item.name, item.spread));
|
||||||
|
} else if (value != null) {
|
||||||
|
this.type = 'map';
|
||||||
|
this.items = Object.keys(value).map(key => new Prop(this, value[key], key));
|
||||||
|
} else {
|
||||||
|
this.type = 'map';
|
||||||
|
this.items = [];
|
||||||
|
}
|
||||||
|
this.items.forEach(item => item.purge());
|
||||||
|
}
|
||||||
|
|
||||||
|
export(serialize = false): PropsMap | PropsList | null {
|
||||||
if (this.items.length < 1) {
|
if (this.items.length < 1) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -48,30 +77,18 @@ export default class Props<O = any> implements IPropParent {
|
|||||||
return this.items.map(item => ({
|
return this.items.map(item => ({
|
||||||
spread: item.spread,
|
spread: item.spread,
|
||||||
name: item.key as string,
|
name: item.key as string,
|
||||||
value: item.value,
|
value: item.export(serialize),
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
const maps: any = {};
|
const maps: any = {};
|
||||||
this.items.forEach(prop => {
|
this.items.forEach(prop => {
|
||||||
if (prop.key) {
|
if (prop.key) {
|
||||||
maps[prop.key] = prop.value;
|
maps[prop.key] = prop.export(serialize);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return maps;
|
return maps;
|
||||||
}
|
}
|
||||||
|
|
||||||
@obx type: 'map' | 'list' = 'map';
|
|
||||||
|
|
||||||
constructor(readonly owner: O, value?: PropsMap | PropsList | null) {
|
|
||||||
if (Array.isArray(value)) {
|
|
||||||
this.type = 'list';
|
|
||||||
this.items = value.map(item => new Prop(this, item.value, item.name, item.spread));
|
|
||||||
} else if (value != null) {
|
|
||||||
this.type = 'map';
|
|
||||||
this.items = Object.keys(value).map(key => new Prop(this, value[key], key));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 根据 path 路径查询属性,如果没有则临时生成一个
|
* 根据 path 路径查询属性,如果没有则临时生成一个
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -56,13 +56,13 @@ export default class RootNode extends Node implements NodeParent {
|
|||||||
get children(): NodeChildren {
|
get children(): NodeChildren {
|
||||||
return this._children as NodeChildren;
|
return this._children as NodeChildren;
|
||||||
}
|
}
|
||||||
get props(): Props<RootNode> {
|
get props(): Props {
|
||||||
return this._props as any;
|
return this._props as any;
|
||||||
}
|
}
|
||||||
get extras(): Props<RootNode> {
|
get extras(): Props {
|
||||||
return this._extras as any;
|
return this._extras as any;
|
||||||
}
|
}
|
||||||
get directives(): Props<RootNode> {
|
get directives(): Props {
|
||||||
return this._directives as any;
|
return this._directives as any;
|
||||||
}
|
}
|
||||||
internalSetParent(parent: null) {}
|
internalSetParent(parent: null) {}
|
||||||
|
|||||||
@ -1 +1,174 @@
|
|||||||
// todo
|
import { EventEmitter } from 'events';
|
||||||
|
import Session from './session';
|
||||||
|
import { autorun, Reaction, untracked } from '@recore/obx';
|
||||||
|
import { NodeSchema } from '../schema';
|
||||||
|
|
||||||
|
|
||||||
|
export interface Serialization<T = any> {
|
||||||
|
serialize(data: NodeSchema): T;
|
||||||
|
unserialize(data: T): NodeSchema;
|
||||||
|
}
|
||||||
|
|
||||||
|
let currentSerializion: Serialization<any> = {
|
||||||
|
serialize(data: NodeSchema): string {
|
||||||
|
return JSON.stringify(data);
|
||||||
|
},
|
||||||
|
unserialize(data: string) {
|
||||||
|
return JSON.parse(data);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export function setSerialization(serializion: Serialization) {
|
||||||
|
currentSerializion = serializion;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class History {
|
||||||
|
private session: Session;
|
||||||
|
private records: Session[];
|
||||||
|
private point: number = 0;
|
||||||
|
private emitter = new EventEmitter();
|
||||||
|
private obx: Reaction;
|
||||||
|
private justWokeup: boolean = false;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
logger: () => any,
|
||||||
|
private redoer: (data: NodeSchema) => void,
|
||||||
|
private timeGap: number = 1000,
|
||||||
|
) {
|
||||||
|
this.session = new Session(0, null, this.timeGap);
|
||||||
|
this.records = [this.session];
|
||||||
|
|
||||||
|
this.obx = autorun(() => {
|
||||||
|
const data = logger();
|
||||||
|
console.info('log');
|
||||||
|
if (this.justWokeup) {
|
||||||
|
this.justWokeup = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
untracked(() => {
|
||||||
|
const log = currentSerializion.serialize(data);
|
||||||
|
if (this.session.cursor === 0 && this.session.isActive()) {
|
||||||
|
this.session.log(log);
|
||||||
|
this.session.end();
|
||||||
|
} else if (this.session) {
|
||||||
|
if (this.session.isActive()) {
|
||||||
|
this.session.log(log);
|
||||||
|
} else {
|
||||||
|
this.session.end();
|
||||||
|
const cursor = this.session.cursor + 1;
|
||||||
|
const session = new Session(cursor, log, this.timeGap);
|
||||||
|
this.session = session;
|
||||||
|
this.records.splice(cursor, this.records.length - cursor, session);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, true).$obx;
|
||||||
|
}
|
||||||
|
|
||||||
|
get hotData() {
|
||||||
|
return this.session.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
isSavePoint(): boolean {
|
||||||
|
return this.point !== this.session.cursor;
|
||||||
|
}
|
||||||
|
|
||||||
|
go(cursor: number) {
|
||||||
|
this.session.end();
|
||||||
|
|
||||||
|
const currentCursor = this.session.cursor;
|
||||||
|
cursor = +cursor;
|
||||||
|
if (cursor < 0) {
|
||||||
|
cursor = 0;
|
||||||
|
} else if (cursor >= this.records.length) {
|
||||||
|
cursor = this.records.length - 1;
|
||||||
|
}
|
||||||
|
if (cursor === currentCursor) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const session = this.records[cursor];
|
||||||
|
const hotData = session.data;
|
||||||
|
|
||||||
|
this.obx.sleep();
|
||||||
|
try {
|
||||||
|
this.redoer(hotData);
|
||||||
|
this.emitter.emit('cursor', hotData);
|
||||||
|
} catch (e) {
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
this.justWokeup = true;
|
||||||
|
this.obx.wakeup();
|
||||||
|
this.session = session;
|
||||||
|
|
||||||
|
this.emitter.emit('statechange', this.getState());
|
||||||
|
}
|
||||||
|
|
||||||
|
back() {
|
||||||
|
if (!this.session) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const cursor = this.session.cursor - 1;
|
||||||
|
this.go(cursor);
|
||||||
|
}
|
||||||
|
|
||||||
|
forward() {
|
||||||
|
if (!this.session) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const cursor = this.session.cursor + 1;
|
||||||
|
this.go(cursor);
|
||||||
|
}
|
||||||
|
|
||||||
|
savePoint() {
|
||||||
|
if (!this.session) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.session.end();
|
||||||
|
this.point = this.session.cursor;
|
||||||
|
this.emitter.emit('statechange', this.getState());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* | 1 | 1 | 1 |
|
||||||
|
* | -------- | -------- | -------- |
|
||||||
|
* | modified | redoable | undoable |
|
||||||
|
*/
|
||||||
|
getState(): number {
|
||||||
|
const cursor = this.session.cursor;
|
||||||
|
let state = 7;
|
||||||
|
// undoable ?
|
||||||
|
if (cursor <= 0) {
|
||||||
|
state -= 1;
|
||||||
|
}
|
||||||
|
// redoable ?
|
||||||
|
if (cursor >= this.records.length - 1) {
|
||||||
|
state -= 2;
|
||||||
|
}
|
||||||
|
// modified ?
|
||||||
|
if (this.point === cursor) {
|
||||||
|
state -= 4;
|
||||||
|
}
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
onStateChange(func: () => any) {
|
||||||
|
this.emitter.on('statechange', func);
|
||||||
|
return () => {
|
||||||
|
this.emitter.removeListener('statechange', func);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
onCursor(func: () => any) {
|
||||||
|
this.emitter.on('cursor', func);
|
||||||
|
return () => {
|
||||||
|
this.emitter.removeListener('cursor', func);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
destroy() {
|
||||||
|
this.emitter.removeAllListeners();
|
||||||
|
this.records = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
44
packages/designer/src/designer/helper/session.ts
Normal file
44
packages/designer/src/designer/helper/session.ts
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
export default class Session {
|
||||||
|
private _data: any;
|
||||||
|
private activedTimer: any;
|
||||||
|
|
||||||
|
get data() {
|
||||||
|
return this._data;
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(readonly cursor: number, data: any, private timeGap: number = 1000) {
|
||||||
|
this.setTimer();
|
||||||
|
this.log(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
log(data: any) {
|
||||||
|
if (!this.isActive()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this._data = data;
|
||||||
|
this.setTimer();
|
||||||
|
}
|
||||||
|
|
||||||
|
isActive() {
|
||||||
|
return this.activedTimer != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
end() {
|
||||||
|
if (this.isActive()) {
|
||||||
|
this.clearTimer();
|
||||||
|
console.info('session end');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private setTimer() {
|
||||||
|
this.clearTimer();
|
||||||
|
this.activedTimer = setTimeout(() => this.end(), this.timeGap);
|
||||||
|
}
|
||||||
|
|
||||||
|
private clearTimer() {
|
||||||
|
if (this.activedTimer) {
|
||||||
|
clearTimeout(this.activedTimer);
|
||||||
|
}
|
||||||
|
this.activedTimer = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -8,7 +8,6 @@ export default class ProjectView extends Component<{ designer: Designer }> {
|
|||||||
render() {
|
render() {
|
||||||
const { designer } = this.props;
|
const { designer } = this.props;
|
||||||
// TODO: support splitview
|
// TODO: support splitview
|
||||||
console.info(designer.project.documents);
|
|
||||||
return (
|
return (
|
||||||
<div className="lc-project">
|
<div className="lc-project">
|
||||||
{designer.project.documents.map(doc => {
|
{designer.project.documents.map(doc => {
|
||||||
|
|||||||
@ -90,15 +90,14 @@ export type PropsList = Array<{
|
|||||||
|
|
||||||
export type NodeData = NodeSchema | JSExpression | DOMText;
|
export type NodeData = NodeSchema | JSExpression | DOMText;
|
||||||
|
|
||||||
export interface JSExpression {
|
|
||||||
type: 'JSExpression';
|
|
||||||
value: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isJSExpression(data: any): data is JSExpression {
|
export function isJSExpression(data: any): data is JSExpression {
|
||||||
return data && data.type === 'JSExpression';
|
return data && data.type === 'JSExpression';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isJSSlot(data: any): data is JSSlot {
|
||||||
|
return data && data.type === 'JSSlot';
|
||||||
|
}
|
||||||
|
|
||||||
export function isDOMText(data: any): data is DOMText {
|
export function isDOMText(data: any): data is DOMText {
|
||||||
return typeof data === 'string';
|
return typeof data === 'string';
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user