Simplify custom node URL (#1141)

* Simplify custom nodes to just be a URL, not a url + port.

* Allow modals to specify max width (#1142)
This commit is contained in:
William O'Beirne 2018-02-24 13:02:07 -05:00 committed by Daniel Ternyak
parent dc24a52e2c
commit b48617e95e
7 changed files with 55 additions and 57 deletions

View File

@ -20,6 +20,9 @@ interface Input {
name: string;
placeholder?: string;
type?: string;
autoComplete?: 'off';
onFocus?(): void;
onBlur?(): void;
}
interface OwnProps {
@ -40,7 +43,6 @@ interface StateProps {
interface State {
name: string;
url: string;
port: string;
network: string;
customNetworkId: string;
customNetworkUnit: string;
@ -56,7 +58,6 @@ class CustomNodeModal extends React.Component<Props, State> {
public state: State = {
name: '',
url: '',
port: '',
network: Object.keys(this.props.staticNetworks)[0],
customNetworkId: '',
customNetworkUnit: '',
@ -94,6 +95,7 @@ class CustomNodeModal extends React.Component<Props, State> {
isOpen={true}
buttons={buttons}
handleClose={handleClose}
maxWidth={580}
>
<div>
{isHttps && <div className="alert alert-warning small">{translate('NODE_Warning')}</div>}
@ -175,27 +177,14 @@ class CustomNodeModal extends React.Component<Props, State> {
</div>
)}
<hr />
<div className="row">
<div className="col-sm-9">
<div className="col-sm-12">
<label>URL</label>
{this.renderInput(
{
name: 'url',
placeholder: 'https://127.0.0.1/'
},
invalids
)}
</div>
<div className="col-sm-3">
<label>{translate('NODE_Port')}</label>
{this.renderInput(
{
name: 'port',
placeholder: '8545',
type: 'number'
placeholder: 'e.g. https://127.0.0.1:8545/',
autoComplete: 'off'
},
invalids
)}
@ -248,6 +237,7 @@ class CustomNodeModal extends React.Component<Props, State> {
})}
value={this.state[input.name]}
onChange={this.handleChange}
autoComplete="off"
{...input}
/>
);
@ -256,7 +246,6 @@ class CustomNodeModal extends React.Component<Props, State> {
private getInvalids(): { [key: string]: boolean } {
const {
url,
port,
hasAuth,
username,
password,
@ -265,7 +254,7 @@ class CustomNodeModal extends React.Component<Props, State> {
customNetworkUnit,
customNetworkChainId
} = this.state;
const required: (keyof State)[] = ['name', 'url', 'port', 'network'];
const required: (keyof State)[] = ['name', 'url', 'network'];
const invalids: { [key: string]: boolean } = {};
// Required fields
@ -275,17 +264,12 @@ class CustomNodeModal extends React.Component<Props, State> {
}
});
// Somewhat valid URL, not 100% fool-proof
if (!/https?\:\/\/\w+/i.test(url)) {
// Parse the URL, and make sure what they typed isn't parsed as relative.
// Not a perfect regex, just checks for protocol + any char
if (!/^https?:\/\/.+/i.test(url)) {
invalids.url = true;
}
// Numeric port within range
const iport = parseInt(port, 10);
if (!iport || iport < 1 || iport > 65535) {
invalids.port = true;
}
// If they have auth, make sure it's provided
if (hasAuth) {
if (!username) {
@ -331,28 +315,25 @@ class CustomNodeModal extends React.Component<Props, State> {
}
private makeCustomNodeConfigFromState(): CustomNodeConfig {
const { network } = this.state;
const { network, url, name, username, password } = this.state;
const networkId =
network === CUSTOM
? this.makeCustomNetworkId(this.makeCustomNetworkConfigFromState())
: network;
const port = parseInt(this.state.port, 10);
const url = this.state.url.trim();
const node: Omit<CustomNodeConfig, 'lib'> = {
isCustom: true,
service: 'your custom node',
id: `${url}:${port}`,
name: this.state.name.trim(),
id: url,
name: name.trim(),
url,
port,
network: networkId,
...(this.state.hasAuth
? {
auth: {
username: this.state.username,
password: this.state.password
username,
password
}
}
: {})

View File

@ -103,23 +103,23 @@ class Header extends Component<Props, State> {
const LanguageDropDown = Dropdown as new () => Dropdown<typeof selectedLanguage>;
const options = nodeOptions.map(n => {
if (n.isCustom) {
const { name: { networkId, nodeId }, isCustom, id, ...rest } = n;
const { label, isCustom, id, ...rest } = n;
return {
...rest,
name: (
<span>
{networkId} - {nodeId} <small>(custom)</small>
{label.network} - {label.nodeName} <small>(custom)</small>
</span>
),
onRemove: () => this.props.removeCustomNode({ id })
};
} else {
const { name: { networkId, service }, isCustom, ...rest } = n;
const { label, isCustom, ...rest } = n;
return {
...rest,
name: (
<span>
{networkId} <small>({service})</small>
{label.network} <small>({label.service})</small>
</span>
)
};

View File

@ -111,7 +111,7 @@ $m-anim-speed: 400ms;
// Mobile styles
@media(max-width: $screen-sm) {
width: calc(100% - 40px);
width: calc(100% - 40px) !important;
}
}

View File

@ -15,8 +15,13 @@ interface Props {
disableButtons?: boolean;
children: any;
buttons?: IButton[];
maxWidth?: number;
handleClose?(): void;
}
interface ModalStyle {
width?: string;
maxWidth?: string;
}
const Fade = ({ children, ...props }) => (
<CSSTransition {...props} timeout={300} classNames="animate-modal">
@ -46,16 +51,22 @@ export default class Modal extends PureComponent<Props, {}> {
}
public render() {
const { isOpen, title, children, buttons, handleClose } = this.props;
const { isOpen, title, children, buttons, handleClose, maxWidth } = this.props;
const hasButtons = buttons && buttons.length;
const modalStyle: ModalStyle = {};
if (maxWidth) {
modalStyle.width = '100%';
modalStyle.maxWidth = `${maxWidth}px`;
}
return (
<TransitionGroup>
{isOpen && (
<Fade>
<div>
<div className={`Modalshade`} />
<div className={`Modal`}>
<div className="Modalshade" />
<div className="Modal" style={modalStyle}>
{title && (
<div className="Modal-header flex-wrapper">
<h2 className="Modal-header-title">{title}</h2>

View File

@ -119,7 +119,7 @@ export function getNodeLib(state: AppState) {
export interface NodeOption {
isCustom: false;
value: string;
name: { networkId?: string; service: string };
label: { network: string; service: string };
color?: string;
hidden?: boolean;
}
@ -127,12 +127,14 @@ export interface NodeOption {
export function getStaticNodeOptions(state: AppState): NodeOption[] {
const staticNetworkConfigs = getStaticNetworkConfigs(state);
return Object.entries(getStaticNodes(state)).map(([nodeId, node]: [string, StaticNodeConfig]) => {
const networkId = node.network;
const associatedNetwork = staticNetworkConfigs[networkId];
const associatedNetwork = staticNetworkConfigs[node.network];
const opt: NodeOption = {
isCustom: node.isCustom,
value: nodeId,
name: { networkId, service: node.service },
label: {
network: node.network,
service: node.service
},
color: associatedNetwork.color,
hidden: node.hidden
};
@ -144,7 +146,10 @@ export interface CustomNodeOption {
isCustom: true;
id: string;
value: string;
name: { networkId?: string; nodeId: string };
label: {
network: string;
nodeName: string;
};
color?: string;
hidden?: boolean;
}
@ -153,15 +158,18 @@ export function getCustomNodeOptions(state: AppState): CustomNodeOption[] {
const staticNetworkConfigs = getStaticNetworkConfigs(state);
const customNetworkConfigs = getCustomNetworkConfigs(state);
return Object.entries(getCustomNodeConfigs(state)).map(
([nodeId, node]: [string, CustomNodeConfig]) => {
const networkId = node.network;
const associatedNetwork = isStaticNetworkId(state, networkId)
? staticNetworkConfigs[networkId]
: customNetworkConfigs[networkId];
([_, node]: [string, CustomNodeConfig]) => {
const chainId = node.network;
const associatedNetwork = isStaticNetworkId(state, chainId)
? staticNetworkConfigs[chainId]
: customNetworkConfigs[chainId];
const opt: CustomNodeOption = {
isCustom: node.isCustom,
value: node.id,
name: { networkId, nodeId },
label: {
network: associatedNetwork.unit,
nodeName: node.name
},
color: associatedNetwork.isCustom ? undefined : associatedNetwork.color,
hidden: false,
id: node.id

View File

@ -10,7 +10,6 @@ interface CustomNodeConfig {
lib: CustomNode;
service: 'your custom node';
url: string;
port: number;
network: string;
auth?: {
username: string;

View File

@ -9,7 +9,6 @@ const firstCustomNode: CustomNodeConfig = {
lib: jest.fn() as any,
name: 'My cool custom node',
network: 'CustomNetworkId',
port: 8080,
service: 'your custom node',
url: '127.0.0.1'
};