Browse Source

refactor: codeEditModal and codeRunModal move to flowChart's folder

master
smallstonesk 4 years ago
parent
commit
f882601284
  1. 36
      packages/core/src/components/codeEditor/index.tsx
  2. 11
      packages/core/src/mods/flowChart/codeEditorModel/index.module.less
  3. 116
      packages/core/src/mods/flowChart/codeEditorModel/index.tsx
  4. 8
      packages/core/src/mods/flowChart/codeRunModal/index.module.less
  5. 50
      packages/core/src/mods/flowChart/codeRunModal/index.tsx
  6. 4
      packages/core/src/mods/flowChart/contextMenu/menuConfig/node.tsx
  7. 2
      packages/core/src/mods/flowChart/createFlowChart.ts
  8. 4
      packages/core/src/mods/flowChart/index.tsx
  9. 20
      packages/core/src/mods/settingBar/components/code/index.module.less
  10. 164
      packages/core/src/mods/settingBar/components/code/index.tsx
  11. 12
      packages/core/src/mods/settingBar/index.tsx
  12. 5
      packages/core/src/mods/settingBar/mods/basic/index.module.less
  13. 42
      packages/core/src/mods/settingBar/mods/basic/index.tsx
  14. 23
      packages/core/src/mods/settingBar/mods/config/index.module.less
  15. 123
      packages/core/src/mods/settingBar/mods/config/index.tsx

36
packages/core/src/components/codeEditor/index.tsx

