fist commit react-renderer

This commit is contained in:
jianhui.fjh 2020-03-04 17:06:52 +08:00
parent 0725160eac
commit 8184d0aaf9
43 changed files with 5518 additions and 1 deletions

View File

@ -0,0 +1,11 @@
{
"presets": [
"@babel/preset-env",
"@babel/preset-react"
],
"plugins": [
"@babel/plugin-syntax-dynamic-import",
"@babel/plugin-proposal-class-properties",
"@babel/plugin-transform-runtime"
]
}

View File

@ -0,0 +1,11 @@
# 忽略目录
build/
tests/
demo/
# node 覆盖率文件
coverage/
# 忽略文件
**/*-min.js
**/*.min.js

View File

@ -0,0 +1,77 @@
module.exports = {
"root": true,
"extends": [
"eslint:recommended",
"plugin:react/recommended"
],
"plugins": [
"react"
],
"env": {
"browser": true,
"node": true,
"es6":true
},
"parser": "babel-eslint",
"parserOptions": {
"ecmaVersion": 6,
"sourceType": "module",
"ecmaFeatures": {
"jsx": true,
"experimentalObjectRestSpread": true
}
},
"rules": {
"react/no-deprecated": 0, // react15.x关闭deprated警告
"constructor-super": 2,//要求在构造函数中有 super() 的调用
"no-case-declarations": 2,//不允许在 case 子句中使用词法声明
"no-class-assign": 2,//禁止修改类声明的变量
"no-compare-neg-zero": 2,//禁止负0比较
"no-cond-assign": 2,//禁止条件表达式中出现赋值操作符
"no-console": [2, {
"allow": ["info", "warn", "error"]
}],//禁止console
"no-const-assign": 2,//禁止修改 const 声明的变量
"no-constant-condition": 2,//禁止在条件中使用常量表达式
"no-control-regex": 2,//禁止在正则表达式中使用控制字符
"no-debugger": 2,//禁止debugger
"no-delete-var": 2,//禁止删除变量
"no-dupe-args": 2,//禁止重复的参数
"no-dupe-class-members": 2,//禁止类成员中出现重复的名称
"no-dupe-keys": 2,//禁止重复的键值
"no-duplicate-case": 2,//禁止重复的case条件
"no-empty-character-class": 2,//禁止在正则表达式中使用空字符集
"no-empty-pattern": 2,//禁止使用空解构模式
"no-empty": 2,//禁止出现空语句块
"no-ex-assign": 2,//禁止对 catch 子句的参数重新赋值
"no-extra-boolean-cast": 2,//禁止不必要的布尔转换
"no-extra-semi": 2,//禁止多余的分号
"no-fallthrough": 2,//禁止 case 语句落空
"no-func-assign": 2,//禁止对 function 声明重新赋值
"no-global-assign": 2,//禁止对全局对象重新赋值
"no-inner-declarations": 2,//禁止在嵌套的块中出现变量声明或 function 声明
"no-invalid-regexp": 2,//禁止 RegExp 构造函数中存在无效的正则表达式字符串
"no-irregular-whitespace": 2,//禁止在字符串和注释之外不规则的空白
"no-mixed-spaces-and-tabs": 2,//禁止空格和 tab 的混合缩进
"no-new-symbol": 2,//禁止对Symbol使用new关键字
"no-obj-calls": 2,//禁止把全局对象作为函数调用
"no-octal": 2,//禁止8进制的字面量
"no-redeclare": 2,//禁止多次声明同一变量
"no-regex-spaces": 2,//禁止正则表达式字面量中出现多个空格
"no-self-assign": 2,//禁止自我赋值
"no-sparse-arrays": 2,//禁用稀疏数组
"no-this-before-super": 2,//禁止在构造函数中,在调用 super() 之前使用 this 或 super
"no-undef": 2,//禁用未声明的变量,除非它们在 /*global */ 注释中被提到
"no-unexpected-multiline": 2,//禁止出现令人困惑的多行表达式
"no-unreachable": 2,//禁止在return、throw、continue 和 break 语句之后出现不可达代码
"no-unsafe-finally": 2,//禁止在 finally 语句块中出现控制流语句
"no-unsafe-negation": 2,//禁止在表达式左侧使用关系表达式
"no-unused-labels": 2,//禁用出现未使用过的标
"no-unused-vars": 2,//禁止出现未使用过的变量
"no-useless-escape": 2,//禁用不必要的转义字符
"require-yield": 2,//要求 generator 函数内有 yield
"use-isnan": 2,//使用isNan() 判断isNaN
"valid-typeof": 2//强制 typeof 表达式与有效的字符串进行比较
},
"settings": {}
}

18
packages/react-renderer/.gitignore vendored Normal file
View File

@ -0,0 +1,18 @@
# See https://help.github.com/ignore-files/ for more about ignoring files.
# dependencies
/node_modules
# production
/build
/dist
/lib
# misc
.idea/
.happypack
.DS_Store
npm-debug.log*
yarn-debug.log*
yarn-error.log*

View File

@ -0,0 +1 @@
/src

View File

@ -0,0 +1,4 @@
{
"printWidth": 120,
"singleQuote": true
}

View File

@ -0,0 +1,84 @@
# 更新日志
## [1.0.0] - 2019-11-13
### 新增
* iceluna-sdk基本功能
## [1.0.1] - 2019-11-26
### 新增
* 粘贴schema时判断如果为非法schema触发illegalSchema.paste消息
* 增加schema.paste 和 schema.copy消息
* schema中支持__ignoreParse标记不需解析成ReactDom的schema结构
* 复制组件后,高亮选中新增的组件;
### 优化
* 修改sdk入口文件
* engine的hidden属性改为suspended
* websocket重连delay时间改为3s
### 修复
* 当异步请求返回数据中success为false的时候依然走请求成功回调由用户自己判断错误处理方式
* 修复预发发布以后异步请求配置变量不解析问题;
* 修复初始数据重新获取导致调试字段t不更新问题
* 修复异步请求配置参数变量解析时机不及时导致参数解析出错问题;
## [1.0.2] - 2019-12-16
### 新增
* 画布支持缩放以及placeholder设置
* window上挂载react和reactDOM
* 支持国际化;
* 画布支持layout设置
### 优化
* 若schema中的loop或者loopArgs变成默认值时删除该属性字段
* 扩展模式时组件最小高度调整为10px
* 采用react.forward透传compFactory和addonFactory高阶组件ref
* 插件通过context透传国际化配置
* 画布最外层组件支持设置固定宽高;
### 修复
* 修发布后的页面被嵌入到iframe中变量解析时的跨域问题
* 修复form循环问题
* 修复模型结构中null及undefined被转成空对象问题
* 修复fetch请求类型为PUT和DELETE时参数序列化问题
* 修复form自定义设置key导致的scopeprops不传递问题
* 修复beforeRequest和afterRequest中this上下文丢失问题
## [1.0.3] - 2019-12-24
### 新增
* compFactory和addonFactory增加version static字段
* 接入小二工作台fetch库
* 增加标准搭建协议转换API
* 上下文增加React Router的match属性
### 优化
* 错误码非200时依旧解析请求返回结果
* 增加编辑器环境判断并兼容vscode复制粘贴操作
* 国际化语言判断的边界校验;
### 修复
* 修复当children设置为变量时且condition为false时报错问题
* 修复未支持国际化的插件报错问题;
* 修复canvas获取viewport不存在时的报错问题
## [1.0.4] - 2020-01-13
### 新增
* 增加黄金令箭埋点API
* 新增ReadMe
* 渲染引擎支持Flagment组件
* 容器类组件支持自动loading及占位高度属性配置
### 优化
* schema序列化增加unsafe选项设置
* 优化自定义组件预览逻辑;
* 渲染引擎scope、ctx和self上下文构造重构
* 异步数据请求API兼容callback写法
* bzb增加根据url参数控制环境
### 修复
* 修复无内容状态容器展示异常的问题;
* 修复自定义组件children属性中添加Block导致Block内部组件无法进行依赖分析从而无法展示问题
* 修复componentsMap获取失败问题
* 升级bzb-request
* 修复初始数据源无内容返回导致容器组件不重新渲染问题;
* 修复单独使用this时变量解析过程中this转义问题

View File

@ -0,0 +1,2 @@
# Contributing Guidelines
TODO

View File

