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:
parent
dc24a52e2c
commit
b48617e95e
|
@ -20,6 +20,9 @@ interface Input {
|
||||||
name: string;
|
name: string;
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
type?: string;
|
type?: string;
|
||||||
|
autoComplete?: 'off';
|
||||||
|
onFocus?(): void;
|
||||||
|
onBlur?(): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface OwnProps {
|
interface OwnProps {
|
||||||
|
@ -40,7 +43,6 @@ interface StateProps {
|
||||||
interface State {
|
interface State {
|
||||||
name: string;
|
name: string;
|
||||||
url: string;
|
url: string;
|
||||||
port: string;
|
|
||||||
network: string;
|
network: string;
|
||||||
customNetworkId: string;
|
customNetworkId: string;
|
||||||
customNetworkUnit: string;
|
customNetworkUnit: string;
|
||||||
|
@ -56,7 +58,6 @@ class CustomNodeModal extends React.Component<Props, State> {
|
||||||
public state: State = {
|
public state: State = {
|
||||||
name: '',
|
name: '',
|
||||||
url: '',
|
url: '',
|
||||||
port: '',
|
|
||||||
network: Object.keys(this.props.staticNetworks)[0],
|
network: Object.keys(this.props.staticNetworks)[0],
|
||||||
customNetworkId: '',
|
customNetworkId: '',
|
||||||
customNetworkUnit: '',
|
customNetworkUnit: '',
|
||||||
|
@ -94,6 +95,7 @@ class CustomNodeModal extends React.Component<Props, State> {
|
||||||
isOpen={true}
|
isOpen={true}
|
||||||
buttons={buttons}
|
buttons={buttons}
|
||||||
handleClose={handleClose}
|
handleClose={handleClose}
|
||||||
|
maxWidth={580}
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
{isHttps && <div className="alert alert-warning small">{translate('NODE_Warning')}</div>}
|
{isHttps && <div className="alert alert-warning small">{translate('NODE_Warning')}</div>}
|
||||||
|
@ -175,27 +177,14 @@ class CustomNodeModal extends React.Component<Props, State> {
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<hr />
|
|
||||||
|
|
||||||
<div className="row">
|
<div className="row">
|
||||||
<div className="col-sm-9">
|
<div className="col-sm-12">
|
||||||
<label>URL</label>
|
<label>URL</label>
|
||||||
{this.renderInput(
|
{this.renderInput(
|
||||||
{
|
{
|
||||||
name: 'url',
|
name: 'url',
|
||||||
placeholder: 'https://127.0.0.1/'
|
placeholder: 'e.g. https://127.0.0.1:8545/',
|
||||||
},
|
autoComplete: 'off'
|
||||||
invalids
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="col-sm-3">
|
|
||||||
<label>{translate('NODE_Port')}</label>
|
|
||||||
{this.renderInput(
|
|
||||||
{
|
|
||||||
name: 'port',
|
|
||||||
placeholder: '8545',
|
|
||||||
type: 'number'
|
|
||||||
},
|
},
|
||||||
invalids
|
invalids
|
||||||
)}
|
)}
|
||||||
|
@ -248,6 +237,7 @@ class CustomNodeModal extends React.Component<Props, State> {
|
||||||
})}
|
})}
|
||||||
value={this.state[input.name]}
|
value={this.state[input.name]}
|
||||||
onChange={this.handleChange}
|
onChange={this.handleChange}
|
||||||
|
autoComplete="off"
|
||||||
{...input}
|
{...input}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
@ -256,7 +246,6 @@ class CustomNodeModal extends React.Component<Props, State> {
|
||||||
private getInvalids(): { [key: string]: boolean } {
|
private getInvalids(): { [key: string]: boolean } {
|
||||||
const {
|
const {
|
||||||
url,
|
url,
|
||||||
port,
|
|
||||||
hasAuth,
|
hasAuth,
|
||||||
username,
|
username,
|
||||||
password,
|
password,
|
||||||
|
@ -265,7 +254,7 @@ class CustomNodeModal extends React.Component<Props, State> {
|
||||||
customNetworkUnit,
|
customNetworkUnit,
|
||||||
customNetworkChainId
|
customNetworkChainId
|
||||||
} = this.state;
|
} = this.state;
|
||||||
const required: (keyof State)[] = ['name', 'url', 'port', 'network'];
|
const required: (keyof State)[] = ['name', 'url', 'network'];
|
||||||
const invalids: { [key: string]: boolean } = {};
|
const invalids: { [key: string]: boolean } = {};
|
||||||
|
|
||||||
// Required fields
|
// Required fields
|
||||||
|
@ -275,17 +264,12 @@ class CustomNodeModal extends React.Component<Props, State> {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Somewhat valid URL, not 100% fool-proof
|
// Parse the URL, and make sure what they typed isn't parsed as relative.
|
||||||
if (!/https?\:\/\/\w+/i.test(url)) {
|
// Not a perfect regex, just checks for protocol + any char
|
||||||
|
if (!/^https?:\/\/.+/i.test(url)) {
|
||||||
invalids.url = true;
|
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 they have auth, make sure it's provided
|
||||||
if (hasAuth) {
|
if (hasAuth) {
|
||||||
if (!username) {
|
if (!username) {
|
||||||
|
@ -331,28 +315,25 @@ class CustomNodeModal extends React.Component<Props, State> {
|
||||||
}
|
}
|
||||||
|
|
||||||
private makeCustomNodeConfigFromState(): CustomNodeConfig {
|
private makeCustomNodeConfigFromState(): CustomNodeConfig {
|
||||||
const { network } = this.state;
|
const { network, url, name, username, password } = this.state;
|
||||||
|
|
||||||
const networkId =
|
const networkId =
|
||||||
network === CUSTOM
|
network === CUSTOM
|
||||||
? this.makeCustomNetworkId(this.makeCustomNetworkConfigFromState())
|
? this.makeCustomNetworkId(this.makeCustomNetworkConfigFromState())
|
||||||
: network;
|
: network;
|
||||||
|
|
||||||
const port = parseInt(this.state.port, 10);
|
|
||||||
const url = this.state.url.trim();
|
|
||||||
const node: Omit<CustomNodeConfig, 'lib'> = {
|
const node: Omit<CustomNodeConfig, 'lib'> = {
|
||||||
isCustom: true,
|
isCustom: true,
|
||||||
service: 'your custom node',
|
service: 'your custom node',
|
||||||
id: `${url}:${port}`,
|
id: url,
|
||||||
name: this.state.name.trim(),
|
name: name.trim(),
|
||||||
url,
|
url,
|
||||||
port,
|
|
||||||
network: networkId,
|
network: networkId,
|
||||||
...(this.state.hasAuth
|
...(this.state.hasAuth
|
||||||
? {
|
? {
|
||||||
auth: {
|
auth: {
|
||||||
username: this.state.username,
|
username,
|
||||||
password: this.state.password
|
password
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
: {})
|
: {})
|
||||||
|
|
|
@ -103,23 +103,23 @@ class Header extends Component<Props, State> {
|
||||||
const LanguageDropDown = Dropdown as new () => Dropdown<typeof selectedLanguage>;
|
const LanguageDropDown = Dropdown as new () => Dropdown<typeof selectedLanguage>;
|
||||||
const options = nodeOptions.map(n => {
|
const options = nodeOptions.map(n => {
|
||||||
if (n.isCustom) {
|
if (n.isCustom) {
|
||||||
const { name: { networkId, nodeId }, isCustom, id, ...rest } = n;
|
const { label, isCustom, id, ...rest } = n;
|
||||||
return {
|
return {
|
||||||
...rest,
|
...rest,
|
||||||
name: (
|
name: (
|
||||||
<span>
|
<span>
|
||||||
{networkId} - {nodeId} <small>(custom)</small>
|
{label.network} - {label.nodeName} <small>(custom)</small>
|
||||||
</span>
|
</span>
|
||||||
),
|
),
|
||||||
onRemove: () => this.props.removeCustomNode({ id })
|
onRemove: () => this.props.removeCustomNode({ id })
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
const { name: { networkId, service }, isCustom, ...rest } = n;
|
const { label, isCustom, ...rest } = n;
|
||||||
return {
|
return {
|
||||||
...rest,
|
...rest,
|
||||||
name: (
|
name: (
|
||||||
<span>
|
<span>
|
||||||
{networkId} <small>({service})</small>
|
{label.network} <small>({label.service})</small>
|
||||||
</span>
|
</span>
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
|
@ -111,7 +111,7 @@ $m-anim-speed: 400ms;
|
||||||
|
|
||||||
// Mobile styles
|
// Mobile styles
|
||||||
@media(max-width: $screen-sm) {
|
@media(max-width: $screen-sm) {
|
||||||
width: calc(100% - 40px);
|
width: calc(100% - 40px) !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -15,8 +15,13 @@ interface Props {
|
||||||
disableButtons?: boolean;
|
disableButtons?: boolean;
|
||||||
children: any;
|
children: any;
|
||||||
buttons?: IButton[];
|
buttons?: IButton[];
|
||||||
|
maxWidth?: number;
|
||||||
handleClose?(): void;
|
handleClose?(): void;
|
||||||
}
|
}
|
||||||
|
interface ModalStyle {
|
||||||
|
width?: string;
|
||||||
|
maxWidth?: string;
|
||||||
|
}
|
||||||
|
|
||||||
const Fade = ({ children, ...props }) => (
|
const Fade = ({ children, ...props }) => (
|
||||||
<CSSTransition {...props} timeout={300} classNames="animate-modal">
|
<CSSTransition {...props} timeout={300} classNames="animate-modal">
|
||||||
|
@ -46,16 +51,22 @@ export default class Modal extends PureComponent<Props, {}> {
|
||||||
}
|
}
|
||||||
|
|
||||||
public render() {
|
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 hasButtons = buttons && buttons.length;
|
||||||
|
const modalStyle: ModalStyle = {};
|
||||||
|
|
||||||
|
if (maxWidth) {
|
||||||
|
modalStyle.width = '100%';
|
||||||
|
modalStyle.maxWidth = `${maxWidth}px`;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TransitionGroup>
|
<TransitionGroup>
|
||||||
{isOpen && (
|
{isOpen && (
|
||||||
<Fade>
|
<Fade>
|
||||||
<div>
|
<div>
|
||||||
<div className={`Modalshade`} />
|
<div className="Modalshade" />
|
||||||
<div className={`Modal`}>
|
<div className="Modal" style={modalStyle}>
|
||||||
{title && (
|
{title && (
|
||||||
<div className="Modal-header flex-wrapper">
|
<div className="Modal-header flex-wrapper">
|
||||||
<h2 className="Modal-header-title">{title}</h2>
|
<h2 className="Modal-header-title">{title}</h2>
|
||||||
|
|
|
@ -119,7 +119,7 @@ export function getNodeLib(state: AppState) {
|
||||||
export interface NodeOption {
|
export interface NodeOption {
|
||||||
isCustom: false;
|
isCustom: false;
|
||||||
value: string;
|
value: string;
|
||||||
name: { networkId?: string; service: string };
|
label: { network: string; service: string };
|
||||||
color?: string;
|
color?: string;
|
||||||
hidden?: boolean;
|
hidden?: boolean;
|
||||||
}
|
}
|
||||||
|
@ -127,12 +127,14 @@ export interface NodeOption {
|
||||||
export function getStaticNodeOptions(state: AppState): NodeOption[] {
|
export function getStaticNodeOptions(state: AppState): NodeOption[] {
|
||||||
const staticNetworkConfigs = getStaticNetworkConfigs(state);
|
const staticNetworkConfigs = getStaticNetworkConfigs(state);
|
||||||
return Object.entries(getStaticNodes(state)).map(([nodeId, node]: [string, StaticNodeConfig]) => {
|
return Object.entries(getStaticNodes(state)).map(([nodeId, node]: [string, StaticNodeConfig]) => {
|
||||||
const networkId = node.network;
|
const associatedNetwork = staticNetworkConfigs[node.network];
|
||||||
const associatedNetwork = staticNetworkConfigs[networkId];
|
|
||||||
const opt: NodeOption = {
|
const opt: NodeOption = {
|
||||||
isCustom: node.isCustom,
|
isCustom: node.isCustom,
|
||||||
value: nodeId,
|
value: nodeId,
|
||||||
name: { networkId, service: node.service },
|
label: {
|
||||||
|
network: node.network,
|
||||||
|
service: node.service
|
||||||
|
},
|
||||||
color: associatedNetwork.color,
|
color: associatedNetwork.color,
|
||||||
hidden: node.hidden
|
hidden: node.hidden
|
||||||
};
|
};
|
||||||
|
@ -144,7 +146,10 @@ export interface CustomNodeOption {
|
||||||
isCustom: true;
|
isCustom: true;
|
||||||
id: string;
|
id: string;
|
||||||
value: string;
|
value: string;
|
||||||
name: { networkId?: string; nodeId: string };
|
label: {
|
||||||
|
network: string;
|
||||||
|
nodeName: string;
|
||||||
|
};
|
||||||
color?: string;
|
color?: string;
|
||||||
hidden?: boolean;
|
hidden?: boolean;
|
||||||
}
|
}
|
||||||
|
@ -153,15 +158,18 @@ export function getCustomNodeOptions(state: AppState): CustomNodeOption[] {
|
||||||
const staticNetworkConfigs = getStaticNetworkConfigs(state);
|
const staticNetworkConfigs = getStaticNetworkConfigs(state);
|
||||||
const customNetworkConfigs = getCustomNetworkConfigs(state);
|
const customNetworkConfigs = getCustomNetworkConfigs(state);
|
||||||
return Object.entries(getCustomNodeConfigs(state)).map(
|
return Object.entries(getCustomNodeConfigs(state)).map(
|
||||||
([nodeId, node]: [string, CustomNodeConfig]) => {
|
([_, node]: [string, CustomNodeConfig]) => {
|
||||||
const networkId = node.network;
|
const chainId = node.network;
|
||||||
const associatedNetwork = isStaticNetworkId(state, networkId)
|
const associatedNetwork = isStaticNetworkId(state, chainId)
|
||||||
? staticNetworkConfigs[networkId]
|
? staticNetworkConfigs[chainId]
|
||||||
: customNetworkConfigs[networkId];
|
: customNetworkConfigs[chainId];
|
||||||
const opt: CustomNodeOption = {
|
const opt: CustomNodeOption = {
|
||||||
isCustom: node.isCustom,
|
isCustom: node.isCustom,
|
||||||
value: node.id,
|
value: node.id,
|
||||||
name: { networkId, nodeId },
|
label: {
|
||||||
|
network: associatedNetwork.unit,
|
||||||
|
nodeName: node.name
|
||||||
|
},
|
||||||
color: associatedNetwork.isCustom ? undefined : associatedNetwork.color,
|
color: associatedNetwork.isCustom ? undefined : associatedNetwork.color,
|
||||||
hidden: false,
|
hidden: false,
|
||||||
id: node.id
|
id: node.id
|
||||||
|
|
|
@ -10,7 +10,6 @@ interface CustomNodeConfig {
|
||||||
lib: CustomNode;
|
lib: CustomNode;
|
||||||
service: 'your custom node';
|
service: 'your custom node';
|
||||||
url: string;
|
url: string;
|
||||||
port: number;
|
|
||||||
network: string;
|
network: string;
|
||||||
auth?: {
|
auth?: {
|
||||||
username: string;
|
username: string;
|
||||||
|
|
|
@ -9,7 +9,6 @@ const firstCustomNode: CustomNodeConfig = {
|
||||||
lib: jest.fn() as any,
|
lib: jest.fn() as any,
|
||||||
name: 'My cool custom node',
|
name: 'My cool custom node',
|
||||||
network: 'CustomNetworkId',
|
network: 'CustomNetworkId',
|
||||||
port: 8080,
|
|
||||||
service: 'your custom node',
|
service: 'your custom node',
|
||||||
url: '127.0.0.1'
|
url: '127.0.0.1'
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in New Issue