diff --git a/packages/cli/src/cmd/dev/index.js b/packages/cli/src/cmd/dev/index.js index 7d8e7d0..5419f9f 100644 --- a/packages/cli/src/cmd/dev/index.js +++ b/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); } } diff --git a/packages/cli/src/utils/server/index.js b/packages/cli/src/utils/server/index.js index a72a4c4..4f388a7 100644 --- a/packages/cli/src/utils/server/index.js +++ b/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})); diff --git a/packages/core/src/api/index.ts b/packages/core/src/api/index.ts new file mode 100644 index 0000000..68c735c --- /dev/null +++ b/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) + }); +} diff --git a/packages/core/src/common/shortcuts.ts b/packages/core/src/common/shortcuts.ts index af52ce1..f875d8c 100644 --- a/packages/core/src/common/shortcuts.ts +++ b/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; } }, diff --git a/packages/core/src/index.tsx b/packages/core/src/index.tsx index b30545f..85cfb64 100644 --- a/packages/core/src/index.tsx +++ b/packages/core/src/index.tsx @@ -33,7 +33,7 @@ const Core: React.FC = props => { return (
-
+
diff --git a/packages/core/src/mods/header/connectStatus/editModal.tsx b/packages/core/src/mods/header/connectStatus/editModal.tsx new file mode 100644 index 0000000..4897973 --- /dev/null +++ b/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 = props => { + + const {visible, onOk, onCancel} = props; + const formRef = useRef(null); + + // events + const onClickOk = (): void => { + formRef.current && formRef.current.validateFields().then((formValues) => { + onOk(); + updateLocalConfig(formValues as ILocalConfig); + }).catch(err => { + console.log(err.message); + }); + }; + + return ( + +
+ + + + + + +
+
+ ); +}; + +export default EditModal; diff --git a/packages/core/src/mods/header/connectStatus/index.module.less b/packages/core/src/mods/header/connectStatus/index.module.less new file mode 100644 index 0000000..5c40745 --- /dev/null +++ b/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; + } + } +} \ No newline at end of file diff --git a/packages/core/src/mods/header/connectStatus/index.tsx b/packages/core/src/mods/header/connectStatus/index.tsx new file mode 100644 index 0000000..d57b28f --- /dev/null +++ b/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 = (props) => { + + const {flowChart} = props; + const [visible, setVisible] = useState(false); + const [projectName, setProjectName] = useState(''); + const [status, setStatus] = useState(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 ( +
+ {status === Status.connected && ( + + {[projectName, '本地连接成功'].join(' ')} + + )} + {status === Status.disconnected && ( + + 本地连接失败 点击修改配置 + + )} + +
+ ); +}; + +export default ConnectStatus; diff --git a/packages/core/src/mods/header/index.module.less b/packages/core/src/mods/header/index.module.less index 320a117..91ff433 100644 --- a/packages/core/src/mods/header/index.module.less +++ b/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; diff --git a/packages/core/src/mods/header/index.tsx b/packages/core/src/mods/header/index.tsx index f527a8c..5cdf62a 100644 --- a/packages/core/src/mods/header/index.tsx +++ b/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 = props => { - const {title = 'iMove'} = props; + const {flowChart} = props; return (
- {title} + iMove +
); }