fix settings pane style 80%

This commit is contained in:
kangwei 2020-03-05 21:13:48 +08:00
parent 0725160eac
commit 9c52978702
66 changed files with 196122 additions and 251 deletions

View File

@ -0,0 +1,16 @@
# EditorConfig is awesome: http://EditorConfig.org
# top-most EditorConfig file
root = true
# Tab indentation
[*]
charset = utf-8
end_of_line = lf
indent_size = 2
indent_style = space
insert_final_newline = true
trim_trailing_whitespace = true
[*.md]
trim_trailing_whitespace = false

View File

@ -0,0 +1,8 @@
.idea/
.vscode/
.theia/
.recore/
build/
.*
~*
node_modules

3
packages/demo/.eslintrc Normal file
View File

@ -0,0 +1,3 @@
{
"extends": "./node_modules/@recore/config/.eslintrc"
}

40
packages/demo/.gitignore vendored Normal file
View File

@ -0,0 +1,40 @@
node_modules/
coverage/
build/
dist/
.idea/
.vscode/
.theia/
.recore/
~*
package-lock.json
# Packages #
############
# it's better to unpack these files and commit the raw source
# git has its own built in compression methods
*.7z
*.dmg
*.gz
*.iso
*.jar
*.rar
*.tar
*.zip
# Logs and databases #
######################
*.log
*.sql
*.sqlite
# OS generated files #
######################
.DS_Store
.Trash*
*.swp
._*
.Spotlight-V100
.Trashes
ehthumbs.db
Thumbs.db

View File

@ -0,0 +1,6 @@
{
"semi": true,
"singleQuote": true,
"printWidth": 120,
"trailingComma": "all"
}

34
packages/demo/README.md Normal file
View File

@ -0,0 +1,34 @@
# demo
A Recore application demo.
## Recore 文档
https://yuque.antfin-inc.com/recore/docs
## DEEP 物料站点
https://fusion.alibaba-inc.com/deep/
## 安装运行
```bash
# 安装依赖
tnpm install
# 启动调试
npm start
# 本地构建(一般来说不需要)
npm run build
# 日常部署:将资源发布到日常 CDN
npm run deploy
# 线上部署:将资源发布到线上 CDN
npm run deploy:online
```
## 项目发布
集团的前端项目发布全部收口到 def 工程研发平台如果之前没有使用过请先阅读此文档https://yuque.antfin-inc.com/xux/docs/rmsztg

12
packages/demo/abc.json Normal file
View File

@ -0,0 +1,12 @@
{
"name": "demo",
"assets": {
"type": "command",
"command": {
"cmd": [
"tnpm ii",
"tnpm run cloud"
]
}
}
}

27
packages/demo/index.html Normal file
View File

@ -0,0 +1,27 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, maximum-scale=1.0, user-scalable=no" />
<title>demo</title>
<link rel="shortcut icon" type="image/png" href="https://img.alicdn.com/tfs/TB1zgoCemrqK1RjSZK9XXXyypXa-96-96.png" />
<script src="https://g.alicdn.com/code/lib/??react/16.9.0/umd/react.production.min.js,react-dom/16.9.0/umd/react-dom.production.min.js,prop-types/15.7.2/prop-types.js"></script>
<!-- React 非压缩版代码,可根据需要替换或通过代理替换后方便调试 -->
<!-- <script src="https://g.alicdn.com/code/lib/??react/16.9.0/umd/react.development.js,react-dom/16.9.0/umd/react-dom.development.js,prop-types/15.7.2/prop-types.js"></script> -->
<script> React.PropTypes = PropTypes; </script>
<script src="/node_modules/@ali/recore/umd/recore.js"></script>
<!-- Recore 压缩版代码,线上 VM 请使用下面的地址,注意选择自己需要的版本号 -->
<!--<script src="https://gw.alipayobjects.com/os/lib/ali/recore/1.5.3/umd/recore.js"></script>-->
<!-- 可选是否导入 mockjs, 可增强 mock 能力 -->
<script src="https://g.alicdn.com/code/lib/Mock.js/1.0.0/mock-min.js"></script>
<link rel="stylesheet" href="https://alifd.alicdn.com/npm/@alifd/next/1.11.6/next.min.css">
<script src="https://g.alicdn.com/mylib/moment/2.24.0/min/moment.min.js"></script>
<style type="text/css">
body {
-webkit-overflow-scrolling : touch;
}
</style>
</head>
<body>
</body>
</html>

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,55 @@
{
"name": "demo",
"version": "1.0.0",
"description": "A Recore application demo.",
"scripts": {
"start": "nowa2 start",
"build": "nowa2 build",
"cloud": "nowa2 build -o",
"precommit": "lint-staged",
"test": "jest",
"upgrade": "nowa2 upgrade",
"deploy": "nowa2 deploy",
"deploy:online": "nowa2 deploy -o"
},
"engines": {
"node": ">= 8.9.0",
"npm": ">=6.1.0"
},
"dependencies": {
"@ali/api-loader": "^0.7.8",
"@ali/api-runtime": "^0.1.4",
"@ali/deep": "1.17.2",
"@ali/recore": "^1.5.0",
"@alifd/layout": "^0.1.19",
"@alife/theme-254": "^0.6.2",
"@antv/data-set": "^0.10",
"bizcharts": "^3.2",
"react": "^16"
},
"devDependencies": {
"@ali/nowa-recore-solution": "^1.7.0",
"@ali/recore-loader": "^2.0.0",
"@nowa/cli": "^0.6",
"@recore/config": "^2.0.0",
"@types/jest": "^21",
"@types/node": "^7",
"@types/react": "^16",
"eslint": "^6.5.1",
"husky": "^1.1.2",
"jest": "^23.4.1",
"lint-staged": "^7.1.2",
"tslib": "^1.9.3",
"typescript": "^3.1.3",
"prettier": "^1.18.2"
},
"lint-staged": {
"./src/**/*.{ts,tsx}": [
"eslint --fix",
"git add"
]
},
"nowa": {
"solution": "@ali/nowa-recore-solution"
}
}

View File

@ -0,0 +1,20 @@
module.exports = {
'[start]': {
writeToDisk: true,
},
'[build]': {},
deep: {
themeConfig: {},
},
externals: {
'@ali/iceluna-sdk': 'var window.LowCodeRenderer',
'@recore/obx': 'var window.Recore',
'@recore/core-obx': 'var window.Recore',
// '@alifd/next': 'var window.Next',
'moment': 'var window.moment',
},
extraEntry: {
'simulator-renderer': '../designer/src/builtins/simulator/renderer/index.ts',
},
vendors: false,
};

View File

@ -0,0 +1,12 @@
.editor {
display: flex;
}
.lc-designer {
height: 400px;
flex: 1;
min-width: 0;
}
.lc-settings-pane {
width: 300px;
border-left: 1px solid rgba(31,56,88,.1);
}

61
packages/demo/src/app.ts Normal file
View File

@ -0,0 +1,61 @@
import { ViewController } from '@ali/recore';
import DesignView from '../../designer/src';
import SettingsPane, { registerSetter } from '../../plugin-settings-pane/src';
import { EventEmitter } from 'events';
import { Input } from '@alifd/next';
import { createElement } from 'react';
registerSetter('ClassNameSetter', () => {
return createElement('div', {
className: 'lc-block-setter'
}, '这里是类名绑定');
});
registerSetter('EventsSetter', () => {
return createElement('div', {
className: 'lc-block-setter'
}, '这里是事件设置');
});
registerSetter('StringSetter', Input);
const emitter = new EventEmitter();
// 应用入口视图,导航和所有页面复用的 UI 在这里处理
export default class App extends ViewController {
static components = {
DesignView,
SettingsPane
};
editor = {
on(type: string, fn: any) {
emitter.on(type, fn);
}
}
$didMount() {
const designer = this.$refs.d.designer;
const pane = this.$refs.pane;
(window as any).LCDesigner = designer;
if (designer.project.activedDocument) {
emitter.emit('designer.actived-document-change', designer.project.activedDocument);
}
designer.project.onActivedDocumentChange((doc: any) => {
emitter.emit('designer.actived-document-change', doc);
});
(this.editor as any).designer = designer;
designer.dragon.from(pane, () => {
return {
type: 'nodedata',
data: {
componentName: 'Button',
props: {
type: 'primary',
},
children: 'awefawef'
},
};
});
}
}

662
packages/demo/src/app.vx Normal file
View File

