OSDN Git Service

add the asset decimal function in transaction page.
[bytom/bytom-electron.git] / src / features / transactions / components / New / New.jsx
index 632b521..015c896 100644 (file)
@@ -1,15 +1,48 @@
-import { BaseNew, FormContainer, FormSection, FieldLabel, JsonField, TextField } from 'features/shared/components'
-import { DropdownButton, MenuItem } from 'react-bootstrap'
-import { reduxForm } from 'redux-form'
+import {
+  BaseNew,
+  FormContainer,
+  FormSection,
+  FieldLabel,
+  TextField,
+  Autocomplete,
+  ObjectSelectorField,
+  AmountUnitField,
+  AmountInputMask
+} from 'features/shared/components'
+import {DropdownButton, MenuItem} from 'react-bootstrap'
+import {reduxForm} from 'redux-form'
 import ActionItem from './FormActionItem'
 import React from 'react'
 import styles from './New.scss'
+import actions from 'actions'
+import { normalizeBTMAmountUnit, converIntToDec } from 'utility/buildInOutDisplay'
+
+const rangeOptions = [
+  {
+    label: 'Standard',
+    label_zh: '标准',
+    value: '20000000'
+  },
+  {
+    label: 'Fast',
+    label_zh: '快速',
+    value: '25000000'
+  },
+  {
+    label: 'Customize',
+    label_zh: '自定义',
+    value: ''
+  }
+]
+
+const btmID = 'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff'
 
 class Form extends React.Component {
   constructor(props) {
     super(props)
     this.state = {
-      showDropdown: false
+      showDropdown: false,
+      showAdvanced: false
     }
 
     this.submitWithValidation = this.submitWithValidation.bind(this)
@@ -20,12 +53,70 @@ class Form extends React.Component {
     this.disableSubmit = this.disableSubmit.bind(this)
   }
 
+  componentDidMount() {
+    if (!this.props.autocompleteIsLoaded) {
+      this.props.fetchAll().then(() => {
+        this.props.didLoadAutocomplete()
+      })
+    }
+    if (!this.props.autocompleteIsAssetLoaded) {
+      this.props.fetchAssetAll().then(() => {
+        this.props.didLoadAssetAutocomplete()
+      })
+    }
+
+    this.props.fields.normalTransaction.gas.type.onChange(rangeOptions[0].label)
+    this.props.fields.normalTransaction.gas.price.onChange(rangeOptions[0].value)
+  }
+
+  balanceAmount(normalTransaction, assetdecimal) {
+    let balances = this.props.balances
+    let filteredBalances = balances
+    if (normalTransaction.accountAlias.value) {
+      filteredBalances = filteredBalances.filter(balance => balance.accountAlias === normalTransaction.accountAlias.value)
+    }
+    if (normalTransaction.accountId.value) {
+      filteredBalances = filteredBalances.filter(balance => balance.accountId === normalTransaction.accountId.value)
+    }
+    if (normalTransaction.assetAlias.value) {
+      filteredBalances = filteredBalances.filter(balance => balance.assetAlias === normalTransaction.assetAlias.value)
+    }
+    if (normalTransaction.assetId.value) {
+      filteredBalances = filteredBalances.filter(balance => balance.assetId === normalTransaction.assetId.value)
+    }
+
+    if(filteredBalances.length === 1){
+      if (filteredBalances[0].assetId === btmID){
+        return normalizeBTMAmountUnit(filteredBalances[0].assetId, filteredBalances[0].amount, this.props.btmAmountUnit)
+      }else if( assetdecimal ){
+        return converIntToDec(filteredBalances[0].amount, assetdecimal)
+      }else{
+        return filteredBalances[0].amount
+      }
+    }else {
+      return null
+    }
+  }
+
+  assetDecimal(normalTransaction) {
+    let asset = this.props.asset
+    let filteredAsset = asset
+    if (normalTransaction.assetAlias.value) {
+      filteredAsset = filteredAsset.filter(asset => asset.alias === normalTransaction.assetAlias.value)
+    }
+    if (normalTransaction.assetId.value) {
+      filteredAsset = filteredAsset.filter(asset => asset.id === normalTransaction.assetId.value)
+    }
+
+    return (filteredAsset.length === 1 && filteredAsset[0].definition && filteredAsset[0].id !== btmID ) ? filteredAsset[0].definition.decimals : null
+  }
+
   toggleDropwdown() {
-    this.setState({ showDropdown: !this.state.showDropdown })
+    this.setState({showDropdown: !this.state.showDropdown})
   }
 
   closeDropdown() {
-    this.setState({ showDropdown: false })
+    this.setState({showDropdown: false})
   }
 
   addActionItem(type) {
@@ -36,24 +127,33 @@ class Form extends React.Component {
     this.closeDropdown()
   }
 
-  disableSubmit(actions) {
-    return actions.length == 0 & !this.state.showAdvanced
+  disableSubmit(actions, normalTransaction) {
+    if (this.state.showAdvanceTx) {
+      return actions.length == 0 && !this.state.showAdvanced
+    }
+
+    const hasValue = target => {
+      return !!(target && target.value)
+    }
+
+    return !((hasValue(normalTransaction.accountId) || hasValue(normalTransaction.accountAlias)) &&
+      (hasValue(normalTransaction.assetId) || hasValue(normalTransaction.assetAlias)) &&
+      hasValue(normalTransaction.address) && (hasValue(normalTransaction.amount)))
   }
 
   removeActionItem(index) {
     this.props.fields.actions.removeField(index)
   }
 
-  submitWithValidation(data) {
-    const lagThreshold = 5
-    if (this.props.replicationLag === null || this.props.replicationLag >= lagThreshold) {
-      return Promise.reject({
-        _error: `Replication lag must be less than ${lagThreshold} to submit transactions via the dashboard. Please wait for the local core to catch up to the generator.`
-      })
+  emptyActions(actions) {
+    if (actions.length != 0) {
+      actions.map(() => this.removeActionItem(0))
     }
+  }
 
+  submitWithValidation(data) {
     return new Promise((resolve, reject) => {
-      this.props.submitForm(data)
+      this.props.submitForm(Object.assign({}, data, {state: this.state}))
         .catch((err) => {
           const response = {}
 
@@ -71,30 +171,147 @@ class Form extends React.Component {
     })
   }
 
+
   render() {
     const {
-      fields: { baseTransaction, actions, submitAction },
+      fields: {baseTransaction, actions, submitAction, password, normalTransaction},
       error,
       handleSubmit,
       submitting
     } = this.props
+    const lang = this.props.lang
+
+    let submitLabel = lang === 'zh' ? '提交交易' : 'Submit transaction'
+    const hasBaseTransaction = ((baseTransaction.value || '').trim()).length > 0
+    if (submitAction.value == 'generate' && !hasBaseTransaction) {
+      submitLabel = lang === 'zh' ? '生成交易JSON' : 'Generate transaction JSON'
+    }
+
+    const gasOnChange = event => {
+      normalTransaction.gas.type.onChange(event)
 
-    let submitLabel = 'Submit transaction'
-    if (submitAction.value == 'generate') {
-      submitLabel = 'Generate transaction hex'
+      const range = rangeOptions.find(item => item.label === event.target.value)
+      normalTransaction.gas.price.onChange(range.value)
     }
+    const assetDecimal = this.assetDecimal(normalTransaction)
+
+    const showAvailableBalance = (normalTransaction.accountAlias.value || normalTransaction.accountId.value) &&
+      (normalTransaction.assetAlias.value || normalTransaction.assetId.value)
+    const availableBalance = this.balanceAmount(normalTransaction, assetDecimal)
+
+    const showBtmAmountUnit = (normalTransaction.assetAlias.value === 'BTM' ||
+      normalTransaction.assetId.value === 'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff')
+
 
-    return(
+    return (
       <FormContainer
         error={error}
-        label='New transaction'
+        label={lang === 'zh' ? '新建交易' : 'New transaction'}
         submitLabel={submitLabel}
         onSubmit={handleSubmit(this.submitWithValidation)}
         showSubmitIndicator={true}
         submitting={submitting}
-        disabled={this.disableSubmit(actions)} >
+        disabled={this.disableSubmit(actions, normalTransaction)}>
 
-        <FormSection title='Actions'>
+        <div className={`btn-group ${styles.btnGroup}`} role='group'>
+          <button
+            className={`btn btn-default ${this.state.showAdvanceTx ? null : 'active'}`}
+            onClick={(e) => {
+              e.preventDefault()
+              this.emptyActions(actions)
+              this.setState({showAdvanceTx: false})
+            }}>
+            {lang === 'zh' ? '简单交易' : 'Normal'}
+          </button>
+          <button
+            className={`btn btn-default ${this.state.showAdvanceTx ? 'active' : null}`}
+            onClick={(e) => {
+              e.preventDefault()
+              this.setState({showAdvanceTx: true})
+            }}>
+            {lang === 'zh' ? '高级交易' : 'Advanced'}
+          </button>
+        </div>
+
+        {!this.state.showAdvanceTx && <FormSection title={lang === 'zh' ? '简单交易' : 'Normal Trasaction'}>
+          <label className={styles.title}>{lang === 'zh' ? '从' : 'From'}</label>
+          <div className={styles.main}>
+            <ObjectSelectorField
+              key='account-selector-field'
+              lang={lang}
+              title={lang === 'zh' ? '账户' : 'Account'}
+              aliasField={Autocomplete.AccountAlias}
+              fieldProps={{
+                id: normalTransaction.accountId,
+                alias: normalTransaction.accountAlias
+              }}
+            />
+            <ObjectSelectorField
+              key='asset-selector-field'
+              lang={lang}
+              title={lang === 'zh' ? '资产' : 'Asset'}
+              aliasField={Autocomplete.AssetAlias}
+              fieldProps={{
+                id: normalTransaction.assetId,
+                alias: normalTransaction.assetAlias
+              }}
+            />
+            {showAvailableBalance && availableBalance &&
+            <small className={styles.balanceHint}>{availableBalance} {lang === 'zh' ? '可用' : 'available'} </small>}
+          </div>
+
+          <label className={styles.title}>{lang === 'zh' ? '至' : 'To'}</label>
+          <div className={styles.main}>
+            <TextField title={lang === 'zh' ? '地址' : 'Address'} fieldProps={normalTransaction.address}/>
+            {!showBtmAmountUnit && !assetDecimal &&
+            <TextField title={lang === 'zh' ? '数量' : 'Amount'} fieldProps={normalTransaction.amount}
+            />}
+            {!showBtmAmountUnit && assetDecimal &&
+            <AmountInputMask title={lang === 'zh' ? '数量' : 'Amount'} fieldProps={normalTransaction.amount} decimal={assetDecimal}
+            />}
+            {showBtmAmountUnit &&
+            <AmountUnitField title={lang === 'zh' ? '数量' : 'Amount'} fieldProps={normalTransaction.amount}/>
+            }
+          </div>
+
+          <label className={styles.title}>Gas</label>
+          <table className={styles.optionsBtnContianer}>
+            {rangeOptions.map((option) =>
+              <tr className={styles.optionsBtn}>
+                <td className={styles.optionsLabel}>
+                  <label>
+                    <input type='radio'
+                           {...normalTransaction.gas.type}
+                           onChange={gasOnChange}
+                           value={option.label}
+                           checked={option.label == normalTransaction.gas.type.value}
+                    />
+                    {lang === 'zh' ? option.label_zh : option.label}
+                  </label>
+                </td>
+                <td>
+                  {option.label == normalTransaction.gas.type.value && option.label !== 'Customize'
+                  && normalizeBTMAmountUnit(btmID, option.value, this.props.btmAmountUnit)}
+                  {
+                    option.label === 'Customize' && normalTransaction.gas.type.value === 'Customize' &&
+                    <div>
+                      <AmountUnitField
+                        autoFocus={true}
+                        fieldProps={normalTransaction.gas.price}
+                        placeholder='Enter gas'/>
+                    </div>
+                  }
+                </td>
+              </tr>
+            )}
+          </table>
+
+          <label className={styles.title}>{lang === 'zh' ? '密码' : 'Password'}</label>
+          <TextField placeholder={lang === 'zh' ? '请输入密码' : 'Please enter the assword'} fieldProps={password}
+                     autoFocus={false} type={'password'}/>
+        </FormSection>}
+
+        {this.state.showAdvanceTx && <FormSection title='Actions'>
           {actions.map((action, index) =>
             <ActionItem
               key={index}
@@ -103,110 +320,147 @@ class Form extends React.Component {
               accounts={this.props.accounts}
               assets={this.props.assets}
               remove={this.removeActionItem}
+              lang={lang}
             />)}
 
-            <div className={`btn-group ${styles.addActionContainer} ${this.state.showDropdown && 'open'}`}>
-              <DropdownButton
-                className={`btn btn-default ${styles.addAction}`}
-                id='input-dropdown-addon'
-                title='+ Add action'
-                onSelect={this.addActionItem}
-              >
-                <MenuItem eventKey='issue'>Issue</MenuItem>
-                <MenuItem eventKey='spend_account'>Spend from account</MenuItem>
-                <MenuItem eventKey='spend_account_unspent_output'>Spend unspent output</MenuItem>
-                <MenuItem eventKey='control_account'>Control with account</MenuItem>
-                <MenuItem eventKey='control_receiver'>Control with receiver</MenuItem>
-                <MenuItem eventKey='retire'>Retire</MenuItem>
-                <MenuItem eventKey='set_transaction_reference_data'>Set transaction reference data</MenuItem>
-              </DropdownButton>
-            </div>
-        </FormSection>
-
-        {!this.state.showAdvanced &&
-          <FormSection>
-            <a href='#'
-              className={styles.showAdvanced}
-              onClick={(e) => {
-                e.preventDefault()
-                this.setState({showAdvanced: true})
-              }}
+          <div className={`btn-group ${styles.addActionContainer} ${this.state.showDropdown && 'open'}`}>
+            <DropdownButton
+              className={`btn btn-default ${styles.addAction}`}
+              id='input-dropdown-addon'
+              title='+ Add action'
+              onSelect={this.addActionItem}
             >
-              Show advanced options
-            </a>
-          </FormSection>
+              <MenuItem eventKey='issue'>Issue</MenuItem>
+              <MenuItem eventKey='spend_account'>Spend from account</MenuItem>
+              <MenuItem eventKey='control_address'>Control with address</MenuItem>
+              <MenuItem eventKey='retire'>Retire</MenuItem>
+            </DropdownButton>
+          </div>
+        </FormSection>}
+
+        {this.state.showAdvanceTx && !this.state.showAdvanced &&
+        <FormSection>
+          <a href='#'
+             className={styles.showAdvanced}
+             onClick={(e) => {
+               e.preventDefault()
+               this.setState({showAdvanced: true})
+             }}
+          >
+            {lang === 'zh' ? '显示高级选项' : 'Show advanced options'}
+          </a>
+        </FormSection>
         }
 
-        {this.state.showAdvanced && <FormSection title='Advanced Options'>
+        {this.state.showAdvanceTx && this.state.showAdvanced &&
+        <FormSection title={lang === 'zh' ? '高级选项' : 'Advanced Options'}>
           <div>
             <TextField
-              title='Base transaction'
-              placeholder='Paste transaction hex here...'
+              title={lang === 'zh' ? '带签名交易' : 'To sign transaction'}
+              placeholder={lang === 'zh' ? '在这里复制交易 HEX ...' : 'Paste transaction hex here...'}
               fieldProps={baseTransaction}
-              autoFocus={true} />
+              autoFocus={true}/>
 
-            <FieldLabel>Transaction Build Type</FieldLabel>
+            <FieldLabel>{lang === 'zh' ? '交易构建类型' : 'Transaction Build Type'}</FieldLabel>
             <table className={styles.submitTable}>
               <tbody>
-                <tr>
-                  <td><input id='submit_action_submit' type='radio' {...submitAction} value='submit' checked={submitAction.value == 'submit'} /></td>
-                  <td>
-                    <label htmlFor='submit_action_submit'>Submit transaction to blockchain</label>
-                    <br />
-                    <label htmlFor='submit_action_submit' className={styles.submitDescription}>
-                      This transaction will be signed by the MockHSM and submitted to the blockchain.
-                    </label>
-                  </td>
-                </tr>
-                <tr>
-                  <td><input id='submit_action_generate' type='radio' {...submitAction} value='generate' checked={submitAction.value == 'generate'} /></td>
-                  <td>
-                    <label htmlFor='submit_action_generate'>Allow additional actions</label>
-                    <br />
-                    <label htmlFor='submit_action_generate' className={styles.submitDescription}>
-                      These actions will be signed by the MockHSM and returned as a
-                      transaction hex string, which should be used as the base
-                      transaction in a multi-party swap. This transaction will be
-                      valid for one hour.
-                    </label>
-                  </td>
-                </tr>
+              <tr>
+                <td><input id='submit_action_submit' type='radio' {...submitAction} value='submit'
+                           checked={submitAction.value == 'submit'}/></td>
+                <td>
+                  <label
+                    htmlFor='submit_action_submit'>{lang === 'zh' ? '向区块链提交交易' : 'Submit transaction to blockchain'}</label>
+                  <br/>
+                  <label htmlFor='submit_action_submit' className={styles.submitDescription}>
+                    {lang === 'zh' ? '此次交易将通过密钥签名然后提交到区块链。' :
+                      'This transaction will be signed by the MockHSM and submitted to the blockchain.'}
+                  </label>
+                </td>
+              </tr>
+              <tr>
+                <td><input id='submit_action_generate' type='radio' {...submitAction} value='generate'
+                           checked={submitAction.value == 'generate'}/></td>
+                <td>
+                  <label htmlFor='submit_action_generate'>{lang === 'zh' ? '需要更多签名' : 'Need more signature'}</label>
+                  <br/>
+                  <label htmlFor='submit_action_generate' className={styles.submitDescription}>
+                    {lang === 'zh' ? '这些actions将通过密钥签名然后作为一个交易 JSON 字符串返回。 作为多签交易的输入,这个JSON字符串需要更多的签名数据。' :
+                      'These actions will be signed by the Key and returned as a transaction JSON string, ' +
+                      'which should be used to sign transaction in a multi-sign spend.'}
+                  </label>
+                </td>
+              </tr>
               </tbody>
             </table>
           </div>
         </FormSection>}
+
+        {
+          this.state.showAdvanceTx && (actions.length > 0 || this.state.showAdvanced) && <FormSection>
+            <label className={styles.title}>{lang === 'zh' ? '密码' : 'Password'}</label>
+            <TextField placeholder={lang === 'zh' ? '请输入密码' : 'Please enter the assword'} fieldProps={password}
+                       autoFocus={false} type={'password'}/>
+          </FormSection>
+        }
       </FormContainer>
     )
   }
 }
 
 const validate = values => {
-  const errors = {actions: {}}
+  const errors = {actions: {}, normalTransaction: {gas: {}}}
 
   // Base transaction
-  let baseTx = values.baseTransaction || ''
-  if (baseTx.trim().match(/[^0-9a-fA-F]/)) {
-    errors.baseTransaction = 'Base transaction must be a hex string.'
+  let baseTx = (values.baseTransaction || '').trim()
+  try {
+    JSON.parse(baseTx)
+  } catch (e) {
+    if (baseTx && e) {
+      errors.baseTransaction = 'To sign transaction must be a JSON string.'
+    }
   }
 
   // Actions
-  let fieldError
+  let numError
   values.actions.forEach((action, index) => {
-    fieldError = JsonField.validator(values.actions[index].referenceData)
-    if (fieldError) {
-      errors.actions[index] = {...errors.actions[index], referenceData: fieldError}
+    numError = (!/^\d+(\.\d+)?$/i.test(values.actions[index].amount))
+    if (numError) {
+      errors.actions[index] = {...errors.actions[index], amount: 'Invalid amount type'}
     }
   })
 
+  // Numerical
+  let normalTx = values.normalTransaction || ''
+  if (normalTx.amount && !/^\d+(\.\d+)?$/i.test(normalTx.amount)) {
+    errors.normalTransaction.amount = 'Invalid amount type'
+  }
   return errors
 }
 
 export default BaseNew.connect(
-  state => ({
-    ...BaseNew.mapStateToProps('transaction')(state),
-    replicationLag: state.core.replicationLag,
+  (state) => {
+    let balances = []
+    for (let key in state.balance.items) {
+      balances.push(state.balance.items[key])
+    }
+
+    return {
+      autocompleteIsLoaded: state.balance.autocompleteIsLoaded,
+      autocompleteIsAssetLoaded: state.balance.autocompleteIsLoaded,
+      lang: state.core.lang,
+      btmAmountUnit: state.core.btmAmountUnit,
+      balances,
+      asset: Object.keys(state.asset.items).map(k => state.asset.items[k]),
+      ...BaseNew.mapStateToProps('transaction')(state)
+    }
+  },
+  (dispatch) => ({
+    didLoadAutocomplete: () => dispatch(actions.balance.didLoadAutocomplete),
+    fetchAll: (cb) => dispatch(actions.balance.fetchAll(cb)),
+    didLoadAssetAutocomplete: () => dispatch(actions.asset.didLoadAutocomplete),
+    fetchAssetAll: (cb) => dispatch(actions.asset.fetchAll(cb)),
+    ...BaseNew.mapDispatchToProps('transaction')(dispatch)
   }),
-  BaseNew.mapDispatchToProps('transaction'),
   reduxForm({
     form: 'NewTransactionForm',
     fields: [
@@ -220,12 +474,27 @@ export default BaseNew.connect(
       'actions[].outputId',
       'actions[].referenceData',
       'actions[].type',
+      'actions[].address',
+      'actions[].password',
+      'normalTransaction.accountAlias',
+      'normalTransaction.accountId',
+      'normalTransaction.amount',
+      'normalTransaction.assetAlias',
+      'normalTransaction.assetId',
+      'normalTransaction.gas',
+      'normalTransaction.gas.type',
+      'normalTransaction.gas.price',
+      'normalTransaction.address',
       'submitAction',
+      'password'
     ],
     validate,
+    touchOnChange: true,
     initialValues: {
       submitAction: 'submit',
     },
   }
   )(Form)
 )
+
+