OSDN Git Service

redesign backup page.
authorZhiting Lin <zlin035@uottawa.ca>
Wed, 27 Jun 2018 06:05:21 +0000 (14:05 +0800)
committerZhiting Lin <zlin035@uottawa.ca>
Wed, 27 Jun 2018 06:05:21 +0000 (14:05 +0800)
src/actions.js
src/features/backup/actions.js [new file with mode: 0644]
src/features/backup/components/Backup.jsx
src/features/backup/components/Backup.scss
src/features/backup/index.js
src/sdk/api/backUp.js [new file with mode: 0644]
src/sdk/client.js

index 9c2906f..6065186 100644 (file)
@@ -2,6 +2,7 @@ import accessControl from 'features/accessControl/actions'
 import { actions as account } from 'features/accounts'
 import { actions as app } from 'features/app'
 import { actions as asset } from 'features/assets'
+import { actions as backUp } from 'features/backup'
 import { actions as balance } from 'features/balances'
 import { actions as configuration } from 'features/configuration'
 import { actions as core } from 'features/core'
@@ -17,6 +18,7 @@ const actions = {
   account,
   app,
   asset,
+  backUp,
   balance,
   configuration,
   core,
diff --git a/src/features/backup/actions.js b/src/features/backup/actions.js
new file mode 100644 (file)
index 0000000..fb43c44
--- /dev/null
@@ -0,0 +1,34 @@
+import { chainClient } from 'utility/environment'
+
+let actions = {
+  rescan: () => {
+    return (dispatch) => {
+      return chainClient().backUp.rescan()
+        .then((resp) => {
+          if (resp.status === 'fail') {
+            dispatch({type: 'ERROR', payload: { 'message': resp.msg}})
+          }else {
+            dispatch({type: 'START_RESCAN'})
+          }
+        })
+        .catch(err => { throw {_error: err} })
+    }
+  },
+
+  restore: (backupData) => {
+    return (dispatch) => {
+      return chainClient().backUp.restore(backupData)
+        .then(resp => {
+          if (resp.status === 'fail') {
+            dispatch({type: 'ERROR', payload: { 'message': resp.msg}})
+          }else {
+            dispatch({type: 'RESTORE_SUCCESS'})
+          }
+        })
+        .catch(err => { throw {_error: err} })
+    }
+  },
+
+}
+
+export default actions
index 3b00b7b..e201306 100644 (file)
@@ -4,31 +4,42 @@ import {PageContent, PageTitle} from 'features/shared/components'
 import styles from './Backup.scss'
 import {connect} from 'react-redux'
 import {chainClient} from 'utility/environment'
+import actions from 'actions'
 
 class Backup extends React.Component {
   constructor(props) {
     super(props)
-    this.connection = chainClient().connection
+    this.state = {
+      value: null
+    }
   }
 
-  backup() {
-    this.connection.request('/backup-wallet').then(resp => {
-      const date = new Date()
-      const dateStr = date.toLocaleDateString().split(' ')[0]
-      const timestamp = date.getTime()
-      const fileName = ['bytom-wallet-backup-', dateStr, timestamp].join('-')
-
-      var element = document.createElement('a')
-      element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(JSON.stringify(resp.data)))
-      element.setAttribute('download', fileName)
-      element.style.display = 'none'
-      document.body.appendChild(element)
-      element.click()
-
-      document.body.removeChild(element)
+  setValue(event) {
+    this.setState({
+      value:event.target.value
     })
   }
 
+  backup() {
+    chainClient().backUp.backup()
+      .then(resp => {
+        const date = new Date()
+        const dateStr = date.toLocaleDateString().split(' ')[0]
+        const timestamp = date.getTime()
+        const fileName = ['bytom-wallet-backup-', dateStr, timestamp].join('-')
+
+        let element = document.createElement('a')
+        element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(JSON.stringify(resp.data)))
+        element.setAttribute('download', fileName)
+        element.style.display = 'none'
+        document.body.appendChild(element)
+        element.click()
+
+        document.body.removeChild(element)
+      })
+      .catch(err => { throw {_error: err} })
+  }
+
   handleFileChange(event) {
     const files = event.target.files
     if (files.length <= 0) {
@@ -39,13 +50,7 @@ class Backup extends React.Component {
     const fileReader = new FileReader()
     fileReader.onload = fileLoadedEvent => {
       const backupData = JSON.parse(fileLoadedEvent.target.result)
-      this.connection.request('/restore-wallet', backupData).then(resp => {
-        if (resp.status === 'fail') {
-          this.props.showError(new Error(resp.msg))
-          return
-        }
-        this.props.showRestoreSuccess()
-      })
+      this.props.restoreFile(backupData)
     }
     fileReader.readAsText(files[0], 'UTF-8')
 
@@ -60,22 +65,103 @@ class Backup extends React.Component {
 
   render() {
     const lang = this.props.core.lang
-    const newButton = <button className='btn btn-primary' onClick={this.backup.bind(this)}>
-      {lang === 'zh' ? '备份' : 'Backup'}
+
+    const newButton = <button className={`btn btn-primary btn-lg ${styles.submit}`} onClick={this.backup.bind(this)}>
+      {lang === 'zh' ? '下载备份' : 'Download Backup'}
     </button>
-    const restoreButton = <button className='btn btn-primary' onClick={this.restore.bind(this)}>
-      {lang === 'zh' ? '恢复' : 'Restore'}
+    const restoreButton = <button className={`btn btn-primary btn-lg ${styles.submit}`} onClick={this.restore.bind(this)}>
+      {lang === 'zh' ? '选择备份文件' : 'Select Restore File'}
+    </button>
+    const rescanButton = <button className={`btn btn-primary btn-lg ${styles.submit}`}  onClick={() => this.props.rescan()}>
+      {lang === 'zh' ? '重新扫描' : 'Rescan'}
     </button>
 
     return (
-      <div className={componentClassNames(this, 'flex-container', styles.mainContainer)}>
+      <div className='flex-container'>
         <PageTitle title={lang === 'zh' ? '备份与恢复' : 'Backup and Restore'}/>
         <PageContent>
-          {newButton}
-          <hr/>
-          {restoreButton}
-          <input id='bytom-restore-file-upload' type='file' style={{'display': 'none', 'alignItems': 'center', 'fontSize': '12px'}}
-                 onChange={this.handleFileChange.bind(this)}/>
+
+          <div onChange={e => this.setValue(e)}>
+            <div className={styles.choices}>
+              <div className={styles.choice_wrapper}>
+                <label>
+                  <input className={styles.choice_radio_button}
+                         type='radio'
+                         name='type'
+                         value='backup'/>
+                  <div className={`${styles.choice} ${styles.new} `}>
+                    <span className={styles.choice_title}>{lang === 'zh' ?'备份':'Back Up'}</span>
+                    <p>
+                      This option will back up all data stored in this core,
+                      including blockchain data, accounts, assets
+                      and balances.
+                    </p>
+                  </div>
+                </label>
+              </div>
+
+              <div className={styles.choice_wrapper}>
+                <label>
+                  <input className={styles.choice_radio_button}
+                         type='radio'
+                         name='type'
+                         value='restore' />
+                  <div className={`${styles.choice} ${styles.join}`}>
+                    <span className={styles.choice_title}>{lang === 'zh' ?'恢复':'Restore'}</span>
+                    <p>
+                      This option will restore the wallet data form files.
+                      You might need to rescan your wallet, if you balance is not up to date
+                    </p>
+                  </div>
+                </label>
+              </div>
+
+              <div className={styles.choice_wrapper}>
+                <label>
+                  <input className={styles.choice_radio_button}
+                         type='radio'
+                         name='type'
+                         value='rescan' />
+                  <div className={`${styles.choice} ${styles.join}`}>
+                    <span className={styles.choice_title}>{lang === 'zh' ?'重新扫描':'Rescan'}</span>
+                    <p>
+                      This option will rescan your wallet and update your balance.
+                    </p>
+                  </div>
+                </label>
+              </div>
+
+
+            </div>
+
+            <div className={styles.choices}>
+              <div>
+                {
+                  this.state.value === 'backup'
+                  &&<span className={styles.submitWrapper}>{newButton}</span>
+                }
+              </div>
+
+              <div>
+                {
+                  this.state.value === 'restore'
+                  &&
+                    <span className={styles.submitWrapper}>{restoreButton}</span>
+                }
+                <input id='bytom-restore-file-upload' type='file'
+                       style={{'display': 'none', 'alignItems': 'center', 'fontSize': '12px'}}
+                       onChange={this.handleFileChange.bind(this)}/>
+              </div>
+              <div>
+                {
+                  this.state.value === 'rescan'
+                  &&
+                  <span className={styles.submitWrapper}>{rescanButton}</span>
+                }
+              </div>
+            </div>
+          </div>
+
         </PageContent>
       </div>
     )
@@ -88,10 +174,9 @@ const mapStateToProps = (state) => ({
 })
 
 const mapDispatchToProps = (dispatch) => ({
-  showError: (err) => {
-    dispatch({type: 'ERROR', payload: err})
-  },
-  showRestoreSuccess: () => dispatch({type: 'RESTORE_SUCCESS'})
+  backup: () => dispatch(actions.backUp.backup()),
+  rescan: () => dispatch(actions.backUp.rescan()),
+  restoreFile: (backUpFile) => dispatch(actions.backUp.restore(backUpFile)),
 })
 
 export default connect(
index b83ce21..e902b39 100644 (file)
-.page_header h1 {
-  margin-bottom: 0;
+.mainContainer {
+  background-color: $background-color;
+}
+
+code {
+  padding-left: 0;
+  font-size: $font-size-code;
 }
 
-.table {
-  margin-bottom: $grid-gutter-width;
+.choices {
+  display: flex;
+  flex-wrap: wrap;
+  justify-content: space-between;
 
-  td {
-    vertical-align: top;
+  > div {
+    width: 30%;
+    min-height: 100%;
   }
 }
 
-.row_label {
-  padding-right: $grid-gutter-width;
-  text-transform: capitalize;
-  font-weight: 500;
-  white-space: pre;
-}
+.choice_wrapper {
+  display: flex;
+  min-height: 100%;
 
-.row_value {
-  white-space: pre;
-  text-align: right;
+  > label {
+    display: flex;
+    min-height:100%;
+    font-weight: normal;
+  }
 }
 
-.block_hash {
-  display: block;
-  word-wrap: break-word;
-  word-break: break-all;
+.choice_radio_button {
+  position: absolute;
+  visibility: hidden;
 }
 
-.flex {
-  display: flex;
-  //overflow:hidden;
-}
+.choice {
+  border: 1px solid $table-border-color;
+  border-radius: $border-radius-base;
+  cursor: pointer;
+  min-height: 100%;
+  padding: 20px $grid-gutter-width;
+  padding-top: 110px;
+  text-align: center;
 
-.col {
-  display: flex;
-  width: 50%;
-  padding: $grid-gutter-width;
-  h4 {
-    margin-top: 0;
+  background-color: $background-color;
+  background-repeat: no-repeat;
+  background-position: center 25px;
+  background-size: 90px 90px;
+
+  &:hover {
+    background-color: $background-emphasis-color;
   }
-}
 
-.sub-row {
-  padding: $grid-gutter-width $grid-gutter-width 0;
-}
+  &.disabled {
+    cursor: default;
+    background-color: $background-emphasis-color;
+    opacity: 0.75;
 
-.top {
-  border-bottom: 1px solid $border-color;
-}
+    .choice_title {
+      color: $text-light-color;
+    }
+  }
 
-.left {
-  padding-left: 0;
-  width: 67%;
-}
+  p {
+    line-height: 1.4;
+  }
 
-.right {
-  border-left: 1px solid $border-color;
-  width: 33%;
-}
+  svg {
+    display: block;
+    margin: 0 auto;
+    width: 80px;
+    height: 80px;
+  }
 
-.replication_lag {
-  display: inline-block;
-  float: right;
-  border-radius: $border-radius-base;
-  color: white;
-  padding: 0 8px;
-  line-height: 1.5;
-  margin-top: 2px;
-  margin-left: -8px;
+  .choice_title{
+    display: block;
+    font-size: $font-size-section-title;
+    margin: 12px 0;
+    color: $text-strong-color;
+    font-weight: 600;
+  }
 }
 
-.green {
-  background: $highlight-secondary;
+.new {
+  background-image: url('images/config/new.png')
 }
 
-.yellow {
-  background: $brand-warning;
+.join {
+  background-image: url('images/config/join.png')
 }
 
-.red {
-  background: darken($highlight-danger-background, 20%);
+.testnet {
+  background-image: url('images/config/testnet.png')
 }
 
-.mainContainer {
-  background-color: $background-color;
-}
+input[type=radio]:checked ~ .choice {
+  strong {
+    color: $text-danger;
+  }
 
-code {
-  padding-left: 0;
-  font-size: $font-size-code;
-}
+  &:hover {
+    background-color: $background-color;
+  }
 
-.switch {
-  position: relative;
-  display: inline-block;
-  width: 44px;
-  height: 22px;
+  &.new {
+    background-image: url('images/config/new-active.png')
+  }
 
-  /* Hide default HTML checkbox */
-  input {display:none;}
+  &.join {
+    background-image: url('images/config/join-active.png')
+  }
 
-  input:checked + .slider {
-    background-color: $highlight-default;
+  &.testnet {
+    background-image: url('images/config/testnet-active.png')
   }
 
-  input:focus + .slider {
-    box-shadow: 0 0 1px $highlight-default;
+
+  .choice_title {
+    color: $brand-primary;
   }
 
-  input:checked + .slider:before {
-    -webkit-transform: translateX(22px);
-    -ms-transform: translateX(22px);
-    transform: translateX(22px);
+  svg {
+    polygon {
+      stroke: $brand-primary;
+      fill: transparentize($brand-primary, 0.85);
+    }
+
+    rect, path {
+      fill: $brand-primary
+    }
   }
 }
 
-/* The slider */
-.slider {
-  position: absolute;
-  cursor: pointer;
-  top: 0;
-  left: 0;
-  right: 0;
-  bottom: 0;
-  background-color: #ccc;
-  -webkit-transition: .4s;
-  transition: .4s;
-  border-radius: 34px;
+.submitWrapper {
+  display: block;
+  margin-top: 30px;
+}
+
+.submit {
+  display: block;
+  width: 100%;
+  margin-top: $gutter-size;
+}
+
+.infoLink {
+  position: relative;
+  left: 4px;
+  top: 1px;
+  color:$text-light-color;
 }
 
-.slider:before {
-  position: absolute;
-  content: "";
-  height: 18px;
-  width: 18px;
-  left: 2px;
-  bottom: 2px;
-  background-color: white;
-  -webkit-transition: .4s;
-  transition: .4s;
-  border-radius: 50%;
-}
\ No newline at end of file
index be538be..b133bca 100644 (file)
@@ -1,5 +1,7 @@
+import actions from './actions'
 import routes from './routes'
 
 export {
+  actions,
   routes,
 }
diff --git a/src/sdk/api/backUp.js b/src/sdk/api/backUp.js
new file mode 100644 (file)
index 0000000..9f0164b
--- /dev/null
@@ -0,0 +1,22 @@
+const shared = require('../shared')
+
+const backUp = (client) => {
+  return {
+    backup: (cb) => shared.tryCallback(
+      client.request('/backup-wallet'),
+      cb
+    ),
+
+    restore: (opts = {}, cb) => shared.tryCallback(
+      client.request('/restore-wallet', opts),
+      cb
+    ),
+
+    rescan: (cb) => shared.tryCallback(
+      client.request('/rescan-wallet'),
+      cb
+    ),
+  }
+}
+
+module.exports = backUp
index 697a14d..bb39011 100644 (file)
@@ -3,6 +3,7 @@ const authorizationGrantsAPI = require('./api/authorizationGrants')
 const accessTokensAPI = require('./api/accessTokens')
 const accountsAPI = require('./api/accounts')
 const assetsAPI = require('./api/assets')
+const backUpAPI = require('./api/backUp')
 const balancesAPI = require('./api/balances')
 const bytomCLI = require('./api/bytomCLI')
 const configAPI = require('./api/config')
@@ -34,6 +35,8 @@ class Client {
 
     this.assets = assetsAPI(this)
 
+    this.backUp = backUpAPI(this)
+
     this.balances = balancesAPI(this)
 
     this.bytomCli = bytomCLI(this)