summaryrefslogtreecommitdiff
path: root/src/components
diff options
context:
space:
mode:
authorhaishanh <[email protected]>2018-11-05 18:32:16 +0800
committerHaishan <[email protected]>2018-11-06 22:37:21 +0800
commit91ecdaa5dd1a65ff0ae944896945c0fe4bc23574 (patch)
tree9f7f476672fae91eca5df9e0ae021b45df950679 /src/components
parent1adaedcbc551f6beee4e9e0b9fbd94197ebdffe5 (diff)
update: convert more components to function ones
includes Input, Config, SideBard. also removed react-redux Provider
Diffstat (limited to 'src/components')
-rw-r--r--src/components/APIConfig.js5
-rw-r--r--src/components/Button.js31
-rw-r--r--src/components/Config.js94
-rw-r--r--src/components/ContentHeader.js26
-rw-r--r--src/components/Icon.js2
-rw-r--r--src/components/Input.js65
-rw-r--r--src/components/Modal.js48
-rw-r--r--src/components/ProxyLatency.js55
-rw-r--r--src/components/Root.js29
-rw-r--r--src/components/SideBar.js8
-rw-r--r--src/components/Switch.js50
-rw-r--r--src/components/ToggleSwitch.js112
12 files changed, 238 insertions, 287 deletions
diff --git a/src/components/APIConfig.js b/src/components/APIConfig.js
index 9654bce..5746040 100644
--- a/src/components/APIConfig.js
+++ b/src/components/APIConfig.js
@@ -27,12 +27,9 @@ function APIConfig2() {
const handleInputOnChange = e => {
const target = e.target;
const { name } = target;
- let value = target.value.trim();
-
- if (value === '') return;
+ let value = target.value;
switch (name) {
case 'port':
- if (Number(value) < 0 || Number(value) > 65535) return;
setPort(value);
break;
case 'hostname':
diff --git a/src/components/Button.js b/src/components/Button.js
index 34fe5fe..eb50ffd 100644
--- a/src/components/Button.js
+++ b/src/components/Button.js
@@ -1,25 +1,20 @@
-import React, { Component } from 'react';
+import React from 'react';
import PropTypes from 'prop-types';
import s0 from 'c/Button.module.scss';
+const noop = () => {};
-class Button extends Component {
- static propTypes = {
- label: PropTypes.string.isRequired,
- onClick: PropTypes.func
- };
+const Button = React.memo(function Button({ label, onClick = noop }) {
+ return (
+ <button className={s0.btn} onClick={onClick}>
+ {label}
+ </button>
+ );
+});
- static defaultProps = {
- onClick: () => {}
- };
-
- render() {
- return (
- <button className={s0.btn} onClick={this.props.onClick}>
- {this.props.label}
- </button>
- );
- }
-}
+Button.propTypes = {
+ label: PropTypes.string.isRequired,
+ onClick: PropTypes.func
+};
export default Button;
diff --git a/src/components/Config.js b/src/components/Config.js
index ca6f9e4..e8fbfba 100644
--- a/src/components/Config.js
+++ b/src/components/Config.js
@@ -1,4 +1,5 @@
-import React, { useEffect } from 'react';
+import React, { useEffect, useState } from 'react';
+import PropTypes from 'prop-types';
import { useComponentState, useActions } from 'm/store';
import { getConfigs, fetchConfigs, updateConfigs } from 'd/configs';
@@ -50,30 +51,76 @@ const actions = {
const mapStateToProps = s => ({ configs: getConfigs(s) });
-export default function Config2() {
- const { fetchConfigs, updateConfigs } = useActions(actions);
+export default function ConfigContainer() {
+ const { fetchConfigs } = useActions(actions);
const { configs } = useComponentState(mapStateToProps);
useEffect(() => {
fetchConfigs();
}, []);
+ return <Config configs={configs} />;
+}
- function handleInputOnChange(ev) {
- const target = ev.target;
- const { name } = target;
+function Config({ configs }) {
+ const { updateConfigs } = useActions(actions);
+ // configState to track component internal state
+ // prevConfigs to track external props.configs
+ const [configState, _setConfigState] = useState(configs);
+ const [prevConfigs, setPrevConfigs] = useState(configs);
+ // equivalent of getDerivedStateFromProps
+ if (prevConfigs !== configs) {
+ setPrevConfigs(configs);
+ _setConfigState(configs);
+ }
- let value;
- switch (target.type) {
- case 'checkbox':
+ const setConfigState = (name, val) => {
+ _setConfigState({
+ ...configState,
+ [name]: val
+ });
+ };
+
+ function handleInputOnChange(e) {
+ const target = e.target;
+ const { name } = target;
+ let { value } = target;
+ switch (target.name) {
+ case 'allow-lan':
value = target.checked;
+ setConfigState(name, value);
+ updateConfigs({ [name]: value });
+ break;
+ case 'mode':
+ case 'log-level':
+ setConfigState(name, value);
+ updateConfigs({ [name]: value });
break;
- case 'number':
- value = Number(target.value);
+ case 'redir-port':
+ case 'socket-port':
+ case 'port':
+ if (target.value !== '') {
+ const num = parseInt(target.value, 10);
+ if (num < 0 || num > 65535) return;
+ }
+ setConfigState(name, value);
break;
default:
- value = target.value;
+ return;
+ }
+ }
+
+ function handleInputOnBlur(e) {
+ const target = e.target;
+ const { name, value } = target;
+ switch (name) {
+ case 'port':
+ case 'socket-port':
+ case 'redir-port': {
+ const num = parseInt(value, 10);
+ if (num < 0 || num > 65535) return;
+ updateConfigs({ [name]: num });
+ break;
+ }
}
- if (configs[name] === value) return;
- updateConfigs({ [name]: value });
}
return (
@@ -84,8 +131,9 @@ export default function Config2() {
<div className={s0.label}>HTTP Proxy Port</div>
<Input
name="port"
- value={configs.port}
+ value={configState.port}
onChange={handleInputOnChange}
+ onBlur={handleInputOnBlur}
/>
</div>
@@ -93,8 +141,9 @@ export default function Config2() {
<div className={s0.label}>SOCKS5 Proxy Port</div>
<Input
name="socket-port"
- value={configs['socket-port']}
+ value={configState['socket-port']}
onChange={handleInputOnChange}
+ onBlur={handleInputOnBlur}
/>
</div>
@@ -102,8 +151,9 @@ export default function Config2() {
<div className={s0.label}>Redir Port</div>
<Input
name="redir-port"
- value={configs['redir-port']}
+ value={configState['redir-port']}
onChange={handleInputOnChange}
+ onBlur={handleInputOnBlur}
/>
</div>
@@ -111,7 +161,7 @@ export default function Config2() {
<div className={s0.label}>Allow LAN</div>
<Switch
name="allow-lan"
- checked={configs['allow-lan']}
+ checked={configState['allow-lan']}
onChange={handleInputOnChange}
/>
</div>
@@ -121,7 +171,7 @@ export default function Config2() {
<ToggleSwitch
options={optionsRule}
name="mode"
- value={configs.mode}
+ value={configState.mode}
onChange={handleInputOnChange}
/>
</div>
@@ -131,7 +181,7 @@ export default function Config2() {
<ToggleSwitch
options={optionsLogLevel}
name="log-level"
- value={configs['log-level']}
+ value={configState['log-level']}
onChange={handleInputOnChange}
/>
</div>
@@ -139,3 +189,7 @@ export default function Config2() {
</div>
);
}
+
+Config.propTypes = {
+ configs: PropTypes.object
+};
diff --git a/src/components/ContentHeader.js b/src/components/ContentHeader.js
index 3e1370b..24d887b 100644
--- a/src/components/ContentHeader.js
+++ b/src/components/ContentHeader.js
@@ -1,20 +1,18 @@
-import React, { Component } from 'react';
+import React from 'react';
import PropTypes from 'prop-types';
import s0 from './ContentHeader.module.scss';
-class ContentHeader extends Component {
- static propTypes = {
- title: PropTypes.string.isRequired
- };
-
- render() {
- return (
- <div className={s0.root}>
- <h1 className={s0.h1}>{this.props.title}</h1>
- </div>
- );
- }
+function ContentHeader({ title }) {
+ return (
+ <div className={s0.root}>
+ <h1 className={s0.h1}>{title}</h1>
+ </div>
+ );
}
-export default ContentHeader;
+ContentHeader.propTypes = {
+ title: PropTypes.string.isRequired
+};
+
+export default React.memo(ContentHeader);
diff --git a/src/components/Icon.js b/src/components/Icon.js
index a9a6393..806f455 100644
--- a/src/components/Icon.js
+++ b/src/components/Icon.js
@@ -19,4 +19,4 @@ Icon.propTypes = {
className: PropTypes.string
};
-export default Icon;
+export default React.memo(Icon);
diff --git a/src/components/Input.js b/src/components/Input.js
index 1d66214..f621b43 100644
--- a/src/components/Input.js
+++ b/src/components/Input.js
@@ -1,61 +1,16 @@
-import React, { Component } from 'react';
+import React from 'react';
import PropTypes from 'prop-types';
import s0 from './Input.module.scss';
-class Input extends Component {
- static propTypes = {
- value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
- type: PropTypes.string,
- onChange: PropTypes.func,
- name: PropTypes.string,
- placeholder: PropTypes.string
- };
-
- static defaultProps = {
- type: 'number',
- placeholder: 'Please input'
- };
-
- state = {
- value: this.props.value,
- lastValueFromProps: this.props.value
- };
-
- static getDerivedStateFromProps(props, state) {
- if (props.value !== state.lastValueFromProps) {
- return {
- lastValueFromProps: props.value,
- value: props.value
- };
- }
- return null;
- }
-
- handleInputOnChange = e => {
- const value = e.target.value;
- const int = parseInt(value, 10);
- if (int < 0 || int > 65535) return;
- this.setState({ value: e.target.value });
- };
-
- render() {
- const { onChange, name, type, placeholder } = this.props;
- const { value } = this.state;
- return (
- <div>
- <input
- className={s0.input}
- name={name}
- type={type}
- placeholder={placeholder}
- value={value}
- onBlur={onChange}
- onChange={this.handleInputOnChange}
- />
- </div>
- );
- }
+export default function Input(props) {
+ return <input className={s0.input} {...props} />;
}
-export default Input;
+Input.propTypes = {
+ value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
+ type: PropTypes.string,
+ onChange: PropTypes.func,
+ name: PropTypes.string,
+ placeholder: PropTypes.string
+};
diff --git a/src/components/Modal.js b/src/components/Modal.js
index 2bb6c09..19accdc 100644
--- a/src/components/Modal.js
+++ b/src/components/Modal.js
@@ -1,34 +1,28 @@
-import React, { Component } from 'react';
+import React from 'react';
import PropTypes from 'prop-types';
import Modal from 'react-modal';
import s0 from './Modal.module.scss';
-class ModalAPIConfig extends Component {
- static propTypes = {
- isOpen: PropTypes.bool.isRequired,
- onRequestClose: PropTypes.func.isRequired
- };
-
- handleClick = e => {
- e.preventDefault();
- };
-
- render() {
- const { isOpen, onRequestClose, children, ...rest } = this.props;
- return (
- <Modal
- isOpen={isOpen}
- onRequestClose={onRequestClose}
- contentLabel="test"
- className={s0.content}
- overlayClassName={s0.overlay}
- {...rest}
- >
- {children}
- </Modal>
- );
- }
+function ModalAPIConfig({ isOpen, onRequestClose, children, ...otherProps }) {
+ return (
+ <Modal
+ isOpen={isOpen}
+ onRequestClose={onRequestClose}
+ contentLabel="API-Config"
+ className={s0.content}
+ overlayClassName={s0.overlay}
+ {...otherProps}
+ >
+ {children}
+ </Modal>
+ );
}
-export default ModalAPIConfig;
+ModalAPIConfig.propTypes = {
+ isOpen: PropTypes.bool.isRequired,
+ onRequestClose: PropTypes.func.isRequired,
+ children: PropTypes.node.isRequired
+};
+
+export default React.memo(ModalAPIConfig);
diff --git a/src/components/ProxyLatency.js b/src/components/ProxyLatency.js
index 5d1aaee..96dd26f 100644
--- a/src/components/ProxyLatency.js
+++ b/src/components/ProxyLatency.js
@@ -1,4 +1,4 @@
-import React, { Component } from 'react';
+import React, { useMemo } from 'react';
import PropTypes from 'prop-types';
import s0 from './ProxyLatency.module.scss';
@@ -10,35 +10,30 @@ const colorMap = {
na: '#909399'
};
-class C extends Component {
- static propTypes = {
- latency: PropTypes.shape({
- number: PropTypes.number,
- error: PropTypes.string
- })
- };
-
- render() {
- const { latency } = this.props;
- const { number, error } = latency;
- let bg;
-
- if (error !== '') {
- bg = colorMap.na;
- } else if (number < 200) {
- bg = colorMap.good;
- } else if (number < 400) {
- bg = colorMap.normal;
- } else {
- bg = colorMap.bad;
- }
-
- return (
- <span className={s0.proxyLatency} style={{ color: bg }}>
- {error !== '' ? <span>{error}</span> : <span>{number} ms</span>}
- </span>
- );
+function getLabelColor(number, error) {
+ if (error !== '') {
+ return colorMap.na;
+ } else if (number < 200) {
+ return colorMap.good;
+ } else if (number < 400) {
+ return colorMap.normal;
}
+ return colorMap.bad;
}
-export default C;
+export default function ProxyLatency({ latency }) {
+ const { number, error } = latency;
+ const color = useMemo(() => getLabelColor(number, error), [number, error]);
+ return (
+ <span className={s0.proxyLatency} style={{ color }}>
+ {error !== '' ? <span>{error}</span> : <span>{number} ms</span>}
+ </span>
+ );
+}
+
+ProxyLatency.propTypes = {
+ latency: PropTypes.shape({
+ number: PropTypes.number,
+ error: PropTypes.string
+ })
+};
diff --git a/src/components/Root.js b/src/components/Root.js
index 96ae243..e9d9141 100644
--- a/src/components/Root.js
+++ b/src/components/Root.js
@@ -1,6 +1,5 @@
import React from 'react';
-import { Provider } from 'react-redux';
-import { Provider as StoreProvider } from 'm/store';
+import { Provider } from 'm/store';
import { HashRouter as Router, Route } from 'react-router-dom';
// import { hot } from 'react-hot-loader';
// import createHistory from 'history/createHashHistory';
@@ -26,21 +25,19 @@ window.store = store;
const Root = () => (
<Provider store={store}>
- <StoreProvider store={store}>
- <Router>
- <div className={s0.app}>
- <APIDiscovery />
- <Route path="/" component={SideBar} />
- <div className={s0.content}>
- <Route exact path="/" render={() => <Home />} />
- <Route exact path="/overview" render={() => <Home />} />
- <Route exact path="/configs" render={() => <Config />} />
- <Route exact path="/logs" render={() => <Logs />} />
- <Route exact path="/proxies" render={() => <Proxies />} />
- </div>
+ <Router>
+ <div className={s0.app}>
+ <APIDiscovery />
+ <Route path="/" render={() => <SideBar />} />
+ <div className={s0.content}>
+ <Route exact path="/" render={() => <Home />} />
+ <Route exact path="/overview" render={() => <Home />} />
+ <Route exact path="/configs" render={() => <Config />} />
+ <Route exact path="/logs" render={() => <Logs />} />
+ <Route exact path="/proxies" render={() => <Proxies />} />
</div>
- </Router>
- </StoreProvider>
+ </div>
+ </Router>
</Provider>
);
// <Route exact path="/__0" component={StyleGuide} />
diff --git a/src/components/SideBar.js b/src/components/SideBar.js
index b8cd61f..cee0960 100644
--- a/src/components/SideBar.js
+++ b/src/components/SideBar.js
@@ -12,14 +12,14 @@ import yacd from 's/yacd.svg';
import s from 'c/SideBar.module.scss';
-function SideBarRow({ iconId, labelText, to }) {
+const SideBarRow = React.memo(function SideBarRow({ iconId, labelText, to }) {
return (
<NavLink exact to={to} className={s.row} activeClassName={s.rowActive}>
<Icon id={iconId} width={28} height={28} />
<div className={s.label}>{labelText}</div>
</NavLink>
);
-}
+});
SideBarRow.propTypes = {
to: PropTypes.string.isRequired,
@@ -27,7 +27,7 @@ SideBarRow.propTypes = {
labelText: PropTypes.string
};
-export default function SideBar() {
+function SideBar() {
return (
<div className={s.root}>
<div className={s.logo}>
@@ -43,3 +43,5 @@ export default function SideBar() {
</div>
);
}
+
+export default React.memo(SideBar);
diff --git a/src/components/Switch.js b/src/components/Switch.js
index 5a2119a..f956b6d 100644
--- a/src/components/Switch.js
+++ b/src/components/Switch.js
@@ -1,35 +1,27 @@
-import React, { Component } from 'react';
+import React from 'react';
import PropTypes from 'prop-types';
import s0 from 'c/Switch.module.scss';
+const noop = () => {};
-class Switch extends Component {
- static propTypes = {
- checked: PropTypes.bool,
- onChange: PropTypes.func,
- name: PropTypes.string
- };
-
- static defaultProps = {
- checked: false,
- name: '',
- onChange: () => {}
- };
-
- render() {
- const { checked, onChange, name } = this.props;
- return (
- <div>
- <input
- type="checkbox"
- name={name}
- checked={checked}
- className={s0.switch}
- onChange={onChange}
- />
- </div>
- );
- }
+function Switch({ checked = false, onChange = noop, name = '' }) {
+ return (
+ <div>
+ <input
+ type="checkbox"
+ name={name}
+ checked={checked}
+ className={s0.switch}
+ onChange={onChange}
+ />
+ </div>
+ );
}
-export default Switch;
+Switch.propTypes = {
+ checked: PropTypes.bool,
+ onChange: PropTypes.func,
+ name: PropTypes.string
+};
+
+export default React.memo(Switch);
diff --git a/src/components/ToggleSwitch.js b/src/components/ToggleSwitch.js
index cdd4726..7303984 100644
--- a/src/components/ToggleSwitch.js
+++ b/src/components/ToggleSwitch.js
@@ -1,77 +1,49 @@
-import React, { Component } from 'react';
+import React, { useRef } from 'react';
import PropTypes from 'prop-types';
import s0 from 'c/ToggleSwitch.module.scss';
-class ToggleSwitch extends Component {
- static propTypes = {
- options: PropTypes.array,
- value: PropTypes.string,
- name: PropTypes.string,
- onChange: PropTypes.func
- };
-
- static defaultProps = {
- options: [
- {
- label: 'Global',
- value: 'Global'
- },
- {
- label: 'Rule',
- value: 'Rule'
- },
- {
- label: 'Direct',
- value: 'Direct'
- }
- ],
- value: 'Rule',
- name: 'rand0'
- };
-
- // handleRadioOnChange = ev => {
- // ev.preventDefault();
- // const value = ev.target.value;
- // if (this.state.value === value) return;
- // this.setState({ value });
- // };
-
- render() {
- const { options, name, value, onChange } = this.props;
- const w = (100 / options.length).toPrecision(3);
- return (
- <div>
- <div className={s0.ToggleSwitch}>
- {options.map((o, idx) => {
- if (value === o.value) this.idx = idx;
- const id = `${name}-${o.label}`;
- let className = idx === 0 ? '' : 'border-left';
- return (
- <label htmlFor={id} key={id} className={className}>
- <input
- id={id}
- name={name}
- type="radio"
- value={o.value}
- checked={value === o.value}
- onChange={onChange}
- />
- <div>{o.label}</div>
- </label>
- );
- })}
- <a
- className={s0.slider}
- style={{
- width: w + '%',
- left: this.idx * w + '%'
- }}
- />
- </div>
+function ToggleSwitch2({ options, value, name, onChange }) {
+ const idxRef = useRef(null);
+ const w = (100 / options.length).toPrecision(3);
+ return (
+ <div>
+ <div className={s0.ToggleSwitch}>
+ {options.map((o, idx) => {
+ if (value === o.value) idxRef.current = idx;
+ const id = `${name}-${o.label}`;
+ let className = idx === 0 ? '' : 'border-left';
+ return (
+ <label htmlFor={id} key={id} className={className}>
+ <input
+ id={id}
+ name={name}
+ type="radio"
+ value={o.value}
+ checked={value === o.value}
+ onChange={onChange}
+ />
+ <div>{o.label}</div>
+ </label>
+ );
+ })}
+ <a
+ className={s0.slider}
+ style={{
+ width: w + '%',
+ left: idxRef.current * w + '%'
+ }}
+ />
</div>
- );
- }
+ </div>
+ );
}
-export default ToggleSwitch;
+ToggleSwitch2.propTypes = {
+ options: PropTypes.array,
+ value: PropTypes.string,
+ name: PropTypes.string,
+ onChange: PropTypes.func
+};
+
+export default React.memo(ToggleSwitch2);