mirror of
https://github.com/alibaba/lowcode-engine.git
synced 2026-04-20 04:18:05 +00:00
Merge branch 'feat/joint-editor' of gitlab.alibaba-inc.com:ali-lowcode/ali-lowcode-engine into feat/joint-editor
This commit is contained in:
commit
6b0e9ec145
@ -65,7 +65,7 @@ export default {
|
|||||||
{
|
{
|
||||||
name: 'prefix',
|
name: 'prefix',
|
||||||
propType: 'string',
|
propType: 'string',
|
||||||
defaultValue: "'next-'"
|
defaultValue: 'next-'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'rtl',
|
name: 'rtl',
|
||||||
@ -78,7 +78,7 @@ export default {
|
|||||||
value: ['primary', 'secondary', 'normal']
|
value: ['primary', 'secondary', 'normal']
|
||||||
},
|
},
|
||||||
description: '按钮的类型',
|
description: '按钮的类型',
|
||||||
defaultValue: "'normal'"
|
defaultValue: 'normal'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'size',
|
name: 'size',
|
||||||
@ -87,7 +87,7 @@ export default {
|
|||||||
value: ['small', 'medium', 'large']
|
value: ['small', 'medium', 'large']
|
||||||
},
|
},
|
||||||
description: '按钮的尺寸',
|
description: '按钮的尺寸',
|
||||||
defaultValue: "'medium'"
|
defaultValue: 'medium'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'iconSize',
|
name: 'iconSize',
|
||||||
@ -104,7 +104,7 @@ export default {
|
|||||||
value: ['submit', 'reset', 'button']
|
value: ['submit', 'reset', 'button']
|
||||||
},
|
},
|
||||||
description: "当 component = 'button' 时,设置 button 标签的 type 值",
|
description: "当 component = 'button' 时,设置 button 标签的 type 值",
|
||||||
defaultValue: "'button'"
|
defaultValue: 'button'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'component',
|
name: 'component',
|
||||||
@ -113,13 +113,13 @@ export default {
|
|||||||
value: ['button', 'a', 'div', 'span']
|
value: ['button', 'a', 'div', 'span']
|
||||||
},
|
},
|
||||||
description: '设置标签类型',
|
description: '设置标签类型',
|
||||||
defaultValue: "'button'"
|
defaultValue: 'button'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'loading',
|
name: 'loading',
|
||||||
propType: 'bool',
|
propType: 'bool',
|
||||||
description: '设置按钮的载入状态',
|
description: '设置按钮的载入状态',
|
||||||
defaultValue: 'false'
|
defaultValue: false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'ghost',
|
name: 'ghost',
|
||||||
@ -128,31 +128,30 @@ export default {
|
|||||||
value: [true, false, 'light', 'dark']
|
value: [true, false, 'light', 'dark']
|
||||||
},
|
},
|
||||||
description: '是否为幽灵按钮',
|
description: '是否为幽灵按钮',
|
||||||
defaultValue: 'false'
|
defaultValue: false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'text',
|
name: 'text',
|
||||||
propType: 'bool',
|
propType: 'bool',
|
||||||
description: '是否为文本按钮',
|
description: '是否为文本按钮',
|
||||||
defaultValue: 'false'
|
defaultValue: false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'warning',
|
name: 'warning',
|
||||||
propType: 'bool',
|
propType: 'bool',
|
||||||
description: '是否为警告按钮',
|
description: '是否为警告按钮',
|
||||||
defaultValue: 'false'
|
defaultValue: false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'disabled',
|
name: 'disabled',
|
||||||
propType: 'bool',
|
propType: 'bool',
|
||||||
description: '是否禁用',
|
description: '是否禁用',
|
||||||
defaultValue: 'false'
|
defaultValue: false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'onClick',
|
name: 'onClick',
|
||||||
propType: 'func',
|
propType: 'func',
|
||||||
description: '点击按钮的回调\n@param {Object} e Event Object',
|
description: '点击按钮的回调\n@param {Object} e Event Object'
|
||||||
defaultValue: '() => {}'
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'className',
|
name: 'className',
|
||||||
@ -187,13 +186,13 @@ export default {
|
|||||||
{
|
{
|
||||||
name: 'prefix',
|
name: 'prefix',
|
||||||
propType: 'string',
|
propType: 'string',
|
||||||
defaultValue: "'next-'"
|
defaultValue: 'next-'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'size',
|
name: 'size',
|
||||||
propType: 'string',
|
propType: 'string',
|
||||||
description: '统一设置 Button 组件的按钮大小',
|
description: '统一设置 Button 组件的按钮大小',
|
||||||
defaultValue: "'medium'"
|
defaultValue: 'medium'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'className',
|
name: 'className',
|
||||||
@ -255,7 +254,7 @@ export default {
|
|||||||
value: ['small', 'medium', 'large']
|
value: ['small', 'medium', 'large']
|
||||||
},
|
},
|
||||||
description: '尺寸\n@enumdesc 小, 中, 大',
|
description: '尺寸\n@enumdesc 小, 中, 大',
|
||||||
defaultValue: "'medium'"
|
defaultValue: 'medium'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'onPressEnter',
|
name: 'onPressEnter',
|
||||||
@ -315,7 +314,7 @@ export default {
|
|||||||
name: 'autoComplete',
|
name: 'autoComplete',
|
||||||
propType: 'string',
|
propType: 'string',
|
||||||
description: '(原生input支持)',
|
description: '(原生input支持)',
|
||||||
defaultValue: "'off'"
|
defaultValue: 'off'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'autoFocus',
|
name: 'autoFocus',
|
||||||
@ -367,7 +366,7 @@ export default {
|
|||||||
name: 'prefix',
|
name: 'prefix',
|
||||||
propType: 'string',
|
propType: 'string',
|
||||||
description: '样式前缀',
|
description: '样式前缀',
|
||||||
defaultValue: '"next-"'
|
defaultValue: 'next-'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'inline',
|
name: 'inline',
|
||||||
@ -382,7 +381,7 @@ export default {
|
|||||||
},
|
},
|
||||||
description:
|
description:
|
||||||
'单个 Item 的 size 自定义,优先级高于 Form 的 size, 并且当组件与 Item 一起使用时,组件自身设置 size 属性无效。\n@enumdesc 大, 中, 小',
|
'单个 Item 的 size 自定义,优先级高于 Form 的 size, 并且当组件与 Item 一起使用时,组件自身设置 size 属性无效。\n@enumdesc 大, 中, 小',
|
||||||
defaultValue: '"medium"'
|
defaultValue: 'medium'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'fullWidth',
|
name: 'fullWidth',
|
||||||
@ -396,7 +395,7 @@ export default {
|
|||||||
value: ['top', 'left', 'inset']
|
value: ['top', 'left', 'inset']
|
||||||
},
|
},
|
||||||
description: '标签的位置\n@enumdesc 上, 左, 内',
|
description: '标签的位置\n@enumdesc 上, 左, 内',
|
||||||
defaultValue: '"left"'
|
defaultValue: 'left'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'labelTextAlign',
|
name: 'labelTextAlign',
|
||||||
@ -414,8 +413,7 @@ export default {
|
|||||||
{
|
{
|
||||||
name: 'saveField',
|
name: 'saveField',
|
||||||
propType: 'func',
|
propType: 'func',
|
||||||
description: '保存 Form 自动生成的 field 对象',
|
description: '保存 Form 自动生成的 field 对象'
|
||||||
defaultValue: 'func.noop'
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'labelCol',
|
name: 'labelCol',
|
||||||
@ -430,8 +428,7 @@ export default {
|
|||||||
{
|
{
|
||||||
name: 'onSubmit',
|
name: 'onSubmit',
|
||||||
propType: 'func',
|
propType: 'func',
|
||||||
description: 'form内有 `htmlType="submit"` 的元素的时候会触发',
|
description: 'form内有 `htmlType="submit"` 的元素的时候会触发'
|
||||||
defaultValue: 'function preventDefault(e) {\n e.preventDefault();\n}'
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'children',
|
name: 'children',
|
||||||
@ -457,8 +454,7 @@ export default {
|
|||||||
name: 'onChange',
|
name: 'onChange',
|
||||||
propType: 'func',
|
propType: 'func',
|
||||||
description:
|
description:
|
||||||
'表单变化回调\n@param {Object} values 表单数据\n@param {Object} item 详细\n@param {String} item.name 变化的组件名\n@param {String} item.value 变化的数据\n@param {Object} item.field field 实例',
|
'表单变化回调\n@param {Object} values 表单数据\n@param {Object} item 详细\n@param {String} item.name 变化的组件名\n@param {String} item.value 变化的数据\n@param {Object} item.field field 实例'
|
||||||
defaultValue: 'func.noop'
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'component',
|
name: 'component',
|
||||||
@ -467,7 +463,7 @@ export default {
|
|||||||
value: ['string', 'func']
|
value: ['string', 'func']
|
||||||
},
|
},
|
||||||
description: '设置标签类型',
|
description: '设置标签类型',
|
||||||
defaultValue: '"form"'
|
defaultValue: 'form'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'fieldOptions',
|
name: 'fieldOptions',
|
||||||
@ -484,7 +480,7 @@ export default {
|
|||||||
value: ['phone', 'tablet', 'desktop']
|
value: ['phone', 'tablet', 'desktop']
|
||||||
},
|
},
|
||||||
description: '预设屏幕宽度',
|
description: '预设屏幕宽度',
|
||||||
defaultValue: '"desktop"'
|
defaultValue: 'desktop'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'responsive',
|
name: 'responsive',
|
||||||
@ -519,7 +515,7 @@ export default {
|
|||||||
name: 'prefix',
|
name: 'prefix',
|
||||||
propType: 'string',
|
propType: 'string',
|
||||||
description: '样式前缀',
|
description: '样式前缀',
|
||||||
defaultValue: "'next-'"
|
defaultValue: 'next-'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'rtl',
|
name: 'rtl',
|
||||||
@ -564,7 +560,7 @@ export default {
|
|||||||
name: 'hasFeedback',
|
name: 'hasFeedback',
|
||||||
propType: 'bool',
|
propType: 'bool',
|
||||||
description: '配合 validateState 属性使用,是否展示 success/loading 的校验状态图标, 目前只有Input支持',
|
description: '配合 validateState 属性使用,是否展示 success/loading 的校验状态图标, 目前只有Input支持',
|
||||||
defaultValue: 'false'
|
defaultValue: false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'style',
|
name: 'style',
|
||||||
@ -786,7 +782,7 @@ export default {
|
|||||||
value: ['string', 'number']
|
value: ['string', 'number']
|
||||||
},
|
},
|
||||||
description: '在响应式布局下,且label在左边时,label的宽度是多少',
|
description: '在响应式布局下,且label在左边时,label的宽度是多少',
|
||||||
defaultValue: '100'
|
defaultValue: 100
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'isPreview',
|
name: 'isPreview',
|
||||||
@ -823,7 +819,7 @@ export default {
|
|||||||
name: 'prefix',
|
name: 'prefix',
|
||||||
propType: 'string',
|
propType: 'string',
|
||||||
description: '样式前缀',
|
description: '样式前缀',
|
||||||
defaultValue: '"next-"'
|
defaultValue: 'next-'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'type',
|
name: 'type',
|
||||||
@ -832,7 +828,7 @@ export default {
|
|||||||
value: ['normal', 'inline']
|
value: ['normal', 'inline']
|
||||||
},
|
},
|
||||||
description: '设置类型\n@enumdesc 普通, 内联',
|
description: '设置类型\n@enumdesc 普通, 内联',
|
||||||
defaultValue: '"normal"'
|
defaultValue: 'normal'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'size',
|
name: 'size',
|
||||||
@ -841,7 +837,7 @@ export default {
|
|||||||
value: ['large', 'medium']
|
value: ['large', 'medium']
|
||||||
},
|
},
|
||||||
description: '大小',
|
description: '大小',
|
||||||
defaultValue: '"medium"'
|
defaultValue: 'medium'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'value',
|
name: 'value',
|
||||||
@ -865,19 +861,19 @@ export default {
|
|||||||
value: ['number', 'string']
|
value: ['number', 'string']
|
||||||
},
|
},
|
||||||
description: '步长',
|
description: '步长',
|
||||||
defaultValue: '1'
|
defaultValue: 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'precision',
|
name: 'precision',
|
||||||
propType: 'number',
|
propType: 'number',
|
||||||
description: '保留小数点后位数',
|
description: '保留小数点后位数',
|
||||||
defaultValue: '0'
|
defaultValue: 0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'editable',
|
name: 'editable',
|
||||||
propType: 'bool',
|
propType: 'bool',
|
||||||
description: '用户是否可以输入',
|
description: '用户是否可以输入',
|
||||||
defaultValue: 'true'
|
defaultValue: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'autoFocus',
|
name: 'autoFocus',
|
||||||
@ -887,14 +883,12 @@ export default {
|
|||||||
{
|
{
|
||||||
name: 'onChange',
|
name: 'onChange',
|
||||||
propType: 'func',
|
propType: 'func',
|
||||||
description: '数值被改变的事件\n@param {Number} value 数据\n@param {Event} e DOM事件对象',
|
description: '数值被改变的事件\n@param {Number} value 数据\n@param {Event} e DOM事件对象'
|
||||||
defaultValue: 'func.noop'
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'onKeyDown',
|
name: 'onKeyDown',
|
||||||
propType: 'func',
|
propType: 'func',
|
||||||
description: '键盘按下',
|
description: '键盘按下'
|
||||||
defaultValue: 'func.noop'
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'onFocus',
|
name: 'onFocus',
|
||||||
@ -904,31 +898,28 @@ export default {
|
|||||||
{
|
{
|
||||||
name: 'onBlur',
|
name: 'onBlur',
|
||||||
propType: 'func',
|
propType: 'func',
|
||||||
description: '焦点失去',
|
description: '焦点失去'
|
||||||
defaultValue: 'func.noop'
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'onCorrect',
|
name: 'onCorrect',
|
||||||
propType: 'func',
|
propType: 'func',
|
||||||
description: '数值订正后的回调\n@param {Object} obj {currentValue,oldValue:String}',
|
description: '数值订正后的回调\n@param {Object} obj {currentValue,oldValue:String}'
|
||||||
defaultValue: 'func.noop'
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'onDisabled',
|
name: 'onDisabled',
|
||||||
propType: 'func',
|
propType: 'func'
|
||||||
defaultValue: 'func.noop'
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'max',
|
name: 'max',
|
||||||
propType: 'number',
|
propType: 'number',
|
||||||
description: '最大值',
|
description: '最大值',
|
||||||
defaultValue: 'Infinity'
|
defaultValue: null
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'min',
|
name: 'min',
|
||||||
propType: 'number',
|
propType: 'number',
|
||||||
description: '最小值',
|
description: '最小值',
|
||||||
defaultValue: '-Infinity'
|
defaultValue: null
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'className',
|
name: 'className',
|
||||||
@ -938,8 +929,7 @@ export default {
|
|||||||
{
|
{
|
||||||
name: 'style',
|
name: 'style',
|
||||||
propType: 'object',
|
propType: 'object',
|
||||||
description: '自定义内联样式',
|
description: '自定义内联样式'
|
||||||
defaultValue: '{}'
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'state',
|
name: 'state',
|
||||||
@ -1015,7 +1005,7 @@ export default {
|
|||||||
value: ['single', 'multiple', 'tag']
|
value: ['single', 'multiple', 'tag']
|
||||||
},
|
},
|
||||||
description: '选择器模式',
|
description: '选择器模式',
|
||||||
defaultValue: '"single"'
|
defaultValue: 'single'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'value',
|
name: 'value',
|
||||||
@ -1078,13 +1068,13 @@ export default {
|
|||||||
name: 'hasArrow',
|
name: 'hasArrow',
|
||||||
propType: 'bool',
|
propType: 'bool',
|
||||||
description: '是否有下拉箭头',
|
description: '是否有下拉箭头',
|
||||||
defaultValue: 'true'
|
defaultValue: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'showSearch',
|
name: 'showSearch',
|
||||||
propType: 'bool',
|
propType: 'bool',
|
||||||
description: '展开后是否能搜索(tag 模式下固定为true)',
|
description: '展开后是否能搜索(tag 模式下固定为true)',
|
||||||
defaultValue: 'false'
|
defaultValue: false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'onSearch',
|
name: 'onSearch',
|
||||||
@ -1119,7 +1109,7 @@ export default {
|
|||||||
name: 'cacheValue',
|
name: 'cacheValue',
|
||||||
propType: 'bool',
|
propType: 'bool',
|
||||||
description: 'dataSource 变化的时是否保留已选的内容',
|
description: 'dataSource 变化的时是否保留已选的内容',
|
||||||
defaultValue: 'true'
|
defaultValue: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'valueRender',
|
name: 'valueRender',
|
||||||
@ -1151,7 +1141,7 @@ export default {
|
|||||||
name: 'tagInline',
|
name: 'tagInline',
|
||||||
propType: 'bool',
|
propType: 'bool',
|
||||||
description: '是否一行显示,仅在 mode 为 multiple 的时候生效',
|
description: '是否一行显示,仅在 mode 为 multiple 的时候生效',
|
||||||
defaultValue: 'false'
|
defaultValue: false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'maxTagCount',
|
name: 'maxTagCount',
|
||||||
@ -1198,8 +1188,7 @@ export default {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'locale',
|
name: 'locale',
|
||||||
propType: 'object',
|
propType: 'object'
|
||||||
defaultValue: 'zhCN.Select'
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
configure: {
|
configure: {
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
import { Input } from '@alifd/next';
|
import { DatePicker, Input, Radio, Select, Switch } from '@alifd/next';
|
||||||
import NumberSetter from '../../../plugin-setters/number-setter';
|
import NumberSetter from '../../../plugin-setters/src/number-setter';
|
||||||
|
import ExpressionSetter from '../../../plugin-setters/src/expression-setter';
|
||||||
|
import MixinSetter from '../../../plugin-setters/src/mixin-setter';
|
||||||
import EventsSetter from '../../../plugin-settings/src/builtin-setters/events-setter'
|
import EventsSetter from '../../../plugin-settings/src/builtin-setters/events-setter'
|
||||||
import { registerSetter } from '../../../plugin-settings/src';
|
import { registerSetter } from '../../../plugin-settings/src';
|
||||||
import { createElement } from 'react';
|
import { createElement } from 'react';
|
||||||
@ -19,3 +21,15 @@ registerSetter('EventsSetter', EventsSetter);
|
|||||||
registerSetter('StringSetter', { component: Input, props: { placeholder: '请输入' } });
|
registerSetter('StringSetter', { component: Input, props: { placeholder: '请输入' } });
|
||||||
|
|
||||||
registerSetter('NumberSetter', NumberSetter as any);
|
registerSetter('NumberSetter', NumberSetter as any);
|
||||||
|
|
||||||
|
registerSetter('StringSetter', { component: Input, props: { placeholder: '请输入' } });
|
||||||
|
registerSetter('ExpressionSetter', ExpressionSetter);
|
||||||
|
registerSetter('MixinSetter', MixinSetter);
|
||||||
|
registerSetter('BoolSetter', Switch);
|
||||||
|
registerSetter('RadioGroupSetter', Radio.RadioGroup);
|
||||||
|
registerSetter('SelectSetter', Select);
|
||||||
|
registerSetter('TextAreaSetter', Input.TextArea);
|
||||||
|
registerSetter('DateSetter', DatePicker);
|
||||||
|
registerSetter('DateYearSetter', DatePicker.YearPicker);
|
||||||
|
registerSetter('DateMonthSetter', DatePicker.MonthPicker);
|
||||||
|
registerSetter('DateRangeSetter', DatePicker.RangePicker);
|
||||||
|
|||||||
@ -1,5 +1,18 @@
|
|||||||
{
|
{
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@alifd/next": "^1.19.16"
|
"@ali/iceluna-comp-expression": "^1.0.6",
|
||||||
|
"@ali/iceluna-comp-form": "^1.0.20",
|
||||||
|
"@ali/iceluna-comp-list": "^1.0.26",
|
||||||
|
"@ali/iceluna-comp-monaco-editor": "^1.0.31",
|
||||||
|
"@ali/iceluna-comp-object-button": "^1.0.23",
|
||||||
|
"@ali/iceluna-comp-react-node": "^1.0.5",
|
||||||
|
"@ali/iceluna-sdk": "^1.0.5-beta.24",
|
||||||
|
"@alifd/next": "^1.19.16",
|
||||||
|
"@alife/next": "^1.19.16",
|
||||||
|
"acorn": "^6.4.1",
|
||||||
|
"intl-messageformat": "^8.2.1",
|
||||||
|
"monaco-editor": "^0.20.0",
|
||||||
|
"qs": "^6.9.1",
|
||||||
|
"react-monaco-editor": "^0.34.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
377
packages/plugin-setters/src/expression-setter.tsx
Normal file
377
packages/plugin-setters/src/expression-setter.tsx
Normal file
@ -0,0 +1,377 @@
|
|||||||
|
import React, { PureComponent } from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { Select, Balloon } from '@alife/next';
|
||||||
|
import * as acorn from 'acorn';
|
||||||
|
|
||||||
|
import { isJSExpression, generateI18n } from './locale/utils';
|
||||||
|
import zhCN from './locale/zh-CN';
|
||||||
|
|
||||||
|
const { Option, AutoComplete } = Select;
|
||||||
|
const { Tooltip } = Balloon;
|
||||||
|
const helpMap = {
|
||||||
|
this: '容器上下文对象',
|
||||||
|
'this.state': '容器的state',
|
||||||
|
'this.props': '容器的props',
|
||||||
|
'this.context': '容器的context',
|
||||||
|
'this.page': '页面上下文对象',
|
||||||
|
'this.component': '组件上下文对象',
|
||||||
|
'this.constants': '应用常量对象',
|
||||||
|
'this.utils': '应用工具对象',
|
||||||
|
'this.dataSourceMap': '容器数据源Map',
|
||||||
|
'this.field': '表单Field对象'
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class ExpressionView extends PureComponent {
|
||||||
|
static displayName = 'Expression';
|
||||||
|
static propTypes = {
|
||||||
|
context: PropTypes.object,
|
||||||
|
dataSource: PropTypes.array,
|
||||||
|
locale: PropTypes.string,
|
||||||
|
messages: PropTypes.object,
|
||||||
|
onChange: PropTypes.func,
|
||||||
|
placeholder: PropTypes.string,
|
||||||
|
value: PropTypes.string
|
||||||
|
};
|
||||||
|
static defaultProps = {
|
||||||
|
context: {},
|
||||||
|
dataSource: [],
|
||||||
|
locale: 'zh-CN',
|
||||||
|
messages: zhCN,
|
||||||
|
onChange: () => {},
|
||||||
|
placeholder: '',
|
||||||
|
value: ''
|
||||||
|
};
|
||||||
|
expression: React.RefObject<unknown>;
|
||||||
|
i18n: any;
|
||||||
|
t: void;
|
||||||
|
$input: any;
|
||||||
|
listenerFun: (event: any) => void;
|
||||||
|
|
||||||
|
static getInitValue(val: { value: any; match: (arg0: RegExp) => any; }) {
|
||||||
|
if (isJSExpression(val)) {
|
||||||
|
if (typeof val === 'object') {
|
||||||
|
return val.value;
|
||||||
|
} else if (typeof val === 'string') {
|
||||||
|
let arr = val.match(/^\{\{(.*?)\}\}$/);
|
||||||
|
if (arr) return arr[1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
constructor(props: Readonly<{}>) {
|
||||||
|
super(props);
|
||||||
|
this.expression = React.createRef();
|
||||||
|
this.i18n = generateI18n(props.locale, props.messages);
|
||||||
|
this.state = {
|
||||||
|
value: ExpressionView.getInitValue(props.value),
|
||||||
|
context: props.context || {},
|
||||||
|
dataSource: props.dataSource || []
|
||||||
|
};
|
||||||
|
}
|
||||||
|
static getDerivedStateFromProps(props: { value: any; }, state: { preValue: any; }) {
|
||||||
|
let curValue = ExpressionView.getInitValue(props.value);
|
||||||
|
if (curValue !== state.preValue) {
|
||||||
|
return {
|
||||||
|
preValue: curValue,
|
||||||
|
value: curValue
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
onChange(value: string, actionType: string) {
|
||||||
|
let realInputValue = value;
|
||||||
|
let realDataSource = null;
|
||||||
|
const cursorIndex = this.getInputCursorPosition();
|
||||||
|
let nextCursorIndex: number;
|
||||||
|
//更新值
|
||||||
|
if (actionType === 'itemClick' || actionType === 'enter') {
|
||||||
|
let curValue = this.state.value;
|
||||||
|
if (curValue) {
|
||||||
|
//如果是非.结束,则替换当前这个变量;
|
||||||
|
let preStr = curValue.substr(0, cursorIndex);
|
||||||
|
let nextStr = curValue.substr(cursorIndex);
|
||||||
|
let preArr = preStr.split('.');
|
||||||
|
let preArrLen = preArr.length;
|
||||||
|
let tarPreStr = '';
|
||||||
|
if (!preArr[preArrLen - 1]) {
|
||||||
|
//如果是.结束,则增加到.后面
|
||||||
|
if (preArr[preArrLen - 2] === 'this') {
|
||||||
|
preArr = preArr.slice(0, preArrLen - 2);
|
||||||
|
preArr.push(value);
|
||||||
|
tarPreStr = preArr.join('.');
|
||||||
|
} else {
|
||||||
|
tarPreStr = preStr + value;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (preArr[preArrLen - 2] === 'this') {
|
||||||
|
preArr = preArr.slice(0, preArrLen - 2);
|
||||||
|
} else {
|
||||||
|
preArr = preArr.slice(0, preArrLen - 1);
|
||||||
|
}
|
||||||
|
preArr.push(value);
|
||||||
|
tarPreStr = preArr.join('.');
|
||||||
|
}
|
||||||
|
realInputValue = tarPreStr + nextStr;
|
||||||
|
realDataSource = this.getDataSource(tarPreStr + '.') || [];
|
||||||
|
nextCursorIndex = tarPreStr.length;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let tarPreStr = value.substr(0, cursorIndex);
|
||||||
|
if (tarPreStr) {
|
||||||
|
let lastChar = tarPreStr.charAt(tarPreStr.length - 1);
|
||||||
|
if (lastChar === '.') {
|
||||||
|
realDataSource = this.getDataSource(tarPreStr) || [];
|
||||||
|
} else {
|
||||||
|
realDataSource = this.getDataSource(tarPreStr + '.');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
realDataSource = this.getDataSource('this.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//更新数据源
|
||||||
|
let newState = {
|
||||||
|
value: realInputValue
|
||||||
|
};
|
||||||
|
if (realDataSource !== null) newState.dataSource = realDataSource;
|
||||||
|
this.setState(newState, () => {
|
||||||
|
nextCursorIndex && this.setInputCursorPosition(nextCursorIndex);
|
||||||
|
});
|
||||||
|
//默认加上变量表达式
|
||||||
|
this.t && clearTimeout(this.t);
|
||||||
|
this.t = setTimeout(() => {
|
||||||
|
const { onChange } = this.props;
|
||||||
|
// realInputValue = realInputValue ? `{{${realInputValue}}}` : undefined;
|
||||||
|
onChange && onChange({
|
||||||
|
type: 'JSExpression',
|
||||||
|
value: realInputValue
|
||||||
|
});
|
||||||
|
}, 300);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取AutoComplete数据源
|
||||||
|
* @param {String}
|
||||||
|
* @return {Array}
|
||||||
|
*/
|
||||||
|
getDataSource(tempStr: string): Array<any> {
|
||||||
|
if (tempStr === '' || /[^\w\.]$/.test(tempStr)) {
|
||||||
|
return this.getDataSource('this.') || [];
|
||||||
|
} else if (/\w\.$/.test(tempStr)) {
|
||||||
|
let currentField = this.getCurrentFiled(tempStr);
|
||||||
|
if (!currentField) return null;
|
||||||
|
let tempKeys = this.getObjectKeys(currentField.str);
|
||||||
|
tempKeys = this.getContextKeys(tempKeys);
|
||||||
|
if (!tempKeys) return null;
|
||||||
|
//给默认情况增加this
|
||||||
|
if (tempStr === 'this.') {
|
||||||
|
tempKeys = tempKeys.map((item: string) => {
|
||||||
|
return 'this.' + item;
|
||||||
|
});
|
||||||
|
tempKeys.unshift('this');
|
||||||
|
}
|
||||||
|
return tempKeys;
|
||||||
|
} else if (/\.$/.test(tempStr)) {
|
||||||
|
return [];
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取光标前的对象字符串,语法解析获取对象字符串
|
||||||
|
* @param {String} str 模板字符串
|
||||||
|
* @return {String} 光标前的对象字符串
|
||||||
|
*/
|
||||||
|
getCurrentFiled(str: string | any[]) {
|
||||||
|
str += 'x'; //.后面加一个x字符,便于acorn解析
|
||||||
|
try {
|
||||||
|
let astTree = acorn.parse(str);
|
||||||
|
let right = astTree.body[0].expression.right || astTree.body[0].expression;
|
||||||
|
if (right.type === 'MemberExpression') {
|
||||||
|
let { start, end } = right;
|
||||||
|
str = str.slice(start, end);
|
||||||
|
return { str, start, end };
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取输入的上下文信息
|
||||||
|
* @param {Array}
|
||||||
|
* @return {Array}
|
||||||
|
*/
|
||||||
|
getContextKeys(keys: any) {
|
||||||
|
// let context = {};
|
||||||
|
// const { appHelper } = this.context;
|
||||||
|
// const activeKey = appHelper && appHelper.activeKey;
|
||||||
|
// if (!activeKey) return;
|
||||||
|
// const activeCtx = appHelper.schemaHelper.compCtxMap && appHelper.schemaHelper.compCtxMap[activeKey];
|
||||||
|
// if (!activeCtx) return null;
|
||||||
|
// let __self = activeCtx;
|
||||||
|
// if (keys && keys.length > 1) {
|
||||||
|
// keys.shift(0);
|
||||||
|
// let path = '/' + keys.join('/');
|
||||||
|
// path = path.replace(/[\[\]]/g, '/');
|
||||||
|
// context = jsonuri.get(__self, path);
|
||||||
|
// if (context && typeof context === 'object') {
|
||||||
|
// return this.filterKey(context);
|
||||||
|
// }
|
||||||
|
// } else if (keys && keys[0] === 'this') {
|
||||||
|
// return this.filterKey(__self);
|
||||||
|
// }
|
||||||
|
// return null;
|
||||||
|
return [
|
||||||
|
"page",
|
||||||
|
"component"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
/*过滤key */
|
||||||
|
filterKey(obj: any) {
|
||||||
|
let filterKeys = [
|
||||||
|
'reloadDataSource',
|
||||||
|
'REACT_HOT_LOADER_RENDERED_GENERATION',
|
||||||
|
'refs',
|
||||||
|
'updater',
|
||||||
|
'appHelper',
|
||||||
|
'isReactComponent',
|
||||||
|
'forceUpdate',
|
||||||
|
'setState',
|
||||||
|
'isPureReactComponent'
|
||||||
|
];
|
||||||
|
let result = [];
|
||||||
|
for (let key in obj) {
|
||||||
|
if (key.indexOf('_') !== 0 && filterKeys.indexOf(key) === -1) {
|
||||||
|
result.push(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据输入项进行筛选
|
||||||
|
* @param {String}
|
||||||
|
* @param {String}
|
||||||
|
* @return {Boolen}
|
||||||
|
*/
|
||||||
|
filterOption(inputValue: string, item: { value: string | any[]; }) {
|
||||||
|
const cursorIndex = this.getInputCursorPosition();
|
||||||
|
let preStr = inputValue.substr(0, cursorIndex);
|
||||||
|
let lastKey = preStr.split('.').slice(-1);
|
||||||
|
if (!lastKey) return true;
|
||||||
|
if (item.value.indexOf(lastKey) > -1) return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { value, dataSource } = this.state;
|
||||||
|
const { placeholder } = this.props;
|
||||||
|
const isValObject = !!(value == '[object Object]');
|
||||||
|
let title = isValObject
|
||||||
|
? this.i18n('valueIllegal')
|
||||||
|
: (value || placeholder || this.i18n('jsExpression')).toString();
|
||||||
|
const cursorIndex = this.getInputCursorPosition();
|
||||||
|
let childNode = cursorIndex ? (
|
||||||
|
<div className="cursor-blink">
|
||||||
|
{title.substr(0, cursorIndex)}
|
||||||
|
<b>|</b>
|
||||||
|
{title.substr(cursorIndex)}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
title
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div ref={this.expression} style={{ width: '100%', display: 'inline-block' }}>
|
||||||
|
<Tooltip
|
||||||
|
triggerType={isValObject ? ['click'] : ['focus']}
|
||||||
|
align="tl"
|
||||||
|
popupClassName="code-input-overlay"
|
||||||
|
trigger={
|
||||||
|
isValObject ? (
|
||||||
|
value
|
||||||
|
) : (
|
||||||
|
<AutoComplete
|
||||||
|
{...this.props}
|
||||||
|
style={{ width: '100%' }}
|
||||||
|
dataSource={dataSource}
|
||||||
|
placeholder={placeholder || this.i18n('jsExpression')}
|
||||||
|
value={value}
|
||||||
|
disabled={isValObject}
|
||||||
|
innerBefore={<span style={{ color: '#999', marginLeft: 4 }}>{'{{'}</span>}
|
||||||
|
innerAfter={<span style={{ color: '#999', marginRight: 4 }}>{'}}'}</span>}
|
||||||
|
itemRender={({ value }) => {
|
||||||
|
return (
|
||||||
|
<Option key={value} text={value} value={value}>
|
||||||
|
<div className="code-input-value">{value}</div>
|
||||||
|
<div className="code-input-help">{helpMap[value]}</div>
|
||||||
|
</Option>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
onChange={this.onChange.bind(this)}
|
||||||
|
filter={this.filterOption.bind(this)}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{childNode}
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
this.$input = this.findInputElement();
|
||||||
|
if (this.$input) {
|
||||||
|
this.listenerFun = event => {
|
||||||
|
let isMoveKey = !!(event.type == 'keyup' && ~[37, 38, 39, 91].indexOf(event.keyCode));
|
||||||
|
let isMouseup = event.type == 'mouseup';
|
||||||
|
if (isMoveKey || isMouseup) {
|
||||||
|
let dataSource = this.getDataSource(this.state.value) || [];
|
||||||
|
this.setState({
|
||||||
|
dataSource
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
this.$input.addEventListener('keyup', this.listenerFun, false);
|
||||||
|
this.$input.addEventListener('mouseup', this.listenerFun, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
componentWillUnmount() {
|
||||||
|
if (this.listenerFun && this.$input) {
|
||||||
|
this.$input.removeEventListener('keyup', this.listenerFun, false);
|
||||||
|
this.$input.removeEventListener('mouseup', this.listenerFun, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 获取Input输入框DOM节点
|
||||||
|
*/
|
||||||
|
findInputElement() {
|
||||||
|
return this.expression.current.children[0].getElementsByTagName('input')[0];
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 获取光标位置
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
getInputCursorPosition() {
|
||||||
|
if (!this.$input) return;
|
||||||
|
return this.$input.selectionStart;
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
* 字符串取得对象keys
|
||||||
|
*/
|
||||||
|
getObjectKeys(str: string) {
|
||||||
|
let keys = [];
|
||||||
|
if (str) keys = str.split('.');
|
||||||
|
return keys.slice(0, keys.length - 1);
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
* 设置input组件光标位置在闭合}前
|
||||||
|
*/
|
||||||
|
setInputCursorPosition(idx: number) {
|
||||||
|
this.$input.setSelectionRange(idx, idx);
|
||||||
|
this.forceUpdate();
|
||||||
|
}
|
||||||
|
}
|
||||||
471
packages/plugin-setters/src/function-setter.tsx
Normal file
471
packages/plugin-setters/src/function-setter.tsx
Normal file
@ -0,0 +1,471 @@
|
|||||||
|
import { Component, isValidElement, ReactElement, ReactNode, PureComponent } from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import MonacoEditor from 'react-monaco-editor';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import { Icon } from '@alifd/next';
|
||||||
|
import ObjectButton from '@ali/iceluna-comp-object-button';
|
||||||
|
import FormItem from '@ali/iceluna-comp-form/lib/item';
|
||||||
|
import { serialize, jsonuri, generateI18n } from '@ali/iceluna-sdk/lib/utils';
|
||||||
|
|
||||||
|
import Snippets from './locale/snippets';
|
||||||
|
import zhCN from './locale/zh-CN';
|
||||||
|
import './index.scss';
|
||||||
|
|
||||||
|
let registerApiAndSnippetStatus = false; //判断注册api机制
|
||||||
|
|
||||||
|
class MonacoEditorDefaultView extends PureComponent {
|
||||||
|
static displayName = 'MonacoEditorDefault';
|
||||||
|
static propTypes = {
|
||||||
|
locale: PropTypes.string,
|
||||||
|
messages: PropTypes.object
|
||||||
|
};
|
||||||
|
static defaultProps = {
|
||||||
|
locale: 'zh-CN',
|
||||||
|
messages: zhCN,
|
||||||
|
width: '100%',
|
||||||
|
height: '100%',
|
||||||
|
language: 'javascript',
|
||||||
|
autoFocus: false, //自动获得焦点
|
||||||
|
autoSubmit: true, //自动提交
|
||||||
|
placeholder: '', //默认占位内容
|
||||||
|
btnText: '提交',
|
||||||
|
btnSize: 'small',
|
||||||
|
rules: [], //校验规则
|
||||||
|
options: {
|
||||||
|
readOnly: false,
|
||||||
|
automaticLayout: true,
|
||||||
|
folding: true, //默认开启折叠代码功能
|
||||||
|
lineNumbers: 'on',
|
||||||
|
wordWrap: 'off',
|
||||||
|
formatOnPaste: true,
|
||||||
|
fontSize: 12,
|
||||||
|
tabSize: 2,
|
||||||
|
scrollBeyondLastLine: false,
|
||||||
|
fixedOverflowWidgets: false,
|
||||||
|
snippetSuggestions: 'top',
|
||||||
|
minimap: {
|
||||||
|
enabled: true
|
||||||
|
},
|
||||||
|
scrollbar: {
|
||||||
|
vertical: 'hidden',
|
||||||
|
horizontal: 'hidden',
|
||||||
|
verticalScrollbarSize: 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.strValue = '';
|
||||||
|
this.i18n = generateI18n(props.locale, props.messages);
|
||||||
|
this.editorRef = React.createRef();
|
||||||
|
this.options = Object.assign({}, MonacoEditorDefaultView.defaultProps.options, props.options);
|
||||||
|
this.fullScreenOptions = {
|
||||||
|
...this.options,
|
||||||
|
lineNumbers: 'on',
|
||||||
|
folding: true,
|
||||||
|
scrollBeyondLastLine: true,
|
||||||
|
minimap: {
|
||||||
|
enabled: true
|
||||||
|
}
|
||||||
|
};
|
||||||
|
this.state = {
|
||||||
|
isFullScreen: false
|
||||||
|
};
|
||||||
|
this.onChange = this.onChange.bind(this);
|
||||||
|
this.onSubmit = this.onSubmit.bind(this);
|
||||||
|
this.fullScreen = this.fullScreen.bind(this);
|
||||||
|
this.format = this.format.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidUpdate() {
|
||||||
|
//如果是全屏操作,获得焦点,光标保留在原来位置;
|
||||||
|
if (this.position) {
|
||||||
|
this.editor.focus();
|
||||||
|
this.editor.setPosition(this.position);
|
||||||
|
delete this.position;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
componentDidMount() {
|
||||||
|
this.editorNode = this.editorRef.current; //记录当前dom节点;
|
||||||
|
this.editorParentNode = this.editorNode.parentNode; //记录父节点;
|
||||||
|
//自动获得焦点, 格式化需要时间
|
||||||
|
if (this.props.autoFocus) {
|
||||||
|
setTimeout(() => {
|
||||||
|
this.editor.setPosition({
|
||||||
|
column: 4,
|
||||||
|
lineNumber: 2
|
||||||
|
});
|
||||||
|
this.editor.focus();
|
||||||
|
}, 100);
|
||||||
|
}
|
||||||
|
//快捷键编码
|
||||||
|
let CtrlCmd = 2048;
|
||||||
|
let KEY_S = 49;
|
||||||
|
let Shift = 1024;
|
||||||
|
let KEY_F = 36;
|
||||||
|
let KEY_B = 32;
|
||||||
|
let Escape = 9;
|
||||||
|
|
||||||
|
this.editor.addCommand(CtrlCmd | KEY_S, () => {
|
||||||
|
this.onSubmit(); //保存快捷键
|
||||||
|
});
|
||||||
|
this.editor.addCommand(CtrlCmd | Shift | KEY_F, () => {
|
||||||
|
this.fullScreen(); //全屏快捷键
|
||||||
|
});
|
||||||
|
this.editor.addCommand(CtrlCmd | KEY_B, () => {
|
||||||
|
this.format(); //美化快捷键
|
||||||
|
});
|
||||||
|
this.editor.addCommand(Escape, () => {
|
||||||
|
this.props.onEscape && this.props.onEscape();
|
||||||
|
});
|
||||||
|
//注册api
|
||||||
|
this.editor.submit = this.onSubmit;
|
||||||
|
this.editor.format = this.format;
|
||||||
|
this.editor.fullScreen = this.fullScreen;
|
||||||
|
this.editor.toJson = this.toJson;
|
||||||
|
this.editor.toObject = this.toObject;
|
||||||
|
this.editor.toFunction = this.toFunction;
|
||||||
|
//针对object情况,改写setValue和getValue api
|
||||||
|
if (this.props.language === 'object') {
|
||||||
|
let getValue = this.editor.getValue;
|
||||||
|
let setValue = this.editor.setValue;
|
||||||
|
this.editor.getValue = () => {
|
||||||
|
return getValue.call(this.editor).substring(this.valuePrefix.length);
|
||||||
|
};
|
||||||
|
this.editor.setValue = value => {
|
||||||
|
return setValue.call(this.editor, [this.valuePrefix + value]);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {
|
||||||
|
value,
|
||||||
|
placeholder,
|
||||||
|
style,
|
||||||
|
className,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
language,
|
||||||
|
theme,
|
||||||
|
options,
|
||||||
|
editorWillMount,
|
||||||
|
editorDidMount,
|
||||||
|
autoSubmit,
|
||||||
|
btnText,
|
||||||
|
btnSize,
|
||||||
|
registerApi
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
const { isFullScreen } = this.state;
|
||||||
|
this.valuePrefix = ''; //值前缀
|
||||||
|
if (language === 'object') this.valuePrefix = 'export default ';
|
||||||
|
if (!this.isFullScreenAction) {
|
||||||
|
//将值转换成目标值
|
||||||
|
let nowValue = this.valueHandler(value || placeholder, language);
|
||||||
|
let curValue = this.valueHandler(this.strValue, language);
|
||||||
|
if (nowValue !== curValue) this.strValue = nowValue;
|
||||||
|
if (language === 'object') this.strValue = this.strValue || placeholder || '{\n\t\n}'; //设置初始化值
|
||||||
|
if (language === 'json' && this.strValue === '{}') this.strValue = '{\n\t\n}';
|
||||||
|
}
|
||||||
|
this.isFullScreenAction = false;
|
||||||
|
//真实高亮语言
|
||||||
|
let tarLanguage = language;
|
||||||
|
if (language === 'object' || language === 'function') {
|
||||||
|
tarLanguage = 'javascript';
|
||||||
|
}
|
||||||
|
let classes = classNames('monaco-editor-wrap', {
|
||||||
|
['monaco-fullscreen']: !!isFullScreen,
|
||||||
|
['monaco-nofullscreen']: !isFullScreen
|
||||||
|
});
|
||||||
|
let tarStyle = Object.assign({ minHeight: 360, width, height }, style);
|
||||||
|
return (
|
||||||
|
<div className={className} style={tarStyle}>
|
||||||
|
<div ref={this.editorRef} style={{ height: '100%', minHeight: 300 }} className={classes}>
|
||||||
|
<MonacoEditor
|
||||||
|
value={this.valuePrefix + this.strValue}
|
||||||
|
width="100%"
|
||||||
|
height="100%"
|
||||||
|
language={tarLanguage}
|
||||||
|
theme={theme || window.__monacoTheme || 'vs-dark'}
|
||||||
|
options={isFullScreen ? this.fullScreenOptions : this.options}
|
||||||
|
onChange={this.onChange}
|
||||||
|
editorWillMount={editorWillMount}
|
||||||
|
editorDidMount={(editor, monaco) => {
|
||||||
|
this.editor = editor;
|
||||||
|
// registerApi({ editor });
|
||||||
|
this.registerApiAndSnippet(monaco);
|
||||||
|
editorDidMount && editorDidMount.call(this, arguments);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<a
|
||||||
|
onClick={this.fullScreen}
|
||||||
|
className="monaco_fullscreen_icon"
|
||||||
|
title={
|
||||||
|
isFullScreen ? `${this.i18n('cancelFullScreen')} cmd+shift+f` : `${this.i18n('fullScreen')} cmd+shift+f`
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Icon type={isFullScreen ? 'quxiaoquanping' : 'quanping'} />
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
//值变化
|
||||||
|
onChange(curValue) {
|
||||||
|
if (curValue === this.valuePrefix + this.strValue) return;
|
||||||
|
const { onAfterChange, language, autoSubmit, onChange } = this.props;
|
||||||
|
this.strValue = curValue; //记录当前格式
|
||||||
|
if (this.ct) clearTimeout(this.ct);
|
||||||
|
this.ct = setTimeout(() => {
|
||||||
|
this.position = this.editor.getPosition();
|
||||||
|
let ret = this.resultHandler(curValue, language);
|
||||||
|
if (autoSubmit) onChange && onChange(ret.value);
|
||||||
|
onAfterChange && onAfterChange(ret.value, ret.error, this.editor);
|
||||||
|
}, 300);
|
||||||
|
}
|
||||||
|
|
||||||
|
//提交动作
|
||||||
|
onSubmit() {
|
||||||
|
const { onSubmit, onChange, language } = this.props;
|
||||||
|
let curValue = this.editor.getValue();
|
||||||
|
let ret = this.resultHandler(curValue, language);
|
||||||
|
if (!ret.error) onChange && onChange(ret.value);
|
||||||
|
onSubmit && onSubmit(ret.value, ret.error, this.editor);
|
||||||
|
}
|
||||||
|
|
||||||
|
//值类型转换处理
|
||||||
|
valueHandler(value, language) {
|
||||||
|
let tarValue = value || '';
|
||||||
|
if (language === 'json') {
|
||||||
|
if (value && typeof value === 'object') {
|
||||||
|
tarValue = JSON.stringify(value, null, 2);
|
||||||
|
} else if (value && typeof value === 'string') {
|
||||||
|
try {
|
||||||
|
let ret = this.toJson(value);
|
||||||
|
if (!ret.error) tarValue = JSON.stringify(ret.value, null, 2);
|
||||||
|
} catch (err) {}
|
||||||
|
}
|
||||||
|
} else if (language === 'function') {
|
||||||
|
if (typeof value === 'function') {
|
||||||
|
tarValue = value.toString();
|
||||||
|
}
|
||||||
|
if (tarValue && typeof tarValue === 'string') {
|
||||||
|
tarValue = js_beautify(tarValue, { indent_size: 2, indent_empty_lines: true });
|
||||||
|
}
|
||||||
|
} else if (language === 'object') {
|
||||||
|
//先转成对象,在进行序列化和格式化;
|
||||||
|
value = value || {};
|
||||||
|
if (value && typeof value === 'object') {
|
||||||
|
try {
|
||||||
|
tarValue = serialize(value, { unsafe: true });
|
||||||
|
tarValue = js_beautify(tarValue, { indent_size: 2, indent_empty_lines: true });
|
||||||
|
} catch (err) {}
|
||||||
|
} else if (typeof value === 'string') {
|
||||||
|
try {
|
||||||
|
let ret = this.resultHandler(value, 'object');
|
||||||
|
tarValue = ret.error ? ret.value : serialize(ret.value, { unsafe: true });
|
||||||
|
tarValue = js_beautify(tarValue, { indent_size: 2, indent_empty_lines: true });
|
||||||
|
} catch (err) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return tarValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
//结果处理
|
||||||
|
resultHandler(value, language) {
|
||||||
|
let ret = { value };
|
||||||
|
if (language === 'json') {
|
||||||
|
ret = this.toJson(value);
|
||||||
|
} else if (language === 'object') {
|
||||||
|
ret = this.toObject(value);
|
||||||
|
} else if (language === 'function') {
|
||||||
|
ret = this.toFunction(value);
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
//设置全屏时的动作
|
||||||
|
fullScreen() {
|
||||||
|
if (!this.editorRef) return;
|
||||||
|
//还原到原来位置;
|
||||||
|
this.position = this.editor.getPosition();
|
||||||
|
if (this.state.isFullScreen) {
|
||||||
|
if (this.editorParentNode) {
|
||||||
|
if (this.editorParentNode.firstChild) {
|
||||||
|
this.editorParentNode.insertBefore(this.editorNode, this.editorParentNode.firstChild);
|
||||||
|
} else {
|
||||||
|
this.editorParentNode.appendChild(this.editorNode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
document.body.appendChild(this.editorNode);
|
||||||
|
}
|
||||||
|
let nextFs = !this.state.isFullScreen;
|
||||||
|
this.isFullScreenAction = true; //记录是全屏幕操作
|
||||||
|
this.setState(
|
||||||
|
{
|
||||||
|
isFullScreen: nextFs
|
||||||
|
},
|
||||||
|
() => {
|
||||||
|
this.editor.updateOptions(nextFs ? this.fullScreenOptions : this.options);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
//美化代码
|
||||||
|
format() {
|
||||||
|
if (!this.editor) return;
|
||||||
|
if (/^\$_obj?\{.*?\}$/m.test(this.editor.getValue())) return;
|
||||||
|
if (this.props.language === 'json' || this.props.language === 'object' || this.props.language === 'function') {
|
||||||
|
let tarValue = js_beautify(this.editor.getValue(), { indent_size: 2 });
|
||||||
|
this.editor.setValue(tarValue);
|
||||||
|
} else if (this.props.language === 'less' || this.props.language === 'css' || this.props.language === 'scss') {
|
||||||
|
let tarValue = css_beautify(this.editor.getValue(), { indent_size: 2 });
|
||||||
|
this.editor.setValue(tarValue);
|
||||||
|
} else {
|
||||||
|
this.editor.getAction('editor.action.formatDocument').run();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//校验是否是json
|
||||||
|
toJson(value) {
|
||||||
|
try {
|
||||||
|
let obj = new Function(`'use strict'; return ${value.replace(/[\r\n\t]/g, '')}`)();
|
||||||
|
if (typeof obj === 'object' && obj) {
|
||||||
|
let tarValue = new Function(`'use strict'; return ${value}`)();
|
||||||
|
return { value: JSON.parse(JSON.stringify(tarValue)) };
|
||||||
|
}
|
||||||
|
return { error: this.i18n('jsonIllegal'), value };
|
||||||
|
} catch (err) {
|
||||||
|
return { error: err, value };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//校验是否为object对象
|
||||||
|
toObject(value) {
|
||||||
|
try {
|
||||||
|
let obj = new Function(`'use strict';return ${value}`)();
|
||||||
|
if (obj && typeof obj === 'object') {
|
||||||
|
if (jsonuri.isCircular(obj)) return { error: this.i18n('circularRef'), value };
|
||||||
|
return { value: obj };
|
||||||
|
} else {
|
||||||
|
return { error: this.i18n('objectIllegal'), value };
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
return { error: err, value };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//校验是否为function
|
||||||
|
toFunction(value) {
|
||||||
|
try {
|
||||||
|
let fun = new Function(`'use strict';return ${value}`)();
|
||||||
|
if (fun && typeof fun === 'function') {
|
||||||
|
return { value: fun };
|
||||||
|
} else {
|
||||||
|
return { error: this.i18n('functionIllegal'), value };
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
return { error: err, value };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//注册api和代码片段
|
||||||
|
registerApiAndSnippet(monaco) {
|
||||||
|
if (registerApiAndSnippetStatus) return;
|
||||||
|
registerApiAndSnippetStatus = true;
|
||||||
|
//注册this.提示的方法;
|
||||||
|
let thisSuggestions = [];
|
||||||
|
Snippets.map(item => {
|
||||||
|
if (!item.label || !item.kind || !item.insertText) return;
|
||||||
|
let tarItem = Object.assign(item, {
|
||||||
|
label: item.label,
|
||||||
|
kind: monaco.languages.CompletionItemKind[item.kind],
|
||||||
|
insertText: item.insertText
|
||||||
|
});
|
||||||
|
if (item.insertTextRules)
|
||||||
|
tarItem.insertTextRules = monaco.languages.CompletionItemInsertTextRule[item.insertTextRules];
|
||||||
|
thisSuggestions.push(tarItem);
|
||||||
|
});
|
||||||
|
monaco.languages.registerCompletionItemProvider('javascript', {
|
||||||
|
provideCompletionItems: (model, position) => {
|
||||||
|
let textUntilPosition = model.getValueInRange({
|
||||||
|
startLineNumber: position.lineNumber,
|
||||||
|
startColumn: 1,
|
||||||
|
endLineNumber: position.lineNumber,
|
||||||
|
endColumn: position.column
|
||||||
|
});
|
||||||
|
let match = textUntilPosition.match(/(^this\.)|(\sthis\.)/);
|
||||||
|
let suggestions = match ? thisSuggestions : [];
|
||||||
|
return { suggestions: suggestions };
|
||||||
|
},
|
||||||
|
triggerCharacters: ['.']
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class Title extends Component<{ title: TitleContent; onClick?: () => void }> {
|
||||||
|
static defaultProps = {
|
||||||
|
locale: 'zh-CN',
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const self = this;
|
||||||
|
const { locale, messages, id, value, onChange, __parent = {}, rules, objectButtonProps, ...restProps } = this.props;
|
||||||
|
let tarRestProps = { ...restProps };
|
||||||
|
let tarObjProps = { ...objectButtonProps };
|
||||||
|
tarObjProps.className = 'luna-monaco-button';
|
||||||
|
if (tarRestProps['data-meta']) {
|
||||||
|
delete tarRestProps['data-meta'];
|
||||||
|
tarObjProps['data-meta'] = 'Field';
|
||||||
|
}
|
||||||
|
tarObjProps.id = id;
|
||||||
|
tarObjProps.value = value;
|
||||||
|
tarObjProps.onChange = onChange;
|
||||||
|
let tarRule = [];
|
||||||
|
//判断,如果是json,function, object等类型,自动追加校验规则;
|
||||||
|
if (tarRestProps.language && ['json', 'function', 'object'].includes(tarRestProps.language)) {
|
||||||
|
if (['json', 'object'].includes(tarRestProps.language)) {
|
||||||
|
tarRule.push({
|
||||||
|
validator: function(rule, value, callback) {
|
||||||
|
if (typeof value !== 'object') {
|
||||||
|
callback(self.i18n('formatError'));
|
||||||
|
} else {
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
tarRule.push({
|
||||||
|
validator: function(rule, value, callback) {
|
||||||
|
if (typeof value !== 'function') {
|
||||||
|
callback(self.i18n('formatError'));
|
||||||
|
} else {
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (rules && Array.isArray(rules) && rules.length) {
|
||||||
|
tarRule = tarRule.concat(rules);
|
||||||
|
}
|
||||||
|
if (__parent && __parent.rules) {
|
||||||
|
tarRule = tarRule.concat(__parent.rules);
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<ObjectButton
|
||||||
|
locale={locale}
|
||||||
|
messages={messages}
|
||||||
|
{...tarObjProps}
|
||||||
|
__parent={__parent}
|
||||||
|
>
|
||||||
|
<FormItem name="nrs_temp_field" rules={tarRule}>
|
||||||
|
<MonacoEditorDefaultView {...tarRestProps} />
|
||||||
|
</FormItem>
|
||||||
|
</ObjectButton>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
93
packages/plugin-setters/src/index.scss
Normal file
93
packages/plugin-setters/src/index.scss
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
// mixin
|
||||||
|
.lowcode-setter-mixin > * {
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
.lowcode-setter-mixin {
|
||||||
|
width: 86%;
|
||||||
|
}
|
||||||
|
.lowcode-setter-mixin .next-input {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.lowcode-setter-mixin .next-select-trigger {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
// function
|
||||||
|
:global {
|
||||||
|
.nrs-monaco-form {
|
||||||
|
.next-form-item:last-child {
|
||||||
|
margin: 0 !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.monaco-editor-wrap {
|
||||||
|
min-height: 300px;
|
||||||
|
.monaco_fullscreen_icon {
|
||||||
|
position: absolute;
|
||||||
|
line-height: 1;
|
||||||
|
z-index: 7;
|
||||||
|
color: #ddd;
|
||||||
|
&:hover {
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.btns-eare {
|
||||||
|
text-align: left;
|
||||||
|
line-height: initial;
|
||||||
|
margin-top: 5px;
|
||||||
|
// button{
|
||||||
|
// margin-right: 10px;
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
&.monaco-nofullscreen {
|
||||||
|
position: relative !important;
|
||||||
|
.monaco_fullscreen_icon {
|
||||||
|
position: absolute;
|
||||||
|
top: 5px;
|
||||||
|
right: 5px;
|
||||||
|
line-height: 1;
|
||||||
|
z-index: 7;
|
||||||
|
i:before {
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&.monaco-fullscreen {
|
||||||
|
position: fixed !important;
|
||||||
|
height: 100% !important;
|
||||||
|
width: 100% !important;
|
||||||
|
border: 0;
|
||||||
|
margin: 0;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
z-index: 1001;
|
||||||
|
overflow: hidden;
|
||||||
|
.monaco_fullscreen_icon {
|
||||||
|
top: 10px;
|
||||||
|
right: 10px;
|
||||||
|
i:before {
|
||||||
|
font-size: 24px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.luna-monaco-button button {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.luna-monaco-button-dialog {
|
||||||
|
.next-dialog-body {
|
||||||
|
padding: 0;
|
||||||
|
.next-form-item {
|
||||||
|
height: 100%;
|
||||||
|
margin-bottom: 0;
|
||||||
|
.next-form-item-control,
|
||||||
|
.next-form-item-control > div {
|
||||||
|
height: 100% !important;
|
||||||
|
}
|
||||||
|
.next-form-item-help {
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
242
packages/plugin-setters/src/locale/snippets.js
Normal file
242
packages/plugin-setters/src/locale/snippets.js
Normal file
@ -0,0 +1,242 @@
|
|||||||
|
export default [
|
||||||
|
{
|
||||||
|
label: 'constants',
|
||||||
|
kind: 'Class',
|
||||||
|
insertText: 'constants',
|
||||||
|
detail: '应用全局常量',
|
||||||
|
documentation: '应用范围定义的通用常量'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'utils',
|
||||||
|
kind: 'Class',
|
||||||
|
insertText: 'utils',
|
||||||
|
detail: '应用全局公共函数',
|
||||||
|
documentation: '应用范围扩展的公共函数'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'state',
|
||||||
|
kind: 'Enum',
|
||||||
|
insertText: 'state',
|
||||||
|
detail: '当前所在容器组件内部状态',
|
||||||
|
documentation: 'React Class内部状态state'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'setState',
|
||||||
|
kind: 'Function',
|
||||||
|
insertText: 'setState({\n\t$0\n})',
|
||||||
|
insertTextRules: 'InsertAsSnippet',
|
||||||
|
detail: '设置当前所在容器组件的state数据',
|
||||||
|
documentation: '原生React方法,会自动更新组件视图'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'reloadDataSource',
|
||||||
|
kind: 'Function',
|
||||||
|
insertText: 'reloadDataSource(${1:${2:namespace}, ${3:false}, ${4:callback}})',
|
||||||
|
insertTextRules: 'InsertAsSnippet',
|
||||||
|
detail: '刷新当前所在的容器组件',
|
||||||
|
documentation: '触发当前所在的容器组件,重新发送异步请求,并用最新数据更新视图'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'location',
|
||||||
|
kind: 'Class',
|
||||||
|
insertText: 'location',
|
||||||
|
detail: '路由解析对象'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'location.query',
|
||||||
|
kind: 'Value',
|
||||||
|
insertText: 'location.query.${1:xxxx}',
|
||||||
|
insertTextRules: 'InsertAsSnippet',
|
||||||
|
detail: '从路由解析对象中获取参数信息'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'history',
|
||||||
|
kind: 'Class',
|
||||||
|
insertText: 'history',
|
||||||
|
detail: '路由历史对象'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'React',
|
||||||
|
kind: 'Keyword',
|
||||||
|
insertText: 'React',
|
||||||
|
detail: 'React对象'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'ReactDOM',
|
||||||
|
kind: 'Keyword',
|
||||||
|
insertText: 'ReactDOM',
|
||||||
|
detail: 'ReactDom对象'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'ReactDOM.findDOMNode',
|
||||||
|
kind: 'Function',
|
||||||
|
insertText: 'ReactDOM.findDOMNode(${1:this.refs.xxxx})',
|
||||||
|
insertTextRules: 'InsertAsSnippet',
|
||||||
|
detail: 'ReactDom查找真实dom node'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Dialog.alert',
|
||||||
|
kind: 'Method',
|
||||||
|
insertText: [
|
||||||
|
'Dialog.alert({',
|
||||||
|
"\tcontent: '${1:Alert content}',",
|
||||||
|
"\ttitle: '${2:Title}',",
|
||||||
|
'\tonOk: () => {',
|
||||||
|
'\t\t$3',
|
||||||
|
'\t}',
|
||||||
|
'})'
|
||||||
|
].join('\n'),
|
||||||
|
insertTextRules: 'InsertAsSnippet',
|
||||||
|
detail: 'alert弹框 By Fusion'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Dialog.confirm',
|
||||||
|
kind: 'Method',
|
||||||
|
insertText: [
|
||||||
|
'Dialog.confirm({',
|
||||||
|
"\tcontent: '${1:Confirm content}',",
|
||||||
|
"\ttitle: '${2:Title}',",
|
||||||
|
'\tonOk: () => {',
|
||||||
|
'\t\t$3',
|
||||||
|
'\t},',
|
||||||
|
'\tonCancel: () => {',
|
||||||
|
'\t\t$4',
|
||||||
|
'\t}',
|
||||||
|
'})'
|
||||||
|
].join('\n'),
|
||||||
|
insertTextRules: 'InsertAsSnippet',
|
||||||
|
detail: '确认弹出框 By Fusion'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Message.success',
|
||||||
|
kind: 'Method',
|
||||||
|
insertText: 'Message.success(${1:content})',
|
||||||
|
insertTextRules: 'InsertAsSnippet',
|
||||||
|
detail: '成功反馈提示 By Fusion'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Message.error',
|
||||||
|
kind: 'Method',
|
||||||
|
insertText: 'Message.error(${1:content})',
|
||||||
|
insertTextRules: 'InsertAsSnippet',
|
||||||
|
detail: '错误反馈提示 By Fusion'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Message.help',
|
||||||
|
kind: 'Method',
|
||||||
|
insertText: 'Message.help(${1:content})',
|
||||||
|
insertTextRules: 'InsertAsSnippet',
|
||||||
|
detail: '帮助反馈提示 By Fusion'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Message.loading',
|
||||||
|
kind: 'Method',
|
||||||
|
insertText: 'Message.loading(${1:content})',
|
||||||
|
insertTextRules: 'InsertAsSnippet',
|
||||||
|
detail: 'loading反馈提示 By Fusion'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Message.notice',
|
||||||
|
kind: 'Method',
|
||||||
|
insertText: 'Message.notice(${1:content})',
|
||||||
|
insertTextRules: 'InsertAsSnippet',
|
||||||
|
detail: '注意反馈提示 By Fusion'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Message.waining',
|
||||||
|
kind: 'Method',
|
||||||
|
insertText: 'Message.waining(${1:content})',
|
||||||
|
insertTextRules: 'InsertAsSnippet',
|
||||||
|
detail: '警告反馈提示 By Fusion'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Modal.confirm',
|
||||||
|
kind: 'Method',
|
||||||
|
insertText: [
|
||||||
|
'Modal.confirm({',
|
||||||
|
"\tcontent: '${1:Confirm content}',",
|
||||||
|
"\ttitle: '${2:Title}',",
|
||||||
|
'\tonOk: () => {',
|
||||||
|
'\t\t$3',
|
||||||
|
'\t},',
|
||||||
|
'\tonCancel: () => {',
|
||||||
|
'\t\t$4',
|
||||||
|
'\t}',
|
||||||
|
'})'
|
||||||
|
].join('\n'),
|
||||||
|
insertTextRules: 'InsertAsSnippet',
|
||||||
|
detail: '确认弹出框 By Antd'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Modal.info',
|
||||||
|
kind: 'Method',
|
||||||
|
insertText: [
|
||||||
|
'Modal.info({',
|
||||||
|
"\tcontent: '${1:Info content}',",
|
||||||
|
"\ttitle: '${2:Title}',",
|
||||||
|
'\tonOk: () => {',
|
||||||
|
'\t\t$3',
|
||||||
|
'\t},',
|
||||||
|
'\tonCancel: () => {',
|
||||||
|
'\t\t$4',
|
||||||
|
'\t}',
|
||||||
|
'})'
|
||||||
|
].join('\n'),
|
||||||
|
insertTextRules: 'InsertAsSnippet',
|
||||||
|
detail: '信息弹出框 By Antd'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Modal.success',
|
||||||
|
kind: 'Method',
|
||||||
|
insertText: [
|
||||||
|
'Modal.success({',
|
||||||
|
"\tcontent: '${1:Success content}',",
|
||||||
|
"\ttitle: '${2:Title}',",
|
||||||
|
'\tonOk: () => {',
|
||||||
|
'\t\t$3',
|
||||||
|
'\t},',
|
||||||
|
'\tonCancel: () => {',
|
||||||
|
'\t\t$4',
|
||||||
|
'\t}',
|
||||||
|
'})'
|
||||||
|
].join('\n'),
|
||||||
|
insertTextRules: 'InsertAsSnippet',
|
||||||
|
detail: '成功弹出框 By Antd'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Modal.error',
|
||||||
|
kind: 'Method',
|
||||||
|
insertText: [
|
||||||
|
'Modal.error({',
|
||||||
|
"\tcontent: '${1:Error content}',",
|
||||||
|
"\ttitle: '${2:Title}',",
|
||||||
|
'\tonOk: () => {',
|
||||||
|
'\t\t$3',
|
||||||
|
'\t},',
|
||||||
|
'\tonCancel: () => {',
|
||||||
|
'\t\t$4',
|
||||||
|
'\t}',
|
||||||
|
'})'
|
||||||
|
].join('\n'),
|
||||||
|
insertTextRules: 'InsertAsSnippet',
|
||||||
|
detail: '错误弹出框 By Antd'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Modal.warning',
|
||||||
|
kind: 'Method',
|
||||||
|
insertText: [
|
||||||
|
'Modal.warning({',
|
||||||
|
"\tcontent: '${1:Warning content}',",
|
||||||
|
"\ttitle: '${2:Title}',",
|
||||||
|
'\tonOk: () => {',
|
||||||
|
'\t\t$3',
|
||||||
|
'\t},',
|
||||||
|
'\tonCancel: () => {',
|
||||||
|
'\t\t$4',
|
||||||
|
'\t}',
|
||||||
|
'})'
|
||||||
|
].join('\n'),
|
||||||
|
insertTextRules: 'InsertAsSnippet',
|
||||||
|
detail: '警告弹出框 By Antd'
|
||||||
|
}
|
||||||
|
];
|
||||||
21
packages/plugin-setters/src/locale/utils.js
Normal file
21
packages/plugin-setters/src/locale/utils.js
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import IntlMessageFormat from 'intl-messageformat';
|
||||||
|
|
||||||
|
export const isJSExpression = (obj = '') => {
|
||||||
|
if(obj && typeof obj === 'object' && obj.type === 'JSExpression') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用于构造国际化字符串处理函数
|
||||||
|
* @param {*} locale 国际化标识,例如 zh-CN、en-US
|
||||||
|
* @param {*} messages 国际化语言包
|
||||||
|
*/
|
||||||
|
export const generateI18n = (locale = 'zh-CN', messages = {}) => {
|
||||||
|
return function (key, values = {}) {
|
||||||
|
if (!messages || !messages[key]) return '';
|
||||||
|
const formater = new IntlMessageFormat(messages[key], locale);
|
||||||
|
return formater.format(values);
|
||||||
|
};
|
||||||
|
}
|
||||||
32
packages/plugin-setters/src/locale/zh-CN.js
Normal file
32
packages/plugin-setters/src/locale/zh-CN.js
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
export default {
|
||||||
|
// function
|
||||||
|
setting: '点击设置',
|
||||||
|
edit: '编辑',
|
||||||
|
submitConfirm: '确认提交 cmd+s',
|
||||||
|
close: '关闭 esc',
|
||||||
|
fullScreen: '全屏',
|
||||||
|
cancelFullScreen: '取消全屏',
|
||||||
|
jsonIllegal: '非json格式',
|
||||||
|
functionIllegal: '非function格式',
|
||||||
|
objectIllegal: '非object格式',
|
||||||
|
circularRef: '对象中出现循环引用的对象',
|
||||||
|
formatError: '格式错误',
|
||||||
|
saved: '已保存',
|
||||||
|
// expression
|
||||||
|
valueIllegal: '值类型为对象类型,与当前组件属性设置的控件类型不匹配,请在属性“代码编辑模式”下进行编辑',
|
||||||
|
jsExpression: '请输入JS表达式',
|
||||||
|
// Mixin
|
||||||
|
input: '字符串Input',
|
||||||
|
textarea: '多行字符串Textarea',
|
||||||
|
expression: '变量控件Expression',
|
||||||
|
monacoEditor: '编辑器MonacoEditor',
|
||||||
|
numberPicker: '数字NumberPicker',
|
||||||
|
bool: '布尔Switch',
|
||||||
|
datePicker: '日期选择DatePicker',
|
||||||
|
select: '下拉选择Select',
|
||||||
|
radio: '单项选择RadioGroup',
|
||||||
|
list: '数组List',
|
||||||
|
object: '对象ObjectButton',
|
||||||
|
reactNode: '节点类型ReactNode',
|
||||||
|
typeError: 'Minix组件属性Types配置错误,存在不支持类型[{type}],请检查组件属性配置',
|
||||||
|
};
|
||||||
302
packages/plugin-setters/src/mixin-setter.tsx
Normal file
302
packages/plugin-setters/src/mixin-setter.tsx
Normal file
@ -0,0 +1,302 @@
|
|||||||
|
import React, { PureComponent } from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import { Dropdown, Button, Menu, Icon, Input, NumberPicker, Switch, Select, Radio, DatePicker } from '@alifd/next';
|
||||||
|
import MonacoEditor from '@ali/iceluna-comp-monaco-editor';
|
||||||
|
|
||||||
|
import { isJSExpression, generateI18n } from './locale/utils';
|
||||||
|
import Expression from './expression-setter';
|
||||||
|
import zhCN from './locale/zh-CN';
|
||||||
|
import './index.scss';
|
||||||
|
|
||||||
|
const { Group: RadioGroup } = Radio;
|
||||||
|
// const isJSExpression = (obj) => {
|
||||||
|
// if(typeof obj === 'object' && obj.type === 'JSExpression') {
|
||||||
|
// return true;
|
||||||
|
// }
|
||||||
|
// return false;
|
||||||
|
// }
|
||||||
|
|
||||||
|
export default class Mixin extends PureComponent {
|
||||||
|
static displayName = 'Mixin';
|
||||||
|
static propTypes = {
|
||||||
|
locale: PropTypes.string,
|
||||||
|
messages: PropTypes.object,
|
||||||
|
defaultType: PropTypes.string,
|
||||||
|
types: PropTypes.arrayOf(PropTypes.string),
|
||||||
|
onlyChangeType: PropTypes.bool,
|
||||||
|
inputProps: PropTypes.object,
|
||||||
|
expressionProps: PropTypes.object,
|
||||||
|
monacoEditorProps: PropTypes.object,
|
||||||
|
switchProps: PropTypes.object,
|
||||||
|
selectProps: PropTypes.object,
|
||||||
|
radioGroupProps: PropTypes.object,
|
||||||
|
};
|
||||||
|
static defaultProps = {
|
||||||
|
locale: 'zh-CN',
|
||||||
|
messages: zhCN
|
||||||
|
};
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
let type = judgeTypeHandler(props, {});
|
||||||
|
this.i18n = generateI18n(props.locale, props.messages);
|
||||||
|
this.state = {
|
||||||
|
preType: type,
|
||||||
|
type
|
||||||
|
};
|
||||||
|
}
|
||||||
|
static getDerivedStateFromProps(props, state) {
|
||||||
|
if ('value' in props) {
|
||||||
|
let curType = judgeTypeHandler(props, state);
|
||||||
|
if (curType !== state.preType) {
|
||||||
|
return {
|
||||||
|
type: curType
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
changeType(type) {
|
||||||
|
if (typeof type === 'object' || type === this.state.type) return;
|
||||||
|
let { onlyChangeType, value, onChange } = this.props;
|
||||||
|
if (onlyChangeType) {
|
||||||
|
this.setState({ type });
|
||||||
|
onChange && onChange(value);
|
||||||
|
} else {
|
||||||
|
let newValue = undefined;
|
||||||
|
if (this.typeMap[type]['props']) {
|
||||||
|
if (this.typeMap[type]['props']['value'] !== undefined) {
|
||||||
|
newValue = this.typeMap[type]['props']['value'];
|
||||||
|
} else if (this.typeMap[type]['props']['defaultValue'] !== undefined) {
|
||||||
|
newValue = this.typeMap[type]['props']['defaultValue'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (type === 'BoolSetter' && newValue === undefined) {
|
||||||
|
newValue = false; //给切换到switch默认值为false
|
||||||
|
}
|
||||||
|
this.setState({ type });
|
||||||
|
onChange && onChange(newValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
render() {
|
||||||
|
const {
|
||||||
|
style = {},
|
||||||
|
className,
|
||||||
|
locale,
|
||||||
|
messages,
|
||||||
|
types = [],
|
||||||
|
defaultType,
|
||||||
|
// inputProps,
|
||||||
|
// expressionProps,
|
||||||
|
// monacoEditorProps,
|
||||||
|
// numberPickerProps,
|
||||||
|
// switchProps,
|
||||||
|
// selectProps,
|
||||||
|
// radioGroupProps,
|
||||||
|
...restProps
|
||||||
|
} = this.props;
|
||||||
|
this.typeMap = {
|
||||||
|
StringSetter: {
|
||||||
|
label: this.i18n('input'),
|
||||||
|
component: Input,
|
||||||
|
// props: inputProps
|
||||||
|
},
|
||||||
|
ExpressionSetter: {
|
||||||
|
label: this.i18n('expression'),
|
||||||
|
component: Expression,
|
||||||
|
// props: expressionProps
|
||||||
|
},
|
||||||
|
// MonacoEditor: {
|
||||||
|
// label: this.i18n('monacoEditor'),
|
||||||
|
// component: MonacoEditor,
|
||||||
|
// props: monacoEditorProps
|
||||||
|
// },
|
||||||
|
NumberSetter: {
|
||||||
|
label: this.i18n('numberPicker'),
|
||||||
|
component: NumberPicker,
|
||||||
|
},
|
||||||
|
BoolSetter: {
|
||||||
|
label: this.i18n('bool'),
|
||||||
|
component: Switch,
|
||||||
|
},
|
||||||
|
SelectSetter: {
|
||||||
|
label: this.i18n('select'),
|
||||||
|
component: Select,
|
||||||
|
},
|
||||||
|
RadioGroupSetter: {
|
||||||
|
label: this.i18n('radio'),
|
||||||
|
component: RadioGroup,
|
||||||
|
// props: radioGroupProps
|
||||||
|
},
|
||||||
|
TextAreaSetter: {
|
||||||
|
label: this.i18n('textarea'),
|
||||||
|
component: Input.TextArea,
|
||||||
|
},
|
||||||
|
DateSetter: {
|
||||||
|
label: this.i18n('DatePicker'),
|
||||||
|
component: DatePicker,
|
||||||
|
},
|
||||||
|
DateYearSetter: {
|
||||||
|
label: this.i18n('DatePicker'),
|
||||||
|
component: DatePicker,
|
||||||
|
},
|
||||||
|
DateMonthSetter: {
|
||||||
|
label: this.i18n('DatePicker'),
|
||||||
|
component: DatePicker,
|
||||||
|
},
|
||||||
|
DateRangeSetter: {
|
||||||
|
label: this.i18n('DatePicker'),
|
||||||
|
component: DatePicker,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let realTypes = [];
|
||||||
|
types.forEach( el => {
|
||||||
|
const { name, props } = el;
|
||||||
|
if (this.typeMap[name]) {
|
||||||
|
this.typeMap[name].props = props;
|
||||||
|
realTypes.push(name);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
let moreBtnNode = null;
|
||||||
|
//如果只有2种,且有变量表达式,则直接展示变量按钮
|
||||||
|
if (realTypes.length > 1) {
|
||||||
|
let isTwoType = !!(realTypes.length === 2 && ~realTypes.indexOf('ExpressionSetter'));
|
||||||
|
let btnProps = {
|
||||||
|
size: 'small',
|
||||||
|
text: true,
|
||||||
|
style: {
|
||||||
|
position: 'absolute',
|
||||||
|
left: '100%',
|
||||||
|
top: 0,
|
||||||
|
bottom: 0,
|
||||||
|
margin: 'auto 0 auto 8px',
|
||||||
|
padding: 0,
|
||||||
|
width: 16,
|
||||||
|
height: 16,
|
||||||
|
lineHeight: '16px',
|
||||||
|
textAlign: 'center'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if (isTwoType) {
|
||||||
|
btnProps.onClick = this.changeType.bind(this, realTypes.indexOf(this.state.type) ? realTypes[0] : realTypes[1]);
|
||||||
|
}
|
||||||
|
let triggerNode = (
|
||||||
|
<Button {...btnProps} size={isTwoType ? 'large' : 'small'}>
|
||||||
|
<Icon type={isTwoType ? 'ceshi' : 'ellipsis'} />
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
if (isTwoType) {
|
||||||
|
moreBtnNode = triggerNode;
|
||||||
|
} else {
|
||||||
|
let MenuItems = [];
|
||||||
|
realTypes.map(type => {
|
||||||
|
if (this.typeMap[type]) {
|
||||||
|
MenuItems.push(<Menu.Item key={type}>{this.typeMap[type]['label']}</Menu.Item>);
|
||||||
|
} else {
|
||||||
|
console.error(
|
||||||
|
this.i18n('typeError', {
|
||||||
|
type
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
let MenuNode = (
|
||||||
|
<Menu
|
||||||
|
selectMode="single"
|
||||||
|
hasSelectedIcon={false}
|
||||||
|
selectedKeys={this.state.type}
|
||||||
|
onItemClick={this.changeType.bind(this)}
|
||||||
|
>
|
||||||
|
{MenuItems}
|
||||||
|
</Menu>
|
||||||
|
);
|
||||||
|
|
||||||
|
moreBtnNode = (
|
||||||
|
<Dropdown trigger={triggerNode} triggerType="click">
|
||||||
|
{MenuNode}
|
||||||
|
</Dropdown>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let TargetNode = this.typeMap[this.state.type] ? this.typeMap[this.state.type]['component'] : 'div';
|
||||||
|
let targetProps = this.typeMap[this.state.type] ? this.typeMap[this.state.type]['props'] : {};
|
||||||
|
|
||||||
|
// 特殊处理Switch的值
|
||||||
|
if (['BoolSetter', 'RadioGroupSetter'].includes(this.state.type)) {
|
||||||
|
restProps.checked = this.props.checked !== undefined ? this.props.checked : this.props.value;
|
||||||
|
}
|
||||||
|
//判断如果Mixin内部有设置onChange, 则同时触发2处onChange
|
||||||
|
if (targetProps && targetProps.onChange && typeof targetProps.onChange === 'function') {
|
||||||
|
let tarOnChange = targetProps.onChange;
|
||||||
|
targetProps.onChange = function() {
|
||||||
|
tarOnChange.apply(null, arguments);
|
||||||
|
restProps.onChange && restProps.onChange.apply(null, arguments);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
let tarStyle = { position: 'relative', ...style };
|
||||||
|
let classes = classNames(className, 'lowcode-setter-mixin');
|
||||||
|
return (
|
||||||
|
<div style={tarStyle} className={classes}>
|
||||||
|
<TargetNode {...restProps} {...targetProps} />
|
||||||
|
{moreBtnNode}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 判断值类型
|
||||||
|
function judgeTypeHandler(props, state) {
|
||||||
|
let { defaultType, types, value } = props;
|
||||||
|
let selectProps: { dataSource: any[]; };
|
||||||
|
let radioGroupProps: { dataSource: any[]; };
|
||||||
|
let typeKeys: any[] = [];
|
||||||
|
types.forEach( el => {
|
||||||
|
typeKeys.push(el.name);
|
||||||
|
})
|
||||||
|
|
||||||
|
types.forEach((el: { name: string; props: {}; }) => {
|
||||||
|
if (el.name === 'SelectSetter') {selectProps === el.props;}
|
||||||
|
if (el.name === 'RadioGroupSetter') {radioGroupProps === el.props;}
|
||||||
|
})
|
||||||
|
if (!defaultType || !typeKeys) return;
|
||||||
|
// 如果defaultType不在typeKeys列表中,默认返回typeKeys的第一项
|
||||||
|
if (!typeKeys.includes(defaultType)) return typeKeys[0];
|
||||||
|
if (isJSExpression(value)) return 'ExpressionSetter';
|
||||||
|
if (value && typeof value === 'string') {
|
||||||
|
if (~typeKeys.indexOf('SelectSetter') && selectProps && selectProps.dataSource) {
|
||||||
|
let hasOption = selectProps.dataSource.some(item => {
|
||||||
|
if (typeof item === 'string' && item === value) return true;
|
||||||
|
if (typeof item === 'object' && item.value === value) return true;
|
||||||
|
});
|
||||||
|
if (hasOption) return 'SelectSetter';
|
||||||
|
}
|
||||||
|
if (~typeKeys.indexOf('RadioGroupSetter') && radioGroupProps && radioGroupProps.dataSource) {
|
||||||
|
let hasOption = radioGroupProps.dataSource.some(item => {
|
||||||
|
if (typeof item === 'object' && item.value === value) return true;
|
||||||
|
});
|
||||||
|
if (hasOption) return 'RadioGroupSetter';
|
||||||
|
}
|
||||||
|
if (~typeKeys.indexOf('StringSetter')) return 'StringSetter';
|
||||||
|
}
|
||||||
|
if (typeof value === 'number') {
|
||||||
|
if (~typeKeys.indexOf('SelectSetter') && selectProps && selectProps.dataSource) {
|
||||||
|
let hasOption = selectProps.dataSource.some(item => {
|
||||||
|
if (typeof item === 'object' && item.value === value) return true;
|
||||||
|
});
|
||||||
|
if (hasOption) return 'Select';
|
||||||
|
}
|
||||||
|
if (~typeKeys.indexOf('RadioGroupSetter') && radioGroupProps && radioGroupProps.dataSource) {
|
||||||
|
let hasOption = radioGroupProps.dataSource.some(item => {
|
||||||
|
if (typeof item === 'object' && item.value === value) return true;
|
||||||
|
});
|
||||||
|
if (hasOption) return 'RadioGroupSetter';
|
||||||
|
}
|
||||||
|
if (~typeKeys.indexOf('NumberSetter')) return 'NumberSetter';
|
||||||
|
}
|
||||||
|
if (~typeKeys.indexOf('NumberSetter') && typeof value === 'number') return 'NumberSetter';
|
||||||
|
if (~typeKeys.indexOf('BoolSetter') && (value === false || value === true)) return 'BoolSetter';
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
if (~typeKeys.indexOf('SelectSetter') && typeof value[0] === 'string') return 'SelectSetter';
|
||||||
|
}
|
||||||
|
return state.type || defaultType;
|
||||||
|
}
|
||||||
11
packages/react-renderer/.babelrc
Normal file
11
packages/react-renderer/.babelrc
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"presets": [
|
||||||
|
"@babel/preset-env",
|
||||||
|
"@babel/preset-react"
|
||||||
|
],
|
||||||
|
"plugins": [
|
||||||
|
"@babel/plugin-syntax-dynamic-import",
|
||||||
|
"@babel/plugin-proposal-class-properties",
|
||||||
|
"@babel/plugin-transform-runtime"
|
||||||
|
]
|
||||||
|
}
|
||||||
11
packages/react-renderer/.eslintignore
Normal file
11
packages/react-renderer/.eslintignore
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
# 忽略目录
|
||||||
|
build/
|
||||||
|
tests/
|
||||||
|
demo/
|
||||||
|
|
||||||
|
# node 覆盖率文件
|
||||||
|
coverage/
|
||||||
|
|
||||||
|
# 忽略文件
|
||||||
|
**/*-min.js
|
||||||
|
**/*.min.js
|
||||||
77
packages/react-renderer/.eslintrc.js
Normal file
77
packages/react-renderer/.eslintrc.js
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
module.exports = {
|
||||||
|
"root": true,
|
||||||
|
"extends": [
|
||||||
|
"eslint:recommended",
|
||||||
|
"plugin:react/recommended"
|
||||||
|
],
|
||||||
|
"plugins": [
|
||||||
|
"react"
|
||||||
|
],
|
||||||
|
"env": {
|
||||||
|
"browser": true,
|
||||||
|
"node": true,
|
||||||
|
"es6":true
|
||||||
|
},
|
||||||
|
"parser": "babel-eslint",
|
||||||
|
"parserOptions": {
|
||||||
|
"ecmaVersion": 6,
|
||||||
|
"sourceType": "module",
|
||||||
|
"ecmaFeatures": {
|
||||||
|
"jsx": true,
|
||||||
|
"experimentalObjectRestSpread": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"rules": {
|
||||||
|
"react/no-deprecated": 0, // react15.x关闭deprated警告
|
||||||
|
"constructor-super": 2,//要求在构造函数中有 super() 的调用
|
||||||
|
"no-case-declarations": 2,//不允许在 case 子句中使用词法声明
|
||||||
|
"no-class-assign": 2,//禁止修改类声明的变量
|
||||||
|
"no-compare-neg-zero": 2,//禁止负0比较
|
||||||
|
"no-cond-assign": 2,//禁止条件表达式中出现赋值操作符
|
||||||
|
"no-console": [2, {
|
||||||
|
"allow": ["info", "warn", "error"]
|
||||||
|
}],//禁止console
|
||||||
|
"no-const-assign": 2,//禁止修改 const 声明的变量
|
||||||
|
"no-constant-condition": 2,//禁止在条件中使用常量表达式
|
||||||
|
"no-control-regex": 2,//禁止在正则表达式中使用控制字符
|
||||||
|
"no-debugger": 2,//禁止debugger
|
||||||
|
"no-delete-var": 2,//禁止删除变量
|
||||||
|
"no-dupe-args": 2,//禁止重复的参数
|
||||||
|
"no-dupe-class-members": 2,//禁止类成员中出现重复的名称
|
||||||
|
"no-dupe-keys": 2,//禁止重复的键值
|
||||||
|
"no-duplicate-case": 2,//禁止重复的case条件
|
||||||
|
"no-empty-character-class": 2,//禁止在正则表达式中使用空字符集
|
||||||
|
"no-empty-pattern": 2,//禁止使用空解构模式
|
||||||
|
"no-empty": 2,//禁止出现空语句块
|
||||||
|
"no-ex-assign": 2,//禁止对 catch 子句的参数重新赋值
|
||||||
|
"no-extra-boolean-cast": 2,//禁止不必要的布尔转换
|
||||||
|
"no-extra-semi": 2,//禁止多余的分号
|
||||||
|
"no-fallthrough": 2,//禁止 case 语句落空
|
||||||
|
"no-func-assign": 2,//禁止对 function 声明重新赋值
|
||||||
|
"no-global-assign": 2,//禁止对全局对象重新赋值
|
||||||
|
"no-inner-declarations": 2,//禁止在嵌套的块中出现变量声明或 function 声明
|
||||||
|
"no-invalid-regexp": 2,//禁止 RegExp 构造函数中存在无效的正则表达式字符串
|
||||||
|
"no-irregular-whitespace": 2,//禁止在字符串和注释之外不规则的空白
|
||||||
|
"no-mixed-spaces-and-tabs": 2,//禁止空格和 tab 的混合缩进
|
||||||
|
"no-new-symbol": 2,//禁止对Symbol使用new关键字
|
||||||
|
"no-obj-calls": 2,//禁止把全局对象作为函数调用
|
||||||
|
"no-octal": 2,//禁止8进制的字面量
|
||||||
|
"no-redeclare": 2,//禁止多次声明同一变量
|
||||||
|
"no-regex-spaces": 2,//禁止正则表达式字面量中出现多个空格
|
||||||
|
"no-self-assign": 2,//禁止自我赋值
|
||||||
|
"no-sparse-arrays": 2,//禁用稀疏数组
|
||||||
|
"no-this-before-super": 2,//禁止在构造函数中,在调用 super() 之前使用 this 或 super
|
||||||
|
"no-undef": 2,//禁用未声明的变量,除非它们在 /*global */ 注释中被提到
|
||||||
|
"no-unexpected-multiline": 2,//禁止出现令人困惑的多行表达式
|
||||||
|
"no-unreachable": 2,//禁止在return、throw、continue 和 break 语句之后出现不可达代码
|
||||||
|
"no-unsafe-finally": 2,//禁止在 finally 语句块中出现控制流语句
|
||||||
|
"no-unsafe-negation": 2,//禁止在表达式左侧使用关系表达式
|
||||||
|
"no-unused-labels": 2,//禁用出现未使用过的标
|
||||||
|
"no-unused-vars": 2,//禁止出现未使用过的变量
|
||||||
|
"no-useless-escape": 2,//禁用不必要的转义字符
|
||||||
|
"require-yield": 2,//要求 generator 函数内有 yield
|
||||||
|
"use-isnan": 2,//使用isNan() 判断isNaN
|
||||||
|
"valid-typeof": 2//强制 typeof 表达式与有效的字符串进行比较
|
||||||
|
},
|
||||||
|
"settings": {}
|
||||||
|
}
|
||||||
18
packages/react-renderer/.gitignore
vendored
Normal file
18
packages/react-renderer/.gitignore
vendored
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
# See https://help.github.com/ignore-files/ for more about ignoring files.
|
||||||
|
|
||||||
|
# dependencies
|
||||||
|
/node_modules
|
||||||
|
|
||||||
|
# production
|
||||||
|
/build
|
||||||
|
/dist
|
||||||
|
/lib
|
||||||
|
|
||||||
|
# misc
|
||||||
|
.idea/
|
||||||
|
.happypack
|
||||||
|
.DS_Store
|
||||||
|
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
1
packages/react-renderer/.npmignore
Normal file
1
packages/react-renderer/.npmignore
Normal file
@ -0,0 +1 @@
|
|||||||
|
/src
|
||||||
4
packages/react-renderer/.prettierrc
Normal file
4
packages/react-renderer/.prettierrc
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"printWidth": 120,
|
||||||
|
"singleQuote": true
|
||||||
|
}
|
||||||
84
packages/react-renderer/CHANGELOG.md
Normal file
84
packages/react-renderer/CHANGELOG.md
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
# 更新日志
|
||||||
|
|
||||||
|
## [1.0.0] - 2019-11-13
|
||||||
|
### 新增
|
||||||
|
* iceluna-sdk基本功能;
|
||||||
|
|
||||||
|
## [1.0.1] - 2019-11-26
|
||||||
|
### 新增
|
||||||
|
* 粘贴schema时判断如果为非法schema触发illegalSchema.paste消息;
|
||||||
|
* 增加schema.paste 和 schema.copy消息;
|
||||||
|
* schema中支持__ignoreParse,标记不需解析成ReactDom的schema结构;
|
||||||
|
* 复制组件后,高亮选中新增的组件;
|
||||||
|
|
||||||
|
### 优化
|
||||||
|
* 修改sdk入口文件;
|
||||||
|
* engine的hidden属性改为suspended;
|
||||||
|
* websocket重连delay时间改为3s;
|
||||||
|
|
||||||
|
### 修复
|
||||||
|
* 当异步请求返回数据中success为false的时候,依然走请求成功回调,由用户自己判断错误处理方式;
|
||||||
|
* 修复预发发布以后异步请求配置变量不解析问题;
|
||||||
|
* 修复初始数据重新获取导致调试字段t不更新问题;
|
||||||
|
* 修复异步请求配置参数变量解析时机不及时导致参数解析出错问题;
|
||||||
|
|
||||||
|
## [1.0.2] - 2019-12-16
|
||||||
|
### 新增
|
||||||
|
* 画布支持缩放以及placeholder设置;
|
||||||
|
* window上挂载react和reactDOM;
|
||||||
|
* 支持国际化;
|
||||||
|
* 画布支持layout设置;
|
||||||
|
|
||||||
|
### 优化
|
||||||
|
* 若schema中的loop或者loopArgs变成默认值时删除该属性字段;
|
||||||
|
* 扩展模式时组件最小高度调整为10px;
|
||||||
|
* 采用react.forward透传compFactory和addonFactory高阶组件ref;
|
||||||
|
* 插件通过context透传国际化配置;
|
||||||
|
* 画布最外层组件支持设置固定宽高;
|
||||||
|
|
||||||
|
### 修复
|
||||||
|
* 修发布后的页面被嵌入到iframe中变量解析时的跨域问题;
|
||||||
|
* 修复form循环问题;
|
||||||
|
* 修复模型结构中null及undefined被转成空对象问题;
|
||||||
|
* 修复fetch请求类型为PUT和DELETE时参数序列化问题;
|
||||||
|
* 修复form自定义设置key导致的scopeprops不传递问题;
|
||||||
|
* 修复beforeRequest和afterRequest中this上下文丢失问题;
|
||||||
|
|
||||||
|
## [1.0.3] - 2019-12-24
|
||||||
|
### 新增
|
||||||
|
* compFactory和addonFactory增加version static字段;
|
||||||
|
* 接入小二工作台fetch库;
|
||||||
|
* 增加标准搭建协议转换API;
|
||||||
|
* 上下文增加React Router的match属性;
|
||||||
|
|
||||||
|
### 优化
|
||||||
|
* 错误码非200时依旧解析请求返回结果;
|
||||||
|
* 增加编辑器环境判断并兼容vscode复制粘贴操作;
|
||||||
|
* 国际化语言判断的边界校验;
|
||||||
|
|
||||||
|
### 修复
|
||||||
|
* 修复当children设置为变量时且condition为false时报错问题;
|
||||||
|
* 修复未支持国际化的插件报错问题;
|
||||||
|
* 修复canvas获取viewport不存在时的报错问题;
|
||||||
|
|
||||||
|
## [1.0.4] - 2020-01-13
|
||||||
|
### 新增
|
||||||
|
* 增加黄金令箭埋点API;
|
||||||
|
* 新增ReadMe;
|
||||||
|
* 渲染引擎支持Flagment组件;
|
||||||
|
* 容器类组件支持自动loading及占位高度属性配置;
|
||||||
|
|
||||||
|
### 优化
|
||||||
|
* schema序列化增加unsafe选项设置;
|
||||||
|
* 优化自定义组件预览逻辑;
|
||||||
|
* 渲染引擎scope、ctx和self上下文构造重构;
|
||||||
|
* 异步数据请求API兼容callback写法;
|
||||||
|
* bzb增加根据url参数控制环境;
|
||||||
|
|
||||||
|
### 修复
|
||||||
|
* 修复无内容状态容器展示异常的问题;
|
||||||
|
* 修复自定义组件children属性中添加Block导致Block内部组件无法进行依赖分析,从而无法展示问题;
|
||||||
|
* 修复componentsMap获取失败问题;
|
||||||
|
* 升级bzb-request;
|
||||||
|
* 修复初始数据源无内容返回导致容器组件不重新渲染问题;
|
||||||
|
* 修复单独使用this时,变量解析过程中this转义问题;
|
||||||
2
packages/react-renderer/CONTRIBUTING.md
Normal file
2
packages/react-renderer/CONTRIBUTING.md
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
# Contributing Guidelines
|
||||||
|
TODO
|
||||||
@ -1 +1,62 @@
|
|||||||
react 渲染模块
|
# iceluna-sdk
|
||||||
|
|
||||||
|
> iceluna 底层引擎渲染能力。
|
||||||
|
|
||||||
|
## Table of Contents
|
||||||
|
|
||||||
|
- [Background](#background)
|
||||||
|
- [Install](#install)
|
||||||
|
- [Develop](#Develop)
|
||||||
|
- [Structure](#Structure)
|
||||||
|
- [Publish](#Publish)
|
||||||
|
- [Maintainers](#maintainers)
|
||||||
|
- [Contributing](#contributing)
|
||||||
|
- [License](#license)
|
||||||
|
|
||||||
|
## Background
|
||||||
|
iceluna 是由淘系技术部研发,面向中后台应用低代码搭建的通用解决方案,集前端应用工程创建、开发、调试及发布的全链路一体化的低代码搭建平台。同时,基于集团中后台 《搭建基础协议》 和 《物料协议》 标准之上,生产低代码搭建物料,沉淀搭建基础设施,助力上层不同业务领域 孵化低代码搭建产品,目标 打造成为基于集团标准的低代码搭建中台。
|
||||||
|
|
||||||
|
iceluna 代码整个项目结构如下图。
|
||||||
|

|
||||||
|
|
||||||
|
该项目 iceluna-SDK 为 iceluna 通用解决方案的提供最底层,最核心的渲染引擎能力。为 iceluna 体系上层可视化编辑器提供基础引擎能力。
|
||||||
|
|
||||||
|
## Install
|
||||||
|
|
||||||
|
```sh
|
||||||
|
# 前提:tnpm install @ali/iceluna-cli -g
|
||||||
|
tnpm i
|
||||||
|
```
|
||||||
|
|
||||||
|
## Develop
|
||||||
|
1. 执行 `tnpm link`, 在全局建立符号链接。
|
||||||
|
2. 到[IDE项目](http://gitlab.alibaba-inc.com/iceluna/iceluna-IDE)启动前,执行 `tnpm link @ali/iceluna-sdk`,建立连接。
|
||||||
|
3. 按照 `iceluna-IDE` 方式调试
|
||||||
|
|
||||||
|
## Structure
|
||||||
|
```
|
||||||
|
.
|
||||||
|
└── src
|
||||||
|
├── comp
|
||||||
|
├── context
|
||||||
|
├── engine
|
||||||
|
├── hoc
|
||||||
|
├── utils
|
||||||
|
```
|
||||||
|
|
||||||
|
## Publish
|
||||||
|
|
||||||
|
## Maintainers
|
||||||
|
|
||||||
|
[@月飞](dingtalk://dingtalkclient/action/sendmsg?dingtalk_id=qhkv9cp), [@下羊](dingtalk://dingtalkclient/action/sendmsg?dingtalk_id=r93lhx4)
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
See [the contributing file](CONTRIBUTING.md)!
|
||||||
|
|
||||||
|
PRs accepted.
|
||||||
|
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
MIT © 2019
|
||||||
|
|||||||
21
packages/react-renderer/demo/example/index.jsx
Normal file
21
packages/react-renderer/demo/example/index.jsx
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import React, { PureComponent } from 'react';
|
||||||
|
|
||||||
|
import Widget from '_';
|
||||||
|
|
||||||
|
import './index.less';
|
||||||
|
|
||||||
|
export default class Example extends PureComponent {
|
||||||
|
static displayName = 'example';
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div className="example">
|
||||||
|
This is an example
|
||||||
|
<Widget />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
2
packages/react-renderer/demo/example/index.less
Normal file
2
packages/react-renderer/demo/example/index.less
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
.example {
|
||||||
|
}
|
||||||
3
packages/react-renderer/demo/index.json
Normal file
3
packages/react-renderer/demo/index.json
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"example": true
|
||||||
|
}
|
||||||
11
packages/react-renderer/package-lock.json
generated
Normal file
11
packages/react-renderer/package-lock.json
generated
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"requires": true,
|
||||||
|
"lockfileVersion": 1,
|
||||||
|
"dependencies": {
|
||||||
|
"prettier": {
|
||||||
|
"version": "1.19.1",
|
||||||
|
"resolved": "https://registry.npm.alibaba-inc.com/prettier/download/prettier-1.19.1.tgz",
|
||||||
|
"integrity": "sha1-99f1/4qc2HKnvkyhQglZVqYHl8s="
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
71
packages/react-renderer/package.json
Normal file
71
packages/react-renderer/package.json
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
{
|
||||||
|
"name": "@ali/lowcode-engine-react-renderer",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"description": "lowcode engine react renderer",
|
||||||
|
"main": "lib/index.js",
|
||||||
|
"scripts": {
|
||||||
|
"babel": "rm -rf lib && babel src -d lib --copy-files",
|
||||||
|
"prettier": "prettier --write \"./src/**/*.{js,jsx,ejs,less,css,scss,json}\" \"./demo/**/*.{js,jsx,ejs,less,css,scss,json}\"",
|
||||||
|
"build": "npm run babel",
|
||||||
|
"prepublish": "npm run prettier && npm run babel"
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git@gitlab.alibaba-inc.com:ali-lowcode/ali-lowcode-engine.git"
|
||||||
|
},
|
||||||
|
"keywords": [
|
||||||
|
"lowcode",
|
||||||
|
"engine",
|
||||||
|
"react"
|
||||||
|
],
|
||||||
|
"author": "xiayang.xy",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"@ali/b3-one": "^0.0.17",
|
||||||
|
"@ali/bzb-request": "^2.6.0-beta.13",
|
||||||
|
"@ali/iceluna-comp-div": "^0.0.5",
|
||||||
|
"@ali/iceluna-rax": "0.0.5",
|
||||||
|
"@ali/lib-mtop": "^2.5.1",
|
||||||
|
"@alifd/next": "^1.18.17",
|
||||||
|
"debug": "^4.1.1",
|
||||||
|
"driver-universal": "^3.1.2",
|
||||||
|
"events": "^3.0.0",
|
||||||
|
"fetch-jsonp": "^1.1.3",
|
||||||
|
"intl-messageformat": "^7.7.2",
|
||||||
|
"jsonuri": "^2.1.2",
|
||||||
|
"keymaster": "^1.6.2",
|
||||||
|
"localforage": "^1.7.3",
|
||||||
|
"lodash": "^4.17.11",
|
||||||
|
"moment": "^2.24.0",
|
||||||
|
"rax": "^1.1.1",
|
||||||
|
"rax-find-dom-node": "^1.0.1",
|
||||||
|
"react-is": "^16.10.1",
|
||||||
|
"serialize-javascript": "^1.7.0",
|
||||||
|
"socket.io-client": "^2.2.0",
|
||||||
|
"whatwg-fetch": "^3.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^16.8.6",
|
||||||
|
"react-dom": "^16.8.6",
|
||||||
|
"prop-types": "^15.7.2"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@babel/cli": "^7.4.4",
|
||||||
|
"@babel/core": "^7.4.4",
|
||||||
|
"@babel/plugin-proposal-class-properties": "^7.0.0",
|
||||||
|
"@babel/plugin-proposal-decorators": "^7.0.0",
|
||||||
|
"@babel/plugin-syntax-dynamic-import": "^7.0.0",
|
||||||
|
"@babel/plugin-transform-runtime": "^7.4.4",
|
||||||
|
"@babel/preset-env": "^7.4.4",
|
||||||
|
"@babel/preset-react": "^7.0.0",
|
||||||
|
"babel-eslint": "^9.0.0",
|
||||||
|
"eslint": "^4.15.0",
|
||||||
|
"eslint-config-airbnb": "^17.1.0",
|
||||||
|
"eslint-loader": "^1.9.0",
|
||||||
|
"eslint-plugin-react": "^7.12.4",
|
||||||
|
"prettier": "^1.12.1"
|
||||||
|
},
|
||||||
|
"publishConfig": {
|
||||||
|
"registry": "http://registry.npm.alibaba-inc.com"
|
||||||
|
}
|
||||||
|
}
|
||||||
33
packages/react-renderer/src/adapter/rax.jsx
Normal file
33
packages/react-renderer/src/adapter/rax.jsx
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
import { createElement, render, useState } from 'rax';
|
||||||
|
import React, { PureComponent } from 'react';
|
||||||
|
import DriverUniversal from 'driver-universal';
|
||||||
|
import { Engine } from '@ali/iceluna-rax';
|
||||||
|
import findDOMNode from 'rax-find-dom-node';
|
||||||
|
|
||||||
|
let updateRax = () => {};
|
||||||
|
|
||||||
|
export default class Rax extends PureComponent {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
const RaxEngine = () => {
|
||||||
|
const [config, setConfig] = useState(this.props);
|
||||||
|
updateRax = setConfig;
|
||||||
|
return createElement(Engine, {
|
||||||
|
...config
|
||||||
|
});
|
||||||
|
};
|
||||||
|
render(createElement(RaxEngine), document.getElementById('luna-rax-container'), { driver: DriverUniversal });
|
||||||
|
}
|
||||||
|
componentDidUpdate() {
|
||||||
|
updateRax(this.props);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return <div id="luna-rax-container" />;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rax.findDOMNode = findDOMNode;
|
||||||
87
packages/react-renderer/src/comp/addon/index.jsx
Normal file
87
packages/react-renderer/src/comp/addon/index.jsx
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
import { PureComponent } from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
|
import AppContext from '../../context/appContext';
|
||||||
|
import { isEmpty, generateI18n, goldlog } from '../../utils';
|
||||||
|
|
||||||
|
export default class Addon extends PureComponent {
|
||||||
|
static displayName = 'lunaAddon';
|
||||||
|
static propTypes = {
|
||||||
|
config: PropTypes.object,
|
||||||
|
locale: PropTypes.string,
|
||||||
|
messages: PropTypes.object
|
||||||
|
};
|
||||||
|
static defaultProps = {
|
||||||
|
config: {}
|
||||||
|
};
|
||||||
|
static contextType = AppContext;
|
||||||
|
constructor(props, context) {
|
||||||
|
super(props, context);
|
||||||
|
if (isEmpty(props.config) || !props.config.addonKey) {
|
||||||
|
console.warn('luna addon has wrong config');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 插件上下文中的appHelper使用IDE的appHelper
|
||||||
|
context.appHelper = (window.__ctx && window.__ctx.appHelper) || context.appHelper;
|
||||||
|
context.locale = props.locale;
|
||||||
|
context.messages = props.messages;
|
||||||
|
// 注册插件
|
||||||
|
this.appHelper = context.appHelper;
|
||||||
|
let { locale, messages } = props;
|
||||||
|
this.i18n = generateI18n(locale, messages);
|
||||||
|
this.addonKey = props.config.addonKey;
|
||||||
|
this.appHelper.addons = this.appHelper.addons || {};
|
||||||
|
this.appHelper.addons[this.addonKey] = this;
|
||||||
|
}
|
||||||
|
|
||||||
|
async componentWillUnmount() {
|
||||||
|
// 销毁插件
|
||||||
|
const config = this.props.config || {};
|
||||||
|
if (config && this.appHelper.addons) {
|
||||||
|
delete this.appHelper.addons[config.addonKey];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
open = () => {
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
close = () => {
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
goldlog = (goKey, params) => {
|
||||||
|
const { addonKey, addonConfig = {} } = this.props.config || {};
|
||||||
|
goldlog(
|
||||||
|
goKey,
|
||||||
|
{
|
||||||
|
addonKey,
|
||||||
|
package: addonConfig.package,
|
||||||
|
version: addonConfig.version,
|
||||||
|
...this.appHelper.logParams,
|
||||||
|
...params
|
||||||
|
},
|
||||||
|
'addon'
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
get utils() {
|
||||||
|
return this.appHelper.utils;
|
||||||
|
}
|
||||||
|
|
||||||
|
get constants() {
|
||||||
|
return this.appHelper.constants;
|
||||||
|
}
|
||||||
|
|
||||||
|
get history() {
|
||||||
|
return this.appHelper.history;
|
||||||
|
}
|
||||||
|
|
||||||
|
get location() {
|
||||||
|
return this.appHelper.location;
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
729
packages/react-renderer/src/comp/canvas/index.jsx
Normal file
729
packages/react-renderer/src/comp/canvas/index.jsx
Normal file
@ -0,0 +1,729 @@
|
|||||||
|
import React, { PureComponent } from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import { on, off } from '@ali/b3-one/lib/event';
|
||||||
|
import AppHelper from '../../utils/appHelper';
|
||||||
|
import SchemaHelper from '../../utils/schemaHelper';
|
||||||
|
import DndHelper from '../../utils/dndHelper';
|
||||||
|
import Engine from '../../engine';
|
||||||
|
|
||||||
|
import CompFactory from '../../hoc/compFactory';
|
||||||
|
import {
|
||||||
|
isSchema,
|
||||||
|
isFileSchema,
|
||||||
|
isEmpty,
|
||||||
|
isJSSlot,
|
||||||
|
jsonuri,
|
||||||
|
registShortCuts,
|
||||||
|
unRegistShortCuts,
|
||||||
|
generateUtils,
|
||||||
|
parseObj,
|
||||||
|
shallowEqual,
|
||||||
|
addCssTag,
|
||||||
|
transformSchemaToPure,
|
||||||
|
goldlog
|
||||||
|
} from '../../utils';
|
||||||
|
import './index.scss';
|
||||||
|
|
||||||
|
const DESIGN_MODE = {
|
||||||
|
EXTEND: 'extend',
|
||||||
|
BORDER: 'border',
|
||||||
|
PREVIEW: 'preview'
|
||||||
|
};
|
||||||
|
|
||||||
|
const DEFAULT_PLACEHOLDER = {
|
||||||
|
emptyImage: '//img.alicdn.com/tfs/TB1zpkUoUT1gK0jSZFhXXaAtVXa-620-430.png',
|
||||||
|
emptyText: '当前页面为空~\n请拖拽组件放入页面容器内吧!',
|
||||||
|
nullImage: '//img.alicdn.com/tfs/TB1m_oSoND1gK0jSZFsXXbldVXa-620-430.png',
|
||||||
|
nullText: '编辑内容不存在~!'
|
||||||
|
};
|
||||||
|
|
||||||
|
export default class Canvas extends PureComponent {
|
||||||
|
static displayName = 'Canvas';
|
||||||
|
static propTypes = {
|
||||||
|
appHelper: PropTypes.object,
|
||||||
|
components: PropTypes.object,
|
||||||
|
engine: PropTypes.element,
|
||||||
|
onCreate: PropTypes.func,
|
||||||
|
initSchema: PropTypes.object,
|
||||||
|
shortCuts: PropTypes.array,
|
||||||
|
utils: PropTypes.object
|
||||||
|
};
|
||||||
|
static defaultProps = {
|
||||||
|
components: {},
|
||||||
|
engine: Engine,
|
||||||
|
onCreate: () => {},
|
||||||
|
initSchema: {},
|
||||||
|
shortCuts: [],
|
||||||
|
utils: {}
|
||||||
|
};
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.appHelper = props.appHelper || new AppHelper();
|
||||||
|
if (!this.appHelper.schemaHelper) {
|
||||||
|
this.appHelper.set('schemaHelper', new SchemaHelper(props.initSchema || {}, this.appHelper));
|
||||||
|
}
|
||||||
|
this.appHelper.set('basicSchemaHelper', this.appHelper.schemaHelper);
|
||||||
|
if (!this.appHelper.dndHelper) {
|
||||||
|
this.appHelper.set('dndHelper', new DndHelper(this.appHelper));
|
||||||
|
}
|
||||||
|
this.appHelper.dndHelper.setCanvasWin(window);
|
||||||
|
if (this.appHelper.designMode === undefined) {
|
||||||
|
this.appHelper.designMode = 'extend';
|
||||||
|
}
|
||||||
|
|
||||||
|
this.canvasAppHelper = new AppHelper({
|
||||||
|
history: this.appHelper.history,
|
||||||
|
location: this.appHelper.location,
|
||||||
|
match: this.appHelper.match
|
||||||
|
});
|
||||||
|
|
||||||
|
this.updateCanvasAppHelper(props);
|
||||||
|
this.appHelper.once('ide.ready', () => {
|
||||||
|
this.updateCanvasAppHelper(props);
|
||||||
|
});
|
||||||
|
|
||||||
|
window.__ctx = {
|
||||||
|
appHelper: this.appHelper,
|
||||||
|
canvasAppHelper: this.canvasAppHelper,
|
||||||
|
components: this.props.components
|
||||||
|
};
|
||||||
|
|
||||||
|
window.goldlog = window.goldlog || window.parent.goldlog;
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
canvasStack: [
|
||||||
|
{
|
||||||
|
lunaKey: 'root',
|
||||||
|
lunaPath: '',
|
||||||
|
name: 'root',
|
||||||
|
schemaHelper: this.appHelper.schemaHelper,
|
||||||
|
schema: this.appHelper.schemaHelper.get('schema')
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
this.appHelper.set('canvasStack', this.state.canvasStack);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
const appHelper = this.appHelper;
|
||||||
|
appHelper.batchOn(['behavior.undo', 'behavior.redo'], this.handleUndoRedo);
|
||||||
|
appHelper.on('schema.reset', this.handleSchemaReset);
|
||||||
|
appHelper.on('material.move', this.handleMaterialMove);
|
||||||
|
appHelper.on('material.add', this.handleMaterialAdd);
|
||||||
|
appHelper.on('material.remove', this.handleMaterialRemove);
|
||||||
|
appHelper.on('material.up', this.handleMaterialMoveUp);
|
||||||
|
appHelper.on('material.down', this.handleMaterialMoveDown);
|
||||||
|
appHelper.on('material.copy', this.handleMaterialCopy);
|
||||||
|
appHelper.on('material.update', this.handleMaterialUpdate);
|
||||||
|
appHelper.on('material.select', this.handleMaterialSelect);
|
||||||
|
appHelper.on('schemaHelper.schema.afterUpdate', this.handleReset);
|
||||||
|
appHelper.on('designMode.change', this.handleDesignModeChange);
|
||||||
|
appHelper.on('preview.change', this.handlePreviewChange);
|
||||||
|
appHelper.on('canvas.stack.push', this.handleCanvasPush);
|
||||||
|
appHelper.on('canvas.stack.pop', this.handleCanvasPop);
|
||||||
|
appHelper.on('canvas.stack.jump', this.handleCanvasJump);
|
||||||
|
appHelper.on('style.update', this.updateStyle);
|
||||||
|
appHelper.batchOn(['utils.update', 'constants.update', 'componentsMap.update'], this.handleCanvasAppHelperUpdate);
|
||||||
|
appHelper.on('viewPort.update', this.handleForceUpdate);
|
||||||
|
|
||||||
|
registShortCuts(this.props.shortCuts, this.appHelper);
|
||||||
|
this.appHelper.set('canvas', this);
|
||||||
|
this.props.onCreate(this.appHelper);
|
||||||
|
appHelper.emit('canvas.ready', this);
|
||||||
|
goldlog(
|
||||||
|
'EXP',
|
||||||
|
{
|
||||||
|
action: 'appear'
|
||||||
|
},
|
||||||
|
'canvas'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
const appHelper = this.appHelper;
|
||||||
|
appHelper.batchOff(['behavior.undo', 'behavior.redo'], this.handleUndoRedo);
|
||||||
|
appHelper.on('schema.reset', this.handleSchemaReset);
|
||||||
|
appHelper.off('material.move', this.handleMaterialMove);
|
||||||
|
appHelper.off('material.add', this.handleMaterialAdd);
|
||||||
|
appHelper.off('material.remove', this.handleMaterialRemove);
|
||||||
|
appHelper.off('material.up', this.handleMaterialMoveUp);
|
||||||
|
appHelper.off('material.down', this.handleMaterialMoveDown);
|
||||||
|
appHelper.off('material.copy', this.handleMaterialCopy);
|
||||||
|
appHelper.off('material.update', this.handleMaterialUpdate);
|
||||||
|
appHelper.off('material.select', this.handleMaterialSelect);
|
||||||
|
appHelper.off('schemaHelper.schema.afterUpdate', this.handleReset);
|
||||||
|
appHelper.off('designMode.change', this.handleDesignModeChange);
|
||||||
|
appHelper.off('preview.change', this.handlePreviewChange);
|
||||||
|
appHelper.off('canvas.stack.push', this.handleCanvasPush);
|
||||||
|
appHelper.off('canvas.stack.pop', this.handleCanvasPop);
|
||||||
|
appHelper.off('canvas.stack.jump', this.handleCanvasJump);
|
||||||
|
appHelper.off('style.update', this.updateStyle);
|
||||||
|
appHelper.batchOff(['utils.update', 'constants.update', 'componentsMap.update'], this.handleCanvasAppHelperUpdate);
|
||||||
|
appHelper.off('viewPort.update', this.handleForceUpdate);
|
||||||
|
unRegistShortCuts(this.props.shortCuts);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 消息处理
|
||||||
|
|
||||||
|
handleMaterialMove = ({ lunaKey, targetKey, direction }) => {
|
||||||
|
const appHelper = this.appHelper;
|
||||||
|
appHelper.schemaHelper.move(lunaKey, targetKey, direction);
|
||||||
|
appHelper.emit('behavior.record');
|
||||||
|
};
|
||||||
|
|
||||||
|
handleMaterialAdd = ({ schema, targetKey, direction }) => {
|
||||||
|
if (!isSchema(schema)) {
|
||||||
|
throw new Error('物料schema结构异常,无法添加!');
|
||||||
|
}
|
||||||
|
const appHelper = this.appHelper;
|
||||||
|
const addSchema = Array.isArray(schema) ? schema[0] : schema;
|
||||||
|
// 对于没有设置文件名的容器组件,交给画布外层处理
|
||||||
|
if (isFileSchema(addSchema) && !addSchema.fileName) {
|
||||||
|
return appHelper.emit('onFileNameMaterial.add', { schema: addSchema, targetKey, direction });
|
||||||
|
}
|
||||||
|
|
||||||
|
const addKey = appHelper.schemaHelper.add(schema, targetKey, direction);
|
||||||
|
appHelper.emit('behavior.record');
|
||||||
|
this.autoSelectComponent(addKey);
|
||||||
|
};
|
||||||
|
|
||||||
|
handleMaterialRemove = lunaKey => {
|
||||||
|
const appHelper = this.appHelper;
|
||||||
|
const schemaHelper = appHelper.schemaHelper;
|
||||||
|
const currCompSchema = schemaHelper.schemaMap[lunaKey];
|
||||||
|
// 获取当前删除物料的相邻物料
|
||||||
|
const nextCompSchema = jsonuri.get(
|
||||||
|
schemaHelper.schema,
|
||||||
|
currCompSchema.__ctx.lunaPath.replace(/\/(\d+)$/, (res, idx) => `/${parseInt(idx) + 1}`)
|
||||||
|
);
|
||||||
|
const activeKey = (nextCompSchema && nextCompSchema.__ctx.lunaKey) || currCompSchema.__ctx.parentKey;
|
||||||
|
appHelper.schemaHelper.remove(lunaKey);
|
||||||
|
appHelper.emit('behavior.record');
|
||||||
|
this.autoSelectComponent(activeKey);
|
||||||
|
};
|
||||||
|
|
||||||
|
handleMaterialMoveUp = lunaKey => {
|
||||||
|
const appHelper = this.appHelper;
|
||||||
|
appHelper.schemaHelper && appHelper.schemaHelper.slide(lunaKey, 'up');
|
||||||
|
appHelper.emit('behavior.record');
|
||||||
|
};
|
||||||
|
|
||||||
|
handleMaterialMoveDown = lunaKey => {
|
||||||
|
const appHelper = this.appHelper;
|
||||||
|
appHelper.schemaHelper && appHelper.schemaHelper.slide(lunaKey, 'down');
|
||||||
|
appHelper.emit('behavior.record');
|
||||||
|
};
|
||||||
|
|
||||||
|
handleMaterialCopy = lunaKey => {
|
||||||
|
const appHelper = this.appHelper;
|
||||||
|
const addKey = appHelper.schemaHelper.copy(lunaKey);
|
||||||
|
|
||||||
|
appHelper.emit('behavior.record');
|
||||||
|
this.autoSelectComponent(addKey);
|
||||||
|
};
|
||||||
|
|
||||||
|
handleMaterialUpdate = ({ lunaKey, props, propsKey }) => {
|
||||||
|
const appHelper = this.appHelper;
|
||||||
|
appHelper.schemaHelper.update(lunaKey, props);
|
||||||
|
appHelper.emit('behavior.record', { lunaKey, propsKey });
|
||||||
|
};
|
||||||
|
|
||||||
|
handleMaterialSelect = (lunaKey, options) => {
|
||||||
|
const appHelper = this.appHelper;
|
||||||
|
if (appHelper.activeKey === lunaKey) return;
|
||||||
|
appHelper.set('activeKey', lunaKey);
|
||||||
|
appHelper.emit('material.select.change', lunaKey, options);
|
||||||
|
const preNode = document.querySelectorAll('[data-active=true]');
|
||||||
|
if (preNode[0] && preNode[0].dataset.lunaKey === lunaKey) return;
|
||||||
|
(preNode || []).forEach(item => {
|
||||||
|
item.removeAttribute('data-active');
|
||||||
|
item.removeAttribute('data-nochild');
|
||||||
|
});
|
||||||
|
//判断是否容器组件且没有子元素
|
||||||
|
if (!lunaKey) {
|
||||||
|
window.parent.t = window.t = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let schema = appHelper.schemaHelper.schemaMap[lunaKey];
|
||||||
|
if (!schema) return;
|
||||||
|
let componentInfo = appHelper.componentsMap[schema.componentName];
|
||||||
|
const currentNode = document.querySelectorAll(`[data-luna-key=${lunaKey}]`);
|
||||||
|
(currentNode || []).forEach(item => {
|
||||||
|
item.setAttribute('data-active', 'true');
|
||||||
|
if (componentInfo && componentInfo.isContainer && schema && (!schema.children || !schema.children.length)) {
|
||||||
|
item.setAttribute('data-nochild', 'true');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// for debug
|
||||||
|
let ctx = this.appHelper.schemaHelper.compCtxMap[lunaKey];
|
||||||
|
let ref = this.appHelper.schemaHelper.compThisMap[lunaKey];
|
||||||
|
let t = {
|
||||||
|
ctx,
|
||||||
|
schema,
|
||||||
|
ref
|
||||||
|
};
|
||||||
|
t.__proto__ = ctx;
|
||||||
|
window.parent.t = window.t = t;
|
||||||
|
};
|
||||||
|
|
||||||
|
handleDesignModeChange = designMode => {
|
||||||
|
this.appHelper.set('designMode', designMode);
|
||||||
|
this.forceUpdate();
|
||||||
|
};
|
||||||
|
|
||||||
|
handlePreviewChange = isPreview => {
|
||||||
|
this.appHelper.set('isPreview', isPreview);
|
||||||
|
this.forceUpdate();
|
||||||
|
};
|
||||||
|
|
||||||
|
handleUndoRedo = schema => {
|
||||||
|
this.appHelper.schemaHelper.reset(schema);
|
||||||
|
this.autoSelectComponent();
|
||||||
|
};
|
||||||
|
|
||||||
|
handleSchemaReset = schema => {
|
||||||
|
this.appHelper.schemaHelper.reset(schema);
|
||||||
|
this.appHelper.emit('behavior.record');
|
||||||
|
this.autoSelectComponent();
|
||||||
|
};
|
||||||
|
|
||||||
|
handleReset = () => {
|
||||||
|
this.updateCanvasStack();
|
||||||
|
this.forceUpdate();
|
||||||
|
this.updateStyle();
|
||||||
|
};
|
||||||
|
|
||||||
|
handleCanvasAppHelperUpdate = () => {
|
||||||
|
this.updateCanvasAppHelper();
|
||||||
|
this.forceUpdate();
|
||||||
|
};
|
||||||
|
|
||||||
|
handleForceUpdate = () => {
|
||||||
|
this.forceUpdate();
|
||||||
|
};
|
||||||
|
|
||||||
|
handleCanvasPush = ({ schema, lunaKey, name }) => {
|
||||||
|
const appHelper = this.appHelper;
|
||||||
|
appHelper.emit('canvas.stack.beforePush');
|
||||||
|
const { canvasStack } = this.state;
|
||||||
|
const tempSchema = {
|
||||||
|
componentName: 'Temp',
|
||||||
|
fileName: 'temp',
|
||||||
|
props: {},
|
||||||
|
children: isJSSlot(schema) ? schema.value : schema //兼容slot
|
||||||
|
};
|
||||||
|
const schemaHelper = new SchemaHelper(transformSchemaToPure(tempSchema), this.appHelper);
|
||||||
|
const schemaMap = this.appHelper.schemaHelper.schemaMap || {};
|
||||||
|
const compCtxMap = this.appHelper.schemaHelper.compCtxMap || {};
|
||||||
|
const currentComp = schemaMap[lunaKey];
|
||||||
|
const undoRedoKey = `${lunaKey}_${canvasStack.length}`;
|
||||||
|
//若是第一层下钻需要先给最上层加上从appHelper中获取的undoRedoKey
|
||||||
|
if (canvasStack.length === 1) {
|
||||||
|
canvasStack[0].undoRedoKey = this.appHelper.undoRedoKey;
|
||||||
|
}
|
||||||
|
const currentData = {
|
||||||
|
lunaKey,
|
||||||
|
lunaPath: currentComp.__ctx.lunaPath,
|
||||||
|
name,
|
||||||
|
schema,
|
||||||
|
schemaHelper,
|
||||||
|
ctx: compCtxMap[lunaKey],
|
||||||
|
undoRedoKey,
|
||||||
|
componentName: currentComp.componentName
|
||||||
|
};
|
||||||
|
appHelper.set('schemaHelper', schemaHelper);
|
||||||
|
appHelper.undoRedoHelper && appHelper.undoRedoHelper.create(undoRedoKey, tempSchema);
|
||||||
|
appHelper.set('undoRedoKey', undoRedoKey);
|
||||||
|
appHelper.set('activeKey', null);
|
||||||
|
this.setState(
|
||||||
|
{
|
||||||
|
canvasStack: [...this.state.canvasStack, currentData]
|
||||||
|
},
|
||||||
|
() => {
|
||||||
|
this.appHelper.set('canvasStack', this.state.canvasStack);
|
||||||
|
this.appHelper.emit('canvas.stack.afterPush', currentData, this.state.canvasStack);
|
||||||
|
this.autoSelectComponent();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
handleCanvasPop = () => {
|
||||||
|
const { canvasStack } = this.state;
|
||||||
|
if (canvasStack.length > 1) {
|
||||||
|
this.handleCanvasJump(null, true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
handleCanvasJump = (idx, isPop) => {
|
||||||
|
const { canvasStack } = this.state;
|
||||||
|
const appHelper = this.appHelper;
|
||||||
|
let preIdx = idx + 1;
|
||||||
|
if (isPop) {
|
||||||
|
appHelper.emit('canvas.stack.beforePop');
|
||||||
|
preIdx = canvasStack.length - 1;
|
||||||
|
idx = preIdx - 1;
|
||||||
|
} else {
|
||||||
|
appHelper.emit('canvas.stack.beforeJump');
|
||||||
|
}
|
||||||
|
if (idx < 0 || idx > canvasStack.length - 1) return;
|
||||||
|
const preData = canvasStack[preIdx];
|
||||||
|
const currentData = canvasStack[idx];
|
||||||
|
appHelper.set('schemaHelper', currentData.schemaHelper);
|
||||||
|
appHelper.set('undoRedoKey', currentData.undoRedoKey);
|
||||||
|
appHelper.undoRedoHelper && appHelper.undoRedoHelper.delete(preData.undoRedoKey);
|
||||||
|
this.setState(
|
||||||
|
{
|
||||||
|
canvasStack: canvasStack.slice(0, idx + 1)
|
||||||
|
},
|
||||||
|
() => {
|
||||||
|
appHelper.set('canvasStack', this.state.canvasStack);
|
||||||
|
appHelper.schemaHelper.reset(appHelper.schemaHelper.schema);
|
||||||
|
appHelper.emit('behavior.record');
|
||||||
|
appHelper.emit(`canvas.stack.${isPop ? 'afterPop' : 'afterJump'}`, preData, this.state.canvasStack);
|
||||||
|
this.autoSelectComponent(preData.lunaKey);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 引擎处理函数
|
||||||
|
|
||||||
|
handleCompGetCtx = (schema, ctx) => {
|
||||||
|
const lunaKey = schema && schema.__ctx && schema.__ctx.lunaKey;
|
||||||
|
if (!lunaKey) return;
|
||||||
|
// console.log('+++++ getCtx', lunaKey, ctx);
|
||||||
|
this.appHelper.schemaHelper.compCtxMap[lunaKey] = ctx;
|
||||||
|
// for debug
|
||||||
|
if (this.appHelper.activeKey && lunaKey === this.appHelper.activeKey) {
|
||||||
|
let ref = this.appHelper.schemaHelper.compThisMap[lunaKey];
|
||||||
|
let t = {
|
||||||
|
ctx,
|
||||||
|
schema,
|
||||||
|
ref
|
||||||
|
};
|
||||||
|
t.__proto__ = ctx;
|
||||||
|
window.parent.t = window.t = t;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
handleCompGetRef = (schema, ref, topLevel) => {
|
||||||
|
const lunaKey = schema && schema.__ctx && schema.__ctx.lunaKey;
|
||||||
|
if (!lunaKey) return;
|
||||||
|
// console.log('----- getRef', lunaKey, ref);
|
||||||
|
const schemaHelper = this.appHelper.schemaHelper;
|
||||||
|
schemaHelper.compThisMap[lunaKey] = ref;
|
||||||
|
if (ref && !ref.__design) {
|
||||||
|
this.updateDesignMode(ref, schema, topLevel);
|
||||||
|
const didUpdate = ref.componentDidUpdate;
|
||||||
|
ref.componentDidUpdate = (...args) => {
|
||||||
|
didUpdate && didUpdate.apply(ref, args);
|
||||||
|
this.updateDesignMode(ref, schema, topLevel);
|
||||||
|
};
|
||||||
|
const willUnmount = ref.componentWillUnmount;
|
||||||
|
ref.componentWillUnmount = (...args) => {
|
||||||
|
willUnmount && willUnmount.apply(ref, args);
|
||||||
|
// console.log('----- destroy', lunaKey, ref);
|
||||||
|
delete schemaHelper.compThisMap[lunaKey];
|
||||||
|
delete schemaHelper.compCtxMap[lunaKey];
|
||||||
|
};
|
||||||
|
ref.__design = true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
handleDnd = (type, ev, schema) => {
|
||||||
|
const lunaKey = schema && schema.__ctx && schema.__ctx.lunaKey;
|
||||||
|
const designMode = this.appHelper.designMode;
|
||||||
|
if (!lunaKey || ![DESIGN_MODE.EXTEND, DESIGN_MODE.BORDER].includes(designMode)) return;
|
||||||
|
const dndHelper = this.appHelper && this.appHelper.dndHelper;
|
||||||
|
if (dndHelper) {
|
||||||
|
dndHelper[`handle${type}`](ev, lunaKey);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
//自动选中组件
|
||||||
|
autoSelectComponent = lunaKey => {
|
||||||
|
const appHelper = this.appHelper;
|
||||||
|
// 若未指定需要选中的组件,且当前有选中的组件不做处理
|
||||||
|
if (appHelper.activeKey && !lunaKey) return;
|
||||||
|
if (!lunaKey) {
|
||||||
|
// 若没有指定的组件,且当前又没有选中组件,默认选中顶部组件,如果是下钻编辑则默认选中第一个子组件
|
||||||
|
const schema = appHelper.schemaHelper.schema;
|
||||||
|
if (schema) {
|
||||||
|
if (schema.componentName === 'Temp') {
|
||||||
|
lunaKey = schema.children && schema.children[0] && schema.children[0].__ctx.lunaKey;
|
||||||
|
} else {
|
||||||
|
lunaKey = schema.__ctx.lunaKey;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
appHelper.emit('material.select', lunaKey);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 构造低代码组件
|
||||||
|
generateLowComps = (props = this.props) => {
|
||||||
|
const { components } = props;
|
||||||
|
const { utils, constants } = this.canvasAppHelper || {};
|
||||||
|
const componentsMap = this.appHelper.componentsMap || {};
|
||||||
|
Object.keys(componentsMap).forEach(key => {
|
||||||
|
const comp = componentsMap[key];
|
||||||
|
// 对自定义组件做特殊处理
|
||||||
|
if (comp.version === '0.0.0' && comp.code) {
|
||||||
|
let schema = parseObj(comp.code);
|
||||||
|
if (isFileSchema(schema) && schema.componentName === 'Component') {
|
||||||
|
components[comp.name] = CompFactory(schema, components, componentsMap, {
|
||||||
|
utils,
|
||||||
|
constants
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return components;
|
||||||
|
};
|
||||||
|
|
||||||
|
updateCanvasAppHelper = (props = this.props) => {
|
||||||
|
const { utils } = props;
|
||||||
|
const { entityInfo = {}, componentsMap } = this.appHelper;
|
||||||
|
this.canvasAppHelper.set({
|
||||||
|
componentsMap,
|
||||||
|
utils: entityInfo.utils ? generateUtils(utils, parseObj(entityInfo.utils)) : utils,
|
||||||
|
constants: parseObj((entityInfo && entityInfo.constants) || {})
|
||||||
|
});
|
||||||
|
this.canvasAppHelper.set('components', this.generateLowComps(props));
|
||||||
|
};
|
||||||
|
|
||||||
|
updateStyle = () => {
|
||||||
|
const entityInfo = this.appHelper.entityInfo || {};
|
||||||
|
const blockSchemaMap = (this.appHelper.schemaHelper && this.appHelper.schemaHelper.blockSchemaMap) || {};
|
||||||
|
const componentsMap = this.appHelper.componentsMap || {};
|
||||||
|
const cssMap = {};
|
||||||
|
// 区块中的样式
|
||||||
|
Object.keys(blockSchemaMap).forEach(item => {
|
||||||
|
const schema = blockSchemaMap[item];
|
||||||
|
cssMap[schema.fileName] = schema.css || (schema.__ctx && schema.__ctx.css) || '';
|
||||||
|
});
|
||||||
|
// 低代码自定义组件中的样式
|
||||||
|
Object.keys(componentsMap).forEach(item => {
|
||||||
|
const comp = componentsMap[item];
|
||||||
|
// 对自定义组件做特殊处理
|
||||||
|
if (comp.version === '0.0.0' && comp.code && comp.css) {
|
||||||
|
cssMap[comp.name] = comp.css;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
cssMap.__global = entityInfo.css || '';
|
||||||
|
if (shallowEqual(this.cacheCssMap, cssMap)) return;
|
||||||
|
this.cacheCssMap = cssMap;
|
||||||
|
const { __global, ...other } = cssMap;
|
||||||
|
addCssTag(
|
||||||
|
'luna-canvas-style',
|
||||||
|
`${__global}\n${Object.keys(other || {})
|
||||||
|
.map(item => cssMap[item])
|
||||||
|
.join('\n')}`
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
updateCanvasStack = () => {
|
||||||
|
const { canvasStack } = this.state;
|
||||||
|
if (canvasStack.length < 2) return;
|
||||||
|
for (let idx = canvasStack.length - 1; idx > 0; idx--) {
|
||||||
|
const currentData = canvasStack[idx];
|
||||||
|
const { lunaPath, name, schemaHelper, schema } = currentData;
|
||||||
|
const preData = canvasStack[idx - 1];
|
||||||
|
let data = schemaHelper.getPureSchema().children;
|
||||||
|
// 如果情况内容则删除属性
|
||||||
|
if (isEmpty(data)) {
|
||||||
|
jsonuri.rm(
|
||||||
|
preData.schemaHelper.schema,
|
||||||
|
name === 'children' ? `${lunaPath}/children` : `${lunaPath}/props/${name.replace('.', '/')}`
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (isJSSlot(schema)) {
|
||||||
|
data = {
|
||||||
|
...schema,
|
||||||
|
value: data
|
||||||
|
};
|
||||||
|
} else if (name !== 'children') {
|
||||||
|
data = {
|
||||||
|
type: 'JSSlot',
|
||||||
|
value: data
|
||||||
|
};
|
||||||
|
}
|
||||||
|
jsonuri.set(
|
||||||
|
preData.schemaHelper.schema,
|
||||||
|
name === 'children' ? `${lunaPath}/children` : `${lunaPath}/props/${name.replace('.', '/')}`,
|
||||||
|
data
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
updateDesignMode = (ref, schema, topLevel) => {
|
||||||
|
if (!ref || (ref && ref.current === null) || !schema.__ctx) return;
|
||||||
|
const { engine } = this.props;
|
||||||
|
const appHelper = this.appHelper;
|
||||||
|
const { activeKey, isPreview, viewPortConfig } = appHelper;
|
||||||
|
const designMode = isPreview ? 'preview' : appHelper.designMode;
|
||||||
|
const node = engine.findDOMNode(ref.current || ref);
|
||||||
|
|
||||||
|
if (!node || !node.getAttribute) return;
|
||||||
|
// 渲染引擎可以通过设置__disableDesignMode属性的方式阻止组件的可视模式;
|
||||||
|
const hasDesignMode =
|
||||||
|
[DESIGN_MODE.EXTEND, DESIGN_MODE.BORDER].includes(designMode) && !ref.props.__disableDesignMode;
|
||||||
|
node.setAttribute('data-design-mode', designMode && hasDesignMode ? `luna-design-${designMode}` : '');
|
||||||
|
if (topLevel) {
|
||||||
|
node.setAttribute('top-container', true);
|
||||||
|
}
|
||||||
|
const lunaKey = schema.__ctx.lunaKey;
|
||||||
|
let instanceName = schema.componentName + (window.parent.__isDebug ? (lunaKey || '_').split('_')[1] : '');
|
||||||
|
switch (schema.componentName) {
|
||||||
|
case 'Page':
|
||||||
|
instanceName = '页面容器 ' + instanceName;
|
||||||
|
break;
|
||||||
|
case 'Block':
|
||||||
|
instanceName = '区块容器 ' + instanceName;
|
||||||
|
break;
|
||||||
|
case 'Component':
|
||||||
|
instanceName = '低代码组件容器 ' + instanceName;
|
||||||
|
break;
|
||||||
|
case 'Addon':
|
||||||
|
instanceName = '插件容器 ' + instanceName;
|
||||||
|
break;
|
||||||
|
case 'Temp':
|
||||||
|
instanceName = '下钻编辑器容器';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (topLevel && viewPortConfig) {
|
||||||
|
node.style.transform = `scale(${viewPortConfig.scale ? viewPortConfig.scale / 100 : 1})`;
|
||||||
|
}
|
||||||
|
node.setAttribute('data-instance-name', instanceName);
|
||||||
|
node.setAttribute('data-luna-key', lunaKey);
|
||||||
|
node.setAttribute('data-luna-path', schema.__ctx.lunaPath);
|
||||||
|
|
||||||
|
if (hasDesignMode) {
|
||||||
|
if (activeKey && activeKey === lunaKey) {
|
||||||
|
node.setAttribute('data-active', true);
|
||||||
|
} else {
|
||||||
|
node.removeAttribute('data-active');
|
||||||
|
}
|
||||||
|
// 点击
|
||||||
|
if (!node.compEvent && schema.componentName !== 'Temp') {
|
||||||
|
node.compEvent = ev => {
|
||||||
|
ev.stopPropagation();
|
||||||
|
appHelper.emit('material.select', lunaKey, { isFromCanvas: true });
|
||||||
|
};
|
||||||
|
on(node, 'mousedown', node.compEvent, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// drag and drop
|
||||||
|
if (!node.draggableFlag) {
|
||||||
|
if (topLevel) {
|
||||||
|
node.ondragleave = ev => this.handleDnd('DragLeave', ev, schema);
|
||||||
|
node.ondrop = ev => this.handleDnd('Drop', ev, schema);
|
||||||
|
} else {
|
||||||
|
node.setAttribute('draggable', 'true');
|
||||||
|
node.ondragstart = ev => this.handleDnd('DragStart', ev, schema);
|
||||||
|
node.ondragend = ev => this.handleDnd('DragEnd', ev, schema);
|
||||||
|
}
|
||||||
|
node.ondragover = ev => this.handleDnd('DragOver', ev, schema);
|
||||||
|
node.draggableFlag = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
//点击
|
||||||
|
if (node.compEvent) {
|
||||||
|
off(node, 'mousedown', node.compEvent, false);
|
||||||
|
delete node.compEvent;
|
||||||
|
}
|
||||||
|
//drag and drop
|
||||||
|
if (node.draggableFlag) {
|
||||||
|
node.removeAttribute('draggable');
|
||||||
|
delete node.ondragstart;
|
||||||
|
delete node.ondragover;
|
||||||
|
delete node.ondragend;
|
||||||
|
delete node.ondragleave;
|
||||||
|
delete node.ondrop;
|
||||||
|
delete node.draggableFlag;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
renderCanvasStack = () => {
|
||||||
|
const { canvasStack } = this.state;
|
||||||
|
const lastIndex = canvasStack.length - 1;
|
||||||
|
const appHelper = this.appHelper;
|
||||||
|
const canvasAppHelper = this.canvasAppHelper;
|
||||||
|
const designMode = appHelper.isPreview ? 'preview' : appHelper.designMode;
|
||||||
|
const Engine = this.props.engine;
|
||||||
|
|
||||||
|
return (canvasStack || []).map((item, idx) => (
|
||||||
|
<div
|
||||||
|
className={classNames(
|
||||||
|
'engine-wrapper',
|
||||||
|
designMode,
|
||||||
|
item.schemaHelper.schema &&
|
||||||
|
item.schemaHelper.schema.props.style && {
|
||||||
|
'fixed-width': item.schemaHelper.schema.props.style.width,
|
||||||
|
'fixed-height': item.schemaHelper.schema.props.style.height
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
key={`${item.lunaKey}_${item.name}`}
|
||||||
|
>
|
||||||
|
<Engine
|
||||||
|
schema={item.schemaHelper.schema}
|
||||||
|
__ctx={item.ctx}
|
||||||
|
designMode={designMode}
|
||||||
|
appHelper={canvasAppHelper}
|
||||||
|
components={canvasAppHelper.components}
|
||||||
|
componentsMap={appHelper.componentsMap}
|
||||||
|
suspended={idx !== lastIndex}
|
||||||
|
onCompGetCtx={this.handleCompGetCtx}
|
||||||
|
onCompGetRef={this.handleCompGetRef}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
));
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { canvasStack } = this.state;
|
||||||
|
const lastIndex = canvasStack.length - 1;
|
||||||
|
const schema = canvasStack[lastIndex] && canvasStack[lastIndex].schemaHelper.schema;
|
||||||
|
|
||||||
|
const appHelper = this.appHelper;
|
||||||
|
const { entityInfo = {}, viewPortConfig = {}, canvasPlaceholder = {} } = appHelper;
|
||||||
|
const components = this.canvasAppHelper.components || {};
|
||||||
|
|
||||||
|
const placeholder = { ...DEFAULT_PLACEHOLDER, ...canvasPlaceholder };
|
||||||
|
const layoutComp = entityInfo.layoutInfo && entityInfo.layoutInfo.name;
|
||||||
|
const layoutProps = (entityInfo.layoutInfo && entityInfo.layoutInfo.realProps) || {};
|
||||||
|
const Layout = layoutComp && components[layoutComp];
|
||||||
|
const { hideLayout } = viewPortConfig;
|
||||||
|
const isDrillDown = canvasStack && canvasStack.length > 1;
|
||||||
|
const isSchemaEmpty = isSchema(schema) && isEmpty(schema.children);
|
||||||
|
const isSchemaNull = schema === null;
|
||||||
|
const canvasStyle = {};
|
||||||
|
if (!isDrillDown) {
|
||||||
|
if (isSchemaEmpty) {
|
||||||
|
canvasStyle.backgroundImage = `url(${placeholder.emptyImage})`;
|
||||||
|
} else if (isSchemaNull) {
|
||||||
|
canvasStyle.backgroundImage = `url(${placeholder.nullImage})`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={classNames('luna-canvas-inner', {
|
||||||
|
empty: isSchemaEmpty,
|
||||||
|
null: isSchemaNull,
|
||||||
|
'drill-down': isDrillDown
|
||||||
|
})}
|
||||||
|
style={canvasStyle}
|
||||||
|
data-placeholder-text={isSchemaEmpty ? placeholder.emptyText : isSchemaNull ? placeholder.nullText : ''}
|
||||||
|
>
|
||||||
|
{Layout && !hideLayout ? (
|
||||||
|
<Layout location={appHelper.location} history={appHelper.history} {...layoutProps}>
|
||||||
|
{this.renderCanvasStack()}
|
||||||
|
</Layout>
|
||||||
|
) : (
|
||||||
|
this.renderCanvasStack()
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
361
packages/react-renderer/src/comp/canvas/index.scss
Normal file
361
packages/react-renderer/src/comp/canvas/index.scss
Normal file
@ -0,0 +1,361 @@
|
|||||||
|
/*增加标签函数*/
|
||||||
|
@mixin labelFun($type: before) {
|
||||||
|
&:#{$type} {
|
||||||
|
content: attr(data-instance-name) !important;
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
right: unset;
|
||||||
|
bottom: unset;
|
||||||
|
color: #666 !important;
|
||||||
|
font-size: 12px !important;
|
||||||
|
float: left;
|
||||||
|
padding: 0 5px !important;
|
||||||
|
line-height: 12px !important;
|
||||||
|
height: 12px;
|
||||||
|
overflow: hidden;
|
||||||
|
background: rgba(222, 222, 222, 0.7);
|
||||||
|
z-index: 2;
|
||||||
|
border-left: 3px solid transparent;
|
||||||
|
transform-origin: 0 0;
|
||||||
|
transform: scale(0.8);
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.luna-block,
|
||||||
|
&.luna-page,
|
||||||
|
&.luna-comp {
|
||||||
|
&:#{$type} {
|
||||||
|
border-color: #2077ff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&[data-active='true'] {
|
||||||
|
&:#{$type} {
|
||||||
|
color: #fff !important;
|
||||||
|
background: #1861d5 !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&[data-design-mode='luna-design-border'] {
|
||||||
|
&:#{$type} {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&[data-active='true'] {
|
||||||
|
&:#{$type} {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.luna-canvas-inner {
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
&.empty,
|
||||||
|
&.null {
|
||||||
|
position: relative;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position: calc(50% - 180px) 50%;
|
||||||
|
background-size: 310px 215px;
|
||||||
|
|
||||||
|
&:after {
|
||||||
|
content: attr(data-placeholder-text);
|
||||||
|
position: absolute;
|
||||||
|
pointer-events: none;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
margin: -40px 0 0 20px;
|
||||||
|
height: 80px;
|
||||||
|
line-height: 40px;
|
||||||
|
color: #aaa;
|
||||||
|
font-size: 24px;
|
||||||
|
white-space: pre;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.empty {
|
||||||
|
&.drill-down {
|
||||||
|
&:before {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:after {
|
||||||
|
content: '请拖入组件';
|
||||||
|
text-align: center;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
margin-left: 0;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.engine-wrapper {
|
||||||
|
height: 100%;
|
||||||
|
display: none;
|
||||||
|
overflow: auto;
|
||||||
|
|
||||||
|
&.extend,
|
||||||
|
&.border {
|
||||||
|
padding: 1px;
|
||||||
|
|
||||||
|
> div {
|
||||||
|
padding: 1px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.fixed-width > div {
|
||||||
|
min-width: unset;
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.fixed-height > div {
|
||||||
|
min-height: unset;
|
||||||
|
}
|
||||||
|
|
||||||
|
> div {
|
||||||
|
transform-origin: left top;
|
||||||
|
min-width: 100%;
|
||||||
|
width: fit-content;
|
||||||
|
min-height: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
a,
|
||||||
|
span:not(.next-input-group):not(.next-input) {
|
||||||
|
&[data-design-mode*='luna-design-'] {
|
||||||
|
display: inline-block;
|
||||||
|
min-height: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-luna-key] {
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-design-mode='luna-design-border'] {
|
||||||
|
min-height: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-design-mode='luna-design-extend'] {
|
||||||
|
min-height: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-design-mode*='luna-design-'] {
|
||||||
|
position: relative;
|
||||||
|
outline: 1px dotted #d9d9d9;
|
||||||
|
zoom: 1;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
outline: 1px dotted #2077ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
[draggable='true'] {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.next-card-body {
|
||||||
|
overflow: inherit !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.next-loading {
|
||||||
|
pointer-events: all !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
&[data-active='true'] {
|
||||||
|
outline: 1px solid #1861d5 !important;
|
||||||
|
|
||||||
|
&[data-nochild='true']:not(.next-step-item):not([top-container='true']) {
|
||||||
|
min-height: 60px;
|
||||||
|
min-width: 200px;
|
||||||
|
|
||||||
|
&:after {
|
||||||
|
content: '请拖入组件';
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
margin: auto;
|
||||||
|
opacity: 1 !important;
|
||||||
|
visibility: visible !important;
|
||||||
|
line-height: 30px;
|
||||||
|
height: 30px;
|
||||||
|
font-size: 13px;
|
||||||
|
color: #ccc;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:not(.next-tag):not(.next-icon):not(.anticon):not(.icon) {
|
||||||
|
@include labelFun(before);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.next-tag,
|
||||||
|
&.next-icon,
|
||||||
|
&.anticon,
|
||||||
|
&.icon {
|
||||||
|
@include labelFun(after);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.next-tabs-tabpane.hidden {
|
||||||
|
min-height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.ant-loop:after {
|
||||||
|
content: '';
|
||||||
|
display: block;
|
||||||
|
clear: both;
|
||||||
|
line-height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-tabs-tabpane {
|
||||||
|
padding: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ide-design-placeholder {
|
||||||
|
position: relative;
|
||||||
|
text-align: center;
|
||||||
|
border: 1px dashed #d9d9d9;
|
||||||
|
outline: none;
|
||||||
|
padding: 0 !important;
|
||||||
|
min-height: 20px;
|
||||||
|
min-width: 80px;
|
||||||
|
|
||||||
|
&:after {
|
||||||
|
content: attr(data-prop);
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
text-align: center;
|
||||||
|
line-height: 20px;
|
||||||
|
color: #e9e9e9;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&[data-instance-name='TableGroupHeaderF'] {
|
||||||
|
clear: both;
|
||||||
|
|
||||||
|
&:after {
|
||||||
|
content: '';
|
||||||
|
width: 100%;
|
||||||
|
height: 0;
|
||||||
|
clear: both;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&[data-design-mode='luna-design-extend'] {
|
||||||
|
&[data-luna-key*='luna_'] {
|
||||||
|
&:not([class*='-input']):not([class*='-picker']):not([class*='-table']):not([class*='-switch']):not([class*='-select']):not(img):not([class*='-btn']):not(.next-tag):not(input):not([class*='-rating']):not([class*='next-menu']) {
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.ant-loop {
|
||||||
|
padding: 10px 0 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[class*='-form-item-control'] {
|
||||||
|
& > [data-design-mode*='luna-design-'] {
|
||||||
|
&:not(button):not(input):not([class*='-input']):not([class*='luna-comp-']) {
|
||||||
|
padding: 0 !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#luna-canvas-effect {
|
||||||
|
position: fixed;
|
||||||
|
background: #1aab11;
|
||||||
|
z-index: 10000000;
|
||||||
|
text-align: center;
|
||||||
|
pointer-events: none;
|
||||||
|
|
||||||
|
&:before,
|
||||||
|
&:after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
width: 2px;
|
||||||
|
height: 2px;
|
||||||
|
border: 4px solid #1aab11;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.left,
|
||||||
|
&.right {
|
||||||
|
width: 2px;
|
||||||
|
|
||||||
|
&:before,
|
||||||
|
&:after {
|
||||||
|
left: -4px;
|
||||||
|
border-left-color: transparent;
|
||||||
|
border-right-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:before {
|
||||||
|
top: 0;
|
||||||
|
border-bottom-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:after {
|
||||||
|
bottom: 0;
|
||||||
|
border-top-width: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.top,
|
||||||
|
&.bottom,
|
||||||
|
&.in {
|
||||||
|
height: 2px;
|
||||||
|
|
||||||
|
&:before,
|
||||||
|
&:after {
|
||||||
|
top: -4px;
|
||||||
|
border-top-color: transparent;
|
||||||
|
border-bottom-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:before {
|
||||||
|
left: 0;
|
||||||
|
border-right-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:after {
|
||||||
|
right: 0;
|
||||||
|
border-left-width: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.in {
|
||||||
|
b {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
b {
|
||||||
|
display: inline-block;
|
||||||
|
position: relative;
|
||||||
|
top: -12px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 0 10px;
|
||||||
|
color: #fff;
|
||||||
|
background: #1aab11;
|
||||||
|
height: 16px !important;
|
||||||
|
line-height: 16px !important;
|
||||||
|
font-weight: normal;
|
||||||
|
font-size: 11px;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
27
packages/react-renderer/src/comp/visualDom/index.jsx
Normal file
27
packages/react-renderer/src/comp/visualDom/index.jsx
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import React, { PureComponent } from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import './index.scss';
|
||||||
|
export default class VisualDom extends PureComponent {
|
||||||
|
static displayName = 'VisualDom';
|
||||||
|
static propTypes = {
|
||||||
|
children: PropTypes.oneOfType([PropTypes.element, PropTypes.arrayOf(PropTypes.element)])
|
||||||
|
};
|
||||||
|
static defaultProps = {
|
||||||
|
children: null
|
||||||
|
};
|
||||||
|
render() {
|
||||||
|
const { children, cell, title, label, text, __componentName } = this.props;
|
||||||
|
let mainContent = children;
|
||||||
|
if (cell && typeof cell === 'function') {
|
||||||
|
mainContent = cell();
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<div className="visual-dom">
|
||||||
|
<div className="panel-container">
|
||||||
|
<span className="title">{title || label || text || __componentName}</span>
|
||||||
|
<div className="content">{mainContent}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
19
packages/react-renderer/src/comp/visualDom/index.scss
Normal file
19
packages/react-renderer/src/comp/visualDom/index.scss
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
.visual-dom {
|
||||||
|
.panel-container {
|
||||||
|
box-sizing: border-box;
|
||||||
|
border: 1px solid #e9e9e9;
|
||||||
|
.title {
|
||||||
|
display: block;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #333;
|
||||||
|
background-color: #ebecf0;
|
||||||
|
line-height: 28px;
|
||||||
|
padding: 0 12px;
|
||||||
|
border-bottom: 1px solid #e9e9e9;
|
||||||
|
}
|
||||||
|
.content {
|
||||||
|
min-height: 20px;
|
||||||
|
padding: 5px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
3
packages/react-renderer/src/context/appContext.js
vendored
Normal file
3
packages/react-renderer/src/context/appContext.js
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
import { createContext } from 'react';
|
||||||
|
const context = (window.__appContext = createContext({}));
|
||||||
|
export default context;
|
||||||
131
packages/react-renderer/src/engine/addonEngine.jsx
Normal file
131
packages/react-renderer/src/engine/addonEngine.jsx
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import Debug from 'debug';
|
||||||
|
import AppContext from '../context/appContext';
|
||||||
|
import classnames from 'classnames';
|
||||||
|
import BaseEngine from './base';
|
||||||
|
import { isSchema, getFileCssName, isEmpty, goldlog } from '../utils';
|
||||||
|
const debug = Debug('engine:addon');
|
||||||
|
export default class AddonEngine extends BaseEngine {
|
||||||
|
static dislayName = 'addon-engine';
|
||||||
|
static propTypes = {
|
||||||
|
config: PropTypes.object,
|
||||||
|
__schema: PropTypes.object
|
||||||
|
};
|
||||||
|
static defaultProps = {
|
||||||
|
config: {},
|
||||||
|
__schema: {}
|
||||||
|
};
|
||||||
|
|
||||||
|
static getDerivedStateFromProps(props, state) {
|
||||||
|
debug(`comp.getDerivedStateFromProps`);
|
||||||
|
const func = props.__schema.lifeCycles && props.__schema.lifeCycles.getDerivedStateFromProps;
|
||||||
|
if (func) {
|
||||||
|
return func(props, state);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(props, context) {
|
||||||
|
super(props, context);
|
||||||
|
this.__generateCtx({
|
||||||
|
component: this
|
||||||
|
});
|
||||||
|
const schema = props.__schema || {};
|
||||||
|
this.state = this.__parseData(schema.state || {});
|
||||||
|
if (isEmpty(props.config) || !props.config.addonKey) {
|
||||||
|
console.warn('luna addon has wrong config');
|
||||||
|
this.state.__hasError = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 注册插件
|
||||||
|
this.addonKey = props.config.addonKey;
|
||||||
|
this.appHelper.addons = this.appHelper.addons || {};
|
||||||
|
this.appHelper.addons[this.addonKey] = this;
|
||||||
|
this.__initDataSource(props);
|
||||||
|
this.open = this.open || (() => {});
|
||||||
|
this.close = this.close || (() => {});
|
||||||
|
this.__setLifeCycleMethods('constructor', arguments);
|
||||||
|
debug(`addon.constructor - ${schema.fileName}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
async getSnapshotBeforeUpdate() {
|
||||||
|
super.getSnapshotBeforeUpdate(...arguments);
|
||||||
|
debug(`addon.getSnapshotBeforeUpdate - ${this.props.__schema.fileName}`);
|
||||||
|
}
|
||||||
|
async componentDidMount() {
|
||||||
|
super.componentDidMount(...arguments);
|
||||||
|
debug(`addon.componentDidMount - ${this.props.__schema.fileName}`);
|
||||||
|
}
|
||||||
|
async componentDidUpdate() {
|
||||||
|
super.componentDidUpdate(...arguments);
|
||||||
|
debug(`addon.componentDidUpdate - ${this.props.__schema.fileName}`);
|
||||||
|
}
|
||||||
|
async componentWillUnmount() {
|
||||||
|
super.componentWillUnmount(...arguments);
|
||||||
|
// 注销插件
|
||||||
|
const config = this.props.config || {};
|
||||||
|
if (config && this.appHelper.addons) {
|
||||||
|
delete this.appHelper.addons[config.addonKey];
|
||||||
|
}
|
||||||
|
debug(`addon.componentWillUnmount - ${this.props.__schema.fileName}`);
|
||||||
|
}
|
||||||
|
async componentDidCatch(e) {
|
||||||
|
super.componentDidCatch(...arguments);
|
||||||
|
debug(`addon.componentDidCatch - ${this.props.__schema.fileName}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
goldlog = (goKey, params) => {
|
||||||
|
const { addonKey, addonConfig = {} } = this.props.config || {};
|
||||||
|
goldlog(
|
||||||
|
goKey,
|
||||||
|
{
|
||||||
|
addonKey,
|
||||||
|
package: addonConfig.package,
|
||||||
|
version: addonConfig.version,
|
||||||
|
...this.appHelper.logParams,
|
||||||
|
...params
|
||||||
|
},
|
||||||
|
'addon'
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
get utils() {
|
||||||
|
const { utils = {} } = this.context.config || {};
|
||||||
|
return { ...this.appHelper.utils, ...utils };
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { __schema } = this.props;
|
||||||
|
|
||||||
|
if (!isSchema(__schema, true) || __schema.componentName !== 'Addon') {
|
||||||
|
return '插件schema结构异常!';
|
||||||
|
}
|
||||||
|
|
||||||
|
debug(`addon.render - ${__schema.fileName}`);
|
||||||
|
this.__generateCtx({
|
||||||
|
component: this
|
||||||
|
});
|
||||||
|
this.__render();
|
||||||
|
|
||||||
|
const { id, className, style } = this.__parseData(__schema.props);
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
ref={this.__getRef}
|
||||||
|
className={classnames('luna-addon', getFileCssName(__schema.fileName), className, this.props.className)}
|
||||||
|
id={this.props.id || id}
|
||||||
|
style={{ ...style, ...this.props.style }}
|
||||||
|
>
|
||||||
|
<AppContext.Provider
|
||||||
|
value={{
|
||||||
|
...this.context,
|
||||||
|
compContext: this,
|
||||||
|
blockContext: this
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{this.__createDom()}
|
||||||
|
</AppContext.Provider>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
506
packages/react-renderer/src/engine/base.jsx
Normal file
506
packages/react-renderer/src/engine/base.jsx
Normal file
@ -0,0 +1,506 @@
|
|||||||
|
import React, { PureComponent } from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import Debug from 'debug';
|
||||||
|
import Div from '@ali/iceluna-comp-div';
|
||||||
|
import VisualDom from '../comp/visualDom';
|
||||||
|
|
||||||
|
import AppContext from '../context/appContext';
|
||||||
|
import DataHelper from '../utils/dataHelper';
|
||||||
|
|
||||||
|
import {
|
||||||
|
forEach,
|
||||||
|
getValue,
|
||||||
|
parseData,
|
||||||
|
parseExpression,
|
||||||
|
isEmpty,
|
||||||
|
isSchema,
|
||||||
|
isFileSchema,
|
||||||
|
isJSExpression,
|
||||||
|
isJSSlot,
|
||||||
|
isJSFunction,
|
||||||
|
transformArrayToMap,
|
||||||
|
transformStringToFunction,
|
||||||
|
checkPropTypes,
|
||||||
|
generateI18n,
|
||||||
|
acceptsRef
|
||||||
|
} from '../utils';
|
||||||
|
|
||||||
|
const debug = Debug('engine:base');
|
||||||
|
const DESIGN_MODE = {
|
||||||
|
EXTEND: 'extend',
|
||||||
|
BORDER: 'border',
|
||||||
|
PREVIEW: 'preview'
|
||||||
|
};
|
||||||
|
const OVERLAY_LIST = ['Dialog', 'Overlay', 'Animate', 'ConfigProvider'];
|
||||||
|
let scopeIdx = 0;
|
||||||
|
|
||||||
|
export default class BaseEngine extends PureComponent {
|
||||||
|
static dislayName = 'base-engine';
|
||||||
|
static propTypes = {
|
||||||
|
locale: PropTypes.string,
|
||||||
|
messages: PropTypes.object,
|
||||||
|
__appHelper: PropTypes.object,
|
||||||
|
__components: PropTypes.object,
|
||||||
|
__componentsMap: PropTypes.object,
|
||||||
|
__ctx: PropTypes.object,
|
||||||
|
__schema: PropTypes.object
|
||||||
|
};
|
||||||
|
static defaultProps = {
|
||||||
|
__schema: {}
|
||||||
|
};
|
||||||
|
static contextType = AppContext;
|
||||||
|
|
||||||
|
constructor(props, context) {
|
||||||
|
super(props, context);
|
||||||
|
this.appHelper = props.__appHelper;
|
||||||
|
this.__compScopes = {};
|
||||||
|
const { locale, messages } = props;
|
||||||
|
this.i18n = generateI18n(locale, messages);
|
||||||
|
this.__bindCustomMethods(props);
|
||||||
|
}
|
||||||
|
|
||||||
|
async getSnapshotBeforeUpdate() {
|
||||||
|
this.__setLifeCycleMethods('getSnapshotBeforeUpdate', arguments);
|
||||||
|
}
|
||||||
|
|
||||||
|
async componentDidMount() {
|
||||||
|
this.reloadDataSource();
|
||||||
|
this.__setLifeCycleMethods('componentDidMount', arguments);
|
||||||
|
}
|
||||||
|
|
||||||
|
async componentDidUpdate() {
|
||||||
|
this.__setLifeCycleMethods('componentDidUpdate', arguments);
|
||||||
|
}
|
||||||
|
|
||||||
|
async componentWillUnmount() {
|
||||||
|
this.__setLifeCycleMethods('componentWillUnmount', arguments);
|
||||||
|
}
|
||||||
|
|
||||||
|
async componentDidCatch(e) {
|
||||||
|
this.__setLifeCycleMethods('componentDidCatch', arguments);
|
||||||
|
console.warn(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
reloadDataSource = () => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
debug('reload data source');
|
||||||
|
if (!this.__dataHelper) {
|
||||||
|
this.__showPlaceholder = false;
|
||||||
|
return resolve();
|
||||||
|
}
|
||||||
|
this.__dataHelper
|
||||||
|
.getInitData()
|
||||||
|
.then(res => {
|
||||||
|
this.__showPlaceholder = false;
|
||||||
|
if (isEmpty(res)) {
|
||||||
|
this.forceUpdate();
|
||||||
|
return resolve();
|
||||||
|
}
|
||||||
|
this.setState(res, resolve);
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
if (this.__showPlaceholder) {
|
||||||
|
this.__showPlaceholder = false;
|
||||||
|
this.forceUpdate();
|
||||||
|
}
|
||||||
|
reject(err);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
__setLifeCycleMethods = (method, args) => {
|
||||||
|
const lifeCycleMethods = getValue(this.props.__schema, 'lifeCycles', {});
|
||||||
|
if (lifeCycleMethods[method]) {
|
||||||
|
try {
|
||||||
|
return lifeCycleMethods[method].apply(this, args);
|
||||||
|
} catch (e) {
|
||||||
|
console.error(`[${this.props.__schema.componentName}]生命周期${method}出错`, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
__bindCustomMethods = (props = this.props) => {
|
||||||
|
const { __schema } = props;
|
||||||
|
const customMethodsList = Object.keys(__schema.methods || {}) || [];
|
||||||
|
this.__customMethodsList &&
|
||||||
|
this.__customMethodsList.forEach(item => {
|
||||||
|
if (!customMethodsList.includes(item)) {
|
||||||
|
delete this[item];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.__customMethodsList = customMethodsList;
|
||||||
|
forEach(__schema.methods, (val, key) => {
|
||||||
|
this[key] = val.bind(this);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
__generateCtx = ctx => {
|
||||||
|
const { pageContext, compContext } = this.context;
|
||||||
|
const obj = {
|
||||||
|
page: pageContext,
|
||||||
|
component: compContext,
|
||||||
|
...ctx
|
||||||
|
};
|
||||||
|
forEach(obj, (val, key) => {
|
||||||
|
this[key] = val;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
__parseData = (data, ctx) => {
|
||||||
|
const { __ctx } = this.props;
|
||||||
|
return parseData(data, ctx || __ctx || this);
|
||||||
|
};
|
||||||
|
|
||||||
|
__initDataSource = (props = this.props) => {
|
||||||
|
const schema = props.__schema || {};
|
||||||
|
const appHelper = props.__appHelper;
|
||||||
|
const dataSource = (schema && schema.dataSource) || {};
|
||||||
|
this.__dataHelper = new DataHelper(this, dataSource, appHelper, config => this.__parseData(config));
|
||||||
|
this.dataSourceMap = this.__dataHelper.dataSourceMap;
|
||||||
|
// 设置容器组件占位,若设置占位则在初始异步请求完成之前用loading占位且不渲染容器组件内部内容
|
||||||
|
this.__showPlaceholder =
|
||||||
|
this.__parseData(schema.props && schema.props.autoLoading) &&
|
||||||
|
(dataSource.list || []).some(item => !!this.__parseData(item.isInit));
|
||||||
|
};
|
||||||
|
|
||||||
|
__render = () => {
|
||||||
|
const schema = this.props.__schema;
|
||||||
|
this.__setLifeCycleMethods('render');
|
||||||
|
|
||||||
|
const engine = this.context.engine;
|
||||||
|
if (engine) {
|
||||||
|
engine.props.onCompGetCtx(schema, this);
|
||||||
|
// 画布场景才需要每次渲染bind自定义方法
|
||||||
|
if (engine.props.designMode) {
|
||||||
|
this.__bindCustomMethods();
|
||||||
|
this.dataSourceMap = this.__dataHelper && this.__dataHelper.updateConfig(schema.dataSource);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
__getRef = ref => {
|
||||||
|
this.__ref = ref;
|
||||||
|
};
|
||||||
|
|
||||||
|
__createDom = () => {
|
||||||
|
const { __schema, __ctx, __components = {} } = this.props;
|
||||||
|
const self = {};
|
||||||
|
self.__proto__ = __ctx || this;
|
||||||
|
return this.__createVirtualDom(__schema.children, self, {
|
||||||
|
schema: __schema,
|
||||||
|
Comp: __components[__schema.componentName]
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// 将模型结构转换成react Element
|
||||||
|
// schema 模型结构
|
||||||
|
// self 为每个渲染组件构造的上下文,self是自上而下继承的
|
||||||
|
// parentInfo 父组件的信息,包含schema和Comp
|
||||||
|
// idx 若为循环渲染的循环Index
|
||||||
|
__createVirtualDom = (schema, self, parentInfo, idx) => {
|
||||||
|
if (!schema) return null;
|
||||||
|
const { __appHelper: appHelper, __components: components = {}, __componentsMap: componentsMap = {} } =
|
||||||
|
this.props || {};
|
||||||
|
const { engine } = this.context || {};
|
||||||
|
if (isJSExpression(schema)) {
|
||||||
|
return parseExpression(schema, self);
|
||||||
|
}
|
||||||
|
if (typeof schema === 'string') return schema;
|
||||||
|
if (typeof schema === 'number' || typeof schema === 'boolean') {
|
||||||
|
return schema.toString();
|
||||||
|
}
|
||||||
|
if (Array.isArray(schema)) {
|
||||||
|
if (schema.length === 1) return this.__createVirtualDom(schema[0], self, parentInfo);
|
||||||
|
return schema.map((item, idx) =>
|
||||||
|
this.__createVirtualDom(item, self, parentInfo, item && item.__ctx && item.__ctx.lunaKey ? '' : idx)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
//解析占位组件
|
||||||
|
if (schema.componentName === 'Flagment' && schema.children) {
|
||||||
|
let tarChildren = isJSExpression(schema.children) ? parseExpression(schema.children, self) : schema.children;
|
||||||
|
return this.__createVirtualDom(tarChildren, self, parentInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (schema.$$typeof) {
|
||||||
|
return schema;
|
||||||
|
}
|
||||||
|
if (!isSchema(schema)) return null;
|
||||||
|
|
||||||
|
let Comp = components[schema.componentName] || Div;
|
||||||
|
|
||||||
|
if (schema.loop !== undefined) {
|
||||||
|
return this.__createLoopVirtualDom(
|
||||||
|
{
|
||||||
|
...schema,
|
||||||
|
loop: parseData(schema.loop, self)
|
||||||
|
},
|
||||||
|
self,
|
||||||
|
parentInfo,
|
||||||
|
idx
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const condition = schema.condition === undefined ? true : parseData(schema.condition, self);
|
||||||
|
if (!condition) return null;
|
||||||
|
|
||||||
|
let scopeKey = '';
|
||||||
|
// 判断组件是否需要生成scope,且只生成一次,挂在this.__compScopes上
|
||||||
|
if (Comp.generateScope) {
|
||||||
|
const key = parseExpression(schema.props.key, self);
|
||||||
|
if (key) {
|
||||||
|
// 如果组件自己设置key则使用组件自己的key
|
||||||
|
scopeKey = key;
|
||||||
|
} else if (!schema.__ctx) {
|
||||||
|
// 在生产环境schema没有__ctx上下文,需要手动生成一个lunaKey
|
||||||
|
schema.__ctx = {
|
||||||
|
lunaKey: `luna${++scopeIdx}`
|
||||||
|
};
|
||||||
|
scopeKey = schema.__ctx.lunaKey;
|
||||||
|
} else {
|
||||||
|
// 需要判断循环的情况
|
||||||
|
scopeKey = schema.__ctx.lunaKey + (idx !== undefined ? `_${idx}` : '');
|
||||||
|
}
|
||||||
|
if (!this.__compScopes[scopeKey]) {
|
||||||
|
this.__compScopes[scopeKey] = Comp.generateScope(this, schema);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 如果组件有设置scope,需要为组件生成一个新的scope上下文
|
||||||
|
if (scopeKey && this.__compScopes[scopeKey]) {
|
||||||
|
const compSelf = { ...this.__compScopes[scopeKey] };
|
||||||
|
compSelf.__proto__ = self;
|
||||||
|
self = compSelf;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 容器类组件的上下文通过props传递,避免context传递带来的嵌套问题
|
||||||
|
const otherProps = isFileSchema(schema)
|
||||||
|
? {
|
||||||
|
__schema: schema,
|
||||||
|
__appHelper: appHelper,
|
||||||
|
__components: components,
|
||||||
|
__componentsMap: componentsMap
|
||||||
|
}
|
||||||
|
: {};
|
||||||
|
if (engine && engine.props.designMode) {
|
||||||
|
otherProps.__designMode = engine.props.designMode;
|
||||||
|
}
|
||||||
|
const componentInfo = componentsMap[schema.componentName] || {};
|
||||||
|
const props = this.__parseProps(schema.props, self, '', {
|
||||||
|
schema,
|
||||||
|
Comp,
|
||||||
|
componentInfo: {
|
||||||
|
...componentInfo,
|
||||||
|
props: transformArrayToMap(componentInfo.props, 'name')
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// 对于可以获取到ref的组件做特殊处理
|
||||||
|
if (acceptsRef(Comp)) {
|
||||||
|
otherProps.ref = ref => {
|
||||||
|
const refProps = props.ref;
|
||||||
|
if (refProps && typeof refProps === 'string') {
|
||||||
|
this[refProps] = ref;
|
||||||
|
}
|
||||||
|
engine && engine.props.onCompGetRef(schema, ref);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
// scope需要传入到组件上
|
||||||
|
if (scopeKey && this.__compScopes[scopeKey]) {
|
||||||
|
props.__scope = this.__compScopes[scopeKey];
|
||||||
|
}
|
||||||
|
if (schema.__ctx && schema.__ctx.lunaKey) {
|
||||||
|
if (!isFileSchema(schema)) {
|
||||||
|
engine && engine.props.onCompGetCtx(schema, self);
|
||||||
|
}
|
||||||
|
props.key = props.key || `${schema.__ctx.lunaKey}_${schema.__ctx.idx || 0}_${idx !== undefined ? idx : ''}`;
|
||||||
|
} else if (typeof idx === 'number' && !props.key) {
|
||||||
|
props.key = idx;
|
||||||
|
}
|
||||||
|
const renderComp = props => (
|
||||||
|
<Comp {...props}>
|
||||||
|
{(!isFileSchema(schema) &&
|
||||||
|
!!schema.children &&
|
||||||
|
this.__createVirtualDom(
|
||||||
|
isJSExpression(schema.children) ? parseExpression(schema.children, self) : schema.children,
|
||||||
|
self,
|
||||||
|
{
|
||||||
|
schema,
|
||||||
|
Comp
|
||||||
|
}
|
||||||
|
)) ||
|
||||||
|
null}
|
||||||
|
</Comp>
|
||||||
|
);
|
||||||
|
//设计模式下的特殊处理
|
||||||
|
if (engine && [DESIGN_MODE.EXTEND, DESIGN_MODE.BORDER].includes(engine.props.designMode)) {
|
||||||
|
//对于overlay,dialog等组件为了使其在设计模式下显示,外层需要增加一个div容器
|
||||||
|
if (OVERLAY_LIST.includes(schema.componentName)) {
|
||||||
|
const { ref, ...overlayProps } = otherProps;
|
||||||
|
return (
|
||||||
|
<Div ref={ref} __designMode={engine.props.designMode}>
|
||||||
|
{renderComp({ ...props, ...overlayProps })}
|
||||||
|
</Div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// 虚拟dom显示
|
||||||
|
if (componentInfo && componentInfo.parentRule) {
|
||||||
|
const parentList = componentInfo.parentRule.split(',');
|
||||||
|
const { schema: parentSchema, Comp: parentComp } = parentInfo;
|
||||||
|
if (!parentList.includes(parentSchema.componentName) || parentComp !== components[parentSchema.componentName]) {
|
||||||
|
props.__componentName = schema.componentName;
|
||||||
|
Comp = VisualDom;
|
||||||
|
} else {
|
||||||
|
// 若虚拟dom在正常的渲染上下文中,就不显示设计模式了
|
||||||
|
props.__disableDesignMode = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return renderComp({ ...props, ...otherProps });
|
||||||
|
};
|
||||||
|
|
||||||
|
__createLoopVirtualDom = (schema, self, parentInfo, idx) => {
|
||||||
|
if (isFileSchema(schema)) {
|
||||||
|
console.warn('file type not support Loop');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (!Array.isArray(schema.loop)) return null;
|
||||||
|
const itemArg = (schema.loopArgs && schema.loopArgs[0]) || 'item';
|
||||||
|
const indexArg = (schema.loopArgs && schema.loopArgs[1]) || 'index';
|
||||||
|
return schema.loop.map((item, i) => {
|
||||||
|
const loopSelf = {
|
||||||
|
[itemArg]: item,
|
||||||
|
[indexArg]: i
|
||||||
|
};
|
||||||
|
loopSelf.__proto__ = self;
|
||||||
|
return this.__createVirtualDom(
|
||||||
|
{
|
||||||
|
...schema,
|
||||||
|
loop: undefined
|
||||||
|
},
|
||||||
|
loopSelf,
|
||||||
|
parentInfo,
|
||||||
|
idx ? `${idx}_${i}` : i
|
||||||
|
);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
__parseProps = (props, self, path, info) => {
|
||||||
|
const { schema, Comp, componentInfo = {} } = info;
|
||||||
|
const propInfo = getValue(componentInfo.props, path);
|
||||||
|
const propType = propInfo && propInfo.extra && propInfo.extra.propType;
|
||||||
|
const ignoreParse = schema.__ignoreParse || [];
|
||||||
|
const checkProps = value => {
|
||||||
|
if (!propType) return value;
|
||||||
|
return checkPropTypes(value, path, propType, componentInfo.name) ? value : undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
const parseReactNode = (data, params) => {
|
||||||
|
if (isEmpty(params)) {
|
||||||
|
return checkProps(this.__createVirtualDom(data, self, { schema, Comp }));
|
||||||
|
} else {
|
||||||
|
return checkProps(function() {
|
||||||
|
const args = {};
|
||||||
|
if (Array.isArray(params) && params.length) {
|
||||||
|
params.map((item, idx) => {
|
||||||
|
if (typeof item === 'string') {
|
||||||
|
args[item] = arguments[idx];
|
||||||
|
} else if (item && typeof item === 'object') {
|
||||||
|
args[item.name] = arguments[idx];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
args.__proto__ = self;
|
||||||
|
return self.__createVirtualDom(data, args, { schema, Comp });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 判断是否需要解析变量
|
||||||
|
if (
|
||||||
|
ignoreParse.some(item => {
|
||||||
|
if (item instanceof RegExp) {
|
||||||
|
return item.test(path);
|
||||||
|
}
|
||||||
|
return item === path;
|
||||||
|
})
|
||||||
|
) {
|
||||||
|
return checkProps(props);
|
||||||
|
}
|
||||||
|
if (isJSExpression(props)) {
|
||||||
|
props = parseExpression(props, self);
|
||||||
|
// 只有当变量解析出来为模型结构的时候才会继续解析
|
||||||
|
if (!isSchema(props) && !isJSSlot(props)) return checkProps(props);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isJSFunction(props)) {
|
||||||
|
props = transformStringToFunction(props.value);
|
||||||
|
}
|
||||||
|
if (isJSSlot(props)) {
|
||||||
|
const { params, value } = props;
|
||||||
|
if (!isSchema(value) || isEmpty(value)) return undefined;
|
||||||
|
return parseReactNode(value, params);
|
||||||
|
}
|
||||||
|
// 兼容通过componentInfo判断的情况
|
||||||
|
if (isSchema(props)) {
|
||||||
|
const isReactNodeFunction = !!(
|
||||||
|
propInfo &&
|
||||||
|
propInfo.type === 'ReactNode' &&
|
||||||
|
propInfo.props &&
|
||||||
|
propInfo.props.type === 'function'
|
||||||
|
);
|
||||||
|
|
||||||
|
const isMixinReactNodeFunction = !!(
|
||||||
|
propInfo &&
|
||||||
|
propInfo.type === 'Mixin' &&
|
||||||
|
propInfo.props &&
|
||||||
|
propInfo.props.types &&
|
||||||
|
propInfo.props.types.indexOf('ReactNode') > -1 &&
|
||||||
|
propInfo.props.reactNodeProps &&
|
||||||
|
propInfo.props.reactNodeProps.type === 'function'
|
||||||
|
);
|
||||||
|
return parseReactNode(
|
||||||
|
props,
|
||||||
|
isReactNodeFunction
|
||||||
|
? propInfo.props.params
|
||||||
|
: isMixinReactNodeFunction
|
||||||
|
? propInfo.props.reactNodeProps.params
|
||||||
|
: null
|
||||||
|
);
|
||||||
|
} else if (Array.isArray(props)) {
|
||||||
|
return checkProps(props.map((item, idx) => this.__parseProps(item, self, path ? `${path}.${idx}` : idx, info)));
|
||||||
|
} else if (typeof props === 'function') {
|
||||||
|
return checkProps(props.bind(self));
|
||||||
|
} else if (props && typeof props === 'object') {
|
||||||
|
if (props.$$typeof) return checkProps(props);
|
||||||
|
const res = {};
|
||||||
|
forEach(props, (val, key) => {
|
||||||
|
if (key.startsWith('__')) {
|
||||||
|
res[key] = val;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
res[key] = this.__parseProps(val, self, path ? `${path}.${key}` : key, info);
|
||||||
|
});
|
||||||
|
return checkProps(res);
|
||||||
|
} else if (typeof props === 'string') {
|
||||||
|
return checkProps(props.trim());
|
||||||
|
}
|
||||||
|
return checkProps(props);
|
||||||
|
};
|
||||||
|
|
||||||
|
get utils() {
|
||||||
|
return this.appHelper && this.appHelper.utils;
|
||||||
|
}
|
||||||
|
get constants() {
|
||||||
|
return this.appHelper && this.appHelper.constants;
|
||||||
|
}
|
||||||
|
get history() {
|
||||||
|
return this.appHelper && this.appHelper.history;
|
||||||
|
}
|
||||||
|
get location() {
|
||||||
|
return this.appHelper && this.appHelper.location;
|
||||||
|
}
|
||||||
|
get match() {
|
||||||
|
return this.appHelper && this.appHelper.match;
|
||||||
|
}
|
||||||
|
render() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
113
packages/react-renderer/src/engine/blockEngine.jsx
Normal file
113
packages/react-renderer/src/engine/blockEngine.jsx
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import Debug from 'debug';
|
||||||
|
import classnames from 'classnames';
|
||||||
|
import Loading from '@alifd/next/lib/loading';
|
||||||
|
import '@alifd/next/lib/loading/style';
|
||||||
|
|
||||||
|
import BaseEngine from './base';
|
||||||
|
import AppContext from '../context/appContext';
|
||||||
|
import { isSchema, getFileCssName } from '../utils';
|
||||||
|
const debug = Debug('engine:block');
|
||||||
|
export default class BlockEngine extends BaseEngine {
|
||||||
|
static dislayName = 'block-engine';
|
||||||
|
static propTypes = {
|
||||||
|
__schema: PropTypes.object
|
||||||
|
};
|
||||||
|
static defaultProps = {
|
||||||
|
__schema: {}
|
||||||
|
};
|
||||||
|
|
||||||
|
static getDerivedStateFromProps(props, state) {
|
||||||
|
debug(`block.getDerivedStateFromProps`);
|
||||||
|
const func = props.__schema.lifeCycles && props.__schema.lifeCycles.getDerivedStateFromProps;
|
||||||
|
if (func) {
|
||||||
|
return func(props, state);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(props, context) {
|
||||||
|
super(props, context);
|
||||||
|
this.__generateCtx();
|
||||||
|
const schema = props.__schema || {};
|
||||||
|
this.state = this.__parseData(schema.state || {});
|
||||||
|
this.__initDataSource(props);
|
||||||
|
this.__setLifeCycleMethods('constructor', arguments);
|
||||||
|
debug(`block.constructor - ${schema.fileName}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
async getSnapshotBeforeUpdate() {
|
||||||
|
super.getSnapshotBeforeUpdate(...arguments);
|
||||||
|
debug(`block.getSnapshotBeforeUpdate - ${this.props.__schema.fileName}`);
|
||||||
|
}
|
||||||
|
async componentDidMount() {
|
||||||
|
super.componentDidMount(...arguments);
|
||||||
|
debug(`block.componentDidMount - ${this.props.__schema.fileName}`);
|
||||||
|
}
|
||||||
|
async componentDidUpdate() {
|
||||||
|
super.componentDidUpdate(...arguments);
|
||||||
|
debug(`block.componentDidUpdate - ${this.props.__schema.fileName}`);
|
||||||
|
}
|
||||||
|
async componentWillUnmount() {
|
||||||
|
super.componentWillUnmount(...arguments);
|
||||||
|
debug(`block.componentWillUnmount - ${this.props.__schema.fileName}`);
|
||||||
|
}
|
||||||
|
async componentDidCatch() {
|
||||||
|
await super.componentDidCatch(...arguments);
|
||||||
|
debug(`block.componentDidCatch - ${this.props.__schema.fileName}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { __schema } = this.props;
|
||||||
|
|
||||||
|
if (!isSchema(__schema, true) || __schema.componentName !== 'Block') {
|
||||||
|
return '区块schema结构异常!';
|
||||||
|
}
|
||||||
|
|
||||||
|
debug(`block.render - ${__schema.fileName}`);
|
||||||
|
this.__generateCtx();
|
||||||
|
this.__render();
|
||||||
|
|
||||||
|
const { id, className, style, autoLoading, defaultHeight = 300, loading } = this.__parseData(__schema.props);
|
||||||
|
const renderContent = () => (
|
||||||
|
<AppContext.Provider
|
||||||
|
value={{
|
||||||
|
...this.context,
|
||||||
|
blockContext: this
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{this.__createDom()}
|
||||||
|
</AppContext.Provider>
|
||||||
|
);
|
||||||
|
|
||||||
|
if (autoLoading || loading !== undefined) {
|
||||||
|
return (
|
||||||
|
<Loading
|
||||||
|
size="medium"
|
||||||
|
visible={!!(this.__showPlaceholder || loading)}
|
||||||
|
style={{
|
||||||
|
height: this.__showPlaceholder ? defaultHeight : 'auto',
|
||||||
|
display: 'block',
|
||||||
|
...style
|
||||||
|
}}
|
||||||
|
className={classnames('luna-block', getFileCssName(__schema.fileName), className, this.props.className)}
|
||||||
|
id={id}
|
||||||
|
>
|
||||||
|
{!this.__showPlaceholder && renderContent()}
|
||||||
|
</Loading>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
ref={this.__getRef}
|
||||||
|
className={classnames('luna-block', getFileCssName(__schema.fileName), className, this.props.className)}
|
||||||
|
id={id}
|
||||||
|
style={style}
|
||||||
|
>
|
||||||
|
{renderContent()}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
122
packages/react-renderer/src/engine/compEngine.jsx
Normal file
122
packages/react-renderer/src/engine/compEngine.jsx
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import Debug from 'debug';
|
||||||
|
import classnames from 'classnames';
|
||||||
|
import Loading from '@alifd/next/lib/loading';
|
||||||
|
import '@alifd/next/lib/loading/style';
|
||||||
|
|
||||||
|
import AppContext from '../context/appContext';
|
||||||
|
import BaseEngine from './base';
|
||||||
|
import { isSchema, getFileCssName } from '../utils';
|
||||||
|
const debug = Debug('engine:comp');
|
||||||
|
export default class CompEngine extends BaseEngine {
|
||||||
|
static dislayName = 'comp-engine';
|
||||||
|
static propTypes = {
|
||||||
|
__schema: PropTypes.object
|
||||||
|
};
|
||||||
|
static defaultProps = {
|
||||||
|
__schema: {}
|
||||||
|
};
|
||||||
|
|
||||||
|
static getDerivedStateFromProps(props, state) {
|
||||||
|
debug(`comp.getDerivedStateFromProps`);
|
||||||
|
const func = props.__schema.lifeCycles && props.__schema.lifeCycles.getDerivedStateFromProps;
|
||||||
|
if (func) {
|
||||||
|
return func(props, state);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(props, context) {
|
||||||
|
super(props, context);
|
||||||
|
this.__generateCtx({
|
||||||
|
component: this
|
||||||
|
});
|
||||||
|
const schema = props.__schema || {};
|
||||||
|
this.state = this.__parseData(schema.state || {});
|
||||||
|
this.__initDataSource(props);
|
||||||
|
this.__setLifeCycleMethods('constructor', arguments);
|
||||||
|
debug(`comp.constructor - ${schema.fileName}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
async getSnapshotBeforeUpdate() {
|
||||||
|
super.getSnapshotBeforeUpdate(...arguments);
|
||||||
|
debug(`comp.getSnapshotBeforeUpdate - ${this.props.__schema.fileName}`);
|
||||||
|
}
|
||||||
|
async componentDidMount() {
|
||||||
|
super.componentDidMount(...arguments);
|
||||||
|
debug(`comp.componentDidMount - ${this.props.__schema.fileName}`);
|
||||||
|
}
|
||||||
|
async componentDidUpdate() {
|
||||||
|
super.componentDidUpdate(...arguments);
|
||||||
|
debug(`comp.componentDidUpdate - ${this.props.__schema.fileName}`);
|
||||||
|
}
|
||||||
|
async componentWillUnmount() {
|
||||||
|
super.componentWillUnmount(...arguments);
|
||||||
|
debug(`comp.componentWillUnmount - ${this.props.__schema.fileName}`);
|
||||||
|
}
|
||||||
|
async componentDidCatch(e) {
|
||||||
|
super.componentDidCatch(...arguments);
|
||||||
|
debug(`comp.componentDidCatch - ${this.props.__schema.fileName}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { __schema } = this.props;
|
||||||
|
|
||||||
|
if (!isSchema(__schema, true) || __schema.componentName !== 'Component') {
|
||||||
|
return '自定义组件schema结构异常!';
|
||||||
|
}
|
||||||
|
|
||||||
|
debug(`comp.render - ${__schema.fileName}`);
|
||||||
|
this.__generateCtx({
|
||||||
|
component: this
|
||||||
|
});
|
||||||
|
this.__render();
|
||||||
|
|
||||||
|
const { id, className, style, noContainer, autoLoading, defaultHeight = 300, loading } = this.__parseData(
|
||||||
|
__schema.props
|
||||||
|
);
|
||||||
|
const renderContent = () => (
|
||||||
|
<AppContext.Provider
|
||||||
|
value={{
|
||||||
|
...this.context,
|
||||||
|
compContext: this,
|
||||||
|
blockContext: this
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{this.__createDom()}
|
||||||
|
</AppContext.Provider>
|
||||||
|
);
|
||||||
|
|
||||||
|
if (noContainer) {
|
||||||
|
return renderContent();
|
||||||
|
}
|
||||||
|
if (autoLoading || loading !== undefined) {
|
||||||
|
return (
|
||||||
|
<Loading
|
||||||
|
size="medium"
|
||||||
|
visible={!!(this.__showPlaceholder || loading)}
|
||||||
|
style={{
|
||||||
|
height: this.__showPlaceholder ? defaultHeight : 'auto',
|
||||||
|
display: 'block',
|
||||||
|
...style
|
||||||
|
}}
|
||||||
|
className={classnames('luna-comp', getFileCssName(__schema.fileName), className, this.props.className)}
|
||||||
|
id={this.props.id || id}
|
||||||
|
>
|
||||||
|
{!this.__showPlaceholder && renderContent()}
|
||||||
|
</Loading>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
ref={this.__getRef}
|
||||||
|
className={classnames('luna-comp', getFileCssName(__schema.fileName), className, this.props.className)}
|
||||||
|
id={this.props.id || id}
|
||||||
|
style={{ ...style, ...this.props.style }}
|
||||||
|
>
|
||||||
|
{renderContent()}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
127
packages/react-renderer/src/engine/index.jsx
Normal file
127
packages/react-renderer/src/engine/index.jsx
Normal file
@ -0,0 +1,127 @@
|
|||||||
|
import React, { PureComponent } from 'react';
|
||||||
|
import ReactDOM from 'react-dom';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import Debug from 'debug';
|
||||||
|
import AppContext from '../context/appContext';
|
||||||
|
import { isFileSchema, goldlog } from '../utils';
|
||||||
|
import Page from './pageEngine';
|
||||||
|
import Component from './compEngine';
|
||||||
|
import Block from './blockEngine';
|
||||||
|
import Addon from './addonEngine';
|
||||||
|
import Temp from './tempEngine';
|
||||||
|
import { isEmpty } from '@ali/b3-one/lib/obj';
|
||||||
|
|
||||||
|
window.React = React;
|
||||||
|
window.ReactDom = ReactDOM;
|
||||||
|
|
||||||
|
const debug = Debug('engine:entry');
|
||||||
|
const ENGINE_COMPS = {
|
||||||
|
Page,
|
||||||
|
Component,
|
||||||
|
Block,
|
||||||
|
Addon,
|
||||||
|
Temp
|
||||||
|
};
|
||||||
|
export default class Engine extends PureComponent {
|
||||||
|
static dislayName = 'engine';
|
||||||
|
static propTypes = {
|
||||||
|
appHelper: PropTypes.object,
|
||||||
|
components: PropTypes.object,
|
||||||
|
componentsMap: PropTypes.object,
|
||||||
|
designMode: PropTypes.string,
|
||||||
|
suspended: PropTypes.bool,
|
||||||
|
schema: PropTypes.oneOfType([PropTypes.array, PropTypes.object]),
|
||||||
|
onCompGetRef: PropTypes.func,
|
||||||
|
onCompGetCtx: PropTypes.func
|
||||||
|
};
|
||||||
|
static defaultProps = {
|
||||||
|
appHelper: null,
|
||||||
|
components: {},
|
||||||
|
componentsMap: {},
|
||||||
|
designMode: '',
|
||||||
|
suspended: false,
|
||||||
|
schema: {},
|
||||||
|
onCompGetRef: () => {},
|
||||||
|
onCompGetCtx: () => {}
|
||||||
|
};
|
||||||
|
|
||||||
|
constructor(props, context) {
|
||||||
|
super(props, context);
|
||||||
|
this.state = {};
|
||||||
|
debug(`entry.constructor - ${props.schema && props.schema.componentName}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
async componentDidMount() {
|
||||||
|
goldlog(
|
||||||
|
'EXP',
|
||||||
|
{
|
||||||
|
action: 'appear',
|
||||||
|
value: !!this.props.designMode
|
||||||
|
},
|
||||||
|
'engine'
|
||||||
|
);
|
||||||
|
debug(`entry.componentDidMount - ${this.props.schema && this.props.schema.componentName}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
async componentDidUpdate() {
|
||||||
|
debug(`entry.componentDidUpdate - ${this.props.schema && this.props.schema.componentName}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
async componentWillUnmount() {
|
||||||
|
debug(`entry.componentWillUnmount - ${this.props.schema && this.props.schema.componentName}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
async componentDidCatch(e) {
|
||||||
|
console.warn(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
shouldComponentUpdate(nextProps) {
|
||||||
|
return !nextProps.suspended;
|
||||||
|
}
|
||||||
|
|
||||||
|
__getRef = ref => {
|
||||||
|
this.__ref = ref;
|
||||||
|
if (ref) {
|
||||||
|
this.props.onCompGetRef(this.props.schema, ref, true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { schema, designMode, appHelper, components, componentsMap } = this.props;
|
||||||
|
if (isEmpty(schema)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (!isFileSchema(schema)) {
|
||||||
|
return '模型结构异常';
|
||||||
|
}
|
||||||
|
debug('entry.render');
|
||||||
|
const allComponents = { ...ENGINE_COMPS, ...components };
|
||||||
|
const Comp = allComponents[schema.componentName];
|
||||||
|
if (Comp) {
|
||||||
|
return (
|
||||||
|
<AppContext.Provider
|
||||||
|
value={{
|
||||||
|
appHelper,
|
||||||
|
components: allComponents,
|
||||||
|
componentsMap,
|
||||||
|
engine: this
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Comp
|
||||||
|
key={schema.__ctx && `${schema.__ctx.lunaKey}_${schema.__ctx.idx || '0'}`}
|
||||||
|
ref={this.__getRef}
|
||||||
|
__appHelper={appHelper}
|
||||||
|
__components={allComponents}
|
||||||
|
__componentsMap={componentsMap}
|
||||||
|
__schema={schema}
|
||||||
|
__designMode={designMode}
|
||||||
|
{...this.props}
|
||||||
|
/>
|
||||||
|
</AppContext.Provider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Engine.findDOMNode = ReactDOM.findDOMNode;
|
||||||
117
packages/react-renderer/src/engine/pageEngine.jsx
Normal file
117
packages/react-renderer/src/engine/pageEngine.jsx
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import Debug from 'debug';
|
||||||
|
import classnames from 'classnames';
|
||||||
|
import Loading from '@alifd/next/lib/loading';
|
||||||
|
import '@alifd/next/lib/loading/style';
|
||||||
|
import AppContext from '../context/appContext';
|
||||||
|
import BaseEngine from './base';
|
||||||
|
import { isSchema, getFileCssName } from '../utils';
|
||||||
|
const debug = Debug('engine:page');
|
||||||
|
|
||||||
|
export default class PageEngine extends BaseEngine {
|
||||||
|
static dislayName = 'page-engine';
|
||||||
|
static propTypes = {
|
||||||
|
__schema: PropTypes.object
|
||||||
|
};
|
||||||
|
static defaultProps = {
|
||||||
|
__schema: {}
|
||||||
|
};
|
||||||
|
|
||||||
|
static getDerivedStateFromProps(props, state) {
|
||||||
|
debug(`page.getDerivedStateFromProps`);
|
||||||
|
const func = props.__schema.lifeCycles && props.__schema.lifeCycles.getDerivedStateFromProps;
|
||||||
|
if (func) {
|
||||||
|
return func(props, state);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(props, context) {
|
||||||
|
super(props, context);
|
||||||
|
this.__generateCtx({
|
||||||
|
page: this
|
||||||
|
});
|
||||||
|
const schema = props.__schema || {};
|
||||||
|
this.state = this.__parseData(schema.state || {});
|
||||||
|
this.__initDataSource(props);
|
||||||
|
this.__setLifeCycleMethods('constructor', arguments);
|
||||||
|
|
||||||
|
debug(`page.constructor - ${schema.fileName}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
async getSnapshotBeforeUpdate() {
|
||||||
|
super.getSnapshotBeforeUpdate(...arguments);
|
||||||
|
debug(`page.getSnapshotBeforeUpdate - ${this.props.__schema.fileName}`);
|
||||||
|
}
|
||||||
|
async componentDidMount() {
|
||||||
|
super.componentDidMount(...arguments);
|
||||||
|
debug(`page.componentDidMount - ${this.props.__schema.fileName}`);
|
||||||
|
}
|
||||||
|
async componentDidUpdate() {
|
||||||
|
super.componentDidUpdate(...arguments);
|
||||||
|
debug(`page.componentDidUpdate - ${this.props.__schema.fileName}`);
|
||||||
|
}
|
||||||
|
async componentWillUnmount() {
|
||||||
|
super.componentWillUnmount(...arguments);
|
||||||
|
debug(`page.componentWillUnmount - ${this.props.__schema.fileName}`);
|
||||||
|
}
|
||||||
|
async componentDidCatch() {
|
||||||
|
await super.componentDidCatch(...arguments);
|
||||||
|
debug(`page.componentDidCatch - ${this.props.__schema.fileName}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { __schema } = this.props;
|
||||||
|
if (!isSchema(__schema, true) || __schema.componentName !== 'Page') {
|
||||||
|
return '页面schema结构异常!';
|
||||||
|
}
|
||||||
|
debug(`page.render - ${__schema.fileName}`);
|
||||||
|
this.__generateCtx({
|
||||||
|
page: this
|
||||||
|
});
|
||||||
|
this.__render();
|
||||||
|
|
||||||
|
const { id, className, style, autoLoading, defaultHeight = 300, loading } = this.__parseData(__schema.props);
|
||||||
|
const renderContent = () => (
|
||||||
|
<AppContext.Provider
|
||||||
|
value={{
|
||||||
|
...this.context,
|
||||||
|
pageContext: this,
|
||||||
|
blockContext: this
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{this.__createDom()}
|
||||||
|
</AppContext.Provider>
|
||||||
|
);
|
||||||
|
|
||||||
|
if (autoLoading || loading !== undefined) {
|
||||||
|
return (
|
||||||
|
<Loading
|
||||||
|
size="medium"
|
||||||
|
visible={!!(this.__showPlaceholder || loading)}
|
||||||
|
style={{
|
||||||
|
height: this.__showPlaceholder ? defaultHeight : 'auto',
|
||||||
|
display: 'block',
|
||||||
|
...style
|
||||||
|
}}
|
||||||
|
className={classnames('luna-page', getFileCssName(__schema.fileName), className, this.props.className)}
|
||||||
|
id={id}
|
||||||
|
>
|
||||||
|
{!this.__showPlaceholder && renderContent()}
|
||||||
|
</Loading>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
ref={this.__getRef}
|
||||||
|
className={classnames('luna-page', getFileCssName(__schema.fileName), className, this.props.className)}
|
||||||
|
id={id}
|
||||||
|
style={style}
|
||||||
|
>
|
||||||
|
{renderContent()}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
66
packages/react-renderer/src/engine/tempEngine.jsx
Normal file
66
packages/react-renderer/src/engine/tempEngine.jsx
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import Debug from 'debug';
|
||||||
|
import AppContext from '../context/appContext';
|
||||||
|
import BaseEngine from './base';
|
||||||
|
import { isSchema } from '../utils';
|
||||||
|
const debug = Debug('engine:temp');
|
||||||
|
export default class TempEngine extends BaseEngine {
|
||||||
|
static dislayName = 'temp-engine';
|
||||||
|
static propTypes = {
|
||||||
|
__ctx: PropTypes.object,
|
||||||
|
__schema: PropTypes.object
|
||||||
|
};
|
||||||
|
static defaultProps = {
|
||||||
|
__ctx: {},
|
||||||
|
__schema: {}
|
||||||
|
};
|
||||||
|
|
||||||
|
constructor(props, context) {
|
||||||
|
super(props, context);
|
||||||
|
this.state = {};
|
||||||
|
this.cacheSetState = {};
|
||||||
|
debug(`temp.constructor - ${props.__schema.fileName}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
const ctx = this.props.__ctx;
|
||||||
|
if (!ctx) return;
|
||||||
|
const setState = ctx.setState;
|
||||||
|
this.cacheSetState = setState;
|
||||||
|
ctx.setState = (...args) => {
|
||||||
|
setState.call(ctx, ...args);
|
||||||
|
setTimeout(() => this.forceUpdate(), 0);
|
||||||
|
};
|
||||||
|
debug(`temp.componentDidMount - ${this.props.__schema.fileName}`);
|
||||||
|
}
|
||||||
|
componentDidUpdate(prevProps, prevState, snapshot) {
|
||||||
|
debug(`temp.componentDidUpdate - ${this.props.__schema.fileName}`);
|
||||||
|
}
|
||||||
|
componentWillUnmount() {
|
||||||
|
const ctx = this.props.__ctx;
|
||||||
|
if (!ctx || !this.cacheSetState) return;
|
||||||
|
ctx.setState = this.cacheSetState;
|
||||||
|
delete this.cacheSetState;
|
||||||
|
debug(`temp.componentWillUnmount - ${this.props.__schema.fileName}`);
|
||||||
|
}
|
||||||
|
componentDidCatch(e) {
|
||||||
|
console.warn(e);
|
||||||
|
debug(`temp.componentDidCatch - ${this.props.__schema.fileName}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { __schema, __ctx } = this.props;
|
||||||
|
if (!isSchema(__schema, true) || __schema.componentName !== 'Temp') {
|
||||||
|
return '下钻编辑schema结构异常!';
|
||||||
|
}
|
||||||
|
|
||||||
|
debug(`temp.render - ${__schema.fileName}`);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div ref={this.__getRef} className="luna-temp">
|
||||||
|
<AppContext.Provider value={{ ...this.context, ...__ctx }}>{this.__createDom()}</AppContext.Provider>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
55
packages/react-renderer/src/hoc/addonFactory.js
vendored
Normal file
55
packages/react-renderer/src/hoc/addonFactory.js
vendored
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
import React, { PureComponent } from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import AddonEngine from '../engine/addonEngine';
|
||||||
|
import BlockEngine from '../engine/blockEngine';
|
||||||
|
import AppContext from '../context/appContext';
|
||||||
|
import { forEach, isFileSchema } from '../utils';
|
||||||
|
export default function addonFactory(schema, components = {}, componentsMap = {}, config = {}) {
|
||||||
|
class LNAddonView extends PureComponent {
|
||||||
|
static dislayName = 'luna-addon-factory';
|
||||||
|
static version = config.version || '0.0.0';
|
||||||
|
static contextType = AppContext;
|
||||||
|
static propTypes = {
|
||||||
|
forwardedRef: PropTypes.func
|
||||||
|
};
|
||||||
|
render() {
|
||||||
|
if (!schema || schema.componentName !== 'Addon' || !isFileSchema(schema)) {
|
||||||
|
console.warn('编辑器插件模型结构异常!');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const { forwardedRef, ...otherProps } = this.props;
|
||||||
|
const props = {
|
||||||
|
...schema.defaultProps,
|
||||||
|
...otherProps,
|
||||||
|
__schema: schema,
|
||||||
|
ref: forwardedRef
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<AppContext.Provider
|
||||||
|
value={{
|
||||||
|
...this.context,
|
||||||
|
appHelper: window.__ctx && window.__ctx.appHelper, // 插件上下文中的appHelper使用IDE的appHelper
|
||||||
|
components: { ...components, Addon: AddonEngine, Block: BlockEngine },
|
||||||
|
componentsMap,
|
||||||
|
config,
|
||||||
|
locale: props.locale,
|
||||||
|
messages: props.messages
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<AddonEngine
|
||||||
|
{...props}
|
||||||
|
__components={{ ...components, Addon: AddonEngine, Block: BlockEngine }}
|
||||||
|
__componentsMap={componentsMap}
|
||||||
|
__appHelper={window.__ctx && window.__ctx.appHelper}
|
||||||
|
/>
|
||||||
|
</AppContext.Provider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const ResComp = React.forwardRef((props, ref) => <LNAddonView {...props} forwardedRef={ref} />);
|
||||||
|
forEach(schema.static, (val, key) => {
|
||||||
|
ResComp[key] = val;
|
||||||
|
});
|
||||||
|
ResComp.version = config.version || '0.0.0';
|
||||||
|
return ResComp;
|
||||||
|
}
|
||||||
75
packages/react-renderer/src/hoc/compFactory.js
vendored
Normal file
75
packages/react-renderer/src/hoc/compFactory.js
vendored
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
import React, { PureComponent } from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import CompEngine from '../engine/compEngine';
|
||||||
|
import BlockEngine from '../engine/blockEngine';
|
||||||
|
import AppContext from '../context/appContext';
|
||||||
|
import AppHelper from '../utils/appHelper';
|
||||||
|
import { forEach, isFileSchema } from '../utils';
|
||||||
|
export default function compFactory(schema, components = {}, componentsMap = {}, config = {}) {
|
||||||
|
// 自定义组件需要有自己独立的appHelper
|
||||||
|
const appHelper = new AppHelper(config);
|
||||||
|
class LNCompView extends PureComponent {
|
||||||
|
static dislayName = 'luna-comp-factory';
|
||||||
|
static version = config.version || '0.0.0';
|
||||||
|
static contextType = AppContext;
|
||||||
|
static propTypes = {
|
||||||
|
forwardedRef: PropTypes.func
|
||||||
|
};
|
||||||
|
render() {
|
||||||
|
if (!schema || schema.componentName !== 'Component' || !isFileSchema(schema)) {
|
||||||
|
console.warn('自定义组件模型结构异常!');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const { forwardedRef, ...otherProps } = this.props;
|
||||||
|
// 低代码组件透传应用上下文
|
||||||
|
const appCtx = ['utils', 'constants'];
|
||||||
|
appCtx.forEach(key => {
|
||||||
|
if (!appHelper[key] && this.context && this.context.appHelper && this.context.appHelper[key]) {
|
||||||
|
appHelper.set(key, this.context.appHelper[key]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const routerCtx = ['history', 'location', 'match'];
|
||||||
|
routerCtx.forEach(key => {
|
||||||
|
if (this.context && this.context.appHelper && this.context.appHelper[key]) {
|
||||||
|
appHelper.set(key, this.context.appHelper[key]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// 支持通过context透传国际化配置
|
||||||
|
const localeProps = {};
|
||||||
|
const { locale, messages } = this.context;
|
||||||
|
if (locale && messages && messages[schema.fileName]) {
|
||||||
|
localeProps.locale = locale;
|
||||||
|
localeProps.messages = messages[schema.fileName];
|
||||||
|
}
|
||||||
|
const props = {
|
||||||
|
...schema.defaultProps,
|
||||||
|
...localeProps,
|
||||||
|
...otherProps,
|
||||||
|
__schema: schema,
|
||||||
|
ref: forwardedRef
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AppContext.Provider
|
||||||
|
value={{
|
||||||
|
...this.context
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CompEngine
|
||||||
|
{...props}
|
||||||
|
__appHelper={appHelper}
|
||||||
|
__components={{ ...components, Component: CompEngine, Block: BlockEngine }}
|
||||||
|
__componentsMap={componentsMap}
|
||||||
|
/>
|
||||||
|
</AppContext.Provider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const ResComp = React.forwardRef((props, ref) => <LNCompView {...props} forwardedRef={ref} />);
|
||||||
|
forEach(schema.static, (val, key) => {
|
||||||
|
ResComp[key] = val;
|
||||||
|
});
|
||||||
|
ResComp.version = config.version || '0.0.0';
|
||||||
|
return ResComp;
|
||||||
|
}
|
||||||
29
packages/react-renderer/src/hoc/localeConfig.js
vendored
Normal file
29
packages/react-renderer/src/hoc/localeConfig.js
vendored
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import React, { PureComponent } from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import AppContext from '../context/appContext';
|
||||||
|
export default function localeConfig(componentName, Component) {
|
||||||
|
class LNLocaleConfigView extends PureComponent {
|
||||||
|
static dislayName = 'luna-locale-config';
|
||||||
|
static contextType = AppContext;
|
||||||
|
static propTypes = {
|
||||||
|
forwardedRef: PropTypes.func
|
||||||
|
};
|
||||||
|
render() {
|
||||||
|
const { forwardedRef, ...otherProps } = this.props;
|
||||||
|
const { locale, messages } = this.context;
|
||||||
|
const localeProps = {};
|
||||||
|
if (locale && messages && messages[componentName]) {
|
||||||
|
localeProps.locale = locale;
|
||||||
|
localeProps.messages = messages[componentName];
|
||||||
|
}
|
||||||
|
const props = {
|
||||||
|
...localeProps,
|
||||||
|
...otherProps,
|
||||||
|
ref: forwardedRef
|
||||||
|
};
|
||||||
|
return <Component {...props} />;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return React.forwardRef((props, ref) => <LNLocaleConfigView {...props} forwardedRef={ref} />);
|
||||||
|
}
|
||||||
15
packages/react-renderer/src/hoc/suspenseWrapper.js
vendored
Normal file
15
packages/react-renderer/src/hoc/suspenseWrapper.js
vendored
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import React, { PureComponent, Suspense } from 'react';
|
||||||
|
export default function SuspenseWrapper(fallback = null) {
|
||||||
|
return function(Component) {
|
||||||
|
class SuspenseWrapper extends PureComponent {
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<Suspense fallback={fallback}>
|
||||||
|
<Component {...this.props} />
|
||||||
|
</Suspense>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return SuspenseWrapper;
|
||||||
|
};
|
||||||
|
}
|
||||||
16
packages/react-renderer/src/index.js
vendored
Normal file
16
packages/react-renderer/src/index.js
vendored
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
export { default as Canvas } from './comp/canvas';
|
||||||
|
export { default as Addon } from './comp/addon';
|
||||||
|
|
||||||
|
export { default as Engine } from './engine';
|
||||||
|
|
||||||
|
export { default as CompFactory } from './hoc/compFactory';
|
||||||
|
export { default as AddonFatory } from './hoc/addonFactory';
|
||||||
|
export { default as LocaleConfig } from './hoc/localeConfig';
|
||||||
|
|
||||||
|
export { default as AppHelper } from './utils/appHelper';
|
||||||
|
export { default as DataHelper } from './utils/dataHelper';
|
||||||
|
export { default as DndHelper } from './utils/dndHelper';
|
||||||
|
export { default as SchemaHelper } from './utils/schemaHelper';
|
||||||
|
export { default as StorageHelper } from './utils/storageHelper';
|
||||||
|
export { default as UndoRedoHelper } from './utils/undoRedoHelper';
|
||||||
|
export { default as WSHelper } from './utils/wsHelper';
|
||||||
49
packages/react-renderer/src/utils/appHelper.js
vendored
Normal file
49
packages/react-renderer/src/utils/appHelper.js
vendored
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
import EventEmitter from 'events';
|
||||||
|
import Debug from 'debug';
|
||||||
|
let instance = null;
|
||||||
|
const debug = Debug('utils:appHelper');
|
||||||
|
EventEmitter.defaultMaxListeners = 100;
|
||||||
|
|
||||||
|
export default class AppHelper extends EventEmitter {
|
||||||
|
static getInstance = () => {
|
||||||
|
if (!instance) {
|
||||||
|
instance = new AppHelper();
|
||||||
|
}
|
||||||
|
return instance;
|
||||||
|
};
|
||||||
|
|
||||||
|
constructor(config) {
|
||||||
|
super();
|
||||||
|
instance = this;
|
||||||
|
Object.assign(this, config);
|
||||||
|
}
|
||||||
|
|
||||||
|
get(key) {
|
||||||
|
return this[key];
|
||||||
|
}
|
||||||
|
|
||||||
|
set(key, val) {
|
||||||
|
if (typeof key === 'string') {
|
||||||
|
this[key] = val;
|
||||||
|
} else if (typeof key === 'object') {
|
||||||
|
Object.keys(key).forEach(item => {
|
||||||
|
this[item] = key[item];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
batchOn(events, lisenter) {
|
||||||
|
if (!Array.isArray(events)) return;
|
||||||
|
events.forEach(event => this.on(event, lisenter));
|
||||||
|
}
|
||||||
|
|
||||||
|
batchOnce(events, lisenter) {
|
||||||
|
if (!Array.isArray(events)) return;
|
||||||
|
events.forEach(event => this.once(event, lisenter));
|
||||||
|
}
|
||||||
|
|
||||||
|
batchOff(events, lisenter) {
|
||||||
|
if (!Array.isArray(events)) return;
|
||||||
|
events.forEach(event => this.off(event, lisenter));
|
||||||
|
}
|
||||||
|
}
|
||||||
299
packages/react-renderer/src/utils/dataHelper.js
vendored
Normal file
299
packages/react-renderer/src/utils/dataHelper.js
vendored
Normal file
@ -0,0 +1,299 @@
|
|||||||
|
import { transformArrayToMap, isJSFunction, transformStringToFunction, clone } from './index';
|
||||||
|
import { jsonp, mtop, request, get, post, bzb } from './request';
|
||||||
|
import Debug from 'debug';
|
||||||
|
const DS_STATUS = {
|
||||||
|
INIT: 'init',
|
||||||
|
LOADING: 'loading',
|
||||||
|
LOADED: 'loaded',
|
||||||
|
ERROR: 'error'
|
||||||
|
};
|
||||||
|
const debug = Debug('utils:dataHelper');
|
||||||
|
export default class DataHelper {
|
||||||
|
constructor(comp, config = {}, appHelper, parser) {
|
||||||
|
this.host = comp;
|
||||||
|
this.config = config;
|
||||||
|
this.parser = parser;
|
||||||
|
this.ajaxList = (config && config.list) || [];
|
||||||
|
this.ajaxMap = transformArrayToMap(this.ajaxList, 'id');
|
||||||
|
this.dataSourceMap = this.generateDataSourceMap();
|
||||||
|
this.appHelper = appHelper;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重置config,dataSourceMap状态会被重置;
|
||||||
|
resetConfig(config = {}) {
|
||||||
|
this.config = config;
|
||||||
|
this.ajaxList = (config && config.list) || [];
|
||||||
|
this.ajaxMap = transformArrayToMap(this.ajaxList, 'id');
|
||||||
|
this.dataSourceMap = this.generateDataSourceMap();
|
||||||
|
return this.dataSourceMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新config,只会更新配置,状态保存;
|
||||||
|
updateConfig(config = {}) {
|
||||||
|
this.config = config;
|
||||||
|
this.ajaxList = (config && config.list) || [];
|
||||||
|
const ajaxMap = transformArrayToMap(this.ajaxList, 'id');
|
||||||
|
// 删除已经移除的接口
|
||||||
|
Object.keys(this.ajaxMap).forEach(key => {
|
||||||
|
if (!ajaxMap[key]) {
|
||||||
|
delete this.dataSourceMap[key];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.ajaxMap = ajaxMap;
|
||||||
|
// 添加未加入到dataSourceMap中的接口
|
||||||
|
this.ajaxList.forEach(item => {
|
||||||
|
if (!this.dataSourceMap[item.id]) {
|
||||||
|
this.dataSourceMap[item.id] = {
|
||||||
|
status: DS_STATUS.INIT,
|
||||||
|
load: (...args) => {
|
||||||
|
return this.getDataSource(item.id, ...args);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return this.dataSourceMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
generateDataSourceMap() {
|
||||||
|
const res = {};
|
||||||
|
this.ajaxList.forEach(item => {
|
||||||
|
res[item.id] = {
|
||||||
|
status: DS_STATUS.INIT,
|
||||||
|
load: (...args) => {
|
||||||
|
return this.getDataSource(item.id, ...args);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
updateDataSourceMap(id, data, error) {
|
||||||
|
this.dataSourceMap[id].error = error ? error : undefined;
|
||||||
|
this.dataSourceMap[id].data = data;
|
||||||
|
this.dataSourceMap[id].status = error ? DS_STATUS.ERROR : DS_STATUS.LOADED;
|
||||||
|
}
|
||||||
|
|
||||||
|
getInitData() {
|
||||||
|
const initSyncData = this.parser(this.ajaxList).filter(item => {
|
||||||
|
if (item.isInit) {
|
||||||
|
this.dataSourceMap[item.id].status = DS_STATUS.LOADING;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
return this.asyncDataHandler(initSyncData).then(res => {
|
||||||
|
let dataHandler = this.config.dataHandler;
|
||||||
|
if (isJSFunction(dataHandler)) {
|
||||||
|
dataHandler = transformStringToFunction(dataHandler.value);
|
||||||
|
}
|
||||||
|
if (!dataHandler || typeof dataHandler !== 'function') return res;
|
||||||
|
try {
|
||||||
|
return dataHandler.call(this.host, res);
|
||||||
|
} catch (e) {
|
||||||
|
console.error('请求数据处理函数运行出错', e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getDataSource(id, params, otherOptions, callback) {
|
||||||
|
const req = this.parser(this.ajaxMap[id]);
|
||||||
|
const options = req.options || {};
|
||||||
|
if (typeof otherOptions === 'function') {
|
||||||
|
callback = otherOptions;
|
||||||
|
otherOptions = {};
|
||||||
|
}
|
||||||
|
const { headers, ...otherProps } = otherOptions || {};
|
||||||
|
if (!req) {
|
||||||
|
console.warn(`getDataSource API named ${id} not exist`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
return this.asyncDataHandler([
|
||||||
|
{
|
||||||
|
...req,
|
||||||
|
options: {
|
||||||
|
...options,
|
||||||
|
// 支持参数为array的情况,当参数为array时,不做参数合并
|
||||||
|
params:
|
||||||
|
Array.isArray(options.params) || Array.isArray(params)
|
||||||
|
? params || options.params
|
||||||
|
: {
|
||||||
|
...options.params,
|
||||||
|
...params
|
||||||
|
},
|
||||||
|
headers: {
|
||||||
|
...options.headers,
|
||||||
|
...headers
|
||||||
|
},
|
||||||
|
...otherProps
|
||||||
|
}
|
||||||
|
}
|
||||||
|
])
|
||||||
|
.then(res => {
|
||||||
|
try {
|
||||||
|
callback && callback(res && res[id]);
|
||||||
|
} catch (e) {
|
||||||
|
console.error('load请求回调函数报错', e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return res && res[id];
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
try {
|
||||||
|
callback && callback(null, err);
|
||||||
|
} catch (e) {
|
||||||
|
console.error('load请求回调函数报错', e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return err;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
asyncDataHandler(asyncDataList) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const allReq = [];
|
||||||
|
const doserReq = [];
|
||||||
|
const doserList = [];
|
||||||
|
const beforeRequest = this.appHelper && this.appHelper.utils && this.appHelper.utils.beforeRequest;
|
||||||
|
const afterRequest = this.appHelper && this.appHelper.utils && this.appHelper.utils.afterRequest;
|
||||||
|
const csrfInput = document.getElementById('_csrf_token');
|
||||||
|
const _tb_token_ = csrfInput && csrfInput.value;
|
||||||
|
asyncDataList.map(req => {
|
||||||
|
const { id, type, options } = req;
|
||||||
|
if (!id || !type) return;
|
||||||
|
if (type === 'doServer') {
|
||||||
|
const { uri, params } = options || {};
|
||||||
|
if (!uri) return;
|
||||||
|
doserList.push(id);
|
||||||
|
doserReq.push({ name: uri, package: 'cms', params });
|
||||||
|
} else {
|
||||||
|
allReq.push(req);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (doserReq.length > 0) {
|
||||||
|
allReq.push({
|
||||||
|
type: 'doServer',
|
||||||
|
options: {
|
||||||
|
uri: '/nrsService.do',
|
||||||
|
cors: true,
|
||||||
|
method: 'POST',
|
||||||
|
params: {
|
||||||
|
data: JSON.stringify(doserReq),
|
||||||
|
_tb_token_
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (allReq.length === 0) resolve({});
|
||||||
|
const res = {};
|
||||||
|
Promise.all(
|
||||||
|
allReq.map(item => {
|
||||||
|
return new Promise(resolve => {
|
||||||
|
const { type, id, dataHandler, options } = item;
|
||||||
|
const doFetch = (type, options) => {
|
||||||
|
this.fetchOne(type, options)
|
||||||
|
.then(data => {
|
||||||
|
if (afterRequest) {
|
||||||
|
this.appHelper.utils.afterRequest(item, data, undefined, (data, error) => {
|
||||||
|
fetchHandler(data, error);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
fetchHandler(data, undefined);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
if (afterRequest) {
|
||||||
|
// 必须要这么调用,否则beforeRequest中的this会丢失
|
||||||
|
this.appHelper.utils.afterRequest(item, undefined, err, (data, error) => {
|
||||||
|
fetchHandler(data, error);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
fetchHandler(undefined, err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
const fetchHandler = (data, error) => {
|
||||||
|
if (type === 'doServer') {
|
||||||
|
if (!Array.isArray(data)) {
|
||||||
|
data = [data];
|
||||||
|
}
|
||||||
|
doserList.forEach((id, idx) => {
|
||||||
|
const req = this.ajaxMap[id];
|
||||||
|
if (req) {
|
||||||
|
res[id] = this.dataHandler(id, req.dataHandler, data && data[idx], error);
|
||||||
|
this.updateDataSourceMap(id, res[id], error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
res[id] = this.dataHandler(id, dataHandler, data, error);
|
||||||
|
this.updateDataSourceMap(id, res[id], error);
|
||||||
|
}
|
||||||
|
resolve();
|
||||||
|
};
|
||||||
|
|
||||||
|
if (type === 'doServer') {
|
||||||
|
doserList.forEach(item => {
|
||||||
|
this.dataSourceMap[item].status = DS_STATUS.LOADING;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.dataSourceMap[id].status = DS_STATUS.LOADING;
|
||||||
|
}
|
||||||
|
// 请求切片
|
||||||
|
if (beforeRequest) {
|
||||||
|
// 必须要这么调用,否则beforeRequest中的this会丢失
|
||||||
|
this.appHelper.utils.beforeRequest(item, clone(options), options => doFetch(type, options));
|
||||||
|
} else {
|
||||||
|
doFetch(type, options);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.then(() => {
|
||||||
|
resolve(res);
|
||||||
|
})
|
||||||
|
.catch(e => {
|
||||||
|
reject(e);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
dataHandler(id, dataHandler, data, error) {
|
||||||
|
if (isJSFunction(dataHandler)) {
|
||||||
|
dataHandler = transformStringToFunction(dataHandler.value);
|
||||||
|
}
|
||||||
|
if (!dataHandler || typeof dataHandler !== 'function') return data;
|
||||||
|
try {
|
||||||
|
return dataHandler.call(this.host, data, error);
|
||||||
|
} catch (e) {
|
||||||
|
console.error('[' + id + ']单个请求数据处理函数运行出错', e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchOne(type, options) {
|
||||||
|
let { uri, method = 'GET', headers, params, ...otherProps } = options;
|
||||||
|
otherProps = otherProps || {};
|
||||||
|
switch (type) {
|
||||||
|
case 'mtop':
|
||||||
|
method && (otherProps.method = method);
|
||||||
|
return mtop(uri, params, otherProps);
|
||||||
|
case 'jsonp':
|
||||||
|
return jsonp(uri, params, otherProps);
|
||||||
|
case 'bzb':
|
||||||
|
return bzb(uri, params, {
|
||||||
|
method,
|
||||||
|
headers,
|
||||||
|
...otherProps
|
||||||
|
});
|
||||||
|
default:
|
||||||
|
method = method.toUpperCase();
|
||||||
|
if (method === 'GET') {
|
||||||
|
return get(uri, params, headers, otherProps);
|
||||||
|
} else if (method === 'POST') {
|
||||||
|
return post(uri, params, headers, otherProps);
|
||||||
|
}
|
||||||
|
return request(uri, method, params, headers, otherProps);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
574
packages/react-renderer/src/utils/dndHelper.js
vendored
Normal file
574
packages/react-renderer/src/utils/dndHelper.js
vendored
Normal file
@ -0,0 +1,574 @@
|
|||||||
|
import ReactDOM from 'react-dom';
|
||||||
|
import Debug from 'debug';
|
||||||
|
import { isFileSchema, isEmpty, throttle, deepEqual } from './index';
|
||||||
|
const DICT = {
|
||||||
|
left: '左',
|
||||||
|
right: '右',
|
||||||
|
top: '上',
|
||||||
|
bottom: '下',
|
||||||
|
in: '里'
|
||||||
|
};
|
||||||
|
const TOP_COMPONENT = ['Page', 'Component', 'Temp']; // 顶端模块,不支持放置兄弟节点
|
||||||
|
const debug = Debug('utils:dndHelper');
|
||||||
|
export default class DndHelper {
|
||||||
|
constructor(appHelper) {
|
||||||
|
this.appHelper = appHelper;
|
||||||
|
this.dragDom = null;
|
||||||
|
this.canvasEffectDom = null;
|
||||||
|
this.treeEffectDom = null;
|
||||||
|
this.containrDom = null;
|
||||||
|
this.sourceEntity = null;
|
||||||
|
this.tempEntity = null;
|
||||||
|
this.dragInfo = null;
|
||||||
|
this.canvasClearTimer = null;
|
||||||
|
this.treeClearTimer = null;
|
||||||
|
this.isDragging = false;
|
||||||
|
this.dragOverFunc = throttle(this.dragOverFunc, 50);
|
||||||
|
}
|
||||||
|
|
||||||
|
setCanvasWin(win) {
|
||||||
|
this.canvasWin = win;
|
||||||
|
if (this.canvasEffectDom) {
|
||||||
|
this.canvasWin.document.body.appendChild(this.canvasEffectDom);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
emit(msg, ...args) {
|
||||||
|
this.appHelper && this.appHelper.emit(msg, ...args);
|
||||||
|
}
|
||||||
|
|
||||||
|
dragOverFunc(ev, schemaOrNode, isTree) {
|
||||||
|
if (!this.isDragging || !this.sourceEntity) return;
|
||||||
|
const entity = isTree
|
||||||
|
? this.getTreeEntity(schemaOrNode, ev)
|
||||||
|
: {
|
||||||
|
target: ev.currentTarget,
|
||||||
|
schema: schemaOrNode
|
||||||
|
};
|
||||||
|
if (this.sourceEntity.schema.__ctx && this.sourceEntity.schema.__ctx.lunaKey === entity.schema.__ctx.lunaKey)
|
||||||
|
return;
|
||||||
|
let dragInfo = null;
|
||||||
|
if (isTree) {
|
||||||
|
dragInfo = this.getTreeDragInfo(ev, entity);
|
||||||
|
} else {
|
||||||
|
dragInfo = this.getDragInfo(ev, entity);
|
||||||
|
}
|
||||||
|
if (!dragInfo || deepEqual(this.dragInfo, dragInfo)) return;
|
||||||
|
this.dragInfo = dragInfo;
|
||||||
|
this.tempEntity = dragInfo.entity;
|
||||||
|
this.clearEffect(isTree);
|
||||||
|
this.addEffect(isTree);
|
||||||
|
}
|
||||||
|
|
||||||
|
changeCanvas() {
|
||||||
|
debug('change canvas', this.sourceEntity, this.tempEntity);
|
||||||
|
if (!this.sourceEntity || !this.tempEntity) return;
|
||||||
|
if (this.sourceEntity.isAdd) {
|
||||||
|
debug('add material', this.sourceEntity.schema, this.tempEntity.schema.__ctx.lunaKey, this.dragInfo.position);
|
||||||
|
this.emit('material.add', {
|
||||||
|
schema: this.sourceEntity.schema,
|
||||||
|
targetKey: this.tempEntity.schema.__ctx.lunaKey,
|
||||||
|
direction: this.dragInfo.position
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.emit('material.move', {
|
||||||
|
lunaKey: this.sourceEntity.schema.__ctx.lunaKey,
|
||||||
|
targetKey: this.tempEntity.schema.__ctx.lunaKey,
|
||||||
|
direction: this.dragInfo.position
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getTreeEntity(node, ev) {
|
||||||
|
if (!node) return;
|
||||||
|
const schemaHelper = this.appHelper.schemaHelper;
|
||||||
|
const lunaKey = node.props.eventKey;
|
||||||
|
const schema = schemaHelper.schemaMap[lunaKey];
|
||||||
|
if (!schema) return;
|
||||||
|
const ref = schemaHelper.compThisMap[lunaKey];
|
||||||
|
const currentTarget = ev.currentTarget;
|
||||||
|
return {
|
||||||
|
schema,
|
||||||
|
target: ref && ReactDOM.findDOMNode(ref),
|
||||||
|
treeNodeTarget: currentTarget
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
getDragTagDom(tagName) {
|
||||||
|
if (!this.dragDom) {
|
||||||
|
const dragDom = document.createElement('div');
|
||||||
|
dragDom.id = 'luna-drag-dom';
|
||||||
|
dragDom.style.height = '24px';
|
||||||
|
dragDom.style.position = 'absolute';
|
||||||
|
dragDom.style.zIndex = 10000000;
|
||||||
|
dragDom.style.transform = 'translateY(-10000px)';
|
||||||
|
dragDom.style.background = 'rgba(0, 0, 0, .5)';
|
||||||
|
dragDom.style.lineHeight = '24px';
|
||||||
|
dragDom.style.color = '#fff';
|
||||||
|
dragDom.style.padding = '0px 10px';
|
||||||
|
dragDom.style.display = 'inline-block';
|
||||||
|
document.body.appendChild(dragDom);
|
||||||
|
this.dragDom = dragDom;
|
||||||
|
}
|
||||||
|
this.dragDom.innerHTML = `<i class="next-icon next-icon-zujianku next-small"></i> ${tagName}`;
|
||||||
|
return this.dragDom;
|
||||||
|
}
|
||||||
|
|
||||||
|
getCanvasEffectDom() {
|
||||||
|
if (!this.canvasWin) {
|
||||||
|
throw new Error('should set the canvasWin first');
|
||||||
|
}
|
||||||
|
if (this.canvasClearTimer) {
|
||||||
|
clearTimeout(this.canvasClearTimer);
|
||||||
|
this.canvasClearTimer = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { position } = this.dragInfo;
|
||||||
|
let canvasEffectDom = this.canvasEffectDom;
|
||||||
|
if (!canvasEffectDom) {
|
||||||
|
canvasEffectDom = document.createElement('div');
|
||||||
|
this.canvasWin.document.body.appendChild(canvasEffectDom);
|
||||||
|
this.canvasEffectDom = canvasEffectDom;
|
||||||
|
}
|
||||||
|
canvasEffectDom.id = 'luna-canvas-effect';
|
||||||
|
canvasEffectDom.innerHTML = `<b>${DICT[position]}</b>`;
|
||||||
|
canvasEffectDom.className = position;
|
||||||
|
canvasEffectDom.style.display = 'block';
|
||||||
|
|
||||||
|
return canvasEffectDom;
|
||||||
|
}
|
||||||
|
|
||||||
|
getTreeEffectDom() {
|
||||||
|
if (this.treeClearTimer) {
|
||||||
|
clearTimeout(this.treeClearTimer);
|
||||||
|
this.treeClearTimer = null;
|
||||||
|
}
|
||||||
|
let treeEffectDom = this.treeEffectDom;
|
||||||
|
if (!treeEffectDom) {
|
||||||
|
treeEffectDom = document.createElement('div');
|
||||||
|
this.treeEffectDom = treeEffectDom;
|
||||||
|
}
|
||||||
|
treeEffectDom.id = 'luna-tree-effect';
|
||||||
|
treeEffectDom.style.display = 'block';
|
||||||
|
return treeEffectDom;
|
||||||
|
}
|
||||||
|
|
||||||
|
getLunaContainerDom(target) {
|
||||||
|
if (!target) return null;
|
||||||
|
let parent = target.parentNode;
|
||||||
|
while (parent && (!parent.dataset || !parent.dataset.lunaKey)) {
|
||||||
|
parent = parent.parentNode;
|
||||||
|
}
|
||||||
|
return parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
clearCompTreeEffect() {
|
||||||
|
const container = document.querySelector('.luna-comp-tree');
|
||||||
|
if (!container) return;
|
||||||
|
|
||||||
|
let treeItems = container.querySelectorAll('.tree-item');
|
||||||
|
(treeItems || []).forEach(item => {
|
||||||
|
const classList = item.classList;
|
||||||
|
if (classList) {
|
||||||
|
classList.remove('top');
|
||||||
|
classList.remove('in');
|
||||||
|
classList.remove('bottom');
|
||||||
|
classList.remove('tree-item');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getDragInfo(ev, entity) {
|
||||||
|
if (!this.sourceEntity || !entity) return null;
|
||||||
|
const { target, schema } = entity;
|
||||||
|
const sourcePath = this.sourceEntity.schema.__ctx && this.sourceEntity.schema.__ctx.lunaPath;
|
||||||
|
const targetPath = schema.__ctx.lunaPath;
|
||||||
|
const sourceTarget = this.sourceEntity.target;
|
||||||
|
|
||||||
|
if (sourcePath === targetPath) return null;
|
||||||
|
if (targetPath && targetPath.startsWith(sourcePath)) return null;
|
||||||
|
const componentsMap = this.appHelper.get('componentsMap');
|
||||||
|
// if (!componentsMap || !componentsMap[schema.componentName]) return null;
|
||||||
|
let isContainer =
|
||||||
|
(componentsMap[schema.componentName] && componentsMap[schema.componentName].isContainer) || isFileSchema(schema); //是否是容器组件
|
||||||
|
if (schema.children && typeof schema.children !== 'object') {
|
||||||
|
//如果children是文本, 非模型结构,则非容器;
|
||||||
|
isContainer = false;
|
||||||
|
}
|
||||||
|
const rect = target.getBoundingClientRect();
|
||||||
|
const isSupportIn =
|
||||||
|
isContainer &&
|
||||||
|
(!schema.children || (schema.children && typeof schema.children === 'object' && isEmpty(schema.children)));
|
||||||
|
const sourceIsInline = sourceTarget && ['inline-block', 'inline'].includes(getComputedStyle(sourceTarget).display);
|
||||||
|
const isInline = ['inline-block', 'inline'].includes(getComputedStyle(target).display) && sourceIsInline;
|
||||||
|
const measure = isInline ? 'width' : 'height';
|
||||||
|
|
||||||
|
let sn = 0;
|
||||||
|
let position = 'top';
|
||||||
|
if (isContainer) {
|
||||||
|
sn = isSupportIn ? rect[measure] * 0.25 : Math.min(rect[measure] * 0.5, 10);
|
||||||
|
} else {
|
||||||
|
sn = rect[measure] * 0.5;
|
||||||
|
}
|
||||||
|
if (TOP_COMPONENT.includes(schema.componentName)) {
|
||||||
|
// 顶端组件,拖拽over时,只能放在其内部
|
||||||
|
position = 'in';
|
||||||
|
} else if (isInline && !isContainer) {
|
||||||
|
if (Math.abs(ev.clientX - rect.left) <= sn) {
|
||||||
|
position = 'left';
|
||||||
|
} else if (Math.abs(ev.clientX - rect.right) <= sn) {
|
||||||
|
position = 'right';
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (Math.abs(ev.clientY - rect.top) <= sn) {
|
||||||
|
position = 'top';
|
||||||
|
} else if (Math.abs(ev.clientY - rect.bottom) <= sn) {
|
||||||
|
position = 'bottom';
|
||||||
|
} else {
|
||||||
|
position = 'in';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 判断是否是相邻元素, 往左|上拖
|
||||||
|
const isPrevSibling = sourceTarget === target.nextElementSibling;
|
||||||
|
if (isPrevSibling) {
|
||||||
|
if (position === 'right') position = 'left';
|
||||||
|
if (position === 'bottom') {
|
||||||
|
position = isContainer ? 'in' : 'top';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 判断是否相邻元素,往右|下拖
|
||||||
|
const isPostSibling = sourceTarget === target.previousElementSibling;
|
||||||
|
if (isPostSibling) {
|
||||||
|
if (position === 'left') position = 'right';
|
||||||
|
if (position === 'top') {
|
||||||
|
position = isContainer ? 'in' : 'bottom';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//如果是容器组件,且包含有子组件,且是in状态,进行智能识别处理;
|
||||||
|
let subChildren = [];
|
||||||
|
const getChildren = node => {
|
||||||
|
if (!node || !node.childNodes || node.childNodes.length === 0) return;
|
||||||
|
node.childNodes.forEach(child => {
|
||||||
|
if (child === sourceTarget) return;
|
||||||
|
if (child && child.getAttribute && child.getAttribute('draggable')) {
|
||||||
|
const isInline = ['inline', 'inline-block'].includes(getComputedStyle(child).display) && sourceIsInline;
|
||||||
|
const rect = child.getBoundingClientRect();
|
||||||
|
const l = Math.abs(ev.clientX - rect.left);
|
||||||
|
const r = Math.abs(ev.clientX - rect.right);
|
||||||
|
const t = Math.abs(ev.clientY - rect.top);
|
||||||
|
const b = Math.abs(ev.clientY - rect.bottom);
|
||||||
|
const minXDistance = Math.min(l, r);
|
||||||
|
const minYDistance = Math.min(t, b);
|
||||||
|
subChildren.push({
|
||||||
|
lunaKey: child.dataset.lunaKey,
|
||||||
|
node: child,
|
||||||
|
minDistance: isInline ? [minXDistance, minYDistance] : [minYDistance, minXDistance],
|
||||||
|
position: isInline ? (l > r ? 'right' : 'left') : b > t ? 'top' : 'bottom'
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
getChildren(child);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
if (position === 'in' && isContainer && !isSupportIn) {
|
||||||
|
getChildren(target);
|
||||||
|
subChildren = subChildren.sort((a, b) => {
|
||||||
|
if (a.minDistance[0] === b.minDistance[0]) {
|
||||||
|
return a.minDistance[1] - b.minDistance[1];
|
||||||
|
}
|
||||||
|
return a.minDistance[0] - b.minDistance[0];
|
||||||
|
});
|
||||||
|
const tempChild = subChildren[0];
|
||||||
|
if (tempChild) {
|
||||||
|
if (sourceTarget === tempChild.node.nextElementSibling && ['bottom', 'right'].includes(tempChild.position))
|
||||||
|
return null;
|
||||||
|
if (sourceTarget === tempChild.node.previousElementSibling && ['top', 'left'].includes(tempChild.position))
|
||||||
|
return null;
|
||||||
|
position = tempChild.position;
|
||||||
|
entity = {
|
||||||
|
target: tempChild.node,
|
||||||
|
schema: this.appHelper.schemaHelper.schemaMap[tempChild.lunaKey]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const containrDom = position === 'in' ? entity.target : this.getLunaContainerDom(entity.target);
|
||||||
|
if (this.containrDom !== containrDom) {
|
||||||
|
if (this.containrDom) {
|
||||||
|
this.containrDom.style.outline = '';
|
||||||
|
}
|
||||||
|
this.containrDom = containrDom;
|
||||||
|
}
|
||||||
|
if (this.containrDom) {
|
||||||
|
containrDom.style.outline = '1px solid #1aab11';
|
||||||
|
}
|
||||||
|
// debug('drag info:', position, isSupportIn, isContainer, entity);
|
||||||
|
return {
|
||||||
|
position,
|
||||||
|
isSupportIn,
|
||||||
|
isContainer,
|
||||||
|
entity
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
getTreeDragInfo(ev, entity) {
|
||||||
|
if (!this.sourceEntity || !entity) return null;
|
||||||
|
const { schema, treeNodeTarget } = entity;
|
||||||
|
const sourcePath = this.sourceEntity.schema.__ctx && this.sourceEntity.schema.__ctx.lunaPath;
|
||||||
|
const targetPath = schema.__ctx.lunaPath;
|
||||||
|
if (sourcePath === targetPath) return null;
|
||||||
|
if (targetPath && targetPath.startsWith(sourcePath)) return null;
|
||||||
|
const componentsMap = this.appHelper.get('componentsMap');
|
||||||
|
// if (!componentsMap || !componentsMap[schema.componentName]) return null;
|
||||||
|
let isContainer =
|
||||||
|
(componentsMap[schema.componentName] && componentsMap[schema.componentName].isContainer) || isFileSchema(schema); //是否是容器组件
|
||||||
|
if (schema.children && typeof schema.children !== 'object') {
|
||||||
|
//如果children是文本, 非模型结构,则非容器;
|
||||||
|
isContainer = false;
|
||||||
|
}
|
||||||
|
const rect = treeNodeTarget.getBoundingClientRect();
|
||||||
|
const isSupportIn =
|
||||||
|
isContainer &&
|
||||||
|
(!schema.children || (schema.children && typeof schema.children === 'object' && isEmpty(schema.children)));
|
||||||
|
|
||||||
|
const sn = isContainer && isSupportIn ? rect.height * 0.25 : rect.height * 0.5;
|
||||||
|
let position = 'in';
|
||||||
|
if (Math.abs(ev.clientY - rect.top) <= sn) {
|
||||||
|
position = 'top';
|
||||||
|
} else if (Math.abs(ev.clientY - rect.bottom) <= sn) {
|
||||||
|
position = 'bottom';
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
position,
|
||||||
|
isSupportIn,
|
||||||
|
isContainer,
|
||||||
|
entity
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
addEffect(isTree) {
|
||||||
|
if (!this.tempEntity) return;
|
||||||
|
const { position } = this.dragInfo;
|
||||||
|
const { target, treeNodeTarget } = this.tempEntity;
|
||||||
|
// this.clearCompTreeEffect();
|
||||||
|
if (isTree) {
|
||||||
|
//画父元素外框
|
||||||
|
let status = true;
|
||||||
|
let node = treeNodeTarget.parentNode;
|
||||||
|
while (status) {
|
||||||
|
if (node && node.parentNode) {
|
||||||
|
if (node.parentNode.tagName == 'LI' && node.parentNode.classList.contains('next-tree-node')) {
|
||||||
|
status = false;
|
||||||
|
if (this.treeNodeTargetParent !== node.parentNode || position === 'in') {
|
||||||
|
this.treeNodeTargetParent && this.treeNodeTargetParent.classList.remove('selected');
|
||||||
|
}
|
||||||
|
this.treeNodeTargetParent = node.parentNode;
|
||||||
|
if (position !== 'in') this.treeNodeTargetParent.classList.add('selected');
|
||||||
|
} else {
|
||||||
|
node = node.parentNode;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
status = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
treeNodeTarget.appendChild(this.getTreeEffectDom());
|
||||||
|
this.treeEffectDom.className = position;
|
||||||
|
} else {
|
||||||
|
const effectDom = this.getCanvasEffectDom();
|
||||||
|
const rect = target.getBoundingClientRect();
|
||||||
|
effectDom.style.left = (position === 'right' ? rect.right : rect.left) + 'px';
|
||||||
|
effectDom.style.top =
|
||||||
|
(position === 'bottom' ? rect.bottom : position === 'in' ? (rect.top + rect.bottom) / 2 : rect.top) + 'px';
|
||||||
|
effectDom.style.height = ['top', 'in', 'bottom'].includes(position) ? '2px' : rect.height + 'px';
|
||||||
|
effectDom.style.width = ['left', 'right'].includes(position) ? '2px' : rect.width + 'px';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
clearCanvasEffect() {
|
||||||
|
if (this.canvasEffectDom) {
|
||||||
|
this.canvasEffectDom.style.display = 'none';
|
||||||
|
}
|
||||||
|
if (this.containrDom) {
|
||||||
|
this.containrDom.style.outline = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
clearTreeEffect() {
|
||||||
|
if (this.treeEffectDom) {
|
||||||
|
this.treeEffectDom.style.display = 'none';
|
||||||
|
}
|
||||||
|
if (this.treeNodeTargetParent) {
|
||||||
|
this.treeNodeTargetParent.classList.remove('selected');
|
||||||
|
}
|
||||||
|
const tempTarget = this.tempEntity && this.tempEntity.treeNodeTarget;
|
||||||
|
const classList = tempTarget && tempTarget.classList;
|
||||||
|
if (classList) {
|
||||||
|
classList.remove('top');
|
||||||
|
classList.remove('bottom');
|
||||||
|
classList.remove('in');
|
||||||
|
classList.remove('tree-item');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
clearEffect(isTree) {
|
||||||
|
if (this.isDragging) {
|
||||||
|
// if (isTree) {
|
||||||
|
if (this.treeClearTimer) {
|
||||||
|
clearTimeout(this.treeClearTimer);
|
||||||
|
this.treeClearTimer = null;
|
||||||
|
}
|
||||||
|
this.treeClearTimer = setTimeout(() => {
|
||||||
|
this.clearTreeEffect();
|
||||||
|
}, 300);
|
||||||
|
// } else {
|
||||||
|
if (this.canvasClearTimer) {
|
||||||
|
clearTimeout(this.canvasClearTimer);
|
||||||
|
this.canvasClearTimer = null;
|
||||||
|
}
|
||||||
|
this.canvasClearTimer = setTimeout(() => {
|
||||||
|
this.clearCanvasEffect();
|
||||||
|
}, 300);
|
||||||
|
// }
|
||||||
|
} else {
|
||||||
|
// if (isTree) {
|
||||||
|
this.clearTreeEffect();
|
||||||
|
// } else {
|
||||||
|
this.clearCanvasEffect();
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleDragStart(ev, lunaKey) {
|
||||||
|
ev.stopPropagation();
|
||||||
|
const target = ev.currentTarget;
|
||||||
|
target.style.filter = 'blur(2px)';
|
||||||
|
const schema = this.appHelper.schemaHelper.schemaMap[lunaKey];
|
||||||
|
ev.dataTransfer.setDragImage(this.getDragTagDom(schema.componentName), 0, 0);
|
||||||
|
this.sourceEntity = {
|
||||||
|
target,
|
||||||
|
schema
|
||||||
|
};
|
||||||
|
this.isDragging = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
handleDragEnd(ev) {
|
||||||
|
ev.stopPropagation();
|
||||||
|
ev.preventDefault();
|
||||||
|
this.isDragging = false;
|
||||||
|
if (!this.sourceEntity) return;
|
||||||
|
if (this.sourceEntity.target) {
|
||||||
|
this.sourceEntity.target.style.filter = '';
|
||||||
|
}
|
||||||
|
this.clearEffect();
|
||||||
|
}
|
||||||
|
|
||||||
|
handleDragOver(ev, lunaKey) {
|
||||||
|
ev.preventDefault();
|
||||||
|
ev.stopPropagation();
|
||||||
|
this.isDragging = true;
|
||||||
|
const schema = this.appHelper.schemaHelper.schemaMap[lunaKey];
|
||||||
|
this.dragOverFunc(
|
||||||
|
{
|
||||||
|
clientX: ev.clientX,
|
||||||
|
clientY: ev.clientY,
|
||||||
|
currentTarget: ev.currentTarget
|
||||||
|
},
|
||||||
|
schema
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleDragLeave(ev) {
|
||||||
|
//避免移动到treeEffectDom上的抖动
|
||||||
|
ev.stopPropagation();
|
||||||
|
if (!this.tempEntity) return;
|
||||||
|
const rect = ev.target.getBoundingClientRect();
|
||||||
|
// 如果鼠标位置还在当前元素范围内则不认为是dragLeave
|
||||||
|
if (ev.x >= rect.left && ev.x <= rect.right && ev.y >= rect.top && ev.y <= rect.bottom) return;
|
||||||
|
debug('canvas drag leave', ev);
|
||||||
|
this.clearEffect();
|
||||||
|
this.dragInfo = null;
|
||||||
|
this.isDragging = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
handleDrop(ev) {
|
||||||
|
ev.stopPropagation();
|
||||||
|
debug('drop+++++');
|
||||||
|
this.isDragging = false;
|
||||||
|
this.changeCanvas();
|
||||||
|
this.clearEffect();
|
||||||
|
}
|
||||||
|
|
||||||
|
handleTreeDragStart(ev) {
|
||||||
|
const { event, node } = ev;
|
||||||
|
event.stopPropagation();
|
||||||
|
const lunaKey = node.props.eventKey;
|
||||||
|
const schema = this.appHelper.schemaHelper.schemaMap[lunaKey];
|
||||||
|
if (!schema) return;
|
||||||
|
|
||||||
|
event.dataTransfer.setDragImage(this.getDragTagDom(schema.componentName), 0, 0);
|
||||||
|
this.sourceEntity = this.getTreeEntity(node, event);
|
||||||
|
if (this.sourceEntity.target) {
|
||||||
|
this.sourceEntity.target.style.filter = 'blur(2px)';
|
||||||
|
}
|
||||||
|
this.isDragging = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
handleTreeDragEnd(ev) {
|
||||||
|
const { event } = ev;
|
||||||
|
event.stopPropagation();
|
||||||
|
event.preventDefault();
|
||||||
|
this.isDragging = false;
|
||||||
|
if (!this.sourceEntity) return;
|
||||||
|
if (this.sourceEntity.target) {
|
||||||
|
this.sourceEntity.target.style.filter = '';
|
||||||
|
}
|
||||||
|
this.clearEffect(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleTreeDragOver(ev) {
|
||||||
|
const { event, node } = ev;
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
this.isDragging = true;
|
||||||
|
this.dragOverFunc(
|
||||||
|
{
|
||||||
|
clientX: event.clientX,
|
||||||
|
clientY: event.clientY,
|
||||||
|
currentTarget: event.currentTarget.children[0]
|
||||||
|
},
|
||||||
|
node,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleTreeDragLeave(ev) {
|
||||||
|
const { event } = ev;
|
||||||
|
event.stopPropagation();
|
||||||
|
if (!this.tempEntity) return;
|
||||||
|
//避免移动到treeEffectDom上的抖动
|
||||||
|
if (this.treeEffectDom && this.treeEffectDom.parentNode.parentNode === event.currentTarget) return;
|
||||||
|
debug('++++ drag leave tree', ev, this.isDragging);
|
||||||
|
this.clearEffect(true);
|
||||||
|
this.isDragging = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
handleTreeDrop(ev) {
|
||||||
|
const { event } = ev;
|
||||||
|
event.stopPropagation();
|
||||||
|
this.isDragging = false;
|
||||||
|
this.changeCanvas();
|
||||||
|
this.clearEffect(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleResourceDragStart(ev, title, schema) {
|
||||||
|
ev.stopPropagation();
|
||||||
|
ev.dataTransfer.setDragImage(this.getDragTagDom(title), -2, -2);
|
||||||
|
this.sourceEntity = {
|
||||||
|
isAdd: true,
|
||||||
|
schema
|
||||||
|
};
|
||||||
|
this.isDragging = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
618
packages/react-renderer/src/utils/index.js
vendored
Normal file
618
packages/react-renderer/src/utils/index.js
vendored
Normal file
@ -0,0 +1,618 @@
|
|||||||
|
import Debug from 'debug';
|
||||||
|
import _keymaster from 'keymaster';
|
||||||
|
export const keymaster = _keymaster;
|
||||||
|
import { forEach as _forEach, shallowEqual as _shallowEqual } from '@ali/b3-one/lib/obj';
|
||||||
|
import { serialize as serializeParams } from '@ali/b3-one/lib/url';
|
||||||
|
export const forEach = _forEach;
|
||||||
|
export const shallowEqual = _shallowEqual;
|
||||||
|
//moment对象配置
|
||||||
|
import _moment from 'moment';
|
||||||
|
import 'moment/locale/zh-cn';
|
||||||
|
export const moment = _moment;
|
||||||
|
moment.locale('zh-cn');
|
||||||
|
import pkg from '../../package.json';
|
||||||
|
window.sdkVersion = pkg.version;
|
||||||
|
|
||||||
|
import _pick from 'lodash/pick';
|
||||||
|
import _deepEqual from 'lodash/isEqualWith';
|
||||||
|
import _clone from 'lodash/cloneDeep';
|
||||||
|
import _isEmpty from 'lodash/isEmpty';
|
||||||
|
import _throttle from 'lodash/throttle';
|
||||||
|
import _debounce from 'lodash/debounce';
|
||||||
|
|
||||||
|
export const pick = _pick;
|
||||||
|
export const deepEqual = _deepEqual;
|
||||||
|
export const clone = _clone;
|
||||||
|
export const isEmpty = _isEmpty;
|
||||||
|
export const throttle = _throttle;
|
||||||
|
export const debounce = _debounce;
|
||||||
|
|
||||||
|
import _serialize from 'serialize-javascript';
|
||||||
|
export const serialize = _serialize;
|
||||||
|
import * as _jsonuri from 'jsonuri';
|
||||||
|
export const jsonuri = _jsonuri;
|
||||||
|
export { get, post, jsonp, mtop, request } from './request';
|
||||||
|
|
||||||
|
import IntlMessageFormat from 'intl-messageformat';
|
||||||
|
|
||||||
|
const ReactIs = require('react-is');
|
||||||
|
const ReactPropTypesSecret = require('prop-types/lib/ReactPropTypesSecret');
|
||||||
|
const factoryWithTypeCheckers = require('prop-types/factoryWithTypeCheckers');
|
||||||
|
const PropTypes2 = factoryWithTypeCheckers(ReactIs.isElement, true);
|
||||||
|
|
||||||
|
const EXPRESSION_TYPE = {
|
||||||
|
JSEXPRESSION: 'JSExpression',
|
||||||
|
JSFUNCTION: 'JSFunction',
|
||||||
|
JSSLOT: 'JSSlot'
|
||||||
|
};
|
||||||
|
const EXPRESSION_REG = /^\{\{(\{.*\}|.*?)\}\}$/;
|
||||||
|
const hasSymbol = typeof Symbol === 'function' && Symbol['for'];
|
||||||
|
const REACT_FORWARD_REF_TYPE = hasSymbol ? Symbol['for']('react.forward_ref') : 0xead0;
|
||||||
|
const debug = Debug('utils:index');
|
||||||
|
|
||||||
|
const ENV = {
|
||||||
|
TBE: 'TBE',
|
||||||
|
WEBIDE: 'WEB-IDE',
|
||||||
|
VSCODE: 'VSCODE',
|
||||||
|
WEB: 'WEB'
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @name isSchema
|
||||||
|
* @description 判断是否是模型结构
|
||||||
|
*/
|
||||||
|
export function isSchema(schema, ignoreArr) {
|
||||||
|
if (isEmpty(schema)) return false;
|
||||||
|
if (!ignoreArr && Array.isArray(schema)) return schema.every(item => isSchema(item));
|
||||||
|
return !!(schema.componentName && schema.props && (typeof schema.props === 'object' || isJSExpression(schema.props)));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isFileSchema(schema) {
|
||||||
|
if (isEmpty(schema)) return false;
|
||||||
|
return ['Page', 'Block', 'Component', 'Addon', 'Temp'].includes(schema.componentName);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 判断当前页面是否被嵌入到同域的页面中
|
||||||
|
export function inSameDomain() {
|
||||||
|
try {
|
||||||
|
return window.parent !== window && window.parent.location.host === window.location.host;
|
||||||
|
} catch (e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getFileCssName(fileName) {
|
||||||
|
if (!fileName) return;
|
||||||
|
let name = fileName.replace(/([A-Z])/g, '-$1').toLowerCase();
|
||||||
|
return ('luna-' + name)
|
||||||
|
.split('-')
|
||||||
|
.filter(p => !!p)
|
||||||
|
.join('-');
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isJSSlot(obj) {
|
||||||
|
return obj && typeof obj === 'object' && EXPRESSION_TYPE.JSSLOT === obj.type;
|
||||||
|
}
|
||||||
|
export function isJSFunction(obj) {
|
||||||
|
return obj && typeof obj === 'object' && EXPRESSION_TYPE.JSFUNCTION === obj.type;
|
||||||
|
}
|
||||||
|
export function isJSExpression(obj) {
|
||||||
|
//兼容两种写法,有js构造表达式的情况
|
||||||
|
const isJSExpressionObj =
|
||||||
|
obj && typeof obj === 'object' && EXPRESSION_TYPE.JSEXPRESSION === obj.type && typeof obj.value === 'string';
|
||||||
|
const isJSExpressionStr = typeof obj === 'string' && EXPRESSION_REG.test(obj.trim());
|
||||||
|
return isJSExpressionObj || isJSExpressionStr;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @name wait
|
||||||
|
* @description 等待函数
|
||||||
|
*/
|
||||||
|
export function wait(ms) {
|
||||||
|
return new Promise(resolve => setTimeout(() => resolve(true), ms));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function curry(Comp, hocs = []) {
|
||||||
|
return hocs.reverse().reduce((pre, cur) => {
|
||||||
|
return cur(pre);
|
||||||
|
}, Comp);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getValue(obj, path, defaultValue) {
|
||||||
|
if (isEmpty(obj) || typeof obj !== 'object') return defaultValue;
|
||||||
|
const res = path.split('.').reduce((pre, cur) => {
|
||||||
|
return pre && pre[cur];
|
||||||
|
}, obj);
|
||||||
|
if (res === undefined) return defaultValue;
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function parseObj(schemaStr) {
|
||||||
|
if (typeof schemaStr !== 'string') return schemaStr;
|
||||||
|
//默认调用顶层窗口的parseObj,保障new Function的window对象是顶层的window对象
|
||||||
|
try {
|
||||||
|
if (inSameDomain() && window.parent.__newFunc) {
|
||||||
|
return window.parent.__newFunc(`"use strict"; return ${schemaStr}`)();
|
||||||
|
}
|
||||||
|
return new Function(`"use strict"; return ${schemaStr}`)();
|
||||||
|
} catch (err) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function fastClone(obj) {
|
||||||
|
return parseObj(serialize(obj, { unsafe: true }));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新obj的内容但不改变obj的指针
|
||||||
|
export function fillObj(receiver = {}, ...suppliers) {
|
||||||
|
Object.keys(receiver).forEach(item => {
|
||||||
|
delete receiver[item];
|
||||||
|
});
|
||||||
|
Object.assign(receiver, ...suppliers);
|
||||||
|
return receiver;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 中划线转驼峰
|
||||||
|
export function toHump(name) {
|
||||||
|
return name.replace(/\-(\w)/g, function(all, letter) {
|
||||||
|
return letter.toUpperCase();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// 驼峰转中划线
|
||||||
|
export function toLine(name) {
|
||||||
|
return name.replace(/([A-Z])/g, '-$1').toLowerCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取当前环境
|
||||||
|
export function getEnv() {
|
||||||
|
const userAgent = navigator.userAgent;
|
||||||
|
const isVscode = /Electron\//.test(userAgent);
|
||||||
|
if (isVscode) return ENV.VSCODE;
|
||||||
|
const isTheia = window.is_theia === true;
|
||||||
|
if (isTheia) return ENV.WEBIDE;
|
||||||
|
return ENV.WEB;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用于构造国际化字符串处理函数
|
||||||
|
* @param {*} locale 国际化标识,例如 zh-CN、en-US
|
||||||
|
* @param {*} messages 国际化语言包
|
||||||
|
*/
|
||||||
|
export function generateI18n(locale = 'zh-CN', messages = {}) {
|
||||||
|
return (key, values = {}) => {
|
||||||
|
if (!messages || !messages[key]) return '';
|
||||||
|
const formater = new IntlMessageFormat(messages[key], locale);
|
||||||
|
return formater.format(values);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断当前组件是否能够设置ref
|
||||||
|
* @param {*} Comp 需要判断的组件
|
||||||
|
*/
|
||||||
|
export function acceptsRef(Comp) {
|
||||||
|
return (
|
||||||
|
(Comp.$$typeof && Comp.$$typeof === REACT_FORWARD_REF_TYPE) || (Comp.prototype && Comp.prototype.isReactComponent)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 黄金令箭埋点
|
||||||
|
* @param {String} gmKey 为黄金令箭业务类型
|
||||||
|
* @param {Object} params 参数
|
||||||
|
* @param {String} logKey 属性串
|
||||||
|
*/
|
||||||
|
export function goldlog(gmKey, params = {}, logKey = 'other') {
|
||||||
|
// vscode 黄金令箭API
|
||||||
|
const sendIDEMessage = window.sendIDEMessage || (inSameDomain() && window.parent.sendIDEMessage);
|
||||||
|
const goKey = serializeParams({
|
||||||
|
sdkVersion: pkg.version,
|
||||||
|
env: getEnv(),
|
||||||
|
...params
|
||||||
|
});
|
||||||
|
if (sendIDEMessage) {
|
||||||
|
sendIDEMessage({
|
||||||
|
action: 'goldlog',
|
||||||
|
data: {
|
||||||
|
logKey: `/iceluna.core.${logKey}`,
|
||||||
|
gmKey,
|
||||||
|
goKey
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
window.goldlog && window.goldlog.record(`/iceluna.core.${logKey}`, gmKey, goKey, 'POST');
|
||||||
|
}
|
||||||
|
|
||||||
|
// utils为编辑器打包生成的utils文件内容,utilsConfig为数据库存放的utils配置
|
||||||
|
export function generateUtils(utils, utilsConfig) {
|
||||||
|
if (!Array.isArray(utilsConfig)) return { ...utils };
|
||||||
|
const res = {};
|
||||||
|
utilsConfig.forEach(item => {
|
||||||
|
if (!item.name || !item.type || !item.content) return;
|
||||||
|
if (item.type === 'function' && typeof item.content === 'function') {
|
||||||
|
res[item.name] = item.content;
|
||||||
|
} else if (item.type === 'npm' && utils[item.name]) {
|
||||||
|
res[item.name] = utils[item.name];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
// 复制到粘贴板
|
||||||
|
export function setClipboardData(str) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
if (typeof str !== 'string') reject('不支持拷贝');
|
||||||
|
if (navigator.clipboard) {
|
||||||
|
navigator.clipboard
|
||||||
|
.writeText(str)
|
||||||
|
.then(() => {
|
||||||
|
resolve();
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
reject('复制失败,请重试!', err);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
const textArea = document.createElement('textarea');
|
||||||
|
textArea.value = str;
|
||||||
|
document.body.appendChild(textArea);
|
||||||
|
textArea.focus();
|
||||||
|
textArea.select();
|
||||||
|
try {
|
||||||
|
let successful = document.execCommand('copy');
|
||||||
|
if (successful) {
|
||||||
|
document.body.removeChild(textArea);
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
document.body.removeChild(textArea);
|
||||||
|
reject('复制失败,请重试!', err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// 获取粘贴板数据
|
||||||
|
export function getClipboardData() {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
if (window.clipboardData) {
|
||||||
|
resolve(window.clipboardData.getData('text'));
|
||||||
|
} else if (navigator.clipboard) {
|
||||||
|
return navigator.clipboard
|
||||||
|
.readText()
|
||||||
|
.then(res => {
|
||||||
|
resolve(res);
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
reject('粘贴板获取失败', err);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
reject('粘贴板获取失败');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// 将函数返回结果转成promise形式,如果函数有返回值则根据返回值的bool类型判断是reject还是resolve,若函数无返回值默认执行resolve
|
||||||
|
export function transformToPromise(input) {
|
||||||
|
if (input instanceof Promise) return input;
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
if (input || input === undefined) {
|
||||||
|
resolve();
|
||||||
|
} else {
|
||||||
|
reject();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function moveArrayItem(arr, sourceIdx, distIdx, direction) {
|
||||||
|
if (
|
||||||
|
!Array.isArray(arr) ||
|
||||||
|
sourceIdx === distIdx ||
|
||||||
|
sourceIdx < 0 ||
|
||||||
|
sourceIdx >= arr.length ||
|
||||||
|
distIdx < 0 ||
|
||||||
|
distIdx >= arr.length
|
||||||
|
)
|
||||||
|
return arr;
|
||||||
|
const item = arr[sourceIdx];
|
||||||
|
if (direction === 'after') {
|
||||||
|
arr.splice(distIdx + 1, 0, item);
|
||||||
|
} else {
|
||||||
|
arr.splice(distIdx, 0, item);
|
||||||
|
}
|
||||||
|
if (sourceIdx < distIdx) {
|
||||||
|
arr.splice(sourceIdx, 1);
|
||||||
|
} else {
|
||||||
|
arr.splice(sourceIdx + 1, 1);
|
||||||
|
}
|
||||||
|
return arr;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function transformArrayToMap(arr, key, overwrite = true) {
|
||||||
|
if (isEmpty(arr) || !Array.isArray(arr)) return {};
|
||||||
|
const res = {};
|
||||||
|
arr.forEach(item => {
|
||||||
|
const curKey = item[key];
|
||||||
|
if (item[key] === undefined) return;
|
||||||
|
if (res[curKey] && !overwrite) return;
|
||||||
|
res[curKey] = item;
|
||||||
|
});
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function checkPropTypes(value, name, rule, componentName) {
|
||||||
|
if (typeof rule === 'string') {
|
||||||
|
rule = new Function(`"use strict"; const PropTypes = arguments[0]; return ${rule}`)(PropTypes2);
|
||||||
|
}
|
||||||
|
if (!rule || typeof rule !== 'function') {
|
||||||
|
console.warn('checkPropTypes should have a function type rule argument');
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
const err = rule(
|
||||||
|
{
|
||||||
|
[name]: value
|
||||||
|
},
|
||||||
|
name,
|
||||||
|
componentName,
|
||||||
|
'prop',
|
||||||
|
null,
|
||||||
|
ReactPropTypesSecret
|
||||||
|
);
|
||||||
|
if (err) {
|
||||||
|
console.warn(err);
|
||||||
|
}
|
||||||
|
return !err;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function transformSchemaToPure(obj) {
|
||||||
|
const pureObj = obj => {
|
||||||
|
if (Array.isArray(obj)) {
|
||||||
|
return obj.map(item => pureObj(item));
|
||||||
|
} else if (typeof obj === 'object') {
|
||||||
|
// 对于undefined及null直接返回
|
||||||
|
if (!obj) return obj;
|
||||||
|
const res = {};
|
||||||
|
forEach(obj, (val, key) => {
|
||||||
|
if (key.startsWith('__') && key !== '__ignoreParse') return;
|
||||||
|
res[key] = pureObj(val);
|
||||||
|
});
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
return obj;
|
||||||
|
};
|
||||||
|
return pureObj(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function transformSchemaToStandard(obj) {
|
||||||
|
const standardObj = obj => {
|
||||||
|
if (Array.isArray(obj)) {
|
||||||
|
return obj.map(item => standardObj(item));
|
||||||
|
} else if (typeof obj === 'object') {
|
||||||
|
// 对于undefined及null直接返回
|
||||||
|
if (!obj) return obj;
|
||||||
|
const res = {};
|
||||||
|
forEach(obj, (val, key) => {
|
||||||
|
if (key.startsWith('__') && key !== '__ignoreParse') return;
|
||||||
|
if (isSchema(val) && key !== 'children' && obj.type !== 'JSSlot') {
|
||||||
|
res[key] = {
|
||||||
|
type: 'JSSlot',
|
||||||
|
value: standardObj(val)
|
||||||
|
};
|
||||||
|
// table特殊处理
|
||||||
|
if (key === 'cell') {
|
||||||
|
res[key].params = ['value', 'index', 'record'];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
res[key] = standardObj(val);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return res;
|
||||||
|
} else if (typeof obj === 'function') {
|
||||||
|
return {
|
||||||
|
type: 'JSFunction',
|
||||||
|
value: obj.toString()
|
||||||
|
};
|
||||||
|
} else if (typeof obj === 'string' && EXPRESSION_REG.test(obj.trim())) {
|
||||||
|
const regRes = obj.trim().match(EXPRESSION_REG);
|
||||||
|
return {
|
||||||
|
type: 'JSExpression',
|
||||||
|
value: (regRes && regRes[1]) || ''
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return obj;
|
||||||
|
};
|
||||||
|
return standardObj(obj, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function transformStringToFunction(str) {
|
||||||
|
if (typeof str !== 'string') return str;
|
||||||
|
if (inSameDomain() && window.parent.__newFunc) {
|
||||||
|
return window.parent.__newFunc(`"use strict"; return ${str}`)();
|
||||||
|
} else {
|
||||||
|
return new Function(`"use strict"; return ${str}`)();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function addCssTag(id, content) {
|
||||||
|
let styleTag = document.getElementById(id);
|
||||||
|
if (styleTag) {
|
||||||
|
styleTag.innerHTML = content;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
styleTag = document.createElement('style');
|
||||||
|
styleTag.id = id;
|
||||||
|
styleTag.class = 'luna-style';
|
||||||
|
styleTag.innerHTML = content;
|
||||||
|
document.head.appendChild(styleTag);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 注册快捷
|
||||||
|
export function registShortCuts(config, appHelper) {
|
||||||
|
const keyboardFilter = (keymaster.filter = event => {
|
||||||
|
let eTarget = event.target || event.srcElement;
|
||||||
|
let tagName = eTarget.tagName;
|
||||||
|
let isInput = !!(tagName == 'INPUT' || tagName == 'SELECT' || tagName == 'TEXTAREA');
|
||||||
|
let isContenteditable = !!eTarget.getAttribute('contenteditable');
|
||||||
|
if (isInput || isContenteditable) {
|
||||||
|
if (event.metaKey === true && [70, 83].includes(event.keyCode)) event.preventDefault(); //禁止触发chrome原生的页面保存或查找
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const ideMessage = appHelper.utils && appHelper.utils.ideMessage;
|
||||||
|
|
||||||
|
//复制
|
||||||
|
if (!document.copyListener) {
|
||||||
|
document.copyListener = e => {
|
||||||
|
if (!keyboardFilter(e) || appHelper.isCopying) return;
|
||||||
|
const schema = appHelper.schemaHelper && appHelper.schemaHelper.schemaMap[appHelper.activeKey];
|
||||||
|
if (!schema || !isSchema(schema)) return;
|
||||||
|
appHelper.isCopying = true;
|
||||||
|
const schemaStr = serialize(transformSchemaToPure(schema), {
|
||||||
|
unsafe: true
|
||||||
|
});
|
||||||
|
setClipboardData(schemaStr)
|
||||||
|
.then(() => {
|
||||||
|
ideMessage && ideMessage('success', '当前内容已复制到剪贴板,请使用快捷键Command+v进行粘贴');
|
||||||
|
appHelper.emit('schema.copy', schemaStr, schema);
|
||||||
|
appHelper.isCopying = false;
|
||||||
|
})
|
||||||
|
.catch(errMsg => {
|
||||||
|
ideMessage && ideMessage('error', errMsg);
|
||||||
|
appHelper.isCopying = false;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
document.addEventListener('copy', document.copyListener);
|
||||||
|
if (window.parent.vscode) {
|
||||||
|
keymaster('command+c', document.copyListener);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//粘贴
|
||||||
|
if (!document.pasteListener) {
|
||||||
|
const doPaste = (e, text) => {
|
||||||
|
if (!keyboardFilter(e) || appHelper.isPasting) return;
|
||||||
|
const schemaHelper = appHelper.schemaHelper;
|
||||||
|
let targetKey = appHelper.activeKey;
|
||||||
|
let direction = 'after';
|
||||||
|
const topKey = schemaHelper.schema && schemaHelper.schema.__ctx && schemaHelper.schema.__ctx.lunaKey;
|
||||||
|
if (!targetKey || topKey === targetKey) {
|
||||||
|
const schemaHelper = appHelper.schemaHelper;
|
||||||
|
const topKey = schemaHelper.schema && schemaHelper.schema.__ctx && schemaHelper.schema.__ctx.lunaKey;
|
||||||
|
if (!topKey) return;
|
||||||
|
targetKey = topKey;
|
||||||
|
direction = 'in';
|
||||||
|
}
|
||||||
|
appHelper.isPasting = true;
|
||||||
|
const schema = parseObj(text);
|
||||||
|
if (!isSchema(schema)) {
|
||||||
|
appHelper.emit('illegalSchema.paste', text);
|
||||||
|
// ideMessage && ideMessage('error', '当前内容不是模型结构,不能粘贴进来!');
|
||||||
|
console.warn('paste schema illegal');
|
||||||
|
appHelper.isPasting = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
appHelper.emit('material.add', {
|
||||||
|
schema,
|
||||||
|
targetKey,
|
||||||
|
direction
|
||||||
|
});
|
||||||
|
appHelper.isPasting = false;
|
||||||
|
appHelper.emit('schema.paste', schema);
|
||||||
|
};
|
||||||
|
document.pasteListener = e => {
|
||||||
|
const clipboardData = e.clipboardData || window.clipboardData;
|
||||||
|
const text = clipboardData && clipboardData.getData('text');
|
||||||
|
doPaste(e, text);
|
||||||
|
};
|
||||||
|
document.addEventListener('paste', document.pasteListener);
|
||||||
|
if (window.parent.vscode) {
|
||||||
|
keymaster('command+v', e => {
|
||||||
|
const sendIDEMessage = window.parent.sendIDEMessage;
|
||||||
|
sendIDEMessage &&
|
||||||
|
sendIDEMessage({
|
||||||
|
action: 'readClipboard'
|
||||||
|
})
|
||||||
|
.then(text => {
|
||||||
|
doPaste(e, text);
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
console.warn(err);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
(config || []).forEach(item => {
|
||||||
|
keymaster(item.keyboard, ev => {
|
||||||
|
ev.preventDefault();
|
||||||
|
item.handler(ev, appHelper, keymaster);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 取消注册快捷
|
||||||
|
export function unRegistShortCuts(config) {
|
||||||
|
(config || []).forEach(item => {
|
||||||
|
keymaster.unbind(item.keyboard);
|
||||||
|
});
|
||||||
|
if (window.parent.vscode) {
|
||||||
|
keymaster.unbind('command+c');
|
||||||
|
keymaster.unbind('command+v');
|
||||||
|
}
|
||||||
|
if (document.copyListener) {
|
||||||
|
document.removeEventListener('copy', document.copyListener);
|
||||||
|
delete document.copyListener;
|
||||||
|
}
|
||||||
|
if (document.pasteListener) {
|
||||||
|
document.removeEventListener('paste', document.pasteListener);
|
||||||
|
delete document.pasteListener;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function parseData(schema, self) {
|
||||||
|
if (isJSExpression(schema)) {
|
||||||
|
return parseExpression(schema, self);
|
||||||
|
} else if (typeof schema === 'string') {
|
||||||
|
return schema.trim();
|
||||||
|
} else if (Array.isArray(schema)) {
|
||||||
|
return schema.map(item => parseData(item, self));
|
||||||
|
} else if (typeof schema === 'function') {
|
||||||
|
return schema.bind(self);
|
||||||
|
} else if (typeof schema === 'object') {
|
||||||
|
// 对于undefined及null直接返回
|
||||||
|
if (!schema) return schema;
|
||||||
|
const res = {};
|
||||||
|
forEach(schema, (val, key) => {
|
||||||
|
if (key.startsWith('__')) return;
|
||||||
|
res[key] = parseData(val, self);
|
||||||
|
});
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
return schema;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*全匹配{{开头,}}结尾的变量表达式,或者对象类型JSExpression,且均不支持省略this */
|
||||||
|
export function parseExpression(str, self) {
|
||||||
|
try {
|
||||||
|
const contextArr = ['"use strict";', 'var __self = arguments[0];'];
|
||||||
|
contextArr.push('return ');
|
||||||
|
let tarStr;
|
||||||
|
//向前兼容,支持标准协议新格式
|
||||||
|
if (typeof str === 'string') {
|
||||||
|
const regRes = str.trim().match(EXPRESSION_REG);
|
||||||
|
tarStr = regRes[1];
|
||||||
|
} else {
|
||||||
|
tarStr = (str.value || '').trim();
|
||||||
|
}
|
||||||
|
tarStr = tarStr.replace(/this(\W|$)/g, (a, b) => `__self${b}`);
|
||||||
|
tarStr = contextArr.join('\n') + tarStr;
|
||||||
|
//默认调用顶层窗口的parseObj,保障new Function的window对象是顶层的window对象
|
||||||
|
if (inSameDomain() && window.parent.__newFunc) {
|
||||||
|
return window.parent.__newFunc(tarStr)(self);
|
||||||
|
}
|
||||||
|
return new Function(tarStr)(self);
|
||||||
|
} catch (err) {
|
||||||
|
debug('parseExpression.error', err, str, self);
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
59
packages/react-renderer/src/utils/postMessager.js
vendored
Normal file
59
packages/react-renderer/src/utils/postMessager.js
vendored
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
import EventEmitter from 'events';
|
||||||
|
import Debug from 'debug';
|
||||||
|
const debug = Debug('utils:postMessager');
|
||||||
|
EventEmitter.defaultMaxListeners = 100;
|
||||||
|
|
||||||
|
export class InnerMessager extends EventEmitter {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this.handleReceive = this.handleReceive.bind(this);
|
||||||
|
window.addEventListener('message', this.handleReceive, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
sendMsg(type, data, targetOrigin = '*') {
|
||||||
|
window.parent &&
|
||||||
|
window.parent.postMessage(
|
||||||
|
{
|
||||||
|
type,
|
||||||
|
data
|
||||||
|
},
|
||||||
|
targetOrigin
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleReceive(e) {
|
||||||
|
if (!e.data || !e.data.type) return;
|
||||||
|
this.emit(e.data.type, e.data.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
destroy() {
|
||||||
|
window.removeEventListener('message', this.handleReceive);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class OuterMessager extends EventEmitter {
|
||||||
|
constructor(innerWindow) {
|
||||||
|
super();
|
||||||
|
this.innerWindow = innerWindow;
|
||||||
|
this.handleReceive = this.handleReceive.bind(this);
|
||||||
|
window.addEventListener('message', this.handleReceive, false);
|
||||||
|
}
|
||||||
|
sendMsg(type, data, targetOrigin = '*') {
|
||||||
|
this.innerWindow &&
|
||||||
|
this.innerWindow.postMessage(
|
||||||
|
{
|
||||||
|
type,
|
||||||
|
data
|
||||||
|
},
|
||||||
|
targetOrigin
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleReceive(e) {
|
||||||
|
if (!e.data || !e.data.type) return;
|
||||||
|
this.emit(e.data.type, e.data.data);
|
||||||
|
}
|
||||||
|
destroy() {
|
||||||
|
window.removeEventListener('message', this.handleReceive);
|
||||||
|
}
|
||||||
|
}
|
||||||
172
packages/react-renderer/src/utils/request.js
vendored
Normal file
172
packages/react-renderer/src/utils/request.js
vendored
Normal file
@ -0,0 +1,172 @@
|
|||||||
|
import 'whatwg-fetch';
|
||||||
|
import fetchMtop from '@ali/lib-mtop';
|
||||||
|
import fetchJsonp from 'fetch-jsonp';
|
||||||
|
import bzbRequest from '@ali/bzb-request';
|
||||||
|
import Debug from 'debug';
|
||||||
|
import { serialize, buildUrl, parseUrl } from '@ali/b3-one/lib/url';
|
||||||
|
const debug = Debug('utils:request');
|
||||||
|
export function get(dataAPI, params = {}, headers = {}, otherProps = {}) {
|
||||||
|
headers = {
|
||||||
|
Accept: 'application/json',
|
||||||
|
...headers
|
||||||
|
};
|
||||||
|
dataAPI = buildUrl(dataAPI, params);
|
||||||
|
return request(dataAPI, 'GET', null, headers, otherProps);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function post(dataAPI, params = {}, headers = {}, otherProps = {}) {
|
||||||
|
headers = {
|
||||||
|
Accept: 'application/json',
|
||||||
|
'Content-Type': 'application/x-www-form-urlencoded',
|
||||||
|
...headers
|
||||||
|
};
|
||||||
|
return request(
|
||||||
|
dataAPI,
|
||||||
|
'POST',
|
||||||
|
headers['Content-Type'].indexOf('application/json') > -1 || Array.isArray(params)
|
||||||
|
? JSON.stringify(params)
|
||||||
|
: serialize(params),
|
||||||
|
headers,
|
||||||
|
otherProps
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function request(dataAPI, method = 'GET', data, headers = {}, otherProps = {}) {
|
||||||
|
switch (method) {
|
||||||
|
case 'PUT':
|
||||||
|
case 'DELETE':
|
||||||
|
headers = {
|
||||||
|
Accept: 'application/json',
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
...headers
|
||||||
|
};
|
||||||
|
data = JSON.stringify(data || {});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
if (otherProps.timeout) {
|
||||||
|
setTimeout(() => {
|
||||||
|
reject(new Error('timeout'));
|
||||||
|
}, otherProps.timeout);
|
||||||
|
}
|
||||||
|
fetch(dataAPI, {
|
||||||
|
method,
|
||||||
|
credentials: 'include',
|
||||||
|
headers,
|
||||||
|
body: data,
|
||||||
|
...otherProps
|
||||||
|
})
|
||||||
|
.then(response => {
|
||||||
|
switch (response.status) {
|
||||||
|
case 200:
|
||||||
|
case 201:
|
||||||
|
case 202:
|
||||||
|
return response.json();
|
||||||
|
case 204:
|
||||||
|
if (method === 'DELETE') {
|
||||||
|
return {
|
||||||
|
success: true
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
__success: false,
|
||||||
|
code: response.status
|
||||||
|
};
|
||||||
|
}
|
||||||
|
case 400:
|
||||||
|
case 401:
|
||||||
|
case 403:
|
||||||
|
case 404:
|
||||||
|
case 406:
|
||||||
|
case 410:
|
||||||
|
case 422:
|
||||||
|
case 500:
|
||||||
|
return response
|
||||||
|
.json()
|
||||||
|
.then(res => {
|
||||||
|
return {
|
||||||
|
__success: false,
|
||||||
|
code: response.status,
|
||||||
|
data: res
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
return {
|
||||||
|
__success: false,
|
||||||
|
code: response.status
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
})
|
||||||
|
.then(json => {
|
||||||
|
if (json && json.__success !== false) {
|
||||||
|
resolve(json);
|
||||||
|
} else {
|
||||||
|
delete json.__success;
|
||||||
|
reject(json);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
reject(err);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function jsonp(dataAPI, params = {}, otherProps = {}) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
otherProps = {
|
||||||
|
timeout: 5000,
|
||||||
|
...otherProps
|
||||||
|
};
|
||||||
|
fetchJsonp(buildUrl(dataAPI, params), otherProps)
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(json => {
|
||||||
|
if (json) {
|
||||||
|
resolve(json);
|
||||||
|
} else {
|
||||||
|
reject();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
reject(err);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function mtop(dataAPI, params, otherProps = {}) {
|
||||||
|
fetchMtop.config.subDomain = otherProps.subDomain || 'm';
|
||||||
|
return fetchMtop.request({
|
||||||
|
api: dataAPI,
|
||||||
|
v: '1.0',
|
||||||
|
data: params,
|
||||||
|
ecode: otherProps.ecode || 0,
|
||||||
|
type: otherProps.method || 'GET',
|
||||||
|
dataType: otherProps.dataType || 'jsonp',
|
||||||
|
AntiFlood: true, // 防刷
|
||||||
|
timeout: otherProps.timeout || 20000
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function bzb(apiCode, params, otherProps = {}) {
|
||||||
|
// 通过url参数设置小二工作台请求环境
|
||||||
|
const getUrlEnv = () => {
|
||||||
|
try {
|
||||||
|
if (window.parent && window.parent.location.host === window.location.host) {
|
||||||
|
const urlInfo = parseUrl(window.parent && window.parent.location.href);
|
||||||
|
return urlInfo && urlInfo.params && urlInfo.params._env;
|
||||||
|
}
|
||||||
|
const urlInfo = parseUrl(window.location.href);
|
||||||
|
return urlInfo && urlInfo.params && urlInfo.params._env;
|
||||||
|
} catch (e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
otherProps.method = otherProps.method || 'GET';
|
||||||
|
otherProps.env = getUrlEnv() || otherProps.env || 'prod';
|
||||||
|
return bzbRequest(apiCode, {
|
||||||
|
data: params,
|
||||||
|
...otherProps
|
||||||
|
});
|
||||||
|
}
|
||||||
482
packages/react-renderer/src/utils/schemaHelper.js
vendored
Normal file
482
packages/react-renderer/src/utils/schemaHelper.js
vendored
Normal file
@ -0,0 +1,482 @@
|
|||||||
|
import { forEach } from '@ali/b3-one/lib/obj';
|
||||||
|
import {
|
||||||
|
clone,
|
||||||
|
fastClone,
|
||||||
|
jsonuri,
|
||||||
|
isSchema,
|
||||||
|
isFileSchema,
|
||||||
|
isJSFunction,
|
||||||
|
isJSExpression,
|
||||||
|
parseObj,
|
||||||
|
transformSchemaToPure,
|
||||||
|
transformSchemaToStandard,
|
||||||
|
isEmpty,
|
||||||
|
moveArrayItem,
|
||||||
|
serialize,
|
||||||
|
deepEqual
|
||||||
|
} from './index';
|
||||||
|
import Debug from 'debug';
|
||||||
|
import compFactory from '../hoc/compFactory';
|
||||||
|
const debug = Debug('utils:schemaHelper');
|
||||||
|
let keyIndex = 0;
|
||||||
|
export default class SchemaHelper {
|
||||||
|
constructor(schema, appHelper) {
|
||||||
|
this.appHelper = appHelper;
|
||||||
|
this.reset(schema, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
reset(schema, isInit) {
|
||||||
|
debug('start reset');
|
||||||
|
this.emit('schemaHelper.schema.beforeReset');
|
||||||
|
this.schemaMap = {};
|
||||||
|
this.blockSchemaMap = {};
|
||||||
|
this.compThisMap = {};
|
||||||
|
this.blockTree = {};
|
||||||
|
this.compTreeMap = {};
|
||||||
|
this.compCtxMap = {};
|
||||||
|
this.rebuild(schema, isInit);
|
||||||
|
this.emit('schemaHelper.schema.afterReset');
|
||||||
|
}
|
||||||
|
|
||||||
|
add(schema, targetKey, direction) {
|
||||||
|
this.emit('schemaHelper.material.beforeAdd');
|
||||||
|
const targetSchema = this.schemaMap[targetKey];
|
||||||
|
if (isEmpty(schema) || !targetSchema) return;
|
||||||
|
let targetPath = targetSchema.__ctx.lunaPath;
|
||||||
|
if (targetPath === '' && direction !== 'in') {
|
||||||
|
console.warn('add error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let newSchema = [];
|
||||||
|
if (Array.isArray(schema)) {
|
||||||
|
newSchema = schema.filter(item => isSchema(item, true));
|
||||||
|
} else if (isSchema(schema)) {
|
||||||
|
newSchema = [schema];
|
||||||
|
} else {
|
||||||
|
console.error('模型结构异常');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (direction === 'in') {
|
||||||
|
const targetNode = jsonuri.get(this.schema, targetPath);
|
||||||
|
targetNode.children = (targetNode.children || []).concat(newSchema);
|
||||||
|
//jsonuri.set(this.schema, targetPath, targetNode);
|
||||||
|
} else {
|
||||||
|
direction = ['left', 'top'].includes(direction) ? 'before' : 'after';
|
||||||
|
newSchema.reverse().forEach(item => {
|
||||||
|
jsonuri.insert(this.schema, targetPath, item, direction);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const addKey = `luna_${keyIndex + 1}`;
|
||||||
|
this.rebuild(this.schema);
|
||||||
|
this.emit('schemaHelper.material.afterAdd', addKey);
|
||||||
|
return addKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
remove(lunaKey) {
|
||||||
|
this.emit('schemaHelper.material.beforeRemove');
|
||||||
|
const schema = this.schemaMap[lunaKey];
|
||||||
|
if (!schema) return;
|
||||||
|
const lunaPath = schema.__ctx.lunaPath;
|
||||||
|
if (lunaPath === '') {
|
||||||
|
console.warn('root node can not be removed');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
jsonuri.rm(this.schema, lunaPath);
|
||||||
|
delete this.schemaMap[lunaKey];
|
||||||
|
delete this.blockSchemaMap[lunaKey];
|
||||||
|
this.rebuild(this.schema);
|
||||||
|
this.emit('schemaHelper.material.afterRemove');
|
||||||
|
}
|
||||||
|
|
||||||
|
move(lunaKey, targetKey, direction) {
|
||||||
|
this.emit('schemaHelper.material.beforeMove');
|
||||||
|
debug('start move');
|
||||||
|
const schema = this.schemaMap[lunaKey];
|
||||||
|
const targetSchema = this.schemaMap[targetKey];
|
||||||
|
if (!schema || !targetSchema) return;
|
||||||
|
let lunaPath = schema.__ctx.lunaPath;
|
||||||
|
let targetPath = targetSchema.__ctx.lunaPath;
|
||||||
|
if (lunaPath === '' || (targetPath === '' && direction !== 'in')) {
|
||||||
|
console.warn('move error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const res = /(.*)\/(\d+)$/.exec(lunaPath);
|
||||||
|
const prefix = res && res[1];
|
||||||
|
const attr = res && res[2];
|
||||||
|
if (!prefix || !attr) {
|
||||||
|
console.warn('异常结构');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const sourceIdx = parseInt(attr);
|
||||||
|
const reg = new RegExp(`^${prefix}/(\\d+)$`);
|
||||||
|
const regRes = reg.exec(targetPath);
|
||||||
|
const sourceParent = jsonuri.get(this.schema, prefix);
|
||||||
|
direction = direction === 'in' ? 'in' : ['left', 'top'].includes(direction) ? 'before' : 'after';
|
||||||
|
if (regRes && regRes[1] && direction !== 'in') {
|
||||||
|
const distIdx = parseInt(regRes[1]);
|
||||||
|
moveArrayItem(sourceParent, sourceIdx, distIdx, direction);
|
||||||
|
} else {
|
||||||
|
if (direction === 'in') {
|
||||||
|
const targetNode = jsonuri.get(this.schema, targetPath);
|
||||||
|
targetNode.children = targetNode.children || [];
|
||||||
|
if (Array.isArray(targetNode.children)) {
|
||||||
|
targetNode.children.push(schema);
|
||||||
|
}
|
||||||
|
jsonuri.set(this.schema, targetPath, targetNode);
|
||||||
|
} else {
|
||||||
|
jsonuri.insert(this.schema, targetPath, schema, direction);
|
||||||
|
}
|
||||||
|
sourceParent.splice(sourceIdx, 1);
|
||||||
|
}
|
||||||
|
this.rebuild(this.schema);
|
||||||
|
this.emit('schemaHelper.material.afterMove');
|
||||||
|
}
|
||||||
|
|
||||||
|
//组件上移 下移
|
||||||
|
// direction 取值 down/up
|
||||||
|
slide(lunaKey, direction) {
|
||||||
|
const schema = this.schemaMap[lunaKey];
|
||||||
|
if (!schema || !direction) return;
|
||||||
|
const lunaPath = schema.__ctx && schema.__ctx.lunaPath;
|
||||||
|
if (!lunaPath) return;
|
||||||
|
if (direction === 'up' && lunaPath.endsWith('/0')) return;
|
||||||
|
const targetPath = lunaPath.replace(/\/(\d+)$/, (res, idx) => {
|
||||||
|
return `/${direction === 'down' ? parseInt(idx) + 1 : parseInt(idx) - 1}`;
|
||||||
|
});
|
||||||
|
const targetSchema = this.getSchemaByPath(targetPath);
|
||||||
|
const targetKey = targetSchema && targetSchema.__ctx && targetSchema.__ctx.lunaKey;
|
||||||
|
if (!targetKey) return;
|
||||||
|
this.move(lunaKey, targetKey, direction === 'down' ? 'bottom' : 'top');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 快速复制
|
||||||
|
copy(lunaKey) {
|
||||||
|
this.emit('schemaHelper.material.beforeCopy');
|
||||||
|
const schema = this.schemaMap[lunaKey];
|
||||||
|
if (!schema) return;
|
||||||
|
const newSchema = transformSchemaToPure(fastClone(schema));
|
||||||
|
delete newSchema.__ctx;
|
||||||
|
const addKey = this.add(newSchema, schema.__ctx.lunaKey, 'bottom');
|
||||||
|
this.emit('schemaHelper.material.afterCopy', addKey);
|
||||||
|
return addKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
update(lunaKey, props) {
|
||||||
|
this.emit('schemaHelper.material.beforeUpdate');
|
||||||
|
const schema = this.schemaMap[lunaKey];
|
||||||
|
if (!schema) return;
|
||||||
|
const {
|
||||||
|
__state,
|
||||||
|
__defaultProps,
|
||||||
|
__fileName,
|
||||||
|
__scss,
|
||||||
|
__loop,
|
||||||
|
__loopArgs,
|
||||||
|
__condition,
|
||||||
|
__lifeCycles,
|
||||||
|
__methods,
|
||||||
|
__dataSource,
|
||||||
|
children,
|
||||||
|
...otherProps
|
||||||
|
} = props;
|
||||||
|
debug('update props', props);
|
||||||
|
|
||||||
|
//自定义组件才处理defaultProps
|
||||||
|
if (schema.componentName === 'Component' && '__defaultProps' in props) {
|
||||||
|
if (!__defaultProps || typeof __defaultProps !== 'object' || isEmpty(__defaultProps)) {
|
||||||
|
delete schema.defaultProps;
|
||||||
|
} else {
|
||||||
|
schema.defaultProps = __defaultProps;
|
||||||
|
}
|
||||||
|
this.appHelper.components[schema.fileName.replace(/^\w/, a => a.toUpperCase())] = compFactory(schema);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果loop值没有设置有效值,则删除schema中这个的字段
|
||||||
|
if ('__loop' in props) {
|
||||||
|
if (!__loop || isEmpty(__loop)) {
|
||||||
|
delete schema.loop;
|
||||||
|
} else {
|
||||||
|
schema.loop = __loop;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 指定循环上下文变量名
|
||||||
|
if ('__loopArgs' in props) {
|
||||||
|
if (
|
||||||
|
__loopArgs === undefined ||
|
||||||
|
(typeof __loopArgs === 'object' && isEmpty(__loopArgs)) ||
|
||||||
|
!Array.isArray(__loopArgs) ||
|
||||||
|
__loopArgs.every(item => !item)
|
||||||
|
) {
|
||||||
|
delete schema.loopArgs;
|
||||||
|
} else {
|
||||||
|
schema.loopArgs = __loopArgs;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 判断条件
|
||||||
|
if ('__condition' in props) {
|
||||||
|
if (__condition === undefined) {
|
||||||
|
delete schema.condition;
|
||||||
|
} else {
|
||||||
|
schema.condition = __condition;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理容器类组件需要考虑的字段
|
||||||
|
if (isFileSchema(schema)) {
|
||||||
|
// filename
|
||||||
|
if ('__fileName' in props) {
|
||||||
|
schema.fileName = __fileName;
|
||||||
|
}
|
||||||
|
// state
|
||||||
|
if ('__state' in props) {
|
||||||
|
// 重走生命周期
|
||||||
|
schema.__ctx && ++schema.__ctx.idx;
|
||||||
|
if (!__state || typeof __state !== 'object' || isEmpty(__state)) {
|
||||||
|
delete schema.state;
|
||||||
|
} else {
|
||||||
|
schema.state = __state;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 生命周期
|
||||||
|
if ('__lifeCycles' in props) {
|
||||||
|
if (!__lifeCycles || typeof __lifeCycles !== 'object' || isEmpty(__lifeCycles)) {
|
||||||
|
delete schema.lifeCycles;
|
||||||
|
} else {
|
||||||
|
schema.lifeCycles = __lifeCycles;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 自定义方法
|
||||||
|
if ('__methods' in props) {
|
||||||
|
if (!__methods || typeof __methods !== 'object' || isEmpty(__methods)) {
|
||||||
|
delete schema.methods;
|
||||||
|
} else {
|
||||||
|
schema.methods = __methods;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 数据源设置
|
||||||
|
if ('__dataSource' in props) {
|
||||||
|
if (this.needContainerReload(schema.dataSource, __dataSource)) {
|
||||||
|
schema.__ctx && ++schema.__ctx.idx;
|
||||||
|
}
|
||||||
|
if (__dataSource === undefined || (typeof __dataSource === 'object' && isEmpty(__dataSource))) {
|
||||||
|
delete schema.dataSource;
|
||||||
|
} else {
|
||||||
|
schema.dataSource = __dataSource;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果scss值没有设置有效值,则删除schema中这个的字段
|
||||||
|
if ('__scss' in props) {
|
||||||
|
if (!__scss) {
|
||||||
|
delete schema.scss;
|
||||||
|
} else {
|
||||||
|
schema.scss = __scss;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 子组件
|
||||||
|
if ('children' in props) {
|
||||||
|
if (children === undefined || (typeof children === 'object' && isEmpty(children))) {
|
||||||
|
delete schema.children;
|
||||||
|
} else {
|
||||||
|
schema.children = children;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
schema.props = {
|
||||||
|
...schema.props,
|
||||||
|
...otherProps
|
||||||
|
};
|
||||||
|
|
||||||
|
//过滤undefined属性
|
||||||
|
Object.keys(schema.props).map(key => {
|
||||||
|
if (schema.props[key] === undefined) {
|
||||||
|
delete schema.props[key];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.rebuild(this.schema);
|
||||||
|
this.emit('schemaHelper.material.afterUpdate');
|
||||||
|
}
|
||||||
|
|
||||||
|
createSchema(componentName, props, isContainer) {
|
||||||
|
const schema = {
|
||||||
|
componentName,
|
||||||
|
props: props || {},
|
||||||
|
__ctx: {
|
||||||
|
lunaKey: ++this.lunaKey
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if (isContainer) {
|
||||||
|
schema.children = [];
|
||||||
|
}
|
||||||
|
return schema;
|
||||||
|
}
|
||||||
|
|
||||||
|
rebuild(schema, isInit) {
|
||||||
|
if (!isFileSchema(schema)) {
|
||||||
|
debug('top schema should be a file type');
|
||||||
|
//对于null的schema特殊处理一下
|
||||||
|
if (schema === null) {
|
||||||
|
this.schema = schema;
|
||||||
|
this.emit(`schemaHelper.schema.${isInit ? 'afterInit' : 'afterUpdate'}`);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.blockTree = null;
|
||||||
|
this.compTreeMap = {};
|
||||||
|
this.compTree = null;
|
||||||
|
this.schemaMap = {};
|
||||||
|
this.blockSchemaMap = {};
|
||||||
|
this.compCtxMap = {};
|
||||||
|
const buildSchema = (schema, parentBlockNode, parentCompNode, path = '') => {
|
||||||
|
if (Array.isArray(schema)) {
|
||||||
|
return schema.map((item, idx) => buildSchema(item, parentBlockNode, parentCompNode, `${path}/${idx}`));
|
||||||
|
} else if (typeof schema === 'object') {
|
||||||
|
// 对于undefined及null直接返回
|
||||||
|
if (!schema) return schema;
|
||||||
|
//JSFunction转函数
|
||||||
|
if (isJSFunction(schema)) {
|
||||||
|
if (typeof schema.value === 'string') {
|
||||||
|
let tarFun = parseObj(schema.value);
|
||||||
|
if (typeof tarFun === 'function') {
|
||||||
|
return tarFun;
|
||||||
|
}
|
||||||
|
} else if (typeof schema.value === 'function') {
|
||||||
|
return schema.value;
|
||||||
|
}
|
||||||
|
return schema;
|
||||||
|
}
|
||||||
|
//如果是对象且是JSExpression
|
||||||
|
if (isJSExpression(schema)) {
|
||||||
|
return '{{' + schema.value + '}}';
|
||||||
|
}
|
||||||
|
const res = {};
|
||||||
|
if (isSchema(schema)) {
|
||||||
|
res.__ctx = schema.__ctx;
|
||||||
|
if (!res.__ctx) {
|
||||||
|
const lunaKey = `luna_${++keyIndex}`;
|
||||||
|
res.__ctx = {
|
||||||
|
idx: 0,
|
||||||
|
lunaKey,
|
||||||
|
lunaPath: path,
|
||||||
|
parentKey: parentCompNode && parentCompNode.lunaKey,
|
||||||
|
blockKey: parentBlockNode && parentBlockNode.lunaKey
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
res.__ctx.lunaPath = path;
|
||||||
|
}
|
||||||
|
const label = schema.componentName + (schema.fileName ? '-' + schema.fileName : '');
|
||||||
|
const lunaKey = res.__ctx && res.__ctx.lunaKey;
|
||||||
|
this.schemaMap[lunaKey] = res;
|
||||||
|
if (isFileSchema(schema)) {
|
||||||
|
this.blockSchemaMap[lunaKey] = res;
|
||||||
|
|
||||||
|
const blockNode = {
|
||||||
|
label,
|
||||||
|
lunaKey,
|
||||||
|
isFile: true,
|
||||||
|
children: []
|
||||||
|
};
|
||||||
|
this.compTreeMap[lunaKey] = blockNode;
|
||||||
|
const compNode = clone(blockNode);
|
||||||
|
if (parentBlockNode) {
|
||||||
|
parentBlockNode.children.push(blockNode);
|
||||||
|
} else {
|
||||||
|
this.blockTree = blockNode;
|
||||||
|
}
|
||||||
|
parentBlockNode = blockNode;
|
||||||
|
if (parentCompNode) {
|
||||||
|
parentCompNode.children.push(compNode);
|
||||||
|
} else {
|
||||||
|
this.compTree = compNode;
|
||||||
|
}
|
||||||
|
parentCompNode = compNode;
|
||||||
|
} else {
|
||||||
|
const compNode = {
|
||||||
|
label,
|
||||||
|
lunaKey,
|
||||||
|
children: []
|
||||||
|
};
|
||||||
|
parentCompNode.children.push(compNode);
|
||||||
|
parentCompNode = compNode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
forEach(schema, (val, key) => {
|
||||||
|
if (key.startsWith('__')) {
|
||||||
|
res[key] = val;
|
||||||
|
} else {
|
||||||
|
res[key] = buildSchema(val, parentBlockNode, parentCompNode, `${path}/${key}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
return schema;
|
||||||
|
};
|
||||||
|
this.emit(`schemaHelper.schema.${isInit ? 'beforeInit' : 'beforeUpdate'}`);
|
||||||
|
this.schema = buildSchema(schema);
|
||||||
|
this.emit(`schemaHelper.schema.${isInit ? 'afterInit' : 'afterUpdate'}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
needContainerReload(preData = {}, nextData = {}) {
|
||||||
|
if (
|
||||||
|
typeof preData.dataHandler === 'function' &&
|
||||||
|
typeof nextData.dataHandler === 'function' &&
|
||||||
|
preData.dataHandler.toString() !== nextData.dataHandler.toString()
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
} else if (preData.dataHandler !== nextData.dataHandler) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return !deepEqual(
|
||||||
|
(preData.list || []).filter(item => item.isInit),
|
||||||
|
(nextData.list || []).filter(item => item.isInit),
|
||||||
|
(pre, next) => {
|
||||||
|
if (typeof pre === 'function' && next === 'function') {
|
||||||
|
return pre.toString() === next.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
emit(msg, ...args) {
|
||||||
|
this.appHelper && this.appHelper.emit(msg, ...args);
|
||||||
|
}
|
||||||
|
|
||||||
|
get(key) {
|
||||||
|
return this[key];
|
||||||
|
}
|
||||||
|
|
||||||
|
getSchemaByPath(path) {
|
||||||
|
return jsonuri.get(this.schema, path);
|
||||||
|
}
|
||||||
|
|
||||||
|
getSchema() {
|
||||||
|
return this.schema;
|
||||||
|
}
|
||||||
|
|
||||||
|
getPureSchema() {
|
||||||
|
return transformSchemaToPure(this.schema);
|
||||||
|
}
|
||||||
|
|
||||||
|
getPureSchemaStr() {
|
||||||
|
return serialize(this.getPureSchema(), {
|
||||||
|
unsafe: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getStandardSchema() {
|
||||||
|
return transformSchemaToStandard(this.schema);
|
||||||
|
}
|
||||||
|
|
||||||
|
getStandardSchemaStr() {
|
||||||
|
return serialize(this.getStandardSchema(), {
|
||||||
|
unsafe: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
81
packages/react-renderer/src/utils/storageHelper.js
vendored
Normal file
81
packages/react-renderer/src/utils/storageHelper.js
vendored
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
import localforage from 'localforage';
|
||||||
|
import Debug from 'debug';
|
||||||
|
import { serialize } from './index';
|
||||||
|
|
||||||
|
const debug = Debug('utils:storageHelper');
|
||||||
|
export default class StorageHelper {
|
||||||
|
constructor(name) {
|
||||||
|
this.store = localforage.createInstance(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
getItem(key) {
|
||||||
|
if (!this.store) {
|
||||||
|
throw new Error('store instance not exist');
|
||||||
|
}
|
||||||
|
return this.store.getItem(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
setItem(key, value) {
|
||||||
|
if (!this.store) {
|
||||||
|
throw new Error('store instance not exist');
|
||||||
|
}
|
||||||
|
return this.store.setItem(key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
removeItem(key) {
|
||||||
|
if (!this.store) {
|
||||||
|
throw new Error('store instance not exist');
|
||||||
|
}
|
||||||
|
return this.store.removeItem(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
clear() {
|
||||||
|
if (!this.store) {
|
||||||
|
throw new Error('store instance not exist');
|
||||||
|
}
|
||||||
|
return this.store.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
addHistory(key, code, limit = 10) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
key = '__luna_history_' + key;
|
||||||
|
this.store
|
||||||
|
.getItem(key)
|
||||||
|
.then(res => {
|
||||||
|
let codeStr = serialize(code, {
|
||||||
|
unsafe: true
|
||||||
|
});
|
||||||
|
if (res && res[0] && res[0].code) {
|
||||||
|
if (codeStr === res[0].code) return;
|
||||||
|
}
|
||||||
|
res = res || [];
|
||||||
|
let newId = 1;
|
||||||
|
if (res && res[0] && res[0].id) {
|
||||||
|
newId = res[0].id + 1;
|
||||||
|
}
|
||||||
|
res.unshift({
|
||||||
|
id: newId,
|
||||||
|
time: +new Date(),
|
||||||
|
code: codeStr
|
||||||
|
});
|
||||||
|
this.store
|
||||||
|
.setItem(key, res.slice(0, limit))
|
||||||
|
.then(res => {
|
||||||
|
resolve(res);
|
||||||
|
})
|
||||||
|
.catch(reject);
|
||||||
|
})
|
||||||
|
.catch(reject);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getHistory(key) {
|
||||||
|
key = '__luna_history_' + key;
|
||||||
|
return this.store.getItem(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
clearHistory(key) {
|
||||||
|
key = '__luna_history_' + key;
|
||||||
|
this.store.removeItem(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
88
packages/react-renderer/src/utils/undoRedoHelper.js
vendored
Normal file
88
packages/react-renderer/src/utils/undoRedoHelper.js
vendored
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
import Debug from 'debug';
|
||||||
|
import { fastClone } from './index';
|
||||||
|
const DEFAULT_CONFIG = {
|
||||||
|
limit: 20
|
||||||
|
};
|
||||||
|
const debug = Debug('utils:undoRedoHelper');
|
||||||
|
export default class UndoRedoHelper {
|
||||||
|
constructor(config) {
|
||||||
|
this.config = { ...DEFAULT_CONFIG, ...config };
|
||||||
|
this.data = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
create(key, value, forceCreate) {
|
||||||
|
if (!this.data[key] || forceCreate) {
|
||||||
|
this.data[key] = {
|
||||||
|
list: [fastClone(value)],
|
||||||
|
idx: 0
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return this.data[key];
|
||||||
|
}
|
||||||
|
|
||||||
|
delete(key) {
|
||||||
|
delete this.data[key];
|
||||||
|
}
|
||||||
|
|
||||||
|
resetRecord(key, value) {
|
||||||
|
const data = this.data[key];
|
||||||
|
if (!data || !data.list) return;
|
||||||
|
data.list = data.list.slice(0, data.idx + 1);
|
||||||
|
data.list[data.idx] = fastClone(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
record(key, value) {
|
||||||
|
const data = this.data[key];
|
||||||
|
const limit = this.config.limit;
|
||||||
|
if (!data || !data.list) return;
|
||||||
|
data.list = data.list.slice(0, data.idx + 1);
|
||||||
|
if (data.list.length >= limit) {
|
||||||
|
data.list.shift();
|
||||||
|
}
|
||||||
|
data.list.push(fastClone(value));
|
||||||
|
++data.idx;
|
||||||
|
}
|
||||||
|
|
||||||
|
undo(key) {
|
||||||
|
const data = this.data[key];
|
||||||
|
if (!data || !data.list) return null;
|
||||||
|
//若没有前置操作,返回当前数据
|
||||||
|
if (data.idx <= 0) return data.list[data.idx];
|
||||||
|
--data.idx;
|
||||||
|
return data.list[data.idx];
|
||||||
|
}
|
||||||
|
redo(key) {
|
||||||
|
const data = this.data[key];
|
||||||
|
if (!data || !data.list) return null;
|
||||||
|
//若没有后续操作,返回当前数据
|
||||||
|
if (data.idx >= data.list.length - 1) return data.list[data.idx];
|
||||||
|
++data.idx;
|
||||||
|
return data.list[data.idx];
|
||||||
|
}
|
||||||
|
|
||||||
|
past(key) {
|
||||||
|
const data = this.data[key];
|
||||||
|
if (!data || !data.list || data.idx <= 0) return null;
|
||||||
|
return data.list[data.idx - 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
present(key) {
|
||||||
|
const data = this.data[key];
|
||||||
|
if (!data || !data.list) return null;
|
||||||
|
return data.list[data.idx];
|
||||||
|
}
|
||||||
|
|
||||||
|
future(key) {
|
||||||
|
const data = this.data[key];
|
||||||
|
if (!data || !data.list || data.idx >= data.list.length - 1) return null;
|
||||||
|
return data.list[data.idx + 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
get(key) {
|
||||||
|
return {
|
||||||
|
past: this.past(key),
|
||||||
|
present: this.present(key),
|
||||||
|
future: this.future(key)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
87
packages/react-renderer/src/utils/wsHelper.js
vendored
Normal file
87
packages/react-renderer/src/utils/wsHelper.js
vendored
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
import Debug from 'debug';
|
||||||
|
import client from 'socket.io-client';
|
||||||
|
import { parseUrl } from '@ali/b3-one/lib/url';
|
||||||
|
const debug = Debug('utils:wsHelper');
|
||||||
|
|
||||||
|
export default class WSHelper {
|
||||||
|
constructor(appHelper, namespace, options) {
|
||||||
|
this.appHelper = appHelper;
|
||||||
|
this.ws = null;
|
||||||
|
this.init(namespace, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
init(namespace = '/', options = {}) {
|
||||||
|
if (this.ws) {
|
||||||
|
this.close();
|
||||||
|
}
|
||||||
|
const urlInfo = parseUrl();
|
||||||
|
const ws = (this.ws = client(namespace, {
|
||||||
|
reconnectionDelay: 3000,
|
||||||
|
transports: ['websocket'],
|
||||||
|
query: urlInfo.params,
|
||||||
|
...options
|
||||||
|
}));
|
||||||
|
const appHelper = this.appHelper;
|
||||||
|
debug('ws.init');
|
||||||
|
|
||||||
|
ws.on('connect', msg => {
|
||||||
|
appHelper.emit('wsHelper.connect.success', msg);
|
||||||
|
debug('ws.connect');
|
||||||
|
});
|
||||||
|
|
||||||
|
ws.on('error', msg => {
|
||||||
|
appHelper.emit('wsHelper.connect.error', msg);
|
||||||
|
debug('ws.error', msg);
|
||||||
|
});
|
||||||
|
|
||||||
|
ws.on('disconnect', msg => {
|
||||||
|
appHelper.emit('wsHelper.connect.break', msg);
|
||||||
|
debug('ws.disconnect', msg);
|
||||||
|
});
|
||||||
|
|
||||||
|
ws.on('reconnecting', msg => {
|
||||||
|
appHelper.emit('wsHelper.connect.retry', msg);
|
||||||
|
debug('ws.reconnecting', msg);
|
||||||
|
});
|
||||||
|
|
||||||
|
ws.on('ping', msg => {
|
||||||
|
debug('ws.ping', msg);
|
||||||
|
});
|
||||||
|
|
||||||
|
ws.on('pong', msg => {
|
||||||
|
debug('ws.pong', msg);
|
||||||
|
});
|
||||||
|
|
||||||
|
ws.on('data', msg => {
|
||||||
|
appHelper.emit('wsHelper.data.receive', msg);
|
||||||
|
if (msg.eventName) {
|
||||||
|
appHelper.emit(`wsHelper.result.${msg.eventName}`, msg);
|
||||||
|
}
|
||||||
|
debug('ws.data', msg);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
close() {
|
||||||
|
if (!this.ws) return;
|
||||||
|
this.ws.close();
|
||||||
|
this.ws = null;
|
||||||
|
this.appHelper.emit('wsHelper.connect.close');
|
||||||
|
}
|
||||||
|
|
||||||
|
send(eventName, ...args) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
try {
|
||||||
|
this.appHelper.emit('wsHelper.data.request', {
|
||||||
|
eventName,
|
||||||
|
params: args
|
||||||
|
});
|
||||||
|
this.appHelper.once(`wsHelper.result.${eventName}`, resolve);
|
||||||
|
this.ws && this.ws.emit(eventName, ...args);
|
||||||
|
debug('ws.send', eventName);
|
||||||
|
} catch (err) {
|
||||||
|
console.error('websocket error:', err);
|
||||||
|
reject(err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user