Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add ability to export network specifications from network setting screen #2360

Merged
merged 14 commits into from
Mar 26, 2020
8 changes: 8 additions & 0 deletions packages/apps-config/src/ui/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,16 @@
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.

import { chainColors, nodeColors } from './general';
import { identityNodes } from './identityIcons';

function sanitize (value?: string): string {
return value?.toLowerCase().replace('-', ' ') || '';
}
export function getSystemIcon (systemName: string): 'beachball' | 'polkadot' | 'substrate' {
return (identityNodes[systemName.toLowerCase().replace(/-/g, ' ')] || 'substrate') as 'substrate';
}

export const getSystemChainColor = (systemChain: string, systemName: string): string | undefined => {
return chainColors[sanitize(systemChain)] || nodeColors[sanitize(systemName)];
};
16 changes: 15 additions & 1 deletion packages/apps/public/locales/en/app-settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,19 @@
"No upgradable extensions found": "No upgradable extensions found",
"upgradable extensions": "upgradable extensions",
"Update metadata": "Update metadata",
"Extensions {{count}}": "Extensions {{count}}"
"Extensions {{count}}": "Extensions {{count}}",
"Chain Specifications as a QR Code": "Chain Specifications as a QR Code",
"Generate Random Color": "Generate Random Color",
"Network Name": "Network Name",
"Name of the network. It only for display purpose.": "Name of the network. It only for display purpose.",
"Color": "Color",
"The color used to distinguish this network with others, use color code with 3 or 6 digits, like \"#FFF\" or \"#111111\"": "The color used to distinguish this network with others, use color code with 3 or 6 digits, like \"#FFF\" or \"#111111\"",
"Genesis Hash": "Genesis Hash",
"Genesis Hash refers to initial state of the chain, it cannot be changed once the chain is launched": "Genesis Hash refers to initial state of the chain, it cannot be changed once the chain is launched",
"Unit": "Unit",
"Unit decides the name of 1 unit token, e.g. \"DOT\" for Polkadot": "Unit decides the name of 1 unit token, e.g. \"DOT\" for Polkadot",
"Address Prefix": "Address Prefix",
"Prefix indicates the ss58 address format in this network, is a number between 0 ~ 255 describes the precise format of the bytes of the address": "Prefix indicates the ss58 address format in this network, is a number between 0 ~ 255 describes the precise format of the bytes of the address",
"Decimals": "Decimals",
"Decimals decides the smallest unit of the token, which is 1/10^decimals": "Decimals decides the smallest unit of the token, which is 1/10^decimals"
}
18 changes: 17 additions & 1 deletion packages/apps/public/locales/ja/app-settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,21 @@
"Select the remote endpoint, either from the dropdown on manual entered via the custom toggle": "ドロップダウンの中からリモートノードを選択するかカスタムエンドポイントをオンにして記入します",
"remote node/endpoint to connect to": "リモートノード・エンドポイント",
"custom endpoint": "カスタムエンドポイント",
"Default browser language (auto-detect)": "ブラウザー設定言語(自動反映)"
"Default browser language (auto-detect)": "ブラウザー設定言語(自動反映)",
"Update metadata": "Update metadata",
"Extensions {{count}}": "Extensions {{count}}",
"Chain Specifications as a QR Code": "Chain Specifications as a QR Code",
"Generate Random Color": "Generate Random Color",
"Network Name": "Network Name",
"Name of the network. It only for display purpose.": "Name of the network. It only for display purpose.",
"Color": "Color",
"The color used to distinguish this network with others, use color code with 3 or 6 digits, like \"#FFF\" or \"#111111\"": "The color used to distinguish this network with others, use color code with 3 or 6 digits, like \"#FFF\" or \"#111111\"",
"Genesis Hash": "Genesis Hash",
"Genesis Hash refers to initial state of the chain, it cannot be changed once the chain is launched": "Genesis Hash refers to initial state of the chain, it cannot be changed once the chain is launched",
"Unit": "Unit",
"Unit decides the name of 1 unit token, e.g. \"DOT\" for Polkadot": "Unit decides the name of 1 unit token, e.g. \"DOT\" for Polkadot",
"Address Prefix": "Address Prefix",
"Prefix indicates the ss58 address format in this network, is a number between 0 ~ 255 describes the precise format of the bytes of the address": "Prefix indicates the ss58 address format in this network, is a number between 0 ~ 255 describes the precise format of the bytes of the address",
"Decimals": "Decimals",
"Decimals decides the smallest unit of the token, which is 1/10^decimals": "Decimals decides the smallest unit of the token, which is 1/10^decimals"
}
20 changes: 19 additions & 1 deletion packages/apps/public/locales/zh/app-settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,23 @@
"Select the remote endpoint, either from the dropdown on manual entered via the custom toggle": "选择远程终端,或者通过自定义切换输入的手动下拉菜单中的任意一个",
"remote node/endpoint to connect to": "远程节点/终端连接到",
"custom endpoint": "自定义终端",
"Default browser language (auto-detect)": "默认浏览器语言 (自动检测)"
"Default browser language (auto-detect)": "默认浏览器语言 (自动检测)",
"No upgradable extensions found": "没有需要升级的浏览器插件",
"upgradable extensions": "升级浏览器插件",
"Update metadata": "升级Metadata",
"Extensions {{count}}": "浏览器插件 {{count}}",
"Chain Specifications as a QR Code": "生成含网络参数的二维码",
"Generate Random Color": "随机生成颜色",
"Network Name": "网络名称",
"Name of the network. It only for display purpose.": "网络名称,仅作为显示用",
"Color": "颜色",
"The color used to distinguish this network with others, use color code with 3 or 6 digits, like \"#FFF\" or \"#111111\"": "颜色用来和其他网络相区别,用3位或者6位的16进制码表示,比如#FFF或#111111",
"Genesis Hash": "创世区块哈希",
"Genesis Hash refers to initial state of the chain, it cannot be changed once the chain is launched": "创世区块哈希代表网络的初始状态,在网络启动后不可改变",
"Unit": "单位",
"Unit decides the name of 1 unit token, e.g. \"DOT\" for Polkadot": "每一个token的单位,比如Polkadot的单位是DOT",
"Address Prefix": "地址前缀",
"Prefix indicates the ss58 address format in this network, is a number between 0 ~ 255 describes the precise format of the bytes of the address": "地址前缀用0~255的数来来表示SS58地址的编码格式",
"Decimals": "小数",
"Decimals decides the smallest unit of the token, which is 1/10^decimals": "小数代表每一单位token最小可以精确到多少位小数"
}
13 changes: 5 additions & 8 deletions packages/apps/src/Apps.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ import { BareProps as Props } from '@polkadot/react-components/types';
import React, { useCallback, useMemo, useState } from 'react';
import store from 'store';
import styled from 'styled-components';
import { defaultColor, chainColors, nodeColors } from '@polkadot/apps-config/ui/general';
import { getSystemChainColor } from '@polkadot/apps-config/ui';
import { defaultColor } from '@polkadot/apps-config/ui/general';
import GlobalStyle from '@polkadot/react-components/styles';
import { useApi } from '@polkadot/react-hooks';
import Signer from '@polkadot/react-signer';
Expand All @@ -28,10 +29,6 @@ interface SidebarState {

export const PORTAL_ID = 'portals';

function sanitize (value?: string): string {
return value?.toLowerCase().replace('-', ' ') || '';
}

function Apps ({ className }: Props): React.ReactElement<Props> {
const { systemChain, systemName } = useApi();
const [sidebar, setSidebar] = useState<SidebarState>({
Expand All @@ -41,9 +38,9 @@ function Apps ({ className }: Props): React.ReactElement<Props> {
...store.get('sidebar', {}),
isMenu: window.innerWidth < SIDEBAR_MENU_THRESHOLD
});
const uiHighlight = useMemo((): string | undefined => {
return chainColors[sanitize(systemChain)] || nodeColors[sanitize(systemName)];
}, [systemChain, systemName]);
const uiHighlight = useMemo((): string | undefined =>
getSystemChainColor(systemChain, systemName), [systemChain, systemName]
);
const { isCollapsed, isMenu, isMenuOpen } = sidebar;

const _setSidebar = useCallback(
Expand Down
23 changes: 23 additions & 0 deletions packages/page-settings/src/Metadata/ChainColorIndicator.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// Copyright 2017-2020 @polkadot/app-settings authors & contributors
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.

import React from 'react';
import styled from 'styled-components';

import { BareProps } from '@polkadot/react-components/types';

interface Props extends BareProps {
color: string;
}

function ChainColorIndicator ({ color, className }: Props): React.ReactElement<Props> {
return <div color={color} className={className}/>;
}

export default React.memo(styled(ChainColorIndicator)`
background-color: ${(props: Props): string => props.color} !important;
width: 100px;
flex: 1;
border-radius: 4px;
`);
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ import React, { useCallback, useMemo, useState } from 'react';
import { Button, Dropdown } from '@polkadot/react-components';
import { useToggle } from '@polkadot/react-hooks';

import { useTranslation } from './translate';
import useChainInfo from './useChainInfo';
import useExtensions from './useExtensions';
import { useTranslation } from '../translate';
import useChainInfo from '../useChainInfo';
import useExtensions from '../useExtensions';

function Extensions (): React.ReactElement {
const { t } = useTranslation();
Expand Down
146 changes: 146 additions & 0 deletions packages/page-settings/src/Metadata/NetworkSpecs.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
// Copyright 2017-2020 @polkadot/app-settings authors & contributors
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.

import { Button, Input } from '@polkadot/react-components';
import { BareProps } from '@polkadot/react-components/types';
import { useApi, useDebounce } from '@polkadot/react-hooks';
import { QrNetworkSpecs } from '@polkadot/react-qr';
import { NetworkSpecsStruct } from '@polkadot/ui-settings';

import React, { useEffect, useReducer, useState } from 'react';
import styled from 'styled-components';

import ChainColorIndicator from './ChainColorIndicator';
import { useTranslation } from '../translate';
import useChainInfo from '../useChainInfo';

function getRandomColor (): string {
const letters = '0123456789ABCDEF';
let color = '#';
for (let i = 0; i < 6; i++) {
color += letters[Math.floor(Math.random() * 16)];
}
return color;
}

function NetworkSpecs ({ className }: BareProps): React.ReactElement<BareProps> | null {
const { t } = useTranslation();
const { isApiReady, systemChain } = useApi();
const chainInfo = useChainInfo();
const initialState = {
decimals: 0,
prefix: 0,
unit: 'UNIT',
title: '',
color: '#FFFFFF',
genesisHash: ''
};
const [qrData, setQrData] = useState<NetworkSpecsStruct>(initialState);
const debouncedQrData = useDebounce(qrData, 500);
const reducer = (state: NetworkSpecsStruct, delta: Partial<NetworkSpecsStruct>): NetworkSpecsStruct => {
const newState = {
...state,
...delta
};
setQrData(newState);
return newState;
};
const [networkSpecs, setNetworkSpecs] = useReducer(reducer, initialState);

useEffect((): void => {
if (chainInfo != null) {
setNetworkSpecs({
color: chainInfo.color || getRandomColor(),
decimals: chainInfo.tokenDecimals,
prefix: chainInfo.ss58Format,
unit: chainInfo.tokenSymbol,
title: systemChain,
genesisHash: chainInfo.genesisHash
});
}
}, [chainInfo, systemChain]);

if (!isApiReady) {
return null;
}

type inputListener = (v: string) => void;
const _onChangeValue = (k: keyof NetworkSpecsStruct): inputListener => (v: string): void => setNetworkSpecs({ [k]: v });
const _onSetRandomColor = (): void => setNetworkSpecs({ color: getRandomColor() });
const _checkColorValid = (): boolean => /^#[\da-fA-F]{6}|#[\da-fA-F]{3}$/.test(networkSpecs.color);

return (
<div className={className}>
<Input
autoFocus
className='full'
help={t('Name of the network. It only for display purpose.')}
label={t('Network Name')}
onChange={_onChangeValue('title')}
value={networkSpecs.title}
/>
<Input
className='full'
help={t('The color used to distinguish this network with others, use color code with 3 or 6 digits, like "#FFF" or "#111111"')}
isError={!_checkColorValid()}
label={t('Color')}
onChange={_onChangeValue('color')}
value={networkSpecs.color}
>
<div className='settings--networkSpecs-colorButton'>
<Button
label={t('Generate Random Color')}
icon='sync'
key='spread'
onClick={_onSetRandomColor}
/>
<ChainColorIndicator color={networkSpecs.color}/>
</div>
</Input>
<Input
className='full'
help={t('Genesis Hash refers to initial state of the chain, it cannot be changed once the chain is launched')}
isDisabled
label={t('Genesis Hash')}
value={networkSpecs.genesisHash}
/>
<Input
className='full'
help={t('Unit decides the name of 1 unit token, e.g. "DOT" for Polkadot')}
isDisabled
label={t('Unit')}
value={networkSpecs.unit}
/>
<Input
className='full'
help={t('Prefix indicates the his network, is a number between 0 ~ 255 describes the precise format of the bytes of the address')}
isDisabled
label={t('Address Prefix')}
value={networkSpecs.prefix}
/>
<Input
className='full'
help={t('Decimals decides the smallest unit of the token, which is 1/10^decimals')}
isDisabled
label={t('Decimals')}
value={networkSpecs.decimals}
/>
<QrNetworkSpecs className='settings--networkSpecs-qr' networkSpecs={debouncedQrData}/>
</div>
);
}

export default React.memo(styled(NetworkSpecs)`
top: .3rem;

.settings--networkSpecs-colorButton {
display: flex;
flex-direction: row;
}

.settings--networkSpecs-qr {
margin: 2rem auto;
max-width: 15rem;
}
`);
31 changes: 31 additions & 0 deletions packages/page-settings/src/Metadata/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// Copyright 2017-2020 @polkadot/app-settings authors & contributors
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.

import { useTranslation } from '@polkadot/app-settings/translate';
hanwencheng marked this conversation as resolved.
Show resolved Hide resolved
import useCounter from '@polkadot/app-settings/useCounter';
import React, { useMemo } from 'react';

import Extensions from './Extensions';
import NetworkSpecs from './NetworkSpecs';

export default function Metadata (): React.ReactElement {
const { t } = useTranslation();
const numExtensions = useCounter();
const extensionsTitle = useMemo(() => t('Extensions {{count}}', {
replace: {
count: numExtensions
? `(${numExtensions})`
: ''
}
}), [numExtensions, t]);

return (
<>
<h1>{extensionsTitle}</h1>
<Extensions />
<h1>{t('Chain Specifications as a QR Code')}</h1>
<NetworkSpecs />
</>
);
}
23 changes: 8 additions & 15 deletions packages/page-settings/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@

import { AppProps as Props } from '@polkadot/react-components/types';

import React, { useMemo } from 'react';
import React from 'react';
import { Route, Switch } from 'react-router';
import { HelpOverlay, Tabs } from '@polkadot/react-components';
import uiSettings from '@polkadot/ui-settings';

import md from './md/basics.md';
import { useTranslation } from './translate';
import Developer from './Developer';
import Extensions from './Extensions';
import Metadata from './Metadata';
import General from './General';
import useCounter from './useCounter';

Expand All @@ -23,29 +23,22 @@ const hidden = uiSettings.uiMode === 'full'
: ['developer'];

function SettingsApp ({ basePath, onStatusChange }: Props): React.ReactElement<Props> {
const numExtensions = useCounter();
const { t } = useTranslation();
const items = useMemo(() => [
const items = [
{
isRoot: true,
name: 'general',
text: t('General')
},
{
name: 'extensions',
text: t('Extensions {{count}}', {
replace: {
count: numExtensions
? `(${numExtensions})`
: ''
}
})
name: 'metadata',
text: t('Metadata')
},
{
name: 'developer',
text: t('Developer')
}
], [numExtensions, t]);
];

return (
<main className='settings--App'>
Expand All @@ -64,8 +57,8 @@ function SettingsApp ({ basePath, onStatusChange }: Props): React.ReactElement<P
onStatusChange={onStatusChange}
/>
</Route>
<Route path={`${basePath}/extensions`}>
<Extensions />
<Route path={`${basePath}/metadata`}>
<Metadata />
</Route>
<Route component={General} />
</Switch>
Expand Down
Loading