├── .babelrc ├── .gitignore ├── LICENSE.md ├── README.md ├── app ├── actions │ ├── AccountActions.js │ ├── AssetActions.js │ ├── BackupActions.js │ ├── BalanceClaimActiveActions.js │ ├── CachedPropertyActions.js │ ├── IntlActions.js │ ├── MarketsActions.js │ ├── NotificationActions.js │ ├── PrivateKeyActions.js │ ├── ScanActions.js │ ├── SettingsActions.js │ ├── TransactionConfirmActions.js │ ├── WalletActions.js │ ├── WalletUnlockActions.js │ └── layout │ │ └── ConfirmActions.js ├── api │ ├── ApplicationApi.js │ ├── WalletApi.js │ └── accountApi.js ├── app.js ├── assets │ ├── fonts │ │ └── WebSymbols-Regular.otf │ ├── imgs │ │ ├── close.png │ │ ├── open.png │ │ └── symbols │ │ │ ├── bkt.png │ │ │ ├── blockpay.png │ │ │ ├── btc.png │ │ │ ├── bts.png │ │ │ ├── btsr.png │ │ │ ├── btwty.png │ │ │ ├── cny.png │ │ │ ├── dao.png │ │ │ ├── dash.png │ │ │ ├── dct.png │ │ │ ├── dgd.png │ │ │ ├── eth.png │ │ │ ├── eur.png │ │ │ ├── eurt.png │ │ │ ├── game.png │ │ │ ├── gold.png │ │ │ ├── grc.png │ │ │ ├── hempsweet.png │ │ │ ├── icoo.png │ │ │ ├── incnt.png │ │ │ ├── lisk.png │ │ │ ├── mkr.png │ │ │ ├── nxc.png │ │ │ ├── obits.png │ │ │ ├── open.btc.png │ │ │ ├── peerplays.png │ │ │ ├── steem.png │ │ │ ├── usd.png │ │ │ └── usdt.png │ ├── loader.js │ ├── locales │ │ ├── locale-en.js │ │ └── locale-zh.js │ └── styles │ │ ├── app.scss │ │ ├── define.scss │ │ ├── flip.scss │ │ ├── modal.scss │ │ └── symbols.scss ├── components │ ├── BaseComponent.js │ ├── Blockchain │ │ ├── BlockTime.js │ │ ├── MemoInfo.js │ │ └── Transaction.js │ ├── GlobalSetting.js │ ├── LastOperation.js │ ├── Loading.js │ ├── NavigationBar.js │ ├── PopupMenu.js │ ├── Root.js │ ├── RootIntl.js │ ├── Settings.js │ ├── TextLoading.js │ ├── TransactionConfirm.js │ ├── Utility │ │ ├── AccountImage.js │ │ ├── AccountName.js │ │ ├── AssetName.js │ │ ├── BalanceComponent.js │ │ ├── BindToChainState.js │ │ ├── ChainTypes.js │ │ ├── FormattedAsset.js │ │ ├── FormattedPrice.js │ │ ├── Identicon.js │ │ ├── PriceText.js │ │ ├── TimeAgo.js │ │ ├── TotalBalanceValue.js │ │ ├── ValueComponent.js │ │ └── intlData.js │ ├── containers │ │ └── GlobalSettingContainer.js │ ├── dashboard │ │ ├── AccountList.js │ │ ├── AssetsItem.js │ │ ├── Balance.js │ │ ├── Dashboard.js │ │ └── Operation.js │ ├── form │ │ ├── XNFullButton.js │ │ ├── XNFullText.js │ │ ├── XNSelect.js │ │ └── XNSwitch.js │ ├── layout │ │ ├── Confirm.js │ │ └── Modal.js │ ├── scanit │ │ └── Scan.js │ ├── transaction │ │ ├── Buy.js │ │ ├── CanBuySell.js │ │ ├── CurrentBalance.js │ │ ├── History.js │ │ ├── MarketList.js │ │ ├── OrderBook.js │ │ ├── Orders.js │ │ ├── Sell.js │ │ ├── TabComponent.js │ │ ├── Transaction.js │ │ ├── TransactionContainer.js │ │ └── TransactionOperation.js │ └── wallet │ │ ├── AccountNameInput.js │ │ ├── AccountSelectInput.js │ │ ├── AmountSelectInput.js │ │ ├── Backup.js │ │ ├── ChangePassword.js │ │ ├── CreateAccount.js │ │ ├── ImportBackup.js │ │ ├── ImportKey.js │ │ ├── KeysView.js │ │ ├── PasswordInput.js │ │ ├── Transfer.js │ │ ├── UnlockWallet.js │ │ └── WalletManage.js ├── idb-helper.js ├── idb-instance.js ├── idb-root.js ├── main.js ├── stores │ ├── AccountRefsStore.js │ ├── AccountStore.js │ ├── AddressIndex.js │ ├── AssetStore.js │ ├── BackupStore.js │ ├── BalanceClaimActiveStore.js │ ├── BaseStore.js │ ├── CachedPropertyStore.js │ ├── ImportKeysStore.js │ ├── IntlStore.js │ ├── MarketsStore.js │ ├── NotificationStore.js │ ├── PrivateKeyStore.js │ ├── ScanStore.js │ ├── SettingsStore.js │ ├── TransactionConfirmStore.js │ ├── WalletDb.js │ ├── WalletManagerStore.js │ ├── WalletUnlockStore.js │ ├── layout │ │ └── ConfirmStore.js │ └── tcomb_structs.js └── workers │ ├── AddressIndexWorker.js │ ├── AesWorker.js │ └── GenesisFilterWorker.js ├── build ├── .gitignore ├── favicon.ico └── index.html ├── common ├── GenesisFilter.js ├── MarketClasses.js ├── account_constants.js ├── account_utils.js ├── altObj.js ├── asset_constants.js ├── asset_utils.js ├── dictionary_en.json ├── localStorage.js ├── localStorageImpl.js ├── market_utils.js └── utils.js ├── cordova ├── config.xml ├── release.bat ├── res │ ├── icon │ │ └── android │ │ │ ├── drawable-hdpi │ │ │ └── icon.png │ │ │ ├── drawable-ldpi │ │ │ └── icon.png │ │ │ ├── drawable-mdpi │ │ │ ├── bts.png │ │ │ └── icon.png │ │ │ └── drawable-xhdpi │ │ │ └── icon.png │ └── screen │ │ └── android │ │ ├── splash-land-hdpi.png │ │ ├── splash-land-ldpi.png │ │ ├── splash-land-mdpi.png │ │ ├── splash-land-xhdpi.png │ │ ├── splash-port-hdpi.png │ │ ├── splash-port-ldpi.png │ │ ├── splash-port-mdpi.png │ │ └── splash-port-xhdpi.png └── www │ └── index.html ├── package.json ├── webpack.config.js └── webpack.pro.config.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "es2015", 4 | "react", 5 | "stage-0" 6 | ], 7 | "ignore": [ 8 | "./common/dictionary_en.json" 9 | ], 10 | "plugins": [] 11 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | package-lock.json 3 | .DS_Store 4 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (C) 2017 necklace,and contributors. 2 | 3 | The MIT License 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # btsgo 2 | bitshares2网页钱包项目
3 | 项目基于bitsharesjs
4 | 开发环境:
5 | 1.Python27 编译node-sass时会用到
6 | 2.Git 用来下载github上的源码
7 | 3.WebStorm 代码编辑IDE
8 | 4.react+webpack 9 | 10 | ## Web版本地址:https://btsgo.net 11 | 12 | # 欢迎打赏BTS:xiangxn 13 | # 欢迎支持见证人:xn-delegate 14 | -------------------------------------------------------------------------------- /app/actions/AccountActions.js: -------------------------------------------------------------------------------- 1 | import alt from "../../common/altObj"; 2 | import accountUtils from "../../common/account_utils"; 3 | import AccountApi from "../api/accountApi"; 4 | 5 | import WalletApi from "../api/WalletApi"; 6 | import ApplicationApi from "../api/ApplicationApi"; 7 | import WalletDb from "../stores/WalletDb"; 8 | import WalletActions from "./WalletActions"; 9 | 10 | let accountSubs = {}; 11 | let accountLookup = {}; 12 | let accountSearch = {}; 13 | let wallet_api = new WalletApi(); 14 | let application_api = new ApplicationApi() 15 | let inProgress = {}; 16 | 17 | /** 18 | * @brief Actions that modify linked accounts 19 | * 20 | * @note this class also includes accountSearch actions which keep track of search result state. The presumption 21 | * is that there is only ever one active "search result" at a time. 22 | */ 23 | class AccountActions { 24 | 25 | /** 26 | * Account search results are not managed by the ChainStore cache so are 27 | * tracked as part of the AccountStore. 28 | */ 29 | accountSearch(start_symbol, limit = 50) { 30 | let uid = `${start_symbol}_${limit}}`; 31 | return (dispatch) => { 32 | if (!accountSearch[uid]) { 33 | accountSearch[uid] = true; 34 | return AccountApi.lookupAccounts(start_symbol, limit) 35 | .then(result => { 36 | accountSearch[uid] = false; 37 | dispatch({accounts: result, searchTerm: start_symbol}); 38 | }); 39 | } 40 | }; 41 | } 42 | 43 | /** 44 | * TODO: The concept of current accounts is deprecated and needs to be removed 45 | */ 46 | setCurrentAccount(name) { 47 | return name; 48 | } 49 | 50 | /** 51 | * TODO: This is a function of teh wallet_api and has no business being part of AccountActions 52 | */ 53 | transfer(from_account, to_account, amount, asset, memo, propose_account = null, fee_asset_id = "1.3.0") { 54 | // Set the fee asset to use 55 | fee_asset_id = accountUtils.getFinalFeeAsset(propose_account || from_account, "transfer", fee_asset_id); 56 | try { 57 | return (dispatch) => { 58 | return application_api.transfer({ 59 | from_account, to_account, amount, asset, memo, propose_account, fee_asset_id 60 | }).then(result => { 61 | // console.log( "transfer result: ", result ) 62 | 63 | dispatch(result); 64 | }); 65 | }; 66 | } catch (error) { 67 | console.log("[AccountActions.js:90] ----- transfer error ----->", error); 68 | return new Promise((resolve, reject) => { 69 | reject(error); 70 | }); 71 | } 72 | } 73 | 74 | /** 75 | * This method exists ont he AccountActions because after creating the account via the wallet, the account needs 76 | * to be linked and added to the local database. 77 | */ 78 | createAccount( 79 | account_name, 80 | registrar, 81 | referrer, 82 | referrer_percent, 83 | refcode 84 | ) { 85 | return (dispatch) => { 86 | return WalletActions.createAccount( 87 | account_name, 88 | registrar, 89 | referrer, 90 | referrer_percent, 91 | refcode 92 | ).then( () => { 93 | dispatch(account_name); 94 | return account_name; 95 | }); 96 | }; 97 | } 98 | 99 | /** 100 | * TODO: This is a function of the wallet_api and has no business being part of AccountActions, the account should already 101 | * be linked. 102 | */ 103 | upgradeAccount(account_id, lifetime) { 104 | // Set the fee asset to use 105 | let fee_asset_id = accountUtils.getFinalFeeAsset(account_id, "account_upgrade"); 106 | 107 | var tr = wallet_api.new_transaction(); 108 | tr.add_type_operation("account_upgrade", { 109 | "fee": { 110 | amount: 0, 111 | asset_id: fee_asset_id 112 | }, 113 | "account_to_upgrade": account_id, 114 | "upgrade_to_lifetime_member": lifetime 115 | }); 116 | return WalletDb.process_transaction(tr, null, true); 117 | } 118 | 119 | linkAccount(name) { 120 | return name; 121 | } 122 | 123 | unlinkAccount(name) { 124 | return name; 125 | } 126 | } 127 | 128 | export default alt.createActions(AccountActions); 129 | -------------------------------------------------------------------------------- /app/actions/BackupActions.js: -------------------------------------------------------------------------------- 1 | import alt from "../../common/altObj"; 2 | import iDB from "../idb-instance"; 3 | import {compress, decompress} from "lzma"; 4 | import {PrivateKey, PublicKey, Aes, key} from "bitsharesjs"; 5 | import WalletActions from "./WalletActions"; 6 | 7 | class BackupActions { 8 | 9 | incommingWebFile(file) { 10 | return (dispatch) => { 11 | let reader = new FileReader(); 12 | reader.onload = evt => { 13 | let contents = new Buffer(evt.target.result, "binary"); 14 | let name = file.name; 15 | let last_modified = file.lastModifiedDate.toString(); 16 | 17 | dispatch({name, contents, last_modified}); 18 | }; 19 | reader.readAsBinaryString(file); 20 | }; 21 | } 22 | 23 | incommingBuffer(params) { 24 | return params; 25 | } 26 | 27 | reset() { 28 | return true; 29 | } 30 | 31 | } 32 | 33 | let BackupActionsWrapped = alt.createActions(BackupActions); 34 | export default BackupActionsWrapped; 35 | 36 | export function backup(backup_pubkey) { 37 | return new Promise( resolve => { 38 | resolve(createWalletObject().then( wallet_object => { 39 | let compression = 1; 40 | return createWalletBackup(backup_pubkey, wallet_object, compression); 41 | })); 42 | }); 43 | } 44 | 45 | /** No click backup.. Works great, but not used (yet?) */ 46 | // export function backupToBin( 47 | // backup_pubkey = WalletDb.getWallet().password_pubkey, 48 | // saveAsCallback = saveAs 49 | // ) { 50 | // backup(backup_pubkey).then( contents => { 51 | // let name = iDB.getCurrentWalletName() + ".bin" 52 | // let blob = new Blob([ contents ], { 53 | // type: "application/octet-stream; charset=us-ascii"}) 54 | // 55 | // if(blob.size !== contents.length) 56 | // throw new Error("Invalid backup to download conversion") 57 | // 58 | // saveAsCallback(blob, name); 59 | // WalletActions.setBackupDate() 60 | // }) 61 | // } 62 | 63 | export function restore(backup_wif, backup, wallet_name) { 64 | return new Promise( resolve => { 65 | resolve(decryptWalletBackup(backup_wif, backup).then( wallet_object => { 66 | return WalletActions.restore(wallet_name, wallet_object); 67 | })); 68 | }); 69 | } 70 | 71 | export function createWalletObject() { 72 | return iDB.backup(); 73 | } 74 | 75 | /** 76 | compression_mode can be 1-9 (1 is fast and pretty good; 9 is slower and probably much better) 77 | */ 78 | export function createWalletBackup( 79 | backup_pubkey, wallet_object, compression_mode, entropy) { 80 | return new Promise( resolve => { 81 | let public_key = PublicKey.fromPublicKeyString(backup_pubkey); 82 | let onetime_private_key = key.get_random_key(entropy); 83 | let walletString = JSON.stringify(wallet_object, null, 0); 84 | compress(walletString, compression_mode, compressedWalletBytes => { 85 | let backup_buffer = 86 | Aes.encrypt_with_checksum(onetime_private_key, public_key, 87 | null/*nonce*/, compressedWalletBytes); 88 | 89 | let onetime_public_key = onetime_private_key.toPublicKey(); 90 | let backup = Buffer.concat([ onetime_public_key.toBuffer(), backup_buffer ]); 91 | resolve(backup); 92 | }); 93 | }); 94 | } 95 | 96 | export function decryptWalletBackup(backup_wif, backup_buffer) { 97 | return new Promise( (resolve, reject) => { 98 | if( ! Buffer.isBuffer(backup_buffer)) 99 | backup_buffer = new Buffer(backup_buffer, "binary"); 100 | 101 | let private_key = PrivateKey.fromWif(backup_wif); 102 | let public_key; 103 | try { 104 | public_key = PublicKey.fromBuffer(backup_buffer.slice(0, 33)); 105 | } catch(e) { 106 | console.error(e, e.stack); 107 | throw new Error("Invalid backup file"); 108 | } 109 | 110 | backup_buffer = backup_buffer.slice(33); 111 | try { 112 | backup_buffer = Aes.decrypt_with_checksum( 113 | private_key, public_key, null/*nonce*/, backup_buffer); 114 | } catch(error) { 115 | console.error("Error decrypting wallet", error, error.stack); 116 | reject("invalid_decryption_key"); 117 | return; 118 | } 119 | 120 | try { 121 | decompress(backup_buffer, wallet_string => { 122 | try { 123 | let wallet_object = JSON.parse(wallet_string); 124 | resolve(wallet_object); 125 | } catch(error) { 126 | if( ! wallet_string) wallet_string = ""; 127 | console.error("Error parsing wallet json", 128 | wallet_string.substring(0,10)+ "..."); 129 | reject("Error parsing wallet json"); 130 | } 131 | }); 132 | } catch(error) { 133 | console.error("Error decompressing wallet", error, error.stack); 134 | reject("Error decompressing wallet"); 135 | return; 136 | } 137 | }); 138 | } 139 | -------------------------------------------------------------------------------- /app/actions/BalanceClaimActiveActions.js: -------------------------------------------------------------------------------- 1 | import alt from "../../common/altObj" 2 | 3 | class BalanceClaimActiveActions { 4 | 5 | setPubkeys(pubkeys) { 6 | return pubkeys; 7 | } 8 | 9 | setSelectedBalanceClaims(selected_balances) { 10 | return selected_balances; 11 | } 12 | 13 | claimAccountChange(claim_account_name) { 14 | return claim_account_name; 15 | } 16 | 17 | } 18 | 19 | var BalanceClaimActiveActionsWrapped = alt.createActions(BalanceClaimActiveActions) 20 | export default BalanceClaimActiveActionsWrapped 21 | -------------------------------------------------------------------------------- /app/actions/CachedPropertyActions.js: -------------------------------------------------------------------------------- 1 | import alt from "../../common/altObj"; 2 | 3 | class CachedPropertyActions { 4 | 5 | set(name, value) { 6 | return { name, value }; 7 | } 8 | 9 | get(name) { 10 | return { name }; 11 | } 12 | 13 | reset() { 14 | return null; 15 | } 16 | 17 | } 18 | 19 | var CachedPropertyActionsWrapped = alt.createActions(CachedPropertyActions) 20 | export default CachedPropertyActionsWrapped -------------------------------------------------------------------------------- /app/actions/IntlActions.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by necklace on 2016/12/27. 3 | */ 4 | import alt from "../../common/altObj"; 5 | 6 | class IntlActions { 7 | switchLocale(locale) { 8 | return {locale}; 9 | } 10 | 11 | getLocale(locale) { 12 | return {locale}; 13 | } 14 | } 15 | 16 | export default alt.createActions(IntlActions); -------------------------------------------------------------------------------- /app/actions/NotificationActions.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by xiangxn on 2017/1/27. 3 | */ 4 | 5 | import alt from "../../common/altObj"; 6 | 7 | class NotificationActions { 8 | 9 | addNotification(notification) { 10 | notification = normalize(notification); 11 | return notification; 12 | } 13 | 14 | success(notification) { 15 | notification = normalize(notification, "success"); 16 | return notification; 17 | } 18 | 19 | error(notification) { 20 | notification = normalize(notification, "error"); 21 | return notification; 22 | } 23 | 24 | warning(notification) { 25 | notification = normalize(notification, "warning"); 26 | return notification; 27 | } 28 | 29 | info(notification) { 30 | notification = normalize(notification, "info"); 31 | return notification; 32 | } 33 | } 34 | 35 | export default alt.createActions(NotificationActions); 36 | 37 | var normalize = (notification, level) => { 38 | if(typeof notification == "string") 39 | notification = {message: notification}; 40 | if(level) 41 | notification.level = level; 42 | return notification; 43 | }; -------------------------------------------------------------------------------- /app/actions/PrivateKeyActions.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by necklace on 2016/12/28. 3 | */ 4 | import alt from '../../common/altObj'; 5 | 6 | class PrivateKeyActions { 7 | 8 | addKey(private_key_object, transaction) { 9 | return (dispatch) => { 10 | return new Promise( resolve => { 11 | dispatch({private_key_object, transaction, resolve}); 12 | }); 13 | }; 14 | } 15 | 16 | loadDbData() { 17 | return (dispatch) => { 18 | return new Promise( resolve => { 19 | dispatch(resolve); 20 | }); 21 | }; 22 | } 23 | } 24 | export default alt.createActions(PrivateKeyActions); -------------------------------------------------------------------------------- /app/actions/ScanActions.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by xiangxn on 2017/10/17. 3 | */ 4 | 5 | import alt from "../../common/altObj"; 6 | 7 | class ScanActions { 8 | 9 | /** 10 | * 开始扫码 11 | * @param routerState 当前路由状态uri 12 | */ 13 | scan(routerState) { 14 | return routerState; 15 | } 16 | 17 | /** 18 | * 设置扫描结果 19 | * @param qrStr 20 | * @return {*} 21 | */ 22 | setScanResult(qrStr) { 23 | return qrStr; 24 | } 25 | 26 | /** 27 | * 重置Scan状态;最好在使用完以后调用一下 28 | * @return {boolean} 29 | */ 30 | reset() { 31 | return true; 32 | } 33 | } 34 | 35 | export default alt.createActions(ScanActions); -------------------------------------------------------------------------------- /app/actions/SettingsActions.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by xiangxn on 2016/12/12. 3 | */ 4 | import alt from '../../common/altObj'; 5 | 6 | class SettingsActions { 7 | 8 | //修改设置 9 | changeSetting(value) { 10 | return value; 11 | } 12 | 13 | //添加api服务器 14 | addWS(ws) { 15 | return ws; 16 | } 17 | 18 | //移除api服务器 19 | removeWS(index) { 20 | return index; 21 | } 22 | 23 | //修改语言 24 | switchLocale(locale) { 25 | return {locale}; 26 | } 27 | 28 | //清除设置 29 | clearSettings() { 30 | return null; 31 | } 32 | 33 | //添加初始账号 34 | addStarAccount(account) { 35 | return account; 36 | } 37 | 38 | //删除初始账号 39 | removeStarAccount(account) { 40 | return account; 41 | } 42 | 43 | //改变资产方向 44 | changeMarketDirection(value) { 45 | return value; 46 | } 47 | } 48 | export default alt.createActions(SettingsActions); -------------------------------------------------------------------------------- /app/actions/TransactionConfirmActions.js: -------------------------------------------------------------------------------- 1 | import alt from "../../common/altObj"; 2 | import {ChainConfig} from "bitsharesjs-ws"; 3 | 4 | class TransactionConfirmActions { 5 | 6 | confirm(transaction) { 7 | return {transaction}; 8 | } 9 | 10 | broadcast(transaction) { 11 | return (dispatch) => { 12 | dispatch({broadcasting: true}); 13 | 14 | let broadcast_timeout = setTimeout(() => { 15 | this.error("Your transaction has expired without being confirmed, please try again later."); 16 | }, ChainConfig.expire_in_secs * 2000); 17 | 18 | transaction.broadcast(() => { 19 | dispatch({broadcasting: false, broadcast: true}); 20 | }).then( (res)=> { 21 | clearTimeout(broadcast_timeout); 22 | dispatch({ 23 | error: null, 24 | broadcasting: false, 25 | broadcast: true, 26 | included: true, 27 | trx_id: res[0].id, 28 | trx_block_num: res[0].block_num, 29 | broadcasted_transaction: true 30 | }); 31 | }).catch( error => { 32 | console.error(error); 33 | clearTimeout(broadcast_timeout); 34 | // messages of length 1 are local exceptions (use the 1st line) 35 | // longer messages are remote API exceptions (use the 2nd line) 36 | let splitError = error.message.split( "\n" ); 37 | let message = splitError[splitError.length === 1 ? 0 : 1]; 38 | dispatch({ 39 | broadcast: false, 40 | broadcasting: false, 41 | error: message 42 | }); 43 | }); 44 | }; 45 | } 46 | 47 | wasBroadcast(res){ 48 | return res; 49 | } 50 | 51 | wasIncluded(res){ 52 | return res; 53 | } 54 | 55 | close() { 56 | return true; 57 | } 58 | 59 | error(msg) { 60 | return {error: msg}; 61 | } 62 | 63 | togglePropose() { 64 | return true; 65 | } 66 | 67 | proposeFeePayingAccount(fee_paying_account) { 68 | return fee_paying_account; 69 | } 70 | } 71 | 72 | export default alt.createActions(TransactionConfirmActions); 73 | -------------------------------------------------------------------------------- /app/actions/WalletUnlockActions.js: -------------------------------------------------------------------------------- 1 | import alt from "../../common/altObj"; 2 | 3 | class WalletUnlockActions { 4 | 5 | /** If you get resolved then the wallet is or was just unlocked. If you get 6 | rejected then the wallet is still locked. 7 | 8 | @return nothing .. Just test for resolve() or reject() 9 | */ 10 | unlock() { 11 | return (dispatch) => { 12 | return new Promise( (resolve, reject) => { 13 | dispatch({resolve, reject}); 14 | }).then( was_unlocked => { 15 | //console.log('... WalletUnlockStore\tmodal unlock') 16 | if(was_unlocked) WrappedWalletUnlockActions.change(); 17 | }); 18 | }; 19 | } 20 | 21 | lock() { 22 | return (dispatch) => { 23 | return new Promise( resolve => { 24 | dispatch({resolve}); 25 | }).then( was_unlocked => { 26 | if(was_unlocked) WrappedWalletUnlockActions.change(); 27 | }); 28 | }; 29 | } 30 | 31 | cancel() { 32 | return true; 33 | } 34 | 35 | change() { 36 | return true; 37 | } 38 | 39 | } 40 | 41 | var WrappedWalletUnlockActions = alt.createActions(WalletUnlockActions) 42 | export default WrappedWalletUnlockActions 43 | -------------------------------------------------------------------------------- /app/actions/layout/ConfirmActions.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by necklace on 2017/3/17. 3 | */ 4 | import alt from "../../../common/altObj"; 5 | 6 | class ConfirmActions { 7 | show(title, msg, onok, oncancel, height, nocancel) { 8 | 9 | return {title, msg, onok, oncancel, height, nocancel}; 10 | } 11 | 12 | reset() { 13 | return true; 14 | } 15 | } 16 | 17 | let WrappedConfirmActions = alt.createActions(ConfirmActions) 18 | export default WrappedConfirmActions -------------------------------------------------------------------------------- /app/api/WalletApi.js: -------------------------------------------------------------------------------- 1 | import {SerializerValidation, TransactionBuilder, TransactionHelper} from "bitsharesjs"; 2 | import ApplicationApi from "./ApplicationApi"; 3 | 4 | class WalletApi { 5 | 6 | constructor() { 7 | this.application_api = new ApplicationApi() 8 | } 9 | 10 | new_transaction() { 11 | return new TransactionBuilder() 12 | } 13 | 14 | sign_and_broadcast( tr, broadcast = true ) { 15 | SerializerValidation.required(tr, "transaction") 16 | return WalletDb.process_transaction( 17 | tr, 18 | null, //signer_private_key, 19 | broadcast 20 | ) 21 | } 22 | 23 | /** Console print any transaction object with zero default values. */ 24 | template(transaction_object_name) { 25 | var object = TransactionHelper.template( 26 | transaction_object_name, 27 | {use_default: true, annotate: true} 28 | ) 29 | // visual 30 | console.error(JSON.stringify(object,null,4)) 31 | 32 | // usable 33 | object = TransactionHelper.template( 34 | transaction_object_name, 35 | {use_default: true, annotate: false} 36 | ) 37 | // visual 38 | console.error(JSON.stringify(object)) 39 | return object 40 | } 41 | 42 | transfer( 43 | from_account_id, 44 | to_account_id, 45 | amount, 46 | asset, 47 | memo_message, 48 | broadcast = true, 49 | encrypt_memo = true, 50 | optional_nonce = null 51 | ) { 52 | console.error("deprecated, call application_api.transfer instead") 53 | return this.application_api.transfer({ 54 | from_account_id, 55 | to_account_id, 56 | amount, 57 | asset, 58 | memo_message, 59 | broadcast, 60 | encrypt_memo, 61 | optional_nonce 62 | }) 63 | } 64 | 65 | } 66 | export default WalletApi 67 | -------------------------------------------------------------------------------- /app/api/accountApi.js: -------------------------------------------------------------------------------- 1 | import {ChainTypes} from "bitsharesjs"; 2 | import {Apis} from "bitsharesjs-ws"; 3 | 4 | let op_history = parseInt(ChainTypes.operation_history, 10); 5 | 6 | class Api { 7 | 8 | lookupAccounts(startChar, limit) { 9 | return Apis.instance().db_api().exec("lookup_accounts", [ 10 | startChar, limit 11 | ]); 12 | } 13 | } 14 | 15 | export default new Api(); 16 | -------------------------------------------------------------------------------- /app/assets/fonts/WebSymbols-Regular.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiangxn/btsgo/4f991511c371aaadb6556179e70611ebf7e6d579/app/assets/fonts/WebSymbols-Regular.otf -------------------------------------------------------------------------------- /app/assets/imgs/close.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiangxn/btsgo/4f991511c371aaadb6556179e70611ebf7e6d579/app/assets/imgs/close.png -------------------------------------------------------------------------------- /app/assets/imgs/open.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiangxn/btsgo/4f991511c371aaadb6556179e70611ebf7e6d579/app/assets/imgs/open.png -------------------------------------------------------------------------------- /app/assets/imgs/symbols/bkt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiangxn/btsgo/4f991511c371aaadb6556179e70611ebf7e6d579/app/assets/imgs/symbols/bkt.png -------------------------------------------------------------------------------- /app/assets/imgs/symbols/blockpay.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiangxn/btsgo/4f991511c371aaadb6556179e70611ebf7e6d579/app/assets/imgs/symbols/blockpay.png -------------------------------------------------------------------------------- /app/assets/imgs/symbols/btc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiangxn/btsgo/4f991511c371aaadb6556179e70611ebf7e6d579/app/assets/imgs/symbols/btc.png -------------------------------------------------------------------------------- /app/assets/imgs/symbols/bts.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiangxn/btsgo/4f991511c371aaadb6556179e70611ebf7e6d579/app/assets/imgs/symbols/bts.png -------------------------------------------------------------------------------- /app/assets/imgs/symbols/btsr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiangxn/btsgo/4f991511c371aaadb6556179e70611ebf7e6d579/app/assets/imgs/symbols/btsr.png -------------------------------------------------------------------------------- /app/assets/imgs/symbols/btwty.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiangxn/btsgo/4f991511c371aaadb6556179e70611ebf7e6d579/app/assets/imgs/symbols/btwty.png -------------------------------------------------------------------------------- /app/assets/imgs/symbols/cny.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiangxn/btsgo/4f991511c371aaadb6556179e70611ebf7e6d579/app/assets/imgs/symbols/cny.png -------------------------------------------------------------------------------- /app/assets/imgs/symbols/dao.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiangxn/btsgo/4f991511c371aaadb6556179e70611ebf7e6d579/app/assets/imgs/symbols/dao.png -------------------------------------------------------------------------------- /app/assets/imgs/symbols/dash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiangxn/btsgo/4f991511c371aaadb6556179e70611ebf7e6d579/app/assets/imgs/symbols/dash.png -------------------------------------------------------------------------------- /app/assets/imgs/symbols/dct.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiangxn/btsgo/4f991511c371aaadb6556179e70611ebf7e6d579/app/assets/imgs/symbols/dct.png -------------------------------------------------------------------------------- /app/assets/imgs/symbols/dgd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiangxn/btsgo/4f991511c371aaadb6556179e70611ebf7e6d579/app/assets/imgs/symbols/dgd.png -------------------------------------------------------------------------------- /app/assets/imgs/symbols/eth.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiangxn/btsgo/4f991511c371aaadb6556179e70611ebf7e6d579/app/assets/imgs/symbols/eth.png -------------------------------------------------------------------------------- /app/assets/imgs/symbols/eur.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiangxn/btsgo/4f991511c371aaadb6556179e70611ebf7e6d579/app/assets/imgs/symbols/eur.png -------------------------------------------------------------------------------- /app/assets/imgs/symbols/eurt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiangxn/btsgo/4f991511c371aaadb6556179e70611ebf7e6d579/app/assets/imgs/symbols/eurt.png -------------------------------------------------------------------------------- /app/assets/imgs/symbols/game.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiangxn/btsgo/4f991511c371aaadb6556179e70611ebf7e6d579/app/assets/imgs/symbols/game.png -------------------------------------------------------------------------------- /app/assets/imgs/symbols/gold.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiangxn/btsgo/4f991511c371aaadb6556179e70611ebf7e6d579/app/assets/imgs/symbols/gold.png -------------------------------------------------------------------------------- /app/assets/imgs/symbols/grc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiangxn/btsgo/4f991511c371aaadb6556179e70611ebf7e6d579/app/assets/imgs/symbols/grc.png -------------------------------------------------------------------------------- /app/assets/imgs/symbols/hempsweet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiangxn/btsgo/4f991511c371aaadb6556179e70611ebf7e6d579/app/assets/imgs/symbols/hempsweet.png -------------------------------------------------------------------------------- /app/assets/imgs/symbols/icoo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiangxn/btsgo/4f991511c371aaadb6556179e70611ebf7e6d579/app/assets/imgs/symbols/icoo.png -------------------------------------------------------------------------------- /app/assets/imgs/symbols/incnt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiangxn/btsgo/4f991511c371aaadb6556179e70611ebf7e6d579/app/assets/imgs/symbols/incnt.png -------------------------------------------------------------------------------- /app/assets/imgs/symbols/lisk.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiangxn/btsgo/4f991511c371aaadb6556179e70611ebf7e6d579/app/assets/imgs/symbols/lisk.png -------------------------------------------------------------------------------- /app/assets/imgs/symbols/mkr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiangxn/btsgo/4f991511c371aaadb6556179e70611ebf7e6d579/app/assets/imgs/symbols/mkr.png -------------------------------------------------------------------------------- /app/assets/imgs/symbols/nxc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiangxn/btsgo/4f991511c371aaadb6556179e70611ebf7e6d579/app/assets/imgs/symbols/nxc.png -------------------------------------------------------------------------------- /app/assets/imgs/symbols/obits.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiangxn/btsgo/4f991511c371aaadb6556179e70611ebf7e6d579/app/assets/imgs/symbols/obits.png -------------------------------------------------------------------------------- /app/assets/imgs/symbols/open.btc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiangxn/btsgo/4f991511c371aaadb6556179e70611ebf7e6d579/app/assets/imgs/symbols/open.btc.png -------------------------------------------------------------------------------- /app/assets/imgs/symbols/peerplays.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiangxn/btsgo/4f991511c371aaadb6556179e70611ebf7e6d579/app/assets/imgs/symbols/peerplays.png -------------------------------------------------------------------------------- /app/assets/imgs/symbols/steem.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiangxn/btsgo/4f991511c371aaadb6556179e70611ebf7e6d579/app/assets/imgs/symbols/steem.png -------------------------------------------------------------------------------- /app/assets/imgs/symbols/usd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiangxn/btsgo/4f991511c371aaadb6556179e70611ebf7e6d579/app/assets/imgs/symbols/usd.png -------------------------------------------------------------------------------- /app/assets/imgs/symbols/usdt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiangxn/btsgo/4f991511c371aaadb6556179e70611ebf7e6d579/app/assets/imgs/symbols/usdt.png -------------------------------------------------------------------------------- /app/assets/loader.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by necklace on 2016/12/17. 3 | */ 4 | import './styles/app.scss'; 5 | import './styles/symbols.scss'; 6 | import "file-loader?name=dictionary.json!../../common/dictionary_en.json"; 7 | import "whatwg-fetch"; -------------------------------------------------------------------------------- /app/assets/styles/define.scss: -------------------------------------------------------------------------------- 1 | //定义主色调 2 | $blueColor: #12b7f5 !default; 3 | $orangeRedColor: #ff8c00 !default; 4 | $greenColor: #228b22 !default; 5 | $whiteColor: #ffffff !default; 6 | $blackColor: #666666 !default; 7 | $blackTColor: rgba(102, 102, 102, 0.9); 8 | $whiteTColor:rgba(255, 255, 255, 0.6); 9 | $grayColor: #cccccc !default; 10 | $bgGrayColor: #dddddd !default; 11 | 12 | //定义字体大小 13 | $icoFontSize: .4rem; 14 | $titleFontSize: .3rem; 15 | $fontSize: .24rem; 16 | $middleSize: .2rem; 17 | $smallFontSize: .18rem; 18 | 19 | //定义图标字体 20 | @font-face { 21 | font-family: 'WebSymbols-Regular'; 22 | src: url('../fonts/WebSymbols-Regular.otf'); 23 | } 24 | 25 | $basefamily: \5FAE\8F6F\96C5\9ED1; -------------------------------------------------------------------------------- /app/assets/styles/flip.scss: -------------------------------------------------------------------------------- 1 | @keyframes modal-flip-enter { 2 | from { 3 | transform: perspective(4rem) rotate3d(1, 0, 0, 80deg); 4 | } 5 | 70% { 6 | transform: perspective(4rem) rotate3d(1, 0, 0, -15deg); 7 | } 8 | to { 9 | transform: perspective(4rem); 10 | } 11 | } 12 | .modal-flip-enter { 13 | animation: modal-flip-enter both ease-in; 14 | backface-visibility: visible !important; 15 | } 16 | @keyframes modal-flip-leave { 17 | from { 18 | transform: perspective(4rem); 19 | } 20 | 30% { 21 | transform: perspective(4rem) rotate3d(1, 0, 0, -15deg); 22 | } 23 | to { 24 | transform: perspective(4rem) rotate3d(1, 0, 0, 80deg); 25 | } 26 | } 27 | .modal-flip-leave { 28 | animation: modal-flip-leave both; 29 | backface-visibility: visible !important; 30 | } -------------------------------------------------------------------------------- /app/assets/styles/modal.scss: -------------------------------------------------------------------------------- 1 | .modal, 2 | .modal-mask { 3 | top: 0; 4 | left: 0; 5 | width: 100%; 6 | height: 100%; 7 | z-index: 2000; 8 | } 9 | 10 | .modal { 11 | position: fixed; 12 | font-size: $fontSize; 13 | } 14 | 15 | /* -- mask -- */ 16 | .modal-mask { 17 | position: absolute; 18 | background: rgba(0, 0, 0, .3); 19 | } 20 | 21 | /* -- dialog -- */ 22 | .modal-dialog { 23 | position: absolute; 24 | top: 0; 25 | left: 0; 26 | right: 0; 27 | bottom: 0; 28 | margin: auto; 29 | z-index: 2001; 30 | padding: .15rem; 31 | background: #fff; 32 | border-radius: .04rem; 33 | box-shadow: 0 .01rem .03rem rgba(0, 0, 0, .2); 34 | } 35 | 36 | /* -- close button -- */ 37 | .modal-close { 38 | position: absolute; 39 | cursor: pointer; 40 | top: .1rem; 41 | right: .1rem; 42 | width: .32rem; 43 | height: .32rem; 44 | } 45 | 46 | .modal-close:before, 47 | .modal-close:after { 48 | position: absolute; 49 | content: ''; 50 | height: .02rem; 51 | width: 100%; 52 | top: 50%; 53 | left: 0; 54 | margin-top: -.01px; 55 | background: #999; 56 | border-radius: 100%; 57 | transition: background .2s; 58 | } 59 | 60 | .modal-close:before { 61 | transform: rotate(45deg); 62 | } 63 | 64 | .modal-close:after { 65 | transform: rotate(-45deg); 66 | } 67 | 68 | .modal-close:hover:before, 69 | .modal-close:hover:after { 70 | background: #333; 71 | } 72 | //翻转动画 73 | @import "flip"; -------------------------------------------------------------------------------- /app/assets/styles/symbols.scss: -------------------------------------------------------------------------------- 1 | // Core asset 2 | .bts { 3 | background-image: url("../imgs/symbols/bts.png"); 4 | } 5 | // BitAssets 6 | .usd { 7 | background-image: url("../imgs/symbols/usd.png"); 8 | } 9 | .eur { 10 | background-image: url("../imgs/symbols/eur.png"); 11 | } 12 | .cny { 13 | background-image: url("../imgs/symbols/cny.png"); 14 | } 15 | .gold { 16 | background-image: url("../imgs/symbols/gold.png"); 17 | } 18 | .btc { 19 | background-image: url("../imgs/symbols/btc.png"); 20 | } 21 | .silver { 22 | background-image: url("../imgs/symbols/bts.png"); 23 | } 24 | // 3rd party assets 25 | .eth { 26 | background-image: url("../imgs/symbols/eth.png"); 27 | } 28 | .steem { 29 | background-image: url("../imgs/symbols/steem.png"); 30 | } 31 | .mkr { 32 | background-image: url("../imgs/symbols/mkr.png"); 33 | } 34 | .dgd { 35 | background-image: url("../imgs/symbols/dgd.png"); 36 | } 37 | .obits { 38 | background-image: url("../imgs/symbols/obits.png"); 39 | } 40 | .btsr { 41 | background-image: url("../imgs/symbols/btsr.png"); 42 | } 43 | .dao { 44 | background-image: url("../imgs/symbols/dao.png"); 45 | } 46 | .lisk { 47 | background-image: url("../imgs/symbols/lisk.png"); 48 | } 49 | .peerplays { 50 | background-image: url("../imgs/symbols/peerplays.png"); 51 | } 52 | .icoo { 53 | background-image: url("../imgs/symbols/icoo.png"); 54 | } 55 | .blockpay { 56 | background-image: url("../imgs/symbols/blockpay.png"); 57 | } 58 | .dash { 59 | background-image: url("../imgs/symbols/dash.png"); 60 | } 61 | .eurt { 62 | background-image: url("../imgs/symbols/eurt.png"); 63 | } 64 | .game { 65 | background-image: url("../imgs/symbols/game.png"); 66 | } 67 | .grc { 68 | background-image: url("../imgs/symbols/grc.png"); 69 | } 70 | .usdt { 71 | background-image: url("../imgs/symbols/usdt.png"); 72 | } 73 | .bkt { 74 | background-image: url("../imgs/symbols/bkt.png"); 75 | } 76 | .dct { 77 | background-image: url("../imgs/symbols/dct.png"); 78 | } 79 | .incnt { 80 | background-image: url("../imgs/symbols/incnt.png"); 81 | } 82 | .nxc { 83 | background-image: url("../imgs/symbols/nxc.png"); 84 | } 85 | .btwty { 86 | background-image: url("../imgs/symbols/btwty.png"); 87 | } 88 | .open-btc { 89 | background-image: url("../imgs/symbols/open.btc.png"); 90 | } 91 | .hempsweet { 92 | background-image: url("../imgs/symbols/hempsweet.png"); 93 | } -------------------------------------------------------------------------------- /app/components/BaseComponent.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by necklace on 2017/1/14. 3 | */ 4 | import React from "react"; 5 | import {intlShape} from 'react-intl'; 6 | 7 | /** 8 | * 核心资产ID 9 | * @type {string} 10 | */ 11 | export const CORE_ASSET_ID = "1.3.0"; 12 | 13 | /** 14 | * 组件基类 15 | * 简单封装了intl实例和router对象 16 | */ 17 | class BaseComponent extends React.Component { 18 | static contextTypes = { 19 | intl: intlShape.isRequired, 20 | router: React.PropTypes.object 21 | }; 22 | 23 | constructor(props) { 24 | super(props); 25 | this.formatMessage = this.formatMessage.bind(this); 26 | } 27 | 28 | formatMessage(id) { 29 | //console.debug('arguments.length',arguments.length); 30 | let values = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; 31 | //console.debug(values) 32 | return this.context.intl.formatMessage({id: id}, values); 33 | } 34 | } 35 | 36 | export default BaseComponent; -------------------------------------------------------------------------------- /app/components/Blockchain/BlockTime.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import BindToChainState from "../Utility/BindToChainState"; 3 | import ChainTypes from "../Utility/ChainTypes"; 4 | import TimeAgo from "../Utility/TimeAgo"; 5 | import utils from "../../../common/utils"; 6 | 7 | class BlockTime extends React.Component { 8 | 9 | static propTypes = { 10 | block_number: React.PropTypes.number.isRequired, 11 | globalObject: ChainTypes.ChainObject.isRequired, 12 | dynGlobalObject: ChainTypes.ChainObject.isRequired 13 | }; 14 | 15 | static defaultProps = { 16 | globalObject: "2.0.0", 17 | dynGlobalObject: "2.1.0" 18 | }; 19 | 20 | constructor(props) { 21 | super(props); 22 | this.state = {time: null}; 23 | } 24 | 25 | componentDidMount() { 26 | this.calcTime(this.props.block_number); 27 | } 28 | 29 | calcTime(block_number) { 30 | this.setState({time: utils.calc_block_time(block_number, this.props.globalObject, this.props.dynGlobalObject)}); 31 | } 32 | 33 | componentWillReceiveProps(next_props) { 34 | if(next_props.block_number !== this.props.block_number) { 35 | this.calcTime(next_props.block_number); 36 | } 37 | } 38 | 39 | /* 40 | shouldComponentUpdate(next_props, next_state) { 41 | return next_props.block_number !== this.props.block_number || next_state.time !== this.state.time; 42 | } 43 | */ 44 | 45 | //{this.state.time ? : null} 46 | render() { 47 | return ( 48 | 49 | {this.state.time ? : null } 50 | 51 | ); 52 | } 53 | } 54 | BlockTime = BindToChainState(BlockTime, {keep_updating: true}); 55 | 56 | export default BlockTime; 57 | -------------------------------------------------------------------------------- /app/components/Blockchain/MemoInfo.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by xiangxn on 2017/2/16. 3 | */ 4 | import React from "react"; 5 | import BaseComponent from "../BaseComponent"; 6 | import utils from "../../../common/utils"; 7 | import connectToStores from 'alt-utils/lib/connectToStores'; 8 | 9 | //stores 10 | import PrivateKeyStore from "../../stores/PrivateKeyStore"; 11 | import WalletUnlockStore from "../../stores/WalletUnlockStore"; 12 | 13 | //actions 14 | import WalletUnlockActions from "../../actions/WalletUnlockActions"; 15 | 16 | class MemoInfo extends BaseComponent { 17 | static defaultProps = { 18 | fullLength: false 19 | }; 20 | 21 | constructor(props) { 22 | super(props); 23 | } 24 | 25 | shouldComponentUpdate(nextProps) { 26 | return ( 27 | !utils.are_equal_shallow(nextProps.memo, this.props.memo) || 28 | nextProps.wallet_locked !== this.props.wallet_locked 29 | ); 30 | } 31 | 32 | unLock(e) { 33 | e.preventDefault(); 34 | WalletUnlockActions.unlock().then(() => { 35 | this.forceUpdate(); 36 | }) 37 | } 38 | 39 | render() { 40 | let {memo, fullLength} = this.props; 41 | if (!memo) { 42 | return null; 43 | } 44 | 45 | let {text, isMine} = PrivateKeyStore.decodeMemo(memo); 46 | 47 | if (!text && isMine) { 48 | return ( 49 | 50 | {this.formatMessage('transfer_memoUnlock')} 51 | 52 | ); 53 | } 54 | let full_memo = text; 55 | if (text && !fullLength && text.length > 35) { 56 | text = text.substr(0, 35) + "..."; 57 | } 58 | 59 | if (text) { 60 | return ( 61 | 62 | 63 | {text} 64 | 65 | 66 | ); 67 | } else { 68 | return null; 69 | } 70 | } 71 | } 72 | 73 | class MemoInfoWrapper extends React.Component { 74 | static getPropsFromStores() { 75 | return {wallet_locked: WalletUnlockStore.getState().locked}; 76 | } 77 | 78 | static getStores() { 79 | return [WalletUnlockStore]; 80 | } 81 | 82 | render() { 83 | return ; 84 | } 85 | } 86 | export default connectToStores(MemoInfoWrapper); -------------------------------------------------------------------------------- /app/components/LastOperation.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by necklace on 2017/1/17. 3 | */ 4 | import React from "react"; 5 | import BaseComponent from "./BaseComponent"; 6 | import {ChainTypes as grapheneChainTypes} from "bitsharesjs"; 7 | import AltContainer from "alt-container"; 8 | import {RecentTransactions} from "./dashboard/Operation"; 9 | 10 | //stores 11 | import AccountStore from "../stores/AccountStore"; 12 | 13 | 14 | const {operations} = grapheneChainTypes; 15 | 16 | 17 | 18 | class LastOperation extends BaseComponent { 19 | constructor(props) { 20 | super(props); 21 | } 22 | getHistory(accountsList, filterOp, customFilter) { 23 | let history = []; 24 | let seen_ops = new Set(); 25 | for (let account of accountsList) { 26 | if(account) { 27 | let h = account.get("history"); 28 | if (h) history = history.concat(h.toJS().filter(op => !seen_ops.has(op.id) && seen_ops.add(op.id))); 29 | } 30 | } 31 | if (filterOp) { 32 | history = history.filter(a => { 33 | return a.op[0] === operations[filterOp]; 34 | }); 35 | } 36 | 37 | if (customFilter) { 38 | history = history.filter(a => { 39 | let finalValue = customFilter.fields.reduce((final, filter) => { 40 | switch (filter) { 41 | case "asset_id": 42 | return final && a.op[1]["amount"][filter] === customFilter.values[filter]; 43 | break; 44 | default: 45 | return final && a.op[1][filter] === customFilter.values[filter]; 46 | break; 47 | } 48 | }, true) 49 | return finalValue; 50 | }); 51 | } 52 | return history; 53 | } 54 | 55 | render() { 56 | let {linkedAccounts} = this.props; 57 | let accountCount = linkedAccounts.size; 58 | 59 | return ( 60 |
61 |
62 |
63 | {this.formatMessage("lastOperation_operation")} 64 | {this.formatMessage("lastOperation_info")} 65 |
66 |
67 | {accountCount ? : 71 |
72 |
73 | } 74 |
75 |
76 | ); 77 | } 78 | } 79 | 80 | class LastOperationContainer extends React.Component { 81 | render() { 82 | return ( 83 | { 87 | return AccountStore.getState().linkedAccounts; 88 | } 89 | }}> 90 | 91 | 92 | ); 93 | } 94 | } 95 | export default LastOperationContainer; -------------------------------------------------------------------------------- /app/components/Loading.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by necklace on 2016/12/21. 3 | */ 4 | import React from 'react'; 5 | 6 | 7 | class Loading extends React.Component { 8 | constructor(props) { 9 | super(props); 10 | this.state = {progress: 0}; 11 | } 12 | 13 | componentDidMount() { 14 | this.timer = setInterval(() => { 15 | let p = this.state.progress; 16 | p += 1; 17 | if (p > 7) p = 0; 18 | this.setState({progress: p}); 19 | }, 70); 20 | } 21 | 22 | componentWillUnmount() { 23 | this.timer && clearTimeout(this.timer); 24 | this.timer = null; 25 | } 26 | 27 | render() { 28 | return ( 29 |
30 |
{this.state.progress}
31 |
32 | ); 33 | } 34 | } 35 | 36 | export default Loading; -------------------------------------------------------------------------------- /app/components/NavigationBar.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by xiangxn on 2016/12/10. 3 | */ 4 | import React from 'react'; 5 | import BaseComponent from "./BaseComponent"; 6 | import AltContainer from "alt-container"; 7 | import WalletUnlockStore from "../stores/WalletUnlockStore"; 8 | import WalletDb from "../stores/WalletDb"; 9 | 10 | //actions 11 | import WalletUnlockActions from "../actions/WalletUnlockActions"; 12 | import PopupMenu, {menuItems} from './PopupMenu'; 13 | import ScanActions from "../actions/ScanActions"; 14 | 15 | class NavigationBar extends BaseComponent { 16 | 17 | constructor(props) { 18 | super(props); 19 | this.state = { 20 | menuTop: '0', 21 | menuLeft: '0', 22 | isShowMenu: false 23 | }; 24 | } 25 | 26 | showMenu() { 27 | let rect = this.refs.menuBtn.getBoundingClientRect(); 28 | /* 29 | let menu = this.refs.mainMenu; 30 | menu.setState({top: rect.top + rect.height - 30, left: rect.left - 145, isShow: !menu.state.isShow}); 31 | */ 32 | //console.debug(rect); 33 | this.setState({ 34 | menuTop: '.6rem',//(rect.top + rect.height) / 100, 35 | menuLeft: '.12rem'// (rect.left + rect.width) / 100, 36 | }); 37 | let menuState = this.refs.menu.state; 38 | this.refs.menu.setState({isShow: !menuState.isShow}); 39 | } 40 | 41 | onMenuClick(data) { 42 | console.debug(data); 43 | //this.setState({isShow: false}); 44 | //browserHistory.push(data.url); 45 | } 46 | 47 | onMenuItemClick(data) { 48 | //console.debug(data); 49 | if (data.url === 'reload') { 50 | window.location.reload(); 51 | } else if (data.url === 'scan') { 52 | ScanActions.scan(this.context.router.location); 53 | this.context.router.push('/' + data.url); 54 | } 55 | } 56 | 57 | onBackClick() { 58 | this.context.router.goBack(); 59 | } 60 | 61 | onUnlockClick(e) { 62 | e.preventDefault(); 63 | let wallet = WalletDb.getWallet(); 64 | if (!wallet) { 65 | this.context.router.push('/create-account'); 66 | return; 67 | } 68 | if (WalletDb.isLocked()) WalletUnlockActions.unlock(); 69 | else WalletUnlockActions.lock(); 70 | } 71 | 72 | getTitle() { 73 | let url = this.context.router.location.pathname; 74 | if (url === "/") { 75 | return this.context.intl.formatMessage({id: "menu_index"}); 76 | } 77 | if(url==="/init-error"){ 78 | return this.context.intl.formatMessage({id:"menu_settings"}); 79 | } 80 | url = url.substring(1); 81 | let menu = menuItems.find((item) => { 82 | if (item.url === "/") return false; 83 | return url.startsWith(item.url.substring(1)); 84 | }); 85 | if (menu !== undefined) { 86 | return this.context.intl.formatMessage({id: menu.name}); 87 | } else { 88 | return null; 89 | } 90 | } 91 | 92 | //SettingsStore.getSetting('apiServer') 93 | //()=>SettingsActions.changeSetting({setting: "locale", value: "cn"}) 94 | 95 | render() { 96 | let titleClass = "top-title"; 97 | if (this.context.router.location.pathname == "/init-error") { 98 | return ( 99 |
100 |
{this.getTitle()}
101 |
102 | ); 103 | } 104 | let props = this.props; 105 | //console.debug(this.context.router); 106 | let backBtn = null; 107 | if (this.context.router.location.pathname !== "/") { 108 | backBtn = (
<
); 109 | } else { 110 | titleClass = "top-left-title"; 111 | } 112 | return ( 113 |
114 |
{this.getTitle()}
115 | {backBtn} 116 |
117 |
118 | {this.props.locked ? 'x' : 'w'} 119 |
120 |
p
121 |
122 | 125 |
126 | ); 127 | } 128 | } 129 | 130 | class NavigationBarContainer extends React.Component { 131 | render() { 132 | return ( 133 | 134 | 135 | 136 | ) 137 | } 138 | } 139 | 140 | export default NavigationBarContainer -------------------------------------------------------------------------------- /app/components/PopupMenu.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by necklace on 2016/12/20. 3 | */ 4 | import React from 'react'; 5 | import {injectIntl, intlShape, FormattedMessage} from 'react-intl'; 6 | 7 | export const menuItems = [ 8 | {name: 'menu_index', url: '/'}, 9 | {name: 'menu_transaction', url: '/markets'},//transaction 10 | {name: 'menu_transfer', url: '/transfer'}, 11 | {name: 'menu_lastOperate', url: '/last-operate'}, 12 | {name: 'menu_scan', url: 'scan'}, 13 | {name: 'menu_createAccount', url: '/create-account'}, 14 | {name: 'menu_settings', url: '/settings'}, 15 | {name: "menu_reload", url: 'reload'} 16 | ]; 17 | 18 | class PopupMenu extends React.Component { 19 | constructor(props) { 20 | super(props); 21 | this.state = {isShow: false}; 22 | this.onDocumentClick = this.onDocumentClick.bind(this); 23 | } 24 | 25 | componentDidUpdate(prevProps, prevState) { 26 | if (this.state.isShow) { 27 | document.addEventListener('click', this.onDocumentClick, false); 28 | } else { 29 | document.removeEventListener('click', this.onDocumentClick, false); 30 | } 31 | } 32 | 33 | onMenuClick(data, e) { 34 | e.nativeEvent.stopImmediatePropagation(); 35 | this.setState({isShow: false}); 36 | //browserHistory.push(data.url); 37 | if (data.url.startsWith('/')) { 38 | this.context.router.push(data.url); 39 | } 40 | else { 41 | this.props.onMenuItemClick && this.props.onMenuItemClick(data); 42 | } 43 | } 44 | 45 | onDocumentClick(e) { 46 | this.setState({isShow: false}); 47 | } 48 | 49 | render() { 50 | let props = this.props; 51 | //let omc = props.onItemClick || this.onMenuClick.bind(this); 52 | if (this.state.isShow) { 53 | return ( 54 |
55 |
56 |
57 |
    58 | {menuItems.map((item) => { 59 | return (
  • 60 |
    {this.context.intl.formatMessage({id: item.name})}
    61 |
  • ); 62 | })} 63 |
64 |
65 |
66 | ); 67 | } else { 68 | return null; 69 | } 70 | } 71 | } 72 | PopupMenu.propTypes = { 73 | top: React.PropTypes.string, 74 | left: React.PropTypes.string, 75 | onMenuItemClick: React.PropTypes.func 76 | }; 77 | PopupMenu.contextTypes = { 78 | intl: intlShape.isRequired, 79 | router: React.PropTypes.object 80 | }; 81 | PopupMenu.defaultProps = { 82 | top: 0, 83 | left: 0, 84 | onMenuItemClick: null 85 | }; 86 | 87 | 88 | export default PopupMenu; -------------------------------------------------------------------------------- /app/components/Root.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | //stores 4 | import AccountStore from "../stores/AccountStore"; 5 | import SettingsStore from '../stores/SettingsStore'; 6 | import NotificationStore from "../stores/NotificationStore"; 7 | 8 | import {ChainStore} from "bitsharesjs"; 9 | import {Apis} from "bitsharesjs-ws"; 10 | 11 | //组件 12 | import Loading from './Loading'; 13 | import NavigationBar from './NavigationBar'; 14 | import NotificationSystem from "react-notification-system"; 15 | import TransactionConfirm from "./TransactionConfirm"; 16 | import UnlockWallet from "./wallet/UnlockWallet"; 17 | import Settings from './Settings'; 18 | import Confirm from './layout/Confirm'; 19 | import GlobalSettingContainer from './containers/GlobalSettingContainer'; 20 | 21 | class Root extends React.Component { 22 | constructor(props) { 23 | super(props); 24 | this.state = { 25 | loading: true, 26 | synced: false, 27 | syncFail: false, 28 | disableChat: SettingsStore.getState().settings.get("disableChat", true) 29 | }; 30 | } 31 | 32 | onNotificationChange() { 33 | let notification = NotificationStore.getState().notification; 34 | if (notification.autoDismiss === void 0) { 35 | notification.autoDismiss = 10; 36 | } 37 | if (this.refs.notificationSystem) this.refs.notificationSystem.addNotification(notification); 38 | } 39 | 40 | componentWillUnmount() { 41 | NotificationStore.unlisten(this.onNotificationChange); 42 | SettingsStore.unlisten(this.onSettingsChange); 43 | } 44 | 45 | componentDidMount() { 46 | try { 47 | NotificationStore.listen(this.onNotificationChange.bind(this)); 48 | SettingsStore.listen(this.onSettingsChange.bind(this)); 49 | ChainStore.init().then(() => { 50 | this.setState({synced: true}); 51 | Promise.all([ 52 | AccountStore.loadDbData() 53 | ]).then(() => { 54 | AccountStore.tryToSetCurrentAccount(); 55 | this.setState({loading: false, syncFail: false}); 56 | }).catch(error => { 57 | console.log("[Root.js] ----- ERROR ----->", error); 58 | this.setState({loading: false}); 59 | }); 60 | }).catch(error => { 61 | console.log("[App.jsx] ----- ChainStore.init error ----->", error); 62 | let syncFail = error.message === "ChainStore sync error, please check your system clock" ? true : false; 63 | this.setState({loading: false, syncFail}); 64 | }); 65 | 66 | } catch (e) { 67 | console.error("error:", e); 68 | } 69 | } 70 | 71 | onSettingsChange() { 72 | let {settings} = SettingsStore.getState(); 73 | if (settings.get("disableChat") !== this.state.disableChat) { 74 | this.setState({ 75 | disableChat: settings.get("disableChat") 76 | }); 77 | } 78 | } 79 | 80 | render() { 81 | let {disableChat, syncFail, loading} = this.state; 82 | let content = null; 83 | if (syncFail) { 84 | content = ( 85 | 86 | 87 | 88 | ); 89 | } else if (loading) { 90 | content = ; 91 | } else { 92 | content = ( 93 |
94 | 95 | {this.props.children} 96 | 97 | 98 | 99 | 100 |
101 | ); 102 | } 103 | //console.debug(content); 104 | return content; 105 | } 106 | } 107 | 108 | export default Root; -------------------------------------------------------------------------------- /app/components/RootIntl.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by necklace on 2016/12/27. 3 | */ 4 | 5 | import React from 'react'; 6 | import connectToStores from 'alt-utils/lib/connectToStores'; 7 | import IntlStore from '../stores/IntlStore'; 8 | import IntlActions from '../actions/IntlActions'; 9 | import {IntlProvider} from 'react-intl'; 10 | import Root from './Root'; 11 | import intlData from "./Utility/intlData"; 12 | 13 | class RootIntl extends React.Component { 14 | static getStores() { 15 | return [IntlStore]; 16 | }; 17 | 18 | static getPropsFromStores() { 19 | let state = IntlStore.getState(); 20 | return { 21 | locale: state.currentLocale, 22 | localeObj: state.localesObject[state.currentLocale] 23 | }; 24 | }; 25 | 26 | componentDidMount() { 27 | IntlActions.switchLocale(this.props.locale); 28 | } 29 | 30 | render() { 31 | //console.debug('this.props.localeObj',this.props.localeObj); 32 | return ( 33 | 38 | 39 | 40 | ); 41 | } 42 | } 43 | 44 | export default connectToStores(RootIntl); -------------------------------------------------------------------------------- /app/components/Settings.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by necklace on 2016/12/21. 3 | */ 4 | 5 | import React from 'react'; 6 | 7 | class Settings extends React.Component { 8 | constructor(props) { 9 | super(props); 10 | } 11 | render() { 12 | return ( 13 |
14 | {this.props.children} 15 |
16 | ); 17 | } 18 | } 19 | 20 | export default Settings; -------------------------------------------------------------------------------- /app/components/TextLoading.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by necklace on 2017/2/3. 3 | */ 4 | import React from 'react'; 5 | 6 | 7 | class TextLoading extends React.Component { 8 | constructor(props) { 9 | super(props); 10 | this.state = {progress: 0}; 11 | } 12 | 13 | componentDidMount() { 14 | this.timer = setInterval(() => { 15 | let p = this.state.progress; 16 | p += 1; 17 | if (p > 7) p = 0; 18 | this.setState({progress: p}); 19 | }, 70); 20 | } 21 | 22 | componentWillUnmount() { 23 | this.timer && clearTimeout(this.timer); 24 | this.timer = null; 25 | } 26 | 27 | render() { 28 | return ( 29 | {this.state.progress} 30 | ); 31 | } 32 | } 33 | 34 | export default TextLoading; -------------------------------------------------------------------------------- /app/components/TransactionConfirm.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by xiangxn on 2017/1/29. 3 | */ 4 | import React from "react"; 5 | import BaseComponent from "./BaseComponent"; 6 | import Modal from "./layout/Modal"; 7 | import Transaction from "./Blockchain/Transaction"; 8 | import TextLoading from "./TextLoading"; 9 | import AltContainer from "alt-container"; 10 | 11 | import {ChainStore} from "bitsharesjs"; 12 | 13 | //actions 14 | import TransactionConfirmActions from "../actions/TransactionConfirmActions"; 15 | 16 | //ChainStores 17 | import WalletDb from "../stores/WalletDb"; 18 | import TransactionConfirmStore from "../stores/TransactionConfirmStore"; 19 | 20 | class TransactionConfirm extends BaseComponent { 21 | constructor(props) { 22 | super(props); 23 | this.state = {visible: false}; 24 | this.show = this.show.bind(this); 25 | this.hide = this.hide.bind(this); 26 | } 27 | 28 | componentWillReceiveProps(nextProps) { 29 | if (nextProps.closed !== this.props.closed) { 30 | if (!nextProps.closed) { 31 | this.show(); 32 | } else { 33 | this.hide(true); 34 | } 35 | } 36 | } 37 | 38 | show() { 39 | this.setState({visible: true}); 40 | } 41 | 42 | hide(ok) { 43 | this.setState({visible: false}); 44 | } 45 | 46 | onConfirmClick(e) { 47 | e.preventDefault(); 48 | if (this.props.propose) { 49 | TransactionConfirmActions.close(); 50 | const propose_options = { 51 | fee_paying_account: ChainStore.getAccount(this.props.fee_paying_account).get("id") 52 | }; 53 | this.props.transaction.update_head_block().then(() => { 54 | WalletDb.process_transaction(this.props.transaction.propose(propose_options), null, true); 55 | }); 56 | } else 57 | TransactionConfirmActions.broadcast(this.props.transaction); 58 | } 59 | 60 | onCloseClick(e) { 61 | e.preventDefault && e.preventDefault(); 62 | TransactionConfirmActions.close(); 63 | } 64 | 65 | render() { 66 | let {broadcast, broadcasting} = this.props; 67 | if (!this.props.transaction || this.props.closed) { 68 | return null; 69 | } 70 | 71 | let title = null; 72 | let button = ( 73 |
74 | 76 |
77 | ); 78 | if (this.props.error || this.props.included) { 79 | if (this.props.error) { 80 | title = ( 81 |
82 | {this.formatMessage('transaction_broadcast_fail')}
83 | {this.props.error} 84 |
85 | );//transaction_broadcast_fail transaction_confirm 86 | } else { 87 | title = ( 88 |
89 | {this.formatMessage('transaction_confirm')}
90 | #{this.props.trx_id}@{this.props.trx_block_num} 91 |
92 | ); 93 | } 94 | } else if (broadcast) { 95 | title = ( 96 |
97 | {this.formatMessage('transaction_broadcast_success')}
98 | {this.formatMessage('transaction_waiting')} 99 |
100 | ); 101 | } else if (broadcasting) { 102 | title = ( 103 |
104 | {this.formatMessage('transaction_broadcasting')}
105 | 106 |
107 | ); 108 | button = null; 109 | } else { 110 | title = ( 111 |
112 | {this.formatMessage('transaction_confirm')} 113 |
114 | ); 115 | button = ( 116 |
117 | 119 |      120 | 122 |
123 | ); 124 | } 125 | return ( 126 |
127 | 130 | {title} 131 | 132 | {button} 133 | 134 |
135 | ); 136 | } 137 | } 138 | 139 | class TransactionConfirmContainer extends React.Component { 140 | render() { 141 | return ( 142 | 143 | 144 | 145 | ) 146 | } 147 | } 148 | export default TransactionConfirmContainer -------------------------------------------------------------------------------- /app/components/Utility/AccountImage.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by xiangxn on 2017/2/19. 3 | */ 4 | import React,{PropTypes} from "react"; 5 | import Identicon from "./Identicon"; 6 | 7 | class AccountImage extends React.Component{ 8 | static propTypes = { 9 | src: PropTypes.string, 10 | account: PropTypes.string, 11 | size: PropTypes.object.isRequired 12 | }; 13 | static defaultProps = { 14 | src: "", 15 | account: "", 16 | size: {height: 120, width: 120} 17 | }; 18 | 19 | render(){ 20 | let {account, image} = this.props; 21 | let {height, width} = this.props.size; 22 | let custom_image = image ? 23 | :(account? 24 | :); 25 | return ( 26 |
U{custom_image}
27 | ); 28 | } 29 | } 30 | 31 | export default AccountImage; -------------------------------------------------------------------------------- /app/components/Utility/AccountName.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by xiangxn on 2017/2/17. 3 | */ 4 | import React from "react"; 5 | import BaseComponent from "../BaseComponent" 6 | import BindToChainState from "../Utility/BindToChainState"; 7 | import ChainTypes from "../Utility/ChainTypes"; 8 | 9 | class AccountName extends BaseComponent { 10 | static propTypes = { 11 | account: ChainTypes.ChainObject.isRequired 12 | }; 13 | 14 | constructor(props) { 15 | super(props); 16 | } 17 | 18 | shouldComponentUpdate(nextProps) { 19 | if (nextProps.account.get("name") && this.props.account.get("name") 20 | && nextProps.account.get("name") === this.props.account.get("name")) { 21 | return false; 22 | } 23 | return true; 24 | } 25 | 26 | render() { 27 | let account_name = this.props.account.get("name"); 28 | if (!account_name) { 29 | return {this.props.account.get("id")}; 30 | } 31 | return {account_name}; 32 | } 33 | } 34 | export default BindToChainState(AccountName); -------------------------------------------------------------------------------- /app/components/Utility/AssetName.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by necklace on 2017/2/4. 3 | */ 4 | import React from "react"; 5 | import BindToChainState from "./BindToChainState"; 6 | import ChainTypes from "./ChainTypes"; 7 | import utils from "../../../common/utils"; 8 | 9 | class AssetName extends React.Component { 10 | static propTypes = { 11 | asset: ChainTypes.ChainAsset.isRequired, 12 | replace: React.PropTypes.bool.isRequired, 13 | name: React.PropTypes.string.isRequired 14 | }; 15 | 16 | static defaultProps = { 17 | replace: true, 18 | noPrefix: false 19 | }; 20 | 21 | shouldComponentUpdate(nextProps) { 22 | return ( 23 | nextProps.replace !== this.props.replace || 24 | nextProps.name !== this.props.replace 25 | ); 26 | } 27 | 28 | render() { 29 | let {name, replace, asset, noPrefix} = this.props; 30 | let isBitAsset = asset.has("bitasset"); 31 | let isPredMarket = isBitAsset && asset.getIn(["bitasset", "is_prediction_market"]); 32 | 33 | let {name: replacedName, prefix} = utils.replaceName(name, isBitAsset && !isPredMarket && asset.get("issuer") === "1.2.0"); 34 | 35 | if (replace && replacedName !== this.props.name) { 36 | return {!noPrefix ? prefix : null}{replacedName}; 37 | } else { 38 | return {!noPrefix ? prefix : null}{name}; 39 | } 40 | } 41 | } 42 | 43 | AssetName = BindToChainState(AssetName); 44 | 45 | export default class AssetNameWrapper extends React.Component { 46 | 47 | render() { 48 | return ( 49 | 50 | ); 51 | } 52 | } -------------------------------------------------------------------------------- /app/components/Utility/BalanceComponent.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by necklace on 2017/2/8. 3 | */ 4 | import React,{PropTypes} from "react"; 5 | import FormattedAsset from "./FormattedAsset"; 6 | import ChainTypes from "./ChainTypes"; 7 | import BindToChainState from "./BindToChainState"; 8 | 9 | /** 10 | * 显示一个余额对象 11 | * balance属性为余额对象的id 12 | */ 13 | class BalanceComponent extends React.Component { 14 | 15 | static propTypes = { 16 | balance: ChainTypes.ChainObject.isRequired, 17 | assetInfo: React.PropTypes.node, 18 | hide_asset: PropTypes.bool, //隐藏资产名 19 | hide_amount: PropTypes.bool //隐藏资产数量 20 | } 21 | static defaultProps = { 22 | hide_asset: false, 23 | hide_amount: false 24 | } 25 | 26 | getBalance(){ 27 | return Number(this.props.balance.get("balance")); 28 | } 29 | 30 | render() { 31 | let amount = this.getBalance(); 32 | let type = this.props.balance.get("asset_type"); 33 | return (); 35 | } 36 | } 37 | 38 | export default BindToChainState(BalanceComponent, {keep_updating: true}); -------------------------------------------------------------------------------- /app/components/Utility/FormattedAsset.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by necklace on 2017/2/3. 3 | */ 4 | import React, {PropTypes} from "react"; 5 | import {FormattedNumber} from "react-intl"; 6 | import ChainTypes from "./ChainTypes"; 7 | import utils from "../../../common/utils"; 8 | import BindToChainState from "./BindToChainState"; 9 | import AssetName from "./AssetName"; 10 | 11 | 12 | class FormattedAsset extends React.Component { 13 | static propTypes = { 14 | amount: PropTypes.any.isRequired, //数量 15 | exact_amount: PropTypes.bool, //准确数量 16 | decimalOffset: PropTypes.number, //十进制偏移 17 | asset: ChainTypes.ChainAsset.isRequired, //资产 18 | hide_asset: PropTypes.bool, //隐藏资产名 19 | hide_amount: PropTypes.bool, //隐藏资产数量 20 | asPercentage: PropTypes.bool, //作为百分比显示 21 | assetInfo: PropTypes.node, //资产信息 22 | className: PropTypes.string //样式名 23 | } 24 | static defaultProps = { 25 | amount: 0, 26 | decimalOffset: 0, 27 | hide_asset: false, 28 | hide_amount: false, 29 | asPercentage: false, 30 | assetInfo: null, 31 | className: "" 32 | } 33 | 34 | render() { 35 | let {amount, decimalOffset, asset, hide_asset, hide_amount, asPercentage} = this.props; 36 | 37 | if (asset && asset.toJS) asset = asset.toJS(); 38 | let decimals = Math.max(0, asset.precision - decimalOffset); 39 | let precision = utils.get_asset_precision(asset.precision); 40 | if (asPercentage) { 41 | let supply = parseInt(asset.dynamic.current_supply, 10); 42 | let percent = utils.format_number((amount / supply) * 100, 4); 43 | return ( 44 | 45 | {percent}% 46 | 47 | ); 48 | } 49 | return ( 50 | 51 | {hide_amount ? null : 52 | 57 | } 58 |   59 | {hide_asset ? null : 60 | 61 | } 62 | 63 | ); 64 | } 65 | } 66 | 67 | FormattedAsset = BindToChainState(FormattedAsset); 68 | 69 | export default FormattedAsset; -------------------------------------------------------------------------------- /app/components/Utility/FormattedPrice.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by necklace on 2017/2/4. 3 | */ 4 | import React from "react"; 5 | import {FormattedNumber} from "react-intl"; 6 | import utils from "../../../common/utils"; 7 | import ChainTypes from "./ChainTypes"; 8 | import BindToChainState from "./BindToChainState"; 9 | import AltContainer from "alt-container"; 10 | import SettingsStore from "../../stores/SettingsStore"; 11 | import SettingsActions from "../../actions/SettingsActions"; 12 | import AssetName from "./AssetName"; 13 | 14 | class FormattedPrice extends React.Component { 15 | 16 | static propTypes = { 17 | base_asset: ChainTypes.ChainAsset.isRequired, 18 | quote_asset: ChainTypes.ChainAsset.isRequired, 19 | base_amount: React.PropTypes.any, 20 | quote_amount: React.PropTypes.any, 21 | decimals: React.PropTypes.number 22 | }; 23 | 24 | static contextTypes = { 25 | router: React.PropTypes.object 26 | }; 27 | 28 | constructor(props) { 29 | super(props); 30 | } 31 | 32 | onFlip() { 33 | let setting = {}; 34 | setting[this.props.marketId] = !this.props.marketDirections.get(this.props.marketId); 35 | SettingsActions.changeMarketDirection(setting); 36 | } 37 | 38 | shouldComponentUpdate(nextProps, nextState) { 39 | return ( 40 | nextProps.marketDirections !== this.props.marketDirections || 41 | nextProps.base_amount !== this.props.base_amount || 42 | nextProps.quote_amount !== this.props.quote_amount || 43 | nextProps.decimals !== this.props.decimals || 44 | !utils.are_equal_shallow(nextState, this.state) 45 | ); 46 | } 47 | 48 | goToMarket(e) { 49 | e.preventDefault(); 50 | this.context.router.push(`/market/${this.props.base_asset.get("symbol")}_${this.props.quote_asset.get("symbol")}`); 51 | } 52 | 53 | render() { 54 | 55 | let {base_asset, quote_asset, base_amount, quote_amount, callPrice, 56 | marketDirections, marketId, hide_symbols} = this.props; 57 | 58 | let invertPrice = marketDirections.get(marketId); 59 | if( (invertPrice && !callPrice) || this.props.invert) { 60 | let tmp = base_asset; 61 | base_asset = quote_asset; 62 | quote_asset = tmp; 63 | let tmp_amount = base_amount; 64 | base_amount = quote_amount; 65 | quote_amount = tmp_amount; 66 | } 67 | 68 | let formatted_value = ""; 69 | if (!this.props.hide_value) { 70 | let base_precision = utils.get_asset_precision(base_asset.get("precision")); 71 | let quote_precision = utils.get_asset_precision(quote_asset.get("precision")); 72 | let value = base_amount / base_precision / (quote_amount / quote_precision); 73 | if (isNaN(value) || !isFinite(value)) { 74 | return n/a; 75 | } 76 | let decimals = this.props.decimals ? this.props.decimals : base_asset.get("precision") + quote_asset.get("precision"); 77 | decimals = Math.min(8, decimals); 78 | if (base_asset.get("id") === "1.3.0") { 79 | base_asset.get("precision"); 80 | } 81 | formatted_value = ( 82 | 87 | ); 88 | } 89 | let symbols = hide_symbols ? "" : 90 | (/); 91 | 92 | return ( 93 | {formatted_value} {symbols} 94 | ); 95 | } 96 | } 97 | 98 | FormattedPrice = BindToChainState(FormattedPrice); 99 | 100 | export default class FormattedPriceWrapper extends React.Component { 101 | 102 | render() { 103 | let marketId = this.props.quote_asset + "_" + this.props.base_asset; 104 | 105 | return ( 106 | { 110 | return SettingsStore.getState().marketDirections; 111 | } 112 | }} 113 | > 114 | 115 | 116 | ); 117 | } 118 | } -------------------------------------------------------------------------------- /app/components/Utility/Identicon.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import {PropTypes, Component} from "react"; 3 | import sha256 from "js-sha256"; 4 | import jdenticon from "jdenticon"; 5 | 6 | var canvas_id_count = 0; 7 | 8 | class Identicon extends Component { 9 | 10 | constructor(props) { 11 | super(props); 12 | this.canvas_id = "identicon_" + (this.props.account||"") + (++canvas_id_count); 13 | } 14 | 15 | shouldComponentUpdate(nextProps) { 16 | return nextProps.size.height !== this.props.size.height || nextProps.size.width !== this.props.size.width || nextProps.account !== this.props.account; 17 | } 18 | 19 | render() { 20 | let {account} = this.props; 21 | let {height, width} = this.props.size; 22 | let hash = account ? sha256(account) : null; 23 | return ( 24 | 25 | ); 26 | } 27 | 28 | repaint() { 29 | if(this.props.account) jdenticon.updateById(this.canvas_id); 30 | else { 31 | let ctx = this.refs.canvas.getContext('2d'); 32 | ctx.fillStyle = "rgba(255, 255, 255, 0.2)"; 33 | let size = ctx.canvas.width; 34 | ctx.clearRect(0, 0, size, size); 35 | ctx.fillRect(0, 0, size, size); 36 | ctx.clearRect(0+1, 0+1, size-2, size-2); 37 | ctx.font = `${size}px sans-serif`; 38 | ctx.fillText("?", size/4, size - size/6); 39 | } 40 | } 41 | 42 | componentDidMount() { 43 | this.repaint(); 44 | } 45 | 46 | componentDidUpdate() { 47 | this.repaint(); 48 | } 49 | } 50 | 51 | Identicon.propTypes = { 52 | size: PropTypes.object.isRequired, 53 | account: PropTypes.string 54 | }; 55 | 56 | export default Identicon; -------------------------------------------------------------------------------- /app/components/Utility/PriceText.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import utils from "../../../common/utils"; 3 | 4 | class PriceText extends React.Component { 5 | 6 | render() { 7 | let {price, preFormattedPrice, quote, base, component} = this.props; 8 | 9 | let formattedPrice = preFormattedPrice ? preFormattedPrice : utils.price_to_text(price, quote, base); 10 | 11 | if (formattedPrice.full >= 1) { 12 | return ( 13 | 14 | {formattedPrice.int}. 15 | {formattedPrice.dec ? {formattedPrice.dec} : null} 16 | {formattedPrice.trailing ? {formattedPrice.trailing} : null} 17 | 18 | ) 19 | } else if (formattedPrice.full >= 0.1) { 20 | return ( 21 | 22 | {formattedPrice.int}. 23 | {formattedPrice.dec ? {formattedPrice.dec} : null} 24 | {formattedPrice.trailing ? {formattedPrice.trailing} : null} 25 | 26 | ) 27 | } else { 28 | return ( 29 | 30 | {formattedPrice.int}. 31 | {formattedPrice.dec ? {formattedPrice.dec} : null} 32 | {formattedPrice.trailing ? {formattedPrice.trailing} : null} 33 | 34 | ) 35 | } 36 | } 37 | } 38 | 39 | export default PriceText; 40 | -------------------------------------------------------------------------------- /app/components/Utility/TimeAgo.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import {FormattedRelative} from "react-intl"; 3 | import {ChainStore} from "bitsharesjs"; 4 | 5 | class TimeAgo extends React.Component { 6 | 7 | static propTypes = { 8 | time: React.PropTypes.any.isRequired, 9 | chain_time: React.PropTypes.bool, 10 | component: React.PropTypes.element, 11 | className: React.PropTypes.string 12 | }; 13 | 14 | static defaultProps = { 15 | chain_time: true 16 | }; 17 | 18 | shouldComponentUpdate(nextProps) { 19 | return ( 20 | nextProps.time !== this.props.time 21 | ); 22 | } 23 | 24 | render() { 25 | let {time, chain_time} = this.props; 26 | var offset_mills = chain_time ? ChainStore.getEstimatedChainTimeOffset() : 0 27 | if (!time) { 28 | return null; 29 | } 30 | 31 | if (typeof time === "string" && time.indexOf("+") === -1) { 32 | time += "+00:00"; 33 | } 34 | 35 | let timePassed = Math.round(( new Date().getTime() - new Date(time).getTime() + offset_mills ) / 1000); 36 | let interval; 37 | 38 | if (timePassed < 60) { // 60s 39 | interval = 500; // 0.5s 40 | } else if (timePassed < 60 * 60) { // 1 hour 41 | interval = 60 * 500; // 30 seconds 42 | } else { 43 | interval = 60 * 60 * 500 // 30 minutes 44 | } 45 | 46 | return ( 47 | 52 | ); 53 | 54 | } 55 | } 56 | 57 | export default TimeAgo; -------------------------------------------------------------------------------- /app/components/Utility/intlData.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | formats: { 3 | "date": { 4 | full: { 5 | second: "numeric", 6 | minute: "numeric", 7 | hour: "numeric", 8 | day: "numeric", 9 | month: "long", 10 | year: "numeric" 11 | }, 12 | short: { 13 | //second: "numeric", 14 | //minute: "numeric", 15 | //hour: "numeric", 16 | day: "numeric", 17 | month: "numeric", 18 | year: "numeric" 19 | }, 20 | time: { 21 | second: "numeric", 22 | minute: "numeric", 23 | hour: "numeric" 24 | } 25 | } 26 | } 27 | }; 28 | -------------------------------------------------------------------------------- /app/components/containers/GlobalSettingContainer.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by necklace on 2017/1/25. 3 | */ 4 | import React from "react"; 5 | import GlobalSetting from "../GlobalSetting"; 6 | import SettingsStore from "../../stores/SettingsStore"; 7 | import IntlStore from "../../stores/IntlStore"; 8 | import AltContainer from "alt-container"; 9 | 10 | class GlobalSettingContainer extends React.Component { 11 | 12 | render() { 13 | 14 | return ( 15 | { 19 | return SettingsStore.getState().settings; 20 | }, 21 | defaults: () => { 22 | return SettingsStore.getState().defaults; 23 | }, 24 | localesObject: () => { 25 | return IntlStore.getState().localesObject; 26 | } 27 | }} 28 | > 29 | 30 | 31 | ); 32 | } 33 | } 34 | 35 | export default GlobalSettingContainer; -------------------------------------------------------------------------------- /app/components/dashboard/AssetsItem.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by necklace on 2017/1/13. 3 | */ 4 | import React from "react"; 5 | import BaseComponent from "../BaseComponent"; 6 | import ChainTypes from "../Utility/ChainTypes"; 7 | import utils from "../../../common/utils"; 8 | import assetUtils from "../../../common/asset_utils"; 9 | import BindToChainState from "../Utility/BindToChainState"; 10 | import connectToStores from "alt-utils/lib/connectToStores"; 11 | import AssetName from "../Utility/AssetName"; 12 | 13 | //actions 14 | import MarketsActions from "../../actions/MarketsActions"; 15 | 16 | //stores 17 | import MarketsStore from "../../stores/MarketsStore"; 18 | 19 | class AssetsItem extends BaseComponent { 20 | static propTypes = { 21 | quote: ChainTypes.ChainAsset.isRequired, 22 | base: ChainTypes.ChainAsset.isRequired, 23 | invert: React.PropTypes.bool 24 | }; 25 | 26 | static defaultProps = { 27 | invert: true 28 | }; 29 | 30 | constructor(props) { 31 | super(props); 32 | this.statsInterval = null; 33 | } 34 | 35 | shouldComponentUpdate(nextProps) { 36 | return ( 37 | !utils.are_equal_shallow(nextProps, this.props) 38 | ); 39 | } 40 | 41 | componentWillMount() { 42 | MarketsActions.getMarketStats.defer(this.props.base, this.props.quote); 43 | this.statsChecked = new Date(); 44 | this.statsInterval = setInterval(MarketsActions.getMarketStats.bind(this, this.props.quote, this.props.base), 35 * 1000); 45 | } 46 | 47 | componentWillUnmount() { 48 | clearInterval(this.statsInterval); 49 | } 50 | 51 | onClickHandler(e) { 52 | e.preventDefault(); 53 | this.context.router.push(`/transaction/${this.props.quote.get("symbol")}_${this.props.base.get("symbol")}`); 54 | /* 55 | this.context.router.push({ 56 | pathname: '/transaction', 57 | state: {baseAsset: this.props.base.get("symbol"), quoteAsset: this.props.quote.get("symbol")} 58 | }); 59 | */ 60 | } 61 | 62 | render() { 63 | let {base, quote, marketStats} = this.props; 64 | 65 | function getImageName(asset) { 66 | let symbol = asset.get("symbol"); 67 | if (symbol === "OPEN.BTC") return symbol.replace('.', '-'); 68 | let imgName = asset.get("symbol").split("."); 69 | return imgName.length === 2 ? imgName[1] : imgName[0]; 70 | } 71 | 72 | let desc = assetUtils.parseDescription(base.getIn(["options", "description"])); 73 | let imgName = getImageName(quote); 74 | let marketID = quote.get("symbol") + "_" + base.get("symbol"); 75 | let stats = marketStats.get(marketID); 76 | let gainClass = "def-label"; 77 | if (stats) { 78 | let change = parseFloat(stats.change); 79 | gainClass = change > 0 ? "green-label" : change < 0 ? "orange-label" : "def-label"; 80 | } 81 | /* 82 | if (imgName === "BTS" || imgName === "CNY" || imgName === "USD" || imgName === "EUR") { 83 | imgName = getImageName(quote); 84 | } 85 | */ 86 | 87 | let unitPrice = (!stats || !stats.close) ? null : utils.format_price( 88 | stats.close.quote.amount, 89 | quote, 90 | stats.close.base.amount, 91 | base, 92 | true, 93 | this.props.invert 94 | ); 95 | //处理价格小数 96 | let precision = 6; 97 | if (["BTC", "OPEN.BTC", "TRADE.BTC", "GOLD", "SILVER"].indexOf(base.get("symbol")) !== -1) { 98 | precision = 8; 99 | } 100 | let ups = (unitPrice) ? unitPrice.replace(',', '') : ''; 101 | if (!isNaN(ups)) { 102 | unitPrice = utils.format_number(ups, ups > 1000 ? 0 : ups > 10 ? 2 : precision, false); 103 | } 104 | return ( 105 |
106 |
107 |
108 | 110 | 113 | 117 | 118 |
119 |
120 | ); 121 | } 122 | } 123 | 124 | AssetsItem = BindToChainState(AssetsItem); 125 | 126 | class AssetsItemWrapper extends React.Component { 127 | static getPropsFromStores() { 128 | return { 129 | marketStats: MarketsStore.getState().allMarketStats 130 | }; 131 | } 132 | 133 | static getStores() { 134 | return [MarketsStore]; 135 | } 136 | 137 | render() { 138 | return ( 139 | 140 | ); 141 | } 142 | } 143 | 144 | export default connectToStores(AssetsItemWrapper); -------------------------------------------------------------------------------- /app/components/dashboard/Dashboard.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by necklace on 2017/1/13. 3 | */ 4 | import React from "react"; 5 | import BaseComponent from "../BaseComponent"; 6 | import Immutable from "immutable"; 7 | import connectToStores from 'alt-utils/lib/connectToStores'; 8 | import utils from "../../../common/utils"; 9 | 10 | //组件 11 | import AssetsItem from "./AssetsItem"; 12 | import AccountList from "./AccountList"; 13 | 14 | //strores 15 | import {ChainStore} from "bitsharesjs"; 16 | import AccountStore from "../../stores/AccountStore"; 17 | 18 | class Dashboard extends BaseComponent { 19 | static getPropsFromStores() { 20 | return {linkedAccounts: AccountStore.getState().linkedAccounts}; 21 | } 22 | 23 | static getStores() { 24 | return [AccountStore]; 25 | } 26 | 27 | constructor(props) { 28 | super(props); 29 | this.state = { 30 | mainMarkets: [ 31 | ["BTS", "CNY",false], 32 | ["BTS", "USD",false], 33 | ["BTS", "EUR",false] 34 | , ["OPEN.BTC", "BTS"], 35 | ["OPEN.BTC", "CNY"], 36 | ["OPEN.BTC", "USD"] 37 | ] 38 | }; 39 | } 40 | 41 | shouldComponentUpdate(nextProps, nextState) { 42 | return ( 43 | !utils.are_equal_shallow(nextState.mainMarkets, this.state.mainMarkets) || 44 | nextProps.linkedAccounts !== this.props.linkedAccounts 45 | ); 46 | } 47 | 48 | 49 | render() { 50 | let {linkedAccounts} = this.props; 51 | let names = linkedAccounts.toArray().sort(); 52 | 53 | let markets = this.state.mainMarkets.map((item, index) => { 54 | if (index < 3) 55 | return ( 56 | 62 | ); 63 | }); 64 | let markets2 = this.state.mainMarkets.map((item, index) => { 65 | if (index > 2) 66 | return ( 67 | 73 | ); 74 | }); 75 | 76 | return ( 77 |
78 |
79 | {markets} 80 |
81 |
82 | {markets2} 83 |
84 | 85 |
86 | ); 87 | } 88 | } 89 | 90 | export default connectToStores(Dashboard); 91 | -------------------------------------------------------------------------------- /app/components/form/XNFullButton.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by necklace on 2017/1/11. 3 | */ 4 | import React from "react"; 5 | 6 | export default class XNFullButton extends React.Component { 7 | constructor(props) { 8 | super(props); 9 | } 10 | 11 | onClick(e) { 12 | this.props.onClick && this.props.onClick(e); 13 | } 14 | 15 | render() { 16 | let icon = null; 17 | if (this.props.isShowIcon) icon = >; 18 | return ( 19 |
20 | 21 | {icon} 22 |
23 | ); 24 | } 25 | } 26 | 27 | XNFullButton.propTypes = { 28 | label: React.PropTypes.string, 29 | isShowIcon: React.PropTypes.bool, 30 | onClick: React.PropTypes.func 31 | }; 32 | XNFullButton.defaultProps = { 33 | label: "", 34 | isShowIcon: true, 35 | onClick: null 36 | }; -------------------------------------------------------------------------------- /app/components/form/XNFullText.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by necklace on 2017/1/25. 3 | */ 4 | 5 | import React from "react"; 6 | 7 | export default class XNFullText extends React.Component { 8 | constructor(props) { 9 | super(props); 10 | this.state = {value: props.value}; 11 | } 12 | 13 | onTextChange(e) { 14 | this.setState({value: e.target.value}); 15 | this.props.onChange && this.props.onChange(e.target.value); 16 | } 17 | 18 | render() { 19 | return ( 20 |
21 | 22 | 23 |
24 | ); 25 | } 26 | } 27 | 28 | XNFullText.propTypes = { 29 | label: React.PropTypes.string, 30 | value: React.PropTypes.number, 31 | type: React.PropTypes.string, 32 | onChange: React.PropTypes.func 33 | }; 34 | XNFullText.defaultProps = { 35 | label: "", 36 | value: 0, 37 | type: 'text', 38 | onChange: null 39 | }; -------------------------------------------------------------------------------- /app/components/form/XNSelect.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by necklace on 2017/1/7. 3 | */ 4 | import React from "react"; 5 | import BaseComponent from "../BaseComponent"; 6 | 7 | class XNSelect extends BaseComponent { 8 | constructor(props) { 9 | super(props); 10 | this.state = { 11 | value: props.value, 12 | data: props.data,//[{text: "中文简体", value: "zh"}, {text: "英文", value: "en"}], 13 | isShowList: false//是否弹出选择列表 14 | }; 15 | this.onDocumentClick = this.onDocumentClick.bind(this); 16 | } 17 | 18 | stopEvent(e) { 19 | e.nativeEvent.stopImmediatePropagation(); 20 | e.stopPropagation(); 21 | e.preventDefault(); 22 | } 23 | 24 | componentDidMount() { 25 | 26 | } 27 | 28 | componentWillReceiveProps(nextProps) { 29 | if (nextProps.value !== this.state.value || nextProps.data !== this.state.data) { 30 | this.setState({value: nextProps.value, data: nextProps.data}); 31 | } 32 | } 33 | 34 | componentDidUpdate(prevProps, prevState) { 35 | if (this.state.isShowList) { 36 | document.addEventListener('click', this.onDocumentClick, false); 37 | } else { 38 | document.removeEventListener('click', this.onDocumentClick, false); 39 | } 40 | } 41 | 42 | componentWillUnmount() { 43 | } 44 | 45 | onDocumentClick(e) { 46 | //console.debug('onDocumentClick', e) 47 | this.setState({isShowList: false}); 48 | } 49 | 50 | openList(e) { 51 | //console.debug('openList') 52 | let flag = !this.state.isShowList; 53 | this.setState({isShowList: flag}); 54 | } 55 | 56 | //单击行事件 57 | onItemClick(d, e) { 58 | e.nativeEvent.stopImmediatePropagation(); 59 | let oldVal = this.state.value; 60 | this.setState({value: d.text}); 61 | if (this.props.onChange !== null && oldVal !== d.value) { 62 | this.props.onChange(d); 63 | } 64 | } 65 | 66 | //删除行事件 67 | onDelItemClick(item, index, e) { 68 | this.stopEvent(e); 69 | //console.debug('onDelItemClick', item); 70 | this.props.onDeleteItem && this.props.onDeleteItem(item, index); 71 | } 72 | 73 | //添加事件 74 | onAddClick(e) { 75 | this.stopEvent(e); 76 | let newItem = this.refs.newItem.value; 77 | //console.debug('onAddClick', newItem); 78 | this.props.onAddItem && this.props.onAddItem(newItem); 79 | } 80 | 81 | onInputClick(e) { 82 | this.stopEvent(e); 83 | } 84 | 85 | render() { 86 | let list = this.state.isShowList === false ? null : ( 87 |
88 |
    89 | {this.state.data.map((item, i) => { 90 | return (
  • 91 | {item.text} 92 | { 93 | this.props.isDelete === false ? null : ( 94 | ' 95 | ) 96 | } 97 |
  • ); 98 | })} 99 |
100 | { 101 | this.props.isAdd === false ? null : ( 102 |
103 | {this.formatMessage('settings_add')} 105 |
106 | ) 107 | } 108 |
109 | ); 110 | return ( 111 |
112 |
113 | 114 | {this.state.value} 115 |
116 | > 117 | {list} 118 |
119 | ); 120 | } 121 | } 122 | XNSelect.propTypes = { 123 | label: React.PropTypes.string, 124 | onChange: React.PropTypes.func, 125 | onDeleteItem: React.PropTypes.func, 126 | onAddItem: React.PropTypes.func, 127 | isAdd: React.PropTypes.bool, 128 | isDelete: React.PropTypes.bool, 129 | data: React.PropTypes.array, 130 | value: React.PropTypes.string 131 | }; 132 | XNSelect.defaultProps = { 133 | label: "显示名称", 134 | onChange: null, 135 | onDeleteItem: null, 136 | onAddItem: null, 137 | isAdd: false, 138 | isDelete: false, 139 | data: [], 140 | value: "" 141 | }; 142 | 143 | export default XNSelect; -------------------------------------------------------------------------------- /app/components/form/XNSwitch.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by necklace on 2017/1/8. 3 | */ 4 | import React, {Component} from "react"; 5 | 6 | export default class XNSwitch extends Component { 7 | constructor(props) { 8 | super(props); 9 | this.state = {value: props.value}; 10 | } 11 | 12 | onSpanClick(e) { 13 | let value = !this.state.value; 14 | this.setState({value: value}); 15 | this.props.onChange && this.props.onChange(value); 16 | } 17 | 18 | render() { 19 | let openClass = this.state.value ? "open" : "close"; 20 | return ( 21 |
22 | 23 | 24 |
25 | ); 26 | } 27 | } 28 | 29 | XNSwitch.propTypes = { 30 | label: React.PropTypes.string, 31 | value: React.PropTypes.bool, 32 | onChange: React.PropTypes.func 33 | }; 34 | XNSwitch.defaultProps = { 35 | label: "显示名称", 36 | value: false, 37 | onChange: null 38 | }; -------------------------------------------------------------------------------- /app/components/layout/Confirm.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by necklace on 2017/3/17. 3 | */ 4 | import React from "react"; 5 | import BaseComponent from "../BaseComponent"; 6 | import AltContainer from "alt-container"; 7 | import ConfirmStore from "../../stores/layout/ConfirmStore"; 8 | import ConfirmActions from "../../actions/layout/ConfirmActions"; 9 | import Modal from "./Modal"; 10 | 11 | class Confirm extends BaseComponent { 12 | 13 | constructor(props) { 14 | super(props); 15 | this.state = this.initState(); 16 | this.close = this.close.bind(this); 17 | } 18 | 19 | initState() { 20 | return {show: false}; 21 | } 22 | 23 | componentWillReceiveProps(props) { 24 | if (props.show !== undefined) { 25 | let show = props.show ? true : false; 26 | this.setState({show: show}); 27 | } 28 | } 29 | 30 | reset() { 31 | ConfirmActions.reset(); 32 | } 33 | 34 | close() { 35 | this.setState({show: false}); 36 | } 37 | 38 | okClick(e) { 39 | this.close(); 40 | if (this.props.onOK) { 41 | this.props.onOK(); 42 | } 43 | this.reset(); 44 | } 45 | 46 | cancelClick(e) { 47 | this.close(); 48 | if (this.props.onCancel) { 49 | this.props.onCancel(); 50 | } 51 | this.reset(); 52 | } 53 | 54 | render() { 55 | let {title, msg, height, showCancelButton} = this.props; 56 | return ( 57 |
58 | 59 |
{title}
60 |
61 |
62 | {msg} 63 |
64 |
65 |      67 | {!showCancelButton ? null : 68 | 70 | } 71 |
72 |
73 |
74 | ); 75 | } 76 | } 77 | 78 | class ConfirmContainer extends React.Component { 79 | render() { 80 | return ( 81 | 82 | 83 | 84 | ) 85 | } 86 | } 87 | export default ConfirmContainer -------------------------------------------------------------------------------- /app/components/layout/Modal.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by xiangxn on 2017/1/29. 3 | */ 4 | import React from "react"; 5 | 6 | const {PropTypes, Component} = React; 7 | const propTypes = { 8 | width: PropTypes.number, 9 | height: PropTypes.number, 10 | measure: PropTypes.string, 11 | visible: PropTypes.bool, 12 | showMask: PropTypes.bool, 13 | showCloseButton: PropTypes.bool, 14 | animation: PropTypes.string, 15 | duration: PropTypes.number, 16 | className: PropTypes.string, 17 | customStyles: PropTypes.object, 18 | customMaskStyles: PropTypes.object, 19 | onClose: PropTypes.func.isRequired, 20 | outsideClose: PropTypes.bool //是否可点窗口外关闭 21 | }; 22 | 23 | const defaultProps = { 24 | width: 6, 25 | height: 3.6, 26 | measure: 'rem', 27 | visible: false, 28 | showMask: true, 29 | showCloseButton: true, 30 | animation: 'flip', 31 | duration: 300, 32 | className: '', 33 | customStyles: {}, 34 | customMaskStyles: {}, 35 | outsideClose: false 36 | }; 37 | 38 | const Dialog = props => { 39 | const _onClose = () => { 40 | if (props.onClose) props.onClose(false); 41 | }; 42 | const className = `modal-dialog modal-${props.animation}-${props.animationType}`; 43 | const CloseButton = props.showCloseButton ? : null; 44 | const {width, height, measure, duration, customStyles} = props; 45 | const style = { 46 | width: width + measure, 47 | height: height + measure, 48 | animationDuration: duration + 'ms', 49 | WebkitAnimationDuration: duration + 'ms' 50 | }; 51 | 52 | const mergedStyles = Object.assign(style, customStyles) 53 | 54 | return ( 55 |
56 | {CloseButton} 57 | {props.children} 58 |
59 | ) 60 | }; 61 | 62 | class Modal extends Component { 63 | 64 | constructor(props) { 65 | super(props); 66 | 67 | this.animationEnd = this.animationEnd.bind(this); 68 | this.state = { 69 | isShow: false, 70 | animationType: 'leave' 71 | }; 72 | } 73 | 74 | componentDidMount() { 75 | if (this.props.visible) { 76 | this.enter(); 77 | } 78 | } 79 | 80 | componentWillReceiveProps(nextProps) { 81 | if (!this.props.visible && nextProps.visible) { 82 | this.enter(); 83 | } else if (this.props.visible && !nextProps.visible) { 84 | this.leave(); 85 | } 86 | } 87 | 88 | enter() { 89 | this.setState({ 90 | isShow: true, 91 | animationType: 'enter' 92 | }); 93 | } 94 | 95 | leave() { 96 | this.setState({ 97 | animationType: 'leave' 98 | }); 99 | } 100 | 101 | animationEnd() { 102 | if (this.state.animationType === 'leave') { 103 | this.setState({ 104 | isShow: false 105 | }); 106 | } 107 | } 108 | 109 | onMaskClick(e) { 110 | if (this.props.outsideClose && this.props.onClose) { 111 | this.props.onClose(false); 112 | } 113 | } 114 | 115 | render() { 116 | const mask = this.props.showMask ? 117 |
: null; 119 | const style = { 120 | display: this.state.isShow ? 'block' : 'none', 121 | WebkitAnimationDuration: this.props.duration + 'ms', 122 | animationDuration: this.props.duration + 'ms' 123 | }; 124 | 125 | return ( 126 |
130 | {mask} 131 | 132 | {this.props.children} 133 | 134 |
135 | ) 136 | } 137 | } 138 | 139 | Modal.propTypes = propTypes; 140 | Modal.defaultProps = defaultProps; 141 | 142 | export default Modal; -------------------------------------------------------------------------------- /app/components/transaction/CanBuySell.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by necklace on 2017/3/1. 3 | */ 4 | import React from "react"; 5 | import ChainTypes from "../Utility/ChainTypes"; 6 | import BindToChainState from "../Utility/BindToChainState"; 7 | import utils from "../../../common/utils"; 8 | import {FormattedNumber} from "react-intl"; 9 | 10 | class CanBuySell extends React.Component { 11 | static propTypes = { 12 | balance: ChainTypes.ChainObject.isRequired, 13 | price: React.PropTypes.number, 14 | fromAsset: ChainTypes.ChainAsset.isRequired, 15 | isAsk: React.PropTypes.bool 16 | }; 17 | static defaultProps = { 18 | isAsk: false 19 | }; 20 | 21 | render() { 22 | let {fromAsset, balance, price}=this.props; 23 | let balances = Number(balance.get("balance")); 24 | let value = 0; 25 | let cn = 'green'; 26 | if (fromAsset && fromAsset.toJS) fromAsset = fromAsset.toJS(); 27 | let precision = utils.get_asset_precision(fromAsset.precision); 28 | if (this.props.isAsk) { 29 | cn = 'orangeRed'; 30 | value = (balances/precision) * price; 31 | } else { 32 | value = (balances/precision) / price; 33 | } 34 | let decimals = Math.max(0, fromAsset.precision); 35 | 36 | return ( 37 | 44 | ); 45 | } 46 | } 47 | export default BindToChainState(CanBuySell); -------------------------------------------------------------------------------- /app/components/transaction/CurrentBalance.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by necklace on 2017/3/1. 3 | */ 4 | import React from "react"; 5 | import BaseComponent from "../BaseComponent"; 6 | import utils from "../../../common/utils"; 7 | import BalanceComponent from "../Utility/BalanceComponent"; 8 | import FormattedAsset from "../Utility/FormattedAsset"; 9 | import CanBuySell from "./CanBuySell"; 10 | 11 | class CurrentBalance extends BaseComponent { 12 | static propTypes = { 13 | isAsk: React.PropTypes.bool, 14 | latestPrice: React.PropTypes.number, 15 | onBalanceClick: React.PropTypes.func 16 | }; 17 | 18 | static defaultProps = { 19 | isAsk: false, 20 | onBalanceClick: null 21 | }; 22 | 23 | constructor(props) { 24 | super(props); 25 | } 26 | 27 | onBalanceClick(balance, e) { 28 | e.d 29 | if (balance != null && this.props.onBalanceClick) { 30 | this.props.onBalanceClick(balance); 31 | } 32 | } 33 | 34 | render() { 35 | let {quoteAsset, baseAsset, currentAccount, latestPrice, isAsk}=this.props; 36 | let base = null, quote = null, accountBalance = null, quoteBalance = null, 37 | baseBalance = null, coreBalance = null, quoteSymbol, baseSymbol, aSymbol, bSymbol, balance; 38 | if (quoteAsset.size && baseAsset.size && currentAccount.size) { 39 | base = baseAsset; 40 | quote = quoteAsset; 41 | baseSymbol = base.get("symbol"); 42 | quoteSymbol = quote.get("symbol"); 43 | 44 | accountBalance = currentAccount.get("balances").toJS(); 45 | 46 | if (accountBalance) { 47 | for (let id in accountBalance) { 48 | if (id === quote.get("id")) { 49 | quoteBalance = accountBalance[id]; 50 | } 51 | if (id === base.get("id")) { 52 | baseBalance = accountBalance[id]; 53 | } 54 | if (id === "1.3.0") { 55 | coreBalance = accountBalance[id]; 56 | } 57 | } 58 | } 59 | } 60 | if (isAsk) { 61 | aSymbol = quote; 62 | bSymbol = base; 63 | balance = quoteBalance; 64 | } else { 65 | aSymbol = base; 66 | bSymbol = quote; 67 | balance = baseBalance; 68 | } 69 | 70 | return ( 71 |
72 |
73 |

{this.formatMessage("transaction_currentBalance", {symbol: utils.getAssetName(aSymbol)})}:  74 | 80 |

81 |

{this.formatMessage(isAsk ? "transaction_canSell" : "transaction_canBuy", {symbol: utils.getAssetName(bSymbol)})}:  82 | {balance == null ? 0 : 83 | 85 | } 86 |

87 |
88 |
89 |
90 | ); 91 | } 92 | } 93 | 94 | export default CurrentBalance; -------------------------------------------------------------------------------- /app/components/transaction/OrderBook.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by necklace on 2017/3/6. 3 | */ 4 | import React from "react"; 5 | import BaseComponent from "../BaseComponent"; 6 | import utils from "../../../common/utils"; 7 | import market_utils from "../../../common/market_utils"; 8 | import AssetName from "../Utility/AssetName"; 9 | 10 | //import PriceText from "../Utility/PriceText"; 11 | 12 | class OrderBookRow extends BaseComponent { 13 | constructor(props) { 14 | super(props); 15 | } 16 | 17 | shouldComponentUpdate(nextProps) { 18 | if (nextProps.order.market_base !== this.props.order.market_base) return false; 19 | return ( 20 | nextProps.order.ne(this.props.order) || 21 | nextProps.index !== this.props.index 22 | ); 23 | } 24 | 25 | render() { 26 | let {order, quote, base} = this.props; 27 | const isBid = order.isBid(); 28 | const isCall = order.isCall(); 29 | 30 | //let price = ; 31 | let {price} = market_utils.parseOrder(order, base, quote); 32 | 33 | return ( 34 |
35 | {utils.formatNumber(order[isBid ? "amountForSale" : "amountToReceive"]().getAmount({real: true}), 4)} 36 | {utils.formatNumber(order[isBid ? "amountToReceive" : "amountForSale"]().getAmount({real: true}), 4)} 37 | {utils.formatNumber(price.full, 4)} 38 |
39 | ); 40 | } 41 | } 42 | 43 | class OrderBook extends BaseComponent { 44 | static propTypes = { 45 | isAsk: React.PropTypes.bool 46 | }; 47 | static defaultProps = { 48 | isAsk: false 49 | }; 50 | 51 | constructor(props) { 52 | super(props); 53 | } 54 | 55 | componentDidMount() { 56 | let uplist = this.refs.uplist; 57 | setTimeout(() => { 58 | uplist.scrollTop = uplist.scrollHeight; 59 | }, 200); 60 | } 61 | 62 | render() { 63 | let { 64 | combinedBids, combinedAsks, highestBid, lowestAsk, quote, base, 65 | totalAsks, totalBids, quoteSymbol, baseSymbol, isAsk 66 | } = this.props; 67 | 68 | let bidRows = null, askRows = null; 69 | if (base && quote) { 70 | let tempBids = combinedBids.filter(a => { 71 | return a.getPrice() >= highestBid.getPrice() / 5; 72 | }); 73 | if (isAsk) { 74 | tempBids.sort((a, b) => { 75 | return a.getPrice() - b.getPrice(); 76 | }); 77 | } 78 | bidRows = tempBids.map((order, index) => { 79 | return ( 80 | 88 | ); 89 | }); 90 | 91 | let tempAsks = combinedAsks 92 | .filter(a => { 93 | return a.getPrice() <= lowestAsk.getPrice() * 5; 94 | }); 95 | if (!isAsk) { 96 | tempAsks.sort((a, b) => { 97 | return b.getPrice() - a.getPrice(); 98 | }); 99 | } 100 | askRows = tempAsks.map((order, index) => { 101 | return ( 102 | 111 | ); 112 | }); 113 | } 114 | 115 | let cnbid, cnask; 116 | if (!isAsk) { 117 | cnbid = 'depth-list-buy scroll'; 118 | cnask = 'depth-list-sell scroll'; 119 | askRows.splice(0, askRows.length - 50); 120 | bidRows.splice(50, bidRows.length); 121 | } else { 122 | cnbid = 'depth-list-sell scroll'; 123 | cnask = 'depth-list-buy scroll'; 124 | bidRows.splice(0, bidRows.length - 50); 125 | askRows.splice(50, askRows.length); 126 | } 127 | 128 | 129 | return ( 130 |
131 |
132 |
133 |
134 |
{this.formatMessage("transaction_depthPrice")}
135 |
136 |
137 | {(!isAsk) ? askRows : bidRows} 138 |
139 |
140 |
141 | {(!isAsk) ? bidRows : askRows} 142 |
143 |
144 | ); 145 | } 146 | } 147 | 148 | export default OrderBook; -------------------------------------------------------------------------------- /app/components/transaction/TabComponent.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by necklace on 2017/1/14. 3 | */ 4 | import React from "react"; 5 | 6 | class TabComponent extends React.Component { 7 | 8 | static propTypes = { 9 | data: React.PropTypes.array.isRequired 10 | }; 11 | static contextTypes = { 12 | router: React.PropTypes.object 13 | }; 14 | 15 | constructor(props) { 16 | super(props); 17 | this.state = {currentIndex: 0}; 18 | } 19 | 20 | componentWillMount() { 21 | //console.debug('location:', this.context.router.location) 22 | let index = 0; 23 | this.props.data.forEach((x, i) => { 24 | if (x.url === this.context.router.location.pathname) 25 | index = i; 26 | }); 27 | this.setState({currentIndex: index}); 28 | } 29 | 30 | clickHandle(index, url) { 31 | //console.debug(this.context.router); 32 | this.setState({currentIndex: index}); 33 | //let path = this.context.router.location.pathname + "/" + url; 34 | let s = this.context.router.location.state; 35 | //this.context.router.push(url,s); 36 | this.context.router.push({pathname: url, state: s}); 37 | } 38 | 39 | render() { 40 | return ( 41 |
42 |
43 |
44 | {this.props.data.map((item, i) => { 45 | return (
{item.name}
); 47 | })} 48 |
49 |
50 |
51 |
52 | {React.cloneElement(this.props.children, {...this.props})} 53 |
54 |
55 | 56 | ); 57 | } 58 | } 59 | 60 | export default TabComponent; -------------------------------------------------------------------------------- /app/components/transaction/Transaction.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by necklace on 2017/1/14. 3 | */ 4 | import React, {PropTypes} from "react"; 5 | import BaseComponent from "../BaseComponent"; 6 | import TabComponent from "./TabComponent"; 7 | import utils from "../../../common/utils"; 8 | import market_utils from "../../../common/market_utils"; 9 | 10 | //组件 11 | import AssetName from "../Utility/AssetName"; 12 | 13 | class Transaction extends BaseComponent { 14 | static propTypes = { 15 | marketCallOrders: PropTypes.object.isRequired, 16 | activeMarketHistory: PropTypes.object.isRequired, 17 | priceData: PropTypes.array.isRequired, 18 | volumeData: PropTypes.array.isRequired 19 | }; 20 | 21 | static defaultProps = { 22 | marketCallOrders: [], 23 | activeMarketHistory: {}, 24 | priceData: [], 25 | volumeData: [] 26 | }; 27 | 28 | constructor(props) { 29 | super(props); 30 | } 31 | 32 | render() { 33 | let marketID = this.props.params.marketID; 34 | let latestPrice = 0; 35 | let aVolume = 0; 36 | let bVolume = 0; 37 | let tabData = [ 38 | {name: this.formatMessage("transaction_pay"), url: `/transaction/${marketID}/buy`}, 39 | {name: this.formatMessage("transaction_sell"), url: `/transaction/${marketID}/sell`}, 40 | {name: this.formatMessage("transaction_orders"), url: `/transaction/${marketID}/orders`}, 41 | {name: this.formatMessage("transaction_history"), url: `/transaction/${marketID}/history`} 42 | ]; 43 | 44 | let {activeMarketHistory, quoteAsset, baseAsset, marketStats} = this.props; 45 | let base = null, quote = null, quoteBalance = null, 46 | baseBalance = null, coreBalance = null, quoteSymbol, baseSymbol, changeClass; 47 | if (quoteAsset.size && baseAsset.size) { 48 | base = baseAsset; 49 | quote = quoteAsset; 50 | baseSymbol = base.get("symbol"); 51 | quoteSymbol = quote.get("symbol"); 52 | } 53 | 54 | 55 | //最新价格 56 | if (activeMarketHistory.size) { 57 | // 取得最后成交订单 58 | let latest_two = activeMarketHistory.take(3); 59 | let latest = latest_two.first(); 60 | let second_latest = latest_two.last(); 61 | let paysAsset, receivesAsset, isAsk = false; 62 | if (latest.pays.asset_id === base.get("id")) { 63 | paysAsset = base; 64 | receivesAsset = quote; 65 | isAsk = true; 66 | } else { 67 | paysAsset = quote; 68 | receivesAsset = base; 69 | } 70 | let flipped = base.get("id").split(".")[2] > quote.get("id").split(".")[2]; 71 | latestPrice = market_utils.parse_order_history(latest, paysAsset, receivesAsset, isAsk, flipped); 72 | 73 | isAsk = false; 74 | if (second_latest) { 75 | if (second_latest.pays.asset_id === base.get("id")) { 76 | paysAsset = base; 77 | receivesAsset = quote; 78 | isAsk = true; 79 | } else { 80 | paysAsset = quote; 81 | receivesAsset = base; 82 | } 83 | 84 | let oldPrice = market_utils.parse_order_history(second_latest, paysAsset, receivesAsset, isAsk, flipped); 85 | changeClass = latestPrice.full === oldPrice.full ? "" : latestPrice.full - oldPrice.full > 0 ? "green" : "orangeRed"; 86 | } 87 | } 88 | 89 | //成交量 90 | const volumeBase = marketStats.get("volumeBase"); 91 | const volumeQuote = marketStats.get("volumeQuote"); 92 | aVolume = utils.format_volume(volumeBase); 93 | bVolume = utils.format_volume(volumeQuote); 94 | 95 | return ( 96 |
97 |
98 |
99 | {this.formatMessage("transaction_latest")} : 100 | {utils.price_text(latestPrice.full, quoteAsset, baseAsset)}  102 | 103 | 104 | {quoteAsset ? / : null} 105 | 106 |
107 |
108 | {this.formatMessage("transaction_volume")} : 109 | {aVolume} 110 | {bVolume} 111 |
112 |
113 | 114 |
115 | ); 116 | } 117 | } 118 | 119 | export default Transaction; -------------------------------------------------------------------------------- /app/components/wallet/AccountSelectInput.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by xiangxn on 2017/2/19. 3 | */ 4 | import React from "react"; 5 | import BaseComponent from "../BaseComponent"; 6 | import utils from "../../../common/utils"; 7 | import BindToChainState from "../Utility/BindToChainState"; 8 | import AccountImage from "../Utility/AccountImage"; 9 | import ChainTypes from "../Utility/ChainTypes"; 10 | import {ChainStore, PublicKey, ChainValidation} from "bitsharesjs"; 11 | 12 | class AccountSelectInput extends BaseComponent { 13 | static propTypes = { 14 | error: React.PropTypes.element, 15 | onChange: React.PropTypes.func, // 用户输入时调用 16 | onAccountChanged: React.PropTypes.func, // 账户选择变化时调用 17 | accountName: React.PropTypes.string, // 用户输入的当前账户名 18 | account: ChainTypes.ChainAccount, // 绑定的账户对象 19 | lable: React.PropTypes.string,//显示标题 20 | placeholder: React.PropTypes.string,//输入框提示内容 21 | } 22 | 23 | constructor(props) { 24 | super(props); 25 | } 26 | 27 | componentDidMount() { 28 | if (this.props.onAccountChanged && this.props.account) 29 | this.props.onAccountChanged(this.props.account); 30 | } 31 | 32 | componentWillReceiveProps(newProps) { 33 | if ((this.props.onAccountChanged && newProps.account) && newProps.account !== this.props.account) 34 | this.props.onAccountChanged(newProps.account); 35 | } 36 | 37 | getNameType(value) { 38 | if (!value) return null; 39 | if (value[0] === "#" && utils.is_object_id("1.2." + value.substring(1))) return "id"; 40 | if (ChainValidation.is_account_name(value, true)) return "name"; 41 | if (this.props.allowPubKey && PublicKey.fromPublicKeyString(value)) return "pubkey"; 42 | return null; 43 | } 44 | 45 | onInputChange(e) { 46 | let value = e.target.value.trim(); 47 | if (!this.props.allowUppercase) { 48 | value = value.toLowerCase(); 49 | } 50 | let newValue = value.match(/(?:#\/account\/)(.*)(?:\/overview)/); 51 | if (newValue) value = newValue[1]; 52 | 53 | if (this.props.onChange && value !== this.props.accountName) this.props.onChange(value); 54 | } 55 | 56 | render() { 57 | let type = this.getNameType(this.props.accountName); 58 | let lookup_display; 59 | if (this.props.allowPubKey) { 60 | if (type === "pubkey") lookup_display = "Public Key"; 61 | } else if (this.props.account) { 62 | if (type === "name") lookup_display = "#" + this.props.account.get("id").substring(4); 63 | else if (type === "id") lookup_display = this.props.account.get("name"); 64 | } 65 | let member_status = null; 66 | if (this.props.account) 67 | member_status = this.formatMessage("transfer_member_" + ChainStore.getAccountMemberStatus(this.props.account)); 68 | return ( 69 |
70 | 71 |
72 |
73 |
74 | {this.props.lable}{member_status} {lookup_display} 75 |
76 |
77 | 80 |
81 |
82 |
{this.props.error}
83 |
84 | ); 85 | } 86 | } 87 | export default BindToChainState(AccountSelectInput, {keep_updating: true}) -------------------------------------------------------------------------------- /app/components/wallet/ChangePassword.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by necklace on 2017/1/12. 3 | */ 4 | import React from "react"; 5 | import BaseComponent from "../BaseComponent"; 6 | import PasswordInput from "../wallet/PasswordInput"; 7 | import TextLoading from "../TextLoading"; 8 | 9 | //stores 10 | import WalletDb from "../../stores/WalletDb"; 11 | 12 | //actions 13 | import NotificationActions from "../../actions/NotificationActions"; 14 | 15 | class ChangePassword extends BaseComponent { 16 | constructor(props) { 17 | super(props); 18 | this.state = this.initState(); 19 | } 20 | 21 | initState() { 22 | return {error_message: null, oldPwd: '', newPwd: '', loading: false}; 23 | } 24 | 25 | onOldPwdChange(e) { 26 | this.setState({oldPwd: e.target.value}); 27 | } 28 | 29 | onNewPwdChange(e) { 30 | if (e.error) { 31 | this.setState({error_message: e.error, newPwd: null}); 32 | } else { 33 | this.setState({error_message: null, newPwd: e.value}); 34 | } 35 | } 36 | 37 | onAccept(e) { 38 | e.preventDefault(); 39 | let {oldPwd, newPwd} = this.state; 40 | this.setState({loading: true}); 41 | if (WalletDb.validatePassword(oldPwd)) { 42 | WalletDb.changePassword(oldPwd, newPwd, true) 43 | .then(() => { 44 | NotificationActions.success("Password changed"); 45 | this.setState(this.initState()); 46 | this.context.router.push("/settings/backup"); 47 | }) 48 | .catch(error => { 49 | console.error(error); 50 | NotificationActions.error("Unable to change password: " + error); 51 | this.setState({loading: false}); 52 | }); 53 | } else { 54 | NotificationActions.error("Invalid Password"); 55 | } 56 | } 57 | 58 | render() { 59 | return ( 60 |
61 |
62 |
{this.formatMessage('wallet_oldPassword')}
63 | 65 |
66 | 67 |
68 | {this.state.loading ? : 69 | 71 | } 72 |
73 | {this.state.error_message === null ? null : 74 |
75 | {this.state.error_message} 76 |
77 | } 78 |
79 | ); 80 | } 81 | } 82 | 83 | export default ChangePassword; -------------------------------------------------------------------------------- /app/components/wallet/PasswordInput.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by xiangxn on 2017/1/28. 3 | */ 4 | import React, {PropTypes} from "react"; 5 | import BaseComponent from "../BaseComponent"; 6 | 7 | class PasswordInput extends BaseComponent { 8 | 9 | static propTypes = { 10 | onChange: PropTypes.func, 11 | onEnter: PropTypes.func, 12 | confirmation: PropTypes.bool, 13 | wrongPassword: PropTypes.bool, 14 | noValidation: PropTypes.bool 15 | }; 16 | 17 | static defaultProps = { 18 | confirmation: false, 19 | wrongPassword: false, 20 | noValidation: false 21 | }; 22 | 23 | constructor() { 24 | super(); 25 | this.handleChange = this.handleChange.bind(this); 26 | this.onKeyDown = this.onKeyDown.bind(this); 27 | this.state = {value: "", error: null, wrong: false, doesnt_match: false}; 28 | } 29 | 30 | value() { 31 | let node = this.refs.password; 32 | return node ? node.value : ""; 33 | } 34 | 35 | clear() { 36 | this.refs.password.value = ""; 37 | if (this.props.confirmation) this.refs.confirm_password.value = ""; 38 | } 39 | 40 | focus() { 41 | this.refs.password.focus(); 42 | } 43 | 44 | valid() { 45 | return !(this.state.error || this.state.wrong || this.state.doesnt_match) && this.state.value.length >= 8; 46 | } 47 | 48 | handleChange(e) { 49 | e.preventDefault(); 50 | e.stopPropagation(); 51 | const confirmation = this.props.confirmation ? this.refs.confirm_password.value : true; 52 | const password = this.refs.password.value; 53 | const doesnt_match = this.props.confirmation ? confirmation && password !== confirmation : false; 54 | if (!this.props.noValidation && !this.state.error && (password.length > 0 && password.length < 8)) 55 | this.state.error = this.formatMessage('wallet_createErrMsg4'); 56 | if(doesnt_match) 57 | this.state.error = this.formatMessage('wallet_createErrMsg5'); 58 | if(this.state.wrong || this.props.wrongPassword) 59 | this.state.error = this.formatMessage('wallet_createErrMsg6'); 60 | let state = { 61 | valid: !this.state.error && !this.state.wrong 62 | && !(this.props.confirmation && doesnt_match) 63 | && confirmation && password.length >= 8, 64 | value: password, 65 | error: this.state.error, 66 | doesnt_match 67 | }; 68 | if (this.props.onChange) this.props.onChange(state); 69 | this.setState(state); 70 | this.setState({error:null}); 71 | } 72 | 73 | onKeyDown(e) { 74 | if (this.props.onEnter && e.keyCode === 13) this.props.onEnter(e); 75 | } 76 | 77 | render() { 78 | return ( 79 |
80 |
81 |
{this.formatMessage("wallet_password")}
82 | 86 |
87 | {!this.props.confirmation ? null : 88 |
89 |
{this.formatMessage("wallet_confirmPassword")}
90 | 94 |
95 | } 96 |
97 | ); 98 | } 99 | } 100 | export default PasswordInput; -------------------------------------------------------------------------------- /app/components/wallet/UnlockWallet.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by necklace on 2017/1/12. 3 | */ 4 | 5 | import React from "react"; 6 | import BaseComponent from "../BaseComponent"; 7 | import Modal from "../layout/Modal"; 8 | 9 | import {Apis} from "bitsharesjs-ws"; 10 | import AltContainer from "alt-container"; 11 | 12 | //stores 13 | import WalletDb from "../../stores/WalletDb"; 14 | import WalletUnlockStore from "../../stores/WalletUnlockStore"; 15 | //actions 16 | import NotificationActions from "../../actions/NotificationActions"; 17 | import WalletUnlockActions from "../../actions/WalletUnlockActions"; 18 | 19 | class UnlockWallet extends BaseComponent { 20 | constructor(props) { 21 | super(props); 22 | this.state = this.getInitialState(); 23 | this.onPasswordEnter = this.onPasswordEnter.bind(this); 24 | } 25 | 26 | getInitialState() { 27 | return { 28 | visible: false, 29 | password_error: null, 30 | password_input_reset: Date.now() 31 | } 32 | } 33 | 34 | componentWillReceiveProps(nextProps) { 35 | if (nextProps.resolve) { 36 | if (WalletDb.isLocked()) 37 | this.show(); 38 | else 39 | nextProps.resolve(); 40 | } 41 | } 42 | 43 | show() { 44 | let wallet = WalletDb.getWallet(); 45 | if (!wallet) { 46 | return; 47 | } 48 | if (Apis.instance().chain_id !== wallet.chain_id) { 49 | NotificationActions.error("This wallet was intended for a different block-chain; expecting " + 50 | wallet.chain_id.substring(0, 4).toUpperCase() + ", but got " + 51 | Apis.instance().chain_id.substring(0, 4).toUpperCase()); 52 | return; 53 | } 54 | this.setState({visible: true}); 55 | } 56 | 57 | hide(ok) { 58 | if (!ok) { 59 | WalletUnlockActions.cancel(); 60 | } 61 | this.setState({visible: false, password_error: null}); 62 | 63 | } 64 | 65 | onPasswordEnter(e) { 66 | e.preventDefault(); 67 | let password = this.refs.password_input.value; 68 | this.setState({password_error: null}); 69 | WalletDb.validatePassword(password || "", true); 70 | if (WalletDb.isLocked()) { 71 | this.setState({password_error: this.formatMessage("wallet_passwordErrMsg")}); 72 | return false; 73 | } 74 | else { 75 | this.refs.password_input.value = ""; 76 | this.props.resolve(); 77 | WalletUnlockActions.change(); 78 | this.setState({password_input_reset: Date.now(), password_error: null, visible: false}); 79 | } 80 | return false; 81 | } 82 | 83 | render() { 84 | return ( 85 |
86 | 87 |
{this.formatMessage('transaction_confirm_unlock')}
88 |
89 |
90 |
91 |
{this.formatMessage('wallet_password')}
92 | 94 |
95 |
96 |
97 | {this.state.password_error} 98 |
99 |
100 | 102 |
103 |
104 |
105 | ); 106 | } 107 | } 108 | 109 | class WalletUnlockModalContainer extends React.Component { 110 | render() { 111 | return ( 112 | 113 | 114 | 115 | ) 116 | } 117 | } 118 | export default WalletUnlockModalContainer -------------------------------------------------------------------------------- /app/components/wallet/WalletManage.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by necklace on 2017/1/11. 3 | */ 4 | import React from "react"; 5 | import BaseComponent from "../BaseComponent"; 6 | import connectToStores from 'alt-utils/lib/connectToStores'; 7 | import XNFullButton from "../form/XNFullButton"; 8 | import XNSelect from "../form/XNSelect"; 9 | 10 | //stores 11 | import WalletManagerStore from "../../stores/WalletManagerStore"; 12 | 13 | //actions 14 | import WalletActions from "../../actions/WalletActions"; 15 | import ConfirmActions from "../../actions/layout/ConfirmActions"; 16 | import WalletUnlockActions from "../../actions/WalletUnlockActions"; 17 | 18 | class WalletManage extends BaseComponent { 19 | static getPropsFromStores() { 20 | return WalletManagerStore.getState(); 21 | } 22 | 23 | static getStores() { 24 | return [WalletManagerStore]; 25 | } 26 | 27 | constructor(props) { 28 | super(props); 29 | this.state = {names: props.wallet_names}; 30 | } 31 | 32 | componentWillReceiveProps(nextProps) { 33 | if (nextProps.wallet_names.size != this.props.wallet_names.size) { 34 | this.setState({names: nextProps.wallet_names}); 35 | } 36 | } 37 | 38 | onImportKeyClick(e) { 39 | e.preventDefault(); 40 | this.context.router.push("/settings/import-key"); 41 | } 42 | 43 | onBackupClick(e) { 44 | e.preventDefault(); 45 | this.context.router.push("/settings/backup"); 46 | } 47 | 48 | onImportBackupClick(e) { 49 | e.preventDefault(); 50 | this.context.router.push("/settings/import-backup"); 51 | } 52 | 53 | onModifyPasswordClick(e) { 54 | e.preventDefault(); 55 | this.context.router.push("/settings/change-password"); 56 | } 57 | 58 | onWalletChange(item) { 59 | WalletActions.setWallet(item.value); 60 | } 61 | 62 | onDeleteWallet(item) { 63 | let names = this.props.wallet_names; 64 | let size = names.size; 65 | let current_wallet = this.props.current_wallet; 66 | let title = this.formatMessage('message_title'); 67 | let msg = this.formatMessage('wallet_confirmDelete'); 68 | WalletUnlockActions.unlock().then(() => { 69 | ConfirmActions.show(title, msg, () => { 70 | WalletManagerStore.onDeleteWallet(item.value); 71 | if (current_wallet === item.value) { 72 | if (size > 1) { 73 | let wn = null; 74 | names.forEach(name => { 75 | if (name !== item.value) { 76 | wn = name; 77 | } 78 | }); 79 | if (wn) WalletActions.setWallet(wn); 80 | } else { 81 | setTimeout(() => { 82 | window.location.reload(); 83 | }, 250); 84 | } 85 | } 86 | }, null, 3); 87 | }); 88 | } 89 | 90 | render() { 91 | let wallets = []; 92 | //console.debug(this.props.wallet_names); 93 | this.state.names.forEach(name => { 94 | wallets.push({text: name, value: name}); 95 | }); 96 | let current_wallet = this.props.current_wallet; 97 | return ( 98 |
99 | 104 | 106 | 108 | 110 | 112 |
113 | ); 114 | 115 | } 116 | } 117 | 118 | export default connectToStores(WalletManage); -------------------------------------------------------------------------------- /app/idb-helper.js: -------------------------------------------------------------------------------- 1 | var db 2 | var idb_helper 3 | 4 | module.exports = idb_helper = { 5 | 6 | set_graphene_db: database => { 7 | db = database 8 | }, 9 | 10 | trx_readwrite: object_stores => { 11 | return db.transaction( 12 | [object_stores], "readwrite" 13 | ) 14 | }, 15 | 16 | on_request_end: (request) => { 17 | //return request => { 18 | return new Promise((resolve, reject) => { 19 | request.onsuccess = new ChainEvent( 20 | request.onsuccess, resolve, request).event 21 | request.onerror = new ChainEvent( 22 | request.onerror, reject, request).event 23 | }) 24 | //}(request) 25 | }, 26 | 27 | on_transaction_end: (transaction) => { 28 | return new Promise((resolve, reject) => { 29 | transaction.oncomplete = new ChainEvent( 30 | transaction.oncomplete, resolve).event 31 | transaction.onabort = new ChainEvent( 32 | transaction.onabort, reject).event 33 | }) 34 | }, 35 | 36 | /** Chain an add event. Provide the @param store and @param object and 37 | this method gives you convenient hooks into the database events. 38 | 39 | @param event_callback (within active transaction) 40 | @return Promise (resolves or rejects outside of the transaction) 41 | */ 42 | add: (store, object, event_callback) => { 43 | return function(object, event_callback) { 44 | let request = store.add(object); 45 | let event_promise = null; 46 | if(event_callback) 47 | request.onsuccess = new ChainEvent( 48 | request.onsuccess, event => { 49 | event_promise = event_callback(event); 50 | }).event; 51 | 52 | let request_promise = idb_helper.on_request_end(request).then( event => { 53 | //DEBUG console.log('... object',object,'result',event.target.result,'event',event) 54 | if ( event.target.result != void 0) { 55 | //todo does event provide the keyPath name? (instead of id) 56 | object.id = event.target.result; 57 | } 58 | return [ object, event ]; 59 | }); 60 | 61 | if(event_promise) 62 | return Promise.all([event_promise, request_promise]); 63 | return request_promise; 64 | 65 | }(object, event_callback);//copy var references for callbacks 66 | }, 67 | 68 | /** callback may return false to indicate that iteration should stop */ 69 | cursor: (store_name, callback, transaction) => { 70 | return new Promise((resolve, reject)=>{ 71 | if( ! transaction) { 72 | transaction = db.transaction( 73 | [store_name], "readonly" 74 | ) 75 | transaction.onerror = error => { 76 | console.error("ERROR idb_helper.cursor transaction", error) 77 | reject(error) 78 | } 79 | } 80 | 81 | let store = transaction.objectStore(store_name); 82 | let request = store.openCursor() 83 | request.onsuccess = e => { 84 | let cursor = e.target.result; 85 | var ret = callback(cursor, e) 86 | if(ret === false) resolve() 87 | if(!cursor) resolve(ret) 88 | }; 89 | request.onerror = (e) => { 90 | var error = { 91 | error: e.target.error.message, 92 | data: e 93 | } 94 | console.log("ERROR idb_helper.cursor request", error) 95 | reject(error); 96 | }; 97 | 98 | }).then() 99 | }, 100 | 101 | autoIncrement_unique: (db, table_name, unique_index) => { 102 | return db.createObjectStore( 103 | table_name, { keyPath: "id", autoIncrement: true } 104 | ).createIndex( 105 | "by_"+unique_index, unique_index, { unique: true } 106 | ) 107 | } 108 | 109 | } 110 | 111 | class ChainEvent { 112 | constructor(existing_on_event, callback, request) { 113 | this.event = (event)=> { 114 | if(event.target.error) 115 | console.error("---- transaction error ---->", event.target.error) 116 | //event.request = request 117 | callback(event) 118 | if(existing_on_event) 119 | existing_on_event(event) 120 | } 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /app/idb-root.js: -------------------------------------------------------------------------------- 1 | import idb_helper from "./idb-helper" 2 | import {Apis} from "bitsharesjs-ws"; 3 | 4 | const DB_VERSION_MAIN = 1 5 | const DB_PREFIX = "btsgo_db" 6 | 7 | /** Usage: openIndexDB.then( db => ... */ 8 | export default class iDBRoot { 9 | 10 | constructor(impl) { 11 | this.impl = impl 12 | } 13 | 14 | setDbSuffix(db_suffix) { 15 | // "graphene_db_06f667" 16 | this.database_name = DB_PREFIX + db_suffix 17 | } 18 | 19 | /** @return promise */ 20 | openIndexedDB() { 21 | if(this.db) return Promise.resolve(this.db) 22 | return new Promise( (resolve, reject) => { 23 | var openRequest = this.impl.open(this.database_name, DB_VERSION_MAIN) 24 | openRequest.onupgradeneeded = e => { 25 | this.db = e.target.result 26 | this.db.createObjectStore("properties", { keyPath: "name" }) 27 | } 28 | openRequest.onsuccess = e => { 29 | this.db = e.target.result 30 | resolve(this.db) 31 | } 32 | openRequest.onerror = e => { 33 | reject(e.target.error) 34 | } 35 | }) 36 | } 37 | 38 | /** @return promise */ 39 | getProperty(name, default_value) { 40 | return this.openIndexedDB().then( db => { 41 | var transaction = db.transaction(["properties"], "readonly") 42 | var store = transaction.objectStore("properties") 43 | return idb_helper.on_request_end( store.get(name) ).then( event => { 44 | var result = event.target.result 45 | return result ? result.value : default_value 46 | }) 47 | }).catch( error => { console.error(error); throw error }) 48 | } 49 | 50 | /** @return promise */ 51 | setProperty(name, value) { 52 | return this.openIndexedDB().then( db => { 53 | var transaction = db.transaction(["properties"], "readwrite") 54 | var store = transaction.objectStore("properties") 55 | if(value && value["toJS"]) value = value.toJS() //Immutable-js 56 | return idb_helper.on_request_end( store.put({name, value}) ) 57 | }).catch( error => { console.error(error); throw error }) 58 | } 59 | 60 | deleteDatabase(are_you_sure = false) { 61 | if( ! are_you_sure) return "Are you sure?" 62 | console.log("deleting", this.database_name) 63 | var req = iDB.impl.deleteDatabase(this.database_name) 64 | return req.result 65 | } 66 | 67 | close() { 68 | this.db.close() 69 | this.db = null 70 | } 71 | 72 | } 73 | -------------------------------------------------------------------------------- /app/main.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by xiangxn on 2016/12/10. 3 | */ 4 | 5 | import './assets/loader'; 6 | import './app'; 7 | 8 | -------------------------------------------------------------------------------- /app/stores/AccountRefsStore.js: -------------------------------------------------------------------------------- 1 | import alt from "../../common/altObj"; 2 | import iDB from "../idb-instance"; 3 | import Immutable from "immutable"; 4 | import BaseStore from "./BaseStore"; 5 | import {ChainStore} from "bitsharesjs"; 6 | import PrivateKeyStore from "./PrivateKeyStore" 7 | import PrivateKeyActions from "../actions/PrivateKeyActions" 8 | 9 | class AccountRefsStore extends BaseStore { 10 | 11 | constructor() { 12 | super() 13 | this._export("loadDbData") 14 | this.state = this._getInitialState() 15 | this.bindListeners({ onAddPrivateKey: PrivateKeyActions.addKey }) 16 | this.no_account_refs = Immutable.Set() // Set of account ids 17 | ChainStore.subscribe(this.chainStoreUpdate.bind(this)) 18 | } 19 | 20 | _getInitialState() { 21 | this.chainstore_account_ids_by_key = null 22 | return { 23 | account_refs: Immutable.Set() 24 | // loading_account_refs: false 25 | } 26 | } 27 | 28 | onAddPrivateKey({private_key_object}) { 29 | if(ChainStore.getAccountRefsOfKey(private_key_object.pubkey) !== undefined) 30 | this.chainStoreUpdate() 31 | } 32 | 33 | loadDbData() { 34 | this.setState(this._getInitialState()) 35 | return loadNoAccountRefs() 36 | .then( no_account_refs => this.no_account_refs = no_account_refs ) 37 | .then( ()=> this.chainStoreUpdate() ) 38 | } 39 | 40 | chainStoreUpdate() { 41 | if(this.chainstore_account_ids_by_key === ChainStore.account_ids_by_key) return 42 | this.chainstore_account_ids_by_key = ChainStore.account_ids_by_key 43 | this.checkPrivateKeyStore() 44 | } 45 | 46 | checkPrivateKeyStore() { 47 | var no_account_refs = this.no_account_refs 48 | var account_refs = Immutable.Set() 49 | PrivateKeyStore.getState().keys.keySeq().forEach( pubkey => { 50 | if(no_account_refs.has(pubkey)) return 51 | var refs = ChainStore.getAccountRefsOfKey(pubkey) 52 | if(refs === undefined) return 53 | if( ! refs.size) { 54 | // Performance optimization... 55 | // There are no references for this public key, this is going 56 | // to block it. There many be many TITAN keys that do not have 57 | // accounts for example. 58 | { 59 | // Do Not block brainkey generated keys.. Those are new and 60 | // account references may be pending. 61 | var private_key_object = PrivateKeyStore.getState().keys.get(pubkey) 62 | if( typeof private_key_object.brainkey_sequence === 'number' ) { 63 | return 64 | } 65 | } 66 | no_account_refs = no_account_refs.add(pubkey) 67 | return 68 | } 69 | account_refs = account_refs.add(refs.valueSeq()) 70 | }) 71 | account_refs = account_refs.flatten() 72 | if( ! this.state.account_refs.equals(account_refs)) { 73 | // console.log("AccountRefsStore account_refs",account_refs.size); 74 | this.setState({account_refs}) 75 | } 76 | if(!this.no_account_refs.equals(no_account_refs)) { 77 | this.no_account_refs = no_account_refs 78 | saveNoAccountRefs(no_account_refs) 79 | } 80 | } 81 | 82 | } 83 | 84 | export default alt.createStore(AccountRefsStore, "AccountRefsStore") 85 | 86 | // Performance optimization for large wallets 87 | function loadNoAccountRefs() { 88 | return iDB.root.getProperty("no_account_refs", []) 89 | .then( array => Immutable.Set(array) ) 90 | } 91 | 92 | function saveNoAccountRefs(no_account_refs) { 93 | var array = [] 94 | for(let pubkey of no_account_refs) array.push(pubkey) 95 | iDB.root.setProperty("no_account_refs", array) 96 | } 97 | -------------------------------------------------------------------------------- /app/stores/AddressIndex.js: -------------------------------------------------------------------------------- 1 | import alt from "../../common/altObj"; 2 | import iDB from "../idb-instance"; 3 | import {key} from "bitsharesjs"; 4 | import {ChainConfig} from "bitsharesjs-ws"; 5 | import Immutable from "immutable" 6 | import BaseStore from "./BaseStore" 7 | 8 | class AddressIndex extends BaseStore { 9 | 10 | constructor() { 11 | super() 12 | this.state = { 13 | addresses: Immutable.Map(), 14 | saving: false 15 | } 16 | this.pubkeys = new Set() 17 | // loadAddyMap is for debugging, this.add will load this on startup 18 | this._export("add", "addAll", "loadAddyMap") 19 | } 20 | 21 | saving() { 22 | if( this.state.saving ) return 23 | this.state.saving = true 24 | this.setState({ saving: true }) 25 | } 26 | 27 | /** Add public key string (if not already added). Reasonably efficient 28 | for less than 10K keys. 29 | */ 30 | add(pubkey) { 31 | this.loadAddyMap().then( () => { 32 | var dirty = false 33 | if(this.pubkeys.has(pubkey)) return 34 | this.pubkeys.add(pubkey) 35 | this.saving() 36 | // Gather all 5 legacy address formats (see key.addresses) 37 | var address_strings = key.addresses(pubkey) 38 | for(let address of address_strings) { 39 | this.state.addresses = this.state.addresses.set(address, pubkey) 40 | dirty = true 41 | } 42 | if( dirty ) { 43 | this.setState({ addresses: this.state.addresses }) 44 | this.saveAddyMap() 45 | } else this.setState({saving: false}) 46 | }).catch ( e => { throw e }) 47 | } 48 | 49 | /** Worker thread implementation (for more than 10K keys) */ 50 | addAll(pubkeys) { 51 | return new Promise( (resolve, reject) => { 52 | this.saving() 53 | this.loadAddyMap().then( () => { 54 | var AddressIndexWorker = require("worker?name=/[hash].js!../workers/AddressIndexWorker") 55 | var worker = new AddressIndexWorker 56 | worker.postMessage({ pubkeys, address_prefix: ChainConfig.address_prefix }) 57 | var _this = this 58 | worker.onmessage = event => { try { 59 | var key_addresses = event.data 60 | var dirty = false 61 | var addresses = _this.state.addresses.withMutations( addresses => { 62 | for(let i = 0; i < pubkeys.length; i++) { 63 | var pubkey = pubkeys[i] 64 | if(_this.pubkeys.has(pubkey)) continue 65 | _this.pubkeys.add(pubkey) 66 | // Gather all 5 legacy address formats (see key.addresses) 67 | var address_strings = key_addresses[i] 68 | for(let address of address_strings) { 69 | addresses.set(address, pubkey) 70 | dirty = true 71 | } 72 | } 73 | }) 74 | if( dirty ) { 75 | _this.setState({ addresses }) 76 | _this.saveAddyMap() 77 | } else { 78 | _this.setState({ saving: false }) 79 | } 80 | resolve() 81 | } catch( e ) { console.error('AddressIndex.addAll', e); reject(e) }} 82 | }).catch ( e => { throw e }) 83 | }) 84 | } 85 | 86 | loadAddyMap() { 87 | if(this.loadAddyMapPromise) return this.loadAddyMapPromise 88 | this.loadAddyMapPromise = iDB.root.getProperty("AddressIndex").then( map => { 89 | this.state.addresses = map ? Immutable.Map(map) : Immutable.Map() 90 | console.log("AddressIndex load", this.state.addresses.size) 91 | this.state.addresses.valueSeq().forEach( pubkey => this.pubkeys.add(pubkey) ) 92 | this.setState({ addresses: this.state.addresses }) 93 | }) 94 | return this.loadAddyMapPromise 95 | } 96 | 97 | saveAddyMap() { 98 | clearTimeout(this.saveAddyMapTimeout) 99 | this.saveAddyMapTimeout = setTimeout(()=> { 100 | console.log("AddressIndex save", this.state.addresses.size) 101 | this.setState({saving: false}) 102 | // If indexedDB fails to save, it will re-try via PrivateKeyStore calling this.add 103 | return iDB.root.setProperty("AddressIndex", this.state.addresses.toObject()) 104 | }, 100) 105 | } 106 | 107 | } 108 | // console.log("post msg a"); 109 | // worker.postMessage("a") 110 | export default alt.createStore(AddressIndex, "AddressIndex"); 111 | -------------------------------------------------------------------------------- /app/stores/AssetStore.js: -------------------------------------------------------------------------------- 1 | import BaseStore from "./BaseStore"; 2 | import Immutable from "immutable"; 3 | import alt from "../../common/altObj"; 4 | import AssetActions from "../actions/AssetActions"; 5 | import {Asset} from "./tcomb_structs"; 6 | import utils from "../../common/utils"; 7 | 8 | 9 | class AssetStore extends BaseStore { 10 | constructor() { 11 | super(); 12 | this.assets = Immutable.Map(); 13 | this.asset_symbol_to_id = {}; 14 | this.searchTerms = {}; 15 | this.lookupResults = []; 16 | 17 | this.bindListeners({ 18 | onGetAssetList: AssetActions.getAssetList, 19 | onGetAsset: AssetActions.getAsset, 20 | onLookupAsset: AssetActions.lookupAsset 21 | }); 22 | this._export("getAsset"); 23 | } 24 | 25 | getAsset(id_or_symbol) { 26 | let id = utils.is_object_id(id_or_symbol) ? id_or_symbol : this.asset_symbol_to_id[id_or_symbol]; 27 | return this.assets.get(id); 28 | } 29 | 30 | onGetAssetList(payload) { 31 | if (!payload) { 32 | return false; 33 | } 34 | 35 | payload.assets.forEach(asset => { 36 | 37 | for (var i = 0; i < payload.dynamic_data.length; i++) { 38 | if (payload.dynamic_data[i].id === asset.dynamic_asset_data_id) { 39 | asset.dynamic_data = payload.dynamic_data[i]; 40 | break; 41 | } 42 | } 43 | 44 | if (asset.bitasset_data_id) { 45 | asset.market_asset = true; 46 | 47 | for (var i = 0; i < payload.bitasset_data.length; i++) { 48 | if (payload.bitasset_data[i].id === asset.bitasset_data_id) { 49 | asset.bitasset_data = payload.bitasset_data[i]; 50 | break; 51 | } 52 | } 53 | } else { 54 | asset.market_asset = false; 55 | } 56 | 57 | this.assets = this.assets.set( 58 | asset.id, 59 | Asset(asset) 60 | ); 61 | 62 | this.asset_symbol_to_id[asset.symbol] = asset.id; 63 | }); 64 | 65 | } 66 | 67 | onGetAsset(payload) { 68 | let { 69 | asset 70 | } = payload; 71 | 72 | if (payload.asset === null) { 73 | this.assets = this.assets.set( 74 | payload.symbol, 75 | {notFound: true} 76 | ); 77 | return true; 78 | } 79 | 80 | // console.log("onGetAsset payload:", payload); 81 | asset.dynamic_data = payload.dynamic_data; 82 | 83 | if (payload.bitasset_data) { 84 | asset.bitasset_data = payload.bitasset_data; 85 | asset.market_asset = true; 86 | } else { 87 | asset.market_asset = false; 88 | } 89 | 90 | this.assets = this.assets.set( 91 | asset.id, 92 | Asset(asset) 93 | ); 94 | 95 | this.asset_symbol_to_id[asset.symbol] = asset.id; 96 | } 97 | 98 | onLookupAsset(payload) { 99 | this.searchTerms[payload.searchID] = payload.symbol; 100 | this.lookupResults = payload.assets; 101 | } 102 | } 103 | 104 | export default alt.createStore(AssetStore, "AssetStore"); -------------------------------------------------------------------------------- /app/stores/BackupStore.js: -------------------------------------------------------------------------------- 1 | import alt from "../../common/altObj"; 2 | import BackupActions from "../actions/BackupActions"; 3 | import BaseStore from "./BaseStore"; 4 | import {hash, PublicKey} from "bitsharesjs"; 5 | 6 | class BackupStore extends BaseStore { 7 | 8 | constructor() { 9 | super() 10 | this.state = this._getInitialState() 11 | this.bindListeners({ 12 | onIncommingFile: BackupActions.incommingWebFile, 13 | onIncommingBuffer: BackupActions.incommingBuffer, 14 | onReset: BackupActions.reset 15 | }) 16 | this._export("setWalletObjct") 17 | } 18 | 19 | _getInitialState() { 20 | return { 21 | name: null, 22 | contents: null, 23 | sha1: null, 24 | size: null, 25 | last_modified: null, 26 | public_key: null, 27 | wallet_object: null 28 | } 29 | } 30 | 31 | setWalletObjct(wallet_object) { 32 | this.setState({wallet_object}) 33 | } 34 | 35 | onReset() { 36 | this.setState(this._getInitialState()) 37 | } 38 | 39 | onIncommingFile({name, contents, last_modified}) { 40 | var sha1 = hash.sha1(contents).toString('hex') 41 | var size = contents.length 42 | var public_key = getBackupPublicKey(contents) 43 | this.setState({ name, contents, sha1, size, last_modified, public_key }) 44 | } 45 | 46 | onIncommingBuffer({name, contents, public_key}) { 47 | this.onReset() 48 | var sha1 = hash.sha1(contents).toString('hex') 49 | var size = contents.length 50 | if( ! public_key) public_key = getBackupPublicKey(contents) 51 | this.setState({name, contents, sha1, size, public_key}) 52 | } 53 | } 54 | 55 | export var BackupStoreWrapped = alt.createStore(BackupStore, "BackupStore"); 56 | export default BackupStoreWrapped 57 | 58 | function getBackupPublicKey(contents) { 59 | try { 60 | return PublicKey.fromBuffer(contents.slice(0, 33)) 61 | } catch(e) { 62 | console.error(e, e.stack) 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /app/stores/BaseStore.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by necklace on 2016/12/27. 3 | */ 4 | 5 | export const STORAGE_KEY = "__btsgo__"; 6 | 7 | class BaseStore { 8 | 9 | _export(...methods) { 10 | let publicMethods = {}; 11 | methods.forEach((method) => { 12 | if(!this[method]) throw new Error(`BaseStore._export: method '${method}' not found in ${this.__proto__._storeName}`); 13 | this[method] = this[method].bind(this); 14 | publicMethods[method] = this[method]; 15 | }); 16 | this.exportPublicMethods(publicMethods); 17 | } 18 | } 19 | 20 | export default BaseStore; -------------------------------------------------------------------------------- /app/stores/CachedPropertyStore.js: -------------------------------------------------------------------------------- 1 | import alt from "../../common/altObj" 2 | import Immutable from "immutable" 3 | import iDB from "../idb-instance" 4 | import BaseStore from "./BaseStore" 5 | import CachedPropertyActions from "../actions/CachedPropertyActions" 6 | 7 | class CachedPropertyStore extends BaseStore { 8 | 9 | constructor() { 10 | super() 11 | this.state = this._getInitialState() 12 | this.bindListeners({ 13 | onSet: CachedPropertyActions.set, 14 | onGet: CachedPropertyActions.get 15 | }) 16 | this._export("get", "reset") 17 | } 18 | 19 | _getInitialState() { 20 | return { 21 | props: Immutable.Map() 22 | } 23 | } 24 | 25 | get(name) { 26 | return this.onGet({ name }) 27 | } 28 | 29 | onSet({ name, value }) { 30 | if(this.state.props.get(name) === value) return 31 | var props = this.state.props.set(name, value) 32 | this.state.props = props 33 | iDB.setCachedProperty(name, value).then(()=> 34 | this.setState({ props })) 35 | } 36 | 37 | onGet({ name }) { 38 | var value = this.state.props.get(name) 39 | if(value !== undefined) return value 40 | iDB.getCachedProperty(name, null).then( value => { 41 | var props = this.state.props.set(name, value) 42 | this.state.props = props 43 | this.setState({ props }) 44 | }) 45 | } 46 | 47 | reset() { 48 | this.state = this._getInitialState() 49 | this.setState(this.state) 50 | } 51 | } 52 | 53 | export var CachedPropertyStoreWrapped = alt.createStore(CachedPropertyStore, "CachedPropertyStore") 54 | export default CachedPropertyStoreWrapped -------------------------------------------------------------------------------- /app/stores/ImportKeysStore.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by xiangxn on 2017/1/27. 3 | */ 4 | 5 | import alt from "../../common/altObj"; 6 | import BaseStore from "./BaseStore"; 7 | 8 | class ImportKeysStore extends BaseStore { 9 | 10 | constructor() { 11 | super() 12 | this.state = this._getInitialState() 13 | this._export("importing") 14 | } 15 | 16 | _getInitialState() { 17 | return { importing: false } 18 | } 19 | 20 | importing(importing) { 21 | this.setState({ importing }) 22 | } 23 | } 24 | 25 | export default alt.createStore(ImportKeysStore, "ImportKeysStore"); -------------------------------------------------------------------------------- /app/stores/IntlStore.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by necklace on 2016/12/27. 3 | */ 4 | 5 | import alt from '../../common/altObj'; 6 | import BaseStore, {STORAGE_KEY} from './BaseStore'; 7 | import IntlActions from '../actions/IntlActions'; 8 | import SettingsActions from '../actions/SettingsActions'; 9 | import ls from '../../common/localStorage'; 10 | 11 | let ss = new ls(STORAGE_KEY); 12 | 13 | import {addLocaleData} from 'react-intl'; 14 | import {zh_CN} from '../assets/locales/locale-zh'; 15 | import {en_US} from '../assets/locales/locale-en'; 16 | import en from 'react-intl/locale-data/en'; 17 | import zh from 'react-intl/locale-data/zh'; 18 | addLocaleData(zh); 19 | addLocaleData(en); 20 | 21 | class IntlStore extends BaseStore { 22 | constructor() { 23 | super(); 24 | this.currentLocale = ss.has("settings_v3") ? ss.get("settings_v3").locale : "zh"; 25 | 26 | this.locales = ["zh", "en"]; 27 | //console.debug(zh_CN); 28 | this.localesObject = {zh: zh_CN, en: en_US}; 29 | 30 | this.bindListeners({ 31 | onSwitchLocale: IntlActions.switchLocale, 32 | onGetLocale: IntlActions.getLocale, 33 | onClearSettings: SettingsActions.clearSettings 34 | }); 35 | 36 | this._export("getCurrentLocale", "hasLocale"); 37 | } 38 | 39 | hasLocale(locale) { 40 | return this.locales.indexOf(locale) !== -1; 41 | } 42 | 43 | getCurrentLocale() { 44 | return this.currentLocale; 45 | } 46 | 47 | onSwitchLocale({locale}) { 48 | 49 | switch (locale) { 50 | case "zh": 51 | this.localesObject[locale] = zh_CN; 52 | break; 53 | case "en": 54 | this.localesObject[locale] = en_US; 55 | break; 56 | default: 57 | this.localesObject[locale] = zh_CN; 58 | break; 59 | } 60 | this.currentLocale = locale; 61 | } 62 | 63 | onGetLocale(locale) { 64 | if (this.locales.indexOf(locale) === -1) { 65 | this.locales.push(locale); 66 | } 67 | } 68 | 69 | onClearSettings() { 70 | this.onSwitchLocale("zh"); 71 | } 72 | } 73 | 74 | export default alt.createStore(IntlStore, "IntlStore"); -------------------------------------------------------------------------------- /app/stores/NotificationStore.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by xiangxn on 2017/1/27. 3 | */ 4 | 5 | import alt from "../../common/altObj"; 6 | import NotificationActions from "../actions/NotificationActions"; 7 | 8 | 9 | class NotificationStore { 10 | 11 | constructor() { 12 | this.bindListeners({ 13 | addNotification: [ 14 | NotificationActions.addNotification, 15 | NotificationActions.success, 16 | NotificationActions.warning, 17 | NotificationActions.error, 18 | NotificationActions.info 19 | ] 20 | }) 21 | 22 | this.state = { 23 | notification: null 24 | } 25 | } 26 | 27 | addNotification(notification) { 28 | this.setState({ notification: notification }) 29 | } 30 | } 31 | 32 | export default alt.createStore(NotificationStore, 'NotificationStore'); -------------------------------------------------------------------------------- /app/stores/ScanStore.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by xiangxn on 2017/10/18. 3 | */ 4 | 5 | import alt from '../../common/altObj'; 6 | import BaseStore from "./BaseStore"; 7 | import ScanActions from "../actions/ScanActions"; 8 | 9 | class ScanStore extends BaseStore { 10 | constructor() { 11 | super(); 12 | this.bindActions(ScanActions); 13 | this.state = this.__initState(); 14 | } 15 | 16 | __initState() { 17 | return {routerState: null, qrStr: null}; 18 | } 19 | 20 | onScan(routerState) { 21 | this.setState({routerState}); 22 | } 23 | 24 | onSetScanResult(qrStr) { 25 | this.setState({qrStr}); 26 | } 27 | 28 | onReset() { 29 | this.setState(this.__initState()); 30 | } 31 | } 32 | 33 | export default alt.createStore(ScanStore, "ScanStore"); -------------------------------------------------------------------------------- /app/stores/TransactionConfirmStore.js: -------------------------------------------------------------------------------- 1 | import alt from "../../common/altObj"; 2 | import TransactionConfirmActions from "../actions/TransactionConfirmActions"; 3 | 4 | class TransactionConfirmStore { 5 | 6 | constructor() { 7 | this.bindActions(TransactionConfirmActions); 8 | this.state = this.getInitialState(); 9 | this.exportPublicMethods({reset: this.reset.bind(this)}); 10 | } 11 | 12 | getInitialState() { 13 | //console.log("-- TransactionConfirmStore.getInitialState -->"); 14 | return { 15 | transaction: null, 16 | error: null, 17 | broadcasting: false, 18 | broadcast: false, 19 | included: false, 20 | trx_id: null, 21 | trx_block_num: null, 22 | closed: true, 23 | broadcasted_transaction: null, 24 | propose: false, 25 | fee_paying_account: null // proposal fee_paying_account 26 | }; 27 | } 28 | 29 | onConfirm({transaction}) { 30 | let init_state = this.getInitialState(); 31 | let state = {...init_state, transaction: transaction, closed: false, broadcasted_transaction: null} 32 | //console.log("-- TransactionConfirmStore.onConfirm -->", state); 33 | this.setState(state); 34 | } 35 | 36 | onClose() { 37 | //console.log("-- TransactionConfirmStore.onClose -->", state); 38 | this.setState({closed: true}); 39 | } 40 | 41 | onBroadcast(payload) { 42 | //console.log("-- TransactionConfirmStore.onBroadcast -->", state); 43 | this.setState(payload); 44 | if (payload.broadcasted_transaction) { 45 | this.setState({ 46 | broadcasted_transaction: this.state.transaction 47 | }); 48 | } 49 | } 50 | 51 | onWasBroadcast(res) { 52 | //console.log("-- TransactionConfirmStore.onWasBroadcast -->", state); 53 | this.setState({broadcasting: false, broadcast: true}); 54 | } 55 | 56 | onWasIncluded(res) { 57 | //console.log("-- TransactionConfirmStore.onWasIncluded -->", this.state); 58 | this.setState({ 59 | error: null, 60 | broadcasting: false, 61 | broadcast: true, 62 | included: true, 63 | trx_id: res[0].id, 64 | trx_block_num: res[0].block_num, 65 | broadcasted_transaction: this.state.transaction 66 | }); 67 | } 68 | 69 | onError({ error }) { 70 | this.setState({broadcast: false, broadcasting: false, error}); 71 | } 72 | 73 | onTogglePropose() { 74 | this.setState({ propose: ! this.state.propose }); 75 | } 76 | 77 | onProposeFeePayingAccount(fee_paying_account) { 78 | this.setState({ fee_paying_account }); 79 | } 80 | 81 | reset() { 82 | //console.log("-- TransactionConfirmStore.reset -->"); 83 | this.state = this.getInitialState(); 84 | } 85 | 86 | } 87 | 88 | export default alt.createStore(TransactionConfirmStore, 'TransactionConfirmStore'); 89 | -------------------------------------------------------------------------------- /app/stores/WalletUnlockStore.js: -------------------------------------------------------------------------------- 1 | import alt from "../../common/altObj"; 2 | import BaseStore, {STORAGE_KEY} from "./BaseStore"; 3 | 4 | import WalletUnlockActions from "../actions/WalletUnlockActions"; 5 | import SettingsActions from "../actions/SettingsActions"; 6 | import WalletDb from "./WalletDb"; 7 | import ls from "../../common/localStorage"; 8 | 9 | let ss = new ls(STORAGE_KEY); 10 | 11 | class WalletUnlockStore extends BaseStore { 12 | 13 | constructor() { 14 | super(); 15 | this.bindActions(WalletUnlockActions); 16 | this.state = {locked: true}; 17 | 18 | this.walletLockTimeout = this._getTimeout(); // seconds (10 minutes 19 | this.timeout = null; 20 | 21 | this.bindListeners({ 22 | onChangeSetting: SettingsActions.changeSetting 23 | }); 24 | 25 | // let timeoutSetting = this._getTimeout(); 26 | 27 | // if (timeoutSetting) { 28 | // this.walletLockTimeout = timeoutSetting; 29 | // } 30 | 31 | } 32 | 33 | onUnlock({resolve, reject}) { 34 | console.log('... onUnlock setState', WalletDb.isLocked()); 35 | 36 | this._setLockTimeout(); 37 | if (!WalletDb.isLocked()) { 38 | resolve() 39 | return 40 | } 41 | 42 | this.setState({resolve, reject, locked: WalletDb.isLocked()}); 43 | } 44 | 45 | onLock({resolve}) { 46 | //DEBUG console.log('... WalletUnlockStore\tprogramatic lock', WalletDb.isLocked()) 47 | if (WalletDb.isLocked()) { 48 | resolve() 49 | return 50 | } 51 | WalletDb.onLock() 52 | this.setState({resolve: null, reject: null, locked: WalletDb.isLocked()}) 53 | resolve() 54 | } 55 | 56 | onCancel() { 57 | //this.state.reject(); 58 | this.setState({resolve: null, reject: null}); 59 | } 60 | 61 | onChange() { 62 | this.setState({locked: WalletDb.isLocked()}) 63 | } 64 | 65 | 66 | onChangeSetting(payload) { 67 | if (payload.setting === "walletLockTimeout") { 68 | this.walletLockTimeout = payload.value; 69 | this._clearLockTimeout(); 70 | this._setLockTimeout(); 71 | } 72 | } 73 | 74 | 75 | _setLockTimeout() { 76 | this._clearLockTimeout(); 77 | this.timeout = setTimeout(() => { 78 | if (!WalletDb.isLocked()) { 79 | console.log("auto locking after", this.walletLockTimeout, "s"); 80 | WalletDb.onLock() 81 | this.setState({locked: true}) 82 | } 83 | ; 84 | }, this.walletLockTimeout * 1000); 85 | } 86 | 87 | _clearLockTimeout() { 88 | if (this.timeout) { 89 | clearTimeout(this.timeout); 90 | this.timeout = null; 91 | } 92 | } 93 | 94 | _getTimeout() { 95 | return parseInt(ss.get("lockTimeout", 600), 10); 96 | } 97 | } 98 | 99 | export default alt.createStore(WalletUnlockStore, 'WalletUnlockStore') 100 | -------------------------------------------------------------------------------- /app/stores/layout/ConfirmStore.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by necklace on 2017/3/17. 3 | */ 4 | import alt from "../../../common/altObj"; 5 | import ConfirmActions from "../../actions/layout/ConfirmActions"; 6 | 7 | class ConfirmStore { 8 | constructor() { 9 | this.bindActions(ConfirmActions); 10 | this.state = this.initState(); 11 | } 12 | 13 | initState() { 14 | return { 15 | show: false, 16 | title: '', 17 | msg: '', 18 | height: 4, 19 | showCancelButton: true, 20 | onOK: null, 21 | onCancel: null 22 | }; 23 | } 24 | 25 | onReset() { 26 | this.setState(this.initState()); 27 | } 28 | 29 | onShow({title, msg, onok, oncancel, height, nocancel}) { 30 | this.setState( 31 | { 32 | show: true, 33 | title: title, 34 | msg: msg, 35 | height: height ? height : 4, 36 | showCancelButton: nocancel ? false : true, 37 | onOK: onok ? onok : null, 38 | onCancel: oncancel ? oncancel : null 39 | } 40 | ); 41 | } 42 | } 43 | export default alt.createStore(ConfirmStore, 'ConfirmStore'); -------------------------------------------------------------------------------- /app/stores/tcomb_structs.js: -------------------------------------------------------------------------------- 1 | import t from "tcomb"; 2 | 3 | let Asset = t.struct({ 4 | bitasset_data_id: t.maybe(t.Str), 5 | bitasset_data: t.maybe(t.Obj), 6 | dynamic_asset_data_id: t.Str, 7 | dynamic_data: t.maybe(t.Obj), 8 | id: t.Str, 9 | issuer: t.Str, 10 | market_asset: t.Bool, 11 | options: t.Obj, 12 | precision: t.Num, 13 | symbol: t.Str 14 | }, "Asset"); 15 | 16 | let Block = t.struct({ 17 | extensions: t.Arr, 18 | id: t.Num, 19 | previous: t.Str, 20 | timestamp: t.Dat, 21 | transactions: t.Arr, 22 | transaction_merkle_root: t.Str, 23 | witness: t.Str, 24 | witness_signature: t.Str 25 | }, "Block"); 26 | 27 | let WalletTcomb = t.struct({ 28 | public_name: t.Str, 29 | created: t.Dat, 30 | last_modified: t.Dat, 31 | backup_date: t.maybe(t.Dat), 32 | password_pubkey: t.Str, 33 | encryption_key: t.Str, 34 | encrypted_brainkey: t.maybe(t.Str), 35 | brainkey_pubkey: t.Str, 36 | brainkey_sequence: t.Num, 37 | brainkey_backup_date: t.maybe(t.Dat), 38 | deposit_keys: t.maybe(t.Obj), 39 | // password_checksum: t.Str, 40 | chain_id: t.Str 41 | }, "WalletTcomb"); 42 | 43 | let PrivateKeyTcomb = t.struct({ 44 | id: t.maybe(t.Num), 45 | pubkey: t.Str, 46 | label: t.maybe(t.Str), 47 | import_account_names: t.maybe(t.Arr), 48 | brainkey_sequence: t.maybe(t.Num), 49 | encrypted_key: t.Str 50 | }, "PrivateKeyTcomb"); 51 | 52 | //let PublicKeyTcomb = t.struct({ 53 | // id: t.maybe(t.Num), 54 | // pubkey: t.Str, 55 | // key_id: t.maybe(t.Str) 56 | //}, "PublicKeyTcomb"); 57 | 58 | let LimitOrder = t.struct({ 59 | expiration: t.Dat, 60 | for_sale: t.Num, 61 | id: t.Str, 62 | sell_price: t.Obj, 63 | seller: t.Str 64 | }, "LimitOrder"); 65 | 66 | let SettleOrder = t.struct({ 67 | settlement_date: t.Dat, 68 | balance: t.Obj, 69 | owner: t.Str 70 | }, "SettleOrder"); 71 | 72 | let ShortOrder = t.struct({ 73 | expiration: t.Dat, 74 | for_sale: t.Num, 75 | id: t.Str, 76 | sell_price: t.Obj, 77 | seller: t.Str 78 | }, "ShortOrder"); 79 | 80 | let CallOrder = t.struct({ 81 | borrower: t.Str, 82 | call_price: t.Obj, 83 | collateral: t.Num, 84 | debt: t.Num, 85 | id: t.Str 86 | }, "CallOrder"); 87 | 88 | 89 | module.exports = { 90 | Asset: Asset, 91 | Block: Block, 92 | WalletTcomb: WalletTcomb, 93 | //PublicKeyTcomb: PublicKeyTcomb, 94 | PrivateKeyTcomb: PrivateKeyTcomb, 95 | LimitOrder: LimitOrder, 96 | ShortOrder: ShortOrder, 97 | CallOrder: CallOrder, 98 | SettleOrder: SettleOrder 99 | }; 100 | -------------------------------------------------------------------------------- /app/workers/AddressIndexWorker.js: -------------------------------------------------------------------------------- 1 | import {key} from "bitsharesjs"; 2 | 3 | onmessage = function(event) { 4 | try { 5 | console.log("AddressIndexWorker start"); 6 | var {pubkeys, address_prefix} = event.data 7 | var results = [] 8 | for (let pubkey of pubkeys) { 9 | results.push( key.addresses(pubkey, address_prefix) ) 10 | } 11 | postMessage( results ) 12 | console.log("AddressIndexWorker done"); 13 | } catch( e ) { 14 | console.error("AddressIndexWorker", e) 15 | } 16 | } -------------------------------------------------------------------------------- /app/workers/AesWorker.js: -------------------------------------------------------------------------------- 1 | import "babel-polyfill"; 2 | import {key, Aes} from "bitsharesjs"; 3 | 4 | onmessage = function(event) { try { 5 | console.log("AesWorker start"); 6 | var {private_plainhex_array, iv, key} = event.data 7 | var aes = new Aes(iv, key) 8 | var private_cipherhex_array = [] 9 | for(let private_plainhex of private_plainhex_array) { 10 | var private_cipherhex = aes.encryptHex( private_plainhex ) 11 | private_cipherhex_array.push( private_cipherhex ) 12 | } 13 | postMessage( private_cipherhex_array ) 14 | console.log("AesWorker done"); 15 | } catch( e ) { console.error("AesWorker", e) } } 16 | -------------------------------------------------------------------------------- /app/workers/GenesisFilterWorker.js: -------------------------------------------------------------------------------- 1 | 2 | import GenesisFilter from "../../common/GenesisFilter"; 3 | 4 | onmessage = function(event) { try { 5 | console.log("GenesisFilterWorker start"); 6 | var { account_keys, bloom_filter } = event.data 7 | var genesis_filter = new GenesisFilter( bloom_filter ) 8 | genesis_filter.filter( account_keys, status => { 9 | if( status.success ) { 10 | postMessage({ account_keys, status }) 11 | console.log("GenesisFilterWorker done") 12 | return 13 | } 14 | postMessage({ status }) 15 | }) 16 | } catch( e ) { console.error("GenesisFilterWorker", e) } } -------------------------------------------------------------------------------- /build/.gitignore: -------------------------------------------------------------------------------- 1 | assets -------------------------------------------------------------------------------- /build/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiangxn/btsgo/4f991511c371aaadb6556179e70611ebf7e6d579/build/favicon.ico -------------------------------------------------------------------------------- /build/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | BTSGO.NET-分布式交易系统 10 | 11 | 26 | 35 | 36 | 37 | 38 |
39 |
Loading...
40 |
41 | 42 |
43 | 44 |
45 | 46 | -------------------------------------------------------------------------------- /common/account_constants.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | account_listing: { 3 | no_listing: 0x0, ///< No opinion is specified about this account 4 | white_listed: 0x1, ///< This account is whitelisted, but not blacklisted 5 | black_listed: 0x2, ///< This account is blacklisted, but not whitelisted 6 | white_and_black_listed: 0x1 | 0x2 ///< This account is both whitelisted and blacklisted 7 | } 8 | }; 9 | -------------------------------------------------------------------------------- /common/account_utils.js: -------------------------------------------------------------------------------- 1 | import {ChainStore} from "bitsharesjs"; 2 | import utils from "./utils"; 3 | import {Asset} from "./MarketClasses"; 4 | 5 | export default class AccountUtils { 6 | 7 | /** 8 | * takes asset as immutable object or id, fee as integer amount 9 | * @return undefined if asset is undefined 10 | * @return false if fee pool has insufficient balance 11 | * @return true if the fee pool has sufficient balance 12 | */ 13 | static checkFeePool(asset, fee) { 14 | asset = asset.toJS ? asset : ChainStore.getAsset(asset); 15 | if (!asset) { 16 | return undefined; 17 | } 18 | 19 | let feePool = parseInt(asset.getIn(["dynamic", "fee_pool"]), 10); 20 | 21 | return feePool >= fee; 22 | } 23 | 24 | static getPossibleFees(account, operation) { 25 | let core = ChainStore.getAsset("1.3.0"); 26 | account = !account || account.toJS ? account : ChainStore.getAccount(account); 27 | 28 | if (!account || !core) { 29 | return {assets: ["1.3.0"], fees: {"1.3.0": 0}}; 30 | } 31 | 32 | let assets = [], fees = {}; 33 | 34 | let globalObject = ChainStore.getObject("2.0.0"); 35 | 36 | let fee = utils.estimateFee(operation, null, globalObject); 37 | 38 | let accountBalances = account.get("balances"); 39 | if (!accountBalances) { 40 | return {assets: ["1.3.0"], fees: {"1.3.0": 0}}; 41 | } 42 | 43 | accountBalances.forEach((balanceID, assetID) => { 44 | let balanceObject = ChainStore.getObject(balanceID); 45 | let balance = balanceObject ? parseInt(balanceObject.get("balance"), 10) : 0; 46 | let hasBalance = false, eqFee; 47 | 48 | if (assetID === "1.3.0" && balance >= fee) { 49 | hasBalance = true; 50 | } else if (balance && ChainStore.getAsset(assetID)) { 51 | let asset = ChainStore.getAsset(assetID); 52 | let price = utils.convertPrice(core, asset.getIn(["options", "core_exchange_rate"]).toJS(), null, asset.get("id")); 53 | 54 | eqFee = parseInt(utils.convertValue(price, fee, core, asset), 10); 55 | if (parseInt(eqFee, 10) !== eqFee) { 56 | eqFee += 1; // Add 1 to round up; 57 | } 58 | if (balance >= eqFee && this.checkFeePool(asset, eqFee)) { 59 | hasBalance = true; 60 | } 61 | } 62 | if (hasBalance) { 63 | assets.push(assetID); 64 | fees[assetID] = eqFee ? eqFee : fee; 65 | } 66 | }) 67 | 68 | return {assets, fees}; 69 | } 70 | 71 | static getFinalFeeAsset(account, operation, fee_asset_id = "1.3.0") { 72 | let {assets: feeAssets} = this.getPossibleFees(account, operation); 73 | if (feeAssets.length === 1) { 74 | fee_asset_id = feeAssets[0]; 75 | } else if (feeAssets.length > 0 && feeAssets.indexOf(fee_asset_id) === -1) { 76 | fee_asset_id = feeAssets[0]; 77 | } 78 | 79 | return fee_asset_id; 80 | } 81 | 82 | static getBalanceById(balanceId) { 83 | if (balanceId) { 84 | let balance = ChainStore.getObject(balanceId); 85 | if (balance) { 86 | let asset = ChainStore.getObject(balance.get("asset_type")); 87 | let amount = Number(balance.get("balance")); 88 | //console.debug(balance.get("asset_type")) 89 | //console.debug(asset.get('precision')) 90 | return new Asset({asset_id: balance.get("asset_type"), amount: amount, precision: asset.get('precision')}); 91 | } 92 | } 93 | return 0; 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /common/altObj.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by xiangxn on 2016/12/12. 3 | */ 4 | import Alt from "alt"; 5 | var alt = new Alt(); 6 | 7 | // 输出所有action日志 8 | //alt.dispatcher.register(console.log.bind(console, 'alt.dispatcher')) 9 | 10 | export default alt; -------------------------------------------------------------------------------- /common/asset_constants.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | permission_flags: { 3 | charge_market_fee : 0x01, /**< an issuer-specified percentage of all market trades in this asset is paid to the issuer */ 4 | white_list : 0x02, /**< accounts must be whitelisted in order to hold this asset */ 5 | override_authority : 0x04, /**< issuer may transfer asset back to himself */ 6 | transfer_restricted : 0x08, /**< require the issuer to be one party to every transfer */ 7 | disable_force_settle : 0x10, /**< disable force settling */ 8 | global_settle : 0x20, /**< allow the bitasset issuer to force a global settling -- this may be set in permissions, but not flags */ 9 | disable_confidential : 0x40, /**< allow the asset to be used with confidential transactions */ 10 | witness_fed_asset : 0x80, /**< allow the asset to be fed by witnesses */ 11 | committee_fed_asset : 0x100 /**< allow the asset to be fed by the committee */ 12 | }, 13 | uia_permission_mask: [ 14 | "charge_market_fee", 15 | "white_list", 16 | "override_authority", 17 | "transfer_restricted", 18 | "disable_confidential" 19 | ], 20 | GRAPHENE_100_PERCENT: 10000, 21 | GRAPHENE_1_PERCENT: 10000 / 100 22 | }; 23 | 24 | 25 | /* 26 | 27 | const static uint32_t ASSET_ISSUER_PERMISSION_MASK = charge_market_fee|white_list|override_authority|transfer_restricted|disable_force_settle|global_settle|disable_confidential 28 | |witness_fed_asset|committee_fed_asset; 29 | const static uint32_t UIA_ASSET_ISSUER_PERMISSION_MASK = charge_market_fee|white_list|override_authority|transfer_restricted|disable_confidential; 30 | 31 | */ 32 | -------------------------------------------------------------------------------- /common/asset_utils.js: -------------------------------------------------------------------------------- 1 | import assetConstants from "./asset_constants"; 2 | 3 | export default class AssetUtils { 4 | 5 | static getFlagBooleans(mask, isBitAsset = false) { 6 | let booleans = { 7 | charge_market_fee : false, 8 | white_list : false, 9 | override_authority : false, 10 | transfer_restricted : false, 11 | disable_force_settle : false, 12 | global_settle : false, 13 | disable_confidential : false, 14 | witness_fed_asset : false, 15 | committee_fed_asset : false 16 | } 17 | 18 | if (mask === "all") { 19 | for (let flag in booleans) { 20 | if (!isBitAsset && (assetConstants.uia_permission_mask.indexOf(flag) === -1)) { 21 | delete booleans[flag]; 22 | } else { 23 | booleans[flag] = true; 24 | } 25 | } 26 | return booleans; 27 | } 28 | 29 | for (let flag in booleans) { 30 | if (!isBitAsset && (assetConstants.uia_permission_mask.indexOf(flag) === -1)) { 31 | delete booleans[flag]; 32 | } else { 33 | if (mask & assetConstants.permission_flags[flag]) { 34 | booleans[flag] = true; 35 | } 36 | } 37 | } 38 | 39 | return booleans; 40 | } 41 | 42 | static getFlags(flagBooleans) { 43 | let keys = Object.keys(assetConstants.permission_flags); 44 | 45 | let flags = 0; 46 | 47 | keys.forEach(key => { 48 | if (flagBooleans[key] && key !== "global_settle") { 49 | flags += assetConstants.permission_flags[key]; 50 | } 51 | }) 52 | 53 | return flags; 54 | } 55 | 56 | static getPermissions(flagBooleans, isBitAsset = false) { 57 | let permissions = isBitAsset ? Object.keys(assetConstants.permission_flags) : assetConstants.uia_permission_mask; 58 | let flags = 0; 59 | permissions.forEach(permission => { 60 | if (flagBooleans[permission] && permission !== "global_settle") { 61 | flags += assetConstants.permission_flags[permission]; 62 | } 63 | }) 64 | 65 | if (isBitAsset) { 66 | flags += assetConstants.permission_flags["global_settle"]; 67 | } 68 | 69 | return flags; 70 | } 71 | 72 | static parseDescription(description) { 73 | let parsed; 74 | try { 75 | parsed = JSON.parse(description) 76 | } catch (error) { 77 | 78 | } 79 | 80 | return parsed ? parsed : {main: description}; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /common/localStorage.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by xiangxn on 2016/12/11. 3 | */ 4 | import ls, {ls_key_exists} from './localStorageImpl'; 5 | 6 | if (null === ls) throw "localStorage is required but isn't available on this platform"; 7 | 8 | module.exports = (key) => { 9 | var STORAGE_KEY = key; 10 | return { 11 | get(key, dv = {}) { 12 | let rv; 13 | if (ls_key_exists(STORAGE_KEY + key, ls)) { 14 | rv = JSON.parse(ls.getItem(STORAGE_KEY + key)); 15 | } 16 | return rv ? rv : dv; 17 | }, 18 | set(key, object) { 19 | if (object && object.toJS) { 20 | object = object.toJS(); 21 | } 22 | ls.setItem(STORAGE_KEY + key, JSON.stringify(object)); 23 | }, 24 | remove(key) { 25 | ls.removeItem(STORAGE_KEY + key); 26 | }, 27 | has(key) { 28 | return ls_key_exists(STORAGE_KEY + key, ls); 29 | } 30 | }; 31 | } -------------------------------------------------------------------------------- /common/localStorageImpl.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by xiangxn on 2016/12/11. 3 | */ 4 | var ls_key_exists = function _ls_key_exists(key,ls) { return (key in ls); } 5 | export {ls_key_exists}; 6 | export default (typeof localStorage === "undefined" ? null : localStorage); -------------------------------------------------------------------------------- /cordova/config.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | BTSGO 4 | 5 | BTSGO.NET-分布式交易系统 6 | 7 | 8 | Necklace 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /cordova/release.bat: -------------------------------------------------------------------------------- 1 | cordova build android --release -------------------------------------------------------------------------------- /cordova/res/icon/android/drawable-hdpi/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiangxn/btsgo/4f991511c371aaadb6556179e70611ebf7e6d579/cordova/res/icon/android/drawable-hdpi/icon.png -------------------------------------------------------------------------------- /cordova/res/icon/android/drawable-ldpi/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiangxn/btsgo/4f991511c371aaadb6556179e70611ebf7e6d579/cordova/res/icon/android/drawable-ldpi/icon.png -------------------------------------------------------------------------------- /cordova/res/icon/android/drawable-mdpi/bts.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiangxn/btsgo/4f991511c371aaadb6556179e70611ebf7e6d579/cordova/res/icon/android/drawable-mdpi/bts.png -------------------------------------------------------------------------------- /cordova/res/icon/android/drawable-mdpi/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiangxn/btsgo/4f991511c371aaadb6556179e70611ebf7e6d579/cordova/res/icon/android/drawable-mdpi/icon.png -------------------------------------------------------------------------------- /cordova/res/icon/android/drawable-xhdpi/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiangxn/btsgo/4f991511c371aaadb6556179e70611ebf7e6d579/cordova/res/icon/android/drawable-xhdpi/icon.png -------------------------------------------------------------------------------- /cordova/res/screen/android/splash-land-hdpi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiangxn/btsgo/4f991511c371aaadb6556179e70611ebf7e6d579/cordova/res/screen/android/splash-land-hdpi.png -------------------------------------------------------------------------------- /cordova/res/screen/android/splash-land-ldpi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiangxn/btsgo/4f991511c371aaadb6556179e70611ebf7e6d579/cordova/res/screen/android/splash-land-ldpi.png -------------------------------------------------------------------------------- /cordova/res/screen/android/splash-land-mdpi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiangxn/btsgo/4f991511c371aaadb6556179e70611ebf7e6d579/cordova/res/screen/android/splash-land-mdpi.png -------------------------------------------------------------------------------- /cordova/res/screen/android/splash-land-xhdpi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiangxn/btsgo/4f991511c371aaadb6556179e70611ebf7e6d579/cordova/res/screen/android/splash-land-xhdpi.png -------------------------------------------------------------------------------- /cordova/res/screen/android/splash-port-hdpi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiangxn/btsgo/4f991511c371aaadb6556179e70611ebf7e6d579/cordova/res/screen/android/splash-port-hdpi.png -------------------------------------------------------------------------------- /cordova/res/screen/android/splash-port-ldpi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiangxn/btsgo/4f991511c371aaadb6556179e70611ebf7e6d579/cordova/res/screen/android/splash-port-ldpi.png -------------------------------------------------------------------------------- /cordova/res/screen/android/splash-port-mdpi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiangxn/btsgo/4f991511c371aaadb6556179e70611ebf7e6d579/cordova/res/screen/android/splash-port-mdpi.png -------------------------------------------------------------------------------- /cordova/res/screen/android/splash-port-xhdpi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiangxn/btsgo/4f991511c371aaadb6556179e70611ebf7e6d579/cordova/res/screen/android/splash-port-xhdpi.png -------------------------------------------------------------------------------- /cordova/www/index.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | BTSGO.NET-分布式交易系统 11 | 12 | 13 | 28 | 37 | 38 | 39 |
40 |
Loading...
41 |
42 | 43 |
44 | 45 |
46 | 47 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "btsgo.net", 3 | "version": "0.1.0", 4 | "description": "bitshares2.0 UI", 5 | "main": "main.js", 6 | "devDependencies": { 7 | "babel-cli": "^6.18.0", 8 | "babel-core": "^6.20.0", 9 | "babel-loader": "^6.2.9", 10 | "babel-preset-es2015": "^6.18.0", 11 | "babel-preset-react": "^6.16.0", 12 | "babel-preset-stage-0": "^6.16.0", 13 | "css-loader": "^0.26.1", 14 | "extract-text-webpack-plugin": "^1.0.1", 15 | "file-loader": "^0.9.0", 16 | "git-rev-sync": "^1.8.0", 17 | "json-loader": "^0.5.4", 18 | "jsx-loader": "^0.13.2", 19 | "node-sass": "^4.0.0", 20 | "postcss-loader": "^1.2.2", 21 | "redux-devtools": "^3.3.1", 22 | "sass-loader": "^4.0.2", 23 | "style-loader": "^0.13.1", 24 | "url-loader": "^0.5.7", 25 | "autoprefixer": "^6.5.4", 26 | "webpack": "^1.14.0", 27 | "webpack-dev-server": "^3.1.11", 28 | "worker-loader": "^0.7.1" 29 | }, 30 | "scripts": { 31 | "build": "webpack", 32 | "start": "webpack-dev-server --history-api-fallback --devtool eval --progress --colors --content-base build", 33 | "release": "webpack --config webpack.pro.config.js -p --progress --display-error-details --colors" 34 | }, 35 | "repository": { 36 | "type": "git", 37 | "url": "git+https://github.com/xiangxn/btsgo.git" 38 | }, 39 | "keywords": [ 40 | "bts", 41 | "ui" 42 | ], 43 | "author": "necklace", 44 | "license": "MIT", 45 | "bugs": { 46 | "url": "https://github.com/xiangxn/btsgo/issues" 47 | }, 48 | "homepage": "https://github.com/xiangxn/btsgo#readme", 49 | "dependencies": { 50 | "alt": "^0.18.6", 51 | "alt-container": "^1.0.2", 52 | "alt-utils": "^1.0.0", 53 | "babel-polyfill": "^6.23.0", 54 | "bignumber.js": "^4.0.0", 55 | "bitsharesjs": "^1.3.1", 56 | "exif-js": "^2.1.1", 57 | "file-saver": "^1.3.3", 58 | "fractional": "^1.0.0", 59 | "history": "^3.2.1", 60 | "immutable": "^3.8.1", 61 | "indexeddbshim": "^2.2.1", 62 | "intl": "^1.2.5", 63 | "jdenticon": "git+https://github.com/cryptonomex/jdenticon.git", 64 | "js-sha256": "^0.2.3", 65 | "lodash": "^4.17.11", 66 | "lzma": "2.1.6", 67 | "numeral": "^2.0.4", 68 | "pica": "^2.0.8", 69 | "qrcode-reader": "^0.2.2", 70 | "react": "^15.4.1", 71 | "react-dom": "^15.4.1", 72 | "react-gestures": "^0.1.8", 73 | "react-intl": "^2.2.0", 74 | "react-notification-system": "^0.2.11", 75 | "react-redux": "^4.4.6", 76 | "react-router": "^3.0.0", 77 | "redux": "^3.6.0", 78 | "tcomb": "^3.2.16", 79 | "whatwg-fetch": "^2.0.2" 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by xiangxn on 2016/12/11. 3 | */ 4 | let path = require('path'); 5 | let webpack = require('webpack'); 6 | let ExtractTextPlugin = require('extract-text-webpack-plugin'); 7 | var git = require("git-rev-sync"); 8 | 9 | var root_dir = path.resolve(__dirname); 10 | 11 | module.exports = { 12 | entry: ['webpack/hot/dev-server', path.resolve(root_dir, './app/main.js')], 13 | output: { 14 | path: path.resolve(root_dir, 'build/assets'), 15 | filename: 'bundle.js', 16 | publicPath: '/assets/' 17 | }, 18 | devServer: { 19 | host: "0.0.0.0", 20 | port: 8080, 21 | hot: true 22 | }, 23 | module: { 24 | loaders: [ 25 | { 26 | test: /\.js|jsx$/, 27 | exclude: [/node_modules/], 28 | loaders: ['babel?presets[]=es2015,presets[]=react,presets[]=stage-0'] 29 | }, 30 | { 31 | test: /\.scss$/, 32 | loader: ExtractTextPlugin.extract('style', 'css!postcss!sass') 33 | }, 34 | { 35 | test: /\.(png|jpg|jpeg|gif|woff|otf)$/, loader: 'url?limit=8192' 36 | }, 37 | { 38 | test: /\.json$/, loader: 'json', 39 | exclude: [ 40 | path.resolve(root_dir, "common") 41 | ] 42 | }] 43 | }, 44 | postcss: [ 45 | require('autoprefixer') 46 | ], 47 | plugins: [ 48 | new webpack.DefinePlugin({ 49 | 'process.env': { 50 | NODE_ENV: JSON.stringify('development') 51 | }, 52 | APP_VERSION: JSON.stringify(git.tag()), 53 | __BASE_URL__: JSON.stringify("assets"), 54 | __HASHHISTORY__: true 55 | }), 56 | new webpack.HotModuleReplacementPlugin(), 57 | new ExtractTextPlugin('style.css', {allChunks: true}) 58 | ], 59 | node: { 60 | fs: "empty" 61 | } 62 | }; -------------------------------------------------------------------------------- /webpack.pro.config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by xiangxn on 2016/12/10. 3 | */ 4 | let path = require('path'); 5 | let webpack = require('webpack'); 6 | let ExtractTextPlugin = require('extract-text-webpack-plugin'); 7 | var git = require("git-rev-sync"); 8 | 9 | var root_dir = path.resolve(__dirname); 10 | 11 | module.exports = { 12 | entry: [path.resolve(root_dir, './app/main.js')],//'babel-polyfill' 13 | output: { 14 | path: path.resolve(root_dir, 'build/assets'), 15 | filename: 'bundle.js', 16 | publicPath: '/assets/' 17 | }, 18 | module: { 19 | loaders: [ 20 | { 21 | test: /\.js|jsx$/, 22 | exclude: [/node_modules/], 23 | loaders: ['babel?presets[]=es2015,presets[]=react,presets[]=stage-0'] 24 | }, 25 | { 26 | test: /\.scss$/, 27 | loader: ExtractTextPlugin.extract('style', 'css!postcss!sass') 28 | //loader: 'css!sass?sourceMap' 29 | }, 30 | { 31 | test: /\.(png|jpg|jpeg|gif|woff|otf)$/, loader: 'url?limit=8192' 32 | }, 33 | { 34 | test: /\.json$/, loader: 'json', 35 | exclude: [ 36 | path.resolve(root_dir, "common") 37 | ] 38 | }] 39 | }, 40 | postcss: [ 41 | require('autoprefixer') 42 | ], 43 | plugins: [ 44 | new webpack.DefinePlugin({ 45 | 'process.env': { 46 | NODE_ENV: JSON.stringify('production') 47 | }, 48 | APP_VERSION: JSON.stringify(git.tag()), 49 | __BASE_URL__: JSON.stringify("assets"), 50 | __HASHHISTORY__: true 51 | }), 52 | new webpack.optimize.UglifyJsPlugin({ 53 | output: {comments: false}, 54 | compress: {warnings: false} 55 | }), 56 | new ExtractTextPlugin('style.css', {allChunks: true}) 57 | ] 58 | }; --------------------------------------------------------------------------------