Browse Source

feat: local debug

master
smallstonesk 4 years ago
parent
commit
4d541a85a3
  1. 11
      packages/cli/src/cmd/dev/index.js
  2. 2
      packages/cli/src/utils/server/index.js
  3. 29
      packages/core/src/api/index.ts
  4. 11
      packages/core/src/common/shortcuts.ts
  5. 2
      packages/core/src/index.tsx
  6. 64
      packages/core/src/mods/header/connectStatus/editModal.tsx
  7. 22
      packages/core/src/mods/header/connectStatus/index.module.less
  8. 100
      packages/core/src/mods/header/connectStatus/index.tsx
  9. 3
      packages/core/src/mods/header/index.module.less
  10. 10
      packages/core/src/mods/header/index.tsx

11
packages/cli/src/cmd/dev/index.js

@ -6,7 +6,10 @@ const simplifyDSL = require('./simplifyDSL');
const extractCodes = require('./extractCodes');
const {createServer} = require('../../utils/server');
const noop = () => {};
const TPL_PATH = path.join(__dirname, './template');
const CACHE_PATH = path.join(process.cwd(), './.cache');
const CACHE_DSL_FILE = path.join(CACHE_PATH, 'imove.dsl.json');
class Dev extends Base {
@ -30,6 +33,7 @@ class Dev extends Base {
await extractCodes(dsl, outputPath);
await fs.copy(TPL_PATH, outputPath);
await mergePkg(dsl, this.projectPath);
await fs.outputFile(CACHE_DSL_FILE, JSON.stringify(dsl, null, 2));
res.status(200).json({isCompiled: true}).end();
console.log('compile successfully!');
} catch(err) {
@ -38,9 +42,16 @@ class Dev extends Base {
}
}
connect = async (req, res) => {
const {projectName} = this.config;
const dsl = await fs.readJson(CACHE_DSL_FILE).catch(noop);
res.status(200).json({projectName, dsl}).end();
}
run() {
const app = createServer();
app.post('/api/save', this.save);
app.get('/api/connect', this.connect);
}
}

2
packages/cli/src/utils/server/index.js