@ -1 +1,62 @@
react 渲染模块
# iceluna-sdk
> iceluna 底层引擎渲染能力。
## Table of Contents
- [Background](#background)
- [Install](#install)
- [Develop](#Develop)
- [Structure](#Structure)
- [Publish](#Publish)
- [Maintainers](#maintainers)
- [Contributing](#contributing)
- [License](#license)
## Background
iceluna 是由淘系技术部研发,面向中后台应用低代码搭建的通用解决方案,集前端应用工程创建、开发、调试及发布的全链路一体化的低代码搭建平台。同时,基于集团中后台 《搭建基础协议》 和 《物料协议》 标准之上,生产低代码搭建物料,沉淀搭建基础设施,助力上层不同业务领域 孵化低代码搭建产品,目标 打造成为基于集团标准的低代码搭建中台。
iceluna 代码整个项目结构如下图。
![iceluna代码仓库结构](https://img.alicdn.com/tfs/TB1KJThsHr1gK0jSZR0XXbP8XXa-660-322.png)
该项目 iceluna-SDK 为 iceluna 通用解决方案的提供最底层,最核心的渲染引擎能力。为 iceluna 体系上层可视化编辑器提供基础引擎能力。
## Install
```sh
# 前提tnpm install @ali/iceluna-cli -g
tnpm i
```
## Develop
1. 执行 `tnpm link`, 在全局建立符号链接。
2. 到[IDE项目](http://gitlab.alibaba-inc.com/iceluna/iceluna-IDE)启动前,执行 `tnpm link @ali/iceluna-sdk`,建立连接。
3. 按照 `iceluna-IDE` 方式调试
## Structure
```
.
└── src
├── comp
├── context
├── engine
├── hoc
├── utils
```
## Publish
## Maintainers
[@月飞](dingtalk://dingtalkclient/action/sendmsg?dingtalk_id=qhkv9cp), [@下羊](dingtalk://dingtalkclient/action/sendmsg?dingtalk_id=r93lhx4)
## Contributing
See [the contributing file](CONTRIBUTING.md)!
PRs accepted.
## License
MIT © 2019

View File

@ -0,0 +1,21 @@
import React, { PureComponent } from 'react';
import Widget from '_';
import './index.less';
export default class Example extends PureComponent {
static displayName = 'example';
constructor(props) {
super(props);
}
render() {
return (
<div className="example">
This is an example
<Widget />
</div>
);
}
}

View File

@ -0,0 +1,2 @@
.example {
}

View File

@ -0,0 +1,3 @@
{
"example": true
}

View File

@ -0,0 +1,11 @@
{
"requires": true,
"lockfileVersion": 1,
"dependencies": {
"prettier": {
"version": "1.19.1",
"resolved": "https://registry.npm.alibaba-inc.com/prettier/download/prettier-1.19.1.tgz",
"integrity": "sha1-99f1/4qc2HKnvkyhQglZVqYHl8s="
}
}
}

View File

@ -0,0 +1,71 @@
{
"name": "@ali/lowcode-engine-react-renderer",
"version": "0.0.1",
"description": "lowcode engine react renderer",
"main": "lib/index.js",
"scripts": {
"babel": "rm -rf lib && babel src -d lib --copy-files",
"prettier": "prettier --write \"./src/**/*.{js,jsx,ejs,less,css,scss,json}\" \"./demo/**/*.{js,jsx,ejs,less,css,scss,json}\"",
"build": "npm run babel",
"prepublish": "npm run prettier && npm run babel"
},
"repository": {
"type": "git",
"url": "git@gitlab.alibaba-inc.com:ali-lowcode/ali-lowcode-engine.git"
},
"keywords": [
"lowcode",
"engine",
"react"
],
"author": "xiayang.xy",
"license": "ISC",
"dependencies": {
"@ali/b3-one": "^0.0.17",
"@ali/bzb-request": "^2.6.0-beta.13",
"@ali/iceluna-comp-div": "^0.0.5",
"@ali/iceluna-rax": "0.0.5",
"@ali/lib-mtop": "^2.5.1",
"@alifd/next": "^1.18.17",
"debug": "^4.1.1",
"driver-universal": "^3.1.2",
"events": "^3.0.0",
"fetch-jsonp": "^1.1.3",
"intl-messageformat": "^7.7.2",
"jsonuri": "^2.1.2",
"keymaster": "^1.6.2",
"localforage": "^1.7.3",
"lodash": "^4.17.11",
"moment": "^2.24.0",
"rax": "^1.1.1",
"rax-find-dom-node": "^1.0.1",
"react-is": "^16.10.1",
"serialize-javascript": "^1.7.0",
"socket.io-client": "^2.2.0",
"whatwg-fetch": "^3.0.0"
},
"peerDependencies": {
"react": "^16.8.6",
"react-dom": "^16.8.6",
"prop-types": "^15.7.2"
},
"devDependencies": {
"@babel/cli": "^7.4.4",
"@babel/core": "^7.4.4",
"@babel/plugin-proposal-class-properties": "^7.0.0",
"@babel/plugin-proposal-decorators": "^7.0.0",
"@babel/plugin-syntax-dynamic-import": "^7.0.0",
"@babel/plugin-transform-runtime": "^7.4.4",
"@babel/preset-env": "^7.4.4",
"@babel/preset-react": "^7.0.0",
"babel-eslint": "^9.0.0",
"eslint": "^4.15.0",
"eslint-config-airbnb": "^17.1.0",
"eslint-loader": "^1.9.0",
"eslint-plugin-react": "^7.12.4",
"prettier": "^1.12.1"
},
"publishConfig": {
"registry": "http://registry.npm.alibaba-inc.com"
}
}

View File

@ -0,0 +1,33 @@
import { createElement, render, useState } from 'rax';
import React, { PureComponent } from 'react';
import DriverUniversal from 'driver-universal';
import { Engine } from '@ali/iceluna-rax';
import findDOMNode from 'rax-find-dom-node';
let updateRax = () => {};
export default class Rax extends PureComponent {
constructor(props) {
super(props);
}
componentDidMount() {
const RaxEngine = () => {
const [config, setConfig] = useState(this.props);
updateRax = setConfig;
return createElement(Engine, {
...config
});
};
render(createElement(RaxEngine), document.getElementById('luna-rax-container'), { driver: DriverUniversal });
}
componentDidUpdate() {
updateRax(this.props);
}
render() {
return <div id="luna-rax-container" />;
}
}
Rax.findDOMNode = findDOMNode;

View File

@ -0,0 +1,87 @@
import { PureComponent } from 'react';
import PropTypes from 'prop-types';
import AppContext from '../../context/appContext';
import { isEmpty, generateI18n, goldlog } from '../../utils';
export default class Addon extends PureComponent {
static displayName = 'lunaAddon';
static propTypes = {
config: PropTypes.object,
locale: PropTypes.string,
messages: PropTypes.object
};
static defaultProps = {
config: {}
};
static contextType = AppContext;
constructor(props, context) {
super(props, context);
if (isEmpty(props.config) || !props.config.addonKey) {
console.warn('luna addon has wrong config');
return;
}
// appHelper使IDEappHelper
context.appHelper = (window.__ctx && window.__ctx.appHelper) || context.appHelper;
context.locale = props.locale;
context.messages = props.messages;
//
this.appHelper = context.appHelper;
let { locale, messages } = props;
this.i18n = generateI18n(locale, messages);
this.addonKey = props.config.addonKey;
this.appHelper.addons = this.appHelper.addons || {};
this.appHelper.addons[this.addonKey] = this;
}
async componentWillUnmount() {
//
const config = this.props.config || {};
if (config && this.appHelper.addons) {
delete this.appHelper.addons[config.addonKey];
}
}
open = () => {
return true;
};
close = () => {
return true;
};
goldlog = (goKey, params) => {
const { addonKey, addonConfig = {} } = this.props.config || {};
goldlog(
goKey,
{
addonKey,
package: addonConfig.package,
version: addonConfig.version,
...this.appHelper.logParams,
...params
},
'addon'
);
};
get utils() {
return this.appHelper.utils;
}
get constants() {
return this.appHelper.constants;
}
get history() {
return this.appHelper.history;
}
get location() {
return this.appHelper.location;
}
render() {
return null;
}
}

View File

@ -0,0 +1,729 @@
import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import { on, off } from '@ali/b3-one/lib/event';
import AppHelper from '../../utils/appHelper';
import SchemaHelper from '../../utils/schemaHelper';
import DndHelper from '../../utils/dndHelper';
import Engine from '../../engine';
import CompFactory from '../../hoc/compFactory';
import {
isSchema,
isFileSchema,
isEmpty,
isJSSlot,
jsonuri,
registShortCuts,
unRegistShortCuts,
generateUtils,
parseObj,
shallowEqual,
addCssTag,
transformSchemaToPure,
goldlog
} from '../../utils';
import './index.scss';
const DESIGN_MODE = {
EXTEND: 'extend',
BORDER: 'border',
PREVIEW: 'preview'
};
const DEFAULT_PLACEHOLDER = {
emptyImage: '//img.alicdn.com/tfs/TB1zpkUoUT1gK0jSZFhXXaAtVXa-620-430.png',
emptyText: '当前页面为空~\n请拖拽组件放入页面容器内吧',
nullImage: '//img.alicdn.com/tfs/TB1m_oSoND1gK0jSZFsXXbldVXa-620-430.png',
nullText: '编辑内容不存在~!'
};
export default class Canvas extends PureComponent {
static displayName = 'Canvas';
static propTypes = {
appHelper: PropTypes.object,
components: PropTypes.object,
engine: PropTypes.element,
onCreate: PropTypes.func,
initSchema: PropTypes.object,
shortCuts: PropTypes.array,
utils: PropTypes.object
};
static defaultProps = {
components: {},
engine: Engine,
onCreate: () => {},
initSchema: {},
shortCuts: [],
utils: {}
};
constructor(props) {
super(props);
this.appHelper = props.appHelper || new AppHelper();
if (!this.appHelper.schemaHelper) {
this.appHelper.set('schemaHelper', new SchemaHelper(props.initSchema || {}, this.appHelper));
}
this.appHelper.set('basicSchemaHelper', this.appHelper.schemaHelper);
if (!this.appHelper.dndHelper) {
this.appHelper.set('dndHelper', new DndHelper(this.appHelper));
}
this.appHelper.dndHelper.setCanvasWin(window);
if (this.appHelper.designMode === undefined) {
this.appHelper.designMode = 'extend';
}
this.canvasAppHelper = new AppHelper({
history: this.appHelper.history,
location: this.appHelper.location,
match: this.appHelper.match
});
this.updateCanvasAppHelper(props);
this.appHelper.once('ide.ready', () => {
this.updateCanvasAppHelper(props);
});
window.__ctx = {
appHelper: this.appHelper,
canvasAppHelper: this.canvasAppHelper,
components: this.props.components
};
window.goldlog = window.goldlog || window.parent.goldlog;
this.state = {
canvasStack: [
{
lunaKey: 'root',
lunaPath: '',
name: 'root',
schemaHelper: this.appHelper.schemaHelper,
schema: this.appHelper.schemaHelper.get('schema')
}
]
};
this.appHelper.set('canvasStack', this.state.canvasStack);
}
componentDidMount() {
const appHelper = this.appHelper;
appHelper.batchOn(['behavior.undo', 'behavior.redo'], this.handleUndoRedo);
appHelper.on('schema.reset', this.handleSchemaReset);
appHelper.on('material.move', this.handleMaterialMove);
appHelper.on('material.add', this.handleMaterialAdd);
appHelper.on('material.remove', this.handleMaterialRemove);
appHelper.on('material.up', this.handleMaterialMoveUp);
appHelper.on('material.down', this.handleMaterialMoveDown);
appHelper.on('material.copy', this.handleMaterialCopy);
appHelper.on('material.update', this.handleMaterialUpdate);
appHelper.on('material.select', this.handleMaterialSelect);
appHelper.on('schemaHelper.schema.afterUpdate', this.handleReset);
appHelper.on('designMode.change', this.handleDesignModeChange);
appHelper.on('preview.change', this.handlePreviewChange);
appHelper.on('canvas.stack.push', this.handleCanvasPush);
appHelper.on('canvas.stack.pop', this.handleCanvasPop);
appHelper.on('canvas.stack.jump', this.handleCanvasJump);
appHelper.on('style.update', this.updateStyle);
appHelper.batchOn(['utils.update', 'constants.update', 'componentsMap.update'], this.handleCanvasAppHelperUpdate);
appHelper.on('viewPort.update', this.handleForceUpdate);
registShortCuts(this.props.shortCuts, this.appHelper);
this.appHelper.set('canvas', this);
this.props.onCreate(this.appHelper);
appHelper.emit('canvas.ready', this);
goldlog(
'EXP',
{
action: 'appear'
},
'canvas'
);
}
componentWillUnmount() {
const appHelper = this.appHelper;
appHelper.batchOff(['behavior.undo', 'behavior.redo'], this.handleUndoRedo);
appHelper.on('schema.reset', this.handleSchemaReset);
appHelper.off('material.move', this.handleMaterialMove);
appHelper.off('material.add', this.handleMaterialAdd);
appHelper.off('material.remove', this.handleMaterialRemove);
appHelper.off('material.up', this.handleMaterialMoveUp);
appHelper.off('material.down', this.handleMaterialMoveDown);
appHelper.off('material.copy', this.handleMaterialCopy);
appHelper.off('material.update', this.handleMaterialUpdate);
appHelper.off('material.select', this.handleMaterialSelect);
appHelper.off('schemaHelper.schema.afterUpdate', this.handleReset);
appHelper.off('designMode.change', this.handleDesignModeChange);
appHelper.off('preview.change', this.handlePreviewChange);
appHelper.off('canvas.stack.push', this.handleCanvasPush);
appHelper.off('canvas.stack.pop', this.handleCanvasPop);
appHelper.off('canvas.stack.jump', this.handleCanvasJump);
appHelper.off('style.update', this.updateStyle);
appHelper.batchOff(['utils.update', 'constants.update', 'componentsMap.update'], this.handleCanvasAppHelperUpdate);
appHelper.off('viewPort.update', this.handleForceUpdate);
unRegistShortCuts(this.props.shortCuts);
}
//
handleMaterialMove = ({ lunaKey, targetKey, direction }) => {
const appHelper = this.appHelper;
appHelper.schemaHelper.move(lunaKey, targetKey, direction);
appHelper.emit('behavior.record');
};
handleMaterialAdd = ({ schema, targetKey, direction }) => {
if (!isSchema(schema)) {
throw new Error('物料schema结构异常无法添加');
}
const appHelper = this.appHelper;
const addSchema = Array.isArray(schema) ? schema[0] : schema;
//
if (isFileSchema(addSchema) && !addSchema.fileName) {
return appHelper.emit('onFileNameMaterial.add', { schema: addSchema, targetKey, direction });
}
const addKey = appHelper.schemaHelper.add(schema, targetKey, direction);
appHelper.emit('behavior.record');
this.autoSelectComponent(addKey);
};
handleMaterialRemove = lunaKey => {
const appHelper = this.appHelper;
const schemaHelper = appHelper.schemaHelper;
const currCompSchema = schemaHelper.schemaMap[lunaKey];
//
const nextCompSchema = jsonuri.get(
schemaHelper.schema,
currCompSchema.__ctx.lunaPath.replace(/\/(\d+)$/, (res, idx) => `/${parseInt(idx) + 1}`)
);
const activeKey = (nextCompSchema && nextCompSchema.__ctx.lunaKey) || currCompSchema.__ctx.parentKey;
appHelper.schemaHelper.remove(lunaKey);
appHelper.emit('behavior.record');
this.autoSelectComponent(activeKey);
};
handleMaterialMoveUp = lunaKey => {
const appHelper = this.appHelper;
appHelper.schemaHelper && appHelper.schemaHelper.slide(lunaKey, 'up');
appHelper.emit('behavior.record');
};
handleMaterialMoveDown = lunaKey => {
const appHelper = this.appHelper;
appHelper.schemaHelper && appHelper.schemaHelper.slide(lunaKey, 'down');
appHelper.emit('behavior.record');
};
handleMaterialCopy = lunaKey => {
const appHelper = this.appHelper;
const addKey = appHelper.schemaHelper.copy(lunaKey);
appHelper.emit('behavior.record');
this.autoSelectComponent(addKey);
};
handleMaterialUpdate = ({ lunaKey, props, propsKey }) => {
const appHelper = this.appHelper;
appHelper.schemaHelper.update(lunaKey, props);
appHelper.emit('behavior.record', { lunaKey, propsKey });
};
handleMaterialSelect = (lunaKey, options) => {
const appHelper = this.appHelper;
if (appHelper.activeKey === lunaKey) return;
appHelper.set('activeKey', lunaKey);
appHelper.emit('material.select.change', lunaKey, options);
const preNode = document.querySelectorAll('[data-active=true]');
if (preNode[0] && preNode[0].dataset.lunaKey === lunaKey) return;
(preNode || []).forEach(item => {
item.removeAttribute('data-active');
item.removeAttribute('data-nochild');
});
//
if (!lunaKey) {
window.parent.t = window.t = null;
return;
}
let schema = appHelper.schemaHelper.schemaMap[lunaKey];
if (!schema) return;
let componentInfo = appHelper.componentsMap[schema.componentName];
const currentNode = document.querySelectorAll(`[data-luna-key=${lunaKey}]`);
(currentNode || []).forEach(item => {
item.setAttribute('data-active', 'true');
if (componentInfo && componentInfo.isContainer && schema && (!schema.children || !schema.children.length)) {
item.setAttribute('data-nochild', 'true');
}
});
// for debug
let ctx = this.appHelper.schemaHelper.compCtxMap[lunaKey];
let ref = this.appHelper.schemaHelper.compThisMap[lunaKey];
let t = {
ctx,
schema,
ref
};
t.__proto__ = ctx;
window.parent.t = window.t = t;
};
handleDesignModeChange = designMode => {
this.appHelper.set('designMode', designMode);
this.forceUpdate();
};
handlePreviewChange = isPreview => {
this.appHelper.set('isPreview', isPreview);
this.forceUpdate();
};
handleUndoRedo = schema => {
this.appHelper.schemaHelper.reset(schema);
this.autoSelectComponent();
};
handleSchemaReset = schema => {
this.appHelper.schemaHelper.reset(schema);
this.appHelper.emit('behavior.record');
this.autoSelectComponent();
};
handleReset = () => {
this.updateCanvasStack();
this.forceUpdate();
this.updateStyle();
};
handleCanvasAppHelperUpdate = () => {
this.updateCanvasAppHelper();
this.forceUpdate();
};
handleForceUpdate = () => {
this.forceUpdate();
};
handleCanvasPush = ({ schema, lunaKey, name }) => {
const appHelper = this.appHelper;
appHelper.emit('canvas.stack.beforePush');
const { canvasStack } = this.state;
const tempSchema = {
componentName: 'Temp',
fileName: 'temp',
props: {},
children: isJSSlot(schema) ? schema.value : schema //slot
};
const schemaHelper = new SchemaHelper(transformSchemaToPure(tempSchema), this.appHelper);
const schemaMap = this.appHelper.schemaHelper.schemaMap || {};
const compCtxMap = this.appHelper.schemaHelper.compCtxMap || {};
const currentComp = schemaMap[lunaKey];
const undoRedoKey = `${lunaKey}_${canvasStack.length}`;
//appHelperundoRedoKey
if (canvasStack.length === 1) {
canvasStack[0].undoRedoKey = this.appHelper.undoRedoKey;
}
const currentData = {
lunaKey,
lunaPath: currentComp.__ctx.lunaPath,
name,
schema,
schemaHelper,
ctx: compCtxMap[lunaKey],
undoRedoKey,
componentName: currentComp.componentName
};
appHelper.set('schemaHelper', schemaHelper);
appHelper.undoRedoHelper && appHelper.undoRedoHelper.create(undoRedoKey, tempSchema);
appHelper.set('undoRedoKey', undoRedoKey);
appHelper.set('activeKey', null);
this.setState(
{
canvasStack: [...this.state.canvasStack, currentData]
},
() => {
this.appHelper.set('canvasStack', this.state.canvasStack);
this.appHelper.emit('canvas.stack.afterPush', currentData, this.state.canvasStack);
this.autoSelectComponent();
}
);
};
handleCanvasPop = () => {
const { canvasStack } = this.state;
if (canvasStack.length > 1) {
this.handleCanvasJump(null, true);
}
};
handleCanvasJump = (idx, isPop) => {
const { canvasStack } = this.state;
const appHelper = this.appHelper;
let preIdx = idx + 1;
if (isPop) {
appHelper.emit('canvas.stack.beforePop');
preIdx = canvasStack.length - 1;
idx = preIdx - 1;
} else {
appHelper.emit('canvas.stack.beforeJump');
}
if (idx < 0 || idx > canvasStack.length - 1) return;
const preData = canvasStack[preIdx];
const currentData = canvasStack[idx];
appHelper.set('schemaHelper', currentData.schemaHelper);
appHelper.set('undoRedoKey', currentData.undoRedoKey);
appHelper.undoRedoHelper && appHelper.undoRedoHelper.delete(preData.undoRedoKey);
this.setState(
{
canvasStack: canvasStack.slice(0, idx + 1)
},
() => {
appHelper.set('canvasStack', this.state.canvasStack);
appHelper.schemaHelper.reset(appHelper.schemaHelper.schema);
appHelper.emit('behavior.record');
appHelper.emit(`canvas.stack.${isPop ? 'afterPop' : 'afterJump'}`, preData, this.state.canvasStack);
this.autoSelectComponent(preData.lunaKey);
}
);
};
//
handleCompGetCtx = (schema, ctx) => {
const lunaKey = schema && schema.__ctx && schema.__ctx.lunaKey;
if (!lunaKey) return;
// console.log('+++++ getCtx', lunaKey, ctx);
this.appHelper.schemaHelper.compCtxMap[lunaKey] = ctx;
// for debug
if (this.appHelper.activeKey && lunaKey === this.appHelper.activeKey) {
let ref = this.appHelper.schemaHelper.compThisMap[lunaKey];
let t = {
ctx,
schema,
ref
};
t.__proto__ = ctx;
window.parent.t = window.t = t;
}
};
handleCompGetRef = (schema, ref, topLevel) => {
const lunaKey = schema && schema.__ctx && schema.__ctx.lunaKey;
if (!lunaKey) return;
// console.log('----- getRef', lunaKey, ref);
const schemaHelper = this.appHelper.schemaHelper;
schemaHelper.compThisMap[lunaKey] = ref;
if (ref && !ref.__design) {
this.updateDesignMode(ref, schema, topLevel);
const didUpdate = ref.componentDidUpdate;
ref.componentDidUpdate = (...args) => {
didUpdate && didUpdate.apply(ref, args);
this.updateDesignMode(ref, schema, topLevel);
};
const willUnmount = ref.componentWillUnmount;
ref.componentWillUnmount = (...args) => {
willUnmount && willUnmount.apply(ref, args);
// console.log('----- destroy', lunaKey, ref);
delete schemaHelper.compThisMap[lunaKey];
delete schemaHelper.compCtxMap[lunaKey];
};
ref.__design = true;
}
};
handleDnd = (type, ev, schema) => {
const lunaKey = schema && schema.__ctx && schema.__ctx.lunaKey;
const designMode = this.appHelper.designMode;
if (!lunaKey || ![DESIGN_MODE.EXTEND, DESIGN_MODE.BORDER].includes(designMode)) return;
const dndHelper = this.appHelper && this.appHelper.dndHelper;
if (dndHelper) {
dndHelper[`handle${type}`](ev, lunaKey);
}
};
//
autoSelectComponent = lunaKey => {
const appHelper = this.appHelper;
//
if (appHelper.activeKey && !lunaKey) return;
if (!lunaKey) {
//
const schema = appHelper.schemaHelper.schema;
if (schema) {
if (schema.componentName === 'Temp') {
lunaKey = schema.children && schema.children[0] && schema.children[0].__ctx.lunaKey;
} else {
lunaKey = schema.__ctx.lunaKey;
}
}
}
appHelper.emit('material.select', lunaKey);
};
//
generateLowComps = (props = this.props) => {
const { components } = props;
const { utils, constants } = this.canvasAppHelper || {};
const componentsMap = this.appHelper.componentsMap || {};
Object.keys(componentsMap).forEach(key => {
const comp = componentsMap[key];
//
if (comp.version === '0.0.0' && comp.code) {
let schema = parseObj(comp.code);
if (isFileSchema(schema) && schema.componentName === 'Component') {
components[comp.name] = CompFactory(schema, components, componentsMap, {
utils,
constants
});
}
}
});
return components;
};
updateCanvasAppHelper = (props = this.props) => {
const { utils } = props;
const { entityInfo = {}, componentsMap } = this.appHelper;
this.canvasAppHelper.set({
componentsMap,
utils: entityInfo.utils ? generateUtils(utils, parseObj(entityInfo.utils)) : utils,
constants: parseObj((entityInfo && entityInfo.constants) || {})
});
this.canvasAppHelper.set('components', this.generateLowComps(props));
};
updateStyle = () => {
const entityInfo = this.appHelper.entityInfo || {};
const blockSchemaMap = (this.appHelper.schemaHelper && this.appHelper.schemaHelper.blockSchemaMap) || {};
const componentsMap = this.appHelper.componentsMap || {};
const cssMap = {};
//
Object.keys(blockSchemaMap).forEach(item => {
const schema = blockSchemaMap[item];
cssMap[schema.fileName] = schema.css || (schema.__ctx && schema.__ctx.css) || '';
});
//
Object.keys(componentsMap).forEach(item => {
const comp = componentsMap[item];
//
if (comp.version === '0.0.0' && comp.code && comp.css) {
cssMap[comp.name] = comp.css;
}
});
cssMap.__global = entityInfo.css || '';
if (shallowEqual(this.cacheCssMap, cssMap)) return;
this.cacheCssMap = cssMap;
const { __global, ...other } = cssMap;
addCssTag(
'luna-canvas-style',
`${__global}\n${Object.keys(other || {})
.map(item => cssMap[item])
.join('\n')}`
);
};
updateCanvasStack = () => {
const { canvasStack } = this.state;
if (canvasStack.length < 2) return;
for (let idx = canvasStack.length - 1; idx > 0; idx--) {
const currentData = canvasStack[idx];
const { lunaPath, name, schemaHelper, schema } = currentData;
const preData = canvasStack[idx - 1];
let data = schemaHelper.getPureSchema().children;
//
if (isEmpty(data)) {
jsonuri.rm(
preData.schemaHelper.schema,
name === 'children' ? `${lunaPath}/children` : `${lunaPath}/props/${name.replace('.', '/')}`
);
continue;
}
if (isJSSlot(schema)) {
data = {
...schema,
value: data
};
} else if (name !== 'children') {
data = {
type: 'JSSlot',
value: data
};
}
jsonuri.set(
preData.schemaHelper.schema,
name === 'children' ? `${lunaPath}/children` : `${lunaPath}/props/${name.replace('.', '/')}`,
data
);
}
};
updateDesignMode = (ref, schema, topLevel) => {
if (!ref || (ref && ref.current === null) || !schema.__ctx) return;
const { engine } = this.props;
const appHelper = this.appHelper;
const { activeKey, isPreview, viewPortConfig } = appHelper;
const designMode = isPreview ? 'preview' : appHelper.designMode;
const node = engine.findDOMNode(ref.current || ref);
if (!node || !node.getAttribute) return;
// __disableDesignMode
const hasDesignMode =
[DESIGN_MODE.EXTEND, DESIGN_MODE.BORDER].includes(designMode) && !ref.props.__disableDesignMode;
node.setAttribute('data-design-mode', designMode && hasDesignMode ? `luna-design-${designMode}` : '');
if (topLevel) {
node.setAttribute('top-container', true);
}
const lunaKey = schema.__ctx.lunaKey;
let instanceName = schema.componentName + (window.parent.__isDebug ? (lunaKey || '_').split('_')[1] : '');
switch (schema.componentName) {
case 'Page':
instanceName = '页面容器 ' + instanceName;
break;
case 'Block':
instanceName = '区块容器 ' + instanceName;
break;
case 'Component':
instanceName = '低代码组件容器 ' + instanceName;
break;
case 'Addon':
instanceName = '插件容器 ' + instanceName;
break;
case 'Temp':
instanceName = '下钻编辑器容器';
}
if (topLevel && viewPortConfig) {
node.style.transform = `scale(${viewPortConfig.scale ? viewPortConfig.scale / 100 : 1})`;
}
node.setAttribute('data-instance-name', instanceName);
node.setAttribute('data-luna-key', lunaKey);
node.setAttribute('data-luna-path', schema.__ctx.lunaPath);
if (hasDesignMode) {
if (activeKey && activeKey === lunaKey) {
node.setAttribute('data-active', true);
} else {
node.removeAttribute('data-active');
}
//
if (!node.compEvent && schema.componentName !== 'Temp') {
node.compEvent = ev => {
ev.stopPropagation();
appHelper.emit('material.select', lunaKey, { isFromCanvas: true });
};
on(node, 'mousedown', node.compEvent, false);
}
// drag and drop
if (!node.draggableFlag) {
if (topLevel) {
node.ondragleave = ev => this.handleDnd('DragLeave', ev, schema);
node.ondrop = ev => this.handleDnd('Drop', ev, schema);
} else {
node.setAttribute('draggable', 'true');
node.ondragstart = ev => this.handleDnd('DragStart', ev, schema);
node.ondragend = ev => this.handleDnd('DragEnd', ev, schema);
}
node.ondragover = ev => this.handleDnd('DragOver', ev, schema);
node.draggableFlag = true;
}
} else {
//
if (node.compEvent) {
off(node, 'mousedown', node.compEvent, false);
delete node.compEvent;
}
//drag and drop
if (node.draggableFlag) {
node.removeAttribute('draggable');
delete node.ondragstart;
delete node.ondragover;
delete node.ondragend;
delete node.ondragleave;
delete node.ondrop;
delete node.draggableFlag;
}
}
};
renderCanvasStack = () => {
const { canvasStack } = this.state;
const lastIndex = canvasStack.length - 1;
const appHelper = this.appHelper;
const canvasAppHelper = this.canvasAppHelper;
const designMode = appHelper.isPreview ? 'preview' : appHelper.designMode;
const Engine = this.props.engine;
return (canvasStack || []).map((item, idx) => (
<div
className={classNames(
'engine-wrapper',
designMode,
item.schemaHelper.schema &&
item.schemaHelper.schema.props.style && {
'fixed-width': item.schemaHelper.schema.props.style.width,
'fixed-height': item.schemaHelper.schema.props.style.height
}
)}
key={`${item.lunaKey}_${item.name}`}
>
<Engine
schema={item.schemaHelper.schema}
__ctx={item.ctx}
designMode={designMode}
appHelper={canvasAppHelper}
components={canvasAppHelper.components}
componentsMap={appHelper.componentsMap}
suspended={idx !== lastIndex}
onCompGetCtx={this.handleCompGetCtx}
onCompGetRef={this.handleCompGetRef}
/>
</div>
));
};
render() {
const { canvasStack } = this.state;
const lastIndex = canvasStack.length - 1;
const schema = canvasStack[lastIndex] && canvasStack[lastIndex].schemaHelper.schema;
const appHelper = this.appHelper;
const { entityInfo = {}, viewPortConfig = {}, canvasPlaceholder = {} } = appHelper;
const components = this.canvasAppHelper.components || {};
const placeholder = { ...DEFAULT_PLACEHOLDER, ...canvasPlaceholder };
const layoutComp = entityInfo.layoutInfo && entityInfo.layoutInfo.name;
const layoutProps = (entityInfo.layoutInfo && entityInfo.layoutInfo.realProps) || {};
const Layout = layoutComp && components[layoutComp];
const { hideLayout } = viewPortConfig;
const isDrillDown = canvasStack && canvasStack.length > 1;
const isSchemaEmpty = isSchema(schema) && isEmpty(schema.children);
const isSchemaNull = schema === null;
const canvasStyle = {};
if (!isDrillDown) {
if (isSchemaEmpty) {
canvasStyle.backgroundImage = `url(${placeholder.emptyImage})`;
} else if (isSchemaNull) {
canvasStyle.backgroundImage = `url(${placeholder.nullImage})`;
}
}
return (
<div
className={classNames('luna-canvas-inner', {
empty: isSchemaEmpty,
null: isSchemaNull,
'drill-down': isDrillDown
})}
style={canvasStyle}
data-placeholder-text={isSchemaEmpty ? placeholder.emptyText : isSchemaNull ? placeholder.nullText : ''}
>
{Layout && !hideLayout ? (
<Layout location={appHelper.location} history={appHelper.history} {...layoutProps}>
{this.renderCanvasStack()}
</Layout>
) : (
this.renderCanvasStack()
)}
</div>
);
}
}

View File

@ -0,0 +1,361 @@
/*增加标签函数*/
@mixin labelFun($type: before) {
&:#{$type} {
content: attr(data-instance-name) !important;
position: absolute;
left: 0;
top: 0;
right: unset;
bottom: unset;
color: #666 !important;
font-size: 12px !important;
float: left;
padding: 0 5px !important;
line-height: 12px !important;
height: 12px;
overflow: hidden;
background: rgba(222, 222, 222, 0.7);
z-index: 2;
border-left: 3px solid transparent;
transform-origin: 0 0;
transform: scale(0.8);
transition: all 0.3s ease;
}
&.luna-block,
&.luna-page,
&.luna-comp {
&:#{$type} {
border-color: #2077ff;
}
}
&[data-active='true'] {
&:#{$type} {
color: #fff !important;
background: #1861d5 !important;
}
}
&[data-design-mode='luna-design-border'] {
&:#{$type} {
display: none;
}
&[data-active='true'] {
&:#{$type} {
display: block;
}
}
}
}
.luna-canvas-inner {
height: 100%;
&.empty,
&.null {
position: relative;
background-repeat: no-repeat;
background-position: calc(50% - 180px) 50%;
background-size: 310px 215px;
&:after {
content: attr(data-placeholder-text);
position: absolute;
pointer-events: none;
top: 50%;
left: 50%;
margin: -40px 0 0 20px;
height: 80px;
line-height: 40px;
color: #aaa;
font-size: 24px;
white-space: pre;
}
}
&.empty {
&.drill-down {
&:before {
display: none;
}
&:after {
content: '请拖入组件';
text-align: center;
left: 0;
right: 0;
margin-left: 0;
font-size: 14px;
}
}
}
.engine-wrapper {
height: 100%;
display: none;
overflow: auto;
&.extend,
&.border {
padding: 1px;
> div {
padding: 1px;
}
}
&:last-child {
display: block;
}
&.fixed-width > div {
min-width: unset;
width: auto;
}
&.fixed-height > div {
min-height: unset;
}
> div {
transform-origin: left top;
min-width: 100%;
width: fit-content;
min-height: 100%;
}
}
}
a,
span:not(.next-input-group):not(.next-input) {
&[data-design-mode*='luna-design-'] {
display: inline-block;
min-height: 16px;
}
}
[data-luna-key] {
transition: all 0.3s ease;
}
[data-design-mode='luna-design-border'] {
min-height: 10px;
}
[data-design-mode='luna-design-extend'] {
min-height: 20px;
}
[data-design-mode*='luna-design-'] {
position: relative;
outline: 1px dotted #d9d9d9;
zoom: 1;
&:hover {
outline: 1px dotted #2077ff;
}
[draggable='true'] {
position: relative;
}
.next-card-body {
overflow: inherit !important;
}
&.next-loading {
pointer-events: all !important;
}
&[data-active='true'] {
outline: 1px solid #1861d5 !important;
&[data-nochild='true']:not(.next-step-item):not([top-container='true']) {
min-height: 60px;
min-width: 200px;
&:after {
content: '请拖入组件';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
margin: auto;
opacity: 1 !important;
visibility: visible !important;
line-height: 30px;
height: 30px;
font-size: 13px;
color: #ccc;
text-align: center;
}
}
}
&:not(.next-tag):not(.next-icon):not(.anticon):not(.icon) {
@include labelFun(before);
}
&.next-tag,
&.next-icon,
&.anticon,
&.icon {
@include labelFun(after);
}
&.next-tabs-tabpane.hidden {
min-height: 0;
}
&.ant-loop:after {
content: '';
display: block;
clear: both;
line-height: 0;
}
.ant-tabs-tabpane {
padding: 1px;
}
.ide-design-placeholder {
position: relative;
text-align: center;
border: 1px dashed #d9d9d9;
outline: none;
padding: 0 !important;
min-height: 20px;
min-width: 80px;
&:after {
content: attr(data-prop);
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
text-align: center;
line-height: 20px;
color: #e9e9e9;
}
}
&[data-instance-name='TableGroupHeaderF'] {
clear: both;
&:after {
content: '';
width: 100%;
height: 0;
clear: both;
overflow: hidden;
}
}
&[data-design-mode='luna-design-extend'] {
&[data-luna-key*='luna_'] {
&:not([class*='-input']):not([class*='-picker']):not([class*='-table']):not([class*='-switch']):not([class*='-select']):not(img):not([class*='-btn']):not(.next-tag):not(input):not([class*='-rating']):not([class*='next-menu']) {
padding: 10px;
}
&.ant-loop {
padding: 10px 0 0;
}
}
[class*='-form-item-control'] {
& > [data-design-mode*='luna-design-'] {
&:not(button):not(input):not([class*='-input']):not([class*='luna-comp-']) {
padding: 0 !important;
}
}
}
}
}
#luna-canvas-effect {
position: fixed;
background: #1aab11;
z-index: 10000000;
text-align: center;
pointer-events: none;
&:before,
&:after {
content: '';
position: absolute;
width: 2px;
height: 2px;
border: 4px solid #1aab11;
pointer-events: none;
}
&.left,
&.right {
width: 2px;
&:before,
&:after {
left: -4px;
border-left-color: transparent;
border-right-color: transparent;
}
&:before {
top: 0;
border-bottom-width: 0;
}
&:after {
bottom: 0;
border-top-width: 0;
}
}
&.top,
&.bottom,
&.in {
height: 2px;
&:before,
&:after {
top: -4px;
border-top-color: transparent;
border-bottom-color: transparent;
}
&:before {
left: 0;
border-right-width: 0;
}
&:after {
right: 0;
border-left-width: 0;
}
}
&.in {
b {
display: inline-block;
}
}
b {
display: inline-block;
position: relative;
top: -12px;
margin: 0 auto;
padding: 0 10px;
color: #fff;
background: #1aab11;
height: 16px !important;
line-height: 16px !important;
font-weight: normal;
font-size: 11px;
display: none;
}
}

View File

@ -0,0 +1,27 @@
import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';
import './index.scss';
export default class VisualDom extends PureComponent {
static displayName = 'VisualDom';
static propTypes = {
children: PropTypes.oneOfType([PropTypes.element, PropTypes.arrayOf(PropTypes.element)])
};
static defaultProps = {
children: null
};
render() {
const { children, cell, title, label, text, __componentName } = this.props;
let mainContent = children;
if (cell && typeof cell === 'function') {
mainContent = cell();
}
return (
<div className="visual-dom">
<div className="panel-container">
<span className="title">{title || label || text || __componentName}</span>
<div className="content">{mainContent}</div>
</div>
</div>
);
}
}

View File

@ -0,0 +1,19 @@
.visual-dom {
.panel-container {
box-sizing: border-box;
border: 1px solid #e9e9e9;
.title {
display: block;
font-size: 12px;
color: #333;
background-color: #ebecf0;
line-height: 28px;
padding: 0 12px;
border-bottom: 1px solid #e9e9e9;
}
.content {
min-height: 20px;
padding: 5px;
}
}
}

View File

@ -0,0 +1,3 @@
import { createContext } from 'react';
const context = (window.__appContext = createContext({}));
export default context;

View File

@ -0,0 +1,131 @@
import React from 'react';
import PropTypes from 'prop-types';
import Debug from 'debug';
import AppContext from '../context/appContext';
import classnames from 'classnames';
import BaseEngine from './base';
import { isSchema, getFileCssName, isEmpty, goldlog } from '../utils';
const debug = Debug('engine:addon');
export default class AddonEngine extends BaseEngine {
static dislayName = 'addon-engine';
static propTypes = {
config: PropTypes.object,
__schema: PropTypes.object
};
static defaultProps = {
config: {},
__schema: {}
};
static getDerivedStateFromProps(props, state) {
debug(`comp.getDerivedStateFromProps`);
const func = props.__schema.lifeCycles && props.__schema.lifeCycles.getDerivedStateFromProps;
if (func) {
return func(props, state);
}
return null;
}
constructor(props, context) {
super(props, context);
this.__generateCtx({
component: this
});
const schema = props.__schema || {};
this.state = this.__parseData(schema.state || {});
if (isEmpty(props.config) || !props.config.addonKey) {
console.warn('luna addon has wrong config');
this.state.__hasError = true;
return;
}
//
this.addonKey = props.config.addonKey;
this.appHelper.addons = this.appHelper.addons || {};
this.appHelper.addons[this.addonKey] = this;
this.__initDataSource(props);
this.open = this.open || (() => {});
this.close = this.close || (() => {});
this.__setLifeCycleMethods('constructor', arguments);
debug(`addon.constructor - ${schema.fileName}`);
}
async getSnapshotBeforeUpdate() {
super.getSnapshotBeforeUpdate(...arguments);
debug(`addon.getSnapshotBeforeUpdate - ${this.props.__schema.fileName}`);
}
async componentDidMount() {
super.componentDidMount(...arguments);
debug(`addon.componentDidMount - ${this.props.__schema.fileName}`);
}
async componentDidUpdate() {
super.componentDidUpdate(...arguments);
debug(`addon.componentDidUpdate - ${this.props.__schema.fileName}`);
}
async componentWillUnmount() {
super.componentWillUnmount(...arguments);
//
const config = this.props.config || {};
if (config && this.appHelper.addons) {
delete this.appHelper.addons[config.addonKey];
}
debug(`addon.componentWillUnmount - ${this.props.__schema.fileName}`);
}
async componentDidCatch(e) {
super.componentDidCatch(...arguments);
debug(`addon.componentDidCatch - ${this.props.__schema.fileName}`);
}
goldlog = (goKey, params) => {
const { addonKey, addonConfig = {} } = this.props.config || {};
goldlog(
goKey,
{
addonKey,
package: addonConfig.package,
version: addonConfig.version,
...this.appHelper.logParams,
...params
},
'addon'
);
};
get utils() {
const { utils = {} } = this.context.config || {};
return { ...this.appHelper.utils, ...utils };
}
render() {
const { __schema } = this.props;
if (!isSchema(__schema, true) || __schema.componentName !== 'Addon') {
return '插件schema结构异常';
}
debug(`addon.render - ${__schema.fileName}`);
this.__generateCtx({
component: this
});
this.__render();
const { id, className, style } = this.__parseData(__schema.props);
return (
<div
ref={this.__getRef}
className={classnames('luna-addon', getFileCssName(__schema.fileName), className, this.props.className)}
id={this.props.id || id}
style={{ ...style, ...this.props.style }}
>
<AppContext.Provider
value={{
...this.context,
compContext: this,
blockContext: this
}}
>
{this.__createDom()}
</AppContext.Provider>
</div>
);
}
}

View File

@ -0,0 +1,506 @@
import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';
import Debug from 'debug';
import Div from '@ali/iceluna-comp-div';
import VisualDom from '../comp/visualDom';
import AppContext from '../context/appContext';
import DataHelper from '../utils/dataHelper';
import {
forEach,
getValue,
parseData,
parseExpression,
isEmpty,
isSchema,
isFileSchema,
isJSExpression,
isJSSlot,
isJSFunction,
transformArrayToMap,
transformStringToFunction,
checkPropTypes,
generateI18n,
acceptsRef
} from '../utils';
const debug = Debug('engine:base');
const DESIGN_MODE = {
EXTEND: 'extend',
BORDER: 'border',
PREVIEW: 'preview'
};
const OVERLAY_LIST = ['Dialog', 'Overlay', 'Animate', 'ConfigProvider'];
let scopeIdx = 0;
export default class BaseEngine extends PureComponent {
static dislayName = 'base-engine';
static propTypes = {
locale: PropTypes.string,
messages: PropTypes.object,
__appHelper: PropTypes.object,
__components: PropTypes.object,
__componentsMap: PropTypes.object,
__ctx: PropTypes.object,
__schema: PropTypes.object
};
static defaultProps = {
__schema: {}
};
static contextType = AppContext;
constructor(props, context) {
super(props, context);
this.appHelper = props.__appHelper;
this.__compScopes = {};
const { locale, messages } = props;
this.i18n = generateI18n(locale, messages);
this.__bindCustomMethods(props);
}
async getSnapshotBeforeUpdate() {
this.__setLifeCycleMethods('getSnapshotBeforeUpdate', arguments);
}
async componentDidMount() {
this.reloadDataSource();
this.__setLifeCycleMethods('componentDidMount', arguments);
}
async componentDidUpdate() {
this.__setLifeCycleMethods('componentDidUpdate', arguments);
}
async componentWillUnmount() {
this.__setLifeCycleMethods('componentWillUnmount', arguments);
}
async componentDidCatch(e) {
this.__setLifeCycleMethods('componentDidCatch', arguments);
console.warn(e);
}
reloadDataSource = () => {
return new Promise((resolve, reject) => {
debug('reload data source');
if (!this.__dataHelper) {
this.__showPlaceholder = false;
return resolve();
}
this.__dataHelper
.getInitData()
.then(res => {
this.__showPlaceholder = false;
if (isEmpty(res)) {
this.forceUpdate();
return resolve();
}
this.setState(res, resolve);
})
.catch(err => {
if (this.__showPlaceholder) {
this.__showPlaceholder = false;
this.forceUpdate();
}
reject(err);
});
});
};
__setLifeCycleMethods = (method, args) => {
const lifeCycleMethods = getValue(this.props.__schema, 'lifeCycles', {});
if (lifeCycleMethods[method]) {
try {
return lifeCycleMethods[method].apply(this, args);
} catch (e) {
console.error(`[${this.props.__schema.componentName}]生命周期${method}出错`, e);
}
}
};
__bindCustomMethods = (props = this.props) => {
const { __schema } = props;
const customMethodsList = Object.keys(__schema.methods || {}) || [];
this.__customMethodsList &&
this.__customMethodsList.forEach(item => {
if (!customMethodsList.includes(item)) {
delete this[item];
}
});
this.__customMethodsList = customMethodsList;
forEach(__schema.methods, (val, key) => {
this[key] = val.bind(this);
});
};
__generateCtx = ctx => {
const { pageContext, compContext } = this.context;
const obj = {
page: pageContext,
component: compContext,
...ctx
};
forEach(obj, (val, key) => {
this[key] = val;
});
};
__parseData = (data, ctx) => {
const { __ctx } = this.props;
return parseData(data, ctx || __ctx || this);
};
__initDataSource = (props = this.props) => {
const schema = props.__schema || {};
const appHelper = props.__appHelper;
const dataSource = (schema && schema.dataSource) || {};
this.__dataHelper = new DataHelper(this, dataSource, appHelper, config => this.__parseData(config));
this.dataSourceMap = this.__dataHelper.dataSourceMap;
// loading
this.__showPlaceholder =
this.__parseData(schema.props && schema.props.autoLoading) &&
(dataSource.list || []).some(item => !!this.__parseData(item.isInit));
};
__render = () => {
const schema = this.props.__schema;
this.__setLifeCycleMethods('render');
const engine = this.context.engine;
if (engine) {
engine.props.onCompGetCtx(schema, this);
// bind
if (engine.props.designMode) {
this.__bindCustomMethods();
this.dataSourceMap = this.__dataHelper && this.__dataHelper.updateConfig(schema.dataSource);
}
}
};
__getRef = ref => {
this.__ref = ref;
};
__createDom = () => {
const { __schema, __ctx, __components = {} } = this.props;
const self = {};
self.__proto__ = __ctx || this;
return this.__createVirtualDom(__schema.children, self, {
schema: __schema,
Comp: __components[__schema.componentName]
});
};
// react Element
// schema
// self self
// parentInfo schemaComp
// idx Index
__createVirtualDom = (schema, self, parentInfo, idx) => {
if (!schema) return null;
const { __appHelper: appHelper, __components: components = {}, __componentsMap: componentsMap = {} } =
this.props || {};
const { engine } = this.context || {};
if (isJSExpression(schema)) {
return parseExpression(schema, self);
}
if (typeof schema === 'string') return schema;
if (typeof schema === 'number' || typeof schema === 'boolean') {
return schema.toString();
}
if (Array.isArray(schema)) {
if (schema.length === 1) return this.__createVirtualDom(schema[0], self, parentInfo);
return schema.map((item, idx) =>
this.__createVirtualDom(item, self, parentInfo, item && item.__ctx && item.__ctx.lunaKey ? '' : idx)
);
}
//
if (schema.componentName === 'Flagment' && schema.children) {
let tarChildren = isJSExpression(schema.children) ? parseExpression(schema.children, self) : schema.children;
return this.__createVirtualDom(tarChildren, self, parentInfo);
}
if (schema.$$typeof) {
return schema;
}
if (!isSchema(schema)) return null;
let Comp = components[schema.componentName] || Div;
if (schema.loop !== undefined) {
return this.__createLoopVirtualDom(
{
...schema,
loop: parseData(schema.loop, self)
},
self,
parentInfo,
idx
);
}
const condition = schema.condition === undefined ? true : parseData(schema.condition, self);
if (!condition) return null;
let scopeKey = '';
// scopethis.__compScopes
if (Comp.generateScope) {
const key = parseExpression(schema.props.key, self);
if (key) {
// key使key
scopeKey = key;
} else if (!schema.__ctx) {
// schema__ctxlunaKey
schema.__ctx = {
lunaKey: `luna${++scopeIdx}`
};
scopeKey = schema.__ctx.lunaKey;
} else {
//
scopeKey = schema.__ctx.lunaKey + (idx !== undefined ? `_${idx}` : '');
}
if (!this.__compScopes[scopeKey]) {
this.__compScopes[scopeKey] = Comp.generateScope(this, schema);
}
}
// scopescope
if (scopeKey && this.__compScopes[scopeKey]) {
const compSelf = { ...this.__compScopes[scopeKey] };
compSelf.__proto__ = self;
self = compSelf;
}
// propscontext
const otherProps = isFileSchema(schema)
? {
__schema: schema,
__appHelper: appHelper,
__components: components,
__componentsMap: componentsMap
}
: {};
if (engine && engine.props.designMode) {
otherProps.__designMode = engine.props.designMode;
}
const componentInfo = componentsMap[schema.componentName] || {};
const props = this.__parseProps(schema.props, self, '', {
schema,
Comp,
componentInfo: {
...componentInfo,
props: transformArrayToMap(componentInfo.props, 'name')
}
});
// ref
if (acceptsRef(Comp)) {
otherProps.ref = ref => {
const refProps = props.ref;
if (refProps && typeof refProps === 'string') {
this[refProps] = ref;
}
engine && engine.props.onCompGetRef(schema, ref);
};
}
// scope
if (scopeKey && this.__compScopes[scopeKey]) {
props.__scope = this.__compScopes[scopeKey];
}
if (schema.__ctx && schema.__ctx.lunaKey) {
if (!isFileSchema(schema)) {
engine && engine.props.onCompGetCtx(schema, self);
}
props.key = props.key || `${schema.__ctx.lunaKey}_${schema.__ctx.idx || 0}_${idx !== undefined ? idx : ''}`;
} else if (typeof idx === 'number' && !props.key) {
props.key = idx;
}
const renderComp = props => (
<Comp {...props}>
{(!isFileSchema(schema) &&
!!schema.children &&
this.__createVirtualDom(
isJSExpression(schema.children) ? parseExpression(schema.children, self) : schema.children,
self,
{
schema,
Comp
}
)) ||
null}
</Comp>
);
//
if (engine && [DESIGN_MODE.EXTEND, DESIGN_MODE.BORDER].includes(engine.props.designMode)) {
//overlay,dialog使div
if (OVERLAY_LIST.includes(schema.componentName)) {
const { ref, ...overlayProps } = otherProps;
return (
<Div ref={ref} __designMode={engine.props.designMode}>
{renderComp({ ...props, ...overlayProps })}
</Div>
);
}
// dom
if (componentInfo && componentInfo.parentRule) {
const parentList = componentInfo.parentRule.split(',');
const { schema: parentSchema, Comp: parentComp } = parentInfo;
if (!parentList.includes(parentSchema.componentName) || parentComp !== components[parentSchema.componentName]) {
props.__componentName = schema.componentName;
Comp = VisualDom;
} else {
// dom
props.__disableDesignMode = true;
}
}
}
return renderComp({ ...props, ...otherProps });
};
__createLoopVirtualDom = (schema, self, parentInfo, idx) => {
if (isFileSchema(schema)) {
console.warn('file type not support Loop');
return null;
}
if (!Array.isArray(schema.loop)) return null;
const itemArg = (schema.loopArgs && schema.loopArgs[0]) || 'item';
const indexArg = (schema.loopArgs && schema.loopArgs[1]) || 'index';
return schema.loop.map((item, i) => {
const loopSelf = {
[itemArg]: item,
[indexArg]: i
};
loopSelf.__proto__ = self;
return this.__createVirtualDom(
{
...schema,
loop: undefined
},
loopSelf,
parentInfo,
idx ? `${idx}_${i}` : i
);
});
};
__parseProps = (props, self, path, info) => {
const { schema, Comp, componentInfo = {} } = info;
const propInfo = getValue(componentInfo.props, path);
const propType = propInfo && propInfo.extra && propInfo.extra.propType;
const ignoreParse = schema.__ignoreParse || [];
const checkProps = value => {
if (!propType) return value;
return checkPropTypes(value, path, propType, componentInfo.name) ? value : undefined;
};
const parseReactNode = (data, params) => {
if (isEmpty(params)) {
return checkProps(this.__createVirtualDom(data, self, { schema, Comp }));
} else {
return checkProps(function() {
const args = {};
if (Array.isArray(params) && params.length) {
params.map((item, idx) => {
if (typeof item === 'string') {
args[item] = arguments[idx];
} else if (item && typeof item === 'object') {
args[item.name] = arguments[idx];
}
});
}
args.__proto__ = self;
return self.__createVirtualDom(data, args, { schema, Comp });
});
}
};
//
if (
ignoreParse.some(item => {
if (item instanceof RegExp) {
return item.test(path);
}
return item === path;
})
) {
return checkProps(props);
}
if (isJSExpression(props)) {
props = parseExpression(props, self);
//
if (!isSchema(props) && !isJSSlot(props)) return checkProps(props);
}
if (isJSFunction(props)) {
props = transformStringToFunction(props.value);
}
if (isJSSlot(props)) {
const { params, value } = props;
if (!isSchema(value) || isEmpty(value)) return undefined;
return parseReactNode(value, params);
}
// componentInfo
if (isSchema(props)) {
const isReactNodeFunction = !!(
propInfo &&
propInfo.type === 'ReactNode' &&
propInfo.props &&
propInfo.props.type === 'function'
);
const isMixinReactNodeFunction = !!(
propInfo &&
propInfo.type === 'Mixin' &&
propInfo.props &&
propInfo.props.types &&
propInfo.props.types.indexOf('ReactNode') > -1 &&
propInfo.props.reactNodeProps &&
propInfo.props.reactNodeProps.type === 'function'
);
return parseReactNode(
props,
isReactNodeFunction
? propInfo.props.params
: isMixinReactNodeFunction
? propInfo.props.reactNodeProps.params
: null
);
} else if (Array.isArray(props)) {
return checkProps(props.map((item, idx) => this.__parseProps(item, self, path ? `${path}.${idx}` : idx, info)));
} else if (typeof props === 'function') {
return checkProps(props.bind(self));
} else if (props && typeof props === 'object') {
if (props.$$typeof) return checkProps(props);
const res = {};
forEach(props, (val, key) => {
if (key.startsWith('__')) {
res[key] = val;
return;
}
res[key] = this.__parseProps(val, self, path ? `${path}.${key}` : key, info);
});
return checkProps(res);
} else if (typeof props === 'string') {
return checkProps(props.trim());
}
return checkProps(props);
};
get utils() {
return this.appHelper && this.appHelper.utils;
}
get constants() {
return this.appHelper && this.appHelper.constants;
}
get history() {
return this.appHelper && this.appHelper.history;
}
get location() {
return this.appHelper && this.appHelper.location;
}
get match() {
return this.appHelper && this.appHelper.match;
}
render() {
return null;
}
}

View File

@ -0,0 +1,113 @@
import React from 'react';
import PropTypes from 'prop-types';
import Debug from 'debug';
import classnames from 'classnames';
import Loading from '@alifd/next/lib/loading';
import '@alifd/next/lib/loading/style';
import BaseEngine from './base';
import AppContext from '../context/appContext';
import { isSchema, getFileCssName } from '../utils';
const debug = Debug('engine:block');
export default class BlockEngine extends BaseEngine {
static dislayName = 'block-engine';
static propTypes = {
__schema: PropTypes.object
};
static defaultProps = {
__schema: {}
};
static getDerivedStateFromProps(props, state) {
debug(`block.getDerivedStateFromProps`);
const func = props.__schema.lifeCycles && props.__schema.lifeCycles.getDerivedStateFromProps;
if (func) {
return func(props, state);
}
return null;
}
constructor(props, context) {
super(props, context);
this.__generateCtx();
const schema = props.__schema || {};
this.state = this.__parseData(schema.state || {});
this.__initDataSource(props);
this.__setLifeCycleMethods('constructor', arguments);
debug(`block.constructor - ${schema.fileName}`);
}
async getSnapshotBeforeUpdate() {
super.getSnapshotBeforeUpdate(...arguments);
debug(`block.getSnapshotBeforeUpdate - ${this.props.__schema.fileName}`);
}
async componentDidMount() {
super.componentDidMount(...arguments);
debug(`block.componentDidMount - ${this.props.__schema.fileName}`);
}
async componentDidUpdate() {
super.componentDidUpdate(...arguments);
debug(`block.componentDidUpdate - ${this.props.__schema.fileName}`);
}
async componentWillUnmount() {
super.componentWillUnmount(...arguments);
debug(`block.componentWillUnmount - ${this.props.__schema.fileName}`);
}
async componentDidCatch() {
await super.componentDidCatch(...arguments);
debug(`block.componentDidCatch - ${this.props.__schema.fileName}`);
}
render() {
const { __schema } = this.props;
if (!isSchema(__schema, true) || __schema.componentName !== 'Block') {
return '区块schema结构异常';
}
debug(`block.render - ${__schema.fileName}`);
this.__generateCtx();
this.__render();
const { id, className, style, autoLoading, defaultHeight = 300, loading } = this.__parseData(__schema.props);
const renderContent = () => (
<AppContext.Provider
value={{
...this.context,
blockContext: this
}}
>
{this.__createDom()}
</AppContext.Provider>
);
if (autoLoading || loading !== undefined) {
return (
<Loading
size="medium"
visible={!!(this.__showPlaceholder || loading)}
style={{
height: this.__showPlaceholder ? defaultHeight : 'auto',
display: 'block',
...style
}}
className={classnames('luna-block', getFileCssName(__schema.fileName), className, this.props.className)}
id={id}
>
{!this.__showPlaceholder && renderContent()}
</Loading>
);
}
return (
<div
ref={this.__getRef}
className={classnames('luna-block', getFileCssName(__schema.fileName), className, this.props.className)}
id={id}
style={style}
>
{renderContent()}
</div>
);
}
}

View File

@ -0,0 +1,122 @@
import React from 'react';
import PropTypes from 'prop-types';
import Debug from 'debug';
import classnames from 'classnames';
import Loading from '@alifd/next/lib/loading';
import '@alifd/next/lib/loading/style';
import AppContext from '../context/appContext';
import BaseEngine from './base';
import { isSchema, getFileCssName } from '../utils';
const debug = Debug('engine:comp');
export default class CompEngine extends BaseEngine {
static dislayName = 'comp-engine';
static propTypes = {
__schema: PropTypes.object
};
static defaultProps = {
__schema: {}
};
static getDerivedStateFromProps(props, state) {
debug(`comp.getDerivedStateFromProps`);
const func = props.__schema.lifeCycles && props.__schema.lifeCycles.getDerivedStateFromProps;
if (func) {
return func(props, state);
}
return null;
}
constructor(props, context) {
super(props, context);
this.__generateCtx({
component: this
});
const schema = props.__schema || {};
this.state = this.__parseData(schema.state || {});
this.__initDataSource(props);
this.__setLifeCycleMethods('constructor', arguments);
debug(`comp.constructor - ${schema.fileName}`);
}
async getSnapshotBeforeUpdate() {
super.getSnapshotBeforeUpdate(...arguments);
debug(`comp.getSnapshotBeforeUpdate - ${this.props.__schema.fileName}`);
}
async componentDidMount() {
super.componentDidMount(...arguments);
debug(`comp.componentDidMount - ${this.props.__schema.fileName}`);
}
async componentDidUpdate() {
super.componentDidUpdate(...arguments);
debug(`comp.componentDidUpdate - ${this.props.__schema.fileName}`);
}
async componentWillUnmount() {
super.componentWillUnmount(...arguments);
debug(`comp.componentWillUnmount - ${this.props.__schema.fileName}`);
}
async componentDidCatch(e) {
super.componentDidCatch(...arguments);
debug(`comp.componentDidCatch - ${this.props.__schema.fileName}`);
}
render() {
const { __schema } = this.props;
if (!isSchema(__schema, true) || __schema.componentName !== 'Component') {
return '自定义组件schema结构异常';
}
debug(`comp.render - ${__schema.fileName}`);
this.__generateCtx({
component: this
});
this.__render();
const { id, className, style, noContainer, autoLoading, defaultHeight = 300, loading } = this.__parseData(
__schema.props
);
const renderContent = () => (
<AppContext.Provider
value={{
...this.context,
compContext: this,
blockContext: this
}}
>
{this.__createDom()}
</AppContext.Provider>
);
if (noContainer) {
return renderContent();
}
if (autoLoading || loading !== undefined) {
return (
<Loading
size="medium"
visible={!!(this.__showPlaceholder || loading)}
style={{
height: this.__showPlaceholder ? defaultHeight : 'auto',
display: 'block',
...style
}}
className={classnames('luna-comp', getFileCssName(__schema.fileName), className, this.props.className)}
id={this.props.id || id}
>
{!this.__showPlaceholder && renderContent()}
</Loading>
);
}
return (
<div
ref={this.__getRef}
className={classnames('luna-comp', getFileCssName(__schema.fileName), className, this.props.className)}
id={this.props.id || id}
style={{ ...style, ...this.props.style }}
>
{renderContent()}
</div>
);
}
}

View File

@ -0,0 +1,127 @@
import React, { PureComponent } from 'react';
import ReactDOM from 'react-dom';
import PropTypes from 'prop-types';
import Debug from 'debug';
import AppContext from '../context/appContext';
import { isFileSchema, goldlog } from '../utils';
import Page from './pageEngine';
import Component from './compEngine';
import Block from './blockEngine';
import Addon from './addonEngine';
import Temp from './tempEngine';
import { isEmpty } from '@ali/b3-one/lib/obj';
window.React = React;
window.ReactDom = ReactDOM;
const debug = Debug('engine:entry');
const ENGINE_COMPS = {
Page,
Component,
Block,
Addon,
Temp
};
export default class Engine extends PureComponent {
static dislayName = 'engine';
static propTypes = {
appHelper: PropTypes.object,
components: PropTypes.object,
componentsMap: PropTypes.object,
designMode: PropTypes.string,
suspended: PropTypes.bool,
schema: PropTypes.oneOfType([PropTypes.array, PropTypes.object]),
onCompGetRef: PropTypes.func,
onCompGetCtx: PropTypes.func
};
static defaultProps = {
appHelper: null,
components: {},
componentsMap: {},
designMode: '',
suspended: false,
schema: {},
onCompGetRef: () => {},
onCompGetCtx: () => {}
};
constructor(props, context) {
super(props, context);
this.state = {};
debug(`entry.constructor - ${props.schema && props.schema.componentName}`);
}
async componentDidMount() {
goldlog(
'EXP',
{
action: 'appear',
value: !!this.props.designMode
},
'engine'
);
debug(`entry.componentDidMount - ${this.props.schema && this.props.schema.componentName}`);
}
async componentDidUpdate() {
debug(`entry.componentDidUpdate - ${this.props.schema && this.props.schema.componentName}`);
}
async componentWillUnmount() {
debug(`entry.componentWillUnmount - ${this.props.schema && this.props.schema.componentName}`);
}
async componentDidCatch(e) {
console.warn(e);
}
shouldComponentUpdate(nextProps) {
return !nextProps.suspended;
}
__getRef = ref => {
this.__ref = ref;
if (ref) {
this.props.onCompGetRef(this.props.schema, ref, true);
}
};
render() {
const { schema, designMode, appHelper, components, componentsMap } = this.props;
if (isEmpty(schema)) {
return null;
}
if (!isFileSchema(schema)) {
return '模型结构异常';
}
debug('entry.render');
const allComponents = { ...ENGINE_COMPS, ...components };
const Comp = allComponents[schema.componentName];
if (Comp) {
return (
<AppContext.Provider
value={{
appHelper,
components: allComponents,
componentsMap,
engine: this
}}
>
<Comp
key={schema.__ctx && `${schema.__ctx.lunaKey}_${schema.__ctx.idx || '0'}`}
ref={this.__getRef}
__appHelper={appHelper}
__components={allComponents}
__componentsMap={componentsMap}
__schema={schema}
__designMode={designMode}
{...this.props}
/>
</AppContext.Provider>
);
}
return null;
}
}
Engine.findDOMNode = ReactDOM.findDOMNode;

View File

@ -0,0 +1,117 @@
import React from 'react';
import PropTypes from 'prop-types';
import Debug from 'debug';
import classnames from 'classnames';
import Loading from '@alifd/next/lib/loading';
import '@alifd/next/lib/loading/style';
import AppContext from '../context/appContext';
import BaseEngine from './base';
import { isSchema, getFileCssName } from '../utils';
const debug = Debug('engine:page');
export default class PageEngine extends BaseEngine {
static dislayName = 'page-engine';
static propTypes = {
__schema: PropTypes.object
};
static defaultProps = {
__schema: {}
};
static getDerivedStateFromProps(props, state) {
debug(`page.getDerivedStateFromProps`);
const func = props.__schema.lifeCycles && props.__schema.lifeCycles.getDerivedStateFromProps;
if (func) {
return func(props, state);
}
return null;
}
constructor(props, context) {
super(props, context);
this.__generateCtx({
page: this
});
const schema = props.__schema || {};
this.state = this.__parseData(schema.state || {});
this.__initDataSource(props);
this.__setLifeCycleMethods('constructor', arguments);
debug(`page.constructor - ${schema.fileName}`);
}
async getSnapshotBeforeUpdate() {
super.getSnapshotBeforeUpdate(...arguments);
debug(`page.getSnapshotBeforeUpdate - ${this.props.__schema.fileName}`);
}
async componentDidMount() {
super.componentDidMount(...arguments);
debug(`page.componentDidMount - ${this.props.__schema.fileName}`);
}
async componentDidUpdate() {
super.componentDidUpdate(...arguments);
debug(`page.componentDidUpdate - ${this.props.__schema.fileName}`);
}
async componentWillUnmount() {
super.componentWillUnmount(...arguments);
debug(`page.componentWillUnmount - ${this.props.__schema.fileName}`);
}
async componentDidCatch() {
await super.componentDidCatch(...arguments);
debug(`page.componentDidCatch - ${this.props.__schema.fileName}`);
}
render() {
const { __schema } = this.props;
if (!isSchema(__schema, true) || __schema.componentName !== 'Page') {
return '页面schema结构异常';
}
debug(`page.render - ${__schema.fileName}`);
this.__generateCtx({
page: this
});
this.__render();
const { id, className, style, autoLoading, defaultHeight = 300, loading } = this.__parseData(__schema.props);
const renderContent = () => (
<AppContext.Provider
value={{
...this.context,
pageContext: this,
blockContext: this
}}
>
{this.__createDom()}
</AppContext.Provider>
);
if (autoLoading || loading !== undefined) {
return (
<Loading
size="medium"
visible={!!(this.__showPlaceholder || loading)}
style={{
height: this.__showPlaceholder ? defaultHeight : 'auto',
display: 'block',
...style
}}
className={classnames('luna-page', getFileCssName(__schema.fileName), className, this.props.className)}
id={id}
>
{!this.__showPlaceholder && renderContent()}
</Loading>
);
}
return (
<div
ref={this.__getRef}
className={classnames('luna-page', getFileCssName(__schema.fileName), className, this.props.className)}
id={id}
style={style}
>
{renderContent()}
</div>
);
}
}

View File

@ -0,0 +1,66 @@
import React from 'react';
import PropTypes from 'prop-types';
import Debug from 'debug';
import AppContext from '../context/appContext';
import BaseEngine from './base';
import { isSchema } from '../utils';
const debug = Debug('engine:temp');
export default class TempEngine extends BaseEngine {
static dislayName = 'temp-engine';
static propTypes = {
__ctx: PropTypes.object,
__schema: PropTypes.object
};
static defaultProps = {
__ctx: {},
__schema: {}
};
constructor(props, context) {
super(props, context);
this.state = {};
this.cacheSetState = {};
debug(`temp.constructor - ${props.__schema.fileName}`);
}
componentDidMount() {
const ctx = this.props.__ctx;
if (!ctx) return;
const setState = ctx.setState;
this.cacheSetState = setState;
ctx.setState = (...args) => {
setState.call(ctx, ...args);
setTimeout(() => this.forceUpdate(), 0);
};
debug(`temp.componentDidMount - ${this.props.__schema.fileName}`);
}
componentDidUpdate(prevProps, prevState, snapshot) {
debug(`temp.componentDidUpdate - ${this.props.__schema.fileName}`);
}
componentWillUnmount() {
const ctx = this.props.__ctx;
if (!ctx || !this.cacheSetState) return;
ctx.setState = this.cacheSetState;
delete this.cacheSetState;
debug(`temp.componentWillUnmount - ${this.props.__schema.fileName}`);
}
componentDidCatch(e) {
console.warn(e);
debug(`temp.componentDidCatch - ${this.props.__schema.fileName}`);
}
render() {
const { __schema, __ctx } = this.props;
if (!isSchema(__schema, true) || __schema.componentName !== 'Temp') {
return '下钻编辑schema结构异常';
}
debug(`temp.render - ${__schema.fileName}`);
return (
<div ref={this.__getRef} className="luna-temp">
<AppContext.Provider value={{ ...this.context, ...__ctx }}>{this.__createDom()}</AppContext.Provider>
</div>
);
}
}

View File

@ -0,0 +1,55 @@
import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';
import AddonEngine from '../engine/addonEngine';
import BlockEngine from '../engine/blockEngine';
import AppContext from '../context/appContext';
import { forEach, isFileSchema } from '../utils';
export default function addonFactory(schema, components = {}, componentsMap = {}, config = {}) {
class LNAddonView extends PureComponent {
static dislayName = 'luna-addon-factory';
static version = config.version || '0.0.0';
static contextType = AppContext;
static propTypes = {
forwardedRef: PropTypes.func
};
render() {
if (!schema || schema.componentName !== 'Addon' || !isFileSchema(schema)) {
console.warn('编辑器插件模型结构异常!');
return null;
}
const { forwardedRef, ...otherProps } = this.props;
const props = {
...schema.defaultProps,
...otherProps,
__schema: schema,
ref: forwardedRef
};
return (
<AppContext.Provider
value={{
...this.context,
appHelper: window.__ctx && window.__ctx.appHelper, // 插件上下文中的appHelper使用IDE的appHelper
components: { ...components, Addon: AddonEngine, Block: BlockEngine },
componentsMap,
config,
locale: props.locale,
messages: props.messages
}}
>
<AddonEngine
{...props}
__components={{ ...components, Addon: AddonEngine, Block: BlockEngine }}
__componentsMap={componentsMap}
__appHelper={window.__ctx && window.__ctx.appHelper}
/>
</AppContext.Provider>
);
}
}
const ResComp = React.forwardRef((props, ref) => <LNAddonView {...props} forwardedRef={ref} />);
forEach(schema.static, (val, key) => {
ResComp[key] = val;
});
ResComp.version = config.version || '0.0.0';
return ResComp;
}

View File

@ -0,0 +1,75 @@
import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';
import CompEngine from '../engine/compEngine';
import BlockEngine from '../engine/blockEngine';
import AppContext from '../context/appContext';
import AppHelper from '../utils/appHelper';
import { forEach, isFileSchema } from '../utils';
export default function compFactory(schema, components = {}, componentsMap = {}, config = {}) {
// 自定义组件需要有自己独立的appHelper
const appHelper = new AppHelper(config);
class LNCompView extends PureComponent {
static dislayName = 'luna-comp-factory';
static version = config.version || '0.0.0';
static contextType = AppContext;
static propTypes = {
forwardedRef: PropTypes.func
};
render() {
if (!schema || schema.componentName !== 'Component' || !isFileSchema(schema)) {
console.warn('自定义组件模型结构异常!');
return null;
}
const { forwardedRef, ...otherProps } = this.props;
// 低代码组件透传应用上下文
const appCtx = ['utils', 'constants'];
appCtx.forEach(key => {
if (!appHelper[key] && this.context && this.context.appHelper && this.context.appHelper[key]) {
appHelper.set(key, this.context.appHelper[key]);
}
});
const routerCtx = ['history', 'location', 'match'];
routerCtx.forEach(key => {
if (this.context && this.context.appHelper && this.context.appHelper[key]) {
appHelper.set(key, this.context.appHelper[key]);
}
});
// 支持通过context透传国际化配置
const localeProps = {};
const { locale, messages } = this.context;
if (locale && messages && messages[schema.fileName]) {
localeProps.locale = locale;
localeProps.messages = messages[schema.fileName];
}
const props = {
...schema.defaultProps,
...localeProps,
...otherProps,
__schema: schema,
ref: forwardedRef
};
return (
<AppContext.Provider
value={{
...this.context
}}
>
<CompEngine
{...props}
__appHelper={appHelper}
__components={{ ...components, Component: CompEngine, Block: BlockEngine }}
__componentsMap={componentsMap}
/>
</AppContext.Provider>
);
}
}
const ResComp = React.forwardRef((props, ref) => <LNCompView {...props} forwardedRef={ref} />);
forEach(schema.static, (val, key) => {
ResComp[key] = val;
});
ResComp.version = config.version || '0.0.0';
return ResComp;
}

View File

@ -0,0 +1,29 @@
import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';
import AppContext from '../context/appContext';
export default function localeConfig(componentName, Component) {
class LNLocaleConfigView extends PureComponent {
static dislayName = 'luna-locale-config';
static contextType = AppContext;
static propTypes = {
forwardedRef: PropTypes.func
};
render() {
const { forwardedRef, ...otherProps } = this.props;
const { locale, messages } = this.context;
const localeProps = {};
if (locale && messages && messages[componentName]) {
localeProps.locale = locale;
localeProps.messages = messages[componentName];
}
const props = {
...localeProps,
...otherProps,
ref: forwardedRef
};
return <Component {...props} />;
}
}
return React.forwardRef((props, ref) => <LNLocaleConfigView {...props} forwardedRef={ref} />);
}

View File

@ -0,0 +1,15 @@
import React, { PureComponent, Suspense } from 'react';
export default function SuspenseWrapper(fallback = null) {
return function(Component) {
class SuspenseWrapper extends PureComponent {
render() {
return (
<Suspense fallback={fallback}>
<Component {...this.props} />
</Suspense>
);
}
}
return SuspenseWrapper;
};
}

16
packages/react-renderer/src/index.js vendored Normal file
View File

@ -0,0 +1,16 @@
export { default as Canvas } from './comp/canvas';
export { default as Addon } from './comp/addon';
export { default as Engine } from './engine';
export { default as CompFactory } from './hoc/compFactory';
export { default as AddonFatory } from './hoc/addonFactory';
export { default as LocaleConfig } from './hoc/localeConfig';
export { default as AppHelper } from './utils/appHelper';
export { default as DataHelper } from './utils/dataHelper';
export { default as DndHelper } from './utils/dndHelper';
export { default as SchemaHelper } from './utils/schemaHelper';
export { default as StorageHelper } from './utils/storageHelper';
export { default as UndoRedoHelper } from './utils/undoRedoHelper';
export { default as WSHelper } from './utils/wsHelper';

View File

@ -0,0 +1,49 @@
import EventEmitter from 'events';
import Debug from 'debug';
let instance = null;
const debug = Debug('utils:appHelper');
EventEmitter.defaultMaxListeners = 100;
export default class AppHelper extends EventEmitter {
static getInstance = () => {
if (!instance) {
instance = new AppHelper();
}
return instance;
};
constructor(config) {
super();
instance = this;
Object.assign(this, config);
}
get(key) {
return this[key];
}
set(key, val) {
if (typeof key === 'string') {
this[key] = val;
} else if (typeof key === 'object') {
Object.keys(key).forEach(item => {
this[item] = key[item];
});
}
}
batchOn(events, lisenter) {
if (!Array.isArray(events)) return;
events.forEach(event => this.on(event, lisenter));
}
batchOnce(events, lisenter) {
if (!Array.isArray(events)) return;
events.forEach(event => this.once(event, lisenter));
}
batchOff(events, lisenter) {
if (!Array.isArray(events)) return;
events.forEach(event => this.off(event, lisenter));
}
}

View File

@ -0,0 +1,299 @@
import { transformArrayToMap, isJSFunction, transformStringToFunction, clone } from './index';
import { jsonp, mtop, request, get, post, bzb } from './request';
import Debug from 'debug';
const DS_STATUS = {
INIT: 'init',
LOADING: 'loading',
LOADED: 'loaded',
ERROR: 'error'
};
const debug = Debug('utils:dataHelper');
export default class DataHelper {
constructor(comp, config = {}, appHelper, parser) {
this.host = comp;
this.config = config;
this.parser = parser;
this.ajaxList = (config && config.list) || [];
this.ajaxMap = transformArrayToMap(this.ajaxList, 'id');
this.dataSourceMap = this.generateDataSourceMap();
this.appHelper = appHelper;
}
// 重置configdataSourceMap状态会被重置
resetConfig(config = {}) {
this.config = config;
this.ajaxList = (config && config.list) || [];
this.ajaxMap = transformArrayToMap(this.ajaxList, 'id');
this.dataSourceMap = this.generateDataSourceMap();
return this.dataSourceMap;
}
// 更新config只会更新配置状态保存
updateConfig(config = {}) {
this.config = config;
this.ajaxList = (config && config.list) || [];
const ajaxMap = transformArrayToMap(this.ajaxList, 'id');
// 删除已经移除的接口
Object.keys(this.ajaxMap).forEach(key => {
if (!ajaxMap[key]) {
delete this.dataSourceMap[key];
}
});
this.ajaxMap = ajaxMap;
// 添加未加入到dataSourceMap中的接口
this.ajaxList.forEach(item => {
if (!this.dataSourceMap[item.id]) {
this.dataSourceMap[item.id] = {
status: DS_STATUS.INIT,
load: (...args) => {
return this.getDataSource(item.id, ...args);
}
};
}
});
return this.dataSourceMap;
}
generateDataSourceMap() {
const res = {};
this.ajaxList.forEach(item => {
res[item.id] = {
status: DS_STATUS.INIT,
load: (...args) => {
return this.getDataSource(item.id, ...args);
}
};
});
return res;
}
updateDataSourceMap(id, data, error) {
this.dataSourceMap[id].error = error ? error : undefined;
this.dataSourceMap[id].data = data;
this.dataSourceMap[id].status = error ? DS_STATUS.ERROR : DS_STATUS.LOADED;
}
getInitData() {
const initSyncData = this.parser(this.ajaxList).filter(item => {
if (item.isInit) {
this.dataSourceMap[item.id].status = DS_STATUS.LOADING;
return true;
}
return false;
});
return this.asyncDataHandler(initSyncData).then(res => {
let dataHandler = this.config.dataHandler;
if (isJSFunction(dataHandler)) {
dataHandler = transformStringToFunction(dataHandler.value);
}
if (!dataHandler || typeof dataHandler !== 'function') return res;
try {
return dataHandler.call(this.host, res);
} catch (e) {
console.error('请求数据处理函数运行出错', e);
return;
}
});
}
getDataSource(id, params, otherOptions, callback) {
const req = this.parser(this.ajaxMap[id]);
const options = req.options || {};
if (typeof otherOptions === 'function') {
callback = otherOptions;
otherOptions = {};
}
const { headers, ...otherProps } = otherOptions || {};
if (!req) {
console.warn(`getDataSource API named ${id} not exist`);
return;
}
return this.asyncDataHandler([
{
...req,
options: {
...options,
// 支持参数为array的情况当参数为array时不做参数合并
params:
Array.isArray(options.params) || Array.isArray(params)
? params || options.params
: {
...options.params,
...params
},
headers: {
...options.headers,
...headers
},
...otherProps
}
}
])
.then(res => {
try {
callback && callback(res && res[id]);
} catch (e) {
console.error('load请求回调函数报错', e);
}
return res && res[id];
})
.catch(err => {
try {
callback && callback(null, err);
} catch (e) {
console.error('load请求回调函数报错', e);
}
return err;
});
}
asyncDataHandler(asyncDataList) {
return new Promise((resolve, reject) => {
const allReq = [];
const doserReq = [];
const doserList = [];
const beforeRequest = this.appHelper && this.appHelper.utils && this.appHelper.utils.beforeRequest;
const afterRequest = this.appHelper && this.appHelper.utils && this.appHelper.utils.afterRequest;
const csrfInput = document.getElementById('_csrf_token');
const _tb_token_ = csrfInput && csrfInput.value;
asyncDataList.map(req => {
const { id, type, options } = req;
if (!id || !type) return;
if (type === 'doServer') {
const { uri, params } = options || {};
if (!uri) return;
doserList.push(id);
doserReq.push({ name: uri, package: 'cms', params });
} else {
allReq.push(req);
}
});
if (doserReq.length > 0) {
allReq.push({
type: 'doServer',
options: {
uri: '/nrsService.do',
cors: true,
method: 'POST',
params: {
data: JSON.stringify(doserReq),
_tb_token_
}
}
});
}
if (allReq.length === 0) resolve({});
const res = {};
Promise.all(
allReq.map(item => {
return new Promise(resolve => {
const { type, id, dataHandler, options } = item;
const doFetch = (type, options) => {
this.fetchOne(type, options)
.then(data => {
if (afterRequest) {
this.appHelper.utils.afterRequest(item, data, undefined, (data, error) => {
fetchHandler(data, error);
});
} else {
fetchHandler(data, undefined);
}
})
.catch(err => {
if (afterRequest) {
// 必须要这么调用否则beforeRequest中的this会丢失
this.appHelper.utils.afterRequest(item, undefined, err, (data, error) => {
fetchHandler(data, error);
});
} else {
fetchHandler(undefined, err);
}
});
};
const fetchHandler = (data, error) => {
if (type === 'doServer') {
if (!Array.isArray(data)) {
data = [data];
}
doserList.forEach((id, idx) => {
const req = this.ajaxMap[id];
if (req) {
res[id] = this.dataHandler(id, req.dataHandler, data && data[idx], error);
this.updateDataSourceMap(id, res[id], error);
}
});
} else {
res[id] = this.dataHandler(id, dataHandler, data, error);
this.updateDataSourceMap(id, res[id], error);
}
resolve();
};
if (type === 'doServer') {
doserList.forEach(item => {
this.dataSourceMap[item].status = DS_STATUS.LOADING;
});
} else {
this.dataSourceMap[id].status = DS_STATUS.LOADING;
}
// 请求切片
if (beforeRequest) {
// 必须要这么调用否则beforeRequest中的this会丢失
this.appHelper.utils.beforeRequest(item, clone(options), options => doFetch(type, options));
} else {
doFetch(type, options);
}
});
})
)
.then(() => {
resolve(res);
})
.catch(e => {
reject(e);
});
});
}
dataHandler(id, dataHandler, data, error) {
if (isJSFunction(dataHandler)) {
dataHandler = transformStringToFunction(dataHandler.value);
}
if (!dataHandler || typeof dataHandler !== 'function') return data;
try {
return dataHandler.call(this.host, data, error);
} catch (e) {
console.error('[' + id + ']单个请求数据处理函数运行出错', e);
return;
}
}
fetchOne(type, options) {
let { uri, method = 'GET', headers, params, ...otherProps } = options;
otherProps = otherProps || {};
switch (type) {
case 'mtop':
method && (otherProps.method = method);
return mtop(uri, params, otherProps);
case 'jsonp':
return jsonp(uri, params, otherProps);
case 'bzb':
return bzb(uri, params, {
method,
headers,
...otherProps
});
default:
method = method.toUpperCase();
if (method === 'GET') {
return get(uri, params, headers, otherProps);
} else if (method === 'POST') {
return post(uri, params, headers, otherProps);
}
return request(uri, method, params, headers, otherProps);
}
}
}

View File

@ -0,0 +1,574 @@
import ReactDOM from 'react-dom';
import Debug from 'debug';
import { isFileSchema, isEmpty, throttle, deepEqual } from './index';
const DICT = {
left: '左',
right: '右',
top: '上',
bottom: '下',
in: '里'
};
const TOP_COMPONENT = ['Page', 'Component', 'Temp']; // 顶端模块,不支持放置兄弟节点
const debug = Debug('utils:dndHelper');
export default class DndHelper {
constructor(appHelper) {
this.appHelper = appHelper;
this.dragDom = null;
this.canvasEffectDom = null;
this.treeEffectDom = null;
this.containrDom = null;
this.sourceEntity = null;
this.tempEntity = null;
this.dragInfo = null;
this.canvasClearTimer = null;
this.treeClearTimer = null;
this.isDragging = false;
this.dragOverFunc = throttle(this.dragOverFunc, 50);
}
setCanvasWin(win) {
this.canvasWin = win;
if (this.canvasEffectDom) {
this.canvasWin.document.body.appendChild(this.canvasEffectDom);
}
}
emit(msg, ...args) {
this.appHelper && this.appHelper.emit(msg, ...args);
}
dragOverFunc(ev, schemaOrNode, isTree) {
if (!this.isDragging || !this.sourceEntity) return;
const entity = isTree
? this.getTreeEntity(schemaOrNode, ev)
: {
target: ev.currentTarget,
schema: schemaOrNode
};
if (this.sourceEntity.schema.__ctx && this.sourceEntity.schema.__ctx.lunaKey === entity.schema.__ctx.lunaKey)
return;
let dragInfo = null;
if (isTree) {
dragInfo = this.getTreeDragInfo(ev, entity);
} else {
dragInfo = this.getDragInfo(ev, entity);
}
if (!dragInfo || deepEqual(this.dragInfo, dragInfo)) return;
this.dragInfo = dragInfo;
this.tempEntity = dragInfo.entity;
this.clearEffect(isTree);
this.addEffect(isTree);
}
changeCanvas() {
debug('change canvas', this.sourceEntity, this.tempEntity);
if (!this.sourceEntity || !this.tempEntity) return;
if (this.sourceEntity.isAdd) {
debug('add material', this.sourceEntity.schema, this.tempEntity.schema.__ctx.lunaKey, this.dragInfo.position);
this.emit('material.add', {
schema: this.sourceEntity.schema,
targetKey: this.tempEntity.schema.__ctx.lunaKey,
direction: this.dragInfo.position
});
} else {
this.emit('material.move', {
lunaKey: this.sourceEntity.schema.__ctx.lunaKey,
targetKey: this.tempEntity.schema.__ctx.lunaKey,
direction: this.dragInfo.position
});
}
}
getTreeEntity(node, ev) {
if (!node) return;
const schemaHelper = this.appHelper.schemaHelper;
const lunaKey = node.props.eventKey;
const schema = schemaHelper.schemaMap[lunaKey];
if (!schema) return;
const ref = schemaHelper.compThisMap[lunaKey];
const currentTarget = ev.currentTarget;
return {
schema,
target: ref && ReactDOM.findDOMNode(ref),
treeNodeTarget: currentTarget
};
}
getDragTagDom(tagName) {
if (!this.dragDom) {
const dragDom = document.createElement('div');
dragDom.id = 'luna-drag-dom';
dragDom.style.height = '24px';
dragDom.style.position = 'absolute';
dragDom.style.zIndex = 10000000;
dragDom.style.transform = 'translateY(-10000px)';
dragDom.style.background = 'rgba(0, 0, 0, .5)';
dragDom.style.lineHeight = '24px';
dragDom.style.color = '#fff';
dragDom.style.padding = '0px 10px';
dragDom.style.display = 'inline-block';
document.body.appendChild(dragDom);
this.dragDom = dragDom;
}
this.dragDom.innerHTML = `<i class="next-icon next-icon-zujianku next-small"></i> ${tagName}`;
return this.dragDom;
}
getCanvasEffectDom() {
if (!this.canvasWin) {
throw new Error('should set the canvasWin first');
}
if (this.canvasClearTimer) {
clearTimeout(this.canvasClearTimer);
this.canvasClearTimer = null;
}
const { position } = this.dragInfo;
let canvasEffectDom = this.canvasEffectDom;
if (!canvasEffectDom) {
canvasEffectDom = document.createElement('div');
this.canvasWin.document.body.appendChild(canvasEffectDom);
this.canvasEffectDom = canvasEffectDom;
}
canvasEffectDom.id = 'luna-canvas-effect';
canvasEffectDom.innerHTML = `<b>${DICT[position]}</b>`;
canvasEffectDom.className = position;
canvasEffectDom.style.display = 'block';
return canvasEffectDom;
}
getTreeEffectDom() {
if (this.treeClearTimer) {
clearTimeout(this.treeClearTimer);
this.treeClearTimer = null;
}
let treeEffectDom = this.treeEffectDom;
if (!treeEffectDom) {
treeEffectDom = document.createElement('div');
this.treeEffectDom = treeEffectDom;
}
treeEffectDom.id = 'luna-tree-effect';
treeEffectDom.style.display = 'block';
return treeEffectDom;
}
getLunaContainerDom(target) {
if (!target) return null;
let parent = target.parentNode;
while (parent && (!parent.dataset || !parent.dataset.lunaKey)) {
parent = parent.parentNode;
}
return parent;
}
clearCompTreeEffect() {
const container = document.querySelector('.luna-comp-tree');
if (!container) return;
let treeItems = container.querySelectorAll('.tree-item');
(treeItems || []).forEach(item => {
const classList = item.classList;
if (classList) {
classList.remove('top');
classList.remove('in');
classList.remove('bottom');
classList.remove('tree-item');
}
});
}
getDragInfo(ev, entity) {
if (!this.sourceEntity || !entity) return null;
const { target, schema } = entity;
const sourcePath = this.sourceEntity.schema.__ctx && this.sourceEntity.schema.__ctx.lunaPath;
const targetPath = schema.__ctx.lunaPath;
const sourceTarget = this.sourceEntity.target;
if (sourcePath === targetPath) return null;
if (targetPath && targetPath.startsWith(sourcePath)) return null;
const componentsMap = this.appHelper.get('componentsMap');
// if (!componentsMap || !componentsMap[schema.componentName]) return null;
let isContainer =
(componentsMap[schema.componentName] && componentsMap[schema.componentName].isContainer) || isFileSchema(schema); //是否是容器组件
if (schema.children && typeof schema.children !== 'object') {
//如果children是文本, 非模型结构,则非容器;
isContainer = false;
}
const rect = target.getBoundingClientRect();
const isSupportIn =
isContainer &&
(!schema.children || (schema.children && typeof schema.children === 'object' && isEmpty(schema.children)));
const sourceIsInline = sourceTarget && ['inline-block', 'inline'].includes(getComputedStyle(sourceTarget).display);
const isInline = ['inline-block', 'inline'].includes(getComputedStyle(target).display) && sourceIsInline;
const measure = isInline ? 'width' : 'height';
let sn = 0;
let position = 'top';
if (isContainer) {
sn = isSupportIn ? rect[measure] * 0.25 : Math.min(rect[measure] * 0.5, 10);
} else {
sn = rect[measure] * 0.5;
}
if (TOP_COMPONENT.includes(schema.componentName)) {
// 顶端组件拖拽over时只能放在其内部
position = 'in';
} else if (isInline && !isContainer) {
if (Math.abs(ev.clientX - rect.left) <= sn) {
position = 'left';
} else if (Math.abs(ev.clientX - rect.right) <= sn) {
position = 'right';
}
} else {
if (Math.abs(ev.clientY - rect.top) <= sn) {
position = 'top';
} else if (Math.abs(ev.clientY - rect.bottom) <= sn) {
position = 'bottom';
} else {
position = 'in';
}
}
// 判断是否是相邻元素, 往左|上拖
const isPrevSibling = sourceTarget === target.nextElementSibling;
if (isPrevSibling) {
if (position === 'right') position = 'left';
if (position === 'bottom') {
position = isContainer ? 'in' : 'top';
}
}
// 判断是否相邻元素,往右|下拖
const isPostSibling = sourceTarget === target.previousElementSibling;
if (isPostSibling) {
if (position === 'left') position = 'right';
if (position === 'top') {
position = isContainer ? 'in' : 'bottom';
}
}
//如果是容器组件且包含有子组件且是in状态进行智能识别处理
let subChildren = [];
const getChildren = node => {
if (!node || !node.childNodes || node.childNodes.length === 0) return;
node.childNodes.forEach(child => {
if (child === sourceTarget) return;
if (child && child.getAttribute && child.getAttribute('draggable')) {
const isInline = ['inline', 'inline-block'].includes(getComputedStyle(child).display) && sourceIsInline;
const rect = child.getBoundingClientRect();
const l = Math.abs(ev.clientX - rect.left);
const r = Math.abs(ev.clientX - rect.right);
const t = Math.abs(ev.clientY - rect.top);
const b = Math.abs(ev.clientY - rect.bottom);
const minXDistance = Math.min(l, r);
const minYDistance = Math.min(t, b);
subChildren.push({
lunaKey: child.dataset.lunaKey,
node: child,
minDistance: isInline ? [minXDistance, minYDistance] : [minYDistance, minXDistance],
position: isInline ? (l > r ? 'right' : 'left') : b > t ? 'top' : 'bottom'
});
} else {
getChildren(child);
}
});
};
if (position === 'in' && isContainer && !isSupportIn) {
getChildren(target);
subChildren = subChildren.sort((a, b) => {
if (a.minDistance[0] === b.minDistance[0]) {
return a.minDistance[1] - b.minDistance[1];
}
return a.minDistance[0] - b.minDistance[0];
});
const tempChild = subChildren[0];
if (tempChild) {
if (sourceTarget === tempChild.node.nextElementSibling && ['bottom', 'right'].includes(tempChild.position))
return null;
if (sourceTarget === tempChild.node.previousElementSibling && ['top', 'left'].includes(tempChild.position))
return null;
position = tempChild.position;
entity = {
target: tempChild.node,
schema: this.appHelper.schemaHelper.schemaMap[tempChild.lunaKey]
};
}
}
const containrDom = position === 'in' ? entity.target : this.getLunaContainerDom(entity.target);
if (this.containrDom !== containrDom) {
if (this.containrDom) {
this.containrDom.style.outline = '';
}
this.containrDom = containrDom;
}
if (this.containrDom) {
containrDom.style.outline = '1px solid #1aab11';
}
// debug('drag info:', position, isSupportIn, isContainer, entity);
return {
position,
isSupportIn,
isContainer,
entity
};
}
getTreeDragInfo(ev, entity) {
if (!this.sourceEntity || !entity) return null;
const { schema, treeNodeTarget } = entity;
const sourcePath = this.sourceEntity.schema.__ctx && this.sourceEntity.schema.__ctx.lunaPath;
const targetPath = schema.__ctx.lunaPath;
if (sourcePath === targetPath) return null;
if (targetPath && targetPath.startsWith(sourcePath)) return null;
const componentsMap = this.appHelper.get('componentsMap');
// if (!componentsMap || !componentsMap[schema.componentName]) return null;
let isContainer =
(componentsMap[schema.componentName] && componentsMap[schema.componentName].isContainer) || isFileSchema(schema); //是否是容器组件
if (schema.children && typeof schema.children !== 'object') {
//如果children是文本, 非模型结构,则非容器;
isContainer = false;
}
const rect = treeNodeTarget.getBoundingClientRect();
const isSupportIn =
isContainer &&
(!schema.children || (schema.children && typeof schema.children === 'object' && isEmpty(schema.children)));
const sn = isContainer && isSupportIn ? rect.height * 0.25 : rect.height * 0.5;
let position = 'in';
if (Math.abs(ev.clientY - rect.top) <= sn) {
position = 'top';
} else if (Math.abs(ev.clientY - rect.bottom) <= sn) {
position = 'bottom';
}
return {
position,
isSupportIn,
isContainer,
entity
};
}
addEffect(isTree) {
if (!this.tempEntity) return;
const { position } = this.dragInfo;
const { target, treeNodeTarget } = this.tempEntity;
// this.clearCompTreeEffect();
if (isTree) {
//画父元素外框
let status = true;
let node = treeNodeTarget.parentNode;
while (status) {
if (node && node.parentNode) {
if (node.parentNode.tagName == 'LI' && node.parentNode.classList.contains('next-tree-node')) {
status = false;
if (this.treeNodeTargetParent !== node.parentNode || position === 'in') {
this.treeNodeTargetParent && this.treeNodeTargetParent.classList.remove('selected');
}
this.treeNodeTargetParent = node.parentNode;
if (position !== 'in') this.treeNodeTargetParent.classList.add('selected');
} else {
node = node.parentNode;
}
} else {
status = false;
}
}
treeNodeTarget.appendChild(this.getTreeEffectDom());
this.treeEffectDom.className = position;
} else {
const effectDom = this.getCanvasEffectDom();
const rect = target.getBoundingClientRect();
effectDom.style.left = (position === 'right' ? rect.right : rect.left) + 'px';
effectDom.style.top =
(position === 'bottom' ? rect.bottom : position === 'in' ? (rect.top + rect.bottom) / 2 : rect.top) + 'px';
effectDom.style.height = ['top', 'in', 'bottom'].includes(position) ? '2px' : rect.height + 'px';
effectDom.style.width = ['left', 'right'].includes(position) ? '2px' : rect.width + 'px';
}
}
clearCanvasEffect() {
if (this.canvasEffectDom) {
this.canvasEffectDom.style.display = 'none';
}
if (this.containrDom) {
this.containrDom.style.outline = '';
}
}
clearTreeEffect() {
if (this.treeEffectDom) {
this.treeEffectDom.style.display = 'none';
}
if (this.treeNodeTargetParent) {
this.treeNodeTargetParent.classList.remove('selected');
}
const tempTarget = this.tempEntity && this.tempEntity.treeNodeTarget;
const classList = tempTarget && tempTarget.classList;
if (classList) {
classList.remove('top');
classList.remove('bottom');
classList.remove('in');
classList.remove('tree-item');
}
}
clearEffect(isTree) {
if (this.isDragging) {
// if (isTree) {
if (this.treeClearTimer) {
clearTimeout(this.treeClearTimer);
this.treeClearTimer = null;
}
this.treeClearTimer = setTimeout(() => {
this.clearTreeEffect();
}, 300);
// } else {
if (this.canvasClearTimer) {
clearTimeout(this.canvasClearTimer);
this.canvasClearTimer = null;
}
this.canvasClearTimer = setTimeout(() => {
this.clearCanvasEffect();
}, 300);
// }
} else {
// if (isTree) {
this.clearTreeEffect();
// } else {
this.clearCanvasEffect();
// }
}
}
handleDragStart(ev, lunaKey) {
ev.stopPropagation();
const target = ev.currentTarget;
target.style.filter = 'blur(2px)';
const schema = this.appHelper.schemaHelper.schemaMap[lunaKey];
ev.dataTransfer.setDragImage(this.getDragTagDom(schema.componentName), 0, 0);
this.sourceEntity = {
target,
schema
};
this.isDragging = true;
}
handleDragEnd(ev) {
ev.stopPropagation();
ev.preventDefault();
this.isDragging = false;
if (!this.sourceEntity) return;
if (this.sourceEntity.target) {
this.sourceEntity.target.style.filter = '';
}
this.clearEffect();
}
handleDragOver(ev, lunaKey) {
ev.preventDefault();
ev.stopPropagation();
this.isDragging = true;
const schema = this.appHelper.schemaHelper.schemaMap[lunaKey];
this.dragOverFunc(
{
clientX: ev.clientX,
clientY: ev.clientY,
currentTarget: ev.currentTarget
},
schema
);
}
handleDragLeave(ev) {
//避免移动到treeEffectDom上的抖动
ev.stopPropagation();
if (!this.tempEntity) return;
const rect = ev.target.getBoundingClientRect();
// 如果鼠标位置还在当前元素范围内则不认为是dragLeave
if (ev.x >= rect.left && ev.x <= rect.right && ev.y >= rect.top && ev.y <= rect.bottom) return;
debug('canvas drag leave', ev);
this.clearEffect();
this.dragInfo = null;
this.isDragging = false;
}
handleDrop(ev) {
ev.stopPropagation();
debug('drop+++++');
this.isDragging = false;
this.changeCanvas();
this.clearEffect();
}
handleTreeDragStart(ev) {
const { event, node } = ev;
event.stopPropagation();
const lunaKey = node.props.eventKey;
const schema = this.appHelper.schemaHelper.schemaMap[lunaKey];
if (!schema) return;
event.dataTransfer.setDragImage(this.getDragTagDom(schema.componentName), 0, 0);
this.sourceEntity = this.getTreeEntity(node, event);
if (this.sourceEntity.target) {
this.sourceEntity.target.style.filter = 'blur(2px)';
}
this.isDragging = true;
}
handleTreeDragEnd(ev) {
const { event } = ev;
event.stopPropagation();
event.preventDefault();
this.isDragging = false;
if (!this.sourceEntity) return;
if (this.sourceEntity.target) {
this.sourceEntity.target.style.filter = '';
}
this.clearEffect(true);
}
handleTreeDragOver(ev) {
const { event, node } = ev;
event.preventDefault();
event.stopPropagation();
this.isDragging = true;
this.dragOverFunc(
{
clientX: event.clientX,
clientY: event.clientY,
currentTarget: event.currentTarget.children[0]
},
node,
true
);
}
handleTreeDragLeave(ev) {
const { event } = ev;
event.stopPropagation();
if (!this.tempEntity) return;
//避免移动到treeEffectDom上的抖动
if (this.treeEffectDom && this.treeEffectDom.parentNode.parentNode === event.currentTarget) return;
debug('++++ drag leave tree', ev, this.isDragging);
this.clearEffect(true);
this.isDragging = false;
}
handleTreeDrop(ev) {
const { event } = ev;
event.stopPropagation();
this.isDragging = false;
this.changeCanvas();
this.clearEffect(true);
}
handleResourceDragStart(ev, title, schema) {
ev.stopPropagation();
ev.dataTransfer.setDragImage(this.getDragTagDom(title), -2, -2);
this.sourceEntity = {
isAdd: true,
schema
};
this.isDragging = true;
}
}

View File

@ -0,0 +1,618 @@
import Debug from 'debug';
import _keymaster from 'keymaster';
export const keymaster = _keymaster;
import { forEach as _forEach, shallowEqual as _shallowEqual } from '@ali/b3-one/lib/obj';
import { serialize as serializeParams } from '@ali/b3-one/lib/url';
export const forEach = _forEach;
export const shallowEqual = _shallowEqual;
//moment对象配置
import _moment from 'moment';
import 'moment/locale/zh-cn';
export const moment = _moment;
moment.locale('zh-cn');
import pkg from '../../package.json';
window.sdkVersion = pkg.version;
import _pick from 'lodash/pick';
import _deepEqual from 'lodash/isEqualWith';
import _clone from 'lodash/cloneDeep';
import _isEmpty from 'lodash/isEmpty';
import _throttle from 'lodash/throttle';
import _debounce from 'lodash/debounce';
export const pick = _pick;
export const deepEqual = _deepEqual;
export const clone = _clone;
export const isEmpty = _isEmpty;
export const throttle = _throttle;
export const debounce = _debounce;
import _serialize from 'serialize-javascript';
export const serialize = _serialize;
import * as _jsonuri from 'jsonuri';
export const jsonuri = _jsonuri;
export { get, post, jsonp, mtop, request } from './request';
import IntlMessageFormat from 'intl-messageformat';
const ReactIs = require('react-is');
const ReactPropTypesSecret = require('prop-types/lib/ReactPropTypesSecret');
const factoryWithTypeCheckers = require('prop-types/factoryWithTypeCheckers');
const PropTypes2 = factoryWithTypeCheckers(ReactIs.isElement, true);
const EXPRESSION_TYPE = {
JSEXPRESSION: 'JSExpression',
JSFUNCTION: 'JSFunction',
JSSLOT: 'JSSlot'
};
const EXPRESSION_REG = /^\{\{(\{.*\}|.*?)\}\}$/;
const hasSymbol = typeof Symbol === 'function' && Symbol['for'];
const REACT_FORWARD_REF_TYPE = hasSymbol ? Symbol['for']('react.forward_ref') : 0xead0;
const debug = Debug('utils:index');
const ENV = {
TBE: 'TBE',
WEBIDE: 'WEB-IDE',
VSCODE: 'VSCODE',
WEB: 'WEB'
};
/**
* @name isSchema
* @description 判断是否是模型结构
*/
export function isSchema(schema, ignoreArr) {
if (isEmpty(schema)) return false;
if (!ignoreArr && Array.isArray(schema)) return schema.every(item => isSchema(item));
return !!(schema.componentName && schema.props && (typeof schema.props === 'object' || isJSExpression(schema.props)));
}
export function isFileSchema(schema) {
if (isEmpty(schema)) return false;
return ['Page', 'Block', 'Component', 'Addon', 'Temp'].includes(schema.componentName);
}
// 判断当前页面是否被嵌入到同域的页面中
export function inSameDomain() {
try {
return window.parent !== window && window.parent.location.host === window.location.host;
} catch (e) {
return false;
}
}
export function getFileCssName(fileName) {
if (!fileName) return;
let name = fileName.replace(/([A-Z])/g, '-$1').toLowerCase();
return ('luna-' + name)
.split('-')
.filter(p => !!p)
.join('-');
}
export function isJSSlot(obj) {
return obj && typeof obj === 'object' && EXPRESSION_TYPE.JSSLOT === obj.type;
}
export function isJSFunction(obj) {
return obj && typeof obj === 'object' && EXPRESSION_TYPE.JSFUNCTION === obj.type;
}
export function isJSExpression(obj) {
//兼容两种写法有js构造表达式的情况
const isJSExpressionObj =
obj && typeof obj === 'object' && EXPRESSION_TYPE.JSEXPRESSION === obj.type && typeof obj.value === 'string';
const isJSExpressionStr = typeof obj === 'string' && EXPRESSION_REG.test(obj.trim());
return isJSExpressionObj || isJSExpressionStr;
}
/**
* @name wait
* @description 等待函数
*/
export function wait(ms) {
return new Promise(resolve => setTimeout(() => resolve(true), ms));
}
export function curry(Comp, hocs = []) {
return hocs.reverse().reduce((pre, cur) => {
return cur(pre);
}, Comp);
}
export function getValue(obj, path, defaultValue) {
if (isEmpty(obj) || typeof obj !== 'object') return defaultValue;
const res = path.split('.').reduce((pre, cur) => {
return pre && pre[cur];
}, obj);
if (res === undefined) return defaultValue;
return res;
}
export function parseObj(schemaStr) {
if (typeof schemaStr !== 'string') return schemaStr;
//默认调用顶层窗口的parseObj,保障new Function的window对象是顶层的window对象
try {
if (inSameDomain() && window.parent.__newFunc) {
return window.parent.__newFunc(`"use strict"; return ${schemaStr}`)();
}
return new Function(`"use strict"; return ${schemaStr}`)();
} catch (err) {
return undefined;
}
}
export function fastClone(obj) {
return parseObj(serialize(obj, { unsafe: true }));
}
// 更新obj的内容但不改变obj的指针
export function fillObj(receiver = {}, ...suppliers) {
Object.keys(receiver).forEach(item => {
delete receiver[item];
});
Object.assign(receiver, ...suppliers);
return receiver;
}
// 中划线转驼峰
export function toHump(name) {
return name.replace(/\-(\w)/g, function(all, letter) {
return letter.toUpperCase();
});
}
// 驼峰转中划线
export function toLine(name) {
return name.replace(/([A-Z])/g, '-$1').toLowerCase();
}
// 获取当前环境
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;
}
/**
* 用于构造国际化字符串处理函数
* @param {*} locale 国际化标识例如 zh-CNen-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);
};
}
/**
* 判断当前组件是否能够设置ref
* @param {*} Comp 需要判断的组件
*/
export function acceptsRef(Comp) {
return (
(Comp.$$typeof && Comp.$$typeof === REACT_FORWARD_REF_TYPE) || (Comp.prototype && Comp.prototype.isReactComponent)
);
}
/**
* 黄金令箭埋点
* @param {String} gmKey 为黄金令箭业务类型
* @param {Object} params 参数
* @param {String} logKey 属性串
*/
export function goldlog(gmKey, params = {}, logKey = 'other') {
// vscode 黄金令箭API
const sendIDEMessage = window.sendIDEMessage || (inSameDomain() && 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');
}
// utils为编辑器打包生成的utils文件内容utilsConfig为数据库存放的utils配置
export function generateUtils(utils, utilsConfig) {
if (!Array.isArray(utilsConfig)) return { ...utils };
const res = {};
utilsConfig.forEach(item => {
if (!item.name || !item.type || !item.content) return;
if (item.type === 'function' && typeof item.content === 'function') {
res[item.name] = item.content;
} else if (item.type === 'npm' && utils[item.name]) {
res[item.name] = utils[item.name];
}
});
return res;
}
// 复制到粘贴板
export function setClipboardData(str) {
return new Promise((resolve, reject) => {
if (typeof str !== 'string') reject('不支持拷贝');
if (navigator.clipboard) {
navigator.clipboard
.writeText(str)
.then(() => {
resolve();
})
.catch(err => {
reject('复制失败,请重试!', err);
});
} else {
const textArea = document.createElement('textarea');
textArea.value = str;
document.body.appendChild(textArea);
textArea.focus();
textArea.select();
try {
let successful = document.execCommand('copy');
if (successful) {
document.body.removeChild(textArea);
resolve();
}
} catch (err) {
document.body.removeChild(textArea);
reject('复制失败,请重试!', err);
}
}
});
}
// 获取粘贴板数据
export function getClipboardData() {
return new Promise((resolve, reject) => {
if (window.clipboardData) {
resolve(window.clipboardData.getData('text'));
} else if (navigator.clipboard) {
return navigator.clipboard
.readText()
.then(res => {
resolve(res);
})
.catch(err => {
reject('粘贴板获取失败', err);
});
} else {
reject('粘贴板获取失败');
}
});
}
// 将函数返回结果转成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 moveArrayItem(arr, sourceIdx, distIdx, direction) {
if (
!Array.isArray(arr) ||
sourceIdx === distIdx ||
sourceIdx < 0 ||
sourceIdx >= arr.length ||
distIdx < 0 ||
distIdx >= arr.length
)
return arr;
const item = arr[sourceIdx];
if (direction === 'after') {
arr.splice(distIdx + 1, 0, item);
} else {
arr.splice(distIdx, 0, item);
}
if (sourceIdx < distIdx) {
arr.splice(sourceIdx, 1);
} else {
arr.splice(sourceIdx + 1, 1);
}
return arr;
}
export function transformArrayToMap(arr, key, overwrite = true) {
if (isEmpty(arr) || !Array.isArray(arr)) return {};
const res = {};
arr.forEach(item => {
const curKey = item[key];
if (item[key] === undefined) return;
if (res[curKey] && !overwrite) return;
res[curKey] = item;
});
return res;
}
export function checkPropTypes(value, name, rule, componentName) {
if (typeof rule === 'string') {
rule = new Function(`"use strict"; const PropTypes = arguments[0]; return ${rule}`)(PropTypes2);
}
if (!rule || typeof rule !== 'function') {
console.warn('checkPropTypes should have a function type rule argument');
return true;
}
const err = rule(
{
[name]: value
},
name,
componentName,
'prop',
null,
ReactPropTypesSecret
);
if (err) {
console.warn(err);
}
return !err;
}
export function transformSchemaToPure(obj) {
const pureObj = obj => {
if (Array.isArray(obj)) {
return obj.map(item => pureObj(item));
} else if (typeof obj === 'object') {
// 对于undefined及null直接返回
if (!obj) return obj;
const res = {};
forEach(obj, (val, key) => {
if (key.startsWith('__') && key !== '__ignoreParse') return;
res[key] = pureObj(val);
});
return res;
}
return obj;
};
return pureObj(obj);
}
export function transformSchemaToStandard(obj) {
const standardObj = obj => {
if (Array.isArray(obj)) {
return obj.map(item => standardObj(item));
} else if (typeof obj === 'object') {
// 对于undefined及null直接返回
if (!obj) return obj;
const res = {};
forEach(obj, (val, key) => {
if (key.startsWith('__') && key !== '__ignoreParse') return;
if (isSchema(val) && key !== 'children' && obj.type !== 'JSSlot') {
res[key] = {
type: 'JSSlot',
value: standardObj(val)
};
// table特殊处理
if (key === 'cell') {
res[key].params = ['value', 'index', 'record'];
}
} else {
res[key] = standardObj(val);
}
});
return res;
} else if (typeof obj === 'function') {
return {
type: 'JSFunction',
value: obj.toString()
};
} else if (typeof obj === 'string' && EXPRESSION_REG.test(obj.trim())) {
const regRes = obj.trim().match(EXPRESSION_REG);
return {
type: 'JSExpression',
value: (regRes && regRes[1]) || ''
};
}
return obj;
};
return standardObj(obj, false);
}
export function transformStringToFunction(str) {
if (typeof str !== 'string') return str;
if (inSameDomain() && window.parent.__newFunc) {
return window.parent.__newFunc(`"use strict"; return ${str}`)();
} else {
return new Function(`"use strict"; return ${str}`)();
}
}
export function addCssTag(id, content) {
let styleTag = document.getElementById(id);
if (styleTag) {
styleTag.innerHTML = content;
return;
}
styleTag = document.createElement('style');
styleTag.id = id;
styleTag.class = 'luna-style';
styleTag.innerHTML = content;
document.head.appendChild(styleTag);
}
// 注册快捷
export function registShortCuts(config, appHelper) {
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;
}
}
export function parseData(schema, self) {
if (isJSExpression(schema)) {
return parseExpression(schema, self);
} else if (typeof schema === 'string') {
return schema.trim();
} else if (Array.isArray(schema)) {
return schema.map(item => parseData(item, self));
} else if (typeof schema === 'function') {
return schema.bind(self);
} else if (typeof schema === 'object') {
// 对于undefined及null直接返回
if (!schema) return schema;
const res = {};
forEach(schema, (val, key) => {
if (key.startsWith('__')) return;
res[key] = parseData(val, self);
});
return res;
}
return schema;
}
/*全匹配{{开头,}}结尾的变量表达式或者对象类型JSExpression且均不支持省略this */
export function parseExpression(str, self) {
try {
const contextArr = ['"use strict";', 'var __self = arguments[0];'];
contextArr.push('return ');
let tarStr;
//向前兼容,支持标准协议新格式
if (typeof str === 'string') {
const regRes = str.trim().match(EXPRESSION_REG);
tarStr = regRes[1];
} else {
tarStr = (str.value || '').trim();
}
tarStr = tarStr.replace(/this(\W|$)/g, (a, b) => `__self${b}`);
tarStr = contextArr.join('\n') + tarStr;
//默认调用顶层窗口的parseObj,保障new Function的window对象是顶层的window对象
if (inSameDomain() && window.parent.__newFunc) {
return window.parent.__newFunc(tarStr)(self);
}
return new Function(tarStr)(self);
} catch (err) {
debug('parseExpression.error', err, str, self);
return undefined;
}
}

View File

@ -0,0 +1,59 @@
import EventEmitter from 'events';
import Debug from 'debug';
const debug = Debug('utils:postMessager');
EventEmitter.defaultMaxListeners = 100;
export class InnerMessager extends EventEmitter {
constructor() {
super();
this.handleReceive = this.handleReceive.bind(this);
window.addEventListener('message', this.handleReceive, false);
}
sendMsg(type, data, targetOrigin = '*') {
window.parent &&
window.parent.postMessage(
{
type,
data
},
targetOrigin
);
}
handleReceive(e) {
if (!e.data || !e.data.type) return;
this.emit(e.data.type, e.data.data);
}
destroy() {
window.removeEventListener('message', this.handleReceive);
}
}
export class OuterMessager extends EventEmitter {
constructor(innerWindow) {
super();
this.innerWindow = innerWindow;
this.handleReceive = this.handleReceive.bind(this);
window.addEventListener('message', this.handleReceive, false);
}
sendMsg(type, data, targetOrigin = '*') {
this.innerWindow &&
this.innerWindow.postMessage(
{
type,
data
},
targetOrigin
);
}
handleReceive(e) {
if (!e.data || !e.data.type) return;
this.emit(e.data.type, e.data.data);
}
destroy() {
window.removeEventListener('message', this.handleReceive);
}
}

View File

@ -0,0 +1,172 @@
import 'whatwg-fetch';
import fetchMtop from '@ali/lib-mtop';
import fetchJsonp from 'fetch-jsonp';
import bzbRequest from '@ali/bzb-request';
import Debug from 'debug';
import { serialize, buildUrl, parseUrl } from '@ali/b3-one/lib/url';
const debug = Debug('utils:request');
export function get(dataAPI, params = {}, headers = {}, otherProps = {}) {
headers = {
Accept: 'application/json',
...headers
};
dataAPI = buildUrl(dataAPI, params);
return request(dataAPI, 'GET', null, headers, otherProps);
}
export function post(dataAPI, params = {}, headers = {}, otherProps = {}) {
headers = {
Accept: 'application/json',
'Content-Type': 'application/x-www-form-urlencoded',
...headers
};
return request(
dataAPI,
'POST',
headers['Content-Type'].indexOf('application/json') > -1 || Array.isArray(params)
? JSON.stringify(params)
: serialize(params),
headers,
otherProps
);
}
export function request(dataAPI, method = 'GET', data, headers = {}, otherProps = {}) {
switch (method) {
case 'PUT':
case 'DELETE':
headers = {
Accept: 'application/json',
'Content-Type': 'application/json',
...headers
};
data = JSON.stringify(data || {});
break;
}
return new Promise((resolve, reject) => {
if (otherProps.timeout) {
setTimeout(() => {
reject(new Error('timeout'));
}, otherProps.timeout);
}
fetch(dataAPI, {
method,
credentials: 'include',
headers,
body: data,
...otherProps
})
.then(response => {
switch (response.status) {
case 200:
case 201:
case 202:
return response.json();
case 204:
if (method === 'DELETE') {
return {
success: true
};
} else {
return {
__success: false,
code: response.status
};
}
case 400:
case 401:
case 403:
case 404:
case 406:
case 410:
case 422:
case 500:
return response
.json()
.then(res => {
return {
__success: false,
code: response.status,
data: res
};
})
.catch(() => {
return {
__success: false,
code: response.status
};
});
}
return null;
})
.then(json => {
if (json && json.__success !== false) {
resolve(json);
} else {
delete json.__success;
reject(json);
}
})
.catch(err => {
reject(err);
});
});
}
export function jsonp(dataAPI, params = {}, otherProps = {}) {
return new Promise((resolve, reject) => {
otherProps = {
timeout: 5000,
...otherProps
};
fetchJsonp(buildUrl(dataAPI, params), otherProps)
.then(response => response.json())
.then(json => {
if (json) {
resolve(json);
} else {
reject();
}
})
.catch(err => {
reject(err);
});
});
}
export function mtop(dataAPI, params, otherProps = {}) {
fetchMtop.config.subDomain = otherProps.subDomain || 'm';
return fetchMtop.request({
api: dataAPI,
v: '1.0',
data: params,
ecode: otherProps.ecode || 0,
type: otherProps.method || 'GET',
dataType: otherProps.dataType || 'jsonp',
AntiFlood: true, // 防刷
timeout: otherProps.timeout || 20000
});
}
export function bzb(apiCode, params, otherProps = {}) {
// 通过url参数设置小二工作台请求环境
const getUrlEnv = () => {
try {
if (window.parent && window.parent.location.host === window.location.host) {
const urlInfo = parseUrl(window.parent && window.parent.location.href);
return urlInfo && urlInfo.params && urlInfo.params._env;
}
const urlInfo = parseUrl(window.location.href);
return urlInfo && urlInfo.params && urlInfo.params._env;
} catch (e) {
return null;
}
};
otherProps.method = otherProps.method || 'GET';
otherProps.env = getUrlEnv() || otherProps.env || 'prod';
return bzbRequest(apiCode, {
data: params,
...otherProps
});
}

View File

@ -0,0 +1,482 @@
import { forEach } from '@ali/b3-one/lib/obj';
import {
clone,
fastClone,
jsonuri,
isSchema,
isFileSchema,
isJSFunction,
isJSExpression,
parseObj,
transformSchemaToPure,
transformSchemaToStandard,
isEmpty,
moveArrayItem,
serialize,
deepEqual
} from './index';
import Debug from 'debug';
import compFactory from '../hoc/compFactory';
const debug = Debug('utils:schemaHelper');
let keyIndex = 0;
export default class SchemaHelper {
constructor(schema, appHelper) {
this.appHelper = appHelper;
this.reset(schema, true);
}
reset(schema, isInit) {
debug('start reset');
this.emit('schemaHelper.schema.beforeReset');
this.schemaMap = {};
this.blockSchemaMap = {};
this.compThisMap = {};
this.blockTree = {};
this.compTreeMap = {};
this.compCtxMap = {};
this.rebuild(schema, isInit);
this.emit('schemaHelper.schema.afterReset');
}
add(schema, targetKey, direction) {
this.emit('schemaHelper.material.beforeAdd');
const targetSchema = this.schemaMap[targetKey];
if (isEmpty(schema) || !targetSchema) return;
let targetPath = targetSchema.__ctx.lunaPath;
if (targetPath === '' && direction !== 'in') {
console.warn('add error');
return;
}
let newSchema = [];
if (Array.isArray(schema)) {
newSchema = schema.filter(item => isSchema(item, true));
} else if (isSchema(schema)) {
newSchema = [schema];
} else {
console.error('模型结构异常');
return;
}
if (direction === 'in') {
const targetNode = jsonuri.get(this.schema, targetPath);
targetNode.children = (targetNode.children || []).concat(newSchema);
//jsonuri.set(this.schema, targetPath, targetNode);
} else {
direction = ['left', 'top'].includes(direction) ? 'before' : 'after';
newSchema.reverse().forEach(item => {
jsonuri.insert(this.schema, targetPath, item, direction);
});
}
const addKey = `luna_${keyIndex + 1}`;
this.rebuild(this.schema);
this.emit('schemaHelper.material.afterAdd', addKey);
return addKey;
}
remove(lunaKey) {
this.emit('schemaHelper.material.beforeRemove');
const schema = this.schemaMap[lunaKey];
if (!schema) return;
const lunaPath = schema.__ctx.lunaPath;
if (lunaPath === '') {
console.warn('root node can not be removed');
return;
}
jsonuri.rm(this.schema, lunaPath);
delete this.schemaMap[lunaKey];
delete this.blockSchemaMap[lunaKey];
this.rebuild(this.schema);
this.emit('schemaHelper.material.afterRemove');
}
move(lunaKey, targetKey, direction) {
this.emit('schemaHelper.material.beforeMove');
debug('start move');
const schema = this.schemaMap[lunaKey];
const targetSchema = this.schemaMap[targetKey];
if (!schema || !targetSchema) return;
let lunaPath = schema.__ctx.lunaPath;
let targetPath = targetSchema.__ctx.lunaPath;
if (lunaPath === '' || (targetPath === '' && direction !== 'in')) {
console.warn('move error');
return;
}
const res = /(.*)\/(\d+)$/.exec(lunaPath);
const prefix = res && res[1];
const attr = res && res[2];
if (!prefix || !attr) {
console.warn('异常结构');
return;
}
const sourceIdx = parseInt(attr);
const reg = new RegExp(`^${prefix}/(\\d+)$`);
const regRes = reg.exec(targetPath);
const sourceParent = jsonuri.get(this.schema, prefix);
direction = direction === 'in' ? 'in' : ['left', 'top'].includes(direction) ? 'before' : 'after';
if (regRes && regRes[1] && direction !== 'in') {
const distIdx = parseInt(regRes[1]);
moveArrayItem(sourceParent, sourceIdx, distIdx, direction);
} else {
if (direction === 'in') {
const targetNode = jsonuri.get(this.schema, targetPath);
targetNode.children = targetNode.children || [];
if (Array.isArray(targetNode.children)) {
targetNode.children.push(schema);
}
jsonuri.set(this.schema, targetPath, targetNode);
} else {
jsonuri.insert(this.schema, targetPath, schema, direction);
}
sourceParent.splice(sourceIdx, 1);
}
this.rebuild(this.schema);
this.emit('schemaHelper.material.afterMove');
}
//组件上移 下移
// direction 取值 down/up
slide(lunaKey, direction) {
const schema = this.schemaMap[lunaKey];
if (!schema || !direction) return;
const lunaPath = schema.__ctx && schema.__ctx.lunaPath;
if (!lunaPath) return;
if (direction === 'up' && lunaPath.endsWith('/0')) return;
const targetPath = lunaPath.replace(/\/(\d+)$/, (res, idx) => {
return `/${direction === 'down' ? parseInt(idx) + 1 : parseInt(idx) - 1}`;
});
const targetSchema = this.getSchemaByPath(targetPath);
const targetKey = targetSchema && targetSchema.__ctx && targetSchema.__ctx.lunaKey;
if (!targetKey) return;
this.move(lunaKey, targetKey, direction === 'down' ? 'bottom' : 'top');
}
// 快速复制
copy(lunaKey) {
this.emit('schemaHelper.material.beforeCopy');
const schema = this.schemaMap[lunaKey];
if (!schema) return;
const newSchema = transformSchemaToPure(fastClone(schema));
delete newSchema.__ctx;
const addKey = this.add(newSchema, schema.__ctx.lunaKey, 'bottom');
this.emit('schemaHelper.material.afterCopy', addKey);
return addKey;
}
update(lunaKey, props) {
this.emit('schemaHelper.material.beforeUpdate');
const schema = this.schemaMap[lunaKey];
if (!schema) return;
const {
__state,
__defaultProps,
__fileName,
__scss,
__loop,
__loopArgs,
__condition,
__lifeCycles,
__methods,
__dataSource,
children,
...otherProps
} = props;
debug('update props', props);
//自定义组件才处理defaultProps
if (schema.componentName === 'Component' && '__defaultProps' in props) {
if (!__defaultProps || typeof __defaultProps !== 'object' || isEmpty(__defaultProps)) {
delete schema.defaultProps;
} else {
schema.defaultProps = __defaultProps;
}
this.appHelper.components[schema.fileName.replace(/^\w/, a => a.toUpperCase())] = compFactory(schema);
}
// 如果loop值没有设置有效值则删除schema中这个的字段
if ('__loop' in props) {
if (!__loop || isEmpty(__loop)) {
delete schema.loop;
} else {
schema.loop = __loop;
}
}
// 指定循环上下文变量名
if ('__loopArgs' in props) {
if (
__loopArgs === undefined ||
(typeof __loopArgs === 'object' && isEmpty(__loopArgs)) ||
!Array.isArray(__loopArgs) ||
__loopArgs.every(item => !item)
) {
delete schema.loopArgs;
} else {
schema.loopArgs = __loopArgs;
}
}
// 判断条件
if ('__condition' in props) {
if (__condition === undefined) {
delete schema.condition;
} else {
schema.condition = __condition;
}
}
// 处理容器类组件需要考虑的字段
if (isFileSchema(schema)) {
// filename
if ('__fileName' in props) {
schema.fileName = __fileName;
}
// state
if ('__state' in props) {
// 重走生命周期
schema.__ctx && ++schema.__ctx.idx;
if (!__state || typeof __state !== 'object' || isEmpty(__state)) {
delete schema.state;
} else {
schema.state = __state;
}
}
// 生命周期
if ('__lifeCycles' in props) {
if (!__lifeCycles || typeof __lifeCycles !== 'object' || isEmpty(__lifeCycles)) {
delete schema.lifeCycles;
} else {
schema.lifeCycles = __lifeCycles;
}
}
// 自定义方法
if ('__methods' in props) {
if (!__methods || typeof __methods !== 'object' || isEmpty(__methods)) {
delete schema.methods;
} else {
schema.methods = __methods;
}
}
// 数据源设置
if ('__dataSource' in props) {
if (this.needContainerReload(schema.dataSource, __dataSource)) {
schema.__ctx && ++schema.__ctx.idx;
}
if (__dataSource === undefined || (typeof __dataSource === 'object' && isEmpty(__dataSource))) {
delete schema.dataSource;
} else {
schema.dataSource = __dataSource;
}
}
// 如果scss值没有设置有效值则删除schema中这个的字段
if ('__scss' in props) {
if (!__scss) {
delete schema.scss;
} else {
schema.scss = __scss;
}
}
}
// 子组件
if ('children' in props) {
if (children === undefined || (typeof children === 'object' && isEmpty(children))) {
delete schema.children;
} else {
schema.children = children;
}
}
schema.props = {
...schema.props,
...otherProps
};
//过滤undefined属性
Object.keys(schema.props).map(key => {
if (schema.props[key] === undefined) {
delete schema.props[key];
}
});
this.rebuild(this.schema);
this.emit('schemaHelper.material.afterUpdate');
}
createSchema(componentName, props, isContainer) {
const schema = {
componentName,
props: props || {},
__ctx: {
lunaKey: ++this.lunaKey
}
};
if (isContainer) {
schema.children = [];
}
return schema;
}
rebuild(schema, isInit) {
if (!isFileSchema(schema)) {
debug('top schema should be a file type');
//对于null的schema特殊处理一下
if (schema === null) {
this.schema = schema;
this.emit(`schemaHelper.schema.${isInit ? 'afterInit' : 'afterUpdate'}`);
}
return;
}
this.blockTree = null;
this.compTreeMap = {};
this.compTree = null;
this.schemaMap = {};
this.blockSchemaMap = {};
this.compCtxMap = {};
const buildSchema = (schema, parentBlockNode, parentCompNode, path = '') => {
if (Array.isArray(schema)) {
return schema.map((item, idx) => buildSchema(item, parentBlockNode, parentCompNode, `${path}/${idx}`));
} else if (typeof schema === 'object') {
// 对于undefined及null直接返回
if (!schema) return schema;
//JSFunction转函数
if (isJSFunction(schema)) {
if (typeof schema.value === 'string') {
let tarFun = parseObj(schema.value);
if (typeof tarFun === 'function') {
return tarFun;
}
} else if (typeof schema.value === 'function') {
return schema.value;
}
return schema;
}
//如果是对象且是JSExpression
if (isJSExpression(schema)) {
return '{{' + schema.value + '}}';
}
const res = {};
if (isSchema(schema)) {
res.__ctx = schema.__ctx;
if (!res.__ctx) {
const lunaKey = `luna_${++keyIndex}`;
res.__ctx = {
idx: 0,
lunaKey,
lunaPath: path,
parentKey: parentCompNode && parentCompNode.lunaKey,
blockKey: parentBlockNode && parentBlockNode.lunaKey
};
} else {
res.__ctx.lunaPath = path;
}
const label = schema.componentName + (schema.fileName ? '-' + schema.fileName : '');
const lunaKey = res.__ctx && res.__ctx.lunaKey;
this.schemaMap[lunaKey] = res;
if (isFileSchema(schema)) {
this.blockSchemaMap[lunaKey] = res;
const blockNode = {
label,
lunaKey,
isFile: true,
children: []
};
this.compTreeMap[lunaKey] = blockNode;
const compNode = clone(blockNode);
if (parentBlockNode) {
parentBlockNode.children.push(blockNode);
} else {
this.blockTree = blockNode;
}
parentBlockNode = blockNode;
if (parentCompNode) {
parentCompNode.children.push(compNode);
} else {
this.compTree = compNode;
}
parentCompNode = compNode;
} else {
const compNode = {
label,
lunaKey,
children: []
};
parentCompNode.children.push(compNode);
parentCompNode = compNode;
}
}
forEach(schema, (val, key) => {
if (key.startsWith('__')) {
res[key] = val;
} else {
res[key] = buildSchema(val, parentBlockNode, parentCompNode, `${path}/${key}`);
}
});
return res;
}
return schema;
};
this.emit(`schemaHelper.schema.${isInit ? 'beforeInit' : 'beforeUpdate'}`);
this.schema = buildSchema(schema);
this.emit(`schemaHelper.schema.${isInit ? 'afterInit' : 'afterUpdate'}`);
}
needContainerReload(preData = {}, nextData = {}) {
if (
typeof preData.dataHandler === 'function' &&
typeof nextData.dataHandler === 'function' &&
preData.dataHandler.toString() !== nextData.dataHandler.toString()
) {
return true;
} else if (preData.dataHandler !== nextData.dataHandler) {
return true;
}
return !deepEqual(
(preData.list || []).filter(item => item.isInit),
(nextData.list || []).filter(item => item.isInit),
(pre, next) => {
if (typeof pre === 'function' && next === 'function') {
return pre.toString() === next.toString();
}
}
);
}
emit(msg, ...args) {
this.appHelper && this.appHelper.emit(msg, ...args);
}
get(key) {
return this[key];
}
getSchemaByPath(path) {
return jsonuri.get(this.schema, path);
}
getSchema() {
return this.schema;
}
getPureSchema() {
return transformSchemaToPure(this.schema);
}
getPureSchemaStr() {
return serialize(this.getPureSchema(), {
unsafe: true
});
}
getStandardSchema() {
return transformSchemaToStandard(this.schema);
}
getStandardSchemaStr() {
return serialize(this.getStandardSchema(), {
unsafe: true
});
}
}

View File

@ -0,0 +1,81 @@
import localforage from 'localforage';
import Debug from 'debug';
import { serialize } from './index';
const debug = Debug('utils:storageHelper');
export default class StorageHelper {
constructor(name) {
this.store = localforage.createInstance(name);
}
getItem(key) {
if (!this.store) {
throw new Error('store instance not exist');
}
return this.store.getItem(key);
}
setItem(key, value) {
if (!this.store) {
throw new Error('store instance not exist');
}
return this.store.setItem(key, value);
}
removeItem(key) {
if (!this.store) {
throw new Error('store instance not exist');
}
return this.store.removeItem(key);
}
clear() {
if (!this.store) {
throw new Error('store instance not exist');
}
return this.store.clear();
}
addHistory(key, code, limit = 10) {
return new Promise((resolve, reject) => {
key = '__luna_history_' + key;
this.store
.getItem(key)
.then(res => {
let codeStr = serialize(code, {
unsafe: true
});
if (res && res[0] && res[0].code) {
if (codeStr === res[0].code) return;
}
res = res || [];
let newId = 1;
if (res && res[0] && res[0].id) {
newId = res[0].id + 1;
}
res.unshift({
id: newId,
time: +new Date(),
code: codeStr
});
this.store
.setItem(key, res.slice(0, limit))
.then(res => {
resolve(res);
})
.catch(reject);
})
.catch(reject);
});
}
getHistory(key) {
key = '__luna_history_' + key;
return this.store.getItem(key);
}
clearHistory(key) {
key = '__luna_history_' + key;
this.store.removeItem(key);
}
}

View File

@ -0,0 +1,88 @@
import Debug from 'debug';
import { fastClone } from './index';
const DEFAULT_CONFIG = {
limit: 20
};
const debug = Debug('utils:undoRedoHelper');
export default class UndoRedoHelper {
constructor(config) {
this.config = { ...DEFAULT_CONFIG, ...config };
this.data = {};
}
create(key, value, forceCreate) {
if (!this.data[key] || forceCreate) {
this.data[key] = {
list: [fastClone(value)],
idx: 0
};
}
return this.data[key];
}
delete(key) {
delete this.data[key];
}
resetRecord(key, value) {
const data = this.data[key];
if (!data || !data.list) return;
data.list = data.list.slice(0, data.idx + 1);
data.list[data.idx] = fastClone(value);
}
record(key, value) {
const data = this.data[key];
const limit = this.config.limit;
if (!data || !data.list) return;
data.list = data.list.slice(0, data.idx + 1);
if (data.list.length >= limit) {
data.list.shift();
}
data.list.push(fastClone(value));
++data.idx;
}
undo(key) {
const data = this.data[key];
if (!data || !data.list) return null;
//若没有前置操作,返回当前数据
if (data.idx <= 0) return data.list[data.idx];
--data.idx;
return data.list[data.idx];
}
redo(key) {
const data = this.data[key];
if (!data || !data.list) return null;
//若没有后续操作,返回当前数据
if (data.idx >= data.list.length - 1) return data.list[data.idx];
++data.idx;
return data.list[data.idx];
}
past(key) {
const data = this.data[key];
if (!data || !data.list || data.idx <= 0) return null;
return data.list[data.idx - 1];
}
present(key) {
const data = this.data[key];
if (!data || !data.list) return null;
return data.list[data.idx];
}
future(key) {
const data = this.data[key];
if (!data || !data.list || data.idx >= data.list.length - 1) return null;
return data.list[data.idx + 1];
}
get(key) {
return {
past: this.past(key),
present: this.present(key),
future: this.future(key)
};
}
}

View File

@ -0,0 +1,87 @@
import Debug from 'debug';
import client from 'socket.io-client';
import { parseUrl } from '@ali/b3-one/lib/url';
const debug = Debug('utils:wsHelper');
export default class WSHelper {
constructor(appHelper, namespace, options) {
this.appHelper = appHelper;
this.ws = null;
this.init(namespace, options);
}
init(namespace = '/', options = {}) {
if (this.ws) {
this.close();
}
const urlInfo = parseUrl();
const ws = (this.ws = client(namespace, {
reconnectionDelay: 3000,
transports: ['websocket'],
query: urlInfo.params,
...options
}));
const appHelper = this.appHelper;
debug('ws.init');
ws.on('connect', msg => {
appHelper.emit('wsHelper.connect.success', msg);
debug('ws.connect');
});
ws.on('error', msg => {
appHelper.emit('wsHelper.connect.error', msg);
debug('ws.error', msg);
});
ws.on('disconnect', msg => {
appHelper.emit('wsHelper.connect.break', msg);
debug('ws.disconnect', msg);
});
ws.on('reconnecting', msg => {
appHelper.emit('wsHelper.connect.retry', msg);
debug('ws.reconnecting', msg);
});
ws.on('ping', msg => {
debug('ws.ping', msg);
});
ws.on('pong', msg => {
debug('ws.pong', msg);
});
ws.on('data', msg => {
appHelper.emit('wsHelper.data.receive', msg);
if (msg.eventName) {
appHelper.emit(`wsHelper.result.${msg.eventName}`, msg);
}
debug('ws.data', msg);
});
}
close() {
if (!this.ws) return;
this.ws.close();
this.ws = null;
this.appHelper.emit('wsHelper.connect.close');
}
send(eventName, ...args) {
return new Promise((resolve, reject) => {
try {
this.appHelper.emit('wsHelper.data.request', {
eventName,
params: args
});
this.appHelper.once(`wsHelper.result.${eventName}`, resolve);
this.ws && this.ws.emit(eventName, ...args);
debug('ws.send', eventName);
} catch (err) {
console.error('websocket error:', err);
reject(err);
}
});
}
}