Basic transfer options on-screen

This commit is contained in:
Jaco Greeff 2018-04-18 11:27:55 +02:00
parent baf3dc6688
commit c4dcc9bb4a
27 changed files with 415 additions and 107 deletions

View File

@ -13,7 +13,7 @@
"test": "react-scripts test --env=jsdom --coverage"
},
"devDependencies": {
"@polkadot/rx-react": "^0.2.2",
"@polkadot/rx-react": "^0.2.4",
"@polkadot/ui-react": "^0.9.10",
"@polkadot/util": "^0.18.6",
"@polkadot/util-keyring": "^0.18.6",

View File

@ -0,0 +1,6 @@
/* Copyright 2017-2018 Jaco Greeff
/* This software may be modified and distributed under the terms
/* of the ISC license. See the LICENSE file for details. */
.testing--Account {
}

View File

@ -0,0 +1,43 @@
// Copyright 2017-2018 Jaco Greeff
// This software may be modified and distributed under the terms
// of the ISC license. See the LICENSE file for details.
// @flow
import type { BaseProps } from '@polkadot/portal/types';
import './Account.css';
import React from 'react';
import Label from 'semantic-ui-react/dist/es/elements/Label';
import Balance from '@polkadot/rx-react/Balance';
import withObservableParams from '@polkadot/rx-react/with/observableParams';
import InputAddress from '../InputAddress';
type Props = BaseProps & {
label: string,
subject: rxjs$Subject
};
export default function Account ({ className, label, subject, style }: Props) {
const AccountBalance = withObservableParams(Balance, subject, ({ publicKey }: KeyringPair): Uint8Array => publicKey());
return (
<div
className={['testing--Account', 'testing--split', className].join(' ')}
style={style}
>
<div className='large'>
<Label>{label}</Label>
<InputAddress
placeholder='0x...'
subject={subject}
/>
</div>
<div className='small'>
<Label>with an available balance of</Label>
<AccountBalance className='ui disabled dropdown selection' />
</div>
</div>
);
}

View File

@ -2,11 +2,11 @@
/* This software may be modified and distributed under the terms
/* of the ISC license. See the LICENSE file for details. */
.testing--Body {
.testing--App {
padding: 0;
}
.testing--Body-BlockNumber {
.testing--App-BestNumber {
margin-bottom: 1rem;
opacity: 0.5;
text-align: right;

View File

@ -0,0 +1,35 @@
// Copyright 2017-2018 Jaco Greeff
// This software may be modified and distributed under the terms
// of the ISC license. See the LICENSE file for details.
// @flow
import type { BaseProps } from '@polkadot/portal/types';
import './App.css';
import React from 'react';
import BestNumber from '@polkadot/rx-react/BestNumber';
import CallDisplay from '../CallDisplay';
import CallSelect from '../CallSelect';
import Sender from '../Sender';
type Props = BaseProps & {};
export default function App ({ className, style }: Props) {
return (
<div
className={['testing--App', className].join(' ')}
style={style}
>
<BestNumber
className='testing--App-BestNumber'
label='best block #'
/>
<Sender />
<CallSelect />
<CallDisplay />
</div>
);
}

View File

@ -1,78 +0,0 @@
// Copyright 2017-2018 Jaco Greeff
// This software may be modified and distributed under the terms
// of the ISC license. See the LICENSE file for details.
// @flow
import type { BaseProps } from '@polkadot/portal/types';
import './Body.css';
import React from 'react';
// import { Observable } from 'rxjs/Observable';
// import { from as fromObservable } from 'rxjs/observable/from';
import Input from 'semantic-ui-react/dist/es/elements/Input';
import Label from 'semantic-ui-react/dist/es/elements/Label';
import Balance from '@polkadot/rx-react/Balance';
import BlockNumber from '@polkadot/rx-react/BlockNumber';
import ContextProvider from '@polkadot/rx-react/ContextProvider';
import withObservableParams from '@polkadot/rx-react/with/observableParams';
import InputAddress from '../InputAddress';
import addrRecipient from '../subject/addrRecipient';
import addrSender from '../subject/addrSender';
type Props = BaseProps & {};
function publicMap (pair: KeyringPair): Uint8Array {
return pair.publicKey();
}
const RecipientBalance = withObservableParams(Balance, addrRecipient, publicMap);
const SenderBalance = withObservableParams(Balance, addrSender, publicMap);
export default function Body ({ className, style }: Props) {
return (
<ContextProvider>
<div
className={['testing--Body', className].join(' ')}
style={style}
>
<BlockNumber
className='testing--Body-BlockNumber'
label='best block #'
/>
<div>
<Label>using the selected account</Label>
<InputAddress
placeholder='0x...'
subject={addrSender}
/>
</div>
<div>
<Label>with an available balance of</Label>
<SenderBalance className='ui disabled dropdown selection' />
</div>
<div>
<Label>send an amount of</Label>
<Input />
</div>
<div>
<Label>to the recipient</Label>
<InputAddress
placeholder='0x...'
subject={addrRecipient}
/>
</div>
<div>
<Label>with an available balance of</Label>
<RecipientBalance className='ui disabled dropdown selection' />
</div>
</div>
</ContextProvider>
);
}

View File

@ -0,0 +1,18 @@
/* Copyright 2017-2018 Jaco Greeff
/* This software may be modified and distributed under the terms
/* of the ISC license. See the LICENSE file for details. */
.testing--CallDisplay {
margin-top: 1.5rem;
}
.testing--CallDisplay-error {
color: red;
opacity: 0.75;
}
.testing--CallDisplay-Component {
margin-bottom: 1.5rem;
}
.testing--CallDisplay-Button {}

View File

@ -0,0 +1,18 @@
// Copyright 2017-2018 Jaco Greeff
// This software may be modified and distributed under the terms
// of the ISC license. See the LICENSE file for details.
// @flow
import type { BaseProps } from '@polkadot/portal/types';
import './CallDisplay.css';
import React from 'react';
export default function Error (props: BaseProps): React$Node {
return (
<div className='testing--CallDisplay-error'>
ERROR: Invalid or unimplemented extrinsic function
</div>
);
}

View File

@ -0,0 +1,49 @@
// Copyright 2017-2018 Jaco Greeff
// This software may be modified and distributed under the terms
// of the ISC license. See the LICENSE file for details.
// @flow
import type { BaseProps } from '@polkadot/portal/types';
import './CallDisplay.css';
import React from 'react';
import Button from 'semantic-ui-react/dist/es/elements/Button';
import withObservable from '@polkadot/rx-react/with/observable';
import StakingTransfer from '../StakingTransfer';
import extrinsicName from '../subject/extrinsicName';
import ErrorComponent from './Error';
type Props = BaseProps & {
value?: string;
};
const COMPONENTS = {
'staking_transfer': StakingTransfer
};
function CallDisplay ({ className, style, value }: Props) {
if (!value) {
return null;
}
const Component = COMPONENTS[value] || ErrorComponent;
return (
<div
className={['testing--CallDisplay', className].join(' ')}
style={style}
>
<Component className='testing--CallDisplay-Component' />
<Button
className='testing--CallDisplay-Execute'
primary
>
Submit Extrinsic
</Button>
</div>
);
}
export default withObservable(CallDisplay, extrinsicName);

View File

@ -0,0 +1,6 @@
/* Copyright 2017-2018 Jaco Greeff
/* This software may be modified and distributed under the terms
/* of the ISC license. See the LICENSE file for details. */
.testing--CallSelect {
}

View File

@ -0,0 +1,45 @@
// Copyright 2017-2018 Jaco Greeff
// This software may be modified and distributed under the terms
// of the ISC license. See the LICENSE file for details.
// @flow
import type { BaseProps } from '@polkadot/portal/types';
import './CallSelect.css';
import React from 'react';
import Dropdown from 'semantic-ui-react/dist/es/modules/Dropdown';
import Label from 'semantic-ui-react/dist/es/elements/Label';
import extrinsicName from '../subject/extrinsicName';
type Props = BaseProps & {};
const callOptions = [
{
text: 'Staking Transfer',
value: 'staking_transfer'
}
];
const onChange = (_, { value }) => {
extrinsicName.next(value);
};
export default function CallSelect ({ className, style }: Props) {
return (
<div
className={['testing--CallSelect', 'testing--split', className].join(' ')}
style={style}
>
<div className='large'>
<Label>submit the following extrinsic</Label>
<Dropdown
selection
onChange={onChange}
options={callOptions}
/>
</div>
</div>
);
}

View File

@ -28,23 +28,20 @@ const testOptions = Object.keys(keyring).map((name) => ({
value: name
}));
export default function InputAddress (_props: Props): React$Node {
const props = Object.assign({}, _props, {
className: ['ui--InputAddress', _props.className].join(' ')
});
export default function InputAddress (props: Props): React$Node {
const onChange = (_, { value }) => {
if (_props.subject) {
_props.subject.next(keyring[value]);
if (props.subject) {
props.subject.next(keyring[value]);
}
};
return (
<Dropdown
search
{...props}
className={['ui--InputAddress', props.className].join(' ')}
selection
options={testOptions}
onChange={onChange}
{...props}
/>
);
}

View File

@ -0,0 +1,6 @@
/* Copyright 2017-2018 Jaco Greeff
/* This software may be modified and distributed under the terms
/* of the ISC license. See the LICENSE file for details. */
.testing--Recipient {
}

View File

@ -0,0 +1,26 @@
// Copyright 2017-2018 Jaco Greeff
// This software may be modified and distributed under the terms
// of the ISC license. See the LICENSE file for details.
// @flow
import type { BaseProps } from '@polkadot/portal/types';
import './Recipient.css';
import React from 'react';
import Account from '../Account';
import addrRecipient from '../subject/addrRecipient';
type Props = BaseProps & {};
export default function Recipient (props: Props) {
return (
<Account
{...props}
className={['testing--Recipient', props.className].join(' ')}
label='to the recipient'
subject={addrRecipient}
/>
);
}

View File

@ -0,0 +1,6 @@
/* Copyright 2017-2018 Jaco Greeff
/* This software may be modified and distributed under the terms
/* of the ISC license. See the LICENSE file for details. */
.testing--Sender {
}

View File

@ -0,0 +1,26 @@
// Copyright 2017-2018 Jaco Greeff
// This software may be modified and distributed under the terms
// of the ISC license. See the LICENSE file for details.
// @flow
import type { BaseProps } from '@polkadot/portal/types';
import './Sender.css';
import React from 'react';
import Account from '../Account';
import addrSender from '../subject/addrSender';
type Props = BaseProps & {};
export default function Sender (props: Props) {
return (
<Account
{...props}
className={['testing--Sender', props.className].join(' ')}
label='using the selected account'
subject={addrSender}
/>
);
}

View File

@ -0,0 +1,7 @@
/* Copyright 2017-2018 Jaco Greeff
/* This software may be modified and distributed under the terms
/* of the ISC license. See the LICENSE file for details. */
.testing--StakingTransfer {
margin-top: 1.5rem;
}

View File

@ -0,0 +1,38 @@
// Copyright 2017-2018 Jaco Greeff
// This software may be modified and distributed under the terms
// of the ISC license. See the LICENSE file for details.
// @flow
import type { BaseProps } from '@polkadot/portal/types';
import './StakingTransfer.css';
import React from 'react';
import Input from 'semantic-ui-react/dist/es/elements/Input';
import Label from 'semantic-ui-react/dist/es/elements/Label';
import Recipient from '../Recipient';
type Props = BaseProps & {};
export default function StakingTransfer ({ className, style }: Props) {
return (
<div
className={['testing--StakingTransfer', className].join(' ')}
style={style}
>
<div className='testing--split'>
<div className='small'>
<Label>sending an amount of</Label>
<Input
defaultValue={0}
min={0}
type='number'
/>
</div>
</div>
<Recipient />
</div>
);
}

View File

@ -0,0 +1,46 @@
/* Copyright 2017-2018 Jaco Greeff
/* This software may be modified and distributed under the terms
/* of the ISC license. See the LICENSE file for details. */
.testing--full,
.testing--split {
width: 100%;
}
.testing--split {
display:flex;
flex-wrap:wrap;
flex-direction:row;
justify-content:flex-start;
align-items:stretch;
}
.testing--split > div {
box-sizing: border-box;
padding-right: 0.5rem;
}
.testing--split > div.full {
flex: 1 100%;
min-width: 0;
}
.testing--split > div.large {
flex: 1 75%;
min-width: 0;
}
.testing--split > div.medium {
flex: 1 50%;
min-width: 0;
}
.testing--split > div.small {
flex: 1 25%;
min-width: 0;
}
.testing--split > div:only-child {
flex-grow: 0;
min-width: 0;
}

View File

@ -3,8 +3,8 @@
// of the ISC license. See the LICENSE file for details.
// @flow
import 'semantic-ui-css/semantic.min.css';
import './index.css';
import Body from './Body';
import App from './App';
export default Body;
export default App;

View File

@ -3,8 +3,8 @@
// of the ISC license. See the LICENSE file for details.
// @flow
import addr from './addr';
import base from './base';
const subject = addr();
const subject = base();
export default subject;

View File

@ -3,8 +3,8 @@
// of the ISC license. See the LICENSE file for details.
// @flow
import addr from './addr';
import base from './base';
const subject = addr();
const subject = base();
export default subject;

View File

@ -5,6 +5,6 @@
import { ReplaySubject } from 'rxjs/ReplaySubject';
export default function addr () {
export default function base () {
return new ReplaySubject(1);
}

View File

@ -0,0 +1,10 @@
// Copyright 2017-2018 Jaco Greeff
// This software may be modified and distributed under the terms
// of the ISC license. See the LICENSE file for details.
// @flow
import base from './base';
const subject = base();
export default subject;

View File

@ -15,6 +15,7 @@
},
"devDependencies": {
"@polkadot/app-testing": "^0.1.0",
"@polkadot/rx-react": "^0.2.4",
"@polkadot/ui-react": "^0.9.10",
"i18next": "^11.1.1",
"i18next-browser-languagedetector": "^2.2.0",

View File

@ -11,15 +11,18 @@ import React from 'react';
import ReactDOM from 'react-dom';
import { I18nextProvider } from 'react-i18next';
import { HashRouter } from 'react-router-dom';
import ContextProvider from '@polkadot/rx-react/ContextProvider';
import i18n from './i18n';
import App from './App';
ReactDOM.render(
<I18nextProvider i18n={i18n}>
<HashRouter>
<App />
</HashRouter>
<ContextProvider>
<HashRouter>
<App />
</HashRouter>
</ContextProvider>
</I18nextProvider>,
// flowlint-next-line unclear-type:off
((document.getElementById('root'): any): Element)

View File

@ -1299,19 +1299,19 @@
"@polkadot/util" "^0.18.4"
babel-runtime "^6.26.0"
"@polkadot/rx-api@^0.2.2":
version "0.2.2"
resolved "https://registry.yarnpkg.com/@polkadot/rx-api/-/rx-api-0.2.2.tgz#ee4e28fd2322e4469780d6f1d0e1529a6c95c258"
"@polkadot/rx-api@^0.2.4":
version "0.2.4"
resolved "https://registry.yarnpkg.com/@polkadot/rx-api/-/rx-api-0.2.4.tgz#e49c88d2d68a4e53d9d2ab0e307c564aa2ac5c20"
dependencies:
"@polkadot/api" "^0.8.14"
"@polkadot/api-provider" "^0.8.14"
rxjs "^5.5.10"
"@polkadot/rx-react@^0.2.2":
version "0.2.2"
resolved "https://registry.yarnpkg.com/@polkadot/rx-react/-/rx-react-0.2.2.tgz#8651dbf8cf7d9f32ebdb7d8d82011e96b92b0c54"
"@polkadot/rx-react@^0.2.4":
version "0.2.4"
resolved "https://registry.yarnpkg.com/@polkadot/rx-react/-/rx-react-0.2.4.tgz#fe142b564db5918c5207d8ae1cde86a3091caa84"
dependencies:
"@polkadot/rx-api" "^0.2.2"
"@polkadot/rx-api" "^0.2.4"
"@polkadot/util" "^0.18.6"
"@polkadot/ui-identicon@^0.9.10":