@ -3,7 +3,7 @@ const cors = require('cors');
const express = require('express');
const bodyParser = require('body-parser');
const createServer = (port = 3456) => {
const createServer = (port = 3500) => {
const app = express();
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({extended: false}));

29
packages/core/src/api/index.ts

@ -0,0 +1,29 @@
export interface ILocalConfig {
ip: string;
port: string;
}
export const localConfig: ILocalConfig = {
ip: '127.0.0.1',
port: '3500'
};
export const updateLocalConfig = (config: ILocalConfig) => {
localConfig.ip = config.ip || localConfig.ip;
localConfig.port = config.port || localConfig.port;
}
export const localConnect = () => {
return fetch(`http://${localConfig.ip}:${localConfig.port}/api/connect`, {
method: 'GET',
headers: {'content-type': 'application/json'}
});
}
export const localSave = (data: any) => {
fetch(`http://${localConfig.ip}:${localConfig.port}/api/save`, {
method: 'POST',
headers: {'content-type': 'application/json'},
body: JSON.stringify(data)
});
}

11
packages/core/src/common/shortcuts.ts

@ -1,5 +1,6 @@
import {Graph} from '@antv/x6';
import {safeGet} from '../utils';
import {localSave} from '../api';
interface Shortcut {
keys: string | string[];
@ -14,15 +15,7 @@ const shortcuts: {[key: string]: Shortcut} = {
save: {
keys: `meta + s`,
handler(flowChart: Graph) {
// TODO: save data
console.log(JSON.stringify(flowChart.toJSON(), null, 2));
fetch('http://localhost:3456/api/save', {
method: 'POST',
headers: {
'content-type': 'application/json'
},
body: JSON.stringify({dsl: flowChart.toJSON()})
});
localSave({dsl: flowChart.toJSON()});
return false;
}
},

2
packages/core/src/index.tsx

@ -33,7 +33,7 @@ const Core: React.FC<IProps> = props => {
return (
<div className={styles.container}>
<Header title={'iMove'}/>
<Header flowChart={flowChart}/>
<div className={styles.body}>
<SideBar flowChart={flowChart}/>
<div className={styles.full}>

64
packages/core/src/mods/header/connectStatus/editModal.tsx

@ -0,0 +1,64 @@
import React, {useState, useEffect, useRef} from 'react';
import 'antd/es/form/style';
import 'antd/es/modal/style';
import 'antd/es/input/style';
import styles from './index.module.less';
import {Input, Form, Modal} from 'antd';
import {FormInstance} from 'antd/lib/form';
import {ILocalConfig, localConfig, updateLocalConfig} from '../../../api';
const PORT_REGEX = /^\d{1,5}$/;
const IP_REGEX = /^(localhost)|(((2(5[0-5]|[0-4]\d))|[0-1]?\d{1,2})(\.((2(5[0-5]|[0-4]\d))|[0-1]?\d{1,2})){3})$/;
const makeRules = (regex: RegExp, errTip: string) => {
return [
{required: true, message: '不能为空!'},
{validator: (_: any, val: string) => regex.test(val) ? Promise.resolve() : Promise.reject(errTip)}
];
}
interface IEditorModalProps {
visible: boolean;
onOk: () => void;
onCancel: () => void;
}
const EditModal: React.FC<IEditorModalProps> = props => {
const {visible, onOk, onCancel} = props;
const formRef = useRef<FormInstance>(null);
// events
const onClickOk = (): void => {
formRef.current && formRef.current.validateFields().then((formValues) => {
onOk();
updateLocalConfig(formValues as ILocalConfig);
}).catch(err => {
console.log(err.message);
});
};
return (
<Modal
className={styles.editModal}
visible={visible}
okText={'保存'}
cancelText={'取消'}
title={'本地连接配置'}
onOk={onClickOk}
onCancel={onCancel}
>
<Form ref={formRef} labelCol={{span: 4}} initialValues={localConfig}>
<Form.Item label={'ip'} name={'ip'} rules={makeRules(IP_REGEX, 'IP格式不合法')}>
<Input/>
</Form.Item>
<Form.Item label={'port'} name={'port'} rules={makeRules(PORT_REGEX, 'port格式不合法')}>
<Input/>
</Form.Item>
</Form>
</Modal>
);
};
export default EditModal;

22
packages/core/src/mods/header/connectStatus/index.module.less

@ -0,0 +1,22 @@
.container {
display: flex;
flex-direction: row;
align-items: center;
margin-right: 20px;
.error {
cursor: pointer;
}
}
.editModal {
width: 450px;
:global {
.ant-form-item:last-child {
margin-bottom: 0;
}
}
}

100
packages/core/src/mods/header/connectStatus/index.tsx

@ -0,0 +1,100 @@
import React, {useState, useEffect} from 'react';
import 'antd/es/tag/style';
import 'antd/es/modal/style';
import 'antd/es/message/style';
import styles from './index.module.less';
import {Graph} from '@antv/x6';
import EditModal from './editModal';
import {Tag, Modal, message} from 'antd';
import {localConnect} from '../../../api';
enum Status {
connected = 'success',
disconnected = 'error'
}
interface IProps {
flowChart: Graph | undefined;
}
const PULSE_RATE = 10 * 1000; // try to connect to local servet per 10 seconds
const ConnectStatus: React.FC<IProps> = (props) => {
const {flowChart} = props;
const [visible, setVisible] = useState<boolean>(false);
const [projectName, setProjectName] = useState<string>('');
const [status, setStatus] = useState<Status>(Status.disconnected);
// life
useEffect(() => {
confirmToSync();
const timer = setInterval(syncLocal, PULSE_RATE);
return () => clearInterval(timer);
}, []);
// network
const syncLocal = () => {
return localConnect().then(res => res.json()).then((data = {}) => {
const {projectName} = data;
setStatus(Status.connected);
setProjectName(projectName);
return data;
}).catch((err) => {
setStatus(Status.disconnected);
console.log('connect local failed, the error is:', err.message);
});
};
const confirmToSync = () => {
syncLocal().then(data => {
const {dsl} = data;
dsl && Modal.confirm({
title: '本地连接成功,是否将数据同步至当前项目?',
onOk() {
try {
console.log(props.flowChart);
flowChart && flowChart.fromJSON(dsl);
} catch(err) {
message.error('同步失败!');
}
}
});
});
};
// events
const onOpenEditModal = (): void => {
setVisible(true);
};
const onCloseEditModal = (): void => {
setVisible(false);
};
const onOk = (): void => {
onCloseEditModal();
confirmToSync();
};
return (
<div className={styles.container}>
{status === Status.connected && (
<Tag color={'success'}>
{[projectName, '本地连接成功'].join(' ')}
</Tag>
)}
{status === Status.disconnected && (
<Tag className={styles.error} color={'error'} onClick={onOpenEditModal}>
</Tag>
)}
<EditModal
visible={visible}
onOk={onOk}
onCancel={onCloseEditModal}
/>
</div>
);
};
export default ConnectStatus;

3
packages/core/src/mods/header/index.module.less

@ -1,4 +1,7 @@
.container {
display: flex;
flex-direction: row;
justify-content: space-between;
height: 48px;
line-height: 48px;
background-color: #FEFEFE;

10
packages/core/src/mods/header/index.tsx

@ -2,15 +2,19 @@ import React from 'react';
import styles from './index.module.less';
import {Graph} from '@antv/x6';
import ConnectStatus from './connectStatus';
interface IProps {
title?: string;
flowChart: Graph | undefined;
}
const Header: React.FC<IProps> = props => {
const {title = 'iMove'} = props;
const {flowChart} = props;
return (
<div className={styles.container}>
<span className={styles.titleText}>{title}</span>
<span className={styles.titleText}>iMove</span>
<ConnectStatus flowChart={flowChart}/>
</div>
);
}

Loading…
Cancel
Save