@ -0,0 +1,662 @@
<div className="editor">
<DesignView
defaultSchema={{
componentsTree: [
{
componentName: 'Page',
fileName: 'form',
props: { style: { paddingTop: 20, paddingRight: 20, paddingLeft: 20 } },
children: [
{
componentName: 'Div',
props: { style: { height: '255px' } },
children: [
{
componentName: 'Text',
props: { text: '内容筛选', style: { fontWeight: 'bold', fontSize: '16px' } },
},
{
componentName: 'Form',
props: {
style: {
display: 'flex',
flexDirection: 'row',
justifyContent: 'flex-start',
alignItems: 'center',
flexWrap: 'wrap',
marginTop: 10,
paddingLeft: 10,
textAlign: 'center',
float: 'left',
minWidth: '900px',
},
labelTextAlign: 'right',
wrapperCol: 12,
labelAlign: 'left',
inline: false,
labelCol: 6,
},
children: [
{
componentName: 'FormItem',
props: {
label: {
type: 'JSSlot',
value: {
componentName: 'Fragment',
children: '所属应用:'
}
},
// '所属应用:',
name: 'appApply',
initValue: '22',
style: {
display: 'flex',
flexDirection: 'row',
justifyContent: 'flex-start',
alignItems: 'center',
flexWrap: 'nowrap',
width: '33.33%',
height: '40px',
verticalAlign: 'middle',
float: 'left',
},
labelTextAlign: 'right',
asterisk: false,
labelAlign: 'left',
size: 'medium',
},
children: [
{
componentName: 'Input',
props: { placeholder: '请输入', size: 'medium', style: { width: '200px' } },
},
],
},
{
componentName: 'FormItem',
props: {
label: '分类ID',
name: 'typeId',
initValue: '22',
style: {
display: 'flex',
flexDirection: 'row',
justifyContent: 'flex-start',
alignItems: 'center',
flexWrap: 'nowrap',
width: '33.33%',
height: '40px',
verticalAlign: 'middle',
float: 'left',
},
},
children: [
{
componentName: 'NumberPicker',
props: { size: 'medium', type: 'normal', style: { width: '200px' } },
},
],
},
{
componentName: 'FormItem',
props: {
label: '标签ID',
name: 'tagId',
initValue: '22',
style: {
display: 'flex',
flexDirection: 'row',
justifyContent: 'flex-start',
alignItems: 'center',
flexWrap: 'nowrap',
width: '33.33%',
height: '40px',
verticalAlign: 'middle',
float: 'left',
},
},
children: [
{
componentName: 'NumberPicker',
props: { size: 'medium', type: 'normal', style: { width: '200px' } },
},
],
},
{
componentName: 'FormItem',
props: {
label: '开始时间:',
name: 'startTime',
style: {
display: 'flex',
flexDirection: 'row',
justifyContent: 'flex-start',
alignItems: 'center',
flexWrap: 'nowrap',
width: '33.33%',
height: '40px',
verticalAlign: 'middle',
float: 'left',
},
},
children: [{ componentName: 'TimePicker', props: {} }],
},
{
componentName: 'FormItem',
props: {
label: '结束时间:',
name: 'workendTIme',
style: {
display: 'flex',
flexDirection: 'row',
justifyContent: 'flex-start',
alignItems: 'center',
flexWrap: 'nowrap',
width: '33.33%',
height: '40px',
verticalAlign: 'middle',
float: 'left',
},
},
children: [{ componentName: 'TimePicker', props: {} }],
},
{
componentName: 'FormItem',
props: {
label: '尺寸:',
name: 'size',
style: {
display: 'flex',
flexDirection: 'row',
justifyContent: 'flex-start',
alignItems: 'center',
flexWrap: 'nowrap',
width: '33.33%',
height: '40px',
verticalAlign: 'middle',
float: 'left',
},
labelTextAlign: 'right',
},
children: [
{
componentName: 'Select',
props: {
dataSource: [
{ label: '教师', value: 't' },
{ label: '医生', value: 'd' },
{ label: '歌手', value: 's' },
],
style: { width: '200px' },
},
},
],
},
{
componentName: 'FormItem',
props: {
label: '删除状态:',
name: 'isRemoved',
style: {
display: 'flex',
flexDirection: 'row',
justifyContent: 'flex-start',
alignItems: 'center',
flexWrap: 'nowrap',
width: '33.33%',
height: '40px',
verticalAlign: 'middle',
float: 'left',
},
},
children: [
{
componentName: 'Select',
props: {
dataSource: [
{ label: '教师', value: 't' },
{ label: '医生', value: 'd' },
{ label: '歌手', value: 's' },
],
style: { width: '200px' },
},
},
],
},
{
componentName: 'FormItem',
props: {
label: '讨论ID',
name: 'talkId',
initValue: '22',
style: {
display: 'flex',
flexDirection: 'row',
justifyContent: 'flex-start',
alignItems: 'center',
flexWrap: 'nowrap',
width: '33.33%',
height: '40px',
verticalAlign: 'middle',
float: 'left',
},
labelTextAlign: 'right',
asterisk: false,
labelAlign: 'left',
size: 'medium',
},
children: [
{
componentName: 'Input',
props: { placeholder: '请输入', size: 'medium', style: { width: '200px' } },
},
],
},
{
componentName: 'FormItem',
props: {
label: '置顶:',
name: 'isTop',
style: {
display: 'flex',
flexDirection: 'row',
justifyContent: 'flex-start',
alignItems: 'center',
flexWrap: 'nowrap',
width: '33.33%',
height: '40px',
verticalAlign: 'middle',
float: 'left',
},
},
children: [
{
componentName: 'Select',
props: {
dataSource: [
{ label: '教师', value: 't' },
{ label: '医生', value: 'd' },
{ label: '歌手', value: 's' },
],
style: { width: '200px' },
},
},
],
},
{
componentName: 'Div',
props: { style: { display: 'block', width: '100%', textAlign: 'left' } },
children: [
{
componentName: 'ButtonGroup',
props: {},
children: [
{
componentName: 'Button',
props: { type: 'normal', style: { margin: '0 5px 0 5px' }, htmlType: 'reset' },
children: '重置',
},
{
componentName: 'Button',
props: { type: 'primary', style: { margin: '0 5px 0 5px' }, htmlType: 'submit' },
children: '确定',
},
],
},
],
},
],
},
],
},
],
dataSource: {
dataHandler: function dataHandler(dataMap) {
let dataSource = [
{
id: 1,
title: '2017秋冬新款背带裙复古格子连衣裙清新背心裙a字裙短裙子',
url: 'https://item.taobao.com/item.htm?id=558559528377',
type: '清单',
publishTime: '17-04-28 20:29:20',
publishStatus: '已发布',
pushStatus: '已推送至订阅号',
operation: { edit: true },
},
{
id: 2,
title: '2017秋冬新款 高质感特定纱线欧美宽松马海毛羊毛毛衣',
url: 'https://item.taobao.com/item.htm?id=558559528377',
type: '清单',
publishTime: '17-04-28 20:29:20',
publishStatus: '已发布',
pushStatus: '已推送至订阅号',
operation: { edit: true },
},
{
id: 3,
title: '日式天然玉米皮草编碗垫锅垫隔热垫茶垫加厚餐垫GD-29',
url: 'https://item.taobao.com/item.htm?id=558559528377',
type: '清单',
publishTime: '17-04-28 20:29:20',
publishStatus: '已发布',
pushStatus: '已推送至订阅号',
operation: { edit: true },
},
{
id: 4,
title: '2017秋冬新款 绑带腰封设计感超顺滑质感落肩铜氨丝连衣裙',
url: 'https://item.taobao.com/item.htm?id=558559528377',
type: '清单',
publishTime: '17-04-28 20:29:20',
publishStatus: '已发布',
pushStatus: '已推送至订阅号',
operation: { edit: true },
},
{
id: 5,
title: '日式糖果色陶瓷柄不锈钢餐具西餐牛扒刀叉勺子咖啡勺',
url: 'https://item.taobao.com/item.htm?id=558559528377',
type: '清单',
publishTime: '17-04-28 20:29:20',
publishStatus: '已发布',
pushStatus: '已推送至订阅号',
operation: { edit: true },
},
{
id: 6,
title: '日式和风深蓝素色文艺餐巾餐垫围裙锅垫隔热手套厨房桌布',
url: 'https://item.taobao.com/item.htm?id=558559528377',
type: '清单',
publishTime: '17-04-28 20:29:20',
publishStatus: '已发布',
pushStatus: '已推送至订阅号',
operation: { edit: true },
},
{
id: 7,
title: '日式雪点樱花手绘陶瓷餐具米饭碗拉面碗寿司盘子汤碗',
url: 'https://item.taobao.com/item.htm?id=558559528377',
type: '清单',
publishTime: '17-04-28 20:29:20',
publishStatus: '已发布',
pushStatus: '已推送至订阅号',
operation: { edit: true },
},
{
id: 8,
title: '川岛屋 釉下彩复古日式陶瓷盘子菜盘圆盘调味碟 米饭碗日式餐具',
url: 'https://item.taobao.com/item.htm?id=558559528377',
type: '清单',
publishTime: '17-04-28 20:29:20',
publishStatus: '已发布',
pushStatus: '已推送至订阅号',
operation: { edit: true },
},
];
return { ...dataMap, dataSource };
},
},
},
{
componentName: 'Page',
fileName: 'list',
props: { style: { paddingTop: 20, paddingRight: 20, paddingLeft: 20 } },
children: [
{
componentName: 'Div',
props: { style: { height: '90px', lineHeight: '30px' } },
children: [
{
componentName: 'Div',
props: { style: { float: 'left' } },
children: [
{ componentName: 'Text', props: { style: { paddingLeft: 5, cursor: 'pointer' }, text: '最热' } },
{
componentName: 'Icon',
props: {
size: 'small',
style: { paddingRight: 5, paddingLeft: 5, fontSize: 14, color: '#4a90e2', cursor: 'pointer' },
type: 'sorting',
},
},
],
},
{
componentName: 'Div',
props: { style: { float: 'left' } },
children: [
{ componentName: 'Text', props: { text: '最新', style: { paddingLeft: 10, cursor: 'pointer' } } },
{
componentName: 'Icon',
props: {
size: 'small',
style: {
paddingRight: 5,
paddingLeft: 5,
fontSize: '14px',
color: '#9b9b9b',
cursor: 'pointer',
},
type: 'sorting',
},
},
],
},
{
componentName: 'Div',
props: { style: { float: 'left' } },
children: [
{
componentName: 'Text',
props: { text: '距离接稿日期最近', style: { paddingLeft: 10, cursor: 'pointer' } },
},
{
componentName: 'Icon',
props: {
size: 'small',
style: { fontSize: '14px', color: '#9b9b9b', cursor: 'pointer' },
type: 'sorting',
},
},
],
},
],
},
{
componentName: 'Div',
props: { style: { marginTop: 5, marginBottom: 15, borderBottom: '1px solid rgba(244,244,244)' } },
children: [
{
componentName: 'Div',
props: { style: { marginBottom: 15 } },
children: [
{
componentName: 'Text',
props: { text: '{{this.item.title}}', style: { color: 'rgba(51,51,51)' } },
},
{
componentName: 'Text',
props: {
text: '{{this.item.datetime}}',
style: { fontSize: '12px', color: '#666', float: 'right' },
},
},
],
},
{
componentName: 'Div',
props: { style: { paddingBottom: 15, fontSize: '13px', color: '#666' } },
children: '{{this.item.description}}',
},
{
componentName: 'Div',
props: { style: { marginBottom: 15 } },
children: [
{
componentName: 'Button',
props: { type: 'normal', style: { marginRight: 5, marginLeft: 5 }, size: 'small' },
children: '{{this.item}}',
loop: '{{this.item.tags}}',
},
{
componentName: 'Div',
props: { style: { marginBottom: 15, float: 'right' } },
children: [
{
componentName: 'Div',
props: {
style: {
display: 'inline-block',
marginRight: 5,
marginBottom: 15,
marginLeft: 5,
fontSize: 12,
color: '#666',
float: 'none',
},
},
children: '{{"点赞:"+this.item.star}}',
},
{
componentName: 'Div',
props: {
style: {
display: 'inline-block',
marginRight: 5,
marginBottom: 15,
marginLeft: 5,
fontSize: 12,
color: '#666',
float: 'none',
},
},
children: '{{"喜爱:"+this.item.like}}',
},
{
componentName: 'Div',
props: {
style: {
display: 'inline-block',
marginRight: 5,
marginBottom: 15,
marginLeft: 5,
fontSize: 12,
color: '#66',
float: 'none',
},
},
children: '{{"评论:"+this.item.comment}}',
},
],
},
],
},
],
loop: '{{this.state.dataSource}}',
},
{
componentName: 'Pagination',
props: {
shape: 'normal',
type: 'normal',
size: 'medium',
style: { marginTop: 10, marginBottom: 30, textAlign: 'right' },
},
},
],
dataSource: {
dataHandler: function dataHandler(dataMap) {
const dataSource = [
{
title: '越夏越嗨皮-7月官方营销活动-技能提升方向',
description:
'商家通过V任务选择主播并达成合作费用按照商品链接计算一个商品为一个价格建议主播在一场直播里最多接60个商品并提供不少于两个小时的直播服务每个商品讲解时间不少于5分钟。 ',
tags: ['直播', '大促', '简介'],
datetime: '2017年12月12日 18:00',
star: Math.floor(Math.random() * 100) + 100,
like: Math.floor(Math.random() * 100) + 200,
comment: Math.floor(Math.random() * 100) + 100,
},
{
title: '越夏越嗨皮-7月官方营销活动-技能提升方向',
description:
'商家通过V任务选择主播并达成合作费用按照商品链接计算一个商品为一个价格建议主播在一场直播里最多接60个商品并提供不少于两个小时的直播服务每个商品讲解时间不少于5分钟。 ',
tags: ['直播', '大促', '简介'],
datetime: '2017年12月12日 18:00',
star: Math.floor(Math.random() * 100) + 100,
like: Math.floor(Math.random() * 100) + 200,
comment: Math.floor(Math.random() * 100) + 100,
},
{
title: '越夏越嗨皮-7月官方营销活动-技能提升方向',
description:
'商家通过V任务选择主播并达成合作费用按照商品链接计算一个商品为一个价格建议主播在一场直播里最多接60个商品并提供不少于两个小时的直播服务每个商品讲解时间不少于5分钟。 ',
tags: ['直播', '大促', '简介'],
datetime: '2017年12月12日 18:00',
star: Math.floor(Math.random() * 100) + 100,
like: Math.floor(Math.random() * 100) + 200,
comment: Math.floor(Math.random() * 100) + 100,
},
{
title: '越夏越嗨皮-7月官方营销活动-技能提升方向',
description:
'商家通过V任务选择主播并达成合作费用按照商品链接计算一个商品为一个价格建议主播在一场直播里最多接60个商品并提供不少于两个小时的直播服务每个商品讲解时间不少于5分钟。 ',
tags: ['直播', '大促', '简介'],
datetime: '2017年12月12日 18:00',
star: Math.floor(Math.random() * 100) + 100,
like: Math.floor(Math.random() * 100) + 200,
comment: Math.floor(Math.random() * 100) + 100,
},
{
title: '越夏越嗨皮-7月官方营销活动-技能提升方向',
description:
'商家通过V任务选择主播并达成合作费用按照商品链接计算一个商品为一个价格建议主播在一场直播里最多接60个商品并提供不少于两个小时的直播服务每个商品讲解时间不少于5分钟。 ',
tags: ['直播', '大促', '简介'],
datetime: '2017年12月12日 18:00',
star: Math.floor(Math.random() * 100) + 100,
like: Math.floor(Math.random() * 100) + 200,
comment: Math.floor(Math.random() * 100) + 100,
},
];
return { dataSource };
},
},
},
],
}}
simulatorProps={{
device: 'legao',
componentsAsset: [
{
type: 'jsUrl',
content: 'https://unpkg.alibaba-inc.com/@alifd/next@1.18.17/dist/next.min.js',
id: 'next',
level: 2,
},
{
type: 'cssUrl',
content: 'https://unpkg.alibaba-inc.com/@alifd/next@1.18.17/dist/next.min.css',
id: 'next',
level: 2,
},
],
}}
componentDescriptionSpecs={[
{
description: 'Button',
npm: { package: '@ali/deep', subName: 'Button', destructuring: false, exportName: 'Next', version: '1.17.2' },
componentName: 'Button',
title: 'Button',
},
]}
ref="d"
/>
<SettingsPane editor={editor} />
</div>
<div ref="pane">
<a href="afeawef">aewfawfe</a>
</div>

19
packages/demo/src/module.d.ts vendored Normal file
View File

@ -0,0 +1,19 @@
/// <reference types="react" />
// tslint:disable
declare const __mock__: boolean;
declare module '*.vx' {
const RecoreComponent: React.ComponentClass<{ [key: string]: any }>;
export default RecoreComponent;
}
declare module '*.svg' {
const SvgIcon: React.ComponentClass<any>;
export default SvgIcon;
}
declare module '@alifd/layout';
declare module '@antv/data-set';
declare module '@ali/iceluna-sdk';

View File

@ -0,0 +1,10 @@
{
"extends": "./node_modules/@recore/config/tsconfig",
"compilerOptions": {
"experimentalDecorators": true
},
"include": [
"./src/",
"../src/"
]
}

View File

@ -2,7 +2,7 @@
"name": "lowcode-designer",
"version": "0.9.0",
"description": "alibaba lowcode designer",
"main": "index.js",
"main": "src/index.ts",
"author": "",
"license": "MIT",
"dependencies": {

View File

@ -46,7 +46,11 @@
}
&-device-legao {
margin: 15px;
top: 15px;
right: 15px;
bottom: 15px;
left: 15px;
width: auto;
box-shadow: 0 2px 10px 0 rgba(31,56,88,.15);
}

View File

