mirror of
https://github.com/alibaba/lowcode-engine.git
synced 2026-04-20 04:18:05 +00:00
Merge branch 'feat/joint-editor' into feat/code-generator
This commit is contained in:
commit
0210c4d9ba
@ -2,13 +2,13 @@
|
|||||||
"name": "lowcode-designer",
|
"name": "lowcode-designer",
|
||||||
"version": "0.9.0",
|
"version": "0.9.0",
|
||||||
"description": "alibaba lowcode designer",
|
"description": "alibaba lowcode designer",
|
||||||
"main": "index.js",
|
"main": "src/index.ts",
|
||||||
"author": "",
|
"author": "",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ali/iceluna-sdk": "^1.0.5-beta.12",
|
"@ali/iceluna-sdk": "^1.0.5-beta.12",
|
||||||
"@recore/core-obx": "^1.0.4",
|
"@recore/obx": "^1.0.8",
|
||||||
"@recore/obx": "^1.0.5",
|
"@recore/obx-react": "^1.0.7",
|
||||||
"@types/medium-editor": "^5.0.3",
|
"@types/medium-editor": "^5.0.3",
|
||||||
"classnames": "^2.2.6",
|
"classnames": "^2.2.6",
|
||||||
"react": "^16",
|
"react": "^16",
|
||||||
|
|||||||
@ -1,11 +1,9 @@
|
|||||||
import { Component } from 'react';
|
import { Component } from 'react';
|
||||||
import { obx } from '@recore/obx';
|
import { obx } from '@recore/obx';
|
||||||
import { observer } from '@recore/core-obx';
|
import { observer } from '@recore/obx-react';
|
||||||
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;
|
||||||
|
|
||||||
@ -20,7 +18,10 @@ export default class Ghost extends Component<{ designer: Designer }> {
|
|||||||
constructor(props: any) {
|
constructor(props: any) {
|
||||||
super(props);
|
super(props);
|
||||||
this.dispose = [
|
this.dispose = [
|
||||||
this.dragon.onDragstart((e) => {
|
this.dragon.onDragstart(e => {
|
||||||
|
if (e.originalEvent.type.substr(0, 4) === 'drag') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
this.dragObject = e.dragObject;
|
this.dragObject = e.dragObject;
|
||||||
this.x = e.globalX;
|
this.x = e.globalX;
|
||||||
this.y = e.globalY;
|
this.y = e.globalY;
|
||||||
@ -50,7 +51,7 @@ export default class Ghost extends Component<{ designer: Designer }> {
|
|||||||
renderGhostGroup() {
|
renderGhostGroup() {
|
||||||
const dragObject = this.dragObject;
|
const dragObject = this.dragObject;
|
||||||
if (isDragNodeObject(dragObject)) {
|
if (isDragNodeObject(dragObject)) {
|
||||||
return dragObject.nodes.map((node) => {
|
return dragObject.nodes.map(node => {
|
||||||
const ghost = (
|
const ghost = (
|
||||||
<div className="lc-ghost" key={node.id}>
|
<div className="lc-ghost" key={node.id}>
|
||||||
<div className="lc-ghost-title">{node.title}</div>
|
<div className="lc-ghost-title">{node.title}</div>
|
||||||
@ -59,17 +60,19 @@ export default class Ghost extends Component<{ designer: Designer }> {
|
|||||||
return ghost;
|
return ghost;
|
||||||
});
|
});
|
||||||
} else if (isDragNodeDataObject(dragObject)) {
|
} else if (isDragNodeDataObject(dragObject)) {
|
||||||
return Array.isArray(dragObject.data) ? dragObject.data.map((item, index) => {
|
return Array.isArray(dragObject.data) ? (
|
||||||
return (
|
dragObject.data.map((item, index) => {
|
||||||
<div className="lc-ghost" key={`ghost-${index}`}>
|
return (
|
||||||
<div className="lc-ghost-title">{item.componentName}</div>
|
<div className="lc-ghost" key={`ghost-${index}`}>
|
||||||
</div>
|
<div className="lc-ghost-title">{item.componentName}</div>
|
||||||
)
|
</div>
|
||||||
}) : (
|
);
|
||||||
|
})
|
||||||
|
) : (
|
||||||
<div className="lc-ghost">
|
<div className="lc-ghost">
|
||||||
<div className="lc-ghost-title">{dragObject.data.componentName}</div>
|
<div className="lc-ghost-title">{dragObject.data.componentName}</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { observer } from '@recore/core-obx';
|
import { observer } from '@recore/obx-react';
|
||||||
import { Component } from 'react';
|
import { Component } from 'react';
|
||||||
import { OutlineHovering } from './outline-hovering';
|
import { OutlineHovering } from './outline-hovering';
|
||||||
import { SimulatorContext } from '../context';
|
import { SimulatorContext } from '../context';
|
||||||
|
|||||||
@ -1,9 +1,14 @@
|
|||||||
import { Component } from 'react';
|
import { Component } from 'react';
|
||||||
import { computed } from '@recore/obx';
|
import { computed } from '@recore/obx';
|
||||||
import { observer } from '@recore/core-obx';
|
import { observer } from '@recore/obx-react';
|
||||||
import { SimulatorContext } from '../context';
|
import { SimulatorContext } from '../context';
|
||||||
import { SimulatorHost } from '../host';
|
import { SimulatorHost } from '../host';
|
||||||
import Location, { Rect, isLocationChildrenDetail, LocationChildrenDetail, isVertical } from '../../../../designer/helper/location';
|
import Location, {
|
||||||
|
Rect,
|
||||||
|
isLocationChildrenDetail,
|
||||||
|
LocationChildrenDetail,
|
||||||
|
isVertical,
|
||||||
|
} from '../../../../designer/helper/location';
|
||||||
import { ISimulator } from '../../../../designer/simulator';
|
import { ISimulator } from '../../../../designer/simulator';
|
||||||
import { NodeParent } from '../../../../designer/document/node/node';
|
import { NodeParent } from '../../../../designer/document/node/node';
|
||||||
import './insertion.less';
|
import './insertion.less';
|
||||||
@ -19,11 +24,7 @@ interface InsertionData {
|
|||||||
/**
|
/**
|
||||||
* 处理拖拽子节点(INode)情况
|
* 处理拖拽子节点(INode)情况
|
||||||
*/
|
*/
|
||||||
function processChildrenDetail(
|
function processChildrenDetail(sim: ISimulator, target: NodeParent, detail: LocationChildrenDetail): InsertionData {
|
||||||
sim: ISimulator,
|
|
||||||
target: NodeParent,
|
|
||||||
detail: LocationChildrenDetail,
|
|
||||||
): InsertionData {
|
|
||||||
let edge = detail.edge || null;
|
let edge = detail.edge || null;
|
||||||
|
|
||||||
if (edge) {
|
if (edge) {
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { Component, Fragment, PureComponent } from 'react';
|
import { Component, Fragment, PureComponent } from 'react';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { observer } from '@recore/core-obx';
|
import { observer } from '@recore/obx-react';
|
||||||
import { SimulatorContext } from '../context';
|
import { SimulatorContext } from '../context';
|
||||||
import { SimulatorHost } from '../host';
|
import { SimulatorHost } from '../host';
|
||||||
import { computed } from '@recore/obx';
|
import { computed } from '@recore/obx';
|
||||||
@ -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 />;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { Component, Fragment } from 'react';
|
import { Component, Fragment } from 'react';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { observer } from '@recore/core-obx';
|
import { observer } from '@recore/obx-react';
|
||||||
import { SimulatorContext } from '../context';
|
import { SimulatorContext } from '../context';
|
||||||
import { SimulatorHost } from '../host';
|
import { SimulatorHost } from '../host';
|
||||||
import { computed } from '@recore/obx';
|
import { computed } from '@recore/obx';
|
||||||
@ -8,17 +8,17 @@ import OffsetObserver from '../../../../designer/helper/offset-observer';
|
|||||||
import Node from '../../../../designer/document/node/node';
|
import Node from '../../../../designer/document/node/node';
|
||||||
|
|
||||||
@observer
|
@observer
|
||||||
export class OutlineSelectingInstance extends Component<{ observed: OffsetObserver; highlight?: boolean }> {
|
export class OutlineSelectingInstance extends Component<{
|
||||||
shouldComponentUpdate() {
|
observed: OffsetObserver;
|
||||||
return false;
|
highlight?: boolean;
|
||||||
}
|
dragging?: boolean;
|
||||||
|
}> {
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
this.props.observed.purge();
|
this.props.observed.purge();
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { observed, highlight } = this.props;
|
const { observed, highlight, dragging } = this.props;
|
||||||
if (!observed.hasOffset) {
|
if (!observed.hasOffset) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -33,6 +33,7 @@ export class OutlineSelectingInstance extends Component<{ observed: OffsetObserv
|
|||||||
|
|
||||||
const className = classNames('lc-outlines lc-outlines-selecting', {
|
const className = classNames('lc-outlines lc-outlines-selecting', {
|
||||||
highlight,
|
highlight,
|
||||||
|
dragging,
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -51,6 +52,10 @@ export class OutlineSelectingForNode extends Component<{ node: Node }> {
|
|||||||
return this.context;
|
return this.context;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get dragging(): boolean {
|
||||||
|
return this.host.designer.dragon.dragging;
|
||||||
|
}
|
||||||
|
|
||||||
@computed get instances() {
|
@computed get instances() {
|
||||||
return this.host.getComponentInstances(this.props.node);
|
return this.host.getComponentInstances(this.props.node);
|
||||||
}
|
}
|
||||||
@ -69,7 +74,7 @@ export class OutlineSelectingForNode extends Component<{ node: Node }> {
|
|||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<Fragment key={node.id}>
|
<Fragment key={node.id}>
|
||||||
{instances.map((instance) => {
|
{instances.map(instance => {
|
||||||
const observed = designer.createOffsetObserver({
|
const observed = designer.createOffsetObserver({
|
||||||
node,
|
node,
|
||||||
instance,
|
instance,
|
||||||
@ -77,7 +82,7 @@ export class OutlineSelectingForNode extends Component<{ node: Node }> {
|
|||||||
if (!observed) {
|
if (!observed) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return <OutlineSelectingInstance key={observed.id} observed={observed} />;
|
return <OutlineSelectingInstance key={observed.id} dragging={this.dragging} observed={observed} />;
|
||||||
})}
|
})}
|
||||||
</Fragment>
|
</Fragment>
|
||||||
);
|
);
|
||||||
@ -92,12 +97,17 @@ export class OutlineSelecting extends Component {
|
|||||||
return this.context;
|
return this.context;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get dragging(): boolean {
|
||||||
|
return this.host.designer.dragon.dragging;
|
||||||
|
}
|
||||||
|
|
||||||
@computed get selecting() {
|
@computed get selecting() {
|
||||||
const doc = this.host.document;
|
const doc = this.host.document;
|
||||||
if (doc.suspensed) {
|
if (doc.suspensed) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return doc.selection.getNodes();
|
const selection = doc.selection;
|
||||||
|
return this.dragging ? selection.getTopNodes() : selection.getNodes();
|
||||||
}
|
}
|
||||||
|
|
||||||
shouldComponentUpdate() {
|
shouldComponentUpdate() {
|
||||||
|
|||||||
@ -22,7 +22,7 @@
|
|||||||
&&-hovering {
|
&&-hovering {
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
border-style: dashed;
|
border-style: dashed;
|
||||||
background: rgba(95, 240, 114, 0.04);
|
background: rgba(0,121,242,.04);
|
||||||
|
|
||||||
&.x-loop {
|
&.x-loop {
|
||||||
border-color: rgba(138, 93, 226, 0.8);
|
border-color: rgba(138, 93, 226, 0.8);
|
||||||
@ -44,6 +44,7 @@
|
|||||||
|
|
||||||
&&-selecting {
|
&&-selecting {
|
||||||
z-index: 2;
|
z-index: 2;
|
||||||
|
border-width: 2px;
|
||||||
|
|
||||||
&.x-loop {
|
&.x-loop {
|
||||||
border-color: rgba(147, 112, 219, 1.0);
|
border-color: rgba(147, 112, 219, 1.0);
|
||||||
@ -67,7 +68,6 @@
|
|||||||
&.dragging {
|
&.dragging {
|
||||||
background: rgba(182, 178, 178, 0.8);
|
background: rgba(182, 178, 178, 0.8);
|
||||||
border: none;
|
border: none;
|
||||||
pointer-events: all;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,7 +4,11 @@ import { SimulatorHost } from './host';
|
|||||||
import { AssetLevel, AssetLevels, AssetList, isAssetBundle, isAssetItem, AssetType, assetItem } from '../utils/asset';
|
import { AssetLevel, AssetLevels, AssetList, isAssetBundle, isAssetItem, AssetType, assetItem } from '../utils/asset';
|
||||||
import { isCSSUrl } from '../../../utils/is-css-url';
|
import { isCSSUrl } from '../../../utils/is-css-url';
|
||||||
|
|
||||||
export function createSimulator(host: SimulatorHost, iframe: HTMLIFrameElement, vendors: AssetList = []): Promise<SimulatorRenderer> {
|
export function createSimulator(
|
||||||
|
host: SimulatorHost,
|
||||||
|
iframe: HTMLIFrameElement,
|
||||||
|
vendors: AssetList = [],
|
||||||
|
): Promise<SimulatorRenderer> {
|
||||||
const win: any = iframe.contentWindow;
|
const win: any = iframe.contentWindow;
|
||||||
const doc = iframe.contentDocument!;
|
const doc = iframe.contentDocument!;
|
||||||
|
|
||||||
@ -12,7 +16,7 @@ export function createSimulator(host: SimulatorHost, iframe: HTMLIFrameElement,
|
|||||||
|
|
||||||
const styles: any = {};
|
const styles: any = {};
|
||||||
const scripts: any = {};
|
const scripts: any = {};
|
||||||
AssetLevels.forEach((lv) => {
|
AssetLevels.forEach(lv => {
|
||||||
styles[lv] = [];
|
styles[lv] = [];
|
||||||
scripts[lv] = [];
|
scripts[lv] = [];
|
||||||
});
|
});
|
||||||
@ -36,9 +40,9 @@ export function createSimulator(host: SimulatorHost, iframe: HTMLIFrameElement,
|
|||||||
asset = assetItem(isCSSUrl(asset) ? AssetType.CSSUrl : AssetType.JSUrl, asset, level)!;
|
asset = assetItem(isCSSUrl(asset) ? AssetType.CSSUrl : AssetType.JSUrl, asset, level)!;
|
||||||
}
|
}
|
||||||
const id = asset.id ? ` data-id="${asset.id}"` : '';
|
const id = asset.id ? ` data-id="${asset.id}"` : '';
|
||||||
const lv = asset.level || level || AssetLevel.BaseDepends;
|
const lv = asset.level || level || AssetLevel.Environment;
|
||||||
if (asset.type === AssetType.JSUrl) {
|
if (asset.type === AssetType.JSUrl) {
|
||||||
(scripts[lv] || scripts[AssetLevel.App]).push(`<script src="${asset.content}"${id}></script>`)
|
(scripts[lv] || scripts[AssetLevel.App]).push(`<script src="${asset.content}"${id}></script>`);
|
||||||
} else if (asset.type === AssetType.JSText) {
|
} else if (asset.type === AssetType.JSText) {
|
||||||
(scripts[lv] || scripts[AssetLevel.App]).push(`<script${id}>${asset.content}</script>`);
|
(scripts[lv] || scripts[AssetLevel.App]).push(`<script${id}>${asset.content}</script>`);
|
||||||
} else if (asset.type === AssetType.CSSUrl) {
|
} else if (asset.type === AssetType.CSSUrl) {
|
||||||
@ -51,12 +55,16 @@ export function createSimulator(host: SimulatorHost, iframe: HTMLIFrameElement,
|
|||||||
|
|
||||||
parseAssetList(vendors);
|
parseAssetList(vendors);
|
||||||
|
|
||||||
const styleFrags = Object.keys(styles).map(key => {
|
const styleFrags = Object.keys(styles)
|
||||||
return styles[key].join('\n') + `<meta level="${key}" />`;
|
.map(key => {
|
||||||
}).join('');
|
return styles[key].join('\n') + `<meta level="${key}" />`;
|
||||||
const scriptFrags = Object.keys(scripts).map(key => {
|
})
|
||||||
return scripts[key].join('\n');
|
.join('');
|
||||||
}).join('');
|
const scriptFrags = Object.keys(scripts)
|
||||||
|
.map(key => {
|
||||||
|
return scripts[key].join('\n');
|
||||||
|
})
|
||||||
|
.join('');
|
||||||
|
|
||||||
doc.open();
|
doc.open();
|
||||||
doc.write(`<!doctype html><html><head><meta charset="utf-8"/>
|
doc.write(`<!doctype html><html><head><meta charset="utf-8"/>
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { Component } from 'react';
|
import { Component } from 'react';
|
||||||
import { observer } from '@recore/core-obx';
|
import { observer } from '@recore/obx-react';
|
||||||
import { SimulatorHost, SimulatorProps } from './host';
|
import { SimulatorHost, SimulatorProps } from './host';
|
||||||
import DocumentModel from '../../../designer/document/document-model';
|
import DocumentModel from '../../../designer/document/document-model';
|
||||||
import { SimulatorContext } from './context';
|
import { SimulatorContext } from './context';
|
||||||
@ -15,10 +15,12 @@ import './host.less';
|
|||||||
Auxiliary 辅助显示层,初始相对 Content 位置 0,0,紧贴 Canvas, 根据 Content 滚动位置,改变相对位置
|
Auxiliary 辅助显示层,初始相对 Content 位置 0,0,紧贴 Canvas, 根据 Content 滚动位置,改变相对位置
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export class SimulatorHostView extends Component<SimulatorProps & {
|
type SimulatorHostProps = SimulatorProps & {
|
||||||
documentContext: DocumentModel;
|
documentContext: DocumentModel;
|
||||||
onMount?: (host: SimulatorHost) => void;
|
onMount?: (host: SimulatorHost) => void;
|
||||||
}> {
|
};
|
||||||
|
|
||||||
|
export class SimulatorHostView extends Component<SimulatorHostProps> {
|
||||||
readonly host: SimulatorHost;
|
readonly host: SimulatorHost;
|
||||||
constructor(props: any) {
|
constructor(props: any) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|||||||
@ -45,8 +45,12 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&-device-legao {
|
&-device-default {
|
||||||
margin: 15px;
|
top: 15px;
|
||||||
|
right: 15px;
|
||||||
|
bottom: 15px;
|
||||||
|
left: 15px;
|
||||||
|
width: auto;
|
||||||
box-shadow: 0 2px 10px 0 rgba(31,56,88,.15);
|
box-shadow: 0 2px 10px 0 rgba(31,56,88,.15);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -6,7 +6,7 @@ import { SimulatorRenderer } from '../renderer/renderer';
|
|||||||
import Node, { NodeParent, isNodeParent, isNode, contains } from '../../../designer/document/node/node';
|
import Node, { NodeParent, isNodeParent, isNode, contains } from '../../../designer/document/node/node';
|
||||||
import DocumentModel from '../../../designer/document/document-model';
|
import DocumentModel from '../../../designer/document/document-model';
|
||||||
import ResourceConsumer from './resource-consumer';
|
import ResourceConsumer from './resource-consumer';
|
||||||
import { AssetLevel, Asset, assetBundle, assetItem, AssetType } from '../utils/asset';
|
import { AssetLevel, Asset, AssetList, assetBundle, assetItem, AssetType } from '../utils/asset';
|
||||||
import {
|
import {
|
||||||
DragObjectType,
|
DragObjectType,
|
||||||
isShaken,
|
isShaken,
|
||||||
@ -29,11 +29,16 @@ import {
|
|||||||
CanvasPoint,
|
CanvasPoint,
|
||||||
} from '../../../designer/helper/location';
|
} from '../../../designer/helper/location';
|
||||||
import { isNodeSchema, NodeSchema } from '../../../designer/schema';
|
import { isNodeSchema, NodeSchema } from '../../../designer/schema';
|
||||||
import { ComponentDescriptionSpec } from '../../../designer/component-config';
|
import { ComponentMetadata } from '../../../designer/component-meta';
|
||||||
import { ReactInstance } from 'react';
|
import { ReactInstance } from 'react';
|
||||||
import { setNativeSelection } from '../../../designer/helper/navtive-selection';
|
|
||||||
import cursor from '../../../designer/helper/cursor';
|
|
||||||
import { isRootNode } from '../../../designer/document/node/root-node';
|
import { isRootNode } from '../../../designer/document/node/root-node';
|
||||||
|
import { parseProps } from '../utils/parse-props';
|
||||||
|
|
||||||
|
export interface LibraryItem {
|
||||||
|
package: string;
|
||||||
|
library: string;
|
||||||
|
urls: Asset;
|
||||||
|
}
|
||||||
|
|
||||||
export interface SimulatorProps {
|
export interface SimulatorProps {
|
||||||
// 从 documentModel 上获取
|
// 从 documentModel 上获取
|
||||||
@ -42,8 +47,9 @@ export interface SimulatorProps {
|
|||||||
device?: 'mobile' | 'iphone' | string;
|
device?: 'mobile' | 'iphone' | string;
|
||||||
deviceClassName?: string;
|
deviceClassName?: string;
|
||||||
simulatorUrl?: Asset;
|
simulatorUrl?: Asset;
|
||||||
dependsAsset?: Asset;
|
environment?: Asset;
|
||||||
themesAsset?: Asset;
|
library?: LibraryItem[];
|
||||||
|
theme?: Asset;
|
||||||
componentsAsset?: Asset;
|
componentsAsset?: Asset;
|
||||||
[key: string]: any;
|
[key: string]: any;
|
||||||
}
|
}
|
||||||
@ -53,22 +59,21 @@ const publicPath = (document.currentScript as HTMLScriptElement).src.replace(/^(
|
|||||||
const defaultSimulatorUrl = (() => {
|
const defaultSimulatorUrl = (() => {
|
||||||
let urls;
|
let urls;
|
||||||
if (process.env.NODE_ENV === 'production') {
|
if (process.env.NODE_ENV === 'production') {
|
||||||
urls = [`${publicPath}simulator-renderer.min.css`, `${publicPath}simulator-renderer.min.js`];
|
urls = [`${publicPath}../css/simulator-renderer.min.css`, `${publicPath}simulator-renderer.min.js`];
|
||||||
} else {
|
} else {
|
||||||
urls = [`${publicPath}simulator-renderer.css`, `${publicPath}simulator-renderer.js`];
|
urls = [`${publicPath}../css/simulator-renderer.css`, `${publicPath}simulator-renderer.js`];
|
||||||
}
|
}
|
||||||
return urls;
|
return urls;
|
||||||
})();
|
})();
|
||||||
|
|
||||||
const defaultDepends = [
|
const defaultEnvironment = [
|
||||||
// https://g.alicdn.com/mylib/??react/16.11.0/umd/react.production.min.js,react-dom/16.8.6/umd/react-dom.production.min.js,prop-types/15.7.2/prop-types.min.js
|
// https://g.alicdn.com/mylib/??react/16.11.0/umd/react.production.min.js,react-dom/16.8.6/umd/react-dom.production.min.js,prop-types/15.7.2/prop-types.min.js
|
||||||
assetItem(AssetType.JSText, 'window.React=parent.React;window.ReactDOM=parent.ReactDOM;', undefined, 'react'),
|
assetItem(AssetType.JSText, 'window.React=parent.React;window.ReactDOM=parent.ReactDOM;', undefined, 'react'),
|
||||||
assetItem(
|
assetItem(
|
||||||
AssetType.JSText,
|
AssetType.JSText,
|
||||||
'window.PropTypes=parent.PropTypes;React.PropTypes=parent.PropTypes; window.__REACT_DEVTOOLS_GLOBAL_HOOK__ = window.parent.__REACT_DEVTOOLS_GLOBAL_HOOK__;',
|
'window.PropTypes=parent.PropTypes;React.PropTypes=parent.PropTypes; window.__REACT_DEVTOOLS_GLOBAL_HOOK__ = window.parent.__REACT_DEVTOOLS_GLOBAL_HOOK__;',
|
||||||
),
|
),
|
||||||
assetItem(AssetType.JSUrl, 'https://g.alicdn.com/mylib/@ali/recore/1.5.7/umd/recore.min.js'),
|
assetItem(AssetType.JSUrl, '/statics/lowcode-renderer.js'),
|
||||||
assetItem(AssetType.JSUrl, 'http://localhost:4444/js/index.js'),
|
|
||||||
];
|
];
|
||||||
|
|
||||||
export class SimulatorHost implements ISimulator<SimulatorProps> {
|
export class SimulatorHost implements ISimulator<SimulatorProps> {
|
||||||
@ -81,7 +86,7 @@ export class SimulatorHost implements ISimulator<SimulatorProps> {
|
|||||||
@computed get device(): string | undefined {
|
@computed get device(): string | undefined {
|
||||||
// 根据 device 不同来做画布外框样式变化 渲染时可选择不同组件
|
// 根据 device 不同来做画布外框样式变化 渲染时可选择不同组件
|
||||||
// renderer 依赖
|
// renderer 依赖
|
||||||
return this.get('device');
|
return this.get('device') || 'default';
|
||||||
}
|
}
|
||||||
|
|
||||||
@computed get deviceClassName(): string | undefined {
|
@computed get deviceClassName(): string | undefined {
|
||||||
@ -98,8 +103,8 @@ export class SimulatorHost implements ISimulator<SimulatorProps> {
|
|||||||
return this.get('componentsAsset');
|
return this.get('componentsAsset');
|
||||||
}
|
}
|
||||||
|
|
||||||
@computed get themesAsset(): Asset | undefined {
|
@computed get theme(): Asset | undefined {
|
||||||
return this.get('themesAsset');
|
return this.get('theme');
|
||||||
}
|
}
|
||||||
|
|
||||||
@computed get componentsMap() {
|
@computed get componentsMap() {
|
||||||
@ -132,15 +137,14 @@ export class SimulatorHost implements ISimulator<SimulatorProps> {
|
|||||||
return autorun(fn as any, true);
|
return autorun(fn as any, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
purge(): void {}
|
purge(): void {
|
||||||
|
// todo
|
||||||
|
}
|
||||||
|
|
||||||
readonly viewport = new Viewport();
|
readonly viewport = new Viewport();
|
||||||
readonly scroller = this.designer.createScroller(this.viewport);
|
readonly scroller = this.designer.createScroller(this.viewport);
|
||||||
|
|
||||||
mountViewport(viewport: Element | null) {
|
mountViewport(viewport: Element | null) {
|
||||||
if (!viewport) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.viewport.mount(viewport);
|
this.viewport.mount(viewport);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -165,18 +169,33 @@ export class SimulatorHost implements ISimulator<SimulatorProps> {
|
|||||||
return {};
|
return {};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
readonly libraryMap: { [key: string]: string } = {};
|
||||||
|
|
||||||
|
private _iframe?: HTMLIFrameElement;
|
||||||
async mountContentFrame(iframe: HTMLIFrameElement | null) {
|
async mountContentFrame(iframe: HTMLIFrameElement | null) {
|
||||||
if (!iframe) {
|
if (!iframe || this._iframe === iframe) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
this._iframe = iframe;
|
||||||
|
|
||||||
this._contentWindow = iframe.contentWindow!;
|
this._contentWindow = iframe.contentWindow!;
|
||||||
|
|
||||||
|
const library = this.get('library') as LibraryItem[];
|
||||||
|
const libraryAsset: AssetList = [];
|
||||||
|
if (library) {
|
||||||
|
library.forEach(item => {
|
||||||
|
this.libraryMap[item.package] = item.library;
|
||||||
|
libraryAsset.push(item.urls);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const vendors = [
|
const vendors = [
|
||||||
// required & use once
|
// required & use once
|
||||||
assetBundle(this.get('dependsAsset') || defaultDepends, AssetLevel.BaseDepends),
|
assetBundle(this.get('environment') || defaultEnvironment, AssetLevel.Environment),
|
||||||
|
// required & use once
|
||||||
|
assetBundle(libraryAsset, AssetLevel.Library),
|
||||||
// required & TODO: think of update
|
// required & TODO: think of update
|
||||||
assetBundle(this.themesAsset, AssetLevel.Theme),
|
assetBundle(this.theme, AssetLevel.Theme),
|
||||||
// required & use once
|
// required & use once
|
||||||
assetBundle(this.get('simulatorUrl') || defaultSimulatorUrl, AssetLevel.Runtime),
|
assetBundle(this.get('simulatorUrl') || defaultSimulatorUrl, AssetLevel.Runtime),
|
||||||
];
|
];
|
||||||
@ -214,68 +233,88 @@ export class SimulatorHost implements ISimulator<SimulatorProps> {
|
|||||||
|
|
||||||
// TODO: think of lock when edit a node
|
// TODO: think of lock when edit a node
|
||||||
// 事件路由
|
// 事件路由
|
||||||
doc.addEventListener('mousedown', (downEvent: MouseEvent) => {
|
doc.addEventListener(
|
||||||
const nodeInst = this.getNodeInstanceFromElement(downEvent.target as Element);
|
'mousedown',
|
||||||
if (!nodeInst?.node) {
|
(downEvent: MouseEvent) => {
|
||||||
selection.clear();
|
// stop response document focus event
|
||||||
return;
|
downEvent.stopPropagation();
|
||||||
}
|
downEvent.preventDefault();
|
||||||
|
|
||||||
const isMulti = downEvent.metaKey || downEvent.ctrlKey;
|
const nodeInst = this.getNodeInstanceFromElement(downEvent.target as Element);
|
||||||
const isLeftButton = downEvent.which === 1 || downEvent.button === 0;
|
const node = nodeInst?.node || this.document.rootNode;
|
||||||
|
const isMulti = downEvent.metaKey || downEvent.ctrlKey;
|
||||||
if (isLeftButton) {
|
const isLeftButton = downEvent.which === 1 || downEvent.button === 0;
|
||||||
let node: Node = nodeInst.node;
|
const checkSelect = (e: MouseEvent) => {
|
||||||
let nodes: Node[] = [node];
|
doc.removeEventListener('mouseup', checkSelect, true);
|
||||||
let ignoreUpSelected = false;
|
if (!isShaken(downEvent, e)) {
|
||||||
if (isMulti) {
|
const id = node.id;
|
||||||
// multi select mode, directily add
|
|
||||||
if (!selection.has(node.id)) {
|
|
||||||
designer.activeTracker.track(node);
|
designer.activeTracker.track(node);
|
||||||
selection.add(node.id);
|
if (isMulti && !isRootNode(node) && selection.has(id)) {
|
||||||
ignoreUpSelected = true;
|
selection.remove(id);
|
||||||
|
} else {
|
||||||
|
selection.select(id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// 获得顶层 nodes
|
};
|
||||||
nodes = selection.getTopNodes();
|
|
||||||
} else if (selection.containsNode(node)) {
|
|
||||||
nodes = selection.getTopNodes();
|
|
||||||
} else {
|
|
||||||
// will clear current selection & select dragment in dragstart
|
|
||||||
}
|
|
||||||
designer.dragon.boost(
|
|
||||||
{
|
|
||||||
type: DragObjectType.Node,
|
|
||||||
nodes,
|
|
||||||
},
|
|
||||||
downEvent,
|
|
||||||
);
|
|
||||||
if (ignoreUpSelected) {
|
|
||||||
// multi select mode has add selected, should return
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const checkSelect = (e: MouseEvent) => {
|
if (isLeftButton && !isRootNode(node)) {
|
||||||
doc.removeEventListener('mouseup', checkSelect, true);
|
let nodes: Node[] = [node];
|
||||||
if (!isShaken(downEvent, e)) {
|
let ignoreUpSelected = false;
|
||||||
// const node = hasConditionFlow(target) ? target.conditionFlow : target;
|
if (isMulti) {
|
||||||
const node = nodeInst.node!;
|
// multi select mode, directily add
|
||||||
const id = node.id;
|
if (!selection.has(node.id)) {
|
||||||
designer.activeTracker.track(node);
|
designer.activeTracker.track(node);
|
||||||
if (isMulti && selection.has(id)) {
|
selection.add(node.id);
|
||||||
selection.remove(id);
|
ignoreUpSelected = true;
|
||||||
|
}
|
||||||
|
selection.remove(this.document.rootNode.id);
|
||||||
|
// 获得顶层 nodes
|
||||||
|
nodes = selection.getTopNodes();
|
||||||
|
} else if (selection.containsNode(node, true)) {
|
||||||
|
nodes = selection.getTopNodes();
|
||||||
} else {
|
} else {
|
||||||
selection.select(id);
|
// will clear current selection & select dragment in dragstart
|
||||||
|
}
|
||||||
|
designer.dragon.boost(
|
||||||
|
{
|
||||||
|
type: DragObjectType.Node,
|
||||||
|
nodes,
|
||||||
|
},
|
||||||
|
downEvent,
|
||||||
|
);
|
||||||
|
if (ignoreUpSelected) {
|
||||||
|
// multi select mode has add selected, should return
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
|
||||||
doc.addEventListener('mouseup', checkSelect, true);
|
doc.addEventListener('mouseup', checkSelect, true);
|
||||||
});
|
},
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
|
||||||
|
doc.addEventListener(
|
||||||
|
'click',
|
||||||
|
e => {
|
||||||
|
// stop response document click event
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
// todo: catch link redirect
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
|
||||||
// cause edit
|
// cause edit
|
||||||
doc.addEventListener('dblclick', (e: MouseEvent) => {
|
doc.addEventListener(
|
||||||
// TODO:
|
'dblclick',
|
||||||
});
|
(e: MouseEvent) => {
|
||||||
|
// stop response document dblclick event
|
||||||
|
e.stopPropagation();
|
||||||
|
e.preventDefault();
|
||||||
|
// todo: quick editing
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private disableHovering?: () => void;
|
private disableHovering?: () => void;
|
||||||
@ -290,8 +329,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();
|
||||||
};
|
};
|
||||||
@ -337,15 +374,35 @@ export class SimulatorHost implements ISimulator<SimulatorProps> {
|
|||||||
/**
|
/**
|
||||||
* @see ISimulator
|
* @see ISimulator
|
||||||
*/
|
*/
|
||||||
describeComponent(component: Component): ComponentDescriptionSpec {
|
generateComponentMetadata(componentName: string): ComponentMetadata {
|
||||||
throw new Error('Method not implemented.');
|
// if html tags
|
||||||
|
if (isHTMLTag(componentName)) {
|
||||||
|
return {
|
||||||
|
componentName,
|
||||||
|
// TODO: read builtins html metadata
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const component = this.getComponent(componentName);
|
||||||
|
|
||||||
|
if (component) {
|
||||||
|
parseProps(component as any);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO:
|
||||||
|
// 1. generate builtin div/p/h1/h2
|
||||||
|
// 2. read propTypes
|
||||||
|
return {
|
||||||
|
componentName,
|
||||||
|
props: parseProps(this.getComponent(componentName)),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @see ISimulator
|
* @see ISimulator
|
||||||
*/
|
*/
|
||||||
getComponent(componentName: string): Component | null {
|
getComponent(componentName: string): Component | null {
|
||||||
return null;
|
return this.renderer?.getComponent(componentName) || null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@obx.val private instancesMap = new Map<string, ReactInstance[]>();
|
@obx.val private instancesMap = new Map<string, ReactInstance[]>();
|
||||||
@ -367,7 +424,9 @@ export class SimulatorHost implements ISimulator<SimulatorProps> {
|
|||||||
/**
|
/**
|
||||||
* @see ISimulator
|
* @see ISimulator
|
||||||
*/
|
*/
|
||||||
getComponentInstanceId(instance: ReactInstance) {}
|
getComponentInstanceId(instance: ReactInstance) {
|
||||||
|
throw new Error('Method not implemented.');
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @see ISimulator
|
* @see ISimulator
|
||||||
@ -408,18 +467,28 @@ export class SimulatorHost implements ISimulator<SimulatorProps> {
|
|||||||
let last: { x: number; y: number; r: number; b: number } | undefined;
|
let last: { x: number; y: number; r: number; b: number } | undefined;
|
||||||
let computed = false;
|
let computed = false;
|
||||||
const elems = elements.slice();
|
const elems = elements.slice();
|
||||||
|
const commonParent: Element | null = null;
|
||||||
while (true) {
|
while (true) {
|
||||||
if (!rects || rects.length < 1) {
|
if (!rects || rects.length < 1) {
|
||||||
const elem = elems.pop();
|
const elem = elems.pop();
|
||||||
if (!elem) {
|
if (!elem) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
/*
|
||||||
|
if (!commonParent) {
|
||||||
|
commonParent = elem.parentElement;
|
||||||
|
} else if (elem.parentElement !== commonParent) {
|
||||||
|
continue;
|
||||||
|
}*/
|
||||||
rects = renderer.getClientRects(elem);
|
rects = renderer.getClientRects(elem);
|
||||||
}
|
}
|
||||||
const rect = rects.pop();
|
const rect = rects.pop();
|
||||||
if (!rect) {
|
if (!rect) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
if (rect.width === 0 && rect.height === 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
if (!last) {
|
if (!last) {
|
||||||
last = {
|
last = {
|
||||||
x: rect.left,
|
x: rect.left,
|
||||||
@ -495,7 +564,7 @@ export class SimulatorHost implements ISimulator<SimulatorProps> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const opt: any = {};
|
const opt: any = {};
|
||||||
let scroll = false;
|
const scroll = false;
|
||||||
|
|
||||||
if (detail) {
|
if (detail) {
|
||||||
// TODO:
|
// TODO:
|
||||||
@ -567,7 +636,7 @@ export class SimulatorHost implements ISimulator<SimulatorProps> {
|
|||||||
this.renderer?.clearState();
|
this.renderer?.clearState();
|
||||||
}
|
}
|
||||||
|
|
||||||
private _sensorAvailable: boolean = true;
|
private _sensorAvailable = true;
|
||||||
/**
|
/**
|
||||||
* @see ISensor
|
* @see ISensor
|
||||||
*/
|
*/
|
||||||
@ -615,7 +684,7 @@ export class SimulatorHost implements ISimulator<SimulatorProps> {
|
|||||||
return e.globalY >= rect.top && e.globalY <= rect.bottom && e.globalX >= rect.left && e.globalX <= rect.right;
|
return e.globalY >= rect.top && e.globalY <= rect.bottom && e.globalX >= rect.left && e.globalX <= rect.right;
|
||||||
}
|
}
|
||||||
|
|
||||||
private sensing: boolean = false;
|
private sensing = false;
|
||||||
/**
|
/**
|
||||||
* @see ISensor
|
* @see ISensor
|
||||||
*/
|
*/
|
||||||
@ -633,7 +702,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;
|
||||||
}
|
}
|
||||||
@ -643,8 +711,10 @@ export class SimulatorHost implements ISimulator<SimulatorProps> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const target = dropTarget;
|
const target = dropTarget;
|
||||||
const targetInstance = e.targetInstance as ReactInstance;
|
|
||||||
|
|
||||||
|
// FIXME: e.target is #document, etc., does not has e.targetInstance
|
||||||
|
|
||||||
|
const targetInstance = e.targetInstance as ReactInstance;
|
||||||
const parentInstance = this.getClosestNodeInstance(targetInstance, target.id);
|
const parentInstance = this.getClosestNodeInstance(targetInstance, target.id);
|
||||||
const edge = this.computeComponentInstanceRect(parentInstance?.instance as any);
|
const edge = this.computeComponentInstanceRect(parentInstance?.instance as any);
|
||||||
|
|
||||||
@ -677,14 +747,12 @@ export class SimulatorHost implements ISimulator<SimulatorProps> {
|
|||||||
let maxBottom = null;
|
let maxBottom = null;
|
||||||
|
|
||||||
for (let i = 0, l = children.size; i < l; i++) {
|
for (let i = 0, l = children.size; i < l; i++) {
|
||||||
let node = children.get(i)!;
|
const node = children.get(i)!;
|
||||||
let index = i;
|
const index = i;
|
||||||
const instances = this.getComponentInstances(node);
|
const instances = this.getComponentInstances(node);
|
||||||
const inst = instances
|
const inst = instances
|
||||||
? instances.length > 1
|
? instances.length > 1
|
||||||
? instances.find(inst => {
|
? instances.find(inst => this.getClosestNodeInstance(inst, target.id)?.instance === targetInstance)
|
||||||
return this.getClosestNodeInstance(inst, target.id)?.instance === targetInstance;
|
|
||||||
})
|
|
||||||
: instances[0]
|
: instances[0]
|
||||||
: null;
|
: null;
|
||||||
const rect = inst ? this.computeComponentInstanceRect(inst) : null;
|
const rect = inst ? this.computeComponentInstanceRect(inst) : null;
|
||||||
@ -808,8 +876,8 @@ export class SimulatorHost implements ISimulator<SimulatorProps> {
|
|||||||
} else {
|
} else {
|
||||||
container = container.parent;
|
container = container.parent;
|
||||||
}
|
}
|
||||||
}
|
} else if (isNode(res)) {
|
||||||
/* else if (res === AT_CHILD) {
|
/* else if (res === AT_CHILD) {
|
||||||
if (!upward) {
|
if (!upward) {
|
||||||
upward = container.parent;
|
upward = container.parent;
|
||||||
}
|
}
|
||||||
@ -819,8 +887,6 @@ export class SimulatorHost implements ISimulator<SimulatorProps> {
|
|||||||
upward = null;
|
upward = null;
|
||||||
}
|
}
|
||||||
}*/
|
}*/
|
||||||
else if (isNode(res)) {
|
|
||||||
console.info('res', res);
|
|
||||||
container = res;
|
container = res;
|
||||||
upward = null;
|
upward = null;
|
||||||
}
|
}
|
||||||
@ -834,7 +900,7 @@ export class SimulatorHost implements ISimulator<SimulatorProps> {
|
|||||||
return this.checkDropTarget(container, dragObject as any);
|
return this.checkDropTarget(container, dragObject as any);
|
||||||
}
|
}
|
||||||
|
|
||||||
const config = container.componentConfig;
|
const config = container.componentMeta;
|
||||||
|
|
||||||
if (!config.isContainer) {
|
if (!config.isContainer) {
|
||||||
return false;
|
return false;
|
||||||
@ -902,7 +968,7 @@ export class SimulatorHost implements ISimulator<SimulatorProps> {
|
|||||||
if (isDragNodeDataObject(dragObject)) {
|
if (isDragNodeDataObject(dragObject)) {
|
||||||
items = Array.isArray(dragObject.data) ? dragObject.data : [dragObject.data];
|
items = Array.isArray(dragObject.data) ? dragObject.data : [dragObject.data];
|
||||||
} else {
|
} else {
|
||||||
items = dragObject.nodes
|
items = dragObject.nodes;
|
||||||
}
|
}
|
||||||
return items.every(item => this.checkNestingDown(dropTarget, item));
|
return items.every(item => this.checkNestingDown(dropTarget, item));
|
||||||
}
|
}
|
||||||
@ -912,14 +978,14 @@ export class SimulatorHost implements ISimulator<SimulatorProps> {
|
|||||||
if (isDragNodeDataObject(dragObject)) {
|
if (isDragNodeDataObject(dragObject)) {
|
||||||
items = Array.isArray(dragObject.data) ? dragObject.data : [dragObject.data];
|
items = Array.isArray(dragObject.data) ? dragObject.data : [dragObject.data];
|
||||||
} else {
|
} else {
|
||||||
items = dragObject.nodes
|
items = dragObject.nodes;
|
||||||
}
|
}
|
||||||
return items.every(item => this.checkNestingUp(dropTarget, item));
|
return items.every(item => this.checkNestingUp(dropTarget, item));
|
||||||
}
|
}
|
||||||
|
|
||||||
checkNestingUp(parent: NodeParent, target: NodeSchema | Node): boolean {
|
checkNestingUp(parent: NodeParent, target: NodeSchema | Node): boolean {
|
||||||
if (isNode(target) || isNodeSchema(target)) {
|
if (isNode(target) || isNodeSchema(target)) {
|
||||||
const config = isNode(target) ? target.componentConfig : this.designer.getComponentConfig(target.componentName);
|
const config = isNode(target) ? target.componentMeta : this.document.getComponentMeta(target.componentName);
|
||||||
if (config) {
|
if (config) {
|
||||||
return config.checkNestingUp(target, parent);
|
return config.checkNestingUp(target, parent);
|
||||||
}
|
}
|
||||||
@ -929,18 +995,22 @@ export class SimulatorHost implements ISimulator<SimulatorProps> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
checkNestingDown(parent: NodeParent, target: NodeSchema | Node): boolean {
|
checkNestingDown(parent: NodeParent, target: NodeSchema | Node): boolean {
|
||||||
const config = parent.componentConfig;
|
const config = parent.componentMeta;
|
||||||
return config.checkNestingDown(parent, target) && this.checkNestingUp(parent, target);
|
return config.checkNestingDown(parent, target) && this.checkNestingUp(parent, target);
|
||||||
}
|
}
|
||||||
// #endregion
|
// #endregion
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isHTMLTag(name: string) {
|
||||||
|
return /^[a-z]\w*$/.test(name);
|
||||||
|
}
|
||||||
|
|
||||||
function isPointInRect(point: CanvasPoint, rect: Rect) {
|
function isPointInRect(point: CanvasPoint, rect: Rect) {
|
||||||
return (
|
return (
|
||||||
point.canvasY >= rect.top &&
|
point.canvasY >= rect.top &&
|
||||||
point.canvasY <= rect.bottom &&
|
point.canvasY <= rect.bottom &&
|
||||||
(point.canvasX >= rect.left && point.canvasX <= rect.right)
|
point.canvasX >= rect.left &&
|
||||||
|
point.canvasX <= rect.right
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -25,7 +25,7 @@ export default class Viewport implements IViewport {
|
|||||||
|
|
||||||
private viewportElement?: Element;
|
private viewportElement?: Element;
|
||||||
mount(viewportElement: Element | null) {
|
mount(viewportElement: Element | null) {
|
||||||
if (!viewportElement) {
|
if (!viewportElement || this.viewportElement === viewportElement) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.viewportElement = viewportElement;
|
this.viewportElement = viewportElement;
|
||||||
@ -54,7 +54,7 @@ export default class Viewport implements IViewport {
|
|||||||
/**
|
/**
|
||||||
* 缩放比例
|
* 缩放比例
|
||||||
*/
|
*/
|
||||||
get scale(): number {
|
@computed get scale(): number {
|
||||||
if (!this.rect || this.contentWidth === AutoFit) {
|
if (!this.rect || this.contentWidth === AutoFit) {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
@ -63,14 +63,14 @@ export default class Viewport implements IViewport {
|
|||||||
|
|
||||||
@obx.ref private _contentWidth: number | AutoFit = AutoFit;
|
@obx.ref private _contentWidth: number | AutoFit = AutoFit;
|
||||||
|
|
||||||
get contentHeight(): number | AutoFit {
|
@computed get contentHeight(): number | AutoFit {
|
||||||
if (!this.rect || this.scale === 1) {
|
if (!this.rect || this.scale === 1) {
|
||||||
return AutoFit;
|
return AutoFit;
|
||||||
}
|
}
|
||||||
return this.height / this.scale;
|
return this.height / this.scale;
|
||||||
}
|
}
|
||||||
|
|
||||||
get contentWidth(): number | AutoFit {
|
@computed get contentWidth(): number | AutoFit {
|
||||||
if (!this.rect || (this._contentWidth !== AutoFit && this._contentWidth <= this.width)) {
|
if (!this.rect || (this._contentWidth !== AutoFit && this._contentWidth <= this.width)) {
|
||||||
return AutoFit;
|
return AutoFit;
|
||||||
}
|
}
|
||||||
@ -98,7 +98,7 @@ export default class Viewport implements IViewport {
|
|||||||
return this._scrollTarget;
|
return this._scrollTarget;
|
||||||
}
|
}
|
||||||
|
|
||||||
@obx private _scrolling: boolean = false;
|
@obx private _scrolling = false;
|
||||||
get scrolling(): boolean {
|
get scrolling(): boolean {
|
||||||
return this._scrolling;
|
return this._scrolling;
|
||||||
}
|
}
|
||||||
@ -120,6 +120,7 @@ export default class Viewport implements IViewport {
|
|||||||
this._scrolling = false;
|
this._scrolling = false;
|
||||||
}, 80);
|
}, 80);
|
||||||
});
|
});
|
||||||
|
target.addEventListener('resize', () => this.touch());
|
||||||
this._scrollTarget = scrollTarget;
|
this._scrollTarget = scrollTarget;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import LowCodeRenderer from '@ali/iceluna-sdk';
|
import LowCodeRenderer from '@ali/lowcode-renderer';
|
||||||
import { ReactInstance, Fragment, Component } from 'react';
|
import { ReactInstance, Fragment, Component } from 'react';
|
||||||
import { observer } from '@recore/core-obx';
|
import { observer } from '@recore/obx-react';
|
||||||
import { SimulatorRenderer } from './renderer';
|
import { SimulatorRenderer } from './renderer';
|
||||||
import './renderer.less';
|
import './renderer.less';
|
||||||
|
|
||||||
@ -16,7 +16,7 @@ export default class SimulatorRendererView extends Component<{ renderer: Simulat
|
|||||||
}
|
}
|
||||||
|
|
||||||
@observer
|
@observer
|
||||||
class Layout extends Component<{ renderer: SimulatorRenderer; }> {
|
class Layout extends Component<{ renderer: SimulatorRenderer }> {
|
||||||
shouldComponentUpdate() {
|
shouldComponentUpdate() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -40,11 +40,11 @@ class Renderer extends Component<{ renderer: SimulatorRenderer }> {
|
|||||||
}
|
}
|
||||||
render() {
|
render() {
|
||||||
const { renderer } = this.props;
|
const { renderer } = this.props;
|
||||||
const { components, schemas } = LowCodeRenderer.others
|
// const { components, schemas } = LowCodeRenderer.others;
|
||||||
return (
|
return (
|
||||||
<LowCodeRenderer
|
<LowCodeRenderer
|
||||||
schema={renderer.schema}
|
schema={renderer.schema}
|
||||||
components={components /*renderer.components*/}
|
components={renderer.components}
|
||||||
appHelper={renderer.context}
|
appHelper={renderer.context}
|
||||||
// context={renderer.context}
|
// context={renderer.context}
|
||||||
designMode={renderer.designMode}
|
designMode={renderer.designMode}
|
||||||
@ -55,7 +55,7 @@ class Renderer extends Component<{ renderer: SimulatorRenderer }> {
|
|||||||
renderer.mountInstance(schema.id, ref);
|
renderer.mountInstance(schema.id, ref);
|
||||||
}}
|
}}
|
||||||
//onCompGetCtx={(schema: any, ctx: object) => {
|
//onCompGetCtx={(schema: any, ctx: object) => {
|
||||||
// renderer.mountContext(schema.id, ctx);
|
// renderer.mountContext(schema.id, ctx);
|
||||||
//}}
|
//}}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -7,9 +7,8 @@ import { RootSchema, NpmInfo } from '../../../designer/schema';
|
|||||||
import { getClientRects } from '../../../utils/get-client-rects';
|
import { getClientRects } from '../../../utils/get-client-rects';
|
||||||
import { Asset } from '../utils/asset';
|
import { Asset } from '../utils/asset';
|
||||||
import loader from '../utils/loader';
|
import loader from '../utils/loader';
|
||||||
import { ComponentDescriptionSpec } from '../../../designer/component-config';
|
|
||||||
import { reactFindDOMNodes, FIBER_KEY } from '../utils/react-find-dom-nodes';
|
import { reactFindDOMNodes, FIBER_KEY } from '../utils/react-find-dom-nodes';
|
||||||
import { isESModule } from '../../../utils/is-es-module';
|
import { isESModule } from '../../../../../utils/is-es-module';
|
||||||
import { NodeInstance } from '../../../designer/simulator';
|
import { NodeInstance } from '../../../designer/simulator';
|
||||||
import { isElement } from '../../../utils/is-element';
|
import { isElement } from '../../../utils/is-element';
|
||||||
import cursor from '../../../designer/helper/cursor';
|
import cursor from '../../../designer/helper/cursor';
|
||||||
@ -28,8 +27,12 @@ export class SimulatorRenderer {
|
|||||||
// sync schema
|
// sync schema
|
||||||
this._schema = host.document.schema;
|
this._schema = host.document.schema;
|
||||||
|
|
||||||
this._componentsMap = host.designer.componentsMap;
|
// todo: split with others, not all should recompute
|
||||||
this.buildComponents();
|
if (this._libraryMap !== host.libraryMap || this._componentsMap !== host.designer.componentsMap) {
|
||||||
|
this._libraryMap = host.libraryMap || {};
|
||||||
|
this._componentsMap = host.designer.componentsMap;
|
||||||
|
this.buildComponents();
|
||||||
|
}
|
||||||
|
|
||||||
// sync designMode
|
// sync designMode
|
||||||
|
|
||||||
@ -39,13 +42,13 @@ export class SimulatorRenderer {
|
|||||||
|
|
||||||
// sync device
|
// sync device
|
||||||
});
|
});
|
||||||
host.componentsConsumer.consume(async (componentsAsset) => {
|
host.componentsConsumer.consume(async componentsAsset => {
|
||||||
if (componentsAsset) {
|
if (componentsAsset) {
|
||||||
await this.load(componentsAsset);
|
await this.load(componentsAsset);
|
||||||
this.buildComponents();
|
this.buildComponents();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
host.injectionConsumer.consume((data) => {
|
host.injectionConsumer.consume(data => {
|
||||||
// sync utils, i18n, contants,... config
|
// sync utils, i18n, contants,... config
|
||||||
this._appContext = {
|
this._appContext = {
|
||||||
utils: {},
|
utils: {},
|
||||||
@ -65,10 +68,11 @@ export class SimulatorRenderer {
|
|||||||
@computed get schema(): any {
|
@computed get schema(): any {
|
||||||
return this._schema;
|
return this._schema;
|
||||||
}
|
}
|
||||||
|
private _libraryMap: { [key: string]: string } = {};
|
||||||
private buildComponents() {
|
private buildComponents() {
|
||||||
this._components = buildComponents(this._componentsMap);
|
this._components = buildComponents(this._libraryMap, this._componentsMap);
|
||||||
}
|
}
|
||||||
@obx.ref private _components = {};
|
@obx.ref private _components: any = {};
|
||||||
@computed get components(): object {
|
@computed get components(): object {
|
||||||
// 根据 device 选择不同组件,进行响应式
|
// 根据 device 选择不同组件,进行响应式
|
||||||
// 更好的做法是,根据 device 选择加载不同的组件资源,甚至是 simulatorUrl
|
// 更好的做法是,根据 device 选择加载不同的组件资源,甚至是 simulatorUrl
|
||||||
@ -142,7 +146,7 @@ export class SimulatorRenderer {
|
|||||||
origUnmount = origUnmount.origUnmount;
|
origUnmount = origUnmount.origUnmount;
|
||||||
}
|
}
|
||||||
// hack! delete instance from map
|
// hack! delete instance from map
|
||||||
const newUnmount = function (this: any) {
|
const newUnmount = function(this: any) {
|
||||||
unmountIntance(id, instance);
|
unmountIntance(id, instance);
|
||||||
origUnmount && origUnmount.call(this);
|
origUnmount && origUnmount.call(this);
|
||||||
};
|
};
|
||||||
@ -175,6 +179,27 @@ export class SimulatorRenderer {
|
|||||||
this.ctxMap.set(id, ctx);
|
this.ctxMap.set(id, ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getComponent(componentName: string) {
|
||||||
|
const paths = componentName.split('.');
|
||||||
|
const subs: string[] = [];
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
const component = this._components[componentName];
|
||||||
|
if (component) {
|
||||||
|
return getSubComponent(component, subs);
|
||||||
|
}
|
||||||
|
|
||||||
|
const sub = paths.pop();
|
||||||
|
if (!sub) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
subs.unshift(sub);
|
||||||
|
componentName = paths.join('.');
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
getComponentInstances(id: string): ReactInstance[] | null {
|
getComponentInstances(id: string): ReactInstance[] | null {
|
||||||
return this.instancesMap.get(id) || null;
|
return this.instancesMap.get(id) || null;
|
||||||
}
|
}
|
||||||
@ -204,7 +229,7 @@ export class SimulatorRenderer {
|
|||||||
cursor.release();
|
cursor.release();
|
||||||
}
|
}
|
||||||
|
|
||||||
private _running: boolean = false;
|
private _running = false;
|
||||||
run() {
|
run() {
|
||||||
if (this._running) {
|
if (this._running) {
|
||||||
return;
|
return;
|
||||||
@ -260,7 +285,7 @@ function getSubComponent(library: any, paths: string[]) {
|
|||||||
return component;
|
return component;
|
||||||
}
|
}
|
||||||
|
|
||||||
function findComponent(componentName: string, npm?: NpmInfo) {
|
function findComponent(libraryMap: LibraryMap, componentName: string, npm?: NpmInfo) {
|
||||||
if (!npm) {
|
if (!npm) {
|
||||||
return accessLibrary(componentName);
|
return accessLibrary(componentName);
|
||||||
}
|
}
|
||||||
@ -270,26 +295,33 @@ function findComponent(componentName: string, npm?: NpmInfo) {
|
|||||||
// export { exportName as componentName } from package
|
// export { exportName as componentName } from package
|
||||||
// if exportName == null exportName === componentName;
|
// if exportName == null exportName === componentName;
|
||||||
// const componentName = exportName.subName, if exportName empty subName donot use
|
// const componentName = exportName.subName, if exportName empty subName donot use
|
||||||
const libraryName = npm.exportName || npm.componentName || componentName;
|
const exportName = npm.exportName || npm.componentName || componentName;
|
||||||
|
const libraryName = libraryMap[npm.package] || exportName;
|
||||||
const library = accessLibrary(libraryName);
|
const library = accessLibrary(libraryName);
|
||||||
const paths = npm.exportName && npm.subName ? npm.subName.split('.') : [];
|
const paths = npm.exportName && npm.subName ? npm.subName.split('.') : [];
|
||||||
if (npm.destructuring) {
|
if (npm.destructuring) {
|
||||||
paths.unshift(libraryName);
|
paths.unshift(exportName);
|
||||||
} else if (isESModule(library)) {
|
} else if (isESModule(library)) {
|
||||||
paths.unshift('default');
|
paths.unshift('default');
|
||||||
}
|
}
|
||||||
return getSubComponent(library, paths);
|
return getSubComponent(library, paths);
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildComponents(componentsMap: { [componentName: string]: ComponentDescriptionSpec }) {
|
export interface LibraryMap {
|
||||||
|
[key: string]: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildComponents(libraryMap: LibraryMap, componentsMap: { [componentName: string]: NpmInfo }) {
|
||||||
const components: any = {};
|
const components: any = {};
|
||||||
Object.keys(componentsMap).forEach(componentName => {
|
Object.keys(componentsMap).forEach(componentName => {
|
||||||
components[componentName] = findComponent(componentName, componentsMap[componentName].npm);
|
const component = findComponent(libraryMap, componentName, componentsMap[componentName]);
|
||||||
|
if (component) {
|
||||||
|
components[componentName] = component;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
return components;
|
return components;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
let REACT_KEY = '';
|
let REACT_KEY = '';
|
||||||
function cacheReactKey(el: Element): Element {
|
function cacheReactKey(el: Element): Element {
|
||||||
if (REACT_KEY !== '') {
|
if (REACT_KEY !== '') {
|
||||||
|
|||||||
@ -6,11 +6,11 @@ export interface AssetItem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export enum AssetLevel {
|
export enum AssetLevel {
|
||||||
// 基础依赖库
|
// 环境依赖库 比如 react, react-dom
|
||||||
BaseDepends = 1,
|
Environment = 1,
|
||||||
// 基础组件库
|
// 基础类库,比如 lodash deep fusion antd
|
||||||
BaseComponents = 2,
|
Library = 2,
|
||||||
// 主题包
|
// 主题
|
||||||
Theme = 3,
|
Theme = 3,
|
||||||
// 运行时
|
// 运行时
|
||||||
Runtime = 4,
|
Runtime = 4,
|
||||||
@ -21,8 +21,8 @@ export enum AssetLevel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const AssetLevels = [
|
export const AssetLevels = [
|
||||||
AssetLevel.BaseDepends,
|
AssetLevel.Environment,
|
||||||
AssetLevel.BaseComponents,
|
AssetLevel.Library,
|
||||||
AssetLevel.Theme,
|
AssetLevel.Theme,
|
||||||
AssetLevel.Runtime,
|
AssetLevel.Runtime,
|
||||||
AssetLevel.Components,
|
AssetLevel.Components,
|
||||||
|
|||||||
@ -1,10 +1,20 @@
|
|||||||
import { load, evaluate } from './script';
|
import { load, evaluate } from './script';
|
||||||
import StylePoint from './style';
|
import StylePoint from './style';
|
||||||
import { Asset, AssetLevel, AssetLevels, AssetType, AssetList, isAssetBundle, isAssetItem, assetItem, AssetItem } from './asset';
|
import {
|
||||||
|
Asset,
|
||||||
|
AssetLevel,
|
||||||
|
AssetLevels,
|
||||||
|
AssetType,
|
||||||
|
AssetList,
|
||||||
|
isAssetBundle,
|
||||||
|
isAssetItem,
|
||||||
|
assetItem,
|
||||||
|
AssetItem,
|
||||||
|
} from './asset';
|
||||||
import { isCSSUrl } from '../../../utils/is-css-url';
|
import { isCSSUrl } from '../../../utils/is-css-url';
|
||||||
|
|
||||||
function parseAssetList(scripts: any, styles: any, assets: AssetList, level?: AssetLevel) {
|
function parseAssetList(scripts: any, styles: any, assets: AssetList, level?: AssetLevel) {
|
||||||
for (let asset of assets) {
|
for (const asset of assets) {
|
||||||
parseAsset(scripts, styles, asset, level);
|
parseAsset(scripts, styles, asset, level);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -36,7 +46,7 @@ function parseAsset(scripts: any, styles: any, asset: Asset | undefined | null,
|
|||||||
let lv = asset.level || level;
|
let lv = asset.level || level;
|
||||||
|
|
||||||
if (!lv || AssetLevel[lv] == null) {
|
if (!lv || AssetLevel[lv] == null) {
|
||||||
lv = AssetLevel.App
|
lv = AssetLevel.App;
|
||||||
}
|
}
|
||||||
|
|
||||||
asset.level = lv;
|
asset.level = lv;
|
||||||
@ -51,19 +61,19 @@ export class AssetLoader {
|
|||||||
async load(asset: Asset) {
|
async load(asset: Asset) {
|
||||||
const styles: any = {};
|
const styles: any = {};
|
||||||
const scripts: any = {};
|
const scripts: any = {};
|
||||||
AssetLevels.forEach((lv) => {
|
AssetLevels.forEach(lv => {
|
||||||
styles[lv] = [];
|
styles[lv] = [];
|
||||||
scripts[lv] = [];
|
scripts[lv] = [];
|
||||||
});
|
});
|
||||||
parseAsset(scripts, styles, asset);
|
parseAsset(scripts, styles, asset);
|
||||||
const styleQueue: AssetItem[] = styles[AssetLevel.BaseDepends].concat(
|
const styleQueue: AssetItem[] = styles[AssetLevel.Environment].concat(
|
||||||
styles[AssetLevel.BaseComponents],
|
styles[AssetLevel.Library],
|
||||||
styles[AssetLevel.Theme],
|
styles[AssetLevel.Theme],
|
||||||
styles[AssetLevel.Runtime],
|
styles[AssetLevel.Runtime],
|
||||||
styles[AssetLevel.App],
|
styles[AssetLevel.App],
|
||||||
);
|
);
|
||||||
const scriptQueue: AssetItem[] = scripts[AssetLevel.BaseDepends].concat(
|
const scriptQueue: AssetItem[] = scripts[AssetLevel.Environment].concat(
|
||||||
scripts[AssetLevel.BaseComponents],
|
scripts[AssetLevel.Library],
|
||||||
scripts[AssetLevel.Theme],
|
scripts[AssetLevel.Theme],
|
||||||
scripts[AssetLevel.Runtime],
|
scripts[AssetLevel.Runtime],
|
||||||
scripts[AssetLevel.App],
|
scripts[AssetLevel.App],
|
||||||
@ -71,9 +81,7 @@ export class AssetLoader {
|
|||||||
await Promise.all(
|
await Promise.all(
|
||||||
styleQueue.map(({ content, level, type, id }) => this.loadStyle(content, level!, type === AssetType.CSSUrl, id)),
|
styleQueue.map(({ content, level, type, id }) => this.loadStyle(content, level!, type === AssetType.CSSUrl, id)),
|
||||||
);
|
);
|
||||||
await Promise.all(
|
await Promise.all(scriptQueue.map(({ content, type }) => this.loadScript(content, type === AssetType.JSUrl)));
|
||||||
scriptQueue.map(({ content, type }) => this.loadScript(content, type === AssetType.JSUrl)),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private stylePoints = new Map<string, StylePoint>();
|
private stylePoints = new Map<string, StylePoint>();
|
||||||
|
|||||||
200
packages/designer/src/builtins/simulator/utils/parse-props.ts
Normal file
200
packages/designer/src/builtins/simulator/utils/parse-props.ts
Normal file
@ -0,0 +1,200 @@
|
|||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { isValidElement } from 'react';
|
||||||
|
import { isElement } from '../../../utils/is-element';
|
||||||
|
import { PropType, PropConfig } from '../../../designer/prop-config';
|
||||||
|
|
||||||
|
export const primitiveTypes = [
|
||||||
|
'string',
|
||||||
|
'number',
|
||||||
|
'array',
|
||||||
|
'bool',
|
||||||
|
'func',
|
||||||
|
'object',
|
||||||
|
'node',
|
||||||
|
'element',
|
||||||
|
'symbol',
|
||||||
|
'any',
|
||||||
|
];
|
||||||
|
|
||||||
|
function makeRequired(propType: any, lowcodeType: string | object) {
|
||||||
|
function lowcodeCheckTypeIsRequired(...rest: any[]) {
|
||||||
|
return propType.isRequired(...rest);
|
||||||
|
}
|
||||||
|
if (typeof lowcodeType === 'string') {
|
||||||
|
lowcodeType = {
|
||||||
|
type: lowcodeType,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
lowcodeCheckTypeIsRequired.lowcodeType = {
|
||||||
|
...lowcodeType,
|
||||||
|
isRequired: true,
|
||||||
|
};
|
||||||
|
return lowcodeCheckTypeIsRequired;
|
||||||
|
}
|
||||||
|
|
||||||
|
function define(propType: any = PropTypes.any, lowcodeType: string | object = {}) {
|
||||||
|
if (!propType._inner && propType.name !== 'lowcodeCheckType') {
|
||||||
|
propType.lowcodeType = lowcodeType;
|
||||||
|
}
|
||||||
|
function lowcodeCheckType(...rest: any[]) {
|
||||||
|
return propType(...rest);
|
||||||
|
}
|
||||||
|
lowcodeCheckType.lowcodeType = lowcodeType;
|
||||||
|
lowcodeCheckType.isRequired = makeRequired(propType, lowcodeType);
|
||||||
|
return lowcodeCheckType;
|
||||||
|
}
|
||||||
|
|
||||||
|
const LowcodeTypes: any = {
|
||||||
|
...PropTypes,
|
||||||
|
define,
|
||||||
|
};
|
||||||
|
|
||||||
|
(window as any).PropTypes = LowcodeTypes;
|
||||||
|
(window as any).React.PropTypes = LowcodeTypes;
|
||||||
|
|
||||||
|
// override primitive type chechers
|
||||||
|
primitiveTypes.forEach(type => {
|
||||||
|
const propType = (PropTypes as any)[type];
|
||||||
|
if (!propType) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
propType._inner = true;
|
||||||
|
LowcodeTypes[type] = define(propType, type);
|
||||||
|
});
|
||||||
|
|
||||||
|
// You can ensure that your prop is limited to specific values by treating
|
||||||
|
// it as an enum.
|
||||||
|
LowcodeTypes.oneOf = (list: any[]) => {
|
||||||
|
return define(PropTypes.oneOf(list), {
|
||||||
|
type: 'oneOf',
|
||||||
|
value: list,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// An array of a certain type
|
||||||
|
LowcodeTypes.arrayOf = (type: any) => {
|
||||||
|
return define(PropTypes.arrayOf(type), {
|
||||||
|
type: 'arrayOf',
|
||||||
|
value: type.lowcodeType || 'any',
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// An object with property values of a certain type
|
||||||
|
LowcodeTypes.objectOf = (type: any) => {
|
||||||
|
return define(PropTypes.objectOf(type), {
|
||||||
|
type: 'objectOf',
|
||||||
|
value: type.lowcodeType || 'any',
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// An object that could be one of many types
|
||||||
|
LowcodeTypes.oneOfType = (types: any[]) => {
|
||||||
|
const itemTypes = types.map(type => type.lowcodeType || 'any');
|
||||||
|
return define(PropTypes.oneOfType(types), {
|
||||||
|
type: 'oneOfType',
|
||||||
|
value: itemTypes,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// An object with warnings on extra properties
|
||||||
|
LowcodeTypes.exact = (typesMap: any) => {
|
||||||
|
const configs = Object.keys(typesMap).map(key => {
|
||||||
|
return {
|
||||||
|
name: key,
|
||||||
|
propType: typesMap[key].lowcodeType || 'any',
|
||||||
|
};
|
||||||
|
});
|
||||||
|
return define(PropTypes.exact(typesMap), {
|
||||||
|
type: 'exact',
|
||||||
|
value: configs,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// An object taking on a particular shape
|
||||||
|
LowcodeTypes.shape = (typesMap: any) => {
|
||||||
|
const configs = Object.keys(typesMap).map(key => {
|
||||||
|
return {
|
||||||
|
name: key,
|
||||||
|
propType: typesMap[key].lowcodeType || 'any',
|
||||||
|
};
|
||||||
|
});
|
||||||
|
return define(PropTypes.shape(typesMap), {
|
||||||
|
type: 'shape',
|
||||||
|
value: configs,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const BasicTypes = ['string', 'number', 'object'];
|
||||||
|
export function parseProps(component: any): PropConfig[] {
|
||||||
|
if (!component) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
const propTypes = component.propTypes || ({} as any);
|
||||||
|
const defaultProps = component.defaultProps || ({} as any);
|
||||||
|
const result: any = {};
|
||||||
|
if (!propTypes) return [];
|
||||||
|
Object.keys(propTypes).forEach(key => {
|
||||||
|
const propTypeItem = propTypes[key];
|
||||||
|
const defaultValue = defaultProps[key];
|
||||||
|
const lowcodeType = propTypeItem.lowcodeType;
|
||||||
|
if (lowcodeType) {
|
||||||
|
result[key] = {
|
||||||
|
name: key,
|
||||||
|
propType: lowcodeType,
|
||||||
|
};
|
||||||
|
if (defaultValue != null) {
|
||||||
|
result[key].defaultValue = defaultValue;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let i = primitiveTypes.length;
|
||||||
|
while (i-- > 0) {
|
||||||
|
const k = primitiveTypes[i];
|
||||||
|
if ((LowcodeTypes as any)[k] === propTypeItem) {
|
||||||
|
result[key] = {
|
||||||
|
name: key,
|
||||||
|
propType: k,
|
||||||
|
};
|
||||||
|
if (defaultValue != null) {
|
||||||
|
result[key].defaultValue = defaultValue;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result[key] = {
|
||||||
|
name: key,
|
||||||
|
propType: 'any',
|
||||||
|
};
|
||||||
|
if (defaultValue != null) {
|
||||||
|
result[key].defaultValue = defaultValue;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Object.keys(defaultProps).forEach(key => {
|
||||||
|
if (result[key]) return;
|
||||||
|
const defaultValue = defaultProps[key];
|
||||||
|
let type: string = typeof defaultValue;
|
||||||
|
if (type === 'boolean') {
|
||||||
|
type = 'bool';
|
||||||
|
} else if (type === 'function') {
|
||||||
|
type = 'func';
|
||||||
|
} else if (type === 'object' && Array.isArray(defaultValue)) {
|
||||||
|
type = 'array';
|
||||||
|
} else if (defaultValue && isValidElement(defaultValue)) {
|
||||||
|
type = 'node';
|
||||||
|
} else if (defaultValue && isElement(defaultValue)) {
|
||||||
|
type = 'element';
|
||||||
|
} else if (!BasicTypes.includes(type)) {
|
||||||
|
type = 'any';
|
||||||
|
}
|
||||||
|
|
||||||
|
result[key] = {
|
||||||
|
name: key,
|
||||||
|
propType: type || 'any',
|
||||||
|
defaultValue,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
return Object.keys(result).map(key => result[key]);
|
||||||
|
}
|
||||||
@ -1,198 +0,0 @@
|
|||||||
import PropTypes from 'prop-types';
|
|
||||||
|
|
||||||
export const primitiveTypeMaps = {
|
|
||||||
string: {
|
|
||||||
defaultValue: '',
|
|
||||||
display: 'inline',
|
|
||||||
setter: 'TextSetter',
|
|
||||||
},
|
|
||||||
number: {
|
|
||||||
display: 'inline',
|
|
||||||
setter: 'NumberSetter' // extends TextSetter
|
|
||||||
},
|
|
||||||
array: {
|
|
||||||
defaultValue: [],
|
|
||||||
display: 'inline',
|
|
||||||
// itemType: any
|
|
||||||
setter: 'ArraySetter' // extends ExpressionSetter
|
|
||||||
},
|
|
||||||
bool: {
|
|
||||||
defaultValue: false,
|
|
||||||
display: 'inline',
|
|
||||||
setter: 'BoolSetter'
|
|
||||||
},
|
|
||||||
func: {
|
|
||||||
defaultValue: () => {},
|
|
||||||
display: 'inline',
|
|
||||||
setter: 'FunctionSetter' // extends ExpressionSetter
|
|
||||||
},
|
|
||||||
object: {
|
|
||||||
defaultValue: {},
|
|
||||||
display: 'inline',
|
|
||||||
// itemType: any
|
|
||||||
setter: 'ObjectSetter' // extends ExpressionSetter
|
|
||||||
},
|
|
||||||
// Anything that can be rendered: numbers, strings, elements or an array
|
|
||||||
// (or fragment) containing these types.
|
|
||||||
node: {
|
|
||||||
defaultValue: '',
|
|
||||||
display: 'inline',
|
|
||||||
setter: 'FragmentSetter',
|
|
||||||
},
|
|
||||||
// A React element.
|
|
||||||
element: {
|
|
||||||
display: 'inline',
|
|
||||||
setter: 'JSXSetter', // extends ExpressionSetter
|
|
||||||
},
|
|
||||||
symbol: {
|
|
||||||
display: 'inline',
|
|
||||||
setter: 'ExpressionSetter',
|
|
||||||
},
|
|
||||||
any: {
|
|
||||||
display: 'inline',
|
|
||||||
setter: 'ExpressionSetter',
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
function makeRequired(propType, visionType) {
|
|
||||||
function visionCheckTypeIsRequired(...rest) {
|
|
||||||
return propType.isRequired(...rest);
|
|
||||||
}
|
|
||||||
visionCheckTypeIsRequired.visionType = {
|
|
||||||
...visionType,
|
|
||||||
required: true,
|
|
||||||
};
|
|
||||||
return visionCheckTypeIsRequired;
|
|
||||||
}
|
|
||||||
|
|
||||||
function define(propType = PropTypes.any, visionType = {}) {
|
|
||||||
if (!propType._inner && propType.name !== 'visionCheckType') {
|
|
||||||
propType.visionType = visionType;
|
|
||||||
}
|
|
||||||
function visionCheckType(...rest) {
|
|
||||||
return propType(...rest);
|
|
||||||
}
|
|
||||||
visionCheckType.visionType = visionType;
|
|
||||||
visionCheckType.isRequired = makeRequired(propType, visionType);
|
|
||||||
return visionCheckType;
|
|
||||||
}
|
|
||||||
|
|
||||||
const VisionTypes = {
|
|
||||||
...PropTypes,
|
|
||||||
define,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default VisionTypes;
|
|
||||||
|
|
||||||
// override primitive type chechers
|
|
||||||
Object.keys(primitiveTypeMaps).forEach((type) => {
|
|
||||||
const propType = PropTypes[type];
|
|
||||||
if (!propType) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
propType._inner = true;
|
|
||||||
VisionTypes[type] = define(propType, primitiveTypeMaps[type]);
|
|
||||||
});
|
|
||||||
|
|
||||||
// You can ensure that your prop is limited to specific values by treating
|
|
||||||
// it as an enum.
|
|
||||||
VisionTypes.oneOf = (list) => {
|
|
||||||
return define(PropTypes.oneOf(list), {
|
|
||||||
defaultValue: list && list[0],
|
|
||||||
display: 'inline',
|
|
||||||
setter: {
|
|
||||||
type: 'SelectSetter',
|
|
||||||
options: list,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
// An array of a certain type
|
|
||||||
VisionTypes.arrayOf = (type) => {
|
|
||||||
return define(PropTypes.arrayOf(type), {
|
|
||||||
defaultValue: [],
|
|
||||||
display: 'inline',
|
|
||||||
setter: {
|
|
||||||
type: 'ArraySetter', // list
|
|
||||||
itemType: type.visionType || primitiveTypeMaps.any, // addable type
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
// An object with property values of a certain type
|
|
||||||
VisionTypes.objectOf = (type) => {
|
|
||||||
return define(PropTypes.objectOf(type), {
|
|
||||||
defaultValue: {},
|
|
||||||
display: 'inline',
|
|
||||||
setter: {
|
|
||||||
type: 'ObjectSetter', // all itemType
|
|
||||||
itemType: type.visionType || primitiveTypeMaps.any, // addable type
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
// An object that could be one of many types
|
|
||||||
VisionTypes.oneOfType = (types) => {
|
|
||||||
const itemType = types.map(type => type.visionType || primitiveTypeMaps.any);
|
|
||||||
return define(PropTypes.oneOfType(types), {
|
|
||||||
defaultValue: itemType[0] && itemType[0].defaultValue,
|
|
||||||
display: 'inline',
|
|
||||||
setter: {
|
|
||||||
type: 'OneOfTypeSetter',
|
|
||||||
itemType, // addable type
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
// You can also declare that a prop is an instance of a class. This uses
|
|
||||||
// JS's instanceof operator.
|
|
||||||
VisionTypes.instanceOf = (classType) => {
|
|
||||||
return define(PropTypes.instanceOf(classType), {
|
|
||||||
display: 'inline',
|
|
||||||
setter: 'ExpressionSetter',
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
// An object with warnings on extra properties
|
|
||||||
VisionTypes.exact = (typesMap) => {
|
|
||||||
const exactTypes = {};
|
|
||||||
const defaultValue = {};
|
|
||||||
Object.keys(typesMap).forEach(key => {
|
|
||||||
exactTypes[key] = typesMap[key].visionType || primitiveTypeMaps.any;
|
|
||||||
defaultValue[key] = exactTypes[key].defaultValue;
|
|
||||||
});
|
|
||||||
return define(PropTypes.exact(typesMap), {
|
|
||||||
defaultValue,
|
|
||||||
display: 'inline',
|
|
||||||
setter: {
|
|
||||||
type: 'ObjectSetter', // all itemType
|
|
||||||
exactTypes,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// An object taking on a particular shape
|
|
||||||
VisionTypes.shape = (typesMap) => {
|
|
||||||
const exactTypes = {};
|
|
||||||
const defaultValue = {};
|
|
||||||
Object.keys(typesMap).forEach(key => {
|
|
||||||
exactTypes[key] = typesMap[key].visionType || primitiveTypeMaps.any;
|
|
||||||
defaultValue[key] = exactTypes[key].defaultValue;
|
|
||||||
});
|
|
||||||
return define(PropTypes.shape(typesMap), {
|
|
||||||
defaultValue,
|
|
||||||
display: 'inline',
|
|
||||||
setter: {
|
|
||||||
type: 'ObjectSetter', // all itemType
|
|
||||||
exactTypes,
|
|
||||||
itemType: primitiveTypeMaps.any, // addable type
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
// color
|
|
||||||
// time
|
|
||||||
// date
|
|
||||||
// range
|
|
||||||
@ -1,384 +0,0 @@
|
|||||||
import { ReactNode, ReactElement, ComponentType } from 'react';
|
|
||||||
import Node, { NodeParent } from './document/node/node';
|
|
||||||
import { NodeData, NodeSchema } from './schema';
|
|
||||||
|
|
||||||
export type BasicTypes = 'array' | 'bool' | 'func' | 'number' | 'object' | 'string' | 'node' | 'element' | 'any';
|
|
||||||
export interface CompositeType {
|
|
||||||
type: BasicTypes;
|
|
||||||
isRequired: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: add complex types
|
|
||||||
|
|
||||||
export interface PropConfig {
|
|
||||||
name: string;
|
|
||||||
propType: BasicTypes | CompositeType;
|
|
||||||
description?: string;
|
|
||||||
defaultValue?: any;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type CustomView = ReactElement | ComponentType<any>;
|
|
||||||
|
|
||||||
export interface TipConfig {
|
|
||||||
className?: string;
|
|
||||||
children?: ReactNode;
|
|
||||||
theme?: string;
|
|
||||||
direction?: string; // 'n|s|w|e|top|bottom|left|right';
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IconConfig {
|
|
||||||
name: string;
|
|
||||||
size?: string;
|
|
||||||
className?: string;
|
|
||||||
effect?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface TitleConfig {
|
|
||||||
label?: ReactNode;
|
|
||||||
tip?: string | ReactElement | TipConfig;
|
|
||||||
icon?: string | ReactElement | IconConfig;
|
|
||||||
className?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type Title = string | ReactElement | TitleConfig;
|
|
||||||
|
|
||||||
export enum DisplayType {
|
|
||||||
Inline = 'inline',
|
|
||||||
Block = 'block',
|
|
||||||
Accordion = 'Accordion',
|
|
||||||
Plain = 'plain',
|
|
||||||
Caption = 'caption',
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface SetterConfig {
|
|
||||||
/**
|
|
||||||
* if *string* passed must be a registered Setter Name
|
|
||||||
*/
|
|
||||||
componentName: string | CustomView;
|
|
||||||
/**
|
|
||||||
* the props pass to Setter Component
|
|
||||||
*/
|
|
||||||
props?: {
|
|
||||||
[prop: string]: any;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* if *string* passed must be a registered Setter Name
|
|
||||||
*/
|
|
||||||
export type SetterType = SetterConfig | string | CustomView;
|
|
||||||
|
|
||||||
export interface SettingFieldConfig {
|
|
||||||
/**
|
|
||||||
* the name of this setting field, which used in quickEditor
|
|
||||||
*/
|
|
||||||
name: string;
|
|
||||||
/**
|
|
||||||
* the field body contains
|
|
||||||
*/
|
|
||||||
setter: SetterType;
|
|
||||||
/**
|
|
||||||
* the prop target which to set, eg. "style.width"
|
|
||||||
* @default sameas .name
|
|
||||||
*/
|
|
||||||
propTarget?: string;
|
|
||||||
/**
|
|
||||||
* the field title
|
|
||||||
* @default sameas .propTarget
|
|
||||||
*/
|
|
||||||
title?: Title;
|
|
||||||
extraProps?: {
|
|
||||||
/**
|
|
||||||
* default value of target prop for setter use
|
|
||||||
*/
|
|
||||||
defaultValue?: any;
|
|
||||||
onChange?: (value: any) => void;
|
|
||||||
getValue?: () => any;
|
|
||||||
/**
|
|
||||||
* the field conditional show, is not set always true
|
|
||||||
* @default undefined
|
|
||||||
*/
|
|
||||||
condition?: (node: Node) => boolean;
|
|
||||||
/**
|
|
||||||
* quick add "required" validation
|
|
||||||
*/
|
|
||||||
required?: boolean;
|
|
||||||
/**
|
|
||||||
* the field display
|
|
||||||
* @default DisplayType.Block
|
|
||||||
*/
|
|
||||||
display?: DisplayType.Inline | DisplayType.Block | DisplayType.Accordion | DisplayType.Plain;
|
|
||||||
/**
|
|
||||||
* default collapsed when display accordion
|
|
||||||
*/
|
|
||||||
defaultCollapsed?: boolean;
|
|
||||||
/**
|
|
||||||
* layout control
|
|
||||||
* number or [column number, left offset]
|
|
||||||
* @default 6
|
|
||||||
*/
|
|
||||||
span?: number | [number, number];
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface SettingGroupConfig {
|
|
||||||
/**
|
|
||||||
* the type "group"
|
|
||||||
*/
|
|
||||||
type: 'group';
|
|
||||||
/**
|
|
||||||
* the name of this setting group, which used in quickEditor
|
|
||||||
*/
|
|
||||||
name?: string;
|
|
||||||
/**
|
|
||||||
* the setting items which group body contains
|
|
||||||
*/
|
|
||||||
items: Array<SettingFieldConfig | SettingGroupConfig | CustomView>;
|
|
||||||
/**
|
|
||||||
* the group title
|
|
||||||
* @default sameas .name
|
|
||||||
*/
|
|
||||||
title?: Title;
|
|
||||||
extraProps: {
|
|
||||||
/**
|
|
||||||
* the field conditional show, is not set always true
|
|
||||||
* @default undefined
|
|
||||||
*/
|
|
||||||
condition?: (node: Node) => boolean;
|
|
||||||
/**
|
|
||||||
* the group display
|
|
||||||
* @default DisplayType.Block
|
|
||||||
*/
|
|
||||||
display?: DisplayType.Block | DisplayType.Accordion;
|
|
||||||
/**
|
|
||||||
* default collapsed when display accordion
|
|
||||||
*/
|
|
||||||
defaultCollapsed?: boolean;
|
|
||||||
/**
|
|
||||||
* the gap between span
|
|
||||||
* @default 0 px
|
|
||||||
*/
|
|
||||||
gap?: number;
|
|
||||||
/**
|
|
||||||
* layout control
|
|
||||||
* number or [column number, left offset]
|
|
||||||
* @default 6
|
|
||||||
*/
|
|
||||||
span?: number | [number, number];
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export type PropSettingConfig = SettingFieldConfig | SettingGroupConfig | CustomView;
|
|
||||||
|
|
||||||
export interface NestingRule {
|
|
||||||
childWhitelist?: string[];
|
|
||||||
parentWhitelist?: string[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Configure {
|
|
||||||
props?: PropSettingConfig[];
|
|
||||||
styles?: object;
|
|
||||||
events?: object;
|
|
||||||
component?: {
|
|
||||||
isContainer?: boolean;
|
|
||||||
isModal?: boolean;
|
|
||||||
descriptor?: string;
|
|
||||||
nestingRule?: NestingRule;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ComponentDescriptionSpec {
|
|
||||||
componentName: string;
|
|
||||||
/**
|
|
||||||
* unique id
|
|
||||||
*/
|
|
||||||
uri?: string;
|
|
||||||
/**
|
|
||||||
* title or description
|
|
||||||
*/
|
|
||||||
title?: string;
|
|
||||||
/**
|
|
||||||
* svg icon for component
|
|
||||||
*/
|
|
||||||
icon?: string | ReactNode;
|
|
||||||
tags?: string[];
|
|
||||||
description?: string;
|
|
||||||
docUrl?: string;
|
|
||||||
screenshot?: string;
|
|
||||||
devMode?: 'procode' | 'lowcode';
|
|
||||||
npm?: {
|
|
||||||
package: string;
|
|
||||||
exportName: string;
|
|
||||||
subName: string;
|
|
||||||
main: string;
|
|
||||||
destructuring: boolean;
|
|
||||||
version: string;
|
|
||||||
};
|
|
||||||
props?: PropConfig[];
|
|
||||||
configure?: PropSettingConfig[] | Configure;
|
|
||||||
}
|
|
||||||
|
|
||||||
function ensureAList(list?: string | string[]): string[] | null {
|
|
||||||
if (!list) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
if (!Array.isArray(list)) {
|
|
||||||
list = list.split(/ *[ ,|] */).filter(Boolean);
|
|
||||||
}
|
|
||||||
if (list.length < 1) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return list;
|
|
||||||
}
|
|
||||||
|
|
||||||
function npmToURI(npm: {
|
|
||||||
package: string;
|
|
||||||
exportName?: string;
|
|
||||||
subName?: string;
|
|
||||||
destructuring?: boolean;
|
|
||||||
main?: string;
|
|
||||||
version: string;
|
|
||||||
}): string {
|
|
||||||
let pkg = [];
|
|
||||||
if (npm.package) {
|
|
||||||
pkg.push(npm.package);
|
|
||||||
}
|
|
||||||
if (npm.main) {
|
|
||||||
if (npm.main[0] === '/') {
|
|
||||||
pkg.push(npm.main.slice(1));
|
|
||||||
} else if (npm.main.slice(0, 2) === './') {
|
|
||||||
pkg.push(npm.main.slice(2));
|
|
||||||
} else {
|
|
||||||
pkg.push(npm.main);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let uri = pkg.join('/');
|
|
||||||
uri += `:${npm.destructuring && npm.exportName ? npm.exportName : 'default'}`;
|
|
||||||
|
|
||||||
if (npm.subName) {
|
|
||||||
uri += `.${npm.subName}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
return uri;
|
|
||||||
}
|
|
||||||
|
|
||||||
function generatePropsConfigure(props: PropConfig[]) {
|
|
||||||
// todo:
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
export class ComponentConfig {
|
|
||||||
readonly isComponentConfig = true;
|
|
||||||
private _uri?: string;
|
|
||||||
get uri(): string {
|
|
||||||
return this._uri!;
|
|
||||||
}
|
|
||||||
private _componentName?: string;
|
|
||||||
get componentName(): string {
|
|
||||||
return this._componentName!;
|
|
||||||
}
|
|
||||||
private _isContainer?: boolean;
|
|
||||||
get isContainer(): boolean {
|
|
||||||
return this._isContainer! || this.isRootComponent();
|
|
||||||
}
|
|
||||||
private _isModal?: boolean;
|
|
||||||
get isModal(): boolean {
|
|
||||||
return this._isModal!;
|
|
||||||
}
|
|
||||||
private _descriptor?: string;
|
|
||||||
get descriptor(): string {
|
|
||||||
return this._descriptor!;
|
|
||||||
}
|
|
||||||
private _acceptable?: boolean;
|
|
||||||
get acceptable(): boolean {
|
|
||||||
return this._acceptable!;
|
|
||||||
}
|
|
||||||
private _configure?: Configure;
|
|
||||||
get configure(): Configure {
|
|
||||||
return this._configure!;
|
|
||||||
}
|
|
||||||
|
|
||||||
private parentWhitelist?: string[] | null;
|
|
||||||
private childWhitelist?: string[] | null;
|
|
||||||
|
|
||||||
get title() {
|
|
||||||
return this._spec.title;
|
|
||||||
}
|
|
||||||
|
|
||||||
get icon() {
|
|
||||||
return this._spec.icon;
|
|
||||||
}
|
|
||||||
|
|
||||||
get propsConfigure() {
|
|
||||||
return this.configure.props;
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(private _spec: ComponentDescriptionSpec) {
|
|
||||||
this.parseSpec(_spec);
|
|
||||||
}
|
|
||||||
|
|
||||||
private parseSpec(spec: ComponentDescriptionSpec) {
|
|
||||||
const { componentName, uri, configure, npm, props } = spec;
|
|
||||||
this._uri = uri || (npm ? npmToURI(npm) : componentName);
|
|
||||||
this._componentName = componentName;
|
|
||||||
this._acceptable = false;
|
|
||||||
|
|
||||||
if (!configure || Array.isArray(configure)) {
|
|
||||||
this._configure = {
|
|
||||||
props: !configure ? [] : configure,
|
|
||||||
styles: {
|
|
||||||
supportClassName: true,
|
|
||||||
supportInlineStyle: true,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
this._configure = configure;
|
|
||||||
}
|
|
||||||
if (!this.configure.props) {
|
|
||||||
this.configure.props = props ? generatePropsConfigure(props) : [];
|
|
||||||
}
|
|
||||||
const { component } = this.configure;
|
|
||||||
if (component) {
|
|
||||||
this._isContainer = component.isContainer ? true : false;
|
|
||||||
this._isModal = component.isModal ? true : false;
|
|
||||||
this._descriptor = component.descriptor;
|
|
||||||
if (component.nestingRule) {
|
|
||||||
const { parentWhitelist, childWhitelist } = component.nestingRule;
|
|
||||||
this.parentWhitelist = ensureAList(parentWhitelist);
|
|
||||||
this.childWhitelist = ensureAList(childWhitelist);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
this._isContainer = false;
|
|
||||||
this._isModal = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
isRootComponent() {
|
|
||||||
return this.componentName === 'Page' || this.componentName === 'Block' || this.componentName === 'Component';
|
|
||||||
}
|
|
||||||
|
|
||||||
set spec(spec: ComponentDescriptionSpec) {
|
|
||||||
this._spec = spec;
|
|
||||||
this.parseSpec(spec);
|
|
||||||
}
|
|
||||||
|
|
||||||
get spec(): ComponentDescriptionSpec {
|
|
||||||
return this._spec;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
checkNestingUp(my: Node | NodeData, parent: NodeParent) {
|
|
||||||
if (this.parentWhitelist) {
|
|
||||||
return this.parentWhitelist.includes(parent.componentName);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
checkNestingDown(my: Node, target: Node | NodeSchema) {
|
|
||||||
if (this.childWhitelist) {
|
|
||||||
return this.childWhitelist.includes(target.componentName);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
224
packages/designer/src/designer/component-meta.ts
Normal file
224
packages/designer/src/designer/component-meta.ts
Normal file
@ -0,0 +1,224 @@
|
|||||||
|
import { ReactNode } from 'react';
|
||||||
|
import Node, { NodeParent } from './document/node/node';
|
||||||
|
import { NodeData, NodeSchema } from './schema';
|
||||||
|
import { PropConfig } from './prop-config';
|
||||||
|
|
||||||
|
export interface NestingRule {
|
||||||
|
childWhitelist?: string[];
|
||||||
|
parentWhitelist?: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Configure {
|
||||||
|
props?: any[];
|
||||||
|
styles?: object;
|
||||||
|
events?: object;
|
||||||
|
component?: {
|
||||||
|
isContainer?: boolean;
|
||||||
|
isModal?: boolean;
|
||||||
|
descriptor?: string;
|
||||||
|
nestingRule?: NestingRule;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ComponentMetadata {
|
||||||
|
componentName: string;
|
||||||
|
/**
|
||||||
|
* unique id
|
||||||
|
*/
|
||||||
|
uri?: string;
|
||||||
|
/**
|
||||||
|
* title or description
|
||||||
|
*/
|
||||||
|
title?: string;
|
||||||
|
/**
|
||||||
|
* svg icon for component
|
||||||
|
*/
|
||||||
|
icon?: string | ReactNode;
|
||||||
|
tags?: string[];
|
||||||
|
description?: string;
|
||||||
|
docUrl?: string;
|
||||||
|
screenshot?: string;
|
||||||
|
devMode?: 'procode' | 'lowcode';
|
||||||
|
npm?: {
|
||||||
|
package: string;
|
||||||
|
exportName: string;
|
||||||
|
subName: string;
|
||||||
|
main: string;
|
||||||
|
destructuring: boolean;
|
||||||
|
version: string;
|
||||||
|
};
|
||||||
|
props?: PropConfig[];
|
||||||
|
configure?: any[] | Configure;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface TransformedComponentMetadata extends ComponentMetadata {
|
||||||
|
configure?: Configure & {
|
||||||
|
combined?: any[];
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function ensureAList(list?: string | string[]): string[] | null {
|
||||||
|
if (!list) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (!Array.isArray(list)) {
|
||||||
|
list = list.split(/ *[ ,|] */).filter(Boolean);
|
||||||
|
}
|
||||||
|
if (list.length < 1) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
function npmToURI(npm: {
|
||||||
|
package: string;
|
||||||
|
exportName?: string;
|
||||||
|
subName?: string;
|
||||||
|
destructuring?: boolean;
|
||||||
|
main?: string;
|
||||||
|
version: string;
|
||||||
|
}): string {
|
||||||
|
const pkg = [];
|
||||||
|
if (npm.package) {
|
||||||
|
pkg.push(npm.package);
|
||||||
|
}
|
||||||
|
if (npm.main) {
|
||||||
|
if (npm.main[0] === '/') {
|
||||||
|
pkg.push(npm.main.slice(1));
|
||||||
|
} else if (npm.main.slice(0, 2) === './') {
|
||||||
|
pkg.push(npm.main.slice(2));
|
||||||
|
} else {
|
||||||
|
pkg.push(npm.main);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let uri = pkg.join('/');
|
||||||
|
uri += `:${npm.destructuring && npm.exportName ? npm.exportName : 'default'}`;
|
||||||
|
|
||||||
|
if (npm.subName) {
|
||||||
|
uri += `.${npm.subName}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return uri;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type MetadataTransducer = (prev: ComponentMetadata) => TransformedComponentMetadata;
|
||||||
|
const metadataTransducers: MetadataTransducer[] = [];
|
||||||
|
|
||||||
|
export function registerMetadataTransducer(transducer: MetadataTransducer) {
|
||||||
|
metadataTransducers.push(transducer);
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ComponentMeta {
|
||||||
|
readonly isComponentMeta = true;
|
||||||
|
private _uri?: string;
|
||||||
|
get uri(): string {
|
||||||
|
return this._uri!;
|
||||||
|
}
|
||||||
|
private _componentName?: string;
|
||||||
|
get componentName(): string {
|
||||||
|
return this._componentName!;
|
||||||
|
}
|
||||||
|
private _isContainer?: boolean;
|
||||||
|
get isContainer(): boolean {
|
||||||
|
return this._isContainer! || this.isRootComponent();
|
||||||
|
}
|
||||||
|
private _isModal?: boolean;
|
||||||
|
get isModal(): boolean {
|
||||||
|
return this._isModal!;
|
||||||
|
}
|
||||||
|
private _descriptor?: string;
|
||||||
|
get descriptor(): string {
|
||||||
|
return this._descriptor!;
|
||||||
|
}
|
||||||
|
private _acceptable?: boolean;
|
||||||
|
get acceptable(): boolean {
|
||||||
|
return this._acceptable!;
|
||||||
|
}
|
||||||
|
private _transformedMetadata?: TransformedComponentMetadata;
|
||||||
|
get configure() {
|
||||||
|
const config = this._transformedMetadata?.configure;
|
||||||
|
return config?.combined || config?.props || [];
|
||||||
|
}
|
||||||
|
|
||||||
|
private parentWhitelist?: string[] | null;
|
||||||
|
private childWhitelist?: string[] | null;
|
||||||
|
|
||||||
|
get title() {
|
||||||
|
return this._metadata.title || this.componentName;
|
||||||
|
}
|
||||||
|
|
||||||
|
get icon() {
|
||||||
|
return this._metadata.icon;
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(private _metadata: ComponentMetadata) {
|
||||||
|
this.parseMetadata(_metadata);
|
||||||
|
}
|
||||||
|
|
||||||
|
private parseMetadata(metadta: ComponentMetadata) {
|
||||||
|
const { componentName, uri, npm, props } = metadta;
|
||||||
|
this._uri = uri || (npm ? npmToURI(npm) : componentName);
|
||||||
|
this._componentName = componentName;
|
||||||
|
|
||||||
|
metadta.uri = this._uri;
|
||||||
|
// 额外转换逻辑
|
||||||
|
this._transformedMetadata = this.transformMetadata(metadta);
|
||||||
|
|
||||||
|
const { configure = {} } = this._transformedMetadata;
|
||||||
|
this._acceptable = false;
|
||||||
|
|
||||||
|
const { component } = configure;
|
||||||
|
if (component) {
|
||||||
|
this._isContainer = component.isContainer ? true : false;
|
||||||
|
this._isModal = component.isModal ? true : false;
|
||||||
|
this._descriptor = component.descriptor;
|
||||||
|
if (component.nestingRule) {
|
||||||
|
const { parentWhitelist, childWhitelist } = component.nestingRule;
|
||||||
|
this.parentWhitelist = ensureAList(parentWhitelist);
|
||||||
|
this.childWhitelist = ensureAList(childWhitelist);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this._isContainer = false;
|
||||||
|
this._isModal = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private transformMetadata(metadta: ComponentMetadata): TransformedComponentMetadata {
|
||||||
|
const result = metadataTransducers.reduce((prevMetadata, current) => {
|
||||||
|
return current(prevMetadata);
|
||||||
|
}, metadta);
|
||||||
|
|
||||||
|
if (!result.configure) {
|
||||||
|
result.configure = {};
|
||||||
|
}
|
||||||
|
return result as any;
|
||||||
|
}
|
||||||
|
|
||||||
|
isRootComponent() {
|
||||||
|
return this.componentName === 'Page' || this.componentName === 'Block' || this.componentName === 'Component';
|
||||||
|
}
|
||||||
|
|
||||||
|
set metadata(metadata: ComponentMetadata) {
|
||||||
|
this._metadata = metadata;
|
||||||
|
this.parseMetadata(metadata);
|
||||||
|
}
|
||||||
|
|
||||||
|
get metadata(): ComponentMetadata {
|
||||||
|
return this._metadata;
|
||||||
|
}
|
||||||
|
|
||||||
|
checkNestingUp(my: Node | NodeData, parent: NodeParent) {
|
||||||
|
if (this.parentWhitelist) {
|
||||||
|
return this.parentWhitelist.includes(parent.componentName);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
checkNestingDown(my: Node, target: Node | NodeSchema) {
|
||||||
|
if (this.childWhitelist) {
|
||||||
|
return this.childWhitelist.includes(target.componentName);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -8,7 +8,9 @@
|
|||||||
--font-size-btn-medium: @fontSize-4;
|
--font-size-btn-medium: @fontSize-4;
|
||||||
--font-size-btn-small: @fontSize-5;
|
--font-size-btn-small: @fontSize-5;
|
||||||
|
|
||||||
--color-brand-light: rgb(102, 188, 92);
|
--color-brand: #006cff;
|
||||||
|
--color-brand-light: #197aff;
|
||||||
|
--color-brand-dark: #0060e5;
|
||||||
--color-icon: rgba(255, 255, 255, 0.8);
|
--color-icon: rgba(255, 255, 255, 0.8);
|
||||||
--color-visited: rgba(179, 182, 201, 0.4);
|
--color-visited: rgba(179, 182, 201, 0.4);
|
||||||
--color-actived: #498ee6;
|
--color-actived: #498ee6;
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
import { ComponentType } from 'react';
|
import { ComponentType as ReactComponentType } from 'react';
|
||||||
import { obx, computed } from '@recore/obx';
|
import { obx, computed, autorun } from '@recore/obx';
|
||||||
import BuiltinSimulatorView from '../builtins/simulator';
|
import BuiltinSimulatorView from '../builtins/simulator';
|
||||||
import Project from './project';
|
import Project from './project';
|
||||||
import { ProjectSchema } from './schema';
|
import { ProjectSchema, NpmInfo } from './schema';
|
||||||
import Dragon, { isDragNodeObject, isDragNodeDataObject, LocateEvent, DragObject } from './helper/dragon';
|
import Dragon, { isDragNodeObject, isDragNodeDataObject, LocateEvent, DragObject } from './helper/dragon';
|
||||||
import ActiveTracker from './helper/active-tracker';
|
import ActiveTracker from './helper/active-tracker';
|
||||||
import Hovering from './helper/hovering';
|
import Hovering from './helper/hovering';
|
||||||
@ -10,10 +10,11 @@ import Location, { LocationData, isLocationChildrenDetail } from './helper/locat
|
|||||||
import DocumentModel from './document/document-model';
|
import DocumentModel from './document/document-model';
|
||||||
import Node, { insertChildren } from './document/node/node';
|
import Node, { insertChildren } from './document/node/node';
|
||||||
import { isRootNode } from './document/node/root-node';
|
import { isRootNode } from './document/node/root-node';
|
||||||
import { ComponentDescriptionSpec, ComponentConfig } from './component-config';
|
import { ComponentMetadata, ComponentMeta } from './component-meta';
|
||||||
import Scroller, { IScrollable } from './helper/scroller';
|
import Scroller, { IScrollable } from './helper/scroller';
|
||||||
import { INodeSelector } from './simulator';
|
import { INodeSelector } from './simulator';
|
||||||
import OffsetObserver, { createOffsetObserver } from './helper/offset-observer';
|
import OffsetObserver, { createOffsetObserver } from './helper/offset-observer';
|
||||||
|
import { EventEmitter } from 'events';
|
||||||
|
|
||||||
export interface DesignerProps {
|
export interface DesignerProps {
|
||||||
className?: string;
|
className?: string;
|
||||||
@ -21,15 +22,15 @@ export interface DesignerProps {
|
|||||||
defaultSchema?: ProjectSchema;
|
defaultSchema?: ProjectSchema;
|
||||||
hotkeys?: object;
|
hotkeys?: object;
|
||||||
simulatorProps?: object | ((document: DocumentModel) => object);
|
simulatorProps?: object | ((document: DocumentModel) => object);
|
||||||
simulatorComponent?: ComponentType<any>;
|
simulatorComponent?: ReactComponentType<any>;
|
||||||
dragGhostComponent?: ComponentType<any>;
|
dragGhostComponent?: ReactComponentType<any>;
|
||||||
suspensed?: boolean;
|
suspensed?: boolean;
|
||||||
componentDescriptionSpecs?: ComponentDescriptionSpec[];
|
componentsDescription?: ComponentMetadata[];
|
||||||
|
eventPipe?: EventEmitter;
|
||||||
onMount?: (designer: Designer) => void;
|
onMount?: (designer: Designer) => void;
|
||||||
onDragstart?: (e: LocateEvent) => void;
|
onDragstart?: (e: LocateEvent) => void;
|
||||||
onDrag?: (e: LocateEvent) => void;
|
onDrag?: (e: LocateEvent) => void;
|
||||||
onDragend?: (e: { dragObject: DragObject; copy: boolean }, loc?: Location) => void;
|
onDragend?: (e: { dragObject: DragObject; copy: boolean }, loc?: Location) => void;
|
||||||
// TODO: ...add other events support
|
|
||||||
[key: string]: any;
|
[key: string]: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -40,26 +41,45 @@ export default class Designer {
|
|||||||
readonly hovering = new Hovering();
|
readonly hovering = new Hovering();
|
||||||
readonly project: Project;
|
readonly project: Project;
|
||||||
|
|
||||||
|
get currentDocument() {
|
||||||
|
return this.project.currentDocument;
|
||||||
|
}
|
||||||
|
|
||||||
|
get currentHistory() {
|
||||||
|
return this.currentDocument?.history;
|
||||||
|
}
|
||||||
|
|
||||||
|
get currentSelection() {
|
||||||
|
return this.currentDocument?.selection;
|
||||||
|
}
|
||||||
|
|
||||||
constructor(props: DesignerProps) {
|
constructor(props: DesignerProps) {
|
||||||
|
this.setProps(props);
|
||||||
|
|
||||||
this.project = new Project(this, props.defaultSchema);
|
this.project = new Project(this, props.defaultSchema);
|
||||||
|
|
||||||
this.dragon.onDragstart(e => {
|
this.dragon.onDragstart(e => {
|
||||||
this.hovering.enable = false;
|
this.hovering.enable = false;
|
||||||
const { dragObject } = e;
|
const { dragObject } = e;
|
||||||
if (isDragNodeObject(dragObject) && dragObject.nodes.length === 1) {
|
if (isDragNodeObject(dragObject)) {
|
||||||
// ensure current selecting
|
if (dragObject.nodes.length === 1) {
|
||||||
dragObject.nodes[0].select();
|
// ensure current selecting
|
||||||
|
dragObject.nodes[0].select();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.currentSelection?.clear();
|
||||||
}
|
}
|
||||||
if (this.props?.onDragstart) {
|
if (this.props?.onDragstart) {
|
||||||
this.props.onDragstart(e);
|
this.props.onDragstart(e);
|
||||||
}
|
}
|
||||||
|
this.postEvent('dragstart', e);
|
||||||
});
|
});
|
||||||
|
|
||||||
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);
|
||||||
}
|
}
|
||||||
|
this.postEvent('drag', e);
|
||||||
});
|
});
|
||||||
|
|
||||||
this.dragon.onDragend(e => {
|
this.dragon.onDragend(e => {
|
||||||
@ -85,6 +105,7 @@ export default class Designer {
|
|||||||
if (this.props?.onDragend) {
|
if (this.props?.onDragend) {
|
||||||
this.props.onDragend(e, loc);
|
this.props.onDragend(e, loc);
|
||||||
}
|
}
|
||||||
|
this.postEvent('dragend', e, loc);
|
||||||
this.hovering.enable = true;
|
this.hovering.enable = true;
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -92,7 +113,49 @@ export default class Designer {
|
|||||||
node.document.simulator?.scrollToNode(node, detail);
|
node.document.simulator?.scrollToNode(node, detail);
|
||||||
});
|
});
|
||||||
|
|
||||||
this.setProps(props);
|
let selectionDispose: undefined | (() => void);
|
||||||
|
const setupSelection = () => {
|
||||||
|
if (selectionDispose) {
|
||||||
|
selectionDispose();
|
||||||
|
selectionDispose = undefined;
|
||||||
|
}
|
||||||
|
this.postEvent('selection-change', this.currentSelection);
|
||||||
|
if (this.currentSelection) {
|
||||||
|
const currentSelection = this.currentSelection;
|
||||||
|
selectionDispose = currentSelection.onSelectionChange(() => {
|
||||||
|
this.postEvent('selection-change', currentSelection);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let historyDispose: undefined | (() => void);
|
||||||
|
const setupHistory = () => {
|
||||||
|
if (historyDispose) {
|
||||||
|
historyDispose();
|
||||||
|
historyDispose = undefined;
|
||||||
|
}
|
||||||
|
this.postEvent('history-change', this.currentHistory);
|
||||||
|
if (this.currentHistory) {
|
||||||
|
const currentHistory = this.currentHistory;
|
||||||
|
historyDispose = currentHistory.onStateChange(() => {
|
||||||
|
this.postEvent('history-change', currentHistory);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
this.project.onCurrentDocumentChange(() => {
|
||||||
|
this.postEvent('current-document-change', this.currentDocument);
|
||||||
|
this.postEvent('selection-change', this.currentSelection);
|
||||||
|
this.postEvent('history-change', this.currentHistory);
|
||||||
|
setupSelection();
|
||||||
|
setupHistory();
|
||||||
|
});
|
||||||
|
setupSelection();
|
||||||
|
setupHistory();
|
||||||
|
|
||||||
|
this.postEvent('designer.ready', this);
|
||||||
|
}
|
||||||
|
|
||||||
|
postEvent(event: string, ...args: any[]) {
|
||||||
|
this.props?.eventPipe?.emit(`designer.${event}`, ...args);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _dropLocation?: Location;
|
private _dropLocation?: Location;
|
||||||
@ -129,7 +192,7 @@ export default class Designer {
|
|||||||
* 获得合适的插入位置
|
* 获得合适的插入位置
|
||||||
*/
|
*/
|
||||||
getSuitableInsertion() {
|
getSuitableInsertion() {
|
||||||
const activedDoc = this.project.activedDocuments[0];
|
const activedDoc = this.project.currentDocument;
|
||||||
if (!activedDoc) {
|
if (!activedDoc) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -166,8 +229,8 @@ export default class Designer {
|
|||||||
if (props.suspensed !== this.props.suspensed && props.suspensed != null) {
|
if (props.suspensed !== this.props.suspensed && props.suspensed != null) {
|
||||||
this.suspensed = props.suspensed;
|
this.suspensed = props.suspensed;
|
||||||
}
|
}
|
||||||
if (props.componentDescriptionSpecs !== this.props.componentDescriptionSpecs && props.componentDescriptionSpecs != null) {
|
if (props.componentsDescription !== this.props.componentsDescription && props.componentsDescription != null) {
|
||||||
this.buildComponentConfigsMap(props.componentDescriptionSpecs);
|
this.buildComponentMetasMap(props.componentsDescription);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// init hotkeys
|
// init hotkeys
|
||||||
@ -183,8 +246,8 @@ export default class Designer {
|
|||||||
if (props.suspensed != null) {
|
if (props.suspensed != null) {
|
||||||
this.suspensed = props.suspensed;
|
this.suspensed = props.suspensed;
|
||||||
}
|
}
|
||||||
if (props.componentDescriptionSpecs != null) {
|
if (props.componentsDescription != null) {
|
||||||
this.buildComponentConfigsMap(props.componentDescriptionSpecs);
|
this.buildComponentMetasMap(props.componentsDescription);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.props = props;
|
this.props = props;
|
||||||
@ -194,9 +257,9 @@ export default class Designer {
|
|||||||
return this.props ? this.props[key] : null;
|
return this.props ? this.props[key] : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@obx.ref private _simulatorComponent?: ComponentType<any>;
|
@obx.ref private _simulatorComponent?: ReactComponentType<any>;
|
||||||
|
|
||||||
@computed get simulatorComponent(): ComponentType<any> {
|
@computed get simulatorComponent(): ReactComponentType<any> {
|
||||||
return this._simulatorComponent || BuiltinSimulatorView;
|
return this._simulatorComponent || BuiltinSimulatorView;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -206,7 +269,7 @@ export default class Designer {
|
|||||||
return this._simulatorProps || {};
|
return this._simulatorProps || {};
|
||||||
}
|
}
|
||||||
|
|
||||||
@obx.ref private _suspensed: boolean = false;
|
@obx.ref private _suspensed = false;
|
||||||
|
|
||||||
get suspensed(): boolean {
|
get suspensed(): boolean {
|
||||||
return this._suspensed;
|
return this._suspensed;
|
||||||
@ -228,38 +291,61 @@ export default class Designer {
|
|||||||
// todo:
|
// todo:
|
||||||
}
|
}
|
||||||
|
|
||||||
@obx.val private _componentConfigsMap = new Map<string, ComponentConfig>();
|
@obx.val private _componentMetasMap = new Map<string, ComponentMeta>();
|
||||||
|
private _lostComponentMetasMap = new Map<string, ComponentMeta>();
|
||||||
|
|
||||||
private buildComponentConfigsMap(specs: ComponentDescriptionSpec[]) {
|
private buildComponentMetasMap(metas: ComponentMetadata[]) {
|
||||||
specs.forEach(spec => {
|
metas.forEach(data => {
|
||||||
const key = spec.componentName;
|
const key = data.componentName;
|
||||||
const had = this._componentConfigsMap.get(key);
|
let meta = this._componentMetasMap.get(key);
|
||||||
if (had) {
|
if (meta) {
|
||||||
had.spec = spec;
|
meta.metadata = data;
|
||||||
} else {
|
} else {
|
||||||
this._componentConfigsMap.set(key, new ComponentConfig(spec));
|
meta = this._lostComponentMetasMap.get(key);
|
||||||
|
|
||||||
|
if (meta) {
|
||||||
|
meta.metadata = data;
|
||||||
|
this._lostComponentMetasMap.delete(key);
|
||||||
|
} else {
|
||||||
|
meta = new ComponentMeta(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
this._componentMetasMap.set(key, meta);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
getComponentConfig(componentName: string): ComponentConfig {
|
getComponentMeta(componentName: string, generateMetadata?: () => ComponentMetadata | null): ComponentMeta {
|
||||||
if (this._componentConfigsMap.has(componentName)) {
|
if (this._componentMetasMap.has(componentName)) {
|
||||||
return this._componentConfigsMap.get(componentName)!;
|
return this._componentMetasMap.get(componentName)!;
|
||||||
}
|
}
|
||||||
|
|
||||||
return new ComponentConfig({
|
if (this._lostComponentMetasMap.has(componentName)) {
|
||||||
|
return this._lostComponentMetasMap.get(componentName)!;
|
||||||
|
}
|
||||||
|
|
||||||
|
const meta = new ComponentMeta({
|
||||||
componentName,
|
componentName,
|
||||||
|
...(generateMetadata ? generateMetadata() : null),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this._lostComponentMetasMap.set(componentName, meta);
|
||||||
|
|
||||||
|
return meta;
|
||||||
}
|
}
|
||||||
|
|
||||||
get componentsMap(): { [key: string]: ComponentDescriptionSpec } {
|
@computed get componentsMap(): { [key: string]: NpmInfo } {
|
||||||
const maps: any = {};
|
const maps: any = {};
|
||||||
this._componentConfigsMap.forEach((config, key) => {
|
this._componentMetasMap.forEach((config, key) => {
|
||||||
maps[key] = config.spec;
|
maps[key] = config.metadata.npm;
|
||||||
});
|
});
|
||||||
return maps;
|
return maps;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
autorun(action: (context: { firstRun: boolean }) => void, sync = false): () => void {
|
||||||
|
return autorun(action, sync as true);
|
||||||
|
}
|
||||||
|
|
||||||
purge() {
|
purge() {
|
||||||
// todo:
|
// todo:
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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 { ComponentMeta } from '../component-meta';
|
||||||
|
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;
|
||||||
|
|
||||||
@ -40,16 +41,28 @@ export default class DocumentModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get fileName(): string {
|
get fileName(): string {
|
||||||
return (this.rootNode.extras.get('fileName')?.value as string) || this.id;
|
return this.rootNode.getExtraProp('fileName')?.getAsString() || this.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
set fileName(fileName: string) {
|
set fileName(fileName: string) {
|
||||||
this.rootNode.extras.get('fileName', true).value = fileName;
|
this.rootNode.getExtraProp('fileName', true)?.setValue(fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(readonly project: Project, schema: RootSchema) {
|
constructor(readonly project: Project, schema: RootSchema) {
|
||||||
this.rootNode = this.createNode(schema) as RootNode;
|
autorun(() => {
|
||||||
|
this.nodes.forEach(item => {
|
||||||
|
if (item.parent == null && item !== this.rootNode) {
|
||||||
|
item.purge();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, 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),
|
||||||
|
);
|
||||||
|
this.setupListenActiveNodes();
|
||||||
}
|
}
|
||||||
|
|
||||||
readonly designer = this.project.designer;
|
readonly designer = this.project.designer;
|
||||||
@ -76,20 +89,59 @@ export default class DocumentModel {
|
|||||||
return node ? !node.isPurged : false;
|
return node ? !node.isPurged : false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@obx.val private activeNodes?: Node[];
|
||||||
|
|
||||||
|
private setupListenActiveNodes() {
|
||||||
|
// todo:
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 根据 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 = {
|
||||||
componentName: '#frag',
|
componentName: 'Leaf',
|
||||||
children: data,
|
children: data,
|
||||||
};
|
};
|
||||||
} 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) {
|
||||||
|
if (node.parent) {
|
||||||
|
node.internalSetParent(null);
|
||||||
|
// will move to another position
|
||||||
|
// todo: this.activeNodes?.push(node);
|
||||||
|
}
|
||||||
|
node.internalSetSlotFor(slotFor);
|
||||||
|
node.import(schema, true);
|
||||||
|
} else if (node) {
|
||||||
|
node = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!node) {
|
||||||
|
node = new Node(this, schema, slotFor);
|
||||||
|
// will add
|
||||||
|
// todo: this.activeNodes?.push(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.nodesMap.has(node.id)) {
|
||||||
|
this.nodesMap.get(node.id)!.internalSetParent(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
@ -137,6 +189,7 @@ export default class DocumentModel {
|
|||||||
}
|
}
|
||||||
this.nodesMap.delete(node.id);
|
this.nodesMap.delete(node.id);
|
||||||
this.nodes.delete(node);
|
this.nodes.delete(node);
|
||||||
|
this.selection.remove(node.id);
|
||||||
node.remove();
|
node.remove();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -184,6 +237,12 @@ export default class DocumentModel {
|
|||||||
return this.rootNode.schema as any;
|
return this.rootNode.schema as any;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
import(schema: RootSchema, checkId = false) {
|
||||||
|
this.rootNode.import(schema, checkId);
|
||||||
|
// todo: purge something
|
||||||
|
// todo: select added and active track added
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 导出节点数据
|
* 导出节点数据
|
||||||
*/
|
*/
|
||||||
@ -199,7 +258,7 @@ export default class DocumentModel {
|
|||||||
* 是否已修改
|
* 是否已修改
|
||||||
*/
|
*/
|
||||||
isModified() {
|
isModified() {
|
||||||
// return !this.history.isSavePoint();
|
return !this.history.isSavePoint();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -222,19 +281,20 @@ export default class DocumentModel {
|
|||||||
// TODO: emit simulator mounted
|
// TODO: emit simulator mounted
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FIXME: does needed?
|
||||||
getComponent(componentName: string): any {
|
getComponent(componentName: string): any {
|
||||||
return this.simulator!.getComponent(componentName);
|
return this.simulator!.getComponent(componentName);
|
||||||
}
|
}
|
||||||
|
|
||||||
getComponentConfig(componentName: string, component?: Component | null): ComponentConfig {
|
getComponentMeta(componentName: string): ComponentMeta {
|
||||||
// TODO: guess componentConfig from component by simulator
|
return this.designer.getComponentMeta(
|
||||||
return this.designer.getComponentConfig(componentName);
|
componentName,
|
||||||
|
() => this.simulator?.generateComponentMetadata(componentName) || null,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@obx.ref private _opened = false;
|
||||||
|
@obx.ref private _suspensed = false;
|
||||||
@obx.ref private _opened: boolean = true;
|
|
||||||
@obx.ref private _suspensed: boolean = false;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 是否不是激活的
|
* 是否不是激活的
|
||||||
@ -284,7 +344,11 @@ export default class DocumentModel {
|
|||||||
* 打开,已载入,默认建立时就打开状态,除非手动关闭
|
* 打开,已载入,默认建立时就打开状态,除非手动关闭
|
||||||
*/
|
*/
|
||||||
open(): void {
|
open(): void {
|
||||||
|
const originState = this._opened;
|
||||||
this._opened = true;
|
this._opened = true;
|
||||||
|
if (originState === false) {
|
||||||
|
this.designer.postEvent('document-open', this);
|
||||||
|
}
|
||||||
if (this._suspensed) {
|
if (this._suspensed) {
|
||||||
this.setSuspense(false);
|
this.setSuspense(false);
|
||||||
} else {
|
} else {
|
||||||
@ -303,7 +367,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,6 +1,6 @@
|
|||||||
import { Component } from 'react';
|
import { Component } from 'react';
|
||||||
import DocumentModel from './document-model';
|
import DocumentModel from './document-model';
|
||||||
import { observer } from '@recore/core-obx';
|
import { observer } from '@recore/obx-react';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
|
||||||
@observer
|
@observer
|
||||||
|
|||||||
@ -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());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 删除一个节点
|
* 删除一个节点
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -1,86 +0,0 @@
|
|||||||
import { obx, computed } from '@recore/obx';
|
|
||||||
import { JSExpression, isJSExpression } from '../../schema';
|
|
||||||
|
|
||||||
export default class NodeContent {
|
|
||||||
@obx.ref private _value: string | JSExpression = '';
|
|
||||||
|
|
||||||
@computed get value(): string | JSExpression {
|
|
||||||
return this._value;
|
|
||||||
}
|
|
||||||
|
|
||||||
set value(val: string | JSExpression) {
|
|
||||||
this._value = val;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获得表达式值
|
|
||||||
*/
|
|
||||||
@computed get code() {
|
|
||||||
if (isJSExpression(this._value)) {
|
|
||||||
return this._value.value;
|
|
||||||
}
|
|
||||||
return JSON.stringify(this.value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置表达式值
|
|
||||||
*/
|
|
||||||
set code(code: string) {
|
|
||||||
if (isJSExpression(this._value)) {
|
|
||||||
this._value = {
|
|
||||||
...this._value,
|
|
||||||
value: code,
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
let useCode: boolean = true;
|
|
||||||
try {
|
|
||||||
const v = JSON.parse(code);
|
|
||||||
const t = typeof v;
|
|
||||||
if (v == null) {
|
|
||||||
this._value = '';
|
|
||||||
useCode = false;
|
|
||||||
} else if (t === 'string' || t === 'number' || t === 'boolean') {
|
|
||||||
this._value = String(v);
|
|
||||||
useCode = false;
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
// ignore
|
|
||||||
}
|
|
||||||
if (useCode) {
|
|
||||||
this._value = {
|
|
||||||
type: 'JSExpression',
|
|
||||||
value: code,
|
|
||||||
mock: this._value,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(value: any) {
|
|
||||||
const type = typeof value;
|
|
||||||
if (value == null) {
|
|
||||||
this._value = '';
|
|
||||||
} else if (type === 'string' || type === 'number' || type === 'boolean') {
|
|
||||||
this._value = String(value);
|
|
||||||
} else if (isJSExpression(value)) {
|
|
||||||
this._value = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 是否表达式
|
|
||||||
*/
|
|
||||||
@computed isJSExpression(): boolean {
|
|
||||||
return isJSExpression(this._value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 是否空值
|
|
||||||
*/
|
|
||||||
@computed isEmpty() {
|
|
||||||
if (isJSExpression(this._value)) {
|
|
||||||
return this._value.value === '';
|
|
||||||
}
|
|
||||||
return this._value === '';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,14 +1,10 @@
|
|||||||
import { obx, computed } from '@recore/obx';
|
import { obx, computed } from '@recore/obx';
|
||||||
import { NodeSchema, NodeData, PropsMap, PropsList } from '../../schema';
|
import { NodeSchema, NodeData, PropsMap, PropsList, isDOMText, isJSExpression } from '../../schema';
|
||||||
import Props from './props/props';
|
import Props, { EXTRA_KEY_PREFIX } from './props/props';
|
||||||
import DocumentModel from '../document-model';
|
import DocumentModel from '../document-model';
|
||||||
import NodeChildren from './node-children';
|
import NodeChildren from './node-children';
|
||||||
import Prop from './props/prop';
|
import Prop from './props/prop';
|
||||||
import NodeContent from './node-content';
|
import { ComponentMeta } from '../../component-meta';
|
||||||
import { Component } from '../../simulator';
|
|
||||||
import { ComponentConfig } from '../../component-config';
|
|
||||||
|
|
||||||
const DIRECTIVES = ['condition', 'conditionGroup', 'loop', 'loopArgs', 'title', 'ignore', 'hidden', 'locked'];
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 基础节点
|
* 基础节点
|
||||||
@ -50,21 +46,12 @@ export default class Node {
|
|||||||
* * Component 组件/元件
|
* * Component 组件/元件
|
||||||
*/
|
*/
|
||||||
readonly componentName: string;
|
readonly componentName: string;
|
||||||
protected _props?: Props<Node>;
|
/**
|
||||||
protected _directives?: Props<Node>;
|
* 属性抽象
|
||||||
protected _extras?: Props<Node>;
|
*/
|
||||||
protected _children: NodeChildren | NodeContent;
|
readonly props: Props;
|
||||||
|
protected _children?: NodeChildren;
|
||||||
@obx.ref private _parent: NodeParent | null = null;
|
@obx.ref private _parent: NodeParent | null = null;
|
||||||
@obx.ref private _zLevel = 0;
|
|
||||||
get props(): Props<Node> | undefined {
|
|
||||||
return this._props;
|
|
||||||
}
|
|
||||||
get directives(): Props<Node> | undefined {
|
|
||||||
return this._directives;
|
|
||||||
}
|
|
||||||
get extras(): Props<Node> | undefined {
|
|
||||||
return this._extras;
|
|
||||||
}
|
|
||||||
/**
|
/**
|
||||||
* 父级节点
|
* 父级节点
|
||||||
*/
|
*/
|
||||||
@ -74,20 +61,23 @@ export default class Node {
|
|||||||
/**
|
/**
|
||||||
* 当前节点子集
|
* 当前节点子集
|
||||||
*/
|
*/
|
||||||
get children(): NodeChildren | NodeContent {
|
get children(): NodeChildren | null {
|
||||||
return this._children;
|
return this._children || null;
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* 当前节点深度
|
* 当前节点深度
|
||||||
*/
|
*/
|
||||||
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 {
|
||||||
let t = this.getDirective('x-title');
|
let t = this.getExtraProp('title');
|
||||||
if (!t && this.componentConfig.descriptor) {
|
if (!t && this.componentMeta.descriptor) {
|
||||||
t = this.getProp(this.componentConfig.descriptor, false);
|
t = this.getProp(this.componentMeta.descriptor, false);
|
||||||
}
|
}
|
||||||
if (t) {
|
if (t) {
|
||||||
const v = t.getAsString();
|
const v = t.getAsString();
|
||||||
@ -95,62 +85,66 @@ export default class Node {
|
|||||||
return v;
|
return v;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return this.componentName;
|
return this.componentMeta.title;
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
||||||
this._props = new Props(this, props);
|
let _props: Props;
|
||||||
this._directives = new Props(this, {});
|
if (isNodeParent(this)) {
|
||||||
Object.keys(extras).forEach(key => {
|
_props = new Props(this, props, extras);
|
||||||
if (DIRECTIVES.indexOf(key) > -1) {
|
|
||||||
this._directives!.add((extras as any)[key], key);
|
|
||||||
delete (extras as any)[key];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
this._extras = new Props(this, extras as any);
|
|
||||||
this._children = new NodeChildren(this as NodeParent, children || []);
|
this._children = new NodeChildren(this as NodeParent, children || []);
|
||||||
} else {
|
} else {
|
||||||
this._children = new NodeContent(children);
|
_props = new Props(this, {
|
||||||
|
children: isDOMText(children) || isJSExpression(children) ? children : '',
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
this.props = _props;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 是否一个父亲类节点
|
* 是否一个父亲类节点
|
||||||
*/
|
*/
|
||||||
get isNodeParent(): boolean {
|
get isNodeParent(): boolean {
|
||||||
return this.componentName.charAt(0) !== '#';
|
return this.componentName !== 'Leaf';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 内部方法,请勿使用
|
* 内部方法,请勿使用
|
||||||
*
|
|
||||||
* @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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -163,34 +157,28 @@ export default class Node {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 节点组件类
|
* 悬停高亮
|
||||||
*/
|
*/
|
||||||
@obx.ref get component(): Component | null {
|
hover(flag = true) {
|
||||||
if (this.isNodeParent) {
|
if (flag) {
|
||||||
return this.document.getComponent(this.componentName);
|
this.document.designer.hovering.hover(this);
|
||||||
|
} else {
|
||||||
|
this.document.designer.hovering.unhover(this);
|
||||||
}
|
}
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 节点组件描述
|
* 节点组件描述
|
||||||
*/
|
*/
|
||||||
@obx.ref get componentConfig(): ComponentConfig {
|
@computed get componentMeta(): ComponentMeta {
|
||||||
return this.document.getComponentConfig(this.componentName, this.component);
|
return this.document.getComponentMeta(this.componentName);
|
||||||
}
|
}
|
||||||
|
|
||||||
@obx.ref get propsData(): PropsMap | PropsList | null {
|
@computed get propsData(): PropsMap | PropsList | null {
|
||||||
if (!this.isNodeParent || this.componentName === 'Fragment') {
|
if (!this.isNodeParent || this.componentName === 'Fragment') {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return this.props?.value || null;
|
return this.props.export(true).props || null;
|
||||||
}
|
|
||||||
|
|
||||||
get directivesData(): PropsMap | null {
|
|
||||||
if (!this.isNodeParent) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return this.directives?.value as PropsMap || null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private _conditionGroup: string | null = null;
|
private _conditionGroup: string | null = null;
|
||||||
@ -227,35 +215,48 @@ export default class Node {
|
|||||||
}
|
}
|
||||||
|
|
||||||
wrapWith(schema: NodeSchema) {
|
wrapWith(schema: NodeSchema) {
|
||||||
|
// todo
|
||||||
}
|
}
|
||||||
|
|
||||||
replaceWith(schema: NodeSchema, migrate: boolean = true) {
|
replaceWith(schema: NodeSchema, migrate = true) {
|
||||||
|
// reuse the same id? or replaceSelection
|
||||||
//
|
//
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
getProp(path: string, stash = true): Prop | null {
|
||||||
// TODO
|
return this.props.query(path, stash as any) || null;
|
||||||
// 外部修改,merge 进来,产生一次可恢复的历史数据
|
|
||||||
merge(data: ElementData) {
|
|
||||||
this.elementData = data;
|
|
||||||
const { leadingComments } = data;
|
|
||||||
this.leadingComments = leadingComments ? leadingComments.slice() : [];
|
|
||||||
this.parse();
|
|
||||||
this.mergeChildren(data.children || []);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: 再利用历史数据,不产生历史数据
|
getExtraProp(key: string, stash = true): Prop | null {
|
||||||
reuse(timelineData: NodeSchema) {}
|
return this.props.get(EXTRA_KEY_PREFIX + key, stash) || null;
|
||||||
*/
|
|
||||||
|
|
||||||
getProp(path: string, useStash: boolean = true): Prop | null {
|
|
||||||
return this.props?.query(path, useStash as any) || null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getDirective(name: string, useStash: boolean = true): Prop | null {
|
/**
|
||||||
return this.directives?.get(name, useStash as any) || null;
|
* 获取单个属性值
|
||||||
|
*/
|
||||||
|
getPropValue(path: string): any {
|
||||||
|
return this.getProp(path, false)?.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置单个属性值
|
||||||
|
*/
|
||||||
|
setPropValue(path: string, value: any) {
|
||||||
|
this.getProp(path, true)!.setValue(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置多个属性值,和原有值合并
|
||||||
|
*/
|
||||||
|
mergeProps(props: PropsMap) {
|
||||||
|
this.props.merge(props);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置多个属性值,替换原有值
|
||||||
|
*/
|
||||||
|
setProps(props?: PropsMap | PropsList | null) {
|
||||||
|
this.props.import(props);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -300,32 +301,54 @@ 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 = false) {
|
||||||
|
const { componentName, id, children, props, ...extras } = data;
|
||||||
|
|
||||||
|
if (isNodeParent(this)) {
|
||||||
|
this.props.import(props, extras);
|
||||||
|
(this._children as NodeChildren).import(children, checkId);
|
||||||
|
} else {
|
||||||
|
this.props.get('children', true)!.setValue(isDOMText(children) || isJSExpression(children) ? children : '');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 导出 schema
|
* 导出 schema
|
||||||
* @param serialize 序列化,加 id 标识符,用于储存为操作记录
|
* @param serialize 序列化,加 id 标识符,用于储存为操作记录
|
||||||
*/
|
*/
|
||||||
exportSchema(serialize = false): NodeSchema {
|
export(serialize = false): NodeSchema {
|
||||||
// TODO...
|
const baseSchema: any = {
|
||||||
const schema: any = {
|
componentName: this.componentName === 'Leaf' ? 'Fragment' : this.componentName,
|
||||||
componentName: this.componentName,
|
|
||||||
...this.extras?.value,
|
|
||||||
props: this.props?.value || {},
|
|
||||||
...this.directives?.value,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if (serialize) {
|
if (serialize) {
|
||||||
schema.id = this.id;
|
baseSchema.id = this.id;
|
||||||
}
|
}
|
||||||
if (isNodeParent(this)) {
|
|
||||||
if (this.children.size > 0) {
|
if (!isNodeParent(this)) {
|
||||||
schema.children = this.children.exportSchema(serialize);
|
baseSchema.children = this.props.get('children')?.export(serialize);
|
||||||
}
|
// FIXME!
|
||||||
} else {
|
return baseSchema.children;
|
||||||
schema.children = (this.children as NodeContent).value;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const { props = {}, extras } = this.props.export(serialize) || {};
|
||||||
|
const schema: any = {
|
||||||
|
...baseSchema,
|
||||||
|
props,
|
||||||
|
...extras,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (this.children.size > 0) {
|
||||||
|
schema.children = this.children.export(serialize);
|
||||||
|
}
|
||||||
|
|
||||||
return schema;
|
return schema;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -351,7 +374,7 @@ export default class Node {
|
|||||||
* 2 thisNode before or after otherNode
|
* 2 thisNode before or after otherNode
|
||||||
* 0 thisNode same as otherNode
|
* 0 thisNode same as otherNode
|
||||||
*/
|
*/
|
||||||
comparePosition(otherNode: Node): number {
|
comparePosition(otherNode: Node): PositionNO {
|
||||||
return comparePosition(this, otherNode);
|
return comparePosition(this, otherNode);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -378,18 +401,14 @@ export default class Node {
|
|||||||
if (isNodeParent(this)) {
|
if (isNodeParent(this)) {
|
||||||
this.children.purge();
|
this.children.purge();
|
||||||
}
|
}
|
||||||
this.props?.purge();
|
this.props.purge();
|
||||||
this.directives?.purge();
|
|
||||||
this.extras?.purge();
|
|
||||||
this.document.internalRemoveAndPurgeNode(this);
|
this.document.internalRemoveAndPurgeNode(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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 extras: Props<Node>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isNode(node: any): node is Node {
|
export function isNode(node: any): node is Node {
|
||||||
@ -436,37 +455,43 @@ export function contains(node1: Node, node2: Node): boolean {
|
|||||||
// 8 node1 contained_by node2
|
// 8 node1 contained_by node2
|
||||||
// 2 node1 before or after node2
|
// 2 node1 before or after node2
|
||||||
// 0 node1 same as node2
|
// 0 node1 same as node2
|
||||||
export function comparePosition(node1: Node, node2: Node): number {
|
export enum PositionNO {
|
||||||
|
Contains = 16,
|
||||||
|
ContainedBy = 8,
|
||||||
|
BeforeOrAfter = 2,
|
||||||
|
TheSame = 0,
|
||||||
|
}
|
||||||
|
export function comparePosition(node1: Node, node2: Node): PositionNO {
|
||||||
if (node1 === node2) {
|
if (node1 === node2) {
|
||||||
return 0;
|
return PositionNO.TheSame;
|
||||||
}
|
}
|
||||||
const l1 = node1.zLevel;
|
const l1 = node1.zLevel;
|
||||||
const l2 = node2.zLevel;
|
const l2 = node2.zLevel;
|
||||||
if (l1 === l2) {
|
if (l1 === l2) {
|
||||||
return 2;
|
return PositionNO.BeforeOrAfter;
|
||||||
}
|
}
|
||||||
|
|
||||||
let p: any;
|
let p: any;
|
||||||
if (l1 > l2) {
|
if (l1 < l2) {
|
||||||
p = getZLevelTop(node2, l1);
|
p = getZLevelTop(node2, l1);
|
||||||
if (p && p === node1) {
|
if (p && p === node1) {
|
||||||
return 16;
|
return PositionNO.Contains;
|
||||||
}
|
}
|
||||||
return 2;
|
return PositionNO.BeforeOrAfter;
|
||||||
}
|
}
|
||||||
|
|
||||||
p = getZLevelTop(node1, l2);
|
p = getZLevelTop(node1, l2);
|
||||||
if (p && p === node2) {
|
if (p && p === node2) {
|
||||||
return 8;
|
return PositionNO.ContainedBy;
|
||||||
}
|
}
|
||||||
|
|
||||||
return 2;
|
return PositionNO.BeforeOrAfter;
|
||||||
}
|
}
|
||||||
|
|
||||||
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 (isNode(thing) && (copy || thing.isSlotRoot)) {
|
||||||
thing = thing.exportSchema(false);
|
thing = thing.export(false);
|
||||||
}
|
}
|
||||||
if (isNode(thing)) {
|
if (isNode(thing)) {
|
||||||
node = thing;
|
node = thing;
|
||||||
@ -496,4 +521,3 @@ export function insertChildren(
|
|||||||
}
|
}
|
||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,10 +1,11 @@
|
|||||||
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 | number, Prop> {
|
||||||
const maps = new Map();
|
const maps = new Map();
|
||||||
if (this.space.size > 0) {
|
if (this.space.size > 0) {
|
||||||
this.space.forEach(prop => {
|
this.space.forEach(prop => {
|
||||||
@ -15,34 +16,32 @@ 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;
|
||||||
}
|
}
|
||||||
const pending: Prop[] = [];
|
const pending: Prop[] = [];
|
||||||
for (const prop of this.space) {
|
for (const prop of this.space) {
|
||||||
if (!prop.isUnset()) {
|
if (!prop.isUnset() && !prop.isVirtual()) {
|
||||||
this.space.delete(prop);
|
this.space.delete(prop);
|
||||||
pending.push(prop);
|
pending.push(prop);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (pending.length > 0) {
|
if (pending.length > 0) {
|
||||||
untracked(() => {
|
untracked(() => {
|
||||||
if (before()) {
|
for (const item of pending) {
|
||||||
for (const item of pending) {
|
write(item);
|
||||||
write(item);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
get(key: string): Prop {
|
get(key: string | number): 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,28 +1,33 @@
|
|||||||
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 type ValueTypes = 'unset' | 'literal' | 'map' | 'list' | 'expression' | 'slot';
|
||||||
|
|
||||||
export default class Prop implements IPropParent {
|
export default class Prop implements IPropParent {
|
||||||
readonly isProp = true;
|
readonly isProp = true;
|
||||||
|
|
||||||
readonly id = uniqueId('prop$');
|
readonly id = uniqueId('prop$');
|
||||||
|
|
||||||
private _type: 'unset' | 'literal' | 'map' | 'list' | 'expression' = 'unset';
|
@obx.ref private _type: ValueTypes = 'unset';
|
||||||
/**
|
/**
|
||||||
* 属性类型
|
* 属性类型
|
||||||
*/
|
*/
|
||||||
get type(): 'unset' | 'literal' | 'map' | 'list' | 'expression' {
|
get type(): ValueTypes {
|
||||||
return this._type;
|
return this._type;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -31,37 +36,100 @@ export default class Prop implements IPropParent {
|
|||||||
/**
|
/**
|
||||||
* 属性值
|
* 属性值
|
||||||
*/
|
*/
|
||||||
@computed get value(): CompositeValue {
|
@computed get value(): CompositeValue | UNSET {
|
||||||
if (this._type === 'unset') {
|
return this.export(true);
|
||||||
return null;
|
}
|
||||||
|
|
||||||
|
export(serialize = false): CompositeValue | UNSET {
|
||||||
|
const type = this._type;
|
||||||
|
|
||||||
|
if (type === 'unset') {
|
||||||
|
return UNSET;
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
||||||
}
|
}
|
||||||
const maps: any = {};
|
const maps: any = {};
|
||||||
this.items!.forEach((prop, key) => {
|
this.items!.forEach((prop, key) => {
|
||||||
maps[key] = prop.value;
|
const v = prop.export(serialize);
|
||||||
|
if (v !== UNSET) {
|
||||||
|
maps[key] = v;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
return maps;
|
return maps;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (type === 'list') {
|
if (type === 'list') {
|
||||||
if (!this._items) {
|
if (!this._items) {
|
||||||
return this._items;
|
return this._value;
|
||||||
}
|
}
|
||||||
return this.items!.map(prop => prop.value);
|
return this.items!.map(prop => {
|
||||||
|
const v = prop.export(serialize);
|
||||||
|
return v === UNSET ? null : v;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _code: string | null = null;
|
||||||
|
/**
|
||||||
|
* 获得表达式值
|
||||||
|
*/
|
||||||
|
@computed get code() {
|
||||||
|
if (isJSExpression(this.value)) {
|
||||||
|
return this.value.value;
|
||||||
|
}
|
||||||
|
// todo: JSFunction ...
|
||||||
|
if (this.type === 'slot') {
|
||||||
|
return JSON.stringify(this._slotNode!.export(false));
|
||||||
|
}
|
||||||
|
return this._code != null ? this._code : JSON.stringify(this.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置表达式值
|
||||||
|
*/
|
||||||
|
set code(code: string) {
|
||||||
|
if (isJSExpression(this._value)) {
|
||||||
|
this.setValue({
|
||||||
|
...this._value,
|
||||||
|
value: code,
|
||||||
|
});
|
||||||
|
this._code = code;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const v = JSON.parse(code);
|
||||||
|
this.setValue(v);
|
||||||
|
this._code = code;
|
||||||
|
return;
|
||||||
|
} catch (e) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setValue({
|
||||||
|
type: 'JSExpression',
|
||||||
|
value: code,
|
||||||
|
mock: this._value,
|
||||||
|
});
|
||||||
|
this._code = code;
|
||||||
|
}
|
||||||
|
|
||||||
@computed getAsString(): string {
|
@computed getAsString(): string {
|
||||||
if (this.type === 'literal') {
|
if (this.type === 'literal') {
|
||||||
return this._value ? String(this._value) : '';
|
return this._value ? String(this._value) : '';
|
||||||
@ -72,18 +140,22 @@ export default class Prop implements IPropParent {
|
|||||||
/**
|
/**
|
||||||
* set value, val should be JSON Object
|
* set value, val should be JSON Object
|
||||||
*/
|
*/
|
||||||
set value(val: CompositeValue) {
|
setValue(val: CompositeValue) {
|
||||||
this._value = val;
|
this._value = val;
|
||||||
|
this._code = null;
|
||||||
const t = typeof val;
|
const t = typeof val;
|
||||||
if (val == null) {
|
if (val == null) {
|
||||||
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 +169,50 @@ 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;
|
|
||||||
|
@computed getValue(): CompositeValue {
|
||||||
|
const v = this.export(true);
|
||||||
|
if (v === UNSET) {
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 as any);
|
||||||
|
}
|
||||||
|
this.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -121,33 +229,32 @@ export default class Prop implements IPropParent {
|
|||||||
return this._type === 'unset';
|
return this._type === 'unset';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
isVirtual() {
|
||||||
* 值是否包含表达式
|
return typeof this.key === 'string' && this.key.charAt(0) === '!';
|
||||||
* 包含 JSExpresion | JSSlot 等值
|
|
||||||
*/
|
|
||||||
@computed isContainJSExpression(): boolean {
|
|
||||||
const type = this._type;
|
|
||||||
if (type === 'expression') {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (type === 'literal' || type === 'unset') {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if ((type === 'list' || type === 'map') && this.items) {
|
|
||||||
return this.items.some(item => item.isContainJSExpression());
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// TODO: improve this logic
|
||||||
* 是否简单 JSON 数据
|
compare(other: Prop | null): number {
|
||||||
*/
|
if (!other || other.isUnset()) {
|
||||||
@computed isJSON() {
|
return this.isUnset() ? 0 : 2;
|
||||||
return !this.isContainJSExpression();
|
}
|
||||||
|
if (other.type !== this.type) {
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
// list
|
||||||
|
if (this.type === 'list') {
|
||||||
|
return this.size === other.size ? 1 : 2;
|
||||||
|
}
|
||||||
|
if (this.type === 'map') {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 'literal' | 'map' | 'expression' | 'slot'
|
||||||
|
return this.code === other.code ? 0 : 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
@obx.val private _items: Prop[] | null = null;
|
@obx.val private _items: Prop[] | null = null;
|
||||||
@obx.val private _maps: Map<string, Prop> | null = null;
|
@obx.val private _maps: Map<string | number, Prop> | null = null;
|
||||||
@computed private get items(): Prop[] | null {
|
@computed private get items(): Prop[] | null {
|
||||||
let _items: any;
|
let _items: any;
|
||||||
untracked(() => {
|
untracked(() => {
|
||||||
@ -182,14 +289,14 @@ export default class Prop implements IPropParent {
|
|||||||
}
|
}
|
||||||
return _items;
|
return _items;
|
||||||
}
|
}
|
||||||
@computed private get maps(): Map<string, Prop> | null {
|
@computed private get maps(): Map<string | number, Prop> | null {
|
||||||
if (!this.items || this.items.length < 1) {
|
if (!this.items) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return this._maps;
|
return this._maps;
|
||||||
}
|
}
|
||||||
|
|
||||||
private stash: StashSpace | undefined;
|
private stash: PropStash | undefined;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 键值
|
* 键值
|
||||||
@ -200,14 +307,17 @@ 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.setValue(value);
|
||||||
}
|
}
|
||||||
this.key = key;
|
this.key = key;
|
||||||
this.spread = spread;
|
this.spread = spread;
|
||||||
@ -215,58 +325,51 @@ export default class Prop implements IPropParent {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取某个属性
|
* 获取某个属性
|
||||||
* @param stash 强制
|
* @param stash 如果不存在,临时获取一个待写入
|
||||||
*/
|
*/
|
||||||
get(path: string, stash: false): Prop | null;
|
get(path: string | number, stash = true): Prop | null {
|
||||||
/**
|
|
||||||
* 获取某个属性, 如果不存在,临时获取一个待写入
|
|
||||||
* @param stash 强制
|
|
||||||
*/
|
|
||||||
get(path: string, stash: true): Prop;
|
|
||||||
/**
|
|
||||||
* 获取某个属性, 如果不存在,临时获取一个待写入
|
|
||||||
*/
|
|
||||||
get(path: string): Prop;
|
|
||||||
get(path: string, stash = true) {
|
|
||||||
const type = this._type;
|
const type = this._type;
|
||||||
if (type !== 'map' && type !== 'unset' && !stash) {
|
if (type !== 'map' && type !== 'list' && type !== 'unset' && !stash) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const maps = type === 'map' ? this.maps : null;
|
const maps = type === 'map' ? this.maps : null;
|
||||||
|
const items = type === 'list' ? this.items : null;
|
||||||
|
|
||||||
let prop: any = maps ? maps.get(path) : null;
|
|
||||||
|
|
||||||
if (prop) {
|
|
||||||
return prop;
|
|
||||||
}
|
|
||||||
|
|
||||||
const i = path.indexOf('.');
|
|
||||||
let entry = path;
|
let entry = path;
|
||||||
let nest = '';
|
let nest = '';
|
||||||
if (i > 0) {
|
if (typeof path !== 'number') {
|
||||||
nest = path.slice(i + 1);
|
const i = path.indexOf('.');
|
||||||
if (nest) {
|
if (i > 0) {
|
||||||
entry = path.slice(0, i);
|
nest = path.slice(i + 1);
|
||||||
prop = maps ? maps.get(entry) : null;
|
if (nest) {
|
||||||
if (prop) {
|
entry = path.slice(0, i);
|
||||||
return prop.get(nest, stash);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let prop: any;
|
||||||
|
if (type === 'list') {
|
||||||
|
if (isValidArrayIndex(entry, this.size)) {
|
||||||
|
prop = items![entry];
|
||||||
|
}
|
||||||
|
} else if (type === 'map') {
|
||||||
|
prop = maps?.get(entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (prop) {
|
||||||
|
return nest ? prop.get(nest, stash) : prop;
|
||||||
|
}
|
||||||
|
|
||||||
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
|
if (item.key) {
|
||||||
this.set(String(item.key), item);
|
this.set(item.key, item, true);
|
||||||
item.parent = this;
|
}
|
||||||
},
|
item.parent = this;
|
||||||
() => {
|
});
|
||||||
return true;
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
prop = this.stash.get(entry);
|
prop = this.stash.get(entry);
|
||||||
if (nest) {
|
if (nest) {
|
||||||
@ -317,7 +420,7 @@ export default class Prop implements IPropParent {
|
|||||||
/**
|
/**
|
||||||
* 元素个数
|
* 元素个数
|
||||||
*/
|
*/
|
||||||
size(): number {
|
get size(): number {
|
||||||
return this.items?.length || 0;
|
return this.items?.length || 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -332,7 +435,7 @@ export default class Prop implements IPropParent {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
if (type === 'unset' || (force && type !== 'list')) {
|
if (type === 'unset' || (force && type !== 'list')) {
|
||||||
this.value = [];
|
this.setValue([]);
|
||||||
}
|
}
|
||||||
const prop = new Prop(this, value);
|
const prop = new Prop(this, value);
|
||||||
this.items!.push(prop);
|
this.items!.push(prop);
|
||||||
@ -344,29 +447,44 @@ export default class Prop implements IPropParent {
|
|||||||
*
|
*
|
||||||
* @param force 强制
|
* @param force 强制
|
||||||
*/
|
*/
|
||||||
set(key: string, value: CompositeValue | Prop, force = false) {
|
set(key: string | number, value: CompositeValue | Prop, force = false) {
|
||||||
const type = this._type;
|
const type = this._type;
|
||||||
if (type !== 'map' && type !== 'unset' && !force) {
|
if (type !== 'map' && type !== 'list' && type !== 'unset' && !force) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
if (type === 'unset' || (force && type !== 'map')) {
|
if (type === 'unset' || (force && type !== 'map')) {
|
||||||
this.value = {};
|
if (isValidArrayIndex(key)) {
|
||||||
|
if (type !== 'list') {
|
||||||
|
this.setValue([]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.setValue({});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
const prop = isProp(value) ? value : new Prop(this, value, key);
|
const prop = isProp(value) ? value : new Prop(this, value, key);
|
||||||
const items = this.items!;
|
const items = this.items!;
|
||||||
const maps = this.maps!;
|
if (this.type === 'list') {
|
||||||
const orig = maps.get(key);
|
if (!isValidArrayIndex(key)) {
|
||||||
if (orig) {
|
return null;
|
||||||
// replace
|
}
|
||||||
const i = items.indexOf(orig);
|
items[key] = prop;
|
||||||
if (i > -1) {
|
} else if (this.maps) {
|
||||||
items.splice(i, 1, prop)[0].purge();
|
const maps = this.maps;
|
||||||
|
const orig = maps.get(key);
|
||||||
|
if (orig) {
|
||||||
|
// replace
|
||||||
|
const i = items.indexOf(orig);
|
||||||
|
if (i > -1) {
|
||||||
|
items.splice(i, 1, prop)[0].purge();
|
||||||
|
}
|
||||||
|
maps.set(key, prop);
|
||||||
|
} else {
|
||||||
|
// push
|
||||||
|
items.push(prop);
|
||||||
|
maps.set(key, prop);
|
||||||
}
|
}
|
||||||
maps.set(key, prop);
|
|
||||||
} else {
|
} else {
|
||||||
// push
|
return null;
|
||||||
items.push(prop);
|
|
||||||
maps.set(key, prop);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return prop;
|
return prop;
|
||||||
@ -401,6 +519,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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -458,3 +579,8 @@ export default class Prop implements IPropParent {
|
|||||||
export function isProp(obj: any): obj is Prop {
|
export function isProp(obj: any): obj is Prop {
|
||||||
return obj && obj.isProp;
|
return obj && obj.isProp;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isValidArrayIndex(key: any, limit = -1): key is number {
|
||||||
|
const n = parseFloat(String(key));
|
||||||
|
return n >= 0 && Math.floor(n) === n && isFinite(n) && (limit < 0 || n < limit);
|
||||||
|
}
|
||||||
|
|||||||
@ -1,14 +1,13 @@
|
|||||||
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, UNSET } from './prop';
|
||||||
|
import Node from '../node';
|
||||||
|
|
||||||
export const UNSET = Symbol.for('unset');
|
export const EXTRA_KEY_PREFIX = '__';
|
||||||
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 +22,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;
|
||||||
},
|
});
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 元素个数
|
* 元素个数
|
||||||
@ -40,58 +38,105 @@ export default class Props<O = any> implements IPropParent {
|
|||||||
return this.items.length;
|
return this.items.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
@computed get value(): PropsMap | PropsList | null {
|
|
||||||
if (this.items.length < 1) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
if (this.type === 'list') {
|
|
||||||
return this.items.map(item => ({
|
|
||||||
spread: item.spread,
|
|
||||||
name: item.key as string,
|
|
||||||
value: item.value,
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
const maps: any = {};
|
|
||||||
this.items.forEach(prop => {
|
|
||||||
if (prop.key) {
|
|
||||||
maps[prop.key] = prop.value;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return maps;
|
|
||||||
}
|
|
||||||
|
|
||||||
@obx type: 'map' | 'list' = 'map';
|
@obx type: 'map' | 'list' = 'map';
|
||||||
|
|
||||||
constructor(readonly owner: O, value?: PropsMap | PropsList | null) {
|
constructor(readonly owner: Node, value?: PropsMap | PropsList | null, extras?: object) {
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
if (extras) {
|
||||||
|
Object.keys(extras).forEach(key => {
|
||||||
|
this.items.push(new Prop(this, (extras as any)[key], EXTRA_KEY_PREFIX + key));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
import(value?: PropsMap | PropsList | null, extras?: object) {
|
||||||
|
this.stash.clear();
|
||||||
|
const originItems = this.items;
|
||||||
if (Array.isArray(value)) {
|
if (Array.isArray(value)) {
|
||||||
this.type = 'list';
|
this.type = 'list';
|
||||||
this.items = value.map(item => new Prop(this, item.value, item.name, item.spread));
|
this.items = value.map(item => new Prop(this, item.value, item.name, item.spread));
|
||||||
} else if (value != null) {
|
} else if (value != null) {
|
||||||
this.type = 'map';
|
this.type = 'map';
|
||||||
this.items = Object.keys(value).map(key => new Prop(this, value[key], key));
|
this.items = Object.keys(value).map(key => new Prop(this, value[key], key));
|
||||||
|
} else {
|
||||||
|
this.type = 'map';
|
||||||
|
this.items = [];
|
||||||
}
|
}
|
||||||
|
if (extras) {
|
||||||
|
Object.keys(extras).forEach(key => {
|
||||||
|
this.items.push(new Prop(this, (extras as any)[key], EXTRA_KEY_PREFIX + key));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
originItems.forEach(item => item.purge());
|
||||||
|
}
|
||||||
|
|
||||||
|
merge(value: PropsMap) {
|
||||||
|
Object.keys(value).forEach(key => {
|
||||||
|
this.query(key, true)!.setValue(value[key]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export(serialize = false): { props?: PropsMap | PropsList; extras?: object } {
|
||||||
|
if (this.items.length < 1) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
let props: any = {};
|
||||||
|
const extras: any = {};
|
||||||
|
if (this.type === 'list') {
|
||||||
|
props = [];
|
||||||
|
this.items.forEach(item => {
|
||||||
|
let value = item.export(serialize);
|
||||||
|
if (value === UNSET) {
|
||||||
|
value = null;
|
||||||
|
}
|
||||||
|
let name = item.key as string;
|
||||||
|
if (name && typeof name === 'string' && name.startsWith(EXTRA_KEY_PREFIX)) {
|
||||||
|
name = name.substr(EXTRA_KEY_PREFIX.length);
|
||||||
|
extras[name] = value;
|
||||||
|
} else {
|
||||||
|
props.push({
|
||||||
|
spread: item.spread,
|
||||||
|
name,
|
||||||
|
value,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.items.forEach(item => {
|
||||||
|
let name = item.key as string;
|
||||||
|
if (name == null) {
|
||||||
|
// todo ...spread
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let value = item.export(serialize);
|
||||||
|
if (value === UNSET) {
|
||||||
|
value = null;
|
||||||
|
}
|
||||||
|
if (typeof name === 'string' && name.startsWith(EXTRA_KEY_PREFIX)) {
|
||||||
|
name = name.substr(EXTRA_KEY_PREFIX.length);
|
||||||
|
extras[name] = value;
|
||||||
|
} else {
|
||||||
|
props[name] = value;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return { props, extras };
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 根据 path 路径查询属性,如果没有则临时生成一个
|
|
||||||
*/
|
|
||||||
query(path: string): Prop;
|
|
||||||
/**
|
/**
|
||||||
* 根据 path 路径查询属性
|
* 根据 path 路径查询属性
|
||||||
*
|
*
|
||||||
* @useStash 如果没有则临时生成一个
|
* @param stash 如果没有则临时生成一个
|
||||||
*/
|
*/
|
||||||
query(path: string, useStash: true): Prop;
|
query(path: string, stash = true): Prop | null {
|
||||||
/**
|
return this.get(path, stash);
|
||||||
* 根据 path 路径查询属性
|
// todo: future support list search
|
||||||
*/
|
|
||||||
query(path: string, useStash: false): Prop | null;
|
|
||||||
/**
|
|
||||||
* 根据 path 路径查询属性
|
|
||||||
*
|
|
||||||
* @useStash 如果没有则临时生成一个
|
|
||||||
*/
|
|
||||||
query(path: string, useStash: boolean = true) {
|
|
||||||
let matchedLength = 0;
|
let matchedLength = 0;
|
||||||
let firstMatched = null;
|
let firstMatched = null;
|
||||||
if (this.items) {
|
if (this.items) {
|
||||||
@ -122,7 +167,7 @@ export default class Props<O = any> implements IPropParent {
|
|||||||
if (firstMatched) {
|
if (firstMatched) {
|
||||||
ret = firstMatched.get(path.slice(matchedLength + 1), true);
|
ret = firstMatched.get(path.slice(matchedLength + 1), true);
|
||||||
}
|
}
|
||||||
if (!ret && useStash) {
|
if (!ret && stash) {
|
||||||
return this.stash.get(path);
|
return this.stash.get(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -131,20 +176,26 @@ export default class Props<O = any> implements IPropParent {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取某个属性, 如果不存在,临时获取一个待写入
|
* 获取某个属性, 如果不存在,临时获取一个待写入
|
||||||
* @param useStash 强制
|
* @param stash 强制
|
||||||
*/
|
*/
|
||||||
get(path: string, useStash: true): Prop;
|
get(path: string, stash = false): Prop | null {
|
||||||
/**
|
let entry = path;
|
||||||
* 获取某个属性
|
let nest = '';
|
||||||
* @param useStash 强制
|
const i = path.indexOf('.');
|
||||||
*/
|
if (i > 0) {
|
||||||
get(path: string, useStash: false): Prop | null;
|
nest = path.slice(i + 1);
|
||||||
/**
|
if (nest) {
|
||||||
* 获取某个属性
|
entry = path.slice(0, i);
|
||||||
*/
|
}
|
||||||
get(path: string): Prop | null;
|
}
|
||||||
get(name: string, useStash = false) {
|
|
||||||
return this.maps.get(name) || (useStash && this.stash.get(name)) || null;
|
const prop = this.maps.get(entry) || (stash && this.stash.get(entry)) || null;
|
||||||
|
|
||||||
|
if (prop) {
|
||||||
|
return nest ? prop.get(nest, stash) : prop;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -17,9 +17,9 @@ import Props from './props/props';
|
|||||||
* meta
|
* meta
|
||||||
* state
|
* state
|
||||||
* defaultProps
|
* defaultProps
|
||||||
|
* dataSource
|
||||||
* lifeCycles
|
* lifeCycles
|
||||||
* methods
|
* methods
|
||||||
* dataSource
|
|
||||||
* css
|
* css
|
||||||
*
|
*
|
||||||
* [Directives **not used**]
|
* [Directives **not used**]
|
||||||
@ -42,30 +42,23 @@ export default class RootNode extends Node implements NodeParent {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
get nextSibling() {
|
get nextSibling() {
|
||||||
return null
|
return null;
|
||||||
}
|
}
|
||||||
get prevSibling() {
|
get prevSibling() {
|
||||||
return null
|
return null;
|
||||||
}
|
}
|
||||||
get zLevel() {
|
get zLevel() {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
get parent() {
|
get parent() {
|
||||||
return null
|
return null;
|
||||||
}
|
}
|
||||||
get children(): NodeChildren {
|
get children(): NodeChildren {
|
||||||
return this._children as NodeChildren;
|
return this._children as NodeChildren;
|
||||||
}
|
}
|
||||||
get props(): Props<RootNode> {
|
internalSetParent(parent: null) {
|
||||||
return this._props as any;
|
// empty
|
||||||
}
|
}
|
||||||
get extras(): Props<RootNode> {
|
|
||||||
return this._extras as any;
|
|
||||||
}
|
|
||||||
get directives(): Props<RootNode> {
|
|
||||||
return this._directives as any;
|
|
||||||
}
|
|
||||||
internalSetParent(parent: null) {}
|
|
||||||
|
|
||||||
constructor(readonly document: DocumentModel, rootSchema: RootSchema) {
|
constructor(readonly document: DocumentModel, rootSchema: RootSchema) {
|
||||||
super(document, rootSchema);
|
super(document, rootSchema);
|
||||||
|
|||||||
@ -1,87 +1,108 @@
|
|||||||
import Node, { comparePosition } from './node/node';
|
import Node, { comparePosition, PositionNO } from './node/node';
|
||||||
import { obx } from '@recore/obx';
|
import { obx } from '@recore/obx';
|
||||||
import DocumentModel from './document-model';
|
import DocumentModel from './document-model';
|
||||||
|
import { EventEmitter } from 'events';
|
||||||
|
|
||||||
export class Selection {
|
export class Selection {
|
||||||
@obx.val private selected: string[] = [];
|
private emitter = new EventEmitter();
|
||||||
|
@obx.val private _selected: string[] = [];
|
||||||
|
/**
|
||||||
|
* 选中的节点 id
|
||||||
|
*/
|
||||||
|
get selected(): string[] {
|
||||||
|
return this._selected;
|
||||||
|
}
|
||||||
|
|
||||||
constructor(private doc: DocumentModel) {}
|
constructor(readonly doc: DocumentModel) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 选中
|
* 选中
|
||||||
*/
|
*/
|
||||||
select(id: string) {
|
select(id: string) {
|
||||||
if (this.selected.length === 1 && this.selected.indexOf(id) > -1) {
|
if (this._selected.length === 1 && this._selected.indexOf(id) > -1) {
|
||||||
// avoid cause reaction
|
// avoid cause reaction
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.selected = [id];
|
this._selected = [id];
|
||||||
|
this.emitter.emit('selectionchange', this._selected);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 批量选中
|
* 批量选中
|
||||||
*/
|
*/
|
||||||
selectAll(ids: string[]) {
|
selectAll(ids: string[]) {
|
||||||
this.selected = ids;
|
this._selected = ids;
|
||||||
|
this.emitter.emit('selectionchange', this._selected);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 清除选中
|
* 清除选中
|
||||||
*/
|
*/
|
||||||
clear() {
|
clear() {
|
||||||
this.selected = [];
|
if (this._selected.length < 1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this._selected = [];
|
||||||
|
this.emitter.emit('selectionchange', this._selected);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 整理选中
|
* 整理选中
|
||||||
*/
|
*/
|
||||||
dispose() {
|
dispose() {
|
||||||
let i = this.selected.length;
|
const l = this._selected.length;
|
||||||
|
let i = l;
|
||||||
while (i-- > 0) {
|
while (i-- > 0) {
|
||||||
const id = this.selected[i];
|
const id = this._selected[i];
|
||||||
if (!this.doc.hasNode(id)) {
|
if (!this.doc.hasNode(id)) {
|
||||||
this.selected.splice(i, 1);
|
this._selected.splice(i, 1);
|
||||||
} else {
|
|
||||||
this.selected[i] = id;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (this._selected.length !== l) {
|
||||||
|
this.emitter.emit('selectionchange', this._selected);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 添加选中
|
* 添加选中
|
||||||
*/
|
*/
|
||||||
add(id: string) {
|
add(id: string) {
|
||||||
if (this.selected.indexOf(id) > -1) {
|
if (this._selected.indexOf(id) > -1) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.selected.push(id);
|
this._selected.push(id);
|
||||||
|
this.emitter.emit('selectionchange', this._selected);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 是否选中
|
* 是否选中
|
||||||
*/
|
*/
|
||||||
has(id: string) {
|
has(id: string) {
|
||||||
return this.selected.indexOf(id) > -1;
|
return this._selected.indexOf(id) > -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 移除选中
|
* 移除选中
|
||||||
*/
|
*/
|
||||||
remove(id: string) {
|
remove(id: string) {
|
||||||
let i = this.selected.indexOf(id);
|
const i = this._selected.indexOf(id);
|
||||||
if (i > -1) {
|
if (i > -1) {
|
||||||
this.selected.splice(i, 1);
|
this._selected.splice(i, 1);
|
||||||
|
this.emitter.emit('selectionchange', this._selected);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 选区是否包含节点
|
* 选区是否包含节点
|
||||||
*/
|
*/
|
||||||
containsNode(node: Node) {
|
containsNode(node: Node, excludeRoot = false) {
|
||||||
for (const id of this.selected) {
|
for (const id of this._selected) {
|
||||||
const parent = this.doc.getNode(id);
|
const parent = this.doc.getNode(id);
|
||||||
|
if (excludeRoot && parent === this.doc.rootNode) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
if (parent?.contains(node)) {
|
if (parent?.contains(node)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -94,7 +115,7 @@ export class Selection {
|
|||||||
*/
|
*/
|
||||||
getNodes() {
|
getNodes() {
|
||||||
const nodes = [];
|
const nodes = [];
|
||||||
for (const id of this.selected) {
|
for (const id of this._selected) {
|
||||||
const node = this.doc.getNode(id);
|
const node = this.doc.getNode(id);
|
||||||
if (node) {
|
if (node) {
|
||||||
nodes.push(node);
|
nodes.push(node);
|
||||||
@ -106,11 +127,12 @@ export class Selection {
|
|||||||
/**
|
/**
|
||||||
* 获取顶层选区节点, 场景:拖拽时,建立蒙层,只蒙在最上层
|
* 获取顶层选区节点, 场景:拖拽时,建立蒙层,只蒙在最上层
|
||||||
*/
|
*/
|
||||||
getTopNodes() {
|
getTopNodes(includeRoot = false) {
|
||||||
const nodes = [];
|
const nodes = [];
|
||||||
for (const id of this.selected) {
|
for (const id of this._selected) {
|
||||||
const node = this.doc.getNode(id);
|
const node = this.doc.getNode(id);
|
||||||
if (!node) {
|
// 排除根节点
|
||||||
|
if (!node || (!includeRoot && node === this.doc.rootNode)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let i = nodes.length;
|
let i = nodes.length;
|
||||||
@ -118,12 +140,12 @@ export class Selection {
|
|||||||
while (i-- > 0) {
|
while (i-- > 0) {
|
||||||
const n = comparePosition(nodes[i], node);
|
const n = comparePosition(nodes[i], node);
|
||||||
// nodes[i] contains node
|
// nodes[i] contains node
|
||||||
if (n === 16 || n === 0) {
|
if (n === PositionNO.Contains || n === PositionNO.TheSame) {
|
||||||
isTop = false;
|
isTop = false;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
// node contains nodes[i], delete nodes[i]
|
// node contains nodes[i], delete nodes[i]
|
||||||
if (n === 8) {
|
if (n === PositionNO.ContainedBy) {
|
||||||
nodes.splice(i, 1);
|
nodes.splice(i, 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -134,4 +156,11 @@ export class Selection {
|
|||||||
}
|
}
|
||||||
return nodes;
|
return nodes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onSelectionChange(fn: () => void): () => void {
|
||||||
|
this.emitter.on('selectionchange', fn);
|
||||||
|
return () => {
|
||||||
|
this.emitter.removeListener('selectionchange', fn);
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -19,7 +19,7 @@ export interface LocateEvent {
|
|||||||
/**
|
/**
|
||||||
* 原始事件
|
* 原始事件
|
||||||
*/
|
*/
|
||||||
readonly originalEvent: MouseEvent;
|
readonly originalEvent: MouseEvent | DragEvent;
|
||||||
/**
|
/**
|
||||||
* 拖拽对象
|
* 拖拽对象
|
||||||
*/
|
*/
|
||||||
@ -124,7 +124,7 @@ const SHAKE_DISTANCE = 4;
|
|||||||
/**
|
/**
|
||||||
* mouse shake check
|
* mouse shake check
|
||||||
*/
|
*/
|
||||||
export function isShaken(e1: MouseEvent, e2: MouseEvent): boolean {
|
export function isShaken(e1: MouseEvent | DragEvent, e2: MouseEvent | DragEvent): boolean {
|
||||||
if ((e1 as any).shaken) {
|
if ((e1 as any).shaken) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -134,6 +134,19 @@ export function isShaken(e1: MouseEvent, e2: MouseEvent): boolean {
|
|||||||
return Math.pow(e1.clientY - e2.clientY, 2) + Math.pow(e1.clientX - e2.clientX, 2) > SHAKE_DISTANCE;
|
return Math.pow(e1.clientY - e2.clientY, 2) + Math.pow(e1.clientX - e2.clientX, 2) > SHAKE_DISTANCE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isInvalidPoint(e: any, last: any): boolean {
|
||||||
|
return (
|
||||||
|
e.clientX === 0 &&
|
||||||
|
e.clientY === 0 &&
|
||||||
|
last &&
|
||||||
|
(Math.abs(last.clientX - e.clientX) > 5 || Math.abs(last.clientY - e.clientY) > 5)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function isSameAs(e1: MouseEvent | DragEvent, e2: MouseEvent | DragEvent): boolean {
|
||||||
|
return e1.clientY === e2.clientY && e1.clientX === e2.clientX;
|
||||||
|
}
|
||||||
|
|
||||||
export function setShaken(e: any) {
|
export function setShaken(e: any) {
|
||||||
e.shaken = true;
|
e.shaken = true;
|
||||||
}
|
}
|
||||||
@ -145,17 +158,36 @@ function getSourceSensor(dragObject: DragObject): ISimulator | null {
|
|||||||
return dragObject.nodes[0]?.document.simulator || null;
|
return dragObject.nodes[0]?.document.simulator || null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function makeSimulatorListener(masterSensors: ISimulator[]): (fn: (sdoc: Document) => void) => void {
|
function makeEventsHandler(
|
||||||
return (fn: (sdoc: Document) => void) => {
|
boostEvent: MouseEvent | DragEvent,
|
||||||
masterSensors.forEach(sim => {
|
sensors: ISimulator[],
|
||||||
|
): (fn: (sdoc: Document) => void) => void {
|
||||||
|
const topDoc = window.top.document;
|
||||||
|
const sourceDoc = boostEvent.view?.document || topDoc;
|
||||||
|
const boostPrevented = boostEvent.defaultPrevented;
|
||||||
|
const docs = new Set<Document>();
|
||||||
|
if (boostPrevented || isDragEvent(boostEvent)) {
|
||||||
|
docs.add(topDoc);
|
||||||
|
}
|
||||||
|
docs.add(sourceDoc);
|
||||||
|
if (sourceDoc !== topDoc || isDragEvent(boostEvent)) {
|
||||||
|
sensors.forEach(sim => {
|
||||||
const sdoc = sim.contentDocument;
|
const sdoc = sim.contentDocument;
|
||||||
if (sdoc) {
|
if (sdoc) {
|
||||||
fn(sdoc);
|
docs.add(sdoc);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return (handle: (sdoc: Document) => void) => {
|
||||||
|
docs.forEach(doc => handle(doc));
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isDragEvent(e: any): e is DragEvent {
|
||||||
|
return e?.type?.substr(0, 4) === 'drag';
|
||||||
|
}
|
||||||
|
|
||||||
export default class Dragon {
|
export default class Dragon {
|
||||||
private sensors: ISensor[] = [];
|
private sensors: ISensor[] = [];
|
||||||
|
|
||||||
@ -167,14 +199,17 @@ export default class Dragon {
|
|||||||
return this._activeSensor;
|
return this._activeSensor;
|
||||||
}
|
}
|
||||||
|
|
||||||
@obx.ref private _dragging: boolean = false;
|
@obx.ref private _dragging = false;
|
||||||
get dragging(): boolean {
|
get dragging(): boolean {
|
||||||
return this._dragging;
|
return this._dragging;
|
||||||
}
|
}
|
||||||
|
|
||||||
private emitter = new EventEmitter();
|
private emitter = new EventEmitter();
|
||||||
|
private emptyImage: HTMLImageElement = new Image();
|
||||||
|
|
||||||
constructor(readonly designer: Designer) {}
|
constructor(readonly designer: Designer) {
|
||||||
|
this.emptyImage.src = 'data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==';
|
||||||
|
}
|
||||||
|
|
||||||
from(shell: Element, boost: (e: MouseEvent) => DragObject | null) {
|
from(shell: Element, boost: (e: MouseEvent) => DragObject | null) {
|
||||||
const mousedown = (e: MouseEvent) => {
|
const mousedown = (e: MouseEvent) => {
|
||||||
@ -197,21 +232,17 @@ export default class Dragon {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
boost(dragObject: DragObject, boostEvent: MouseEvent) {
|
boost(dragObject: DragObject, boostEvent: MouseEvent | DragEvent) {
|
||||||
const doc = document;
|
|
||||||
const sourceDoc = boostEvent.view?.document;
|
|
||||||
const masterSensors = this.getMasterSensors();
|
|
||||||
const listenSimulators = !sourceDoc || sourceDoc === doc ? makeSimulatorListener(masterSensors) : null;
|
|
||||||
const alwaysListen = listenSimulators ? doc : sourceDoc!;
|
|
||||||
const designer = this.designer;
|
const designer = this.designer;
|
||||||
const newBie = dragObject.type !== DragObjectType.Node;
|
const masterSensors = this.getMasterSensors();
|
||||||
|
const handleEvents = makeEventsHandler(boostEvent, masterSensors);
|
||||||
|
const newBie = !isDragNodeObject(dragObject);
|
||||||
|
const forceCopyState = isDragNodeObject(dragObject) && dragObject.nodes.some(node => node.isSlotRoot);
|
||||||
|
const isBoostFromDragAPI = boostEvent.type.substr(0, 4) === 'drag';
|
||||||
let lastSensor: ISensor | undefined;
|
let lastSensor: ISensor | undefined;
|
||||||
|
|
||||||
this._dragging = false;
|
this._dragging = false;
|
||||||
|
|
||||||
// 禁用默认的文稿拖选
|
|
||||||
this.setNativeSelection(false);
|
|
||||||
|
|
||||||
const checkesc = (e: KeyboardEvent) => {
|
const checkesc = (e: KeyboardEvent) => {
|
||||||
if (e.keyCode === 27) {
|
if (e.keyCode === 27) {
|
||||||
designer.clearLocation();
|
designer.clearLocation();
|
||||||
@ -219,20 +250,47 @@ export default class Dragon {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const checkcopy = (e: MouseEvent) => {
|
let copy = false;
|
||||||
|
const checkcopy = (e: MouseEvent | DragEvent | KeyboardEvent) => {
|
||||||
|
if (isDragEvent(e) && e.dataTransfer) {
|
||||||
|
if (newBie || forceCopyState) {
|
||||||
|
e.dataTransfer.dropEffect = 'copy';
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (newBie) {
|
if (newBie) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (e.altKey || e.ctrlKey) {
|
if (e.altKey || e.ctrlKey) {
|
||||||
|
copy = true;
|
||||||
this.setCopyState(true);
|
this.setCopyState(true);
|
||||||
|
if (isDragEvent(e) && e.dataTransfer) {
|
||||||
|
e.dataTransfer.dropEffect = 'copy';
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
this.setCopyState(false);
|
copy = false;
|
||||||
|
if (!forceCopyState) {
|
||||||
|
this.setCopyState(false);
|
||||||
|
if (isDragEvent(e) && e.dataTransfer) {
|
||||||
|
e.dataTransfer.dropEffect = 'move';
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const drag = (e: MouseEvent) => {
|
let lastArrive: any;
|
||||||
|
const drag = (e: MouseEvent | DragEvent) => {
|
||||||
checkcopy(e);
|
checkcopy(e);
|
||||||
|
|
||||||
|
if (isInvalidPoint(e, lastArrive)) return;
|
||||||
|
|
||||||
|
if (lastArrive && isSameAs(e, lastArrive)) {
|
||||||
|
lastArrive = e;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
lastArrive = e;
|
||||||
|
|
||||||
const locateEvent = createLocateEvent(e);
|
const locateEvent = createLocateEvent(e);
|
||||||
const sensor = chooseSensor(locateEvent);
|
const sensor = chooseSensor(locateEvent);
|
||||||
if (sensor) {
|
if (sensor) {
|
||||||
@ -245,44 +303,61 @@ export default class Dragon {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const dragstart = () => {
|
const dragstart = () => {
|
||||||
|
this._dragging = true;
|
||||||
|
setShaken(boostEvent);
|
||||||
const locateEvent = createLocateEvent(boostEvent);
|
const locateEvent = createLocateEvent(boostEvent);
|
||||||
if (newBie) {
|
if (newBie || forceCopyState) {
|
||||||
this.setCopyState(true);
|
this.setCopyState(true);
|
||||||
} else {
|
} else {
|
||||||
chooseSensor(locateEvent);
|
chooseSensor(locateEvent);
|
||||||
}
|
}
|
||||||
this.setDraggingState(true);
|
this.setDraggingState(true);
|
||||||
// ESC cancel drag
|
// ESC cancel drag
|
||||||
alwaysListen.addEventListener('keydown', checkesc, false);
|
if (!isBoostFromDragAPI) {
|
||||||
listenSimulators &&
|
handleEvents(doc => {
|
||||||
listenSimulators(sdoc => {
|
doc.addEventListener('keydown', checkesc, false);
|
||||||
sdoc.addEventListener('keydown', checkesc, false);
|
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
this.emitter.emit('dragstart', locateEvent);
|
this.emitter.emit('dragstart', locateEvent);
|
||||||
};
|
};
|
||||||
|
|
||||||
const move = (e: MouseEvent) => {
|
const move = (e: MouseEvent | DragEvent) => {
|
||||||
if (this.dragging) {
|
if (isBoostFromDragAPI) {
|
||||||
|
e.preventDefault();
|
||||||
|
}
|
||||||
|
if (this._dragging) {
|
||||||
drag(e);
|
drag(e);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isShaken(boostEvent, e)) {
|
if (isShaken(boostEvent, e)) {
|
||||||
this._dragging = true;
|
|
||||||
|
|
||||||
setShaken(boostEvent);
|
|
||||||
dragstart();
|
dragstart();
|
||||||
drag(e);
|
drag(e);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let didDrop = true;
|
||||||
|
const drop = (e: DragEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
didDrop = true;
|
||||||
|
};
|
||||||
|
|
||||||
const over = (e?: any) => {
|
const over = (e?: any) => {
|
||||||
|
if (e && isDragEvent(e)) {
|
||||||
|
e.preventDefault();
|
||||||
|
}
|
||||||
if (lastSensor) {
|
if (lastSensor) {
|
||||||
lastSensor.deactiveSensor();
|
lastSensor.deactiveSensor();
|
||||||
}
|
}
|
||||||
this.setNativeSelection(true);
|
if (isBoostFromDragAPI) {
|
||||||
const copy = !newBie && this.isCopyState();
|
if (!didDrop) {
|
||||||
|
designer.clearLocation();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.setNativeSelection(true);
|
||||||
|
}
|
||||||
this.clearState();
|
this.clearState();
|
||||||
|
|
||||||
let exception;
|
let exception;
|
||||||
@ -295,31 +370,26 @@ export default class Dragon {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
alwaysListen.removeEventListener('mousemove', move, true);
|
handleEvents(doc => {
|
||||||
alwaysListen.removeEventListener('mouseup', over, true);
|
if (isBoostFromDragAPI) {
|
||||||
alwaysListen.removeEventListener('mousedown', over, true);
|
doc.removeEventListener('dragover', move, true);
|
||||||
alwaysListen.removeEventListener('keydown', checkesc, false);
|
doc.removeEventListener('dragend', over, true);
|
||||||
alwaysListen.removeEventListener('keydown', checkcopy as any, false);
|
doc.removeEventListener('drop', drop, true);
|
||||||
alwaysListen.removeEventListener('keyup', checkcopy as any, false);
|
} else {
|
||||||
listenSimulators &&
|
doc.removeEventListener('mousemove', move, true);
|
||||||
listenSimulators(sdoc => {
|
doc.removeEventListener('mouseup', over, true);
|
||||||
sdoc.removeEventListener('mousemove', move, true);
|
}
|
||||||
sdoc.removeEventListener('mouseup', over, true);
|
doc.removeEventListener('mousedown', over, true);
|
||||||
sdoc.removeEventListener('mousedown', over, true);
|
doc.removeEventListener('keydown', checkesc, false);
|
||||||
sdoc.removeEventListener('keydown', checkesc, false);
|
doc.removeEventListener('keydown', checkcopy, false);
|
||||||
sdoc.removeEventListener('keydown', checkcopy as any, false);
|
doc.removeEventListener('keyup', checkcopy, false);
|
||||||
sdoc.removeEventListener('keyup', checkcopy as any, false);
|
});
|
||||||
});
|
|
||||||
if (exception) {
|
if (exception) {
|
||||||
throw exception;
|
throw exception;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const createLocateEvent = (e: MouseEvent): LocateEvent => {
|
const createLocateEvent = (e: MouseEvent | DragEvent): LocateEvent => {
|
||||||
if (isLocateEvent(e)) {
|
|
||||||
return e;
|
|
||||||
}
|
|
||||||
|
|
||||||
const evt: any = {
|
const evt: any = {
|
||||||
type: 'LocateEvent',
|
type: 'LocateEvent',
|
||||||
dragObject,
|
dragObject,
|
||||||
@ -334,7 +404,7 @@ export default class Dragon {
|
|||||||
evt.globalY = e.clientY;
|
evt.globalY = e.clientY;
|
||||||
} else {
|
} else {
|
||||||
let srcSim: ISimulator | undefined;
|
let srcSim: ISimulator | undefined;
|
||||||
let lastSim = lastSensor && isSimulator(lastSensor) ? lastSensor : null;
|
const lastSim = lastSensor && isSimulator(lastSensor) ? lastSensor : null;
|
||||||
if (lastSim && lastSim.contentDocument === sourceDocument) {
|
if (lastSim && lastSim.contentDocument === sourceDocument) {
|
||||||
srcSim = lastSim;
|
srcSim = lastSim;
|
||||||
} else {
|
} else {
|
||||||
@ -386,34 +456,54 @@ export default class Dragon {
|
|||||||
return sensor;
|
return sensor;
|
||||||
};
|
};
|
||||||
|
|
||||||
alwaysListen.addEventListener('mousemove', move, true);
|
if (isDragEvent(boostEvent)) {
|
||||||
alwaysListen.addEventListener('mouseup', over, true);
|
const { dataTransfer } = boostEvent;
|
||||||
alwaysListen.addEventListener('mousedown', over, true);
|
|
||||||
listenSimulators &&
|
if (dataTransfer) {
|
||||||
listenSimulators(sdoc => {
|
// dataTransfer.setDragImage(this.emptyImage, 0, 0);
|
||||||
// alwaysListen = global document
|
dataTransfer.effectAllowed = 'all';
|
||||||
// listen others simulator iframe
|
// dataTransfer.dropEffect = newBie || forceCopyState ? 'copy' : 'move';
|
||||||
sdoc.addEventListener('mousemove', move, true);
|
|
||||||
sdoc.addEventListener('mouseup', over, true);
|
try {
|
||||||
sdoc.addEventListener('mousedown', over, true);
|
dataTransfer.setData('application/json', '{}');
|
||||||
});
|
} catch (ex) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dragstart();
|
||||||
|
} else {
|
||||||
|
this.setNativeSelection(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleEvents(doc => {
|
||||||
|
if (isBoostFromDragAPI) {
|
||||||
|
doc.addEventListener('dragover', move, true);
|
||||||
|
// dragexit
|
||||||
|
didDrop = false;
|
||||||
|
doc.addEventListener('drop', drop, true);
|
||||||
|
doc.addEventListener('dragend', over, true);
|
||||||
|
} else {
|
||||||
|
doc.addEventListener('mousemove', move, true);
|
||||||
|
doc.addEventListener('mouseup', over, true);
|
||||||
|
}
|
||||||
|
doc.addEventListener('mousedown', over, true);
|
||||||
|
});
|
||||||
|
|
||||||
// future think: drag things from browser-out or a iframe-pane
|
// future think: drag things from browser-out or a iframe-pane
|
||||||
|
|
||||||
if (!newBie) {
|
if (!newBie && !isBoostFromDragAPI) {
|
||||||
alwaysListen.addEventListener('keydown', checkcopy as any, false);
|
handleEvents(doc => {
|
||||||
alwaysListen.addEventListener('keyup', checkcopy as any, false);
|
doc.addEventListener('keydown', checkcopy, false);
|
||||||
listenSimulators &&
|
doc.addEventListener('keyup', checkcopy, false);
|
||||||
listenSimulators(sdoc => {
|
});
|
||||||
sdoc.addEventListener('keydown', checkcopy as any, false);
|
|
||||||
sdoc.addEventListener('keyup', checkcopy as any, false);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private getMasterSensors(): ISimulator[] {
|
private getMasterSensors(): ISimulator[] {
|
||||||
return this.designer.project.documents
|
return this.designer.project.documents
|
||||||
.map(doc => {
|
.map(doc => {
|
||||||
|
// TODO: not use actived,
|
||||||
if (doc.actived && doc.simulator?.sensorAvailable) {
|
if (doc.actived && doc.simulator?.sensorAvailable) {
|
||||||
return doc.simulator;
|
return doc.simulator;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1 +1,178 @@
|
|||||||
// todo
|
import { EventEmitter } from 'events';
|
||||||
|
import Session from './session';
|
||||||
|
import { autorun, Reaction, untracked } from '@recore/obx';
|
||||||
|
import { NodeSchema } from '../schema';
|
||||||
|
|
||||||
|
// TODO: cache to localStorage
|
||||||
|
|
||||||
|
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 = 0;
|
||||||
|
private emitter = new EventEmitter();
|
||||||
|
private obx: Reaction;
|
||||||
|
private justWokeup = 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();
|
||||||
|
// TODO: remove this line
|
||||||
|
console.info('log');
|
||||||
|
if (this.justWokeup) {
|
||||||
|
this.justWokeup = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
untracked(() => {
|
||||||
|
const log = currentSerializion.serialize(data);
|
||||||
|
if (this.session.cursor === 0 && this.session.isActive()) {
|
||||||
|
// first log
|
||||||
|
this.session.log(log);
|
||||||
|
this.session.end();
|
||||||
|
} else if (this.session) {
|
||||||
|
if (this.session.isActive()) {
|
||||||
|
this.session.log(log);
|
||||||
|
} else {
|
||||||
|
this.session.end();
|
||||||
|
const lastState = this.getState();
|
||||||
|
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);
|
||||||
|
const currentState = this.getState();
|
||||||
|
if (currentState !== lastState) {
|
||||||
|
this.emitter.emit('statechange', currentState);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, 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(currentSerializion.unserialize(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 = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -24,6 +24,12 @@ export default class Hovering {
|
|||||||
this._current = node;
|
this._current = node;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
unhover(node: Node) {
|
||||||
|
if (this._current === node) {
|
||||||
|
this._current = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
leave(document: DocumentModel) {
|
leave(document: DocumentModel) {
|
||||||
if (this.current && this.current.document === document) {
|
if (this.current && this.current.document === document) {
|
||||||
this._current = null;
|
this._current = null;
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import { obx, computed } from '@recore/obx';
|
import { obx, computed } from '@recore/obx';
|
||||||
import { INodeSelector, IViewport } from '../simulator';
|
import { INodeSelector, IViewport } from '../simulator';
|
||||||
import { uniqueId } from '../../utils/unique-id';
|
import { uniqueId } from '../../../../utils/unique-id';
|
||||||
|
import { isRootNode } from '../document/node/root-node';
|
||||||
|
|
||||||
export default class OffsetObserver {
|
export default class OffsetObserver {
|
||||||
readonly id = uniqueId('oobx');
|
readonly id = uniqueId('oobx');
|
||||||
@ -17,25 +18,25 @@ export default class OffsetObserver {
|
|||||||
@obx hasOffset = false;
|
@obx hasOffset = false;
|
||||||
@computed get offsetLeft() {
|
@computed get offsetLeft() {
|
||||||
if (!this.viewport.scrolling || this.lastOffsetLeft == null) {
|
if (!this.viewport.scrolling || this.lastOffsetLeft == null) {
|
||||||
this.lastOffsetLeft = (this.left + this.viewport.scrollX) * this.scale;
|
this.lastOffsetLeft = this.isRoot ? this.viewport.scrollX : (this.left + this.viewport.scrollX) * this.scale;
|
||||||
}
|
}
|
||||||
return this.lastOffsetLeft;
|
return this.lastOffsetLeft;
|
||||||
}
|
}
|
||||||
@computed get offsetTop() {
|
@computed get offsetTop() {
|
||||||
if (!this.viewport.scrolling || this.lastOffsetTop == null) {
|
if (!this.viewport.scrolling || this.lastOffsetTop == null) {
|
||||||
this.lastOffsetTop = (this.top + this.viewport.scrollY) * this.scale;
|
this.lastOffsetTop = this.isRoot ? this.viewport.scrollY : (this.top + this.viewport.scrollY) * this.scale;
|
||||||
}
|
}
|
||||||
return this.lastOffsetTop;
|
return this.lastOffsetTop;
|
||||||
}
|
}
|
||||||
@computed get offsetHeight() {
|
@computed get offsetHeight() {
|
||||||
if (!this.viewport.scrolling || this.lastOffsetHeight == null) {
|
if (!this.viewport.scrolling || this.lastOffsetHeight == null) {
|
||||||
this.lastOffsetHeight = this.height * this.scale;
|
this.lastOffsetHeight = this.isRoot ? this.viewport.height : this.height * this.scale;
|
||||||
}
|
}
|
||||||
return this.lastOffsetHeight;
|
return this.lastOffsetHeight;
|
||||||
}
|
}
|
||||||
@computed get offsetWidth() {
|
@computed get offsetWidth() {
|
||||||
if (!this.viewport.scrolling || this.lastOffsetWidth == null) {
|
if (!this.viewport.scrolling || this.lastOffsetWidth == null) {
|
||||||
this.lastOffsetWidth = this.width * this.scale;
|
this.lastOffsetWidth = this.isRoot ? this.viewport.width : this.width * this.scale;
|
||||||
}
|
}
|
||||||
return this.lastOffsetWidth;
|
return this.lastOffsetWidth;
|
||||||
}
|
}
|
||||||
@ -46,12 +47,18 @@ export default class OffsetObserver {
|
|||||||
|
|
||||||
private pid: number | undefined;
|
private pid: number | undefined;
|
||||||
private viewport: IViewport;
|
private viewport: IViewport;
|
||||||
|
private isRoot: boolean;
|
||||||
|
|
||||||
constructor(readonly nodeInstance: INodeSelector) {
|
constructor(readonly nodeInstance: INodeSelector) {
|
||||||
const { node, instance } = nodeInstance;
|
const { node, instance } = nodeInstance;
|
||||||
const doc = node.document;
|
const doc = node.document;
|
||||||
const host = doc.simulator!;
|
const host = doc.simulator!;
|
||||||
|
this.isRoot = isRootNode(node);
|
||||||
this.viewport = host.viewport;
|
this.viewport = host.viewport;
|
||||||
|
if (this.isRoot) {
|
||||||
|
this.hasOffset = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (!instance) {
|
if (!instance) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,5 +1,5 @@
|
|||||||
import { Component } from 'react';
|
import { Component } from 'react';
|
||||||
import { observer } from '@recore/core-obx';
|
import { observer } from '@recore/obx-react';
|
||||||
import Designer from './designer';
|
import Designer from './designer';
|
||||||
import DocumentView from './document/document-view';
|
import DocumentView from './document/document-view';
|
||||||
|
|
||||||
@ -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 => {
|
||||||
|
|||||||
@ -27,8 +27,8 @@ export default class Project {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@computed get activedDocuments() {
|
@computed get currentDocument() {
|
||||||
return this.documents.filter(doc => doc.actived);
|
return this.documents.find(doc => doc.actived);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -106,14 +106,12 @@ export default class Project {
|
|||||||
}
|
}
|
||||||
|
|
||||||
checkExclusive(actived: DocumentModel) {
|
checkExclusive(actived: DocumentModel) {
|
||||||
if (this.canvasDisplayMode !== 'exclusive') {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.documents.forEach((doc) => {
|
this.documents.forEach((doc) => {
|
||||||
if (doc !== actived) {
|
if (doc !== actived) {
|
||||||
doc.suspense();
|
doc.suspense();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
this.emitter.emit('current-document-change', actived);
|
||||||
}
|
}
|
||||||
|
|
||||||
closeOthers(opened: DocumentModel) {
|
closeOthers(opened: DocumentModel) {
|
||||||
@ -124,7 +122,14 @@ export default class Project {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onCurrentDocumentChange(fn: (doc: DocumentModel) => void): () => void {
|
||||||
|
this.emitter.on('current-document-change', fn);
|
||||||
|
return () => {
|
||||||
|
this.emitter.removeListener('current-document-change', fn);
|
||||||
|
};
|
||||||
|
}
|
||||||
// 通知标记删除,需要告知服务端
|
// 通知标记删除,需要告知服务端
|
||||||
// 项目角度编辑不是全量打开所有文档,是按需加载,哪个更新就通知更新谁,
|
// 项目角度编辑不是全量打开所有文档,是按需加载,哪个更新就通知更新谁,
|
||||||
// 哪个删除就
|
// 哪个删除就
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
46
packages/designer/src/designer/prop-config.ts
Normal file
46
packages/designer/src/designer/prop-config.ts
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
export type PropType = BasicType | RequiredType | ComplexType;
|
||||||
|
export type BasicType = 'array' | 'bool' | 'func' | 'number' | 'object' | 'string' | 'node' | 'element' | 'any';
|
||||||
|
export type ComplexType = OneOf | OneOfType | ArrayOf | ObjectOf | Shape | Exact;
|
||||||
|
|
||||||
|
export interface RequiredType {
|
||||||
|
type: BasicType;
|
||||||
|
isRequired?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface OneOf {
|
||||||
|
type: 'oneOf';
|
||||||
|
value: string[];
|
||||||
|
isRequired?: boolean;
|
||||||
|
}
|
||||||
|
export interface OneOfType {
|
||||||
|
type: 'oneOfType';
|
||||||
|
value: PropType[];
|
||||||
|
isRequired?: boolean;
|
||||||
|
}
|
||||||
|
export interface ArrayOf {
|
||||||
|
type: 'arrayOf';
|
||||||
|
value: PropType;
|
||||||
|
isRequired?: boolean;
|
||||||
|
}
|
||||||
|
export interface ObjectOf {
|
||||||
|
type: 'objectOf';
|
||||||
|
value: PropType;
|
||||||
|
isRequired?: boolean;
|
||||||
|
}
|
||||||
|
export interface Shape {
|
||||||
|
type: 'shape';
|
||||||
|
value: PropConfig[];
|
||||||
|
isRequired?: boolean;
|
||||||
|
}
|
||||||
|
export interface Exact {
|
||||||
|
type: 'exact';
|
||||||
|
value: PropConfig[];
|
||||||
|
isRequired?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PropConfig {
|
||||||
|
name: string;
|
||||||
|
propType: PropType;
|
||||||
|
description?: string;
|
||||||
|
defaultValue?: any;
|
||||||
|
}
|
||||||
@ -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';
|
||||||
}
|
}
|
||||||
@ -106,7 +105,7 @@ export function isDOMText(data: any): data is DOMText {
|
|||||||
export type DOMText = string;
|
export type DOMText = string;
|
||||||
|
|
||||||
export interface RootSchema extends NodeSchema {
|
export interface RootSchema extends NodeSchema {
|
||||||
componentName: 'Block' | 'Page' | 'Component';
|
componentName: string; // 'Block' | 'Page' | 'Component';
|
||||||
fileName: string;
|
fileName: string;
|
||||||
meta?: object;
|
meta?: object;
|
||||||
state?: {
|
state?: {
|
||||||
@ -121,7 +120,7 @@ export interface RootSchema extends NodeSchema {
|
|||||||
css?: string;
|
css?: string;
|
||||||
dataSource?: {
|
dataSource?: {
|
||||||
items: DataSourceConfig[];
|
items: DataSourceConfig[];
|
||||||
};
|
} | any;
|
||||||
defaultProps?: CompositeObject;
|
defaultProps?: CompositeObject;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import { LocateEvent, ISensor } from './helper/dragon';
|
|||||||
import { Point } from './helper/location';
|
import { Point } from './helper/location';
|
||||||
import Node from './document/node/node';
|
import Node from './document/node/node';
|
||||||
import { ScrollTarget, IScrollable } from './helper/scroller';
|
import { ScrollTarget, IScrollable } from './helper/scroller';
|
||||||
import { ComponentDescriptionSpec } from './component-config';
|
import { ComponentMetadata } from './component-meta';
|
||||||
|
|
||||||
export type AutoFit = '100%';
|
export type AutoFit = '100%';
|
||||||
export const AutoFit = '100%';
|
export const AutoFit = '100%';
|
||||||
@ -85,7 +85,6 @@ export interface ISimulator<P = object> extends ISensor {
|
|||||||
// 获取区块代码, 通过 components 传递,可异步获取
|
// 获取区块代码, 通过 components 传递,可异步获取
|
||||||
setProps(props: P): void;
|
setProps(props: P): void;
|
||||||
|
|
||||||
|
|
||||||
setSuspense(suspensed: boolean): void;
|
setSuspense(suspensed: boolean): void;
|
||||||
|
|
||||||
// #region ========= drag and drop helpers =============
|
// #region ========= drag and drop helpers =============
|
||||||
@ -117,7 +116,7 @@ export interface ISimulator<P = object> extends ISensor {
|
|||||||
/**
|
/**
|
||||||
* 描述组件
|
* 描述组件
|
||||||
*/
|
*/
|
||||||
describeComponent(component: Component): ComponentDescriptionSpec;
|
generateComponentMetadata(componentName: string): ComponentMetadata;
|
||||||
/**
|
/**
|
||||||
* 根据组件信息获取组件类
|
* 根据组件信息获取组件类
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { isPlainObject } from './is-plain-object';
|
import { isPlainObject } from '../../../utils/is-plain-object';
|
||||||
|
|
||||||
export function cloneDeep(src: any): any {
|
export function cloneDeep(src: any): any {
|
||||||
const type = typeof src;
|
const type = typeof src;
|
||||||
|
|||||||
@ -4,6 +4,6 @@
|
|||||||
"experimentalDecorators": true
|
"experimentalDecorators": true
|
||||||
},
|
},
|
||||||
"include": [
|
"include": [
|
||||||
"./src/"
|
"./src/", "../utils/unique-id.ts", "../utils/is-plain-object.ts", "../utils/is-object.ts", "../utils/is-function.ts"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
6
packages/editor-framework/.eslintignore
Normal file
6
packages/editor-framework/.eslintignore
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
# 忽略目录
|
||||||
|
build/
|
||||||
|
node_modules/
|
||||||
|
**/*-min.js
|
||||||
|
**/*.min.js
|
||||||
|
coverage/
|
||||||
5
packages/editor-framework/.eslintrc.js
Normal file
5
packages/editor-framework/.eslintrc.js
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
const { eslint, deepmerge } = require('@ice/spec');
|
||||||
|
|
||||||
|
module.exports = deepmerge(eslint, {
|
||||||
|
rules: {},
|
||||||
|
});
|
||||||
22
packages/editor-framework/.gitignore
vendored
Normal file
22
packages/editor-framework/.gitignore
vendored
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
# See https://help.github.com/ignore-files/ for more about ignoring files.
|
||||||
|
|
||||||
|
# dependencies
|
||||||
|
node_modules/
|
||||||
|
|
||||||
|
# production
|
||||||
|
build/
|
||||||
|
dist/
|
||||||
|
tmp/
|
||||||
|
lib/
|
||||||
|
|
||||||
|
# misc
|
||||||
|
.idea/
|
||||||
|
.happypack
|
||||||
|
.DS_Store
|
||||||
|
*.swp
|
||||||
|
*.dia~
|
||||||
|
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
CHANGELOG.md
|
||||||
6
packages/editor-framework/.prettierrc
Normal file
6
packages/editor-framework/.prettierrc
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"semi": true,
|
||||||
|
"singleQuote": true,
|
||||||
|
"printWidth": 120,
|
||||||
|
"trailingComma": "all"
|
||||||
|
}
|
||||||
11
packages/editor-framework/README copy.md
Normal file
11
packages/editor-framework/README copy.md
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
# demo component
|
||||||
|
|
||||||
|
t-s-demo
|
||||||
|
|
||||||
|
intro component
|
||||||
|
|
||||||
|
## API
|
||||||
|
|
||||||
|
| 参数名 | 说明 | 必填 | 类型 | 默认值 | 备注 |
|
||||||
|
| ------ | ---- | ---- | ---- | ------ | ---- |
|
||||||
|
| | | | | | |
|
||||||
@ -1 +1 @@
|
|||||||
编辑器框架
|
## todo
|
||||||
|
|||||||
9
packages/editor-framework/build.json
Normal file
9
packages/editor-framework/build.json
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"plugins": [
|
||||||
|
"build-plugin-component",
|
||||||
|
"build-plugin-fusion",
|
||||||
|
["build-plugin-moment-locales", {
|
||||||
|
"locales": ["zh-cn"]
|
||||||
|
}]
|
||||||
|
]
|
||||||
|
}
|
||||||
24
packages/editor-framework/demo/usage.md
Normal file
24
packages/editor-framework/demo/usage.md
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
---
|
||||||
|
title: Simple Usage
|
||||||
|
order: 1
|
||||||
|
---
|
||||||
|
|
||||||
|
本 Demo 演示一行文字的用法。
|
||||||
|
|
||||||
|
````jsx
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
import ReactDOM from 'react-dom';
|
||||||
|
|
||||||
|
class App extends Component {
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ReactDOM.render((
|
||||||
|
<App />
|
||||||
|
), mountNode);
|
||||||
|
````
|
||||||
3
packages/editor-framework/es/context.d.ts
vendored
Normal file
3
packages/editor-framework/es/context.d.ts
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
/// <reference types="react" />
|
||||||
|
declare const context: import("react").Context<{}>;
|
||||||
|
export default context;
|
||||||
3
packages/editor-framework/es/context.js
Normal file
3
packages/editor-framework/es/context.js
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
import { createContext } from 'react';
|
||||||
|
var context = createContext({});
|
||||||
|
export default context;
|
||||||
55
packages/editor-framework/package.json
Normal file
55
packages/editor-framework/package.json
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
{
|
||||||
|
"name": "@ali/lowcode-engine-editor",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"description": "alibaba lowcode editor core",
|
||||||
|
"files": [
|
||||||
|
"demo/",
|
||||||
|
"es/",
|
||||||
|
"lib/",
|
||||||
|
"build/"
|
||||||
|
],
|
||||||
|
"main": "lib/index.js",
|
||||||
|
"module": "es/index.js",
|
||||||
|
"stylePath": "style.js",
|
||||||
|
"scripts": {
|
||||||
|
"start": "build-scripts start",
|
||||||
|
"build": "build-scripts build",
|
||||||
|
"prepublishOnly": "npm run prettier && npm run build",
|
||||||
|
"lint": "eslint --cache --ext .js,.jsx ./",
|
||||||
|
"prettier": "prettier --write \"./src/**/*.{ts,tsx,js,jsx,ejs,less,css,scss,json}\" "
|
||||||
|
},
|
||||||
|
"keywords": [
|
||||||
|
"lowcode",
|
||||||
|
"editor"
|
||||||
|
],
|
||||||
|
"author": "xiayang.xy",
|
||||||
|
"dependencies": {
|
||||||
|
"debug": "^4.1.1",
|
||||||
|
"events": "^3.1.0",
|
||||||
|
"intl-messageformat": "^7.8.4",
|
||||||
|
"lodash": "^4.17.15",
|
||||||
|
"prop-types": "^15.5.8",
|
||||||
|
"store": "^2.0.12"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@alib/build-scripts": "^0.1.3",
|
||||||
|
"@alifd/next": "1.x",
|
||||||
|
"@ice/spec": "^0.1.1",
|
||||||
|
"@types/lodash": "^4.14.149",
|
||||||
|
"@types/react": "^16.9.13",
|
||||||
|
"@types/react-dom": "^16.9.4",
|
||||||
|
"build-plugin-component": "^0.2.7-1",
|
||||||
|
"build-plugin-fusion": "^0.1.0",
|
||||||
|
"build-plugin-moment-locales": "^0.1.0",
|
||||||
|
"eslint": "^6.0.1",
|
||||||
|
"prettier": "^1.19.1",
|
||||||
|
"react": "^16.8.0",
|
||||||
|
"react-dom": "^16.8.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^16.8.0",
|
||||||
|
"@alifd/next": "1.x"
|
||||||
|
},
|
||||||
|
"license": "MIT",
|
||||||
|
"homepage": "https://unpkg.com/editor-framework@0.0.1/build/index.html"
|
||||||
|
}
|
||||||
3
packages/editor-framework/src/context.ts
Normal file
3
packages/editor-framework/src/context.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
import { createContext } from 'react';
|
||||||
|
const context = createContext({});
|
||||||
|
export default context;
|
||||||
48
packages/editor-framework/src/definitions.ts
Normal file
48
packages/editor-framework/src/definitions.ts
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
|
||||||
|
export interface EditorConfig {
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface NpmConfig {
|
||||||
|
version: string,
|
||||||
|
package: string,
|
||||||
|
main?: string,
|
||||||
|
exportName?: string,
|
||||||
|
subName?: string,
|
||||||
|
destructuring?: boolean
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface SkeletonConfig {
|
||||||
|
config: NpmConfig,
|
||||||
|
props?: object,
|
||||||
|
handler?: (EditorConfig) => EditorConfig
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface FusionTheme {
|
||||||
|
package: string,
|
||||||
|
version: string
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface ThemeConfig {
|
||||||
|
fusion?: FusionTheme
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PluginsConfig {
|
||||||
|
[key]: Array<PluginConfig>
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface PluginConfig {
|
||||||
|
pluginKey: string,
|
||||||
|
type: string,
|
||||||
|
props: object,
|
||||||
|
config: NpmConfig,
|
||||||
|
pluginProps: object
|
||||||
|
};
|
||||||
|
|
||||||
|
export type HooksConfig = Array<HookConfig>;
|
||||||
|
|
||||||
|
export interface HookConfig {
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
186
packages/editor-framework/src/editor.ts
Normal file
186
packages/editor-framework/src/editor.ts
Normal file
@ -0,0 +1,186 @@
|
|||||||
|
import EventEmitter from 'events';
|
||||||
|
import Debug from 'debug';
|
||||||
|
import store from 'store';
|
||||||
|
|
||||||
|
import {
|
||||||
|
unRegistShortCuts,
|
||||||
|
registShortCuts,
|
||||||
|
transformToPromise,
|
||||||
|
generateI18n
|
||||||
|
} from './utils';
|
||||||
|
|
||||||
|
// 根据url参数设置debug选项
|
||||||
|
const res = /_?debug=(.*?)(&|$)/.exec(location.search);
|
||||||
|
if (res && res[1]) {
|
||||||
|
window.__isDebug = true;
|
||||||
|
store.storage.write('debug', res[1] === 'true' ? '*' : res[1]);
|
||||||
|
} else {
|
||||||
|
window.__isDebug = false;
|
||||||
|
store.remove('debug');
|
||||||
|
}
|
||||||
|
|
||||||
|
//重要,用于矫正画布执行new Function的window对象上下文
|
||||||
|
window.__newFunc = funContext => {
|
||||||
|
return new Function(funContext);
|
||||||
|
};
|
||||||
|
|
||||||
|
//关闭浏览器前提醒,只有产生过交互才会生效
|
||||||
|
window.onbeforeunload = function(e) {
|
||||||
|
e = e || window.event;
|
||||||
|
// 本地调试不生效
|
||||||
|
if (location.href.indexOf('localhost') > 0) return;
|
||||||
|
var msg = '您确定要离开此页面吗?';
|
||||||
|
e.cancelBubble = true;
|
||||||
|
e.returnValue = msg;
|
||||||
|
if (e.stopPropagation) {
|
||||||
|
e.stopPropagation();
|
||||||
|
e.preventDefault();
|
||||||
|
}
|
||||||
|
return msg;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
let instance = null;
|
||||||
|
const debug = Debug('editor');
|
||||||
|
EventEmitter.defaultMaxListeners = 100;
|
||||||
|
|
||||||
|
export interface editor {
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
export default class Editor extends EventEmitter {
|
||||||
|
static getInstance = () => {
|
||||||
|
if (!instance) {
|
||||||
|
instance = new Editor();
|
||||||
|
}
|
||||||
|
return instance;
|
||||||
|
};
|
||||||
|
|
||||||
|
constructor(config) {
|
||||||
|
super();
|
||||||
|
instance = this;
|
||||||
|
Object.assign(this, config);
|
||||||
|
this.init();
|
||||||
|
}
|
||||||
|
|
||||||
|
init() {
|
||||||
|
const {
|
||||||
|
hooks,
|
||||||
|
shortCuts,
|
||||||
|
lifeCycles
|
||||||
|
} = this.config || {};
|
||||||
|
this.destroy();
|
||||||
|
this.locale = store.get('lowcode-editor-locale') || 'zh-CN';
|
||||||
|
this.messages = this.messagesSet[this.locale];
|
||||||
|
this.i18n = generateI18n(this.locale, this.messages);
|
||||||
|
this.pluginStatus = this.initPluginStatus();
|
||||||
|
this.initHooks(hooks, appHelper);
|
||||||
|
|
||||||
|
appHelper.emit('editor.beforeInit');
|
||||||
|
const init = lifeCycles && lifeCycles.init || () => {};
|
||||||
|
// 用户可以通过设置extensions.init自定义初始化流程;
|
||||||
|
transformToPromise(init(this))
|
||||||
|
.then(() => {
|
||||||
|
// 注册快捷键
|
||||||
|
registShortCuts(shortCuts, this);
|
||||||
|
this.emit('editor.afterInit');
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
console.warn(err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
destroy() {
|
||||||
|
try {
|
||||||
|
const {
|
||||||
|
hooks = [],
|
||||||
|
shortCuts = [],
|
||||||
|
lifeCycles = {}
|
||||||
|
} = this.config;
|
||||||
|
unRegistShortCuts(shortCuts);
|
||||||
|
this.destroyHooks(hooks);
|
||||||
|
lifeCycles.destroy && lifeCycles.destroy();
|
||||||
|
} catch (err) {
|
||||||
|
console.warn(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get(key:string):any {
|
||||||
|
return this[key];
|
||||||
|
}
|
||||||
|
|
||||||
|
set(key:string|object, val:any):void {
|
||||||
|
if (typeof key === 'string') {
|
||||||
|
if (['init', 'destroy', 'get', 'set', 'batchOn', 'batchOff', 'batchOnce'].includes(key)) {
|
||||||
|
console.warning('init, destroy, get, set, batchOn, batchOff, batchOnce is private attribute');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this[key] = val;
|
||||||
|
} else if (typeof key === 'object') {
|
||||||
|
Object.keys(key).forEach(item => {
|
||||||
|
this[item] = key[item];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
batchOn(events:Array<string>, lisenter:function):void {
|
||||||
|
if (!Array.isArray(events)) return;
|
||||||
|
events.forEach(event => this.on(event, lisenter));
|
||||||
|
}
|
||||||
|
|
||||||
|
batchOnce(events:Array<string>, lisenter:function):void {
|
||||||
|
if (!Array.isArray(events)) return;
|
||||||
|
events.forEach(event => this.once(event, lisenter));
|
||||||
|
}
|
||||||
|
|
||||||
|
batchOff(events:Array<string>, lisenter:function):void {
|
||||||
|
if (!Array.isArray(events)) return;
|
||||||
|
events.forEach(event => this.off(event, lisenter));
|
||||||
|
}
|
||||||
|
|
||||||
|
//销毁hooks中的消息监听
|
||||||
|
private destroyHooks(hooks = []) {
|
||||||
|
hooks.forEach((item, idx) => {
|
||||||
|
if (typeof this.__hooksFuncs[idx] === 'function') {
|
||||||
|
this.appHelper.off(item.message, this.__hooksFuncs[idx]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
delete this.__hooksFuncs;
|
||||||
|
};
|
||||||
|
|
||||||
|
//初始化hooks中的消息监听
|
||||||
|
private initHooks(hooks = []) {
|
||||||
|
this.__hooksFuncs = hooks.map(item => {
|
||||||
|
const func = (...args) => {
|
||||||
|
item.handler(this, ...args);
|
||||||
|
};
|
||||||
|
this[item.type](item.message, func);
|
||||||
|
return func;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
private initPluginStatus () {
|
||||||
|
const {plugins = {}} = this.config;
|
||||||
|
const pluginAreas = Object.keys(plugins);
|
||||||
|
const res = {};
|
||||||
|
pluginAreas.forEach(area => {
|
||||||
|
(plugins[area] || []).forEach(plugin => {
|
||||||
|
if (plugin.type === 'Divider') return;
|
||||||
|
const { visible, disabled, dotted } = plugin.props || {};
|
||||||
|
res[plugin.pluginKey] = {
|
||||||
|
visible: typeof visible === 'boolean' ? visible : true,
|
||||||
|
disabled: typeof disabled === 'boolean' ? disabled : false,
|
||||||
|
dotted: typeof dotted === 'boolean' ? dotted : false
|
||||||
|
};
|
||||||
|
const pluginClass = this.props.components[skeletonUtils.generateAddonCompName(addon.addonKey)];
|
||||||
|
// 判断如果编辑器插件有init静态方法,则在此执行init方法
|
||||||
|
if (pluginClass && pluginClass.init) {
|
||||||
|
pluginClass.init(this);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return res;
|
||||||
|
};
|
||||||
|
}
|
||||||
4
packages/editor-framework/src/index.ts
Normal file
4
packages/editor-framework/src/index.ts
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
import Editor from './editor';
|
||||||
|
|
||||||
|
|
||||||
|
export default Editor;
|
||||||
129
packages/editor-framework/src/plugin.ts
Normal file
129
packages/editor-framework/src/plugin.ts
Normal file
@ -0,0 +1,129 @@
|
|||||||
|
import { PureComponent } from 'react';
|
||||||
|
|
||||||
|
import EditorContext from './context';
|
||||||
|
import { isEmpty, generateI18n, goldlog } from './utils';
|
||||||
|
|
||||||
|
export interface pluginProps {
|
||||||
|
config: object,
|
||||||
|
editor: object,
|
||||||
|
locale: string,
|
||||||
|
messages: object
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function plugin(Comp) {
|
||||||
|
|
||||||
|
class Plugin extends PureComponent<pluginProps> {
|
||||||
|
static displayName = 'lowcode-editor-plugin';
|
||||||
|
static defaultProps = {
|
||||||
|
config: {}
|
||||||
|
};
|
||||||
|
static contextType = EditorContext;
|
||||||
|
constructor(props, context) {
|
||||||
|
super(props, context);
|
||||||
|
if (isEmpty(props.config) || !props.config.pluginKey) {
|
||||||
|
console.warn('lowcode editor plugin has wrong config');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { locale, messages, editor } = props;
|
||||||
|
// 注册插件
|
||||||
|
this.editor = editor;
|
||||||
|
this.i18n = generateI18n(locale, messages);
|
||||||
|
this.pluginKey = props.config.pluginKey;
|
||||||
|
editor.plugins = editor.plugins || {};
|
||||||
|
editor.plugins[this.pluginKey] = this;
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
// 销毁插件
|
||||||
|
if (this.editor && this.editor.plugins) {
|
||||||
|
delete this.editor.plugins[this.pluginKey];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {
|
||||||
|
config
|
||||||
|
} = this.props;
|
||||||
|
return <Comp i18n={this.i18n} editor={this.editor} config={config} {...config.pluginProps}/>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Plugin;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export class Plugin extends PureComponent<pluginProps> {
|
||||||
|
static displayName = 'lowcode-editor-plugin';
|
||||||
|
static defaultProps = {
|
||||||
|
config: {}
|
||||||
|
};
|
||||||
|
static contextType = EditorContext;
|
||||||
|
constructor(props, context) {
|
||||||
|
super(props, context);
|
||||||
|
if (isEmpty(props.config) || !props.config.addonKey) {
|
||||||
|
console.warn('luna addon has wrong config');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const { locale, messages, editor } = props;
|
||||||
|
// 注册插件
|
||||||
|
this.editor = editor;
|
||||||
|
this.i18n = generateI18n(locale, messages);
|
||||||
|
this.pluginKey = props.config.pluginKey;
|
||||||
|
editor.plugins = editor.plugins || {};
|
||||||
|
editor.plugins[this.pluginKey] = this;
|
||||||
|
}
|
||||||
|
|
||||||
|
async componentWillUnmount() {
|
||||||
|
// 销毁插件
|
||||||
|
if (this.editor && this.editor.plugins) {
|
||||||
|
delete this.editor.plugins[this.pluginKey];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
open = () => {
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
close = () => {
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
goldlog = (goKey:string, params:any) => {
|
||||||
|
const { pluginKey, config = {} } = this.props.config || {};
|
||||||
|
goldlog(
|
||||||
|
goKey,
|
||||||
|
{
|
||||||
|
pluginKey,
|
||||||
|
package: config.package,
|
||||||
|
version: config.version,
|
||||||
|
...this.editor.logParams,
|
||||||
|
...params
|
||||||
|
},
|
||||||
|
'addon'
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
get utils() {
|
||||||
|
return this.editor.utils;
|
||||||
|
}
|
||||||
|
|
||||||
|
get constants() {
|
||||||
|
return this.editor.constants;
|
||||||
|
}
|
||||||
|
|
||||||
|
get history() {
|
||||||
|
return this.editor.history;
|
||||||
|
}
|
||||||
|
|
||||||
|
get location() {
|
||||||
|
return this.editor.location;
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
242
packages/editor-framework/src/utils.ts
Normal file
242
packages/editor-framework/src/utils.ts
Normal file
@ -0,0 +1,242 @@
|
|||||||
|
|
||||||
|
import IntlMessageFormat from 'intl-messageformat';
|
||||||
|
import _isEmpty from 'lodash/isEmpty';
|
||||||
|
|
||||||
|
export const isEmpty = _isEmpty;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用于构造国际化字符串处理函数
|
||||||
|
* @param {*} locale 国际化标识,例如 zh-CN、en-US
|
||||||
|
* @param {*} messages 国际化语言包
|
||||||
|
*/
|
||||||
|
export function generateI18n(locale = 'zh-CN', messages = {}) {
|
||||||
|
return (key, values = {}) => {
|
||||||
|
if (!messages || !messages[key]) return '';
|
||||||
|
const formater = new IntlMessageFormat(messages[key], locale);
|
||||||
|
return formater.format(values);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 序列化参数
|
||||||
|
* @param {*} obj 参数
|
||||||
|
*/
|
||||||
|
export function serializeParams(obj:object):string {
|
||||||
|
if (typeof obj !== 'object') return '';
|
||||||
|
|
||||||
|
const res:Array<string> = [];
|
||||||
|
Object.entries(obj).forEach(([key, val]) => {
|
||||||
|
if (val === null || val === undefined || val === '') return;
|
||||||
|
if (typeof val === 'object') {
|
||||||
|
res.push(`${encodeURIComponent(key)}=${encodeURIComponent(JSON.stringify(val))}`);
|
||||||
|
} else {
|
||||||
|
res.push(`${encodeURIComponent(key)}=${encodeURIComponent(val)}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return res.join('&');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 黄金令箭埋点
|
||||||
|
* @param {String} gmKey 为黄金令箭业务类型
|
||||||
|
* @param {Object} params 参数
|
||||||
|
* @param {String} logKey 属性串
|
||||||
|
*/
|
||||||
|
export function goldlog(gmKey, params = {}, logKey = 'other') {
|
||||||
|
const sendIDEMessage = window.sendIDEMessage || window.parent.sendIDEMessage;
|
||||||
|
const goKey = serializeParams({
|
||||||
|
sdkVersion: pkg.version,
|
||||||
|
env: getEnv(),
|
||||||
|
...params
|
||||||
|
});
|
||||||
|
if (sendIDEMessage) {
|
||||||
|
sendIDEMessage({
|
||||||
|
action: 'goldlog',
|
||||||
|
data: {
|
||||||
|
logKey: `/iceluna.core.${logKey}`,
|
||||||
|
gmKey,
|
||||||
|
goKey
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
window.goldlog && window.goldlog.record(`/iceluna.core.${logKey}`, gmKey, goKey, 'POST');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取当前编辑器环境
|
||||||
|
*/
|
||||||
|
export function getEnv() {
|
||||||
|
const userAgent = navigator.userAgent;
|
||||||
|
const isVscode = /Electron\//.test(userAgent);
|
||||||
|
if (isVscode) return ENV.VSCODE;
|
||||||
|
const isTheia = window.is_theia === true;
|
||||||
|
if (isTheia) return ENV.WEBIDE;
|
||||||
|
return ENV.WEB;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 注册快捷键
|
||||||
|
export function registShortCuts(config, editor) {
|
||||||
|
const keyboardFilter = (keymaster.filter = event => {
|
||||||
|
let eTarget = event.target || event.srcElement;
|
||||||
|
let tagName = eTarget.tagName;
|
||||||
|
let isInput = !!(tagName == 'INPUT' || tagName == 'SELECT' || tagName == 'TEXTAREA');
|
||||||
|
let isContenteditable = !!eTarget.getAttribute('contenteditable');
|
||||||
|
if (isInput || isContenteditable) {
|
||||||
|
if (event.metaKey === true && [70, 83].includes(event.keyCode)) event.preventDefault(); //禁止触发chrome原生的页面保存或查找
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const ideMessage = appHelper.utils && appHelper.utils.ideMessage;
|
||||||
|
|
||||||
|
//复制
|
||||||
|
if (!document.copyListener) {
|
||||||
|
document.copyListener = e => {
|
||||||
|
if (!keyboardFilter(e) || appHelper.isCopying) return;
|
||||||
|
const schema = appHelper.schemaHelper && appHelper.schemaHelper.schemaMap[appHelper.activeKey];
|
||||||
|
if (!schema || !isSchema(schema)) return;
|
||||||
|
appHelper.isCopying = true;
|
||||||
|
const schemaStr = serialize(transformSchemaToPure(schema), {
|
||||||
|
unsafe: true
|
||||||
|
});
|
||||||
|
setClipboardData(schemaStr)
|
||||||
|
.then(() => {
|
||||||
|
ideMessage && ideMessage('success', '当前内容已复制到剪贴板,请使用快捷键Command+v进行粘贴');
|
||||||
|
appHelper.emit('schema.copy', schemaStr, schema);
|
||||||
|
appHelper.isCopying = false;
|
||||||
|
})
|
||||||
|
.catch(errMsg => {
|
||||||
|
ideMessage && ideMessage('error', errMsg);
|
||||||
|
appHelper.isCopying = false;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
document.addEventListener('copy', document.copyListener);
|
||||||
|
if (window.parent.vscode) {
|
||||||
|
keymaster('command+c', document.copyListener);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//粘贴
|
||||||
|
if (!document.pasteListener) {
|
||||||
|
const doPaste = (e, text) => {
|
||||||
|
if (!keyboardFilter(e) || appHelper.isPasting) return;
|
||||||
|
const schemaHelper = appHelper.schemaHelper;
|
||||||
|
let targetKey = appHelper.activeKey;
|
||||||
|
let direction = 'after';
|
||||||
|
const topKey = schemaHelper.schema && schemaHelper.schema.__ctx && schemaHelper.schema.__ctx.lunaKey;
|
||||||
|
if (!targetKey || topKey === targetKey) {
|
||||||
|
const schemaHelper = appHelper.schemaHelper;
|
||||||
|
const topKey = schemaHelper.schema && schemaHelper.schema.__ctx && schemaHelper.schema.__ctx.lunaKey;
|
||||||
|
if (!topKey) return;
|
||||||
|
targetKey = topKey;
|
||||||
|
direction = 'in';
|
||||||
|
}
|
||||||
|
appHelper.isPasting = true;
|
||||||
|
const schema = parseObj(text);
|
||||||
|
if (!isSchema(schema)) {
|
||||||
|
appHelper.emit('illegalSchema.paste', text);
|
||||||
|
// ideMessage && ideMessage('error', '当前内容不是模型结构,不能粘贴进来!');
|
||||||
|
console.warn('paste schema illegal');
|
||||||
|
appHelper.isPasting = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
appHelper.emit('material.add', {
|
||||||
|
schema,
|
||||||
|
targetKey,
|
||||||
|
direction
|
||||||
|
});
|
||||||
|
appHelper.isPasting = false;
|
||||||
|
appHelper.emit('schema.paste', schema);
|
||||||
|
};
|
||||||
|
document.pasteListener = e => {
|
||||||
|
const clipboardData = e.clipboardData || window.clipboardData;
|
||||||
|
const text = clipboardData && clipboardData.getData('text');
|
||||||
|
doPaste(e, text);
|
||||||
|
};
|
||||||
|
document.addEventListener('paste', document.pasteListener);
|
||||||
|
if (window.parent.vscode) {
|
||||||
|
keymaster('command+v', e => {
|
||||||
|
const sendIDEMessage = window.parent.sendIDEMessage;
|
||||||
|
sendIDEMessage &&
|
||||||
|
sendIDEMessage({
|
||||||
|
action: 'readClipboard'
|
||||||
|
})
|
||||||
|
.then(text => {
|
||||||
|
doPaste(e, text);
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
console.warn(err);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
(config || []).forEach(item => {
|
||||||
|
keymaster(item.keyboard, ev => {
|
||||||
|
ev.preventDefault();
|
||||||
|
item.handler(ev, appHelper, keymaster);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 取消注册快捷
|
||||||
|
export function unRegistShortCuts(config) {
|
||||||
|
(config || []).forEach(item => {
|
||||||
|
keymaster.unbind(item.keyboard);
|
||||||
|
});
|
||||||
|
if (window.parent.vscode) {
|
||||||
|
keymaster.unbind('command+c');
|
||||||
|
keymaster.unbind('command+v');
|
||||||
|
}
|
||||||
|
if (document.copyListener) {
|
||||||
|
document.removeEventListener('copy', document.copyListener);
|
||||||
|
delete document.copyListener;
|
||||||
|
}
|
||||||
|
if (document.pasteListener) {
|
||||||
|
document.removeEventListener('paste', document.pasteListener);
|
||||||
|
delete document.pasteListener;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 将函数返回结果转成promise形式,如果函数有返回值则根据返回值的bool类型判断是reject还是resolve,若函数无返回值默认执行resolve
|
||||||
|
export function transformToPromise(input) {
|
||||||
|
if (input instanceof Promise) return input;
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
if (input || input === undefined) {
|
||||||
|
resolve();
|
||||||
|
} else {
|
||||||
|
reject();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function comboEditorConfig(defaultConfig, customConfig) {
|
||||||
|
const { ideConfig = {}, utils = {} } = this.props;
|
||||||
|
const comboShortCuts = () => {
|
||||||
|
const defaultShortCuts = defaultIdeConfig.shortCuts;
|
||||||
|
const shortCuts = ideConfig.shortCuts || [];
|
||||||
|
const configMap = skeletonUtils.transformArrayToMap(defaultShortCuts, 'keyboard');
|
||||||
|
(shortCuts || []).forEach(item => {
|
||||||
|
configMap[item.keyboard] = item;
|
||||||
|
});
|
||||||
|
return Object.keys(configMap).map(key => configMap[key]);
|
||||||
|
};
|
||||||
|
return {
|
||||||
|
...ideConfig,
|
||||||
|
utils: {
|
||||||
|
...skeletonUtils,
|
||||||
|
...utils
|
||||||
|
},
|
||||||
|
constants: {
|
||||||
|
...defaultIdeConfig.constants,
|
||||||
|
...ideConfig.constants
|
||||||
|
},
|
||||||
|
extensions: {
|
||||||
|
...defaultIdeConfig.extensions,
|
||||||
|
...ideConfig.extensions
|
||||||
|
},
|
||||||
|
shortCuts: comboShortCuts()
|
||||||
|
};
|
||||||
|
}
|
||||||
21
packages/editor-framework/tsconfig.json
Normal file
21
packages/editor-framework/tsconfig.json
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"compileOnSave": false,
|
||||||
|
"buildOnSave": false,
|
||||||
|
"compilerOptions": {
|
||||||
|
"outDir": "build",
|
||||||
|
"module": "esnext",
|
||||||
|
"target": "es6",
|
||||||
|
"jsx": "react",
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"lib": ["es6", "dom"],
|
||||||
|
"sourceMap": true,
|
||||||
|
"allowJs": true,
|
||||||
|
"noUnusedLocals": true,
|
||||||
|
"noImplicitReturns": true,
|
||||||
|
"noImplicitThis": true,
|
||||||
|
"noImplicitAny": true,
|
||||||
|
"skipLibCheck": true
|
||||||
|
},
|
||||||
|
"include": ["src/*.ts", "src/*.tsx"],
|
||||||
|
"exclude": ["node_modules", "build", "public"]
|
||||||
|
}
|
||||||
12
packages/editor-skeleton/.editorconfig
Normal file
12
packages/editor-skeleton/.editorconfig
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
# http://editorconfig.org
|
||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 2
|
||||||
|
charset = utf-8
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
insert_final_newline = true
|
||||||
|
|
||||||
|
[*.md]
|
||||||
|
trim_trailing_whitespace = false
|
||||||
11
packages/editor-skeleton/.eslintignore
Normal file
11
packages/editor-skeleton/.eslintignore
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
# 忽略目录
|
||||||
|
build/
|
||||||
|
tests/
|
||||||
|
demo/
|
||||||
|
|
||||||
|
# node 覆盖率文件
|
||||||
|
coverage/
|
||||||
|
|
||||||
|
# 忽略文件
|
||||||
|
**/*-min.js
|
||||||
|
**/*.min.js
|
||||||
7
packages/editor-skeleton/.eslintrc.js
Normal file
7
packages/editor-skeleton/.eslintrc.js
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
const { eslint, deepmerge } = require('@ice/spec');
|
||||||
|
|
||||||
|
module.exports = deepmerge(eslint, {
|
||||||
|
rules: {
|
||||||
|
"global-require": 0,
|
||||||
|
},
|
||||||
|
});
|
||||||
20
packages/editor-skeleton/.gitignore
vendored
Normal file
20
packages/editor-skeleton/.gitignore
vendored
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
# See https://help.github.com/ignore-files/ for more about ignoring files.
|
||||||
|
|
||||||
|
# dependencies
|
||||||
|
/node_modules
|
||||||
|
|
||||||
|
# production
|
||||||
|
/build
|
||||||
|
/dist
|
||||||
|
|
||||||
|
# misc
|
||||||
|
.idea/
|
||||||
|
.happypack
|
||||||
|
.DS_Store
|
||||||
|
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
|
||||||
|
# ignore d.ts auto generated by css-modules-typescript-loader
|
||||||
|
*.module.scss.d.ts
|
||||||
7
packages/editor-skeleton/.stylelintignore
Normal file
7
packages/editor-skeleton/.stylelintignore
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
# 忽略目录
|
||||||
|
build/
|
||||||
|
tests/
|
||||||
|
demo/
|
||||||
|
|
||||||
|
# node 覆盖率文件
|
||||||
|
coverage/
|
||||||
3
packages/editor-skeleton/.stylelintrc.js
Normal file
3
packages/editor-skeleton/.stylelintrc.js
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
const { stylelint } = require('@ice/spec');
|
||||||
|
|
||||||
|
module.exports = stylelint;
|
||||||
1
packages/editor-skeleton/README.md
Normal file
1
packages/editor-skeleton/README.md
Normal file
@ -0,0 +1 @@
|
|||||||
|
## todo
|
||||||
4
packages/editor-skeleton/abc.json
Normal file
4
packages/editor-skeleton/abc.json
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"type": "ice-scripts",
|
||||||
|
"builder": "@ali/builder-ice-scripts"
|
||||||
|
}
|
||||||
9
packages/editor-skeleton/build.json
Normal file
9
packages/editor-skeleton/build.json
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"plugins": [
|
||||||
|
"build-plugin-component",
|
||||||
|
"build-plugin-fusion",
|
||||||
|
["build-plugin-moment-locales", {
|
||||||
|
"locales": ["zh-cn"]
|
||||||
|
}]
|
||||||
|
]
|
||||||
|
}
|
||||||
24
packages/editor-skeleton/demo/usage.md
Normal file
24
packages/editor-skeleton/demo/usage.md
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
---
|
||||||
|
title: Simple Usage
|
||||||
|
order: 1
|
||||||
|
---
|
||||||
|
|
||||||
|
本 Demo 演示一行文字的用法。
|
||||||
|
|
||||||
|
````jsx
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
import ReactDOM from 'react-dom';
|
||||||
|
|
||||||
|
class App extends Component {
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ReactDOM.render((
|
||||||
|
<App />
|
||||||
|
), mountNode);
|
||||||
|
````
|
||||||
30
packages/editor-skeleton/es/components/LeftPlugin/index.d.ts
vendored
Normal file
30
packages/editor-skeleton/es/components/LeftPlugin/index.d.ts
vendored
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import { PureComponent } from 'react';
|
||||||
|
import './index.scss';
|
||||||
|
export default class LeftAddon extends PureComponent {
|
||||||
|
static displayName: string;
|
||||||
|
static propTypes: {
|
||||||
|
active: any;
|
||||||
|
config: any;
|
||||||
|
disabled: any;
|
||||||
|
dotted: any;
|
||||||
|
locked: any;
|
||||||
|
onClick: any;
|
||||||
|
};
|
||||||
|
static defaultProps: {
|
||||||
|
active: boolean;
|
||||||
|
config: {};
|
||||||
|
disabled: boolean;
|
||||||
|
dotted: boolean;
|
||||||
|
locked: boolean;
|
||||||
|
onClick: () => void;
|
||||||
|
};
|
||||||
|
static contextType: any;
|
||||||
|
constructor(props: any, context: any);
|
||||||
|
componentDidMount(): void;
|
||||||
|
componentWillUnmount(): void;
|
||||||
|
handleClose: () => void;
|
||||||
|
handleOpen: () => void;
|
||||||
|
handleShow: () => void;
|
||||||
|
renderIcon: (clickCallback: any) => JSX.Element;
|
||||||
|
render(): JSX.Element;
|
||||||
|
}
|
||||||
259
packages/editor-skeleton/es/components/LeftPlugin/index.js
Normal file
259
packages/editor-skeleton/es/components/LeftPlugin/index.js
Normal file
@ -0,0 +1,259 @@
|
|||||||
|
import _extends from "@babel/runtime/helpers/extends";
|
||||||
|
import _inheritsLoose from "@babel/runtime/helpers/inheritsLoose";
|
||||||
|
import React, { PureComponent, Fragment } from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import AppContext from '@ali/iceluna-sdk/lib/context/appContext';
|
||||||
|
import { Balloon, Dialog, Icon, Badge } from '@alife/next';
|
||||||
|
import './index.scss';
|
||||||
|
|
||||||
|
var LeftAddon = /*#__PURE__*/function (_PureComponent) {
|
||||||
|
_inheritsLoose(LeftAddon, _PureComponent);
|
||||||
|
|
||||||
|
function LeftAddon(_props, context) {
|
||||||
|
var _this;
|
||||||
|
|
||||||
|
_this = _PureComponent.call(this, _props, context) || this;
|
||||||
|
|
||||||
|
_this.handleClose = function () {
|
||||||
|
var addonKey = _this.props.config && _this.props.config.addonKey;
|
||||||
|
var currentAddon = _this.appHelper.addons && _this.appHelper.addons[addonKey];
|
||||||
|
|
||||||
|
if (currentAddon) {
|
||||||
|
_this.utils.transformToPromise(currentAddon.close()).then(function () {
|
||||||
|
_this.setState({
|
||||||
|
dialogVisible: false
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
_this.handleOpen = function () {
|
||||||
|
// todo 对话框类型的插件初始时拿不到插件实例
|
||||||
|
_this.setState({
|
||||||
|
dialogVisible: true
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
_this.handleShow = function () {
|
||||||
|
var _this$props = _this.props,
|
||||||
|
disabled = _this$props.disabled,
|
||||||
|
config = _this$props.config,
|
||||||
|
onClick = _this$props.onClick;
|
||||||
|
var addonKey = config && config.addonKey;
|
||||||
|
if (disabled || !addonKey) return; //考虑到弹窗情况,延时发送消息
|
||||||
|
|
||||||
|
setTimeout(function () {
|
||||||
|
return _this.appHelper.emit(addonKey + ".addon.activate");
|
||||||
|
}, 0);
|
||||||
|
|
||||||
|
_this.handleOpen();
|
||||||
|
|
||||||
|
onClick && onClick();
|
||||||
|
};
|
||||||
|
|
||||||
|
_this.renderIcon = function (clickCallback) {
|
||||||
|
var _this$props2 = _this.props,
|
||||||
|
active = _this$props2.active,
|
||||||
|
disabled = _this$props2.disabled,
|
||||||
|
dotted = _this$props2.dotted,
|
||||||
|
locked = _this$props2.locked,
|
||||||
|
_onClick = _this$props2.onClick,
|
||||||
|
config = _this$props2.config;
|
||||||
|
|
||||||
|
var _ref = config || {},
|
||||||
|
addonKey = _ref.addonKey,
|
||||||
|
props = _ref.props;
|
||||||
|
|
||||||
|
var _ref2 = props || {},
|
||||||
|
icon = _ref2.icon,
|
||||||
|
title = _ref2.title;
|
||||||
|
|
||||||
|
return React.createElement("div", {
|
||||||
|
className: classNames('luna-left-addon', addonKey, {
|
||||||
|
active: active,
|
||||||
|
disabled: disabled,
|
||||||
|
locked: locked
|
||||||
|
}),
|
||||||
|
"data-tooltip": title,
|
||||||
|
onClick: function onClick() {
|
||||||
|
if (disabled) return; //考虑到弹窗情况,延时发送消息
|
||||||
|
|
||||||
|
clickCallback && clickCallback();
|
||||||
|
_onClick && _onClick();
|
||||||
|
}
|
||||||
|
}, dotted ? React.createElement(Badge, {
|
||||||
|
dot: true
|
||||||
|
}, React.createElement(Icon, {
|
||||||
|
type: icon,
|
||||||
|
size: "small"
|
||||||
|
})) : React.createElement(Icon, {
|
||||||
|
type: icon,
|
||||||
|
size: "small"
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
_this.state = {
|
||||||
|
dialogVisible: false
|
||||||
|
};
|
||||||
|
_this.appHelper = context.appHelper;
|
||||||
|
_this.utils = _this.appHelper.utils;
|
||||||
|
_this.constants = _this.appHelper.constants;
|
||||||
|
return _this;
|
||||||
|
}
|
||||||
|
|
||||||
|
var _proto = LeftAddon.prototype;
|
||||||
|
|
||||||
|
_proto.componentDidMount = function componentDidMount() {
|
||||||
|
var config = this.props.config;
|
||||||
|
var addonKey = config && config.addonKey;
|
||||||
|
var appHelper = this.appHelper;
|
||||||
|
|
||||||
|
if (appHelper && addonKey) {
|
||||||
|
appHelper.on(addonKey + ".dialog.show", this.handleShow);
|
||||||
|
appHelper.on(addonKey + ".dialog.close", this.handleClose);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
_proto.componentWillUnmount = function componentWillUnmount() {
|
||||||
|
var config = this.props.config;
|
||||||
|
var appHelper = this.appHelper;
|
||||||
|
var addonKey = config && config.addonKey;
|
||||||
|
|
||||||
|
if (appHelper && addonKey) {
|
||||||
|
appHelper.off(addonKey + ".dialog.show", this.handleShow);
|
||||||
|
appHelper.off(addonKey + ".dialog.close", this.handleClose);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
_proto.render = function render() {
|
||||||
|
var _this2 = this;
|
||||||
|
|
||||||
|
var _this$props3 = this.props,
|
||||||
|
dotted = _this$props3.dotted,
|
||||||
|
locked = _this$props3.locked,
|
||||||
|
active = _this$props3.active,
|
||||||
|
disabled = _this$props3.disabled,
|
||||||
|
config = _this$props3.config;
|
||||||
|
|
||||||
|
var _ref3 = config || {},
|
||||||
|
addonKey = _ref3.addonKey,
|
||||||
|
props = _ref3.props,
|
||||||
|
type = _ref3.type,
|
||||||
|
addonProps = _ref3.addonProps;
|
||||||
|
|
||||||
|
var _ref4 = props || {},
|
||||||
|
_onClick2 = _ref4.onClick,
|
||||||
|
title = _ref4.title;
|
||||||
|
|
||||||
|
var dialogVisible = this.state.dialogVisible;
|
||||||
|
var _this$context = this.context,
|
||||||
|
appHelper = _this$context.appHelper,
|
||||||
|
components = _this$context.components;
|
||||||
|
if (!addonKey || !type || !props) return null;
|
||||||
|
var componentName = appHelper.utils.generateAddonCompName(addonKey);
|
||||||
|
var localeProps = {};
|
||||||
|
var locale = appHelper.locale,
|
||||||
|
messages = appHelper.messages;
|
||||||
|
|
||||||
|
if (locale) {
|
||||||
|
localeProps.locale = locale;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (messages && messages[componentName]) {
|
||||||
|
localeProps.messages = messages[componentName];
|
||||||
|
}
|
||||||
|
|
||||||
|
var AddonComp = components && components[componentName];
|
||||||
|
var node = AddonComp && React.createElement(AddonComp, _extends({
|
||||||
|
active: active,
|
||||||
|
locked: locked,
|
||||||
|
disabled: disabled,
|
||||||
|
config: config,
|
||||||
|
onClick: function onClick() {
|
||||||
|
_onClick2 && _onClick2.call(null, appHelper);
|
||||||
|
}
|
||||||
|
}, localeProps, addonProps || {})) || null;
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case 'LinkIcon':
|
||||||
|
return React.createElement("a", props.linkProps || {}, this.renderIcon(function () {
|
||||||
|
_onClick2 && _onClick2.call(null, appHelper);
|
||||||
|
}));
|
||||||
|
|
||||||
|
case 'Icon':
|
||||||
|
return this.renderIcon(function () {
|
||||||
|
_onClick2 && _onClick2.call(null, appHelper);
|
||||||
|
});
|
||||||
|
|
||||||
|
case 'DialogIcon':
|
||||||
|
return React.createElement(Fragment, null, this.renderIcon(function () {
|
||||||
|
_onClick2 && _onClick2.call(null, appHelper);
|
||||||
|
|
||||||
|
_this2.handleOpen();
|
||||||
|
}), React.createElement(Dialog, _extends({
|
||||||
|
onOk: function onOk() {
|
||||||
|
appHelper.emit(addonKey + ".dialog.onOk");
|
||||||
|
|
||||||
|
_this2.handleClose();
|
||||||
|
},
|
||||||
|
onCancel: this.handleClose,
|
||||||
|
onClose: this.handleClose,
|
||||||
|
title: title
|
||||||
|
}, props.dialogProps || {}, {
|
||||||
|
visible: dialogVisible
|
||||||
|
}), node));
|
||||||
|
|
||||||
|
case 'BalloonIcon':
|
||||||
|
return React.createElement(Balloon, _extends({
|
||||||
|
trigger: this.renderIcon(function () {
|
||||||
|
_onClick2 && _onClick2.call(null, appHelper);
|
||||||
|
}),
|
||||||
|
align: "r",
|
||||||
|
triggerType: ['click', 'hover']
|
||||||
|
}, props.balloonProps || {}), node);
|
||||||
|
|
||||||
|
case 'PanelIcon':
|
||||||
|
return this.renderIcon(function () {
|
||||||
|
_onClick2 && _onClick2.call(null, appHelper);
|
||||||
|
|
||||||
|
_this2.handleOpen();
|
||||||
|
});
|
||||||
|
|
||||||
|
case 'Custom':
|
||||||
|
return dotted ? React.createElement(Badge, {
|
||||||
|
dot: true
|
||||||
|
}, node) : node;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return LeftAddon;
|
||||||
|
}(PureComponent);
|
||||||
|
|
||||||
|
LeftAddon.displayName = 'LunaLeftAddon';
|
||||||
|
LeftAddon.propTypes = {
|
||||||
|
active: PropTypes.bool,
|
||||||
|
config: PropTypes.shape({
|
||||||
|
addonKey: PropTypes.string,
|
||||||
|
addonProps: PropTypes.object,
|
||||||
|
props: PropTypes.object,
|
||||||
|
type: PropTypes.oneOf(['DialogIcon', 'BalloonIcon', 'PanelIcon', 'LinkIcon', 'Icon', 'Custom'])
|
||||||
|
}),
|
||||||
|
disabled: PropTypes.bool,
|
||||||
|
dotted: PropTypes.bool,
|
||||||
|
locked: PropTypes.bool,
|
||||||
|
onClick: PropTypes.func
|
||||||
|
};
|
||||||
|
LeftAddon.defaultProps = {
|
||||||
|
active: false,
|
||||||
|
config: {},
|
||||||
|
disabled: false,
|
||||||
|
dotted: false,
|
||||||
|
locked: false,
|
||||||
|
onClick: function onClick() {}
|
||||||
|
};
|
||||||
|
LeftAddon.contextType = AppContext;
|
||||||
|
export { LeftAddon as default };
|
||||||
59
packages/editor-skeleton/es/components/LeftPlugin/index.scss
Normal file
59
packages/editor-skeleton/es/components/LeftPlugin/index.scss
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
.luna-left-addon {
|
||||||
|
font-size: 16px;
|
||||||
|
text-align: center;
|
||||||
|
line-height: 36px;
|
||||||
|
height: 36px;
|
||||||
|
position: relative;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
color: #777;
|
||||||
|
&.collapse {
|
||||||
|
height: 40px;
|
||||||
|
color: #8c8c8c;
|
||||||
|
border-bottom: 1px solid #bfbfbf;
|
||||||
|
}
|
||||||
|
&.locked {
|
||||||
|
color: red !important;
|
||||||
|
}
|
||||||
|
&.active {
|
||||||
|
color: #fff !important;
|
||||||
|
background-color: $color-brand1-9 !important;
|
||||||
|
&.disabled {
|
||||||
|
color: #fff;
|
||||||
|
background-color: $color-fill1-7;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&.disabled {
|
||||||
|
cursor: not-allowed;
|
||||||
|
color: $color-text1-1;
|
||||||
|
}
|
||||||
|
&:hover {
|
||||||
|
background-color: $color-brand1-1;
|
||||||
|
color: $color-brand1-6;
|
||||||
|
&:before {
|
||||||
|
content: attr(data-tooltip);
|
||||||
|
display: block;
|
||||||
|
position: absolute;
|
||||||
|
left: 50px;
|
||||||
|
top: 5px;
|
||||||
|
line-height: 18px;
|
||||||
|
font-size: 12px;
|
||||||
|
white-space: nowrap;
|
||||||
|
padding: 6px 8px;
|
||||||
|
border-radius: 4px;
|
||||||
|
background: rgba(0, 0, 0, 0.75);
|
||||||
|
color: #fff;
|
||||||
|
z-index: 100;
|
||||||
|
}
|
||||||
|
&:after {
|
||||||
|
content: '';
|
||||||
|
display: block;
|
||||||
|
position: absolute;
|
||||||
|
left: 40px;
|
||||||
|
top: 15px;
|
||||||
|
border: 5px solid transparent;
|
||||||
|
border-right-color: rgba(0, 0, 0, 0.75);
|
||||||
|
z-index: 100;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
30
packages/editor-skeleton/es/components/TopIcon/index.d.ts
vendored
Normal file
30
packages/editor-skeleton/es/components/TopIcon/index.d.ts
vendored
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import { PureComponent } from 'react';
|
||||||
|
import './index.scss';
|
||||||
|
export default class TopIcon extends PureComponent {
|
||||||
|
static displayName: string;
|
||||||
|
static propTypes: {
|
||||||
|
active: any;
|
||||||
|
className: any;
|
||||||
|
disabled: any;
|
||||||
|
icon: any;
|
||||||
|
id: any;
|
||||||
|
locked: any;
|
||||||
|
onClick: any;
|
||||||
|
showTitle: any;
|
||||||
|
style: any;
|
||||||
|
title: any;
|
||||||
|
};
|
||||||
|
static defaultProps: {
|
||||||
|
active: boolean;
|
||||||
|
className: string;
|
||||||
|
disabled: boolean;
|
||||||
|
icon: string;
|
||||||
|
id: string;
|
||||||
|
locked: boolean;
|
||||||
|
onClick: () => void;
|
||||||
|
showTitle: boolean;
|
||||||
|
style: {};
|
||||||
|
title: string;
|
||||||
|
};
|
||||||
|
render(): JSX.Element;
|
||||||
|
}
|
||||||
76
packages/editor-skeleton/es/components/TopIcon/index.js
Normal file
76
packages/editor-skeleton/es/components/TopIcon/index.js
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
import _Button from "@alifd/next/es/button";
|
||||||
|
import _Icon from "@alifd/next/es/icon";
|
||||||
|
import _inheritsLoose from "@babel/runtime/helpers/inheritsLoose";
|
||||||
|
import React, { PureComponent } from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import './index.scss';
|
||||||
|
|
||||||
|
var TopIcon = /*#__PURE__*/function (_PureComponent) {
|
||||||
|
_inheritsLoose(TopIcon, _PureComponent);
|
||||||
|
|
||||||
|
function TopIcon() {
|
||||||
|
return _PureComponent.apply(this, arguments) || this;
|
||||||
|
}
|
||||||
|
|
||||||
|
var _proto = TopIcon.prototype;
|
||||||
|
|
||||||
|
_proto.render = function render() {
|
||||||
|
var _this$props = this.props,
|
||||||
|
active = _this$props.active,
|
||||||
|
disabled = _this$props.disabled,
|
||||||
|
icon = _this$props.icon,
|
||||||
|
locked = _this$props.locked,
|
||||||
|
title = _this$props.title,
|
||||||
|
className = _this$props.className,
|
||||||
|
id = _this$props.id,
|
||||||
|
style = _this$props.style,
|
||||||
|
showTitle = _this$props.showTitle,
|
||||||
|
onClick = _this$props.onClick;
|
||||||
|
return React.createElement(_Button, {
|
||||||
|
type: "normal",
|
||||||
|
size: "large",
|
||||||
|
text: true,
|
||||||
|
className: classNames('lowcode-top-btn', className, {
|
||||||
|
active: active,
|
||||||
|
disabled: disabled,
|
||||||
|
locked: locked
|
||||||
|
}),
|
||||||
|
id: id,
|
||||||
|
style: style,
|
||||||
|
onClick: disabled ? null : onClick
|
||||||
|
}, React.createElement("div", null, React.createElement(_Icon, {
|
||||||
|
size: "large",
|
||||||
|
type: icon
|
||||||
|
}), showTitle && React.createElement("span", null, title)));
|
||||||
|
};
|
||||||
|
|
||||||
|
return TopIcon;
|
||||||
|
}(PureComponent);
|
||||||
|
|
||||||
|
TopIcon.displayName = 'TopIcon';
|
||||||
|
TopIcon.propTypes = {
|
||||||
|
active: PropTypes.bool,
|
||||||
|
className: PropTypes.string,
|
||||||
|
disabled: PropTypes.bool,
|
||||||
|
icon: PropTypes.string,
|
||||||
|
id: PropTypes.string,
|
||||||
|
locked: PropTypes.bool,
|
||||||
|
onClick: PropTypes.func,
|
||||||
|
showTitle: PropTypes.bool,
|
||||||
|
style: PropTypes.object,
|
||||||
|
title: PropTypes.string
|
||||||
|
};
|
||||||
|
TopIcon.defaultProps = {
|
||||||
|
active: false,
|
||||||
|
className: '',
|
||||||
|
disabled: false,
|
||||||
|
icon: '',
|
||||||
|
id: '',
|
||||||
|
locked: false,
|
||||||
|
onClick: function onClick() {},
|
||||||
|
showTitle: false,
|
||||||
|
style: {},
|
||||||
|
title: ''
|
||||||
|
};
|
||||||
|
export { TopIcon as default };
|
||||||
32
packages/editor-skeleton/es/components/TopIcon/index.scss
Normal file
32
packages/editor-skeleton/es/components/TopIcon/index.scss
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
.next-btn.next-large.lowcode-top-btn {
|
||||||
|
width: 44px;
|
||||||
|
height: 44px;
|
||||||
|
padding: 0;
|
||||||
|
margin: 4px -2px;
|
||||||
|
text-align: center;
|
||||||
|
border-radius: 8px;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
color: #777;
|
||||||
|
&.disabled {
|
||||||
|
cursor: not-allowed;
|
||||||
|
color: $color-text1-1;
|
||||||
|
}
|
||||||
|
&.locked {
|
||||||
|
color: red !important;
|
||||||
|
}
|
||||||
|
i.next-icon {
|
||||||
|
&:before {
|
||||||
|
font-size: 17px;
|
||||||
|
}
|
||||||
|
margin-right: 0;
|
||||||
|
line-height: 18px;
|
||||||
|
}
|
||||||
|
span {
|
||||||
|
display: block;
|
||||||
|
margin: 0px -5px 0;
|
||||||
|
line-height: 16px;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 12px;
|
||||||
|
transform: scale(0.8);
|
||||||
|
}
|
||||||
|
}
|
||||||
21
packages/editor-skeleton/es/components/TopPlugin/index.d.ts
vendored
Normal file
21
packages/editor-skeleton/es/components/TopPlugin/index.d.ts
vendored
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import { PureComponent } from 'react';
|
||||||
|
import './index.scss';
|
||||||
|
export default class TopPlugin extends PureComponent {
|
||||||
|
static displayName: string;
|
||||||
|
static defaultProps: {
|
||||||
|
active: boolean;
|
||||||
|
config: {};
|
||||||
|
disabled: boolean;
|
||||||
|
dotted: boolean;
|
||||||
|
locked: boolean;
|
||||||
|
onClick: () => void;
|
||||||
|
};
|
||||||
|
constructor(props: any, context: any);
|
||||||
|
componentDidMount(): void;
|
||||||
|
componentWillUnmount(): void;
|
||||||
|
handleShow: () => void;
|
||||||
|
handleClose: () => void;
|
||||||
|
handleOpen: () => void;
|
||||||
|
renderIcon: (clickCallback: any) => JSX.Element;
|
||||||
|
render(): JSX.Element;
|
||||||
|
}
|
||||||
213
packages/editor-skeleton/es/components/TopPlugin/index.js
Normal file
213
packages/editor-skeleton/es/components/TopPlugin/index.js
Normal file
@ -0,0 +1,213 @@
|
|||||||
|
import _Balloon from "@alifd/next/es/balloon";
|
||||||
|
import _Dialog from "@alifd/next/es/dialog";
|
||||||
|
import _extends from "@babel/runtime/helpers/extends";
|
||||||
|
import _Badge from "@alifd/next/es/badge";
|
||||||
|
import _inheritsLoose from "@babel/runtime/helpers/inheritsLoose";
|
||||||
|
import React, { PureComponent, Fragment } from 'react';
|
||||||
|
import TopIcon from '../TopIcon';
|
||||||
|
import './index.scss';
|
||||||
|
|
||||||
|
var TopPlugin = /*#__PURE__*/function (_PureComponent) {
|
||||||
|
_inheritsLoose(TopPlugin, _PureComponent);
|
||||||
|
|
||||||
|
function TopPlugin(_props, context) {
|
||||||
|
var _this;
|
||||||
|
|
||||||
|
_this = _PureComponent.call(this, _props, context) || this;
|
||||||
|
|
||||||
|
_this.handleShow = function () {
|
||||||
|
var _this$props = _this.props,
|
||||||
|
disabled = _this$props.disabled,
|
||||||
|
config = _this$props.config,
|
||||||
|
onClick = _this$props.onClick;
|
||||||
|
var addonKey = config && config.addonKey;
|
||||||
|
if (disabled || !addonKey) return; //考虑到弹窗情况,延时发送消息
|
||||||
|
|
||||||
|
setTimeout(function () {
|
||||||
|
return _this.appHelper.emit(addonKey + ".addon.activate");
|
||||||
|
}, 0);
|
||||||
|
|
||||||
|
_this.handleOpen();
|
||||||
|
|
||||||
|
onClick && onClick();
|
||||||
|
};
|
||||||
|
|
||||||
|
_this.handleClose = function () {
|
||||||
|
var addonKey = _this.props.config && _this.props.config.addonKey;
|
||||||
|
var currentAddon = _this.appHelper.addons && _this.appHelper.addons[addonKey];
|
||||||
|
|
||||||
|
if (currentAddon) {
|
||||||
|
_this.utils.transformToPromise(currentAddon.close()).then(function () {
|
||||||
|
_this.setState({
|
||||||
|
dialogVisible: false
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
_this.handleOpen = function () {
|
||||||
|
// todo dialog类型的插件初始时拿不动插件实例
|
||||||
|
_this.setState({
|
||||||
|
dialogVisible: true
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
_this.renderIcon = function (clickCallback) {
|
||||||
|
var _this$props2 = _this.props,
|
||||||
|
active = _this$props2.active,
|
||||||
|
disabled = _this$props2.disabled,
|
||||||
|
dotted = _this$props2.dotted,
|
||||||
|
locked = _this$props2.locked,
|
||||||
|
config = _this$props2.config,
|
||||||
|
_onClick = _this$props2.onClick;
|
||||||
|
|
||||||
|
var _ref = config || {},
|
||||||
|
pluginKey = _ref.pluginKey,
|
||||||
|
props = _ref.props;
|
||||||
|
|
||||||
|
var _ref2 = props || {},
|
||||||
|
icon = _ref2.icon,
|
||||||
|
title = _ref2.title;
|
||||||
|
|
||||||
|
var node = React.createElement(TopIcon, {
|
||||||
|
className: "lowcode-top-addon " + pluginKey,
|
||||||
|
active: active,
|
||||||
|
disabled: disabled,
|
||||||
|
locked: locked,
|
||||||
|
icon: icon,
|
||||||
|
title: title,
|
||||||
|
onClick: function onClick() {
|
||||||
|
if (disabled) return; //考虑到弹窗情况,延时发送消息
|
||||||
|
|
||||||
|
setTimeout(function () {
|
||||||
|
return _this.appHelper.emit(pluginKey + ".addon.activate");
|
||||||
|
}, 0);
|
||||||
|
clickCallback && clickCallback();
|
||||||
|
_onClick && _onClick();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return dotted ? React.createElement(_Badge, {
|
||||||
|
dot: true
|
||||||
|
}, node) : node;
|
||||||
|
};
|
||||||
|
|
||||||
|
_this.state = {
|
||||||
|
dialogVisible: false
|
||||||
|
};
|
||||||
|
return _this;
|
||||||
|
}
|
||||||
|
|
||||||
|
var _proto = TopPlugin.prototype;
|
||||||
|
|
||||||
|
_proto.componentDidMount = function componentDidMount() {
|
||||||
|
var config = this.props.config;
|
||||||
|
var pluginKey = config && config.pluginKey; // const appHelper = this.appHelper;
|
||||||
|
// if (appHelper && addonKey) {
|
||||||
|
// appHelper.on(`${addonKey}.dialog.show`, this.handleShow);
|
||||||
|
// appHelper.on(`${addonKey}.dialog.close`, this.handleClose);
|
||||||
|
// }
|
||||||
|
};
|
||||||
|
|
||||||
|
_proto.componentWillUnmount = function componentWillUnmount() {// const { config } = this.props;
|
||||||
|
// const addonKey = config && config.addonKey;
|
||||||
|
// const appHelper = this.appHelper;
|
||||||
|
// if (appHelper && addonKey) {
|
||||||
|
// appHelper.off(`${addonKey}.dialog.show`, this.handleShow);
|
||||||
|
// appHelper.off(`${addonKey}.dialog.close`, this.handleClose);
|
||||||
|
// }
|
||||||
|
};
|
||||||
|
|
||||||
|
_proto.render = function render() {
|
||||||
|
var _this2 = this;
|
||||||
|
|
||||||
|
var _this$props3 = this.props,
|
||||||
|
active = _this$props3.active,
|
||||||
|
dotted = _this$props3.dotted,
|
||||||
|
locked = _this$props3.locked,
|
||||||
|
disabled = _this$props3.disabled,
|
||||||
|
config = _this$props3.config,
|
||||||
|
editor = _this$props3.editor,
|
||||||
|
Comp = _this$props3.pluginClass;
|
||||||
|
|
||||||
|
var _ref3 = config || {},
|
||||||
|
pluginKey = _ref3.pluginKey,
|
||||||
|
pluginProps = _ref3.pluginProps,
|
||||||
|
props = _ref3.props,
|
||||||
|
type = _ref3.type;
|
||||||
|
|
||||||
|
var _ref4 = props || {},
|
||||||
|
_onClick2 = _ref4.onClick,
|
||||||
|
title = _ref4.title;
|
||||||
|
|
||||||
|
var dialogVisible = this.state.dialogVisible;
|
||||||
|
if (!pluginKey || !type || !Comp) return null;
|
||||||
|
var node = React.createElement(Comp, _extends({
|
||||||
|
active: active,
|
||||||
|
locked: locked,
|
||||||
|
disabled: disabled,
|
||||||
|
config: config,
|
||||||
|
onClick: function onClick() {
|
||||||
|
_onClick2 && _onClick2.call(null, editor);
|
||||||
|
}
|
||||||
|
}, pluginProps));
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case 'LinkIcon':
|
||||||
|
return React.createElement("a", props.linkProps, this.renderIcon(function () {
|
||||||
|
_onClick2 && _onClick2.call(null, editor);
|
||||||
|
}));
|
||||||
|
|
||||||
|
case 'Icon':
|
||||||
|
return this.renderIcon(function () {
|
||||||
|
_onClick2 && _onClick2.call(null, editor);
|
||||||
|
});
|
||||||
|
|
||||||
|
case 'DialogIcon':
|
||||||
|
return React.createElement(Fragment, null, this.renderIcon(function () {
|
||||||
|
_onClick2 && _onClick2.call(null, editor);
|
||||||
|
|
||||||
|
_this2.handleOpen();
|
||||||
|
}), React.createElement(_Dialog, _extends({
|
||||||
|
onOk: function onOk() {
|
||||||
|
editor.emit(pluginKey + ".dialog.onOk");
|
||||||
|
|
||||||
|
_this2.handleClose();
|
||||||
|
},
|
||||||
|
onCancel: this.handleClose,
|
||||||
|
onClose: this.handleClose,
|
||||||
|
title: title
|
||||||
|
}, props.dialogProps, {
|
||||||
|
visible: dialogVisible
|
||||||
|
}), node));
|
||||||
|
|
||||||
|
case 'BalloonIcon':
|
||||||
|
return React.createElement(_Balloon, _extends({
|
||||||
|
trigger: this.renderIcon(function () {
|
||||||
|
_onClick2 && _onClick2.call(null, editor);
|
||||||
|
}),
|
||||||
|
triggerType: ['click', 'hover']
|
||||||
|
}, props.balloonProps), node);
|
||||||
|
|
||||||
|
case 'Custom':
|
||||||
|
return dotted ? React.createElement(_Badge, {
|
||||||
|
dot: true
|
||||||
|
}, node) : node;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return TopPlugin;
|
||||||
|
}(PureComponent);
|
||||||
|
|
||||||
|
TopPlugin.displayName = 'lowcodeTopPlugin';
|
||||||
|
TopPlugin.defaultProps = {
|
||||||
|
active: false,
|
||||||
|
config: {},
|
||||||
|
disabled: false,
|
||||||
|
dotted: false,
|
||||||
|
locked: false,
|
||||||
|
onClick: function onClick() {}
|
||||||
|
};
|
||||||
|
export { TopPlugin as default };
|
||||||
@ -0,0 +1,2 @@
|
|||||||
|
.lowcode-top-addon {
|
||||||
|
}
|
||||||
14
packages/editor-skeleton/es/config/skeleton.d.ts
vendored
Normal file
14
packages/editor-skeleton/es/config/skeleton.d.ts
vendored
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
declare const routerConfig: {
|
||||||
|
path: string;
|
||||||
|
component: any;
|
||||||
|
children: ({
|
||||||
|
path: string;
|
||||||
|
component: any;
|
||||||
|
redirect?: undefined;
|
||||||
|
} | {
|
||||||
|
path: string;
|
||||||
|
redirect: string;
|
||||||
|
component?: undefined;
|
||||||
|
})[];
|
||||||
|
}[];
|
||||||
|
export default routerConfig;
|
||||||
14
packages/editor-skeleton/es/config/skeleton.js
Normal file
14
packages/editor-skeleton/es/config/skeleton.js
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import Dashboard from '@/pages/Dashboard';
|
||||||
|
import BasicLayout from '@/layouts/BasicLayout';
|
||||||
|
var routerConfig = [{
|
||||||
|
path: '/',
|
||||||
|
component: BasicLayout,
|
||||||
|
children: [{
|
||||||
|
path: '/dashboard',
|
||||||
|
component: Dashboard
|
||||||
|
}, {
|
||||||
|
path: '/',
|
||||||
|
redirect: '/dashboard'
|
||||||
|
}]
|
||||||
|
}];
|
||||||
|
export default routerConfig;
|
||||||
2
packages/editor-skeleton/es/config/utils.d.ts
vendored
Normal file
2
packages/editor-skeleton/es/config/utils.d.ts
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
declare const asideMenuConfig: any[];
|
||||||
|
export { asideMenuConfig };
|
||||||
3
packages/editor-skeleton/es/config/utils.js
Normal file
3
packages/editor-skeleton/es/config/utils.js
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
// 菜单配置
|
||||||
|
var asideMenuConfig = [];
|
||||||
|
export { asideMenuConfig };
|
||||||
33
packages/editor-skeleton/es/global.scss
Normal file
33
packages/editor-skeleton/es/global.scss
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
body {
|
||||||
|
font-family: PingFangSC-Regular, Roboto, Helvetica Neue, Helvetica, Tahoma,
|
||||||
|
Arial, PingFang SC-Light, Microsoft YaHei;
|
||||||
|
font-size: 12px;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
* {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.next-loading {
|
||||||
|
.next-loading-wrap {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.lowcode-editor {
|
||||||
|
.lowcode-main-content {
|
||||||
|
position: absolute;
|
||||||
|
top: 48px;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
display: flex;
|
||||||
|
background-color: #d8d8d8;
|
||||||
|
}
|
||||||
|
.lowcode-center-area {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
padding: 10px;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
8
packages/editor-skeleton/es/index.d.ts
vendored
Normal file
8
packages/editor-skeleton/es/index.d.ts
vendored
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import { PureComponent } from 'react';
|
||||||
|
import './global.scss';
|
||||||
|
export default class Skeleton extends PureComponent {
|
||||||
|
static displayName: string;
|
||||||
|
constructor(props: any);
|
||||||
|
componentWillUnmount(): void;
|
||||||
|
render(): JSX.Element;
|
||||||
|
}
|
||||||
70
packages/editor-skeleton/es/index.js
Normal file
70
packages/editor-skeleton/es/index.js
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
import _ConfigProvider from "@alifd/next/es/config-provider";
|
||||||
|
import _Loading from "@alifd/next/es/loading";
|
||||||
|
import _inheritsLoose from "@babel/runtime/helpers/inheritsLoose";
|
||||||
|
import React, { PureComponent } from 'react'; // import Editor from '@ali/lowcode-engine-editor';
|
||||||
|
|
||||||
|
import TopArea from './layouts/TopArea';
|
||||||
|
import LeftArea from './layouts/LeftArea';
|
||||||
|
import CenterArea from './layouts/CenterArea';
|
||||||
|
import RightArea from './layouts/RightArea';
|
||||||
|
import './global.scss';
|
||||||
|
|
||||||
|
var Skeleton = /*#__PURE__*/function (_PureComponent) {
|
||||||
|
_inheritsLoose(Skeleton, _PureComponent);
|
||||||
|
|
||||||
|
function Skeleton(props) {
|
||||||
|
var _this;
|
||||||
|
|
||||||
|
_this = _PureComponent.call(this, props) || this; // this.editor = new Editor(props.config, props.utils);
|
||||||
|
|
||||||
|
_this.editor = {
|
||||||
|
on: function on() {},
|
||||||
|
off: function off() {},
|
||||||
|
config: props.config,
|
||||||
|
pluginComponents: props.pluginComponents
|
||||||
|
};
|
||||||
|
return _this;
|
||||||
|
}
|
||||||
|
|
||||||
|
var _proto = Skeleton.prototype;
|
||||||
|
|
||||||
|
_proto.componentWillUnmount = function componentWillUnmount() {// this.editor && this.editor.destroy();
|
||||||
|
// this.editor = null;
|
||||||
|
};
|
||||||
|
|
||||||
|
_proto.render = function render() {
|
||||||
|
var _this$props = this.props,
|
||||||
|
location = _this$props.location,
|
||||||
|
history = _this$props.history,
|
||||||
|
messages = _this$props.messages;
|
||||||
|
this.editor.location = location;
|
||||||
|
this.editor.history = history;
|
||||||
|
this.editor.messages = messages;
|
||||||
|
return React.createElement(_ConfigProvider, null, React.createElement(_Loading, {
|
||||||
|
tip: "Loading",
|
||||||
|
size: "large",
|
||||||
|
visible: false,
|
||||||
|
shape: "fusion-reactor",
|
||||||
|
fullScreen: true
|
||||||
|
}, React.createElement("div", {
|
||||||
|
className: "lowcode-editor"
|
||||||
|
}, React.createElement(TopArea, {
|
||||||
|
editor: this.editor
|
||||||
|
}), React.createElement("div", {
|
||||||
|
className: "lowcode-main-content"
|
||||||
|
}, React.createElement(LeftArea.Nav, {
|
||||||
|
editor: this.editor
|
||||||
|
}), React.createElement(LeftArea.Panel, {
|
||||||
|
editor: this.editor
|
||||||
|
}), React.createElement(CenterArea, {
|
||||||
|
editor: this.editor
|
||||||
|
}), React.createElement(RightArea, {
|
||||||
|
editor: this.editor
|
||||||
|
})))));
|
||||||
|
};
|
||||||
|
|
||||||
|
return Skeleton;
|
||||||
|
}(PureComponent);
|
||||||
|
|
||||||
|
Skeleton.displayName = 'lowcodeEditorSkeleton';
|
||||||
|
export { Skeleton as default };
|
||||||
7
packages/editor-skeleton/es/layouts/CenterArea/index.d.ts
vendored
Normal file
7
packages/editor-skeleton/es/layouts/CenterArea/index.d.ts
vendored
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import { PureComponent } from 'react';
|
||||||
|
import './index.scss';
|
||||||
|
export default class CenterArea extends PureComponent {
|
||||||
|
static displayName: string;
|
||||||
|
constructor(props: any);
|
||||||
|
render(): JSX.Element;
|
||||||
|
}
|
||||||
24
packages/editor-skeleton/es/layouts/CenterArea/index.js
Normal file
24
packages/editor-skeleton/es/layouts/CenterArea/index.js
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import _inheritsLoose from "@babel/runtime/helpers/inheritsLoose";
|
||||||
|
import React, { PureComponent } from 'react';
|
||||||
|
import './index.scss';
|
||||||
|
|
||||||
|
var CenterArea = /*#__PURE__*/function (_PureComponent) {
|
||||||
|
_inheritsLoose(CenterArea, _PureComponent);
|
||||||
|
|
||||||
|
function CenterArea(props) {
|
||||||
|
return _PureComponent.call(this, props) || this;
|
||||||
|
}
|
||||||
|
|
||||||
|
var _proto = CenterArea.prototype;
|
||||||
|
|
||||||
|
_proto.render = function render() {
|
||||||
|
return React.createElement("div", {
|
||||||
|
className: "lowcode-center-area"
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return CenterArea;
|
||||||
|
}(PureComponent);
|
||||||
|
|
||||||
|
CenterArea.displayName = 'lowcodeCenterArea';
|
||||||
|
export { CenterArea as default };
|
||||||
@ -0,0 +1,3 @@
|
|||||||
|
.lowcode-center-area {
|
||||||
|
padding: 12px;
|
||||||
|
}
|
||||||
5
packages/editor-skeleton/es/layouts/LeftArea/index.d.ts
vendored
Normal file
5
packages/editor-skeleton/es/layouts/LeftArea/index.d.ts
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
declare const _default: {
|
||||||
|
Nav: any;
|
||||||
|
Panel: any;
|
||||||
|
};
|
||||||
|
export default _default;
|
||||||
6
packages/editor-skeleton/es/layouts/LeftArea/index.js
Normal file
6
packages/editor-skeleton/es/layouts/LeftArea/index.js
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
import Nav from './nav';
|
||||||
|
import Panel from './panel';
|
||||||
|
export default {
|
||||||
|
Nav: Nav,
|
||||||
|
Panel: Panel
|
||||||
|
};
|
||||||
21
packages/editor-skeleton/es/layouts/LeftArea/index.scss
Normal file
21
packages/editor-skeleton/es/layouts/LeftArea/index.scss
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
.lowcode-left-area-nav {
|
||||||
|
width: 48px;
|
||||||
|
height: 100%;
|
||||||
|
background: #ffffff;
|
||||||
|
border-right: 1px solid #e8ebee;
|
||||||
|
position: relative;
|
||||||
|
.top-area {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
width: 100%;
|
||||||
|
background: #ffffff;
|
||||||
|
max-height: 100%;
|
||||||
|
}
|
||||||
|
.bottom-area {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 20px;
|
||||||
|
width: 100%;
|
||||||
|
background: #ffffff;
|
||||||
|
max-height: calc(100% - 20px);
|
||||||
|
}
|
||||||
|
}
|
||||||
7
packages/editor-skeleton/es/layouts/LeftArea/nav.d.ts
vendored
Normal file
7
packages/editor-skeleton/es/layouts/LeftArea/nav.d.ts
vendored
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import { PureComponent } from 'react';
|
||||||
|
import './index.scss';
|
||||||
|
export default class LeftAreaPanel extends PureComponent {
|
||||||
|
static displayName: string;
|
||||||
|
constructor(props: any);
|
||||||
|
render(): JSX.Element;
|
||||||
|
}
|
||||||
24
packages/editor-skeleton/es/layouts/LeftArea/nav.js
Normal file
24
packages/editor-skeleton/es/layouts/LeftArea/nav.js
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import _inheritsLoose from "@babel/runtime/helpers/inheritsLoose";
|
||||||
|
import React, { PureComponent } from 'react';
|
||||||
|
import './index.scss';
|
||||||
|
|
||||||
|
var LeftAreaPanel = /*#__PURE__*/function (_PureComponent) {
|
||||||
|
_inheritsLoose(LeftAreaPanel, _PureComponent);
|
||||||
|
|
||||||
|
function LeftAreaPanel(props) {
|
||||||
|
return _PureComponent.call(this, props) || this;
|
||||||
|
}
|
||||||
|
|
||||||
|
var _proto = LeftAreaPanel.prototype;
|
||||||
|
|
||||||
|
_proto.render = function render() {
|
||||||
|
return React.createElement("div", {
|
||||||
|
className: "lowcode-left-area-nav"
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return LeftAreaPanel;
|
||||||
|
}(PureComponent);
|
||||||
|
|
||||||
|
LeftAreaPanel.displayName = 'lowcodeLeftAreaNav';
|
||||||
|
export { LeftAreaPanel as default };
|
||||||
7
packages/editor-skeleton/es/layouts/LeftArea/panel.d.ts
vendored
Normal file
7
packages/editor-skeleton/es/layouts/LeftArea/panel.d.ts
vendored
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import { PureComponent } from 'react';
|
||||||
|
import './index.scss';
|
||||||
|
export default class LeftAreaPanel extends PureComponent {
|
||||||
|
static displayName: string;
|
||||||
|
constructor(props: any);
|
||||||
|
render(): JSX.Element;
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user