Browse Source

feat: add configuration button for npm registry

master
Hans Chan 4 years ago
parent
commit
9ec849b5ad
  1. 34
      packages/core/src/api/index.ts
  2. 2
      packages/core/src/components/console/index.tsx
  3. 32
      packages/core/src/mods/header/configuration/editModal.tsx
  4. 10
      packages/core/src/mods/header/configuration/index.module.less
  5. 35
      packages/core/src/mods/header/configuration/index.tsx
  6. 16
      packages/core/src/mods/header/connectStatus/index.module.less
  7. 74
      packages/core/src/mods/header/connectStatus/index.tsx
  8. 55
      packages/core/src/mods/header/index.tsx

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

@ -1,9 +1,11 @@
import axios from 'axios'; import axios from 'axios';
import { message } from 'antd'; import { message } from 'antd';
const LOCAL_CONFIG_KEY = 'IMOVE:LOCAL_CONFIG_KEY';
export interface ILocalConfig { export interface ILocalConfig {
ip: string; ip: string;
port: string; port: string;
npmRegistry: string;
} }
export enum ActionType { export enum ActionType {
@ -52,17 +54,38 @@ const request = (function () {
}; };
})(); })();
export const localConfig: ILocalConfig = { /**
ip: '127.0.0.1', * get local config data (saved in localStorage)
port: '3500', * @returns local config
*/
export const getLocalConfig = (): ILocalConfig => {
const savedConfigString = localStorage.getItem(LOCAL_CONFIG_KEY) || '';
let savedConfig = {} as ILocalConfig;
try {
savedConfig = JSON.parse(savedConfigString);
} catch (e) {}
return {
ip: savedConfig.ip || '127.0.0.1',
port: savedConfig.port || '3500',
npmRegistry: savedConfig.npmRegistry || 'https://registry.npm.taobao.org',
};
}; };
// export const localConfig: ILocalConfig = getLocalConfig();
/**
* get local config data (saved in localStorage)
*/
export const updateLocalConfig = (config: ILocalConfig) => { export const updateLocalConfig = (config: ILocalConfig) => {
localConfig.ip = config.ip || localConfig.ip; const savedConfig = getLocalConfig();
localConfig.port = config.port || localConfig.port; savedConfig.ip = config.ip || savedConfig.ip;
savedConfig.port = config.port || savedConfig.port;
savedConfig.npmRegistry = config.npmRegistry || savedConfig.npmRegistry;
localStorage.setItem(LOCAL_CONFIG_KEY, JSON.stringify(savedConfig));
}; };
export const localConnect = () => { export const localConnect = () => {
const localConfig = getLocalConfig();
return fetch(`http://${localConfig.ip}:${localConfig.port}/api/connect`, { return fetch(`http://${localConfig.ip}:${localConfig.port}/api/connect`, {
method: 'GET', method: 'GET',
headers: { 'content-type': 'application/json' }, headers: { 'content-type': 'application/json' },
@ -70,6 +93,7 @@ export const localConnect = () => {
}; };
export const localSave = (data: any) => { export const localSave = (data: any) => {
const localConfig = getLocalConfig();
fetch(`http://${localConfig.ip}:${localConfig.port}/api/save`, { fetch(`http://${localConfig.ip}:${localConfig.port}/api/save`, {
method: 'POST', method: 'POST',
headers: { 'content-type': 'application/json' }, headers: { 'content-type': 'application/json' },

2
packages/core/src/components/console/index.tsx

@ -93,7 +93,7 @@ const LogLine: React.FC<ILog> = (props) => {
const renderLink = (data: string) => { const renderLink = (data: string) => {
return ( return (
<a href={data} target={'_blank'}> <a href={data} target={'_blank'} rel="noreferrer">
{data} {data}
</a> </a>
); );

32
packages/core/src/mods/header/connectStatus/editModal.tsx → packages/core/src/mods/header/configuration/editModal.tsx

@ -1,5 +1,4 @@
import React, { useRef } from 'react'; import React, { useRef } from 'react';
import 'antd/es/form/style'; import 'antd/es/form/style';
import 'antd/es/modal/style'; import 'antd/es/modal/style';
import 'antd/es/input/style'; import 'antd/es/input/style';
@ -7,10 +6,12 @@ import styles from './index.module.less';
import { Input, Form, Modal } from 'antd'; import { Input, Form, Modal } from 'antd';
import { FormInstance } from 'antd/lib/form'; import { FormInstance } from 'antd/lib/form';
import { ILocalConfig, localConfig, updateLocalConfig } from '../../../api'; import { ILocalConfig, getLocalConfig, updateLocalConfig } from '../../../api';
const PORT_REGEX = /^\d{1,5}$/; const PORT_REGEX = /^\d{1,5}$/;
const IP_REGEX = /^(localhost)|(((2(5[0-5]|[0-4]\d))|[01]?\d{1,2})(\.((2(5[0-5]|[0-4]\d))|[01]?\d{1,2})){3})$/; const IP_REGEX = /^(localhost)|(((2(5[0-5]|[0-4]\d))|[01]?\d{1,2})(\.((2(5[0-5]|[0-4]\d))|[01]?\d{1,2})){3})$/;
const NPM_REGISTRY_REGEX = /^https?:\/\//; // 简单 URL 检查
const makeRules = (regex: RegExp, errTip: string) => { const makeRules = (regex: RegExp, errTip: string) => {
return [ return [
{ required: true, message: '不能为空!' }, { required: true, message: '不能为空!' },
@ -27,13 +28,13 @@ interface IEditorModalProps {
onCancel: () => void; onCancel: () => void;
} }
const EditModal: React.FC<IEditorModalProps> = (props) => { const EditModal: React.FC<IEditorModalProps> = (props: IEditorModalProps) => {
const { visible, onOk, onCancel } = props; const { visible, onOk, onCancel } = props;
const formRef = useRef<FormInstance>(null); const formRef = useRef<FormInstance>(null);
// events // events
const onClickOk = (): void => { const onClickOk = (): void => {
formRef.current && if (formRef.current) {
formRef.current formRef.current
.validateFields() .validateFields()
.then((formValues) => { .then((formValues) => {
@ -43,6 +44,7 @@ const EditModal: React.FC<IEditorModalProps> = (props) => {
.catch((error) => { .catch((error) => {
console.log(error.message); console.log(error.message);
}); });
}
}; };
return ( return (
@ -51,22 +53,34 @@ const EditModal: React.FC<IEditorModalProps> = (props) => {
visible={visible} visible={visible}
okText={'保存'} okText={'保存'}
cancelText={'取消'} cancelText={'取消'}
title={'本地连接配置'} title={'配置'}
onOk={onClickOk} onOk={onClickOk}
onCancel={onCancel} onCancel={onCancel}
> >
<Form ref={formRef} labelCol={{ span: 4 }} initialValues={localConfig}> <Form
ref={formRef}
labelCol={{ span: 6 }}
labelAlign="right"
initialValues={getLocalConfig()}
>
<Form.Item <Form.Item
label={'ip'} label={'本地连接 IP'}
name={'ip'} name={'ip'}
rules={makeRules(IP_REGEX, 'IP格式不合法')} rules={makeRules(IP_REGEX, 'IP格式不合法')}
> >
<Input /> <Input />
</Form.Item> </Form.Item>
<Form.Item <Form.Item
label={'port'} label={'本地连接端口'}
name={'port'} name={'port'}
rules={makeRules(PORT_REGEX, 'port格式不合法')} rules={makeRules(PORT_REGEX, '端口格式不合法')}
>
<Input />
</Form.Item>
<Form.Item
label={'NPM 源'}
name={'npmRegistry'}
rules={makeRules(NPM_REGISTRY_REGEX, 'NPM 源地址不合法')}
> >
<Input /> <Input />
</Form.Item> </Form.Item>

10
packages/core/src/mods/header/configuration/index.module.less

@ -0,0 +1,10 @@
.editModal {
width: 450px;
:global {
.ant-form-item:last-child {
margin-bottom: 0;
}
}
}

35
packages/core/src/mods/header/configuration/index.tsx

@ -0,0 +1,35 @@
import React, { useState, useCallback } from 'react';
import { Button } from 'antd';
import { SettingOutlined } from '@ant-design/icons';
import EditModal from './editModal';
interface IProps {
confirmToSync: () => Promise<void>;
}
const Configuration: React.FC<IProps> = (props: IProps) => {
const [visible, setVisible] = useState<boolean>(false);
// events
const onOpenEditModal = useCallback(() => {
setVisible(true);
}, [setVisible]);
const onCloseEditModal = useCallback(() => {
setVisible(false);
}, []);
const onOk = useCallback(() => {
onCloseEditModal();
props.confirmToSync();
}, [onCloseEditModal]);
return (
<div>
<Button size="small" type="text" onClick={onOpenEditModal}>
<SettingOutlined />
</Button>
<EditModal visible={visible} onOk={onOk} onCancel={onCloseEditModal} />
</div>
);
};
export default Configuration;

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

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

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

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

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

@ -1,19 +1,60 @@
import React from 'react'; import React, { useState, useCallback } from 'react';
import styles from './index.module.less'; import styles from './index.module.less';
import { Graph } from '@antv/x6'; import { Graph } from '@antv/x6';
import { Modal, message } from 'antd';
import Guide from './guide'; import Guide from './guide';
import Export from './export'; import Export from './export';
import ImportDSL from './importDSL'; import ImportDSL from './importDSL';
import ConnectStatus from './connectStatus'; import ConnectStatus, { Status } from './connectStatus';
import Configuration from './configuration';
import { localConnect } from '../../api';
interface IProps { interface IProps {
flowChart: Graph; flowChart: Graph;
} }
const Header: React.FC<IProps> = (props) => { const Header: React.FC<IProps> = (props: IProps) => {
const { flowChart } = props; const { flowChart } = props;
const [projectName, setProjectName] = useState<string>('');
const [status, setStatus] = useState<Status>(Status.disconnected);
// network
const syncLocal = useCallback(() => {
return localConnect()
.then((res) => res.json())
.then((data = {}) => {
console.log(data);
setStatus(Status.connected);
setProjectName(data.projectName);
return data;
})
.catch((error) => {
console.log(error);
setStatus(Status.disconnected);
console.log('connect local failed, the error is:', error.message);
});
}, [setStatus, setProjectName]);
const confirmToSync = useCallback(() => {
return syncLocal().then((data) => {
const { dsl } = data || {};
if (dsl) {
Modal.confirm({
title: '本地连接成功,是否将数据同步至当前项目?',
onOk() {
try {
flowChart.fromJSON(dsl);
} catch (error) {
message.error('同步失败!');
}
},
});
}
});
}, [syncLocal, flowChart]);
return ( return (
<div className={styles.container}> <div className={styles.container}>
<a href="https://github.com/imgcook/imove"> <a href="https://github.com/imgcook/imove">
@ -23,7 +64,13 @@ const Header: React.FC<IProps> = (props) => {
<Guide /> <Guide />
<Export flowChart={flowChart} /> <Export flowChart={flowChart} />
<ImportDSL flowChart={flowChart} /> <ImportDSL flowChart={flowChart} />
<ConnectStatus flowChart={flowChart} /> <ConnectStatus
status={status}
projectName={projectName}
syncLocal={syncLocal}
confirmToSync={confirmToSync}
/>
<Configuration confirmToSync={confirmToSync} />
</div> </div>
</div> </div>
); );

Loading…
Cancel
Save