@ -29,7 +29,7 @@ import {
CanvasPoint,
} from '../../../designer/helper/location';
import { isNodeSchema, NodeSchema } from '../../../designer/schema';
import { ComponentDescriptionSpec } from '../../../designer/component-config';
import { ComponentDescription } from '../../../designer/component-type';
import { ReactInstance } from 'react';
import { setNativeSelection } from '../../../designer/helper/navtive-selection';
import cursor from '../../../designer/helper/cursor';
@ -68,7 +68,7 @@ const defaultDepends = [
'window.PropTypes=parent.PropTypes;React.PropTypes=parent.PropTypes; window.__REACT_DEVTOOLS_GLOBAL_HOOK__ = window.parent.__REACT_DEVTOOLS_GLOBAL_HOOK__;',
),
assetItem(AssetType.JSUrl, 'https://g.alicdn.com/mylib/@ali/recore/1.5.7/umd/recore.min.js'),
assetItem(AssetType.JSUrl, 'http://localhost:4444/js/index.js'),
assetItem(AssetType.JSUrl, '/lowcode-renderer.js'),
];
export class SimulatorHost implements ISimulator<SimulatorProps> {
@ -335,7 +335,7 @@ export class SimulatorHost implements ISimulator<SimulatorProps> {
/**
* @see ISimulator
*/
describeComponent(component: Component): ComponentDescriptionSpec {
describeComponent(component: Component): ComponentDescription {
throw new Error('Method not implemented.');
}
@ -817,7 +817,6 @@ export class SimulatorHost implements ISimulator<SimulatorProps> {
}
}*/
else if (isNode(res)) {
console.info('res', res);
container = res;
upward = null;
}
@ -831,7 +830,7 @@ export class SimulatorHost implements ISimulator<SimulatorProps> {
return this.checkDropTarget(container, dragObject as any);
}
const config = container.componentConfig;
const config = container.componentType;
if (!config.isContainer) {
return false;
@ -916,7 +915,7 @@ export class SimulatorHost implements ISimulator<SimulatorProps> {
checkNestingUp(parent: NodeParent, target: NodeSchema | Node): boolean {
if (isNode(target) || isNodeSchema(target)) {
const config = isNode(target) ? target.componentConfig : this.designer.getComponentConfig(target.componentName);
const config = isNode(target) ? target.componentType : this.designer.getComponentType(target.componentName);
if (config) {
return config.checkNestingUp(target, parent);
}
@ -926,7 +925,7 @@ export class SimulatorHost implements ISimulator<SimulatorProps> {
}
checkNestingDown(parent: NodeParent, target: NodeSchema | Node): boolean {
const config = parent.componentConfig;
const config = parent.componentType;
return config.checkNestingDown(parent, target) && this.checkNestingUp(parent, target);
}
// #endregion

View File

@ -7,9 +7,9 @@ import { RootSchema, NpmInfo } from '../../../designer/schema';
import { getClientRects } from '../../../utils/get-client-rects';
import { Asset } from '../utils/asset';
import loader from '../utils/loader';
import { ComponentDescriptionSpec } from '../../../designer/component-config';
import { ComponentDescription } from '../../../designer/component-type';
import { reactFindDOMNodes, FIBER_KEY } from '../utils/react-find-dom-nodes';
import { isESModule } from '../../../utils/is-es-module';
import { isESModule } from '../../../../../utils/is-es-module';
import { NodeInstance } from '../../../designer/simulator';
import { isElement } from '../../../utils/is-element';
import cursor from '../../../designer/helper/cursor';
@ -281,7 +281,7 @@ function findComponent(componentName: string, npm?: NpmInfo) {
return getSubComponent(library, paths);
}
function buildComponents(componentsMap: { [componentName: string]: ComponentDescriptionSpec }) {
function buildComponents(componentsMap: { [componentName: string]: ComponentDescription }) {
const components: any = {};
Object.keys(componentsMap).forEach(componentName => {
components[componentName] = findComponent(componentName, componentsMap[componentName].npm);

View File

@ -1,4 +1,4 @@
import { ReactNode, ReactElement, ComponentType } from 'react';
import { ReactNode } from 'react';
import Node, { NodeParent } from './document/node/node';
import { NodeData, NodeSchema } from './schema';
@ -17,166 +17,13 @@ export interface PropConfig {
defaultValue?: any;
}
export type CustomView = ReactElement | ComponentType<any>;
export interface TipConfig {
className?: string;
children?: ReactNode;
theme?: string;
direction?: string; // 'n|s|w|e|top|bottom|left|right';
}
export interface IconConfig {
name: string;
size?: string;
className?: string;
effect?: string;
}
export interface TitleConfig {
label?: ReactNode;
tip?: string | ReactElement | TipConfig;
icon?: string | ReactElement | IconConfig;
className?: string;
}
export type Title = string | ReactElement | TitleConfig;
export enum DisplayType {
Inline = 'inline',
Block = 'block',
Accordion = 'Accordion',
Plain = 'plain',
Caption = 'caption',
}
export interface SetterConfig {
/**
* if *string* passed must be a registered Setter Name
*/
componentName: string | CustomView;
/**
* the props pass to Setter Component
*/
props?: {
[prop: string]: any;
};
}
/**
* if *string* passed must be a registered Setter Name
*/
export type SetterType = SetterConfig | string | CustomView;
export interface SettingFieldConfig {
/**
* the name of this setting field, which used in quickEditor
*/
name: string;
/**
* the field body contains
*/
setter: SetterType;
/**
* the prop target which to set, eg. "style.width"
* @default sameas .name
*/
propTarget?: string;
/**
* the field title
* @default sameas .propTarget
*/
title?: Title;
extraProps?: {
/**
* default value of target prop for setter use
*/
defaultValue?: any;
onChange?: (value: any) => void;
getValue?: () => any;
/**
* the field conditional show, is not set always true
* @default undefined
*/
condition?: (node: Node) => boolean;
/**
* quick add "required" validation
*/
required?: boolean;
/**
* the field display
* @default DisplayType.Block
*/
display?: DisplayType.Inline | DisplayType.Block | DisplayType.Accordion | DisplayType.Plain;
/**
* default collapsed when display accordion
*/
defaultCollapsed?: boolean;
/**
* layout control
* number or [column number, left offset]
* @default 6
*/
span?: number | [number, number];
};
}
export interface SettingGroupConfig {
/**
* the type "group"
*/
type: 'group';
/**
* the name of this setting group, which used in quickEditor
*/
name?: string;
/**
* the setting items which group body contains
*/
items: Array<SettingFieldConfig | SettingGroupConfig | CustomView>;
/**
* the group title
* @default sameas .name
*/
title?: Title;
extraProps: {
/**
* the field conditional show, is not set always true
* @default undefined
*/
condition?: (node: Node) => boolean;
/**
* the group display
* @default DisplayType.Block
*/
display?: DisplayType.Block | DisplayType.Accordion;
/**
* default collapsed when display accordion
*/
defaultCollapsed?: boolean;
/**
* the gap between span
* @default 0 px
*/
gap?: number;
/**
* layout control
* number or [column number, left offset]
* @default 6
*/
span?: number | [number, number];
};
}
export type PropSettingConfig = SettingFieldConfig | SettingGroupConfig | CustomView;
export interface NestingRule {
childWhitelist?: string[];
parentWhitelist?: string[];
}
export interface Configure {
props?: PropSettingConfig[];
props?: any[];
styles?: object;
events?: object;
component?: {
@ -187,7 +34,7 @@ export interface Configure {
};
}
export interface ComponentDescriptionSpec {
export interface ComponentDescription {
componentName: string;
/**
* unique id
@ -215,7 +62,7 @@ export interface ComponentDescriptionSpec {
version: string;
};
props?: PropConfig[];
configure?: PropSettingConfig[] | Configure;
configure?: any[] | Configure;
}
function ensureAList(list?: string | string[]): string[] | null {
@ -268,8 +115,8 @@ function generatePropsConfigure(props: PropConfig[]) {
return [];
}
export class ComponentConfig {
readonly isComponentConfig = true;
export class ComponentType {
readonly isComponentType = true;
private _uri?: string;
get uri(): string {
return this._uri!;
@ -295,8 +142,59 @@ export class ComponentConfig {
return this._acceptable!;
}
private _configure?: Configure;
get configure(): Configure {
return this._configure!;
get configure() {
return [{
name: '#props',
title: "属性",
items: [{
name: 'title',
title: '标题',
setter: 'StringSetter'
}, {
name: 'description',
title: '描述',
setter: {
componentName: 'StringSetter',
props: {
multiline: true,
}
}
}]
}, {
name: '#styles',
title: "样式",
items: [{
name: 'className',
title: '类名绑定',
setter: 'ClassNameSetter'
}, {
name: 'className2',
title: '类名绑定',
setter: 'StringSetter'
}, {
name: '#inlineStyles',
title: '行内样式',
items: []
}]
}, {
name: '#events',
title: "事件",
items: [{
name: '!events',
title: '事件绑定',
setter: 'EventsSetter'
}]
}, {
name: '#data',
title: "数据",
items: []
}, {
name: '#a',
title: "数据1",
}, {
name: '#b',
title: "数据2",
}];
}
private parentWhitelist?: string[] | null;
@ -310,15 +208,11 @@ export class ComponentConfig {
return this._spec.icon;
}
get propsConfigure() {
return this.configure.props;
}
constructor(private _spec: ComponentDescriptionSpec) {
constructor(private _spec: ComponentDescription) {
this.parseSpec(_spec);
}
private parseSpec(spec: ComponentDescriptionSpec) {
private parseSpec(spec: ComponentDescription) {
const { componentName, uri, configure, npm, props } = spec;
this._uri = uri || (npm ? npmToURI(npm) : componentName);
this._componentName = componentName;
@ -335,10 +229,10 @@ export class ComponentConfig {
} else {
this._configure = configure;
}
if (!this.configure.props) {
this.configure.props = props ? generatePropsConfigure(props) : [];
if (!this._configure.props) {
this._configure.props = props ? generatePropsConfigure(props) : [];
}
const { component } = this.configure;
const { component } = this._configure;
if (component) {
this._isContainer = component.isContainer ? true : false;
this._isModal = component.isModal ? true : false;
@ -358,12 +252,12 @@ export class ComponentConfig {
return this.componentName === 'Page' || this.componentName === 'Block' || this.componentName === 'Component';
}
set spec(spec: ComponentDescriptionSpec) {
set spec(spec: ComponentDescription) {
this._spec = spec;
this.parseSpec(spec);
}
get spec(): ComponentDescriptionSpec {
get spec(): ComponentDescription {
return this._spec;
}

View File

@ -1,5 +1,5 @@
import { ComponentType } from 'react';
import { obx, computed } from '@recore/obx';
import { ComponentType as ReactComponentType } from 'react';
import { obx, computed, autorun } from '@recore/obx';
import BuiltinSimulatorView from '../builtins/simulator';
import Project from './project';
import { ProjectSchema } from './schema';
@ -10,7 +10,7 @@ import Location, { LocationData, isLocationChildrenDetail } from './helper/locat
import DocumentModel from './document/document-model';
import Node, { insertChildren } from './document/node/node';
import { isRootNode } from './document/node/root-node';
import { ComponentDescriptionSpec, ComponentConfig } from './component-config';
import { ComponentDescription, ComponentType } from './component-type';
import Scroller, { IScrollable } from './helper/scroller';
import { INodeSelector } from './simulator';
import OffsetObserver, { createOffsetObserver } from './helper/offset-observer';
@ -21,10 +21,10 @@ export interface DesignerProps {
defaultSchema?: ProjectSchema;
hotkeys?: object;
simulatorProps?: object | ((document: DocumentModel) => object);
simulatorComponent?: ComponentType<any>;
dragGhostComponent?: ComponentType<any>;
simulatorComponent?: ReactComponentType<any>;
dragGhostComponent?: ReactComponentType<any>;
suspensed?: boolean;
componentDescriptionSpecs?: ComponentDescriptionSpec[];
componentDescriptionSpecs?: ComponentDescription[];
onMount?: (designer: Designer) => void;
onDragstart?: (e: LocateEvent) => void;
onDrag?: (e: LocateEvent) => void;
@ -128,7 +128,7 @@ export default class Designer {
*
*/
getSuitableInsertion() {
const activedDoc = this.project.activedDocuments[0];
const activedDoc = this.project.activedDocument;
if (!activedDoc) {
return null;
}
@ -166,7 +166,7 @@ export default class Designer {
this.suspensed = props.suspensed;
}
if (props.componentDescriptionSpecs !== this.props.componentDescriptionSpecs && props.componentDescriptionSpecs != null) {
this.buildComponentConfigsMap(props.componentDescriptionSpecs);
this.buildComponentTypesMap(props.componentDescriptionSpecs);
}
} else {
// init hotkeys
@ -183,7 +183,7 @@ export default class Designer {
this.suspensed = props.suspensed;
}
if (props.componentDescriptionSpecs != null) {
this.buildComponentConfigsMap(props.componentDescriptionSpecs);
this.buildComponentTypesMap(props.componentDescriptionSpecs);
}
}
this.props = props;
@ -193,9 +193,9 @@ export default class Designer {
return this.props ? this.props[key] : null;
}
@obx.ref private _simulatorComponent?: ComponentType<any>;
@obx.ref private _simulatorComponent?: ReactComponentType<any>;
@computed get simulatorComponent(): ComponentType<any> {
@computed get simulatorComponent(): ReactComponentType<any> {
return this._simulatorComponent || BuiltinSimulatorView;
}
@ -227,38 +227,42 @@ export default class Designer {
// todo:
}
@obx.val private _componentConfigsMap = new Map<string, ComponentConfig>();
@obx.val private _componentTypesMap = new Map<string, ComponentType>();
private buildComponentConfigsMap(specs: ComponentDescriptionSpec[]) {
private buildComponentTypesMap(specs: ComponentDescription[]) {
specs.forEach(spec => {
const key = spec.componentName;
const had = this._componentConfigsMap.get(key);
const had = this._componentTypesMap.get(key);
if (had) {
had.spec = spec;
} else {
this._componentConfigsMap.set(key, new ComponentConfig(spec));
this._componentTypesMap.set(key, new ComponentType(spec));
}
});
}
getComponentConfig(componentName: string): ComponentConfig {
if (this._componentConfigsMap.has(componentName)) {
return this._componentConfigsMap.get(componentName)!;
getComponentType(componentName: string): ComponentType {
if (this._componentTypesMap.has(componentName)) {
return this._componentTypesMap.get(componentName)!;
}
return new ComponentConfig({
return new ComponentType({
componentName,
});
}
get componentsMap(): { [key: string]: ComponentDescriptionSpec } {
get componentsMap(): { [key: string]: ComponentDescription } {
const maps: any = {};
this._componentConfigsMap.forEach((config, key) => {
this._componentTypesMap.forEach((config, key) => {
maps[key] = config.spec;
});
return maps;
}
autorun(action: (context: { firstRun: boolean }) => void, sync: boolean = false): () => void {
return autorun(action, sync as true);
}
purge() {
// todo:
}

View File

@ -6,7 +6,7 @@ import RootNode from './node/root-node';
import { ISimulator, Component } from '../simulator';
import { computed, obx, autorun } from '@recore/obx';
import Location from '../helper/location';
import { ComponentConfig } from '../component-config';
import { ComponentType } from '../component-type';
import History from '../helper/history';
import Prop from './node/props/prop';
@ -49,21 +49,20 @@ export default class DocumentModel {
}
constructor(readonly project: Project, schema: RootSchema) {
// todo: purge this autorun
/*
autorun(() => {
this.nodes.forEach(item => {
if (item.parent == null && item !== this.rootNode) {
// item.remove();
item.purge();
}
});
}, true);*/
}, true);
this.rootNode = this.createRootNode(schema);
this.id = this.rootNode.id;
this.history = new History(
() => this.schema,
(schema) => this.import(schema as RootSchema, true),
);
this.setupListenActiveNodes();
}
readonly designer = this.project.designer;
@ -90,6 +89,12 @@ export default class DocumentModel {
return node ? !node.isPurged : false;
}
@obx.val private activeNodes?: Node[];
private setupListenActiveNodes() {
// todo:
}
/**
* schema
*/
@ -108,7 +113,11 @@ export default class DocumentModel {
if (schema.id) {
node = this.getNode(schema.id);
if (node && node.componentName === schema.componentName) {
node.internalSetParent(null);
if (node.parent) {
node.internalSetParent(null);
// will move to another position
// todo: this.activeNodes?.push(node);
}
node.internalSetSlotFor(slotFor);
node.import(schema, true);
} else if (node) {
@ -117,10 +126,12 @@ export default class DocumentModel {
}
if (!node) {
node = new Node(this, schema, slotFor);
// will add
// todo: this.activeNodes?.push(node);
}
if (this.nodesMap.has(node.id)) {
node.purge();
this.nodesMap.get(node.id)!.internalSetParent(null);
}
this.nodesMap.set(node.id, node);
@ -178,6 +189,7 @@ export default class DocumentModel {
}
this.nodesMap.delete(node.id);
this.nodes.delete(node);
this.selection.remove(node.id);
node.remove();
}
@ -228,6 +240,7 @@ export default class DocumentModel {
import(schema: RootSchema, checkId: boolean = false) {
this.rootNode.import(schema, checkId);
// todo: purge something
// todo: select added and active track added
}
/**
@ -245,7 +258,7 @@ export default class DocumentModel {
*
*/
isModified() {
// return !this.history.isSavePoint();
return !this.history.isSavePoint();
}
/**
@ -272,9 +285,9 @@ export default class DocumentModel {
return this.simulator!.getComponent(componentName);
}
getComponentConfig(componentName: string, component?: Component | null): ComponentConfig {
getComponentType(componentName: string, component?: Component | null): ComponentType {
// TODO: guess componentConfig from component by simulator
return this.designer.getComponentConfig(componentName);
return this.designer.getComponentType(componentName);
}
@obx.ref private _opened: boolean = true;

View File

@ -6,7 +6,7 @@ import NodeChildren from './node-children';
import Prop from './props/prop';
import NodeContent from './node-content';
import { Component } from '../../simulator';
import { ComponentConfig } from '../../component-config';
import { ComponentType } from '../../component-type';
const DIRECTIVES = ['condition', 'conditionGroup', 'loop', 'loopArgs', 'title', 'ignore', 'hidden', 'locked'];
@ -89,8 +89,8 @@ export default class Node {
@computed get title(): string {
let t = this.getDirective('x-title');
if (!t && this.componentConfig.descriptor) {
t = this.getProp(this.componentConfig.descriptor, false);
if (!t && this.componentType.descriptor) {
t = this.getProp(this.componentType.descriptor, false);
}
if (t) {
const v = t.getAsString();
@ -186,8 +186,8 @@ export default class Node {
/**
*
*/
@obx.ref get componentConfig(): ComponentConfig {
return this.document.getComponentConfig(this.componentName, this.component);
@obx.ref get componentType(): ComponentType {
return this.document.getComponentType(this.componentName, this.component);
}
@obx.ref get propsData(): PropsMap | PropsList | null {
@ -250,6 +250,34 @@ export default class Node {
return this.props?.query(path, useStash as any) || null;
}
/**
*
*/
getPropValue(path: string): any {
return this.getProp(path, false)?.value;
}
/**
*
*/
setPropValue(path: string, value: any) {
this.getProp(path, true)!.value = value;
}
/**
*
*/
mergeProps(props: PropsMap) {
this.props?.merge(props);
}
/**
*
*/
setProps(props?: PropsMap | PropsList | null) {
this.props?.import(props);
}
getDirective(name: string, useStash: boolean = true): Prop | null {
return this.directives?.get(name, useStash as any) || null;
}
@ -484,7 +512,7 @@ export function comparePosition(node1: Node, node2: Node): number {
export function insertChild(container: NodeParent, thing: Node | NodeData, at?: number | null, copy?: boolean): Node {
let node: Node;
if (copy && isNode(thing)) {
if (isNode(thing) && (copy || thing.isSlotRoot)) {
thing = thing.export(false);
}
if (isNode(thing)) {

View File

@ -2,7 +2,7 @@ import { untracked, computed, obx } from '@recore/obx';
import { valueToSource } from '../../../../utils/value-to-source';
import { CompositeValue, isJSExpression, isJSSlot, NodeSchema, NodeData, isNodeSchema } from '../../../schema';
import PropStash from './prop-stash';
import { uniqueId } from '../../../../utils/unique-id';
import { uniqueId } from '../../../../../../utils/unique-id';
import { isPlainObject } from '../../../../utils/is-plain-object';
import { hasOwnProperty } from '../../../../utils/has-own-property';
import Props from './props';

View File

@ -1,5 +1,5 @@
import { computed, obx } from '@recore/obx';
import { uniqueId } from '../../../../utils/unique-id';
import { uniqueId } from '../../../../../../utils/unique-id';
import { CompositeValue, PropsList, PropsMap } from '../../../schema';
import PropStash from './prop-stash';
import Prop, { IPropParent } from './prop';
@ -69,6 +69,12 @@ export default class Props implements IPropParent {
this.items.forEach(item => item.purge());
}
merge(value: PropsMap) {
Object.keys(value).forEach(key => {
this.query(key).value = value[key];
});
}
export(serialize = false): PropsMap | PropsList | null {
if (this.items.length < 1) {
return null;

View File

@ -1,9 +1,11 @@
import Node, { comparePosition } from './node/node';
import { obx } from '@recore/obx';
import DocumentModel from './document-model';
import { EventEmitter } from 'events';
export class Selection {
@obx.val private selected: string[] = [];
private emitter = new EventEmitter();
constructor(private doc: DocumentModel) {}
@ -17,6 +19,7 @@ export class Selection {
}
this.selected = [id];
this.emitter.emit('selectionchange');
}
/**
@ -24,28 +27,35 @@ export class Selection {
*/
selectAll(ids: string[]) {
this.selected = ids;
this.emitter.emit('selectionchange');
}
/**
*
*/
clear() {
if (this.selected.length < 1) {
return;
}
this.selected = [];
this.emitter.emit('selectionchange');
}
/**
*
*/
dispose() {
let i = this.selected.length;
const l = this.selected.length;
let i = l;
while (i-- > 0) {
const id = this.selected[i];
if (!this.doc.hasNode(id)) {
this.selected.splice(i, 1);
} else {
this.selected[i] = id;
}
}
if (this.selected.length !== l) {
this.emitter.emit('selectionchange');
}
}
/**
@ -57,6 +67,7 @@ export class Selection {
}
this.selected.push(id);
this.emitter.emit('selectionchange');
}
/**
@ -73,6 +84,7 @@ export class Selection {
let i = this.selected.indexOf(id);
if (i > -1) {
this.selected.splice(i, 1);
this.emitter.emit('selectionchange');
}
}
@ -134,4 +146,11 @@ export class Selection {
}
return nodes;
}
onSelectionChange(fn: () => void): () => void {
this.emitter.addListener('selectionchange', fn);
return () => {
this.emitter.removeListener('selectionchange', fn);
};
}
}

View File

@ -204,7 +204,8 @@ export default class Dragon {
const listenSimulators = !sourceDoc || sourceDoc === doc ? makeSimulatorListener(masterSensors) : null;
const alwaysListen = listenSimulators ? doc : sourceDoc!;
const designer = this.designer;
const newBie = dragObject.type !== DragObjectType.Node;
const newBie = !isDragNodeObject(dragObject);
const forceCopyState = isDragNodeObject(dragObject) && dragObject.nodes.some(node => node.isSlotRoot);
let lastSensor: ISensor | undefined;
this._dragging = false;
@ -219,14 +220,19 @@ export default class Dragon {
}
};
let copy = false;
const checkcopy = (e: MouseEvent) => {
if (newBie) {
return;
}
if (e.altKey || e.ctrlKey) {
copy = true;
this.setCopyState(true);
} else {
this.setCopyState(false);
copy = false;
if (!forceCopyState) {
this.setCopyState(false);
}
}
};
@ -246,7 +252,7 @@ export default class Dragon {
const dragstart = () => {
const locateEvent = createLocateEvent(boostEvent);
if (newBie) {
if (newBie || forceCopyState) {
this.setCopyState(true);
} else {
chooseSensor(locateEvent);
@ -282,7 +288,6 @@ export default class Dragon {
lastSensor.deactiveSensor();
}
this.setNativeSelection(true);
const copy = !newBie && this.isCopyState();
this.clearState();
let exception;
@ -414,6 +419,7 @@ export default class Dragon {
private getMasterSensors(): ISimulator[] {
return this.designer.project.documents
.map(doc => {
// TODO: not use actived,
if (doc.actived && doc.simulator?.sensorAvailable) {
return doc.simulator;
}

View File

@ -3,6 +3,7 @@ import Session from './session';
import { autorun, Reaction, untracked } from '@recore/obx';
import { NodeSchema } from '../schema';
// TODO: cache to localStorage
export interface Serialization<T = any> {
serialize(data: NodeSchema): T;
@ -92,7 +93,7 @@ export default class History {
this.obx.sleep();
try {
this.redoer(hotData);
this.redoer(currentSerializion.unserialize(hotData));
this.emitter.emit('cursor', hotData);
} catch (e) {
//

View File

@ -1,6 +1,6 @@
import { obx, computed } from '@recore/obx';
import { INodeSelector, IViewport } from '../simulator';
import { uniqueId } from '../../utils/unique-id';
import { uniqueId } from '../../../../utils/unique-id';
export default class OffsetObserver {
readonly id = uniqueId('oobx');

View File

@ -27,8 +27,8 @@ export default class Project {
});
}
@computed get activedDocuments() {
return this.documents.filter(doc => doc.actived);
@computed get activedDocument() {
return this.documents.find(doc => doc.actived);
}
/**
@ -106,14 +106,12 @@ export default class Project {
}
checkExclusive(actived: DocumentModel) {
if (this.canvasDisplayMode !== 'exclusive') {
return;
}
this.documents.forEach((doc) => {
if (doc !== actived) {
doc.suspense();
}
});
this.emitter.emit('actived-document-change', actived);
}
closeOthers(opened: DocumentModel) {
@ -127,4 +125,10 @@ export default class Project {
// 通知标记删除,需要告知服务端
// 项目角度编辑不是全量打开所有文档,是按需加载,哪个更新就通知更新谁,
// 哪个删除就
onActivedDocumentChange(fn: (doc: DocumentModel) => void): () => void {
this.emitter.on('actived-document-change', fn);
return () => {
this.emitter.removeListener('actived-document-change', fn);
};
}
}

View File

@ -105,7 +105,7 @@ export function isDOMText(data: any): data is DOMText {
export type DOMText = string;
export interface RootSchema extends NodeSchema {
componentName: 'Block' | 'Page' | 'Component';
componentName: string; // 'Block' | 'Page' | 'Component';
fileName: string;
meta?: object;
state?: {
@ -120,7 +120,7 @@ export interface RootSchema extends NodeSchema {
css?: string;
dataSource?: {
items: DataSourceConfig[];
};
} | any;
defaultProps?: CompositeObject;
}

View File

@ -3,7 +3,7 @@ import { LocateEvent, ISensor } from './helper/dragon';
import { Point } from './helper/location';
import Node from './document/node/node';
import { ScrollTarget, IScrollable } from './helper/scroller';
import { ComponentDescriptionSpec } from './component-config';
import { ComponentDescription } from './component-type';
export type AutoFit = '100%';
export const AutoFit = '100%';
@ -117,7 +117,7 @@ export interface ISimulator<P = object> extends ISensor {
/**
*
*/
describeComponent(component: Component): ComponentDescriptionSpec;
describeComponent(component: Component): ComponentDescription;
/**
*
*/

View File

@ -4,6 +4,6 @@
"experimentalDecorators": true
},
"include": [
"./src/"
"./src/", "../utils/unique-id.ts"
]
}

View File

@ -1 +0,0 @@
事件面板

View File

@ -0,0 +1,6 @@
.idea/
.vscode/
build/
.*
~*
node_modules

View File

@ -0,0 +1,3 @@
{
"extends": "./node_modules/@recore/config/.eslintrc"
}

View File

@ -0,0 +1,6 @@
{
"semi": true,
"singleQuote": true,
"printWidth": 120,
"trailingComma": "all"
}

View File

@ -0,0 +1,43 @@
{
"name": "@ali/lowcode-plugin-settings-pane",
"version": "0.0.0",
"description": "xxx for Ali lowCode engine",
"main": "src/index.tsx",
"files": [
"lib"
],
"scripts": {
"build": "tsc",
"test": "ava",
"test:snapshot": "ava --update-snapshots"
},
"dependencies": {
"@alifd/next": "^1.19.16",
"classnames": "^2.2.6",
"react": "^16",
"react-dom": "^16.7.0"
},
"devDependencies": {
"@recore/config": "^2.0.0",
"@types/classnames": "^2.2.7",
"@types/node": "^13.7.1",
"@types/react": "^16",
"@types/react-dom": "^16",
"eslint": "^6.5.1",
"prettier": "^1.18.2",
"tslib": "^1.9.3",
"typescript": "^3.1.3",
"ts-node": "^8.0.1"
},
"ava": {
"compileEnhancements": false,
"snapshotDir": "test/fixtures/__snapshots__",
"extensions": [
"ts"
],
"require": [
"ts-node/register"
]
},
"license": "MIT"
}

View File

@ -0,0 +1,125 @@
@import '../variables.less';
@x-gap: 10px;
@y-gap: 8px;
.lc-field {
// head
.lc-field-head {
display: flex;
align-items: center;
justify-content: space-between;
.lc-field-title {
display: flex;
align-items: center;
}
.lc-field-icon {
margin-right: @x-gap;
transform-origin: center;
transition: transform 0.1s;
}
}
&.lc-inline-field {
display: flex;
align-items: center;
// for top-level style
padding: 8px 10px;
> .lc-field-head {
width: 70px;
margin-right: 1px;
.lc-title-label {
width: 70px;
word-break: break-all;
}
}
> .lc-field-body {
flex: 1;
display: flex;
align-items: center;
}
}
&.lc-block-field, &.lc-accordion-field {
display: block;
&:not(:first-child) {
border-top: 1px solid var(--color-line-normal, @dark-alpha-2);
}
> .lc-field-head {
padding-left: @x-gap;
height: 32px;
display: flex;
align-items: center;
font-weight: 500;
background: var(--color-block-background-shallow, rgba(31,56,88,.06));
border-bottom: 1px solid var(--color-line-normal, @dark-alpha-2);
color: var(--color-title, @white-alpha-2);
user-select: none;
}
> .lc-field-body {
padding: @y-gap @x-gap/2;
}
+ .lc-inline-field {
border-top: 1px solid var(--color-line-normal, @dark-alpha-2);
}
}
&.lc-accordion-field {
// collapsed
&.lc-field-is-collapsed {
> .lc-field-head .lc-field-icon {
transform: rotate(180deg);
}
> .lc-field-body {
display: none;
}
}
// 邻近的保持上下距离
+ .lc-field {
margin-top: @y-gap;
}
}
// 2rd level reset
.lc-field-body {
.lc-inline-field {
padding: @y-gap @x-gap/2 0 @x-gap/2;
&:first-child {
padding-top: 0;
}
+ .lc-accordion-field, +.lc-block-field {
margin-top: @y-gap;
}
}
.lc-field {
border-top: none !important;
}
.lc-accordion-field, .lc-block-field {
> .lc-field-head {
padding-left: @x-gap/2;
background: var(--color-block-background-light);
border-bottom-color: var(--color-line-light);
> .lc-field-icon {
margin-right: @x-gap/2;
}
}
}
// 3rd level field title width should short
.lc-field-body .lc-inline-field {
> .lc-field-head {
width: 50px;
.lc-title-label {
width: 50px;
}
}
}
}
}

View File

@ -0,0 +1,94 @@
import { Component } from 'react';
import classNames from 'classnames';
import { Icon } from '@alifd/next';
import Title, { TitleContent } from '../title';
import './index.less';
export interface FieldProps {
className?: string;
// span
title?: TitleContent;
}
export class Field extends Component<FieldProps> {
private shell: HTMLDivElement | null = null;
private checkIsBlockField() {
if (this.shell) {
const setter = this.shell.lastElementChild!.firstElementChild;
if (setter && setter.classList.contains('lc-block-setter')) {
this.shell.classList.add('lc-block-field');
this.shell.classList.remove('lc-inline-field');
} else {
this.shell.classList.remove('lc-block-field');
this.shell.classList.add('lc-inline-field');
}
}
}
componentDidUpdate() {
this.checkIsBlockField();
}
componentDidMount() {
this.checkIsBlockField();
}
render() {
const { className, children, title } = this.props;
return (
<div ref={shell => (this.shell = shell)} className={classNames('lc-field lc-inline-field', className)}>
{title && (
<div className="lc-field-head">
<div className="lc-field-title">
<Title title={title} />
</div>
</div>
)}
<div className="lc-field-body">{children}</div>
</div>
);
}
}
export interface FieldGroupProps extends FieldProps {
defaultCollapsed?: boolean;
// gap?: number;
onExpandChange?: (collapsed: boolean) => void;
}
export class FieldGroup extends Component<FieldGroupProps> {
state = {
collapsed: this.props.defaultCollapsed,
};
toggleExpand() {
const { onExpandChange } = this.props;
const collapsed = !this.state.collapsed;
this.setState({
collapsed,
});
onExpandChange && onExpandChange(collapsed);
}
render() {
const { className, children, title } = this.props;
return (
<div
className={classNames('lc-field lc-accordion-field', className, {
'lc-field-is-collapsed': this.state.collapsed,
})}
>
{title && (
<div className="lc-field-head" onClick={this.toggleExpand.bind(this)}>
<div className="lc-field-title">
<Title title={title} />
</div>
<Icon className="lc-field-icon" type="arrow-up" size="xs" />
</div>
)}
<div className="lc-field-body">{children}</div>
</div>
);
}
}

View File

@ -0,0 +1,91 @@
import React, { Component } from 'react';
import { Tab, Breadcrumb, Icon } from '@alifd/next';
import { SettingsMain, SettingField, isSettingField } from './main';
import './style.less';
import Title from './title';
import SettingsTab, { registerSetter, createSetterContent, getSetter, createSettingFieldView } from './settings-tab';
export default class SettingsPane extends Component {
private main: SettingsMain;
constructor(props: any) {
super(props);
this.main = new SettingsMain(props.editor);
this.main.onNodesChange(() => {
this.forceUpdate();
});
}
shouldComponentUpdate() {
return false;
}
componentWillUnmount() {
this.main.purge();
}
renderBreadcrumb() {
if (this.main.isMulti) {
return (
<div className="lc-settings-navigator">
{this.main.componentType ? this.main.componentType.icon : <Icon type="ellipsis" />}
<span></span>
</div>
);
}
return (
<div className="lc-settings-navigator">
{this.main.componentType ? this.main.componentType.icon : <Icon type="ellipsis" />}
<Breadcrumb>
<Breadcrumb.Item></Breadcrumb.Item>
<Breadcrumb.Item></Breadcrumb.Item>
<Breadcrumb.Item></Breadcrumb.Item>
</Breadcrumb>
</div>
);
}
render() {
if (this.main.isNone) {
// 未选中节点,提示选中 或者 显示根节点设置
return <div className="lc-settings-pane"></div>;
}
if (!this.main.isSame) {
// todo: future support 获取设置项交集编辑
return <div className="lc-settings-pane"></div>;
}
const { items } = this.main;
if (items.length > 5 || items.some(item => !isSettingField(item) || !item.isGroup)) {
return (
<div className="lc-settings-pane">
{this.renderBreadcrumb()}
<div className="lc-settings-body">
<SettingsTab target={this.main} />
</div>
</div>
);
}
return (
<div className="lc-settings-pane">
<Tab
navClassName="lc-settings-tabs"
animation={false}
contentClassName="lc-settings-tabs-content"
extra={this.renderBreadcrumb()}
>
{(items as SettingField[]).map(field => (
<Tab.Item className="lc-settings-tab-item" title={<Title title={field.title} />} key={field.name}>
<SettingsTab target={field} />
</Tab.Item>
))}
</Tab>
</div>
);
}
}
export { registerSetter, createSetterContent, getSetter, createSettingFieldView };

View File

@ -0,0 +1,424 @@
import { EventEmitter } from 'events';
import { uniqueId } from '../../utils/unique-id';
import { ComponentType } from '../../designer/src/designer/component-type';
import Node from '../../designer/src/designer/document/node/node';
import { TitleContent } from './title';
import { ReactElement, ComponentType as ReactComponentType, isValidElement } from 'react';
import DocumentModel from '../../designer/src/designer/document/document-model';
import { isReactComponent } from '../../utils/is-react';
import Designer from '../../designer/src/designer/designer';
export interface SettingTarget {
// 所设置的节点集,至少一个
readonly nodes: Node[];
readonly componentType: ComponentType | null;
readonly items: Array<SettingField | CustomView>;
/**
*
*/
readonly isSame: boolean;
/**
*
*/
readonly isOne: boolean;
/**
*
*/
readonly isMulti: boolean;
/**
*
*/
readonly isNone: boolean;
/**
*
*/
readonly editor: object;
readonly designer?: Designer;
/**
*
*/
onEffect(action: () => void): () => void;
/*
// 所有属性值数据
readonly props: object;
// 获取属性值
getPropValue(propName: string): any;
// 设置多个属性值,替换原有值
setProps(data: object): void;
// 设置多个属性值,和原有值合并
mergeProps(data: object): void;
// 绑定属性值发生变化时
onPropsChange(fn: () => void): () => void;
// 设置属性值
setPropValue(path: string, value: any) {}
*/
}
export type CustomView = ReactElement | ReactComponentType<any>;
export function isCustomView(obj: any): obj is CustomView {
return obj && (isValidElement(obj) || isReactComponent(obj));
}
export interface SetterConfig {
/**
* if *string* passed must be a registered Setter Name
*/
componentName: string | CustomView;
/**
* the props pass to Setter Component
*/
props?: {
[prop: string]: any;
};
children?: any;
}
/**
* if *string* passed must be a registered Setter Name, future support blockSchema
*/
export type SetterType = SetterConfig | string | CustomView;
export interface FieldExtraProps {
/**
* default value of target prop for setter use
*/
defaultValue?: any;
onChange?: (val: any, field: SettingField, editor: any) => void;
getValue?: (field: SettingField, editor: any) => any;
/**
* the field conditional show, is not set always true
* @default undefined
*/
condition?: (field: SettingField, editor: any) => boolean;
/**
* default collapsed when display accordion
*/
defaultCollapsed?: boolean;
}
export interface FieldConfig extends FieldExtraProps {
type?: 'field' | 'group';
/**
* the name of this setting field, which used in quickEditor
*/
name: string;
/**
* the field title
* @default sameas .name
*/
title?: TitleContent;
/**
* the field body contains when .type = 'field'
*/
setter?: SetterType;
/**
* the setting items which group body contains when .type = 'group'
*/
items?: FieldConfig[];
/**
* extra props for field
*/
extraProps?: FieldExtraProps;
}
export class SettingField implements SettingTarget {
readonly isSettingField = true;
readonly id = uniqueId('field');
readonly type: 'field' | 'virtual-field' | 'group';
readonly isGroup: boolean;
readonly name: string;
readonly title: TitleContent;
readonly editor: any;
readonly extraProps: FieldExtraProps;
readonly setter?: SetterType;
readonly isSame: boolean;
readonly isMulti: boolean;
readonly isOne: boolean;
readonly isNone: boolean;
readonly nodes: Node[];
readonly componentType: ComponentType | null;
readonly designer: Designer;
constructor(readonly parent: SettingTarget, private config: FieldConfig) {
const { type, title, name, items, setter, extraProps, ...rest } = config;
if (type == null) {
const c = name.substr(0, 1);
if (c === '#') {
this.type = 'group';
} else if (c === '!') {
this.type = 'virtual-field';
} else {
this.type = 'field';
}
} else {
this.type = type;
}
// initial self properties
this.name = name;
this.title = title || name;
this.setter = setter;
this.extraProps = {
...rest,
...extraProps,
};
this.isGroup = this.type === 'group';
// copy parent properties
this.editor = parent.editor;
this.nodes = parent.nodes;
this.componentType = parent.componentType;
this.isSame = parent.isSame;
this.isMulti = parent.isMulti;
this.isOne = parent.isOne;
this.isNone = parent.isNone;
this.designer = parent.designer!;
// initial items
if (this.type === 'group' && items) {
this.initItems(items);
}
}
onEffect(action: () => void): () => void {
return this.designer.autorun(action, true);
}
private _items: Array<SettingField | CustomView> = [];
private initItems(items: Array<FieldConfig | CustomView>) {
this._items = items.map((item) => {
if (isCustomView(item)) {
return item;
}
return new SettingField(this, item);
});
}
private disposeItems() {
this._items.forEach(item => isSettingField(item) && item.purge());
this._items = [];
}
get isSameValue() {
return true;
}
get items() {
return this._items;
}
get prop(): SettingTargetProp | void {
if (this.type === 'field') {
}
return;
}
getValue(): any {
return null;
}
setValue(val: any) {
}
// 添加
// addItem(config: FieldConfig): SettingField {}
// 删除
deleteItem() {}
// 移动
insertItem(item: SettingField, index?: number) {}
remove() {}
purge() {
this.disposeItems();
}
}
export function isSettingField(obj: any): obj is SettingField {
return obj && obj.isSettingField;
}
export class SettingTargetProp {}
export class SettingsMain implements SettingTarget {
private emitter = new EventEmitter();
private _nodes: Node[] = [];
private _items: Array<SettingField | CustomView> = [];
private _sessionId = '';
private _componentType: ComponentType | null = null;
private _isSame: boolean = true;
get nodes(): Node[] {
return this._nodes;
}
get componentType() {
return this._componentType;
}
get items() {
return this._items;
}
/**
*
*/
get isSame(): boolean {
return this._isSame;
}
/**
*
*/
get isOne(): boolean {
return this.nodes.length === 1;
}
/**
*
*/
get isMulti(): boolean {
return this.nodes.length > 1;
}
/**
*
*/
get isNone() {
return this.nodes.length < 1;
}
private disposeListener: () => void;
private _designer?: Designer;
get designer() {
return this._designer;
}
constructor(readonly editor: any) {
let selectionChangeDispose: any = null;
const setupDoc = (doc: DocumentModel) => {
if (selectionChangeDispose) {
selectionChangeDispose();
selectionChangeDispose = null;
}
if (doc) {
if (!this._designer) {
this._designer = doc.designer;
}
const selection = doc.selection;
this.setup(selection.getNodes());
selectionChangeDispose = doc.selection.onSelectionChange(() => {
this.setup(selection.getNodes());
});
} else {
this.setup([]);
}
};
const activedDispose = editor.on('designer.actived-document-change', setupDoc);
if (editor.designer) {
setupDoc(editor.designer.project.activedDocument);
}
this.disposeListener = () => {
if (selectionChangeDispose) {
selectionChangeDispose();
}
activedDispose();
};
}
onEffect(action: () => void): () => void {
action();
return this.onNodesChange(action);
}
private setup(nodes: Node[]) {
this._nodes = nodes;
// check nodes change
const sessionId = this.nodes
.map(node => node.id)
.sort()
.join(',');
if (sessionId === this._sessionId) {
return;
}
this._sessionId = sessionId;
// setups
this.setupComponentType();
// todo: enhance that componentType not changed
// clear fields
this.setupItems();
// emit change
this.emitter.emit('nodeschange');
}
private disposeItems() {
this._items.forEach(item => isSettingField(item) && item.purge());
this._items = [];
}
private setupComponentType() {
if (this.nodes.length < 1) {
this._isSame = false;
this._componentType = null;
return;
}
const first = this.nodes[0];
const type = first.componentType;
const l = this.nodes.length;
let theSame = true;
for (let i = 1; i < l; i++) {
const other = this.nodes[i];
if ((other as any).componentType !== type) {
theSame = false;
break;
}
}
if (theSame) {
this._isSame = true;
this._componentType = type;
}
}
private setupItems() {
this.disposeItems();
if (this.componentType) {
this._items = this.componentType.configure.map(item => {
if (isCustomView(item)) {
return item;
}
return new SettingField(this, item as any);
});
}
}
onNodesChange(fn: () => void): () => void {
this.emitter.on('nodeschange', fn);
return () => {
this.emitter.removeListener('nodeschange', fn);
};
}
purge() {
this.disposeListener();
this.disposeItems();
this.emitter.removeAllListeners();
}
}

View File

@ -0,0 +1,215 @@
import { Component, isValidElement, ReactNode, ReactElement, ComponentType as ReactComponentType } from 'react';
import { isReactClass } from '../../utils/is-react';
import { createContent } from '../../utils/create-content';
import { SettingField, CustomView, isSettingField, SettingTarget } from './main';
import { Field, FieldGroup } from './field';
const settersMap = new Map<string, ReactElement | ReactComponentType<any>>();
export function registerSetter(type: string, setter: ReactElement | ReactComponentType<any>) {
settersMap.set(type, setter);
}
export function getSetter(type: string): ReactElement | ReactComponentType<any> | null {
return settersMap.get(type) || null;
}
export function createSetterContent(setter: any, props: object): ReactNode {
if (typeof setter === 'string') {
setter = getSetter(setter);
}
return createContent(setter, props);
}
class SettingFieldView extends Component<{ field: SettingField }> {
state = {
visible: false,
value: null,
};
private dispose: () => void;
constructor(props: any) {
super(props);
const { field } = this.props;
const { condition } = field.extraProps;
let firstRun: boolean = true;
this.dispose = field.onEffect(() => {
const state: any = {};
state.visible = (field.isOne && typeof condition === 'function') ? !condition(field, field.editor) : true;
if (state.visible) {
state.value = field.getValue();
}
if (firstRun) {
firstRun = false;
this.state = state;
} else {
this.setState(state);
}
});
}
shouldComponentUpdate(_: any, nextState: any) {
if (nextState.value !== this.state.value || nextState.visible !== this.state.visible) {
return true;
}
return false;
}
componentWillUnmount() {
this.dispose();
}
render() {
const { field } = this.props;
const { setter, title, extraProps, isSameValue } = field;
const { defaultValue } = extraProps;
const { visible, value } = this.state;
// reaction point
if (!visible) {
return null;
}
let setterType = setter;
let props: any = {};
if (typeof setter === 'object' && 'componentName' in setter && !(isValidElement(setter) || isReactClass(setter))) {
setterType = (setter as any).componentName;
props = (setter as any).props;
}
if (defaultValue != null && !('defaultValue' in props)) {
props.defaultValue = defaultValue;
}
if (!('placeholder' in props) && !isSameValue) {
props.placeholder = '多种值';
}
// todo: error handling
return (
<Field title={title}>
{createSetterContent(setterType, {
...props,
key: field.id,
// === injection
prop: field,
field,
// === IO
value, // reaction point
onChange: (value: any) => {
field.setValue(value);
}
})}
</Field>
);
}
}
class SettingGroupView extends Component<{ field: SettingField }> {
state = {
visible: false,
items: [],
};
private dispose: () => void;
constructor(props: any) {
super(props);
const { field } = this.props;
const { condition } = field.extraProps;
let firstRun: boolean = true;
this.dispose = field.onEffect(() => {
const state: any = {};
state.visible = (field.isOne && typeof condition === 'function') ? !condition(field, field.editor) : true;
if (state.visible) {
state.items = field.items.slice();
}
if (firstRun) {
firstRun = false;
this.state = state;
} else {
this.setState(state);
}
});
}
shouldComponentUpdate(_: any, nextState: any) {
if (nextState.items !== this.state.items || nextState.visible !== this.state.visible) {
return true;
}
return false;
}
componentWillUnmount() {
this.dispose();
}
render() {
const { field } = this.props;
const { title, extraProps } = field;
const { defaultCollapsed } = extraProps;
const { visible, items } = this.state;
// reaction point
if (!visible) {
return null;
}
return (
<FieldGroup title={title} defaultCollapsed={defaultCollapsed}>
{items.map((item, index) => createSettingFieldView(item, field, index))}
</FieldGroup>
);
}
}
export function createSettingFieldView(item: SettingField | CustomView, field: SettingTarget, index: number) {
if (isSettingField(item)) {
if (item.isGroup) {
return <SettingGroupView field={item} key={item.id} />;
} else {
return <SettingFieldView field={item} key={item.id} />;
}
} else {
return createContent(item, { key: index, field, editor: field.editor });
}
}
export default class SettingsTab extends Component<{ target: SettingTarget }> {
state: { items: Array<SettingField | CustomView> } = {
items: [],
};
private dispose: () => void;
constructor(props: any) {
super(props);
const { target } = this.props;
let firstRun: boolean = true;
this.dispose = target.onEffect(() => {
const state = {
items: target.items.slice(),
};
if (firstRun) {
firstRun = false;
this.state = state;
} else {
this.setState(state);
}
});
}
shouldComponentUpdate(_: any, nextState: any) {
if (nextState.items !== this.state.items) {
return true;
}
return false;
}
componentWillUnmount() {
this.dispose();
}
render() {
const { items } = this.state;
const { target } = this.props;
return (
<div className="lc-settings-singlepane">
{items.map((item, index) => createSettingFieldView(item, target, index))}
</div>
);
}
}

View File

@ -0,0 +1,100 @@
:root {
--color-brand: #006cff;
--color-brand-light: #197aff;
--color-brand-dark: #0060e5;
--color-icon-normal: rgba(31, 56, 88, 0.4);
--color-icon-hover: rgba(31, 56, 88, 0.6);
--color-icon-active: #006cff;
--color-icon-reverse: #ffffff;
--color-line-light: rgba(31, 56, 88, 0.05);
--color-line-normal: rgba(31, 56, 88, 0.1);
--color-line-darken: rgba(18, 32, 50, 0.1);
--color-title: rgba(0, 0, 0, 0.8);
--color-text: rgba(0, 0, 0, 0.6);
--color-text-dark: rgba(0, 0, 0, 0.6);
--color-text-light: rgba(26, 26, 26, 0.6);
--color-text-reverse: rgba(255, 255, 255, 0.8);
--color-text-regular: rgba(31, 56, 88, 0.8);
--color-field-label: rgba(0, 0, 0, 0.4);
--color-field-text: rgba(0, 0, 0, 0.6);
--color-field-placeholder: rgba(31, 56, 88, 0.3);
--color-field-border: rgba(31, 56, 88, 0.3);
--color-field-border-hover: rgba(31, 56, 88, 0.4);
--color-field-border-active: rgba(31, 56, 88, 0.6);
--color-field-background: #ffffff;
--color-pane-background: #ffffff;
--color-block-background-normal: #ffffff;
--color-block-background-light: rgba(31, 56, 88, 0.03);
--color-block-background-shallow: rgba(31, 56, 88, 0.06);
--color-block-background-dark: rgba(31, 56, 88, 0.1);
--color-block-background-disabled: rgba(31, 56, 88, 0.2);
--color-block-background-deep-dark: #BAC3CC;
}
.lc-settings-pane {
position: relative;
.lc-settings-navigator {
height: 30px;
display: flex;
align-items: center;
padding-left: 5px;
border-bottom: 1px solid var(--color-line-normal);
}
.lc-settings-body {
position: absolute;
top: 30px;
right: 0;
left: 0;
bottom: 0;
overflow-y: auto;
}
.lc-settings-singlepane {
padding-bottom: 50px;
}
// ====== reset fusion-tabs =====
.lc-settings-tabs {
position: relative;
overflow: visible;
> .next-tabs-nav-extra {
position: absolute !important;
top: 40px !important;
left: 0 !important;
height: 30px;
right: 0;
transform: none !important;
}
.next-tabs-nav-container {
.next-tabs-nav {
display: flex;
.next-tabs-tab.lc-settings-tab-item {
flex: 1;
min-width: 0;
.next-tabs-tab-inner {
text-align: center;
}
}
}
}
}
.lc-settings-tabs-content {
position: absolute;
top: 70px;
left:0;
right: 0;
bottom: 0;
.next-tabs-tabpane {
position: absolute;
top: 0;
right: 0;
left: 0;
bottom: 0;
overflow-y: auto;
}
}
}

View File

@ -0,0 +1,23 @@
import { uniqueId } from '../../../utils/unique-id';
import { Component, ReactNode } from 'react';
import { saveTips } from './tip-handler';
export interface TipConfig {
className?: string;
children?: ReactNode;
theme?: string;
direction?: string; // 'n|s|w|e|top|bottom|left|right';
}
export default class EmbedTip extends Component<TipConfig> {
private id = uniqueId('tips$');
componentWillUnmount() {
saveTips(this.id, null);
}
render() {
saveTips(this.id, this.props);
return <meta data-role="tip" data-tip-id={this.id} />;
}
}

View File

@ -0,0 +1,4 @@
import './style.less';
export { default as EmbedTip } from './embed-tip';
export { default as TipContainer } from './tip-container';

View File

@ -0,0 +1,215 @@
@keyframes shake {
from,
to {
margin: 0;
}
20%,
60% {
margin: 0 10px 0 -10px;
}
40%,
80% {
margin: 0 -10px 0 10px;
}
}
@keyframes drop {
from {
transform: translateY(-100%);
}
to {
transform: translateY(0);
}
}
@keyframes appear-left {
from {
transform: translateX(8px);
opacity: 0.8;
}
to {
transform: translateX(0);
opacity: 1;
}
}
@keyframes appear-right {
from {
transform: translateX(-8px);
opacity: 0.8;
}
to {
transform: translateX(0);
opacity: 1;
}
}
@keyframes appear-top {
from {
transform: translateY(8px);
opacity: 0.8;
}
to {
transform: translateY(0);
opacity: 1;
}
}
@keyframes appear-bottom {
from {
transform: translateY(-8px);
opacity: 0.8;
}
to {
transform: translateY(0);
opacity: 1;
}
}
@keyframes scale {
from {
transform: scale(0.9);
}
to {
transform: scale(1);
}
}
@keyframes spining {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
@keyframes pulse {
from,
to {
transform: scale(1);
opacity: 0.7;
}
50% {
transform: scale(1.02);
opacity: 1;
}
}
.lc-arrow {
position: absolute;
width: 36px;
height: 10px;
box-sizing: border-box;
overflow: hidden;
&:after {
content: '';
display: block;
width: 0;
height: 0;
margin: 0 auto;
border: 8px solid transparent;
border-top-color: var(--color-pane-background, rgb(255, 255, 255));
}
transform-origin: 0 0;
}
.lc-align-top > .lc-arrow {
bottom: 0;
left: 0;
transform: translateY(100%);
}
.lc-align-right > .lc-arrow {
left: 0;
top: 0;
transform: rotate(90deg);
}
.lc-align-left > .lc-arrow {
right: 0;
top: 0;
transform-origin: right top;
transform: rotate(-90deg);
}
.lc-align-bottom > .lc-arrow {
top: 0;
left: 0;
transform: scaleY(-1);
}
.lc-tip {
z-index: 2;
position: fixed;
box-sizing: border-box;
background: #57a672;
max-height: 400px;
color: var(--color-text-reverse, rgba(255, 255, 255, 0.8));
left: 0;
top: 0;
visibility: hidden;
opacity: 0;
border-radius: 3px;
padding: 6px 8px;
text-shadow: 0 -1px rgba(0, 0, 0, 0.3);
font-size: var(--font-size-text);
line-height: 14px;
max-width: 200px;
pointer-events: none;
&.lc-align-top {
transform: translateY(8px);
}
&.lc-align-bottom {
transform: translateY(-8px);
}
&.lc-align-left {
transform: translateX(8px);
}
&.lc-align-right {
transform: translateX(-8px);
}
.lc-arrow {
width: 24px;
height: 8px;
&:after {
border: 6px solid transparent;
border-top-color: #57a672;
}
}
&.lc-theme-black {
background: rgba(0, 0, 0, 0.7);
.lc-arrow:after {
border-top-color: rgba(0, 0, 0, 0.7);
}
}
&.lc-theme-green {
background: #57a672;
.lc-arrow:after {
border-top-color: #57a672;
}
}
&.lc-visible {
visibility: visible;
}
&.lc-visible-animate {
visibility: visible;
opacity: 1;
transition: transform ease-out 200ms, opacity ease-out 200ms;
}
will-change: transform, width, height, opacity, left, top;
}
.lc-tips-container {
pointer-events: none;
position: fixed;
top: 0;
left: 0;
overflow: visible;
z-index: 2000;
}

View File

@ -0,0 +1,36 @@
import { Component } from 'react';
import Tip from './tip';
import tipHandler from './tip-handler';
export default class TipContainer extends Component {
shouldComponentUpdate() {
return false;
}
private dispose?: () => void;
componentDidMount() {
const over = (e: MouseEvent) => tipHandler.setTarget(e.target as any);
const down = () => tipHandler.hideImmediately();
document.addEventListener('mouseover', over, false);
document.addEventListener('mousedown', down, true);
this.dispose = () => {
document.removeEventListener('mouseover', over, false);
document.removeEventListener('mousedown', down, true);
};
}
componentWillMount() {
if (this.dispose) {
this.dispose();
}
}
render() {
return (
<div className="lc-tips-container">
<Tip />
</div>
);
}
}

View File

@ -0,0 +1,139 @@
import { TipConfig } from './embed-tip';
import { EventEmitter } from 'events';
export interface TipOptions extends TipConfig {
target: HTMLElement;
}
function findTip(target: HTMLElement | null): TipOptions | null {
if (!target) {
return null;
}
// optimize deep finding on mouseover
let loopupLimit = 10;
while (target && loopupLimit-- > 0) {
// get tip from target node
if (target.dataset && target.dataset.tip) {
return {
children: target.dataset.tip,
direction: target.dataset.direction || target.dataset.dir,
theme: target.dataset.theme,
target,
};
}
// or get tip from child nodes
let child: HTMLElement | null = target.lastElementChild as HTMLElement;
while (child) {
if (child.dataset && child.dataset.role === 'tip') {
const tipId = child.dataset.tipId;
if (!tipId) {
return null;
}
const tipProps = tipsMap.get(tipId);
if (!tipProps) {
return null;
}
return {
...tipProps,
target,
};
}
child = child.previousElementSibling as HTMLElement;
}
target = target.parentNode as HTMLElement;
}
return null;
}
class TipHandler {
tip: TipOptions | null = null;
private showDelay: number | null = null;
private hideDelay: number | null = null;
private emitter = new EventEmitter();
setTarget(target: HTMLElement) {
const tip = findTip(target);
if (tip) {
if (this.tip) {
// the some target should return
if (this.tip.target === tip.target) {
this.tip = tip;
return;
}
// not show already, reset show delay
if (this.showDelay) {
clearTimeout(this.showDelay);
this.showDelay = null;
this.tip = null;
} else {
if (this.hideDelay) {
clearTimeout(this.hideDelay);
this.hideDelay = null;
}
this.tip = tip;
this.emitter.emit('tipchange');
return;
}
}
this.tip = tip;
if (this.hideDelay) {
clearTimeout(this.hideDelay);
this.hideDelay = null;
this.emitter.emit('tipchange');
} else {
this.showDelay = setTimeout(() => {
this.showDelay = null;
this.emitter.emit('tipchange');
}, 350) as any;
}
} else {
if (this.showDelay) {
clearTimeout(this.showDelay);
this.showDelay = null;
} else {
this.hideDelay = setTimeout(() => {
this.hideDelay = null;
}, 100) as any;
}
this.tip = null;
this.emitter.emit('tipchange');
}
}
hideImmediately() {
if (this.hideDelay) {
clearTimeout(this.hideDelay);
this.hideDelay = null;
}
if (this.showDelay) {
clearTimeout(this.showDelay);
this.showDelay = null;
}
this.tip = null;
this.emitter.emit('tipchange');
}
onChange(func: () => void) {
this.emitter.on('tipchange', func);
return () => {
this.emitter.removeListener('tipchange', func);
};
}
}
const tipsMap = new Map<string, TipConfig>();
export function saveTips(id: string, props: TipConfig | null) {
if (props) {
tipsMap.set(id, props);
} else {
tipsMap.delete(id);
}
}
export default new TipHandler();

View File

@ -0,0 +1,120 @@
import { Component } from 'react';
import classNames from 'classnames';
import { resolvePosition } from './utils';
import tipHandler from './tip-handler';
export default class Tip extends Component {
private dispose?: () => void;
constructor(props: any) {
super(props);
this.dispose = tipHandler.onChange(() => this.forceUpdate());
}
shouldComponentUpdate() {
return false;
}
componentDidMount() {
this.updateTip();
}
componentDidUpdate() {
this.updateTip();
}
componentWillUnmount() {
if (this.dispose) {
this.dispose();
}
this.clearTimer();
}
private timer: number | null = null;
clearTimer() {
if (this.timer) {
clearTimeout(this.timer);
this.timer = null;
}
}
private shell: HTMLDivElement | null = null;
private originClassName: string = '';
updateTip() {
if (!this.shell) {
return;
}
const shell = this.shell;
const arrow = shell.querySelector('.lc-arrow') as HTMLElement;
// reset
shell.className = this.originClassName;
shell.style.cssText = '';
arrow.style.cssText = '';
this.clearTimer();
const tip = tipHandler.tip;
if (!tip) {
return;
}
const { target, direction } = tip;
const targetRect = target.getBoundingClientRect();
if (targetRect.width === 0 || targetRect.height === 0) {
return;
}
const shellRect = shell.getBoundingClientRect();
const bounds = {
left: 1,
top: 1,
right: document.documentElement.clientWidth - 1,
bottom: document.documentElement.clientHeight - 1,
};
const arrowRect = arrow.getBoundingClientRect();
const { dir, left, top, arrowLeft, arrowTop } = resolvePosition(
shellRect,
targetRect,
arrowRect,
bounds,
direction,
);
shell.classList.add(`lc-align-${dir}`);
shell.style.top = `${top}px`;
shell.style.left = `${left}px`;
shell.style.width = `${shellRect.width}px`;
shell.style.height = `${shellRect.height}px`;
if (dir === 'top' || dir === 'bottom') {
arrow.style.left = `${arrowLeft}px`;
} else {
arrow.style.top = `${arrowTop}px`;
}
this.timer = window.setTimeout(() => {
shell.classList.add('lc-visible-animate');
shell.style.transform = 'none';
}, 10); /**/
}
render() {
const tip: any = tipHandler.tip || {};
const className = classNames('lc-tip', tip.className, tip && tip.theme ? `lc-theme-${tip.theme}` : null);
this.originClassName = className;
return (
<div
className={className}
ref={ref => {
this.shell = ref;
}}
>
<i className="lc-arrow" />
<div className="lc-tip-content">{tip.children}</div>
</div>
);
}
}

View File

@ -0,0 +1,234 @@
function resolveEdge(popup: any, target: any, arrow: any, bounds: any) {
const sx = arrow.width > target.width ? (arrow.width - target.width) / 2 : 0;
const sy = arrow.width > target.height ? (arrow.width - target.height) / 2 : 0;
const top = Math.max(target.top - popup.height + arrow.width - sy, bounds.top);
const right = Math.min(target.right + popup.width - arrow.width + sx, bounds.right);
const bottom = Math.min(target.bottom + popup.height - arrow.width + sy, bounds.bottom);
const left = Math.max(target.left - popup.width + arrow.width - sx, bounds.left);
return { top, right, bottom, left };
}
function resolveDirection(popup: any, target: any, edge: any, bounds: any, prefers: any) {
if (prefers.forceDirection) {
return prefers.dir;
}
const extendWidth = popup.width + popup.extraOffset;
const extendHeight = popup.height + popup.extraOffset;
const SY = popup.width * extendHeight;
const SX = popup.height * extendWidth;
const mw = Math.min(edge.right - edge.left, popup.width);
const mh = Math.min(edge.bottom - edge.top, popup.height);
const mat: any = {
top: () => {
const s = mw * Math.min(target.top - bounds.top, extendHeight);
return { s, enough: s >= SY };
},
bottom: () => {
const s = mw * Math.min(bounds.bottom - target.bottom, extendHeight);
return { s, enough: s >= SY };
},
left: () => {
const s = mh * Math.min(target.left - bounds.left, extendWidth);
return { s, enough: s >= SX };
},
right: () => {
const s = mh * Math.min(bounds.right - target.right, extendWidth);
return { s, enough: s >= SX };
},
};
const orders = ['top', 'right', 'bottom', 'left'];
if (prefers.dir) {
const i = orders.indexOf(prefers.dir);
if (i > -1) {
orders.splice(i, 1);
orders.unshift(prefers.dir);
}
}
let ms = 0;
let prefer = orders[0];
for (let i = 0, l = orders.length; i < l; i++) {
const dir = orders[i];
const { s, enough } = mat[dir]();
if (enough) {
return dir;
}
if (s > ms) {
ms = s;
prefer = dir;
}
}
return prefer;
}
function resolvePrefer(prefer: any) {
if (!prefer) {
return {};
}
const force = prefer[0] === '!';
if (force) {
prefer = prefer.substr(1);
}
let [dir, offset] = prefer.split(/\s+/);
let forceDirection = false;
let forceOffset = false;
if (dir === 'center') {
dir = 'auto';
if (!offset) {
offset = 'center';
}
}
if (force) {
if (dir && dir !== 'auto') {
forceDirection = true;
}
if (offset && offset !== 'auto') {
forceOffset = true;
}
}
return { dir, offset, forceDirection, forceOffset };
}
export function resolvePosition(popup: any, target: any, arrow: any, bounds: any, prefer: any) {
popup = {
extraOffset: arrow.height,
top: popup.top,
right: popup.right,
left: popup.left,
bottom: popup.bottom,
height: popup.height,
width: popup.width,
};
const prefers = resolvePrefer(prefer);
const edge = resolveEdge(popup, target, arrow, bounds);
// 选择方向
const dir = resolveDirection(popup, target, edge, bounds, prefers);
let top;
let left;
let arrowTop;
let arrowLeft;
// 或得该方位上横向 或 纵向的 偏移
if (dir === 'top' || dir === 'bottom') {
if (dir === 'top') {
top = target.top - popup.extraOffset - popup.height;
} else {
top = target.bottom + popup.extraOffset;
}
// 解决横向偏移
const offset = arrow.width > target.width ? (arrow.width - target.width) / 2 : 0;
const minLeft = target.left + arrow.width - offset - popup.width;
const maxLeft = target.right - arrow.width + offset;
const centerLeft = target.left - (popup.width - target.width) / 2;
if (prefers.offset === 'left') {
left = minLeft;
} else if (prefers.offset === 'right') {
left = maxLeft;
} else {
left = centerLeft;
}
if (!prefers.forceOffset) {
left = Math.max(Math.min(edge.right - popup.width, left), minLeft);
left = Math.min(Math.max(edge.left, left), maxLeft);
}
arrowLeft = Math.min(popup.width - arrow.width, Math.max(target.left - (arrow.width - target.width) / 2 - left, 0));
} else {
if (dir === 'left') {
left = target.left - popup.extraOffset - popup.width;
} else {
left = target.right + popup.extraOffset;
}
// 解决纵向偏移
const offset = arrow.width > target.height ? (arrow.width - target.height) / 2 : 0;
const minTop = target.top + arrow.width - offset - popup.height;
const maxTop = target.bottom - arrow.width + offset;
const centerTop = target.top - (popup.height - target.height) / 2;
if (prefers.offset === 'top') {
top = minTop;
} else if (prefers.offset === 'bottom') {
top = maxTop;
} else {
top = centerTop;
}
if (!prefers.forceOffset) {
top = Math.max(Math.min(edge.bottom - popup.height, top), minTop);
top = Math.min(Math.max(edge.top, top), maxTop);
}
arrowTop = Math.min(popup.height - arrow.height, Math.max(target.top - (arrow.width - target.height) / 2 - top, 0));
}
return { dir, left, top, arrowLeft, arrowTop };
}
const percentPresets: any = {
right: 1,
left: 0,
top: 0,
bottom: 1,
center: 0.5,
};
function isPercent(val: any) {
return /^[\d.]+%$/.test(val);
}
function resolveRelativeValue(val: any, offset: any, total: any) {
if (!val) {
val = 0;
} else if (isPercent(val)) {
val = (parseFloat(val) / 100) * total;
} else if (percentPresets.hasOwnProperty(val)) {
val = percentPresets[val] * total;
} else {
val = parseFloat(val);
if (isNaN(val)) {
val = 0;
}
}
return `${val + offset}px`;
}
export function resolveRelativePosition(align: any, popup: any, bounds: any) {
if (!align) {
// return default position
return {
top: '38.2%',
left: 'calc(50% - 110px)',
};
}
let [xAlign, yAlign] = align.trim().split(/\s+/);
if (xAlign === 'top' || xAlign === 'bottom' || yAlign === 'left' || yAlign === 'right') {
const tmp = xAlign;
xAlign = yAlign;
yAlign = tmp;
}
if (xAlign === 'center' && !yAlign) {
yAlign = 'center';
}
return {
left: resolveRelativeValue(xAlign, 0, bounds.right - bounds.left - popup.width),
top: resolveRelativeValue(yAlign, 0, bounds.bottom - bounds.top - popup.height),
};
}

View File

@ -0,0 +1,61 @@
import { Component, isValidElement, ReactElement, ReactNode } from 'react';
import { Icon } from '@alifd/next';
import classNames from 'classnames';
import EmbedTip, { TipConfig } from '../tip/embed-tip';
import './title.less';
export interface IconConfig {
type: string;
size?: number | "small" | "xxs" | "xs" | "medium" | "large" | "xl" | "xxl" | "xxxl" | "inherit";
className?: string;
}
export interface TitleConfig {
label?: ReactNode;
tip?: string | ReactElement | TipConfig;
icon?: string | ReactElement | IconConfig;
className?: string;
}
export type TitleContent = string | ReactElement | TitleConfig;
export default class Title extends Component<{ title: TitleContent; onClick?: () => void }> {
render() {
let { title } = this.props;
if (isValidElement(title)) {
return title;
}
if (typeof title === 'string') {
title = { label: title }; // tslint:disable-line
}
let icon = null;
if (title.icon) {
if (isValidElement(title.icon)) {
icon = title.icon;
} else {
const iconProps = typeof title.icon === 'string' ? { type: title.icon } : title.icon;
icon = <Icon {...iconProps} />;
}
}
let tip: any = null;
if (title.tip) {
if (isValidElement(title.tip) && title.tip.type === EmbedTip) {
tip = title.tip;
} else {
const tipProps =
typeof title.tip === 'object' && !isValidElement(title.tip) ? title.tip : { children: title.tip };
tip = <EmbedTip direction="top" theme="black" {...tipProps} />;
}
}
return (
<div className={classNames('lc-title', title.className)} onClick={this.props.onClick}>
{icon ? <div className="lc-title-icon">{icon}</div> : null}
{title.label ? <span className="lc-title-label">{title.label}</span> : null}
{tip}
</div>
);
}
}

View File

@ -0,0 +1,14 @@
.lc-title {
display: inline-flex;
align-items: center;
color: var(--color-text);
.lc-title-icon {
display: flex;
align-items: center;
margin-right: 4px;
}
}
.actived .lc-title {
color: var(--color-actived);
}

View File

@ -0,0 +1,170 @@
/*
* 基础的 DPL 定义使用了 kuma base 的定义,参考:
* https://github.com/uxcore/kuma-base/tree/master/variables
*/
/**
* ===========================================================
* ==================== Font Family ==========================
* ===========================================================
*/
/*
* @font-family: "STHeiti", "Microsoft Yahei", "Lucida Grande", "Lucida Sans Unicode", Helvetica, Arial, Verdana, sans-serif;
*/
@font-family: 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', Helvetica, Arial, sans-serif;
@font-family-code: Monaco, 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', Helvetica, Arial, sans-serif;
/**
* ===========================================================
* ===================== Color DPL ===========================
* ===========================================================
*/
@brand-color-1: rgba(0, 108, 255, 1);
@brand-color-2: rgba(25, 122, 255, 1);
@brand-color-3: rgba(0, 96, 229, 1);
@brand-color-1-3: rgba(0, 108, 255, 0.6);
@brand-color-1-4: rgba(0, 108, 255, 0.4);
@brand-color-1-5: rgba(0, 108, 255, 0.3);
@brand-color-1-6: rgba(0, 108, 255, 0.2);
@brand-color-1-7: rgba(0, 108, 255, 0.1);
@brand-color: @brand-color-1;
@white-alpha-1: rgb(255, 255, 255); // W-1
@white-alpha-2: rgba(255, 255, 255, 0.8); // W-2 A80
@white-alpha-3: rgba(255, 255, 255, 0.6); // W-3 A60
@white-alpha-4: rgba(255, 255, 255, 0.4); // W-4 A40
@white-alpha-5: rgba(255, 255, 255, 0.3); // W-5 A30
@white-alpha-6: rgba(255, 255, 255, 0.2); // W-6 A20
@white-alpha-7: rgba(255, 255, 255, 0.1); // W-7 A10
@white-alpha-8: rgba(255, 255, 255, 0.06); // W-8 A6
@dark-alpha-1: rgba(0, 0, 0, 1); // D-1 A100
@dark-alpha-2: rgba(0, 0, 0, 0.8); // D-2 A80
@dark-alpha-3: rgba(0, 0, 0, 0.6); // D-3 A60
@dark-alpha-4: rgba(0, 0, 0, 0.4); // D-4 A40
@dark-alpha-5: rgba(0, 0, 0, 0.3); // D-5 A30
@dark-alpha-6: rgba(0, 0, 0, 0.2); // D-6 A20
@dark-alpha-7: rgba(0, 0, 0, 0.1); // D-7 A10
@dark-alpha-8: rgba(0, 0, 0, 0.06); // D-8 A6
@dark-alpha-9: rgba(0, 0, 0, 0.04); // D-9 A4
@normal-alpha-1: rgba(31, 56, 88, 1); // N-1 A100
@normal-alpha-2: rgba(31, 56, 88, 0.8); // N-2 A80
@normal-alpha-3: rgba(31, 56, 88, 0.6); // N-3 A60
@normal-alpha-4: rgba(31, 56, 88, 0.4); // N-4 A40
@normal-alpha-5: rgba(31, 56, 88, 0.3); // N-5 A30
@normal-alpha-6: rgba(31, 56, 88, 0.2); // N-6 A20
@normal-alpha-7: rgba(31, 56, 88, 0.1); // N-7 A10
@normal-alpha-8: rgba(31, 56, 88, 0.06); // N-8 A6
@normal-alpha-9: rgba(31, 56, 88, 0.04); // N-9 A4
@normal-3: #77879c;
@normal-4: #a3aebd;
@normal-5: #bac3cc;
@normal-6: #d1d7de;
@gray-dark: #333; // N2_4
@gray: #666; // N2_3
@gray-light: #999; // N2_2
@gray-lighter: #ccc; // N2_1
@brand-secondary: #2c2f33; // B2_3
// 补色
@brand-complement: #00b3e8; // B3_1
// 复合
@brand-comosite: #00c587; // B3_2
// 浓度
@brand-deep: #73461d; // B3_3
// F1-1
@brand-danger: rgb(240, 70, 49);
// F1-2 (10% white)
@brand-danger-hover: rgba(240, 70, 49, 0.9);
// F1-3 (5% black)
@brand-danger-focus: rgba(240, 70, 49, 0.95);
// F2-1
@brand-warning: rgb(250, 189, 14);
// F3-1
@brand-success: rgb(102, 188, 92);
// F4-1
@brand-link: rgb(102, 188, 92);
// F4-2
@brand-link-hover: #2e76a6;
// F1-1-7 A10
@brand-danger-alpha-7: rgba(240, 70, 49, 0.9);
// F1-1-8 A6
@brand-danger-alpha-8: rgba(240, 70, 49, 0.8);
// F2-1-2 A80
@brand-warning-alpha-2: rgba(250, 189, 14, 0.8);
// F2-1-7 A10
@brand-warning-alpha-7: rgba(250, 189, 14, 0.9);
// F3-1-2 A80
@brand-success-alpha-2: rgba(102, 188, 92, 0.8);
// F3-1-7 A10
@brand-success-alpha-7: rgba(102, 188, 92, 0.9);
// F4-1-7 A10
@brand-link-alpha-7: rgba(102, 188, 92, 0.9);
// 文本色
@text-primary-color: @dark-alpha-3;
@text-secondary-color: @normal-alpha-3;
@text-thirdary-color: @dark-alpha-4;
@text-disabled-color: @normal-alpha-5;
@text-helper-color: @dark-alpha-4;
@text-danger-color: @brand-danger;
@text-ali-color: #ec6c00;
/**
* ===========================================================
* =================== Shadow Box ============================
* ===========================================================
*/
@box-shadow-1: 0 1px 4px 0 rgba(31, 56, 88, 0.15); // 1 级阴影,物体由原来存在于底面的物体展开,物体和底面关联紧密
@box-shadow-2: 0 2px 10px 0 rgba(31, 56, 88, 0.15); // 2 级阴影hover状态物体层级较高
@box-shadow-3: 0 4px 15px 0 rgba(31, 56, 88, 0.15); // 3 级阴影,当物体层级高于所有界面元素,弹窗用
/**
* ===========================================================
* ================= FontSize of Level =======================
* ===========================================================
*/
@fontSize-1: 26px;
@fontSize-2: 20px;
@fontSize-3: 16px;
@fontSize-4: 14px;
@fontSize-5: 12px;
@fontLineHeight-1: 38px;
@fontLineHeight-2: 30px;
@fontLineHeight-3: 26px;
@fontLineHeight-4: 24px;
@fontLineHeight-5: 20px;
/**
* ===========================================================
* ================= FontSize of Level =======================
* ===========================================================
*/
@global-border-radius: 3px;
@input-border-radius: 3px;
@popup-border-radius: 6px;
/**
* ===========================================================
* ===================== Transistion =========================
* ===========================================================
*/
@transition-duration: 0.3s;
@transition-ease: cubic-bezier(0.23, 1, 0.32, 1);
@transition-delay: 0s;

View File

@ -0,0 +1,9 @@
{
"extends": "./node_modules/@recore/config/tsconfig",
"compilerOptions": {
"experimentalDecorators": true
},
"include": [
"./src/"
]
}

View File

@ -1 +0,0 @@
样式面板

View File

@ -1,17 +1,13 @@
import { ReactNode, ComponentType, isValidElement, cloneElement, createElement, ReactElement } from 'react';
import { isReactClass } from './is-react';
import { ReactNode, ComponentType, isValidElement, cloneElement, createElement } from 'react';
import { isReactComponent } from './is-react';
export function createContent(content: ReactNode | ComponentType<any>, props?: object): ReactNode {
if (isValidElement(content)) {
return props ? cloneElement(content, props) : content;
}
if (isReactClass(content)) {
if (isReactComponent(content)) {
return createElement(content, props);
}
if (typeof content === 'function') {
return content(props) as ReactElement;
}
return content;
}