@ -1,14 +1,23 @@
import React, {useMemo} from 'react'; import React, {
useMemo,
useState,
useEffect
} from 'react';
import { import {
monaco, monaco,
EditorDidMount,
ControlledEditor, ControlledEditor,
ControlledEditorProps ControlledEditorProps
} from '@monaco-editor/react'; } from '@monaco-editor/react';
import monokaiTheme from './theme-monokai'; import monokaiTheme from './theme-monokai';
let KeyMod: any = {};
let KeyCode: any = {};
monaco.init().then((monaco) => { monaco.init().then((monaco) => {
KeyMod = monaco.KeyMod;
KeyCode = monaco.KeyCode;
monaco.editor.defineTheme('monokai', monokaiTheme); monaco.editor.defineTheme('monokai', monokaiTheme);
}); });
@ -16,17 +25,38 @@ const CODE_EDITOR_OPTIONS = {
fontSize: 14 fontSize: 14
}; };
export const CodeEditor: React.FC<ControlledEditorProps> = (props) => { interface IProps extends ControlledEditorProps {
const {options, ...rest} = props; onSave?: (code: string) => void;
}
export const CodeEditor: React.FC<IProps> = (props) => {
const {options, editorDidMount, onSave, ...rest} = props;
const [editorInst, setEditorInst] = useState<any>();
const editorOptions = useMemo(() => { const editorOptions = useMemo(() => {
return Object.assign({}, CODE_EDITOR_OPTIONS, options); return Object.assign({}, CODE_EDITOR_OPTIONS, options);
}, [options]); }, [options]);
useEffect(() => {
if (editorInst) {
// NOTE: how to add command(https://microsoft.github.io/monaco-editor/api/interfaces/monaco.editor.istandalonecodeeditor.html#addcommand)
editorInst.addCommand(KeyMod.CtrlCmd | KeyCode.KEY_S, () => {
onSave && onSave(editorInst.getValue());
});
}
}, [editorInst, onSave]);
const onEditorDidMount: EditorDidMount = (getEditorValue, editor) => {
setEditorInst(editor);
editorDidMount && editorDidMount(getEditorValue, editor);
};
return ( return (
<ControlledEditor <ControlledEditor
theme={'monokai'} theme={'monokai'}
language={'javascript'} language={'javascript'}
options={editorOptions} options={editorOptions}
editorDidMount={onEditorDidMount}
{...rest} {...rest}
/> />
); );

11
packages/core/src/mods/flowChart/codeEditorModel/index.module.less

@ -0,0 +1,11 @@
.depsInfoModalContent {
padding-top: 20px;
}
.modal {
:global {
.ant-modal-body {
padding: 0;
}
}
}

116
packages/core/src/mods/flowChart/codeEditorModel/index.tsx

@ -0,0 +1,116 @@
import React, {
useState,
useEffect
} from 'react';
import 'antd/es/modal/style';
import styles from './index.module.less';
import { Graph } from '@antv/x6';
import { Modal, message } from 'antd';
import JsonView from 'react-json-view';
import { safeParse } from '../../../utils';
import analyzeDeps from '../../../utils/analyzeDeps';
import CodeEditor from '../../../components/codeEditor';
interface IProps {
title?: string;
flowChart: Graph;
}
const CodeEditModal: React.FC<IProps> = (props) => {
const { title = '编辑代码', flowChart } = props;
const [code, setCode] = useState<string>('');
const [visible, setVisible] = useState<boolean>(false);
const updateNodeCode = (code: string): void => {
const cell = flowChart.getSelectedCells()[0];
const { dependencies } = cell.getData();
cell.setData({ code });
message.success('代码保存成功', 1);
const excludeDeps = safeParse(dependencies);
analyzeDeps(code, Object.keys(excludeDeps)).then((deps): void => {
if (Object.keys(deps).length > 0) {
Modal.info({
title: '检测到您的代码有新依赖,已为您自动更新',
content: (
<div className={styles.depsInfoModalContent}>
<JsonView
src={deps}
name={'dependencies'}
collapsed={false}
enableClipboard={false}
displayDataTypes={false}
displayObjectSize={false}
/>
</div>
),
onOk() {
const newDeps = { ...excludeDeps, ...deps };
cell.setData({
code,
dependencies: JSON.stringify(newDeps, null, 2)
});
},
});
}
});
};
// life
useEffect(() => {
const handler = () => setVisible(true);
flowChart.on('graph:editCode', handler);
return () => {
flowChart.off('graph:editCode', handler);
};
}, []);
useEffect(() => {
if (visible) {
const cell = flowChart.getSelectedCells()[0];
const { code } = cell.getData() || {};
setCode(code);
} else {
setCode('');
}
}, [visible]);
// events
const onOk = (): void => {
setVisible(false);
updateNodeCode(code);
};
const onCancel = (): void => {
setVisible(false);
}
const onChangeCode = (ev: any, newCode: string | undefined = ''): void => {
setCode(newCode);
};
const onSaveCode = (newCode: string) => {
updateNodeCode(newCode);
};
return (
<Modal
className={styles.modal}
width={1000}
title={title}
okText={'保存'}
visible={visible}
cancelText={'取消'}
onOk={onOk}
onCancel={onCancel}
>
<CodeEditor
value={code}
width={'100%'}
height={'600px'}
onChange={onChangeCode}
onSave={onSaveCode}
/>
</Modal>
);
};
export default CodeEditModal;

8
packages/core/src/mods/flowChart/codeRunModal/index.module.less

@ -0,0 +1,8 @@
.modal {
:global {
.ant-modal-body {
padding: 0;
height: 650px;
}
}
}

50
packages/core/src/mods/flowChart/codeRunModal/index.tsx

@ -0,0 +1,50 @@
import React, {
useState,
useEffect,
useCallback
} from 'react';
import styles from './index.module.less';
import { Modal } from 'antd';
import { Graph } from '@antv/x6';
import CodeRun from '../../../components/codeRun';
interface IEditModalProps {
title?: string;
flowChart: Graph;
}
const CodeRunModal: React.FC<IEditModalProps> = (props): JSX.Element => {
const { title = '执行代码', flowChart } = props;
const [visible, setVisible] = useState(false);
useEffect(() => {
const handler = () => setVisible(true);
flowChart.on('graph:runCode', handler);
return () => {
flowChart.off('graph:runCode', handler);
};
}, [flowChart]);
// events
const onClose = useCallback((): void => {
setVisible(false);
}, []);
return (
<Modal
className={styles.modal}
width={1000}
title={title}
visible={visible}
footer={null}
onCancel={onClose}
>
<CodeRun flowChart={flowChart}/>
</Modal>
);
};
export default CodeRunModal;

4
packages/core/src/mods/flowChart/contextMenu/menuConfig/node.tsx

@ -56,7 +56,7 @@ const nodeMenuConfig = [
return getSelectedNodes(flowChart).length !== 1; return getSelectedNodes(flowChart).length !== 1;
}, },
handler(flowChart: Graph) { handler(flowChart: Graph) {
flowChart.trigger('settingBar:clickEditCode'); flowChart.trigger('graph:editCode');
} }
}, },
{ {
@ -67,7 +67,7 @@ const nodeMenuConfig = [
return getSelectedNodes(flowChart).length !== 1; return getSelectedNodes(flowChart).length !== 1;
}, },
handler(flowChart: Graph) { handler(flowChart: Graph) {
flowChart.trigger('settingBar:runCode'); flowChart.trigger('graph:runCode');
} }
} }
]; ];

