smallstonesk
4 years ago
11 changed files with 321 additions and 207 deletions
@ -1,44 +1,55 @@ |
|||
|
|||
.container { |
|||
display: flex; |
|||
flex-direction: row; |
|||
justify-content: space-between; |
|||
flex-direction: column; |
|||
flex: 1; |
|||
height: 100%; |
|||
|
|||
.left{ |
|||
padding:10px; |
|||
.pane { |
|||
display: flex; |
|||
flex: 1; |
|||
border:1px solid #eee; |
|||
} |
|||
} |
|||
|
|||
.editor{ |
|||
width:100%; |
|||
height:600px; |
|||
.card { |
|||
display: flex; |
|||
flex-direction: column; |
|||
flex: 1; |
|||
background-color: #ffffff; |
|||
|
|||
.cardTitleText { |
|||
margin: 10px 24px 0; |
|||
font-size: 16px; |
|||
font-weight: 500; |
|||
color: #000000; |
|||
} |
|||
|
|||
.visualInput{ |
|||
width:100%; |
|||
.cardBody { |
|||
padding: 10px 24px; |
|||
height: 100%; |
|||
overflow: scroll; |
|||
} |
|||
} |
|||
|
|||
.right{ |
|||
:global { |
|||
.ant-tabs { |
|||
height: 100%; |
|||
flex:1; |
|||
display: flex; |
|||
flex-direction: column; |
|||
justify-content: space-between; |
|||
} |
|||
|
|||
.rightTop{ |
|||
height:50%; |
|||
padding:10px; |
|||
flex:1; |
|||
border:1px solid #eee; |
|||
overflow-y: auto; |
|||
.ant-tabs-content-holder { |
|||
height: 100%; |
|||
} |
|||
|
|||
.rightBottom{ |
|||
height:300px; |
|||
flex:1; |
|||
border:1px solid #eee; |
|||
.ant-tabs-content { |
|||
height: 100%; |
|||
overflow: scroll; |
|||
} |
|||
|
|||
.sc-bdVaJa.cjjWdp { |
|||
opacity: 1; |
|||
background-color: #efefef; |
|||
|
|||
&:hover { |
|||
border-color: transparent; |
|||
} |
|||
} |
|||
} |
|||
|
@ -1,135 +1,91 @@ |
|||
import React from 'react'; |
|||
import JsonView from 'react-json-view'; |
|||
import { Tabs, Form, Input, Button, Space } from 'antd'; |
|||
const { TabPane } = Tabs; |
|||
import CodeEditor from '../codeEditor'; |
|||
import Console from '../console'; |
|||
import { inputJson, outputJson } from './json' |
|||
import React, { |
|||
useState, |
|||
useCallback, |
|||
} from 'react'; |
|||
|
|||
import styles from './index.module.less'; |
|||
import { MinusCircleOutlined, PlusOutlined } from '@ant-design/icons'; |
|||
interface MyFormItemProps { |
|||
type: string |
|||
} |
|||
|
|||
const MyFormItem: React.FC<MyFormItemProps> = (props) => { |
|||
return ( |
|||
<> |
|||
<h3>{props.type}</h3> |
|||
<Form.List name={props.type}> |
|||
{(fields, { add, remove }) => ( |
|||
<> |
|||
{fields.map(field => ( |
|||
<Space key={field.key} align="baseline"> |
|||
<Form.Item |
|||
{...field} |
|||
label="key" |
|||
name={[field.name, 'key']} |
|||
fieldKey={[field.fieldKey, 'key']} |
|||
> |
|||
<Input /> |
|||
</Form.Item> |
|||
<Form.Item |
|||
{...field} |
|||
label="value" |
|||
name={[field.name, 'value']} |
|||
fieldKey={[field.fieldKey, 'value']} |
|||
> |
|||
<Input /> |
|||
</Form.Item> |
|||
<MinusCircleOutlined onClick={() => remove(field.name)} /> |
|||
</Space> |
|||
))} |
|||
<Form.Item> |
|||
<Button type="dashed" onClick={() => add()} block icon={<PlusOutlined />}>新增</Button> |
|||
</Form.Item> |
|||
</> |
|||
)} |
|||
</Form.List> |
|||
</> |
|||
) |
|||
} |
|||
import Console from '../console'; |
|||
import InputPanel from './inputPanel'; |
|||
import JsonView from 'react-json-view'; |
|||
|
|||
interface VisualInputProps { |
|||
onChange: (value: object) => void |
|||
} |
|||
// FIXME: https://github.com/tomkp/react-split-pane/issues/541
|
|||
// @ts-ignore
|
|||
import SplitPane from 'react-split-pane/lib/SplitPane'; |
|||
// @ts-ignore
|
|||
import Pane from 'react-split-pane/lib/Pane'; |
|||
|
|||
const defaultInput = { |
|||
pipe: {}, |
|||
context: {}, |
|||
payload: {}, |
|||
config: {} |
|||
}; |
|||
|
|||
const VisualInput: React.FC<VisualInputProps> = (props) => { |
|||
const [form] = Form.useForm() |
|||
const PAYLOAD = 'payload', PIPE = 'pipe', CONTEXT = 'context', CONFIG = 'config' |
|||
const onChange = () => { |
|||
props.onChange(form.getFieldsValue()) |
|||
interface ICardProps { |
|||
title: string; |
|||
} |
|||
|
|||
const Card: React.FC<ICardProps> = (props) => { |
|||
const {title} = props; |
|||
|
|||
return ( |
|||
<div className={styles.visualInput}> |
|||
<Form form={form} name="form" autoComplete="off" onValuesChange={onChange}> |
|||
<MyFormItem type={PAYLOAD} /> |
|||
<MyFormItem type={PIPE} /> |
|||
<MyFormItem type={CONTEXT} /> |
|||
<MyFormItem type={CONFIG} /> |
|||
</Form> |
|||
<div className={styles.card}> |
|||
<div className={styles.cardTitleText}>{title}</div> |
|||
<div className={styles.cardBody}> |
|||
{props.children} |
|||
</div> |
|||
) |
|||
} |
|||
interface CodeRunProps { |
|||
onChange: (value: object) => void |
|||
} |
|||
</div> |
|||
); |
|||
}; |
|||
|
|||
const CodeRun: React.FC<CodeRunProps> = (props) => { |
|||
const onFormChange = (value: any) => { |
|||
props.onChange(value) |
|||
interface ICodeRunProps { |
|||
} |
|||
const onJsonChange = (ev: any, newCode: any): void => { |
|||
const codeStr = newCode.slice(15) |
|||
const formatCodeStr = codeStr.replace(/ |\/\/.*/g, '').replace(/\s/g, '').replace(/(\w+):/g, '"$1":').replace(/'/g, '"') |
|||
try { |
|||
props.onChange(JSON.parse(formatCodeStr)) |
|||
} catch (err) { |
|||
props.onChange({}) |
|||
} |
|||
}; |
|||
|
|||
const CodeRun: React.FC<ICodeRunProps> = (props) => { |
|||
|
|||
const [input, setInput] = useState(defaultInput); |
|||
const [output, setOutput] = useState({}); |
|||
|
|||
const onChangeInput = useCallback((val: any) => { |
|||
setInput(val); |
|||
}, []); |
|||
|
|||
return ( |
|||
<div className={styles.container}> |
|||
<div className={styles.left}> |
|||
<Tabs |
|||
type="card" |
|||
tabBarGutter={0} |
|||
defaultActiveKey={'basic'} |
|||
tabBarStyle={{ display: 'flex', flex: 1, justifyContent: 'center', alignItems: 'center' }} |
|||
> |
|||
<TabPane className={styles.tabPane} tab={'可视化'} key={'visualTab'}> |
|||
<VisualInput onChange={onFormChange} /> |
|||
</TabPane> |
|||
<TabPane className={styles.tabPane} tab={'JSON输入'} key={'jsonTab'}> |
|||
<CodeEditor |
|||
value={inputJson} |
|||
className={styles.editor} |
|||
onChange={onJsonChange} |
|||
<SplitPane split={'horizontal'}> |
|||
<Pane initialSize={'380px'} minSize={'43px'}> |
|||
<SplitPane split={'vertical'}> |
|||
<Pane className={styles.pane} minSize={'360px'} maxSize={'660px'}> |
|||
<Card title={'输入'}> |
|||
<InputPanel |
|||
data={input} |
|||
onChange={onChangeInput} |
|||
/> |
|||
</TabPane> |
|||
</Tabs> |
|||
</div> |
|||
|
|||
<div className={styles.right}> |
|||
<div className={styles.rightTop}> |
|||
<p>输出</p> |
|||
</Card> |
|||
</Pane> |
|||
<Pane className={styles.pane}> |
|||
<Card title={'输出'}> |
|||
<JsonView |
|||
name={null} |
|||
collapsed={false} |
|||
enableClipboard={false} |
|||
displayDataTypes={false} |
|||
displayObjectSize={false} |
|||
src={outputJson} |
|||
src={{}} |
|||
/> |
|||
</div> |
|||
<div className={styles.rightBottom}> |
|||
</Card> |
|||
</Pane> |
|||
</SplitPane> |
|||
</Pane> |
|||
<Pane className={styles.pane} minSize={'150px'}> |
|||
<Card title={'控制台'}> |
|||
<Console /> |
|||
</Card> |
|||
</Pane> |
|||
</SplitPane> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
|
|||
) |
|||
); |
|||
} |
|||
|
|||
export default CodeRun; |
|||
|
@ -0,0 +1,24 @@ |
|||
.itemHeader { |
|||
margin-bottom: 8px; |
|||
|
|||
.itemTitleText { |
|||
font-size: 15px; |
|||
font-weight: 500; |
|||
} |
|||
|
|||
.itemDescText { |
|||
margin-left: 5px; |
|||
font-size: 12px; |
|||
color: #999; |
|||
} |
|||
} |
|||
|
|||
:global { |
|||
.ant-tabs-nav { |
|||
margin-bottom: 5px !important; |
|||
} |
|||
|
|||
.ant-form-item { |
|||
margin-bottom: 8px; |
|||
} |
|||
} |
@ -0,0 +1,166 @@ |
|||
import React, { |
|||
useRef, |
|||
useEffect, |
|||
} from 'react'; |
|||
|
|||
import styles from './index.module.less'; |
|||
|
|||
import CodeEditor from '../../codeEditor'; |
|||
import { Tabs, Form, Input, Button, Space } from 'antd'; |
|||
import { MinusCircleOutlined, PlusOutlined } from '@ant-design/icons'; |
|||
|
|||
const inputItems = [ |
|||
{ type: 'pipe', desc: '上一节点返回值' }, |
|||
{ type: 'context', desc: '当前逻辑流程公用数据' }, |
|||
{ type: 'payload', desc: '当前逻辑流程通用数据' }, |
|||
{ type: 'config', desc: '当前节点投放配置' }, |
|||
]; |
|||
|
|||
interface IVisualFormItemProps { |
|||
type: string; |
|||
desc: string; |
|||
} |
|||
|
|||
const VisualFormItem: React.FC<IVisualFormItemProps> = (props) => { |
|||
const { type, desc } = props; |
|||
|
|||
return ( |
|||
<React.Fragment> |
|||
<div className={styles.itemHeader}> |
|||
<span className={styles.itemTitleText}>{type}</span> |
|||
<span className={styles.itemDescText}>{desc}</span> |
|||
</div> |
|||
<Form.List name={type}> |
|||
{(fields, { add, remove }) => ( |
|||
<React.Fragment> |
|||
{fields.map(field => ( |
|||
<Space key={field.key} align={'baseline'}> |
|||
<Form.Item |
|||
{...field} |
|||
label={'key'} |
|||
name={[field.name, 'key']} |
|||
fieldKey={[field.fieldKey, 'key']} |
|||
> |
|||
<Input /> |
|||
</Form.Item> |
|||
<Form.Item |
|||
{...field} |
|||
label={'value'} |
|||
name={[field.name, 'value']} |
|||
fieldKey={[field.fieldKey, 'value']} |
|||
> |
|||
<Input /> |
|||
</Form.Item> |
|||
<MinusCircleOutlined onClick={() => remove(field.name)} /> |
|||
</Space> |
|||
))} |
|||
<Form.Item> |
|||
<Button |
|||
block={true} |
|||
type={'dashed'} |
|||
icon={<PlusOutlined />} |
|||
onClick={() => add()} |
|||
> |
|||
新增 |
|||
</Button> |
|||
</Form.Item> |
|||
</React.Fragment> |
|||
)} |
|||
</Form.List> |
|||
</React.Fragment> |
|||
); |
|||
}; |
|||
|
|||
interface IVisualPanelProps { |
|||
data: any; |
|||
onChange: (value: object) => void |
|||
} |
|||
|
|||
const VisualPanel: React.FC<IVisualPanelProps> = (props) => { |
|||
const { data, onChange } = props; |
|||
const [form] = Form.useForm(); |
|||
|
|||
useEffect(() => { |
|||
const filedsValue: { [key: string]: any } = {}; |
|||
for (const { type } of inputItems) { |
|||
const mockValue = data[type] || {}; |
|||
filedsValue[type] = Object.keys(mockValue).map(key => ({ key, value: mockValue[key] })); |
|||
} |
|||
form.setFieldsValue(filedsValue); |
|||
}, [data]); |
|||
|
|||
const onFormChange = () => { |
|||
const input: { [key: string]: any } = {}; |
|||
const filedsValue = form.getFieldsValue(); |
|||
for (const { type } of inputItems) { |
|||
const mockValue = filedsValue[type] || []; |
|||
input[type] = mockValue.reduce((prev: any, cur: { key: string, value: any }) => { |
|||
const { key, value } = cur; |
|||
prev[key] = value; |
|||
return prev; |
|||
}, {}); |
|||
} |
|||
onChange(input); |
|||
}; |
|||
|
|||
return ( |
|||
<div className={styles.visualInput}> |
|||
<Form |
|||
form={form} |
|||
autoComplete={'on'} |
|||
onChange={onFormChange} |
|||
> |
|||
{inputItems.map(({ type, desc }, index) => ( |
|||
<VisualFormItem |
|||
key={index} |
|||
type={type} |
|||
desc={desc} |
|||
/> |
|||
))} |
|||
</Form> |
|||
</div> |
|||
); |
|||
}; |
|||
|
|||
interface IInputPanelProps { |
|||
data: any; |
|||
onChange: (data: any) => void; |
|||
} |
|||
|
|||
const InputPanel: React.FC<IInputPanelProps> = (props) => { |
|||
const cache: any = useRef({}); |
|||
const { data, onChange } = props; |
|||
|
|||
const onVisualChange = (newForm: any) => { |
|||
clearTimeout(cache.current.timer); |
|||
cache.current.timer = setTimeout(() => onChange(newForm), 1000); |
|||
}; |
|||
const onEditorChange = (ev: any, newCode: string | undefined = '') => { |
|||
clearTimeout(cache.current.timer); |
|||
cache.current.timer = setTimeout(() => { |
|||
try { |
|||
onChange(JSON.parse(newCode)); |
|||
} catch (err) { |
|||
console.log(err); |
|||
} |
|||
}, 1000); |
|||
}; |
|||
|
|||
return ( |
|||
<Tabs type={'card'} defaultActiveKey={'visualTab'}> |
|||
<Tabs.TabPane className={styles.tabPane} tab={'可视化模式'} key={'visualTab'}> |
|||
<VisualPanel data={data} onChange={onVisualChange} /> |
|||
</Tabs.TabPane> |
|||
<Tabs.TabPane className={styles.tabPane} tab={'源码模式'} key={'jsonTab'}> |
|||
<CodeEditor |
|||
height={'100%'} |
|||
language={'json'} |
|||
value={JSON.stringify(data, null, 2)} |
|||
onChange={onEditorChange} |
|||
/> |
|||
</Tabs.TabPane> |
|||
</Tabs> |
|||
); |
|||
}; |
|||
|
|||
export default InputPanel; |
@ -1,39 +0,0 @@ |
|||
const inputJson = `export default {
|
|||
// 上游数据
|
|||
pipe: { |
|||
success: false, |
|||
message: '未登录' |
|||
}, |
|||
// 公用数据
|
|||
context: { |
|||
data: { |
|||
isLogin: false |
|||
} |
|||
}, |
|||
// 传入载荷
|
|||
payload: { |
|||
name:'xxx' |
|||
}, |
|||
// 节点配置项
|
|||
config: { |
|||
hasLog: true |
|||
} |
|||
}`;
|
|||
|
|||
const outputJson = { |
|||
pipe: { |
|||
success: false, |
|||
message: '未登录' |
|||
}, |
|||
context: { |
|||
data: { |
|||
isLogin: false, |
|||
name: 'xxx' |
|||
} |
|||
} |
|||
} |
|||
|
|||
export { |
|||
inputJson, |
|||
outputJson |
|||
} |
Loading…
Reference in new issue