2
packages/core/src/mods/flowChart/createFlowChart.ts

@ -56,7 +56,7 @@ const registerEvents = (flowChart: Graph): void => {
} }
}); });
flowChart.on('node:dblclick', () => { flowChart.on('node:dblclick', () => {
flowChart.trigger('settingBar:clickEditCode'); flowChart.trigger('graph:editCode');
}); });
flowChart.on('blank:contextmenu', (args) => { flowChart.on('blank:contextmenu', (args) => {
const { e: { clientX, clientY } } = args; const { e: { clientX, clientY } } = args;

4
packages/core/src/mods/flowChart/index.tsx

@ -10,6 +10,8 @@ import { Graph } from '@antv/x6';
import { queryGraph } from '../../api'; import { queryGraph } from '../../api';
import { parseQuery } from '../../utils'; import { parseQuery } from '../../utils';
import createFlowChart from './createFlowChart'; import createFlowChart from './createFlowChart';
import CodeRunModal from './codeRunModal';
import CodeEditorModal from './codeEditorModel';
import FlowChartContextMenu from './contextMenu'; import FlowChartContextMenu from './contextMenu';
interface IProps { interface IProps {
@ -102,6 +104,8 @@ const FlowChart: React.FC<IProps> = (props) => {
<div className={styles.container} ref={wrapperRef}> <div className={styles.container} ref={wrapperRef}>
<div className={styles.flowChart} ref={graphRef} /> <div className={styles.flowChart} ref={graphRef} />
<div className={styles.miniMap} ref={miniMapRef} /> <div className={styles.miniMap} ref={miniMapRef} />
{flowChart && <CodeRunModal flowChart={flowChart}/>}
{flowChart && <CodeEditorModal flowChart={flowChart}/>}
{flowChart && <FlowChartContextMenu {...contextMenuInfo} flowChart={flowChart} />} {flowChart && <FlowChartContextMenu {...contextMenuInfo} flowChart={flowChart} />}
</div> </div>
); );

20
packages/core/src/mods/settingBar/components/code/index.module.less

@ -1,20 +0,0 @@
.container {
margin-top: 20px;
.titleText {
font-size: 14px;
margin-bottom: 10px;
}
.btn {
margin-top: 5px;
}
}
.editModal {
:global {
.ant-modal-body {
padding: 0;
}
}
}

164
packages/core/src/mods/settingBar/components/code/index.tsx

@ -1,164 +0,0 @@
import React, { useState, useEffect } from 'react';
import 'antd/es/modal/style';
import 'antd/es/button/style';
import styles from './index.module.less';
import { Graph } from '@antv/x6';
import { Button, Modal, message } from 'antd';
import { monaco, EditorDidMount } from '@monaco-editor/react';
import CodeEditor from '../../../../components/codeEditor';
import CodeRun from '../../../../components/codeRun';
interface IProps {
value: any;
name: string;
title: string;
flowChart: Graph;
onValueChange: (value: string) => void;
}
// NOTE: avoid monaco init many times
let KeyMod: any = {};
let KeyCode: any = {};
monaco.init().then(monaco => {
KeyMod = monaco.KeyMod;
KeyCode = monaco.KeyCode;
});
const Code: React.FC<IProps> = (props) => {
const { title, value, onValueChange, flowChart } = props;
const [visible, setVisible] = useState<boolean>(false);
useEffect(() => {
flowChart.on('settingBar:clickEditCode', onClickEdit);
return () => {
flowChart.off('settingBar:clickEditCode', onClickEdit);
};
}, []);
// events
const onClickEdit = (): void => {
setVisible(true);
};
const onClickCancel = (): void => {
setVisible(false);
};
const onClickOk = (newCode: string): void => {
setVisible(false);
onValueChange(newCode);
};
const onSave = (newCode: string): void => {
onValueChange(newCode);
};
return (
<div className={styles.container}>
<p className={styles.titleText}>{title}</p>
<Button block={true} className={styles.btn} onClick={onClickEdit}>
</Button>
<EditModal
title={title}
value={value}
visible={visible}
flowChart={flowChart}
onSave={onSave}
onOk={onClickOk}
onCancel={onClickCancel}
/>
</div>
);
};
interface IEditorModalProps {
visible: boolean;
title: string;
value: string;
flowChart: Graph;
onSave: (val: string) => void;
onOk: (val: string) => void;
onCancel: () => void;
}
const EditModal: React.FC<IEditorModalProps> = (props) => {
const { visible, title, value, flowChart, onSave, onOk, onCancel } = props;
const [code, setCode] = useState<string>(value);
const [editorInst, setEditorInst] = useState<any>();
const [codeRunVisible, setCodeRunVisible] = useState<boolean>(false);
// life
useEffect(() => {
// set value when opening modal
// clear content when closing modal
if (visible) {
setCode(value);
} else {
setCode('');
}
}, [visible]);
useEffect(() => {
if (editorInst) {
// NOTE: how to add command(https://microsoft.github.io/monaco-editor/api/interfaces/monaco.editor.istandalonecodeeditor.html#addcommand)
editorInst.addCommand(KeyMod.CtrlCmd | KeyCode.KEY_S, () => {
onSave(editorInst.getValue());
message.success('保存成功', 1);
});
}
}, [editorInst, onSave]);
// events
const onEditOk = (): void => {
// onOk(code);
setCodeRunVisible(true)
};
const onRunOk = (): void => {
// setCodeRunVisible(false)
}
const onRunCancel = (): void => {
setCodeRunVisible(false)
}
const onChangeCode = (ev: any, newCode: string | undefined = ''): void => {
if (newCode !== code) {
setCode(newCode);
}
};
const onEditorDidMount: EditorDidMount = (getEditorValue, editor) => {
setEditorInst(editor);
};
return (
<Modal
className={styles.editModal}
width={1000}
title={title}
okText={'执行代码'}
visible={visible}
cancelText={'取消'}
onOk={onEditOk}
onCancel={onCancel}
>
<CodeEditor
value={code}
width={'100%'}
height={'600px'}
onChange={onChangeCode}
editorDidMount={onEditorDidMount}
/>
<Modal
className={styles.runModal}
width={1000}
title={'执行代码'}
okText={'运行'}
visible={codeRunVisible}
cancelText={'取消'}
onOk={onRunOk}
onCancel={onRunCancel}
>
<CodeRun flowChart={flowChart}/>
</Modal>
</Modal>
);
};
export default Code;

12
packages/core/src/mods/settingBar/index.tsx

@ -7,8 +7,7 @@ import styles from './index.module.less';
import { Graph } from '@antv/x6'; import { Graph } from '@antv/x6';
import { Empty, Tabs } from 'antd'; import { Empty, Tabs } from 'antd';
import Basic from './mods/basic'; import Basic from './mods/basic';
import Config from './mods/config'; // import TestCase from './mods/testCase';
import TestCase from './mods/testCase';
const { TabPane } = Tabs; const { TabPane } = Tabs;
@ -36,15 +35,12 @@ const SettingBar: React.FC<IProps> = (props) => {
defaultActiveKey={'basic'} defaultActiveKey={'basic'}
tabBarStyle={{ display: 'flex', flex: 1, justifyContent: 'center', alignItems: 'center' }} tabBarStyle={{ display: 'flex', flex: 1, justifyContent: 'center', alignItems: 'center' }}
> >
<TabPane tab={'基础信息'} key={'basic'}> <TabPane tab={'节点配置'} key={'basic'}>
<Basic selectedCell={nodes[0]} flowChart={flowChart} /> <Basic selectedCell={nodes[0]} flowChart={flowChart} />
</TabPane> </TabPane>
{/* <TabPane tab={''} key={'config'}> {/* <TabPane tab={''} key={'testCase'} forceRender>
<Config selectedCell={nodes[0]} />
</TabPane> */}
<TabPane tab={'测试用例'} key={'testCase'} forceRender>
<TestCase selectedCell={nodes[0]} flowChart={flowChart} /> <TestCase selectedCell={nodes[0]} flowChart={flowChart} />
</TabPane> </TabPane> */}
</Tabs> </Tabs>
</div> </div>
); );

5
packages/core/src/mods/settingBar/mods/basic/index.module.less

@ -1,6 +1,7 @@
.container { .container {
height: 800px; height: 800px;
overflow-y: auto; overflow-y: auto;
&::-webkit-scrollbar { &::-webkit-scrollbar {
display: none; display: none;
} }
@ -9,7 +10,3 @@
margin-top:20px; margin-top:20px;
} }
} }
.depsInfoModalContent {
padding-top: 20px;
}

42
packages/core/src/mods/settingBar/mods/basic/index.tsx

@ -5,14 +5,10 @@ import React, {
import styles from './index.module.less'; import styles from './index.module.less';
import { Modal, Card } from 'antd'; import { Card } from 'antd';
import { Cell, Graph } from '@antv/x6'; import { Cell, Graph } from '@antv/x6';
import JsonView from 'react-json-view';
import Json from '../../components/json'; import Json from '../../components/json';
import Code from '../../components/code';
import Input from '../../components/input'; import Input from '../../components/input';
import { safeParse } from '../../../../utils/index';
import analyzeDeps from '../../../../utils/analyzeDeps';
interface IProps { interface IProps {
selectedCell: Cell; selectedCell: Cell;
@ -21,16 +17,15 @@ interface IProps {
interface IBasicData { interface IBasicData {
label: string; label: string;
code: string;
trigger?: string; trigger?: string;
dependencies: string; dependencies: string;
configSchema: string; configSchema: string;
} }
const Basic: React.FC<IProps> = (props) => { const Basic: React.FC<IProps> = (props) => {
const { selectedCell, flowChart } = props; const { selectedCell } = props;
const [data, setData] = useState<IBasicData>(selectedCell.getData()); const [data, setData] = useState<IBasicData>(selectedCell.getData());
const { label, code, trigger, dependencies, configSchema } = data || {}; const { label, trigger, dependencies, configSchema } = data || {};
// life // life
useEffect(() => { useEffect(() => {
@ -51,7 +46,6 @@ const Basic: React.FC<IProps> = (props) => {
}; };
const onChangeConfigSchema = (val: string): void => { const onChangeConfigSchema = (val: string): void => {
commonChange('configSchema', val); commonChange('configSchema', val);
selectedCell.trigger('change:configSchema', { configSchema: val });
}; };
const onChangeTrigger = (val: string): void => { const onChangeTrigger = (val: string): void => {
commonChange('trigger', val); commonChange('trigger', val);
@ -59,36 +53,6 @@ const Basic: React.FC<IProps> = (props) => {
const onChangeDependencies = (val: string): void => { const onChangeDependencies = (val: string): void => {
commonChange('dependencies', val); commonChange('dependencies', val);
}; };
const onChangeCode = (val: string): void => {
commonChange('code', val);
// NOTE: if code changes, check whether new dependencies are added
const excludeDeps = safeParse(data?.dependencies as string);
analyzeDeps(val, Object.keys(excludeDeps)).then((deps) => {
if (Object.keys(deps).length > 0) {
Modal.info({
title: '检测到您的代码有新依赖,已为您自动更新',
content: (
<div className={styles.depsInfoModalContent}>
<JsonView
src={deps}
name={'dependencies'}
collapsed={false}
enableClipboard={false}
displayDataTypes={false}
displayObjectSize={false}
/>
</div>
),
onOk() {
batchUpdate({
code: val,
dependencies: JSON.stringify({ ...excludeDeps, ...deps }, null, 2)
});
},
});
}
});
};
return ( return (
<div className={styles.container}> <div className={styles.container}>

23
packages/core/src/mods/settingBar/mods/config/index.module.less

@ -1,23 +0,0 @@
.container {
display: flex;
flex-direction: column;
padding: 0 20px;
height: 100%;
.empty {
margin-top: 100px;
}
.footContainer {
display: flex;
flex-direction: row;
justify-content: flex-end;
margin-top: 20px;
.saveBtn {
margin-left: 20px;
}
}
}

123
packages/core/src/mods/settingBar/mods/config/index.tsx

@ -1,123 +0,0 @@
import React, { useState, useEffect } from 'react';
import styles from './index.module.less';
import { Cell } from '@antv/x6';
import { Button, Empty } from 'antd';
import Input from '../../components/input';
import Checkbox from '../../components/checkbox';
import { safeParse } from '../../../../utils';
// FIXME
// save original config in tempConfig when change to edit mode
// and recover original config when click cancel button
let tempConfig: any = null;
enum Mode {
edit = 'edit',
readOnly = 'readOnly',
}
interface IConfig {
[key: string]: any;
}
interface IProps {
selectedCell: Cell;
}
const Config: React.FC<IProps> = (props) => {
const { selectedCell } = props;
const [mode, setMode] = useState<Mode>(Mode.readOnly);
const [configData, setConfigData] = useState<IConfig>({});
const [configSchema, setConfigSchema] = useState<IConfig>({});
// life
useEffect(() => {
const { configSchema, configData = {} } = selectedCell.getData() || {};
setConfigData(configData);
setConfigSchema(safeParse(configSchema));
selectedCell.on('change:configSchema', (data: { configSchema: string }) => {
setConfigSchema(safeParse(data.configSchema));
});
return () => {
selectedCell.off('change:configSchema');
};
}, [selectedCell]);
// events
const onFieldValueChange = (key: string, value: any) => {
setConfigData(Object.assign({}, configData, { [key]: value }));
};
const onClickEdit = (): void => {
setMode(Mode.edit);
tempConfig = configData;
};
const onClickCancel = (): void => {
setMode(Mode.readOnly);
setConfigData(tempConfig);
};
const onClickSave = (): void => {
setMode(Mode.readOnly);
selectedCell.setData({ configData });
};
// no config schema
if (!configSchema || Object.keys(configSchema).length === 0) {
return (
<div className={styles.container}>
<Empty
className={styles.empty}
description={'请编辑投放配置schema'}
image={Empty.PRESENTED_IMAGE_SIMPLE}
/>
</div>
);
} else {
return (
<div className={styles.container}>
{Object.keys(configSchema).map((key, idx) => {
const { title, description, type } = configSchema[key];
const FieldComponent = Helper.getFieldComponent(type);
return (
<FieldComponent
key={idx}
name={key}
title={title}
value={configData[key]}
description={description}
disabled={mode === Mode.readOnly}
onValueChange={(value: any) => onFieldValueChange(key, value)}
/>
);
})}
{mode === Mode.readOnly ? (
<div className={styles.footContainer}>
<Button block onClick={onClickEdit}>
</Button>
</div>
) : (
<div className={styles.footContainer}>
<Button onClick={onClickCancel}></Button>
<Button className={styles.saveBtn} type={'primary'} onClick={onClickSave}>
</Button>
</div>
)}
</div>
);
}
};
const Helper = {
getFieldComponent(type: string) {
if (type === 'boolean') {
return Checkbox;
} else {
return Input;
}
},
};
export default Config;
Loading…
Cancel
Save