commit
798988597b
@ -0,0 +1,202 @@ |
|||||||
|
version: 2 |
||||||
|
|
||||||
|
workflows: |
||||||
|
version: 2 |
||||||
|
full_test: |
||||||
|
jobs: |
||||||
|
- prep-deps-npm |
||||||
|
- prep-deps-firefox |
||||||
|
- prep-scss: |
||||||
|
requires: |
||||||
|
- prep-deps-npm |
||||||
|
- test-lint: |
||||||
|
requires: |
||||||
|
- prep-deps-npm |
||||||
|
- test-unit: |
||||||
|
requires: |
||||||
|
- prep-deps-npm |
||||||
|
- test-integration-mascara-chrome: |
||||||
|
requires: |
||||||
|
- prep-deps-npm |
||||||
|
- prep-scss |
||||||
|
- test-integration-mascara-firefox: |
||||||
|
requires: |
||||||
|
- prep-deps-npm |
||||||
|
- prep-deps-firefox |
||||||
|
- prep-scss |
||||||
|
- test-integration-flat-chrome: |
||||||
|
requires: |
||||||
|
- prep-deps-npm |
||||||
|
- prep-scss |
||||||
|
- test-integration-flat-firefox: |
||||||
|
requires: |
||||||
|
- prep-deps-npm |
||||||
|
- prep-deps-firefox |
||||||
|
- prep-scss |
||||||
|
|
||||||
|
jobs: |
||||||
|
prep-deps-npm: |
||||||
|
docker: |
||||||
|
- image: circleci/node:8-browsers |
||||||
|
steps: |
||||||
|
- checkout |
||||||
|
- restore_cache: |
||||||
|
key: dependency-cache-{{ checksum "package-lock.json" }} |
||||||
|
- run: |
||||||
|
name: Install deps via npm |
||||||
|
command: npm install |
||||||
|
- save_cache: |
||||||
|
key: dependency-cache-{{ checksum "package-lock.json" }} |
||||||
|
paths: |
||||||
|
- node_modules |
||||||
|
|
||||||
|
prep-deps-firefox: |
||||||
|
docker: |
||||||
|
- image: circleci/node:8-browsers |
||||||
|
steps: |
||||||
|
- checkout |
||||||
|
- run: |
||||||
|
name: Download Firefox |
||||||
|
command: > |
||||||
|
wget https://ftp.mozilla.org/pub/firefox/releases/58.0/linux-x86_64/en-US/firefox-58.0.tar.bz2 |
||||||
|
&& tar xjf firefox-58.0.tar.bz2 |
||||||
|
- save_cache: |
||||||
|
key: dependency-cache-firefox-{{ .Revision }} |
||||||
|
paths: |
||||||
|
- firefox |
||||||
|
|
||||||
|
|
||||||
|
prep-scss: |
||||||
|
docker: |
||||||
|
- image: circleci/node:8-browsers |
||||||
|
steps: |
||||||
|
- checkout |
||||||
|
- restore_cache: |
||||||
|
key: dependency-cache-{{ checksum "package-lock.json" }} |
||||||
|
- run: |
||||||
|
name: Get Scss Cache key |
||||||
|
# this allows us to checksum against a whole directory |
||||||
|
command: find ui/app/css -type f -exec md5sum {} \; | sort -k 2 > scss_checksum |
||||||
|
- run: |
||||||
|
name: Build for integration tests |
||||||
|
command: npm run test:integration:build |
||||||
|
- save_cache: |
||||||
|
key: scss-cache-{{ checksum "scss_checksum" }} |
||||||
|
paths: |
||||||
|
- ui/app/css/output |
||||||
|
|
||||||
|
test-lint: |
||||||
|
docker: |
||||||
|
- image: circleci/node:8-browsers |
||||||
|
steps: |
||||||
|
- checkout |
||||||
|
- restore_cache: |
||||||
|
key: dependency-cache-{{ checksum "package-lock.json" }} |
||||||
|
- run: |
||||||
|
name: Test |
||||||
|
command: npm run lint |
||||||
|
|
||||||
|
test-unit: |
||||||
|
docker: |
||||||
|
- image: circleci/node:8-browsers |
||||||
|
steps: |
||||||
|
- checkout |
||||||
|
- restore_cache: |
||||||
|
key: dependency-cache-{{ checksum "package-lock.json" }} |
||||||
|
- run: |
||||||
|
name: test:coverage |
||||||
|
command: npm run test:coverage |
||||||
|
|
||||||
|
test-integration-flat-firefox: |
||||||
|
environment: |
||||||
|
browsers: '["Firefox"]' |
||||||
|
docker: |
||||||
|
- image: circleci/node:8-browsers |
||||||
|
steps: |
||||||
|
- checkout |
||||||
|
- restore_cache: |
||||||
|
key: dependency-cache-firefox-{{ .Revision }} |
||||||
|
- run: |
||||||
|
name: Install firefox |
||||||
|
command: > |
||||||
|
sudo rm -r /opt/firefox |
||||||
|
&& sudo mv firefox /opt/firefox58 |
||||||
|
&& sudo mv /usr/bin/firefox /usr/bin/firefox-old |
||||||
|
&& sudo ln -s /opt/firefox58/firefox /usr/bin/firefox |
||||||
|
- restore_cache: |
||||||
|
key: dependency-cache-{{ checksum "package-lock.json" }} |
||||||
|
- run: |
||||||
|
name: Get Scss Cache key |
||||||
|
# this allows us to checksum against a whole directory |
||||||
|
command: find ui/app/css -type f -exec md5sum {} \; | sort -k 2 > scss_checksum |
||||||
|
- restore_cache: |
||||||
|
key: scss-cache-{{ checksum "scss_checksum" }} |
||||||
|
- run: |
||||||
|
name: test:integration:flat |
||||||
|
command: npm run test:flat |
||||||
|
|
||||||
|
test-integration-flat-chrome: |
||||||
|
environment: |
||||||
|
browsers: '["Chrome"]' |
||||||
|
docker: |
||||||
|
- image: circleci/node:8-browsers |
||||||
|
steps: |
||||||
|
- checkout |
||||||
|
- restore_cache: |
||||||
|
key: dependency-cache-{{ checksum "package-lock.json" }} |
||||||
|
- run: |
||||||
|
name: Get Scss Cache key |
||||||
|
# this allows us to checksum against a whole directory |
||||||
|
command: find ui/app/css -type f -exec md5sum {} \; | sort -k 2 > scss_checksum |
||||||
|
- restore_cache: |
||||||
|
key: scss-cache-{{ checksum "scss_checksum" }} |
||||||
|
- run: |
||||||
|
name: test:integration:flat |
||||||
|
command: npm run test:flat |
||||||
|
|
||||||
|
test-integration-mascara-firefox: |
||||||
|
environment: |
||||||
|
browsers: '["Firefox"]' |
||||||
|
docker: |
||||||
|
- image: circleci/node:8-browsers |
||||||
|
steps: |
||||||
|
- checkout |
||||||
|
- restore_cache: |
||||||
|
key: dependency-cache-firefox-{{ .Revision }} |
||||||
|
- run: |
||||||
|
name: Install firefox |
||||||
|
command: > |
||||||
|
sudo rm -r /opt/firefox |
||||||
|
&& sudo mv firefox /opt/firefox58 |
||||||
|
&& sudo mv /usr/bin/firefox /usr/bin/firefox-old |
||||||
|
&& sudo ln -s /opt/firefox58/firefox /usr/bin/firefox |
||||||
|
- restore_cache: |
||||||
|
key: dependency-cache-{{ checksum "package-lock.json" }} |
||||||
|
- run: |
||||||
|
name: Get Scss Cache key |
||||||
|
# this allows us to checksum against a whole directory |
||||||
|
command: find ui/app/css -type f -exec md5sum {} \; | sort -k 2 > scss_checksum |
||||||
|
- restore_cache: |
||||||
|
key: scss-cache-{{ checksum "scss_checksum" }} |
||||||
|
- run: |
||||||
|
name: test:integration:mascara |
||||||
|
command: npm run test:mascara |
||||||
|
|
||||||
|
test-integration-mascara-chrome: |
||||||
|
environment: |
||||||
|
browsers: '["Chrome"]' |
||||||
|
docker: |
||||||
|
- image: circleci/node:8-browsers |
||||||
|
steps: |
||||||
|
- checkout |
||||||
|
- restore_cache: |
||||||
|
key: dependency-cache-{{ checksum "package-lock.json" }} |
||||||
|
- run: |
||||||
|
name: Get Scss Cache key |
||||||
|
# this allows us to checksum against a whole directory |
||||||
|
command: find ui/app/css -type f -exec md5sum {} \; | sort -k 2 > scss_checksum |
||||||
|
- restore_cache: |
||||||
|
key: scss-cache-{{ checksum "scss_checksum" }} |
||||||
|
- run: |
||||||
|
name: test:integration:mascara |
||||||
|
command: npm run test:mascara |
@ -0,0 +1,3 @@ |
|||||||
|
{ |
||||||
|
"exceptions": ["https://nodesecurity.io/advisories/566"] |
||||||
|
} |
@ -1,10 +1,609 @@ |
|||||||
{ |
{ |
||||||
|
"accept": { |
||||||
|
"message": "Accept" |
||||||
|
}, |
||||||
|
"account": { |
||||||
|
"message": "Account" |
||||||
|
}, |
||||||
|
"accountDetails": { |
||||||
|
"message": "Account Details" |
||||||
|
}, |
||||||
|
"accountName": { |
||||||
|
"message": "Account Name" |
||||||
|
}, |
||||||
|
"address": { |
||||||
|
"message": "Address" |
||||||
|
}, |
||||||
|
"addToken": { |
||||||
|
"message": "Add Token" |
||||||
|
}, |
||||||
|
"amount": { |
||||||
|
"message": "Amount" |
||||||
|
}, |
||||||
|
"amountPlusGas": { |
||||||
|
"message": "Amount + Gas" |
||||||
|
}, |
||||||
|
"appDescription": { |
||||||
|
"message": "Ethereum Browser Extension", |
||||||
|
"description": "The description of the application" |
||||||
|
}, |
||||||
"appName": { |
"appName": { |
||||||
"message": "MetaMask", |
"message": "MetaMask", |
||||||
"description": "The name of the application" |
"description": "The name of the application" |
||||||
}, |
}, |
||||||
"appDescription": { |
"attemptingConnect": { |
||||||
"message": "Ethereum Identity Management", |
"message": "Attempting to connect to blockchain." |
||||||
"description": "The description of the application" |
}, |
||||||
|
"available": { |
||||||
|
"message": "Available" |
||||||
|
}, |
||||||
|
"back": { |
||||||
|
"message": "Back" |
||||||
|
}, |
||||||
|
"balance": { |
||||||
|
"message": "Balance:" |
||||||
|
}, |
||||||
|
"balanceIsInsufficientGas": { |
||||||
|
"message": "Insufficient balance for current gas total" |
||||||
|
}, |
||||||
|
"beta": { |
||||||
|
"message": "BETA" |
||||||
|
}, |
||||||
|
"betweenMinAndMax": { |
||||||
|
"message": "must be greater than or equal to $1 and less than or equal to $2.", |
||||||
|
"description": "helper for inputting hex as decimal input" |
||||||
|
}, |
||||||
|
"borrowDharma": { |
||||||
|
"message": "Borrow With Dharma (Beta)" |
||||||
|
}, |
||||||
|
"buy": { |
||||||
|
"message": "Buy" |
||||||
|
}, |
||||||
|
"buyCoinbase": { |
||||||
|
"message": "Buy on Coinbase" |
||||||
|
}, |
||||||
|
"buyCoinbaseExplainer": { |
||||||
|
"message": "Coinbase is the world’s most popular way to buy and sell bitcoin, ethereum, and litecoin." |
||||||
|
}, |
||||||
|
"cancel": { |
||||||
|
"message": "Cancel" |
||||||
|
}, |
||||||
|
"clickCopy": { |
||||||
|
"message": "Click to Copy" |
||||||
|
}, |
||||||
|
"confirm": { |
||||||
|
"message": "Confirm" |
||||||
|
}, |
||||||
|
"confirmContract": { |
||||||
|
"message": "Confirm Contract" |
||||||
|
}, |
||||||
|
"confirmPassword": { |
||||||
|
"message": "Confirm Password" |
||||||
|
}, |
||||||
|
"confirmTransaction": { |
||||||
|
"message": "Confirm Transaction" |
||||||
|
}, |
||||||
|
"continueToCoinbase": { |
||||||
|
"message": "Continue to Coinbase" |
||||||
|
}, |
||||||
|
"contractDeployment": { |
||||||
|
"message": "Contract Deployment" |
||||||
|
}, |
||||||
|
"conversionProgress": { |
||||||
|
"message": "Conversion in progress" |
||||||
|
}, |
||||||
|
"copiedButton": { |
||||||
|
"message": "Copied" |
||||||
|
}, |
||||||
|
"copiedClipboard": { |
||||||
|
"message": "Copied to Clipboard" |
||||||
|
}, |
||||||
|
"copiedExclamation": { |
||||||
|
"message": "Copied!" |
||||||
|
}, |
||||||
|
"copy": { |
||||||
|
"message": "Copy" |
||||||
|
}, |
||||||
|
"copyToClipboard": { |
||||||
|
"message": "Copy to clipboard" |
||||||
|
}, |
||||||
|
"copyButton": { |
||||||
|
"message": " Copy " |
||||||
|
}, |
||||||
|
"copyPrivateKey": { |
||||||
|
"message": "This is your private key (click to copy)" |
||||||
|
}, |
||||||
|
"create": { |
||||||
|
"message": "Create" |
||||||
|
}, |
||||||
|
"createAccount": { |
||||||
|
"message": "Create Account" |
||||||
|
}, |
||||||
|
"createDen": { |
||||||
|
"message": "Create" |
||||||
|
}, |
||||||
|
"crypto": { |
||||||
|
"message": "Crypto", |
||||||
|
"description": "Exchange type (cryptocurrencies)" |
||||||
|
}, |
||||||
|
"customGas": { |
||||||
|
"message": "Customize Gas" |
||||||
|
}, |
||||||
|
"customize": { |
||||||
|
"message": "Customize" |
||||||
|
}, |
||||||
|
"customRPC": { |
||||||
|
"message": "Custom RPC" |
||||||
|
}, |
||||||
|
"defaultNetwork": { |
||||||
|
"message": "The default network for Ether transactions is Main Net." |
||||||
|
}, |
||||||
|
"denExplainer": { |
||||||
|
"message": "Your DEN is your password-encrypted storage within MetaMask." |
||||||
|
}, |
||||||
|
"deposit": { |
||||||
|
"message": "Deposit" |
||||||
|
}, |
||||||
|
"depositBTC": { |
||||||
|
"message": "Deposit your BTC to the address below:" |
||||||
|
}, |
||||||
|
"depositCoin": { |
||||||
|
"message": "Deposit your $1 to the address below", |
||||||
|
"description": "Tells the user what coin they have selected to deposit with shapeshift" |
||||||
|
}, |
||||||
|
"depositEth": { |
||||||
|
"message": "Deposit Eth" |
||||||
|
}, |
||||||
|
"depositEther": { |
||||||
|
"message": "Deposit Ether" |
||||||
|
}, |
||||||
|
"depositFiat": { |
||||||
|
"message": "Deposit with Fiat" |
||||||
|
}, |
||||||
|
"depositFromAccount": { |
||||||
|
"message": "Deposit from another account" |
||||||
|
}, |
||||||
|
"depositShapeShift": { |
||||||
|
"message": "Deposit with ShapeShift" |
||||||
|
}, |
||||||
|
"depositShapeShiftExplainer": { |
||||||
|
"message": "If you own other cryptocurrencies, you can trade and deposit Ether directly into your MetaMask wallet. No Account Needed." |
||||||
|
}, |
||||||
|
"details": { |
||||||
|
"message": "Details" |
||||||
|
}, |
||||||
|
"directDeposit": { |
||||||
|
"message": "Direct Deposit" |
||||||
|
}, |
||||||
|
"directDepositEther": { |
||||||
|
"message": "Directly Deposit Ether" |
||||||
|
}, |
||||||
|
"directDepositEtherExplainer": { |
||||||
|
"message": "If you already have some Ether, the quickest way to get Ether in your new wallet by direct deposit." |
||||||
|
}, |
||||||
|
"done": { |
||||||
|
"message": "Done" |
||||||
|
}, |
||||||
|
"edit": { |
||||||
|
"message": "Edit" |
||||||
|
}, |
||||||
|
"editAccountName": { |
||||||
|
"message": "Edit Account Name" |
||||||
|
}, |
||||||
|
"encryptNewDen": { |
||||||
|
"message": "Encrypt your new DEN" |
||||||
|
}, |
||||||
|
"enterPassword": { |
||||||
|
"message": "Enter password" |
||||||
|
}, |
||||||
|
"etherscanView": { |
||||||
|
"message": "View account on Etherscan" |
||||||
|
}, |
||||||
|
"exchangeRate": { |
||||||
|
"message": "Exchange Rate" |
||||||
|
}, |
||||||
|
"exportPrivateKey": { |
||||||
|
"message": "Export Private Key" |
||||||
|
}, |
||||||
|
"exportPrivateKeyWarning": { |
||||||
|
"message": "Export private keys at your own risk." |
||||||
|
}, |
||||||
|
"failed": { |
||||||
|
"message": "Failed" |
||||||
|
}, |
||||||
|
"fiat": { |
||||||
|
"message": "FIAT", |
||||||
|
"description": "Exchange type" |
||||||
|
}, |
||||||
|
"fileImportFail": { |
||||||
|
"message": "File import not working? Click here!", |
||||||
|
"description": "Helps user import their account from a JSON file" |
||||||
|
}, |
||||||
|
"from": { |
||||||
|
"message": "From" |
||||||
|
}, |
||||||
|
"fromShapeShift": { |
||||||
|
"message": "From ShapeShift" |
||||||
|
}, |
||||||
|
"gas": { |
||||||
|
"message": "Gas", |
||||||
|
"description": "Short indication of gas cost" |
||||||
|
}, |
||||||
|
"gasFee": { |
||||||
|
"message": "Gas Fee" |
||||||
|
}, |
||||||
|
"gasLimit": { |
||||||
|
"message": "Gas Limit" |
||||||
|
}, |
||||||
|
"gasLimitCalculation": { |
||||||
|
"message": "We calculate the suggested gas limit based on network success rates." |
||||||
|
}, |
||||||
|
"gasLimitRequired": { |
||||||
|
"message": "Gas Limit Required" |
||||||
|
}, |
||||||
|
"gasLimitTooLow": { |
||||||
|
"message": "Gas limit must be at least 21000" |
||||||
|
}, |
||||||
|
"gasPrice": { |
||||||
|
"message": "Gas Price (GWEI)" |
||||||
|
}, |
||||||
|
"gasPriceCalculation": { |
||||||
|
"message": "We calculate the suggested gas prices based on network success rates." |
||||||
|
}, |
||||||
|
"gasPriceRequired": { |
||||||
|
"message": "Gas Price Required" |
||||||
|
}, |
||||||
|
"getEther": { |
||||||
|
"message": "Get Ether" |
||||||
|
}, |
||||||
|
"getEtherFromFaucet": { |
||||||
|
"message": "Get Ether from a faucet for the $1", |
||||||
|
"description": "Displays network name for Ether faucet" |
||||||
|
}, |
||||||
|
"greaterThanMin": { |
||||||
|
"message": "must be greater than or equal to $1.", |
||||||
|
"description": "helper for inputting hex as decimal input" |
||||||
|
}, |
||||||
|
"here": { |
||||||
|
"message": "here", |
||||||
|
"description": "as in -click here- for more information (goes with troubleTokenBalances)" |
||||||
|
}, |
||||||
|
"hide": { |
||||||
|
"message": "Hide" |
||||||
|
}, |
||||||
|
"hideToken": { |
||||||
|
"message": "Hide Token" |
||||||
|
}, |
||||||
|
"hideTokenPrompt": { |
||||||
|
"message": "Hide Token?" |
||||||
|
}, |
||||||
|
"howToDeposit": { |
||||||
|
"message": "How would you like to deposit Ether?" |
||||||
|
}, |
||||||
|
"import": { |
||||||
|
"message": "Import", |
||||||
|
"description": "Button to import an account from a selected file" |
||||||
|
}, |
||||||
|
"importAccount": { |
||||||
|
"message": "Import Account" |
||||||
|
}, |
||||||
|
"importAnAccount": { |
||||||
|
"message": "Import an account" |
||||||
|
}, |
||||||
|
"importDen": { |
||||||
|
"message": "Import Existing DEN" |
||||||
|
}, |
||||||
|
"imported": { |
||||||
|
"message": "Imported", |
||||||
|
"description": "status showing that an account has been fully loaded into the keyring" |
||||||
|
}, |
||||||
|
"infoHelp": { |
||||||
|
"message": "Info & Help" |
||||||
|
}, |
||||||
|
"invalidAddress": { |
||||||
|
"message": "Invalid address" |
||||||
|
}, |
||||||
|
"invalidGasParams": { |
||||||
|
"message": "Invalid Gas Parameters" |
||||||
|
}, |
||||||
|
"invalidInput": { |
||||||
|
"message": "Invalid input." |
||||||
|
}, |
||||||
|
"invalidRequest": { |
||||||
|
"message": "Invalid Request" |
||||||
|
}, |
||||||
|
"jsonFile": { |
||||||
|
"message": "JSON File", |
||||||
|
"description": "format for importing an account" |
||||||
|
}, |
||||||
|
"kovan": { |
||||||
|
"message": "Kovan Test Network" |
||||||
|
}, |
||||||
|
"lessThanMax": { |
||||||
|
"message": "must be less than or equal to $1.", |
||||||
|
"description": "helper for inputting hex as decimal input" |
||||||
|
}, |
||||||
|
"limit": { |
||||||
|
"message": "Limit" |
||||||
|
}, |
||||||
|
"loading": { |
||||||
|
"message": "Loading..." |
||||||
|
}, |
||||||
|
"loadingTokens": { |
||||||
|
"message": "Loading Tokens..." |
||||||
|
}, |
||||||
|
"localhost": { |
||||||
|
"message": "Localhost 8545" |
||||||
|
}, |
||||||
|
"logout": { |
||||||
|
"message": "Log out" |
||||||
|
}, |
||||||
|
"loose": { |
||||||
|
"message": "Loose" |
||||||
|
}, |
||||||
|
"mainnet": { |
||||||
|
"message": "Main Ethereum Network" |
||||||
|
}, |
||||||
|
"message": { |
||||||
|
"message": "Message" |
||||||
|
}, |
||||||
|
"min": { |
||||||
|
"message": "Minimum" |
||||||
|
}, |
||||||
|
"myAccounts": { |
||||||
|
"message": "My Accounts" |
||||||
|
}, |
||||||
|
"needEtherInWallet": { |
||||||
|
"message": "To interact with decentralized applications using MetaMask, you’ll need Ether in your wallet." |
||||||
|
}, |
||||||
|
"needImportFile": { |
||||||
|
"message": "You must select a file to import.", |
||||||
|
"description": "User is important an account and needs to add a file to continue" |
||||||
|
}, |
||||||
|
"needImportPassword": { |
||||||
|
"message": "You must enter a password for the selected file.", |
||||||
|
"description": "Password and file needed to import an account" |
||||||
|
}, |
||||||
|
"networks": { |
||||||
|
"message": "Networks" |
||||||
|
}, |
||||||
|
"newAccount": { |
||||||
|
"message": "New Account" |
||||||
|
}, |
||||||
|
"newAccountNumberName": { |
||||||
|
"message": "Account $1", |
||||||
|
"description": "Default name of next account to be created on create account screen" |
||||||
|
}, |
||||||
|
"newContract": { |
||||||
|
"message": "New Contract" |
||||||
|
}, |
||||||
|
"newPassword": { |
||||||
|
"message": "New Password (min 8 chars)" |
||||||
|
}, |
||||||
|
"newRecipient": { |
||||||
|
"message": "New Recipient" |
||||||
|
}, |
||||||
|
"next": { |
||||||
|
"message": "Next" |
||||||
|
}, |
||||||
|
"noAddressForName": { |
||||||
|
"message": "No address has been set for this name." |
||||||
|
}, |
||||||
|
"noDeposits": { |
||||||
|
"message": "No deposits received" |
||||||
|
}, |
||||||
|
"noTransactionHistory": { |
||||||
|
"message": "No transaction history." |
||||||
|
}, |
||||||
|
"noTransactions": { |
||||||
|
"message": "No Transactions" |
||||||
|
}, |
||||||
|
"notStarted": { |
||||||
|
"message": "Not Started" |
||||||
|
}, |
||||||
|
"oldUI": { |
||||||
|
"message": "Old UI" |
||||||
|
}, |
||||||
|
"oldUIMessage": { |
||||||
|
"message": "You have returned to the old UI. You can switch back to the New UI through the option in the top right dropdown menu." |
||||||
|
}, |
||||||
|
"or": { |
||||||
|
"message": "or", |
||||||
|
"description": "choice between creating or importing a new account" |
||||||
|
}, |
||||||
|
"passwordMismatch": { |
||||||
|
"message": "passwords don't match", |
||||||
|
"description": "in password creation process, the two new password fields did not match" |
||||||
|
}, |
||||||
|
"passwordShort": { |
||||||
|
"message": "password not long enough", |
||||||
|
"description": "in password creation process, the password is not long enough to be secure" |
||||||
|
}, |
||||||
|
"pastePrivateKey": { |
||||||
|
"message": "Paste your private key string here:", |
||||||
|
"description": "For importing an account from a private key" |
||||||
|
}, |
||||||
|
"pasteSeed": { |
||||||
|
"message": "Paste your seed phrase here!" |
||||||
|
}, |
||||||
|
"pleaseReviewTransaction": { |
||||||
|
"message": "Please review your transaction." |
||||||
|
}, |
||||||
|
"privateKey": { |
||||||
|
"message": "Private Key", |
||||||
|
"description": "select this type of file to use to import an account" |
||||||
|
}, |
||||||
|
"privateKeyWarning": { |
||||||
|
"message": "Warning: Never disclose this key. Anyone with your private keys can take steal any assets held in your account." |
||||||
|
}, |
||||||
|
"privateNetwork": { |
||||||
|
"message": "Private Network" |
||||||
|
}, |
||||||
|
"qrCode": { |
||||||
|
"message": "Show QR Code" |
||||||
|
}, |
||||||
|
"readdToken": { |
||||||
|
"message": "You can add this token back in the future by going go to “Add token” in your accounts options menu." |
||||||
|
}, |
||||||
|
"readMore": { |
||||||
|
"message": "Read more here." |
||||||
|
}, |
||||||
|
"receive": { |
||||||
|
"message": "Receive" |
||||||
|
}, |
||||||
|
"recipientAddress": { |
||||||
|
"message": "Recipient Address" |
||||||
|
}, |
||||||
|
"refundAddress": { |
||||||
|
"message": "Your Refund Address" |
||||||
|
}, |
||||||
|
"rejected": { |
||||||
|
"message": "Rejected" |
||||||
|
}, |
||||||
|
"required": { |
||||||
|
"message": "Required" |
||||||
|
}, |
||||||
|
"retryWithMoreGas": { |
||||||
|
"message": "Retry with a higher gas price here" |
||||||
|
}, |
||||||
|
"revert": { |
||||||
|
"message": "Revert" |
||||||
|
}, |
||||||
|
"rinkeby": { |
||||||
|
"message": "Rinkeby Test Network" |
||||||
|
}, |
||||||
|
"ropsten": { |
||||||
|
"message": "Ropsten Test Network" |
||||||
|
}, |
||||||
|
"sampleAccountName": { |
||||||
|
"message": "E.g. My new account", |
||||||
|
"description": "Help user understand concept of adding a human-readable name to their account" |
||||||
|
}, |
||||||
|
"save": { |
||||||
|
"message": "Save" |
||||||
|
}, |
||||||
|
"saveAsFile": { |
||||||
|
"message": "Save as File", |
||||||
|
"description": "Account export process" |
||||||
|
}, |
||||||
|
"selectService": { |
||||||
|
"message": "Select Service" |
||||||
|
}, |
||||||
|
"send": { |
||||||
|
"message": "Send" |
||||||
|
}, |
||||||
|
"sendTokens": { |
||||||
|
"message": "Send Tokens" |
||||||
|
}, |
||||||
|
"sendTokensAnywhere": { |
||||||
|
"message": "Send Tokens to anyone with an Ethereum account" |
||||||
|
}, |
||||||
|
"settings": { |
||||||
|
"message": "Settings" |
||||||
|
}, |
||||||
|
"shapeshiftBuy": { |
||||||
|
"message": "Buy with Shapeshift" |
||||||
|
}, |
||||||
|
"showPrivateKeys": { |
||||||
|
"message": "Show Private Keys" |
||||||
|
}, |
||||||
|
"showQRCode": { |
||||||
|
"message": "Show QR Code" |
||||||
|
}, |
||||||
|
"sign": { |
||||||
|
"message": "Sign" |
||||||
|
}, |
||||||
|
"signMessage": { |
||||||
|
"message": "Sign Message" |
||||||
|
}, |
||||||
|
"signNotice": { |
||||||
|
"message": "Signing this message can have \ndangerous side effects. Only sign messages from \nsites you fully trust with your entire account.\n This dangerous method will be removed in a future version. " |
||||||
|
}, |
||||||
|
"sigRequest": { |
||||||
|
"message": "Signature Request" |
||||||
|
}, |
||||||
|
"sigRequested": { |
||||||
|
"message": "Signature Requested" |
||||||
|
}, |
||||||
|
"status": { |
||||||
|
"message": "Status" |
||||||
|
}, |
||||||
|
"submit": { |
||||||
|
"message": "Submit" |
||||||
|
}, |
||||||
|
"takesTooLong": { |
||||||
|
"message": "Taking too long?" |
||||||
|
}, |
||||||
|
"testFaucet": { |
||||||
|
"message": "Test Faucet" |
||||||
|
}, |
||||||
|
"to": { |
||||||
|
"message": "To" |
||||||
|
}, |
||||||
|
"toETHviaShapeShift": { |
||||||
|
"message": "$1 to ETH via ShapeShift", |
||||||
|
"description": "system will fill in deposit type in start of message" |
||||||
|
}, |
||||||
|
"tokenBalance": { |
||||||
|
"message": "Your Token Balance is:" |
||||||
|
}, |
||||||
|
"total": { |
||||||
|
"message": "Total" |
||||||
|
}, |
||||||
|
"transactionMemo": { |
||||||
|
"message": "Transaction memo (optional)" |
||||||
|
}, |
||||||
|
"transactionNumber": { |
||||||
|
"message": "Transaction Number" |
||||||
|
}, |
||||||
|
"transfers": { |
||||||
|
"message": "Transfers" |
||||||
|
}, |
||||||
|
"troubleTokenBalances": { |
||||||
|
"message": "We had trouble loading your token balances. You can view them ", |
||||||
|
"description": "Followed by a link (here) to view token balances" |
||||||
|
}, |
||||||
|
"typePassword": { |
||||||
|
"message": "Type Your Password" |
||||||
|
}, |
||||||
|
"uiWelcome": { |
||||||
|
"message": "Welcome to the New UI (Beta)" |
||||||
|
}, |
||||||
|
"uiWelcomeMessage": { |
||||||
|
"message": "You are now using the new Metamask UI. Take a look around, try out new features like sending tokens, and let us know if you have any issues." |
||||||
|
}, |
||||||
|
"unavailable": { |
||||||
|
"message": "Unavailable" |
||||||
|
}, |
||||||
|
"unknown": { |
||||||
|
"message": "Unknown" |
||||||
|
}, |
||||||
|
"unknownNetwork": { |
||||||
|
"message": "Unknown Private Network" |
||||||
|
}, |
||||||
|
"unknownNetworkId": { |
||||||
|
"message": "Unknown network ID" |
||||||
|
}, |
||||||
|
"usaOnly": { |
||||||
|
"message": "USA only", |
||||||
|
"description": "Using this exchange is limited to people inside the USA" |
||||||
|
}, |
||||||
|
"usedByClients": { |
||||||
|
"message": "Used by a variety of different clients" |
||||||
|
}, |
||||||
|
"viewAccount": { |
||||||
|
"message": "View Account" |
||||||
|
}, |
||||||
|
"warning": { |
||||||
|
"message": "Warning" |
||||||
|
}, |
||||||
|
"whatsThis": { |
||||||
|
"message": "What's this?" |
||||||
|
}, |
||||||
|
"yourSigRequested": { |
||||||
|
"message": "Your signature is being requested" |
||||||
|
}, |
||||||
|
"youSign": { |
||||||
|
"message": "You are signing" |
||||||
} |
} |
||||||
} |
} |
||||||
|
@ -0,0 +1,38 @@ |
|||||||
|
// We should not rely on local storage in an extension!
|
||||||
|
// We should use this instead!
|
||||||
|
// https://developer.mozilla.org/en-US/Add-ons/WebExtensions/API/storage/local
|
||||||
|
|
||||||
|
const extension = require('extensionizer') |
||||||
|
const { promisify } = require('util').promisify |
||||||
|
|
||||||
|
module.exports = class ExtensionStore { |
||||||
|
constructor() { |
||||||
|
this.isSupported = !!(extension.storage.local) |
||||||
|
if (!this.isSupported) { |
||||||
|
log.error('Storage local API not available.') |
||||||
|
} |
||||||
|
const local = extension.storage.local |
||||||
|
this._get = promisify(local.get).bind(local) |
||||||
|
this._set = promisify(local.set).bind(local) |
||||||
|
} |
||||||
|
|
||||||
|
async get() { |
||||||
|
if (!this.isSupported) return undefined |
||||||
|
const result = await this._get() |
||||||
|
// extension.storage.local always returns an obj
|
||||||
|
// if the object is empty, treat it as undefined
|
||||||
|
if (isEmpty(result)) { |
||||||
|
return undefined |
||||||
|
} else { |
||||||
|
return result |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
async set(state) { |
||||||
|
return this._set(state) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
function isEmpty(obj) { |
||||||
|
return Object.keys(obj).length === 0 |
||||||
|
} |
@ -1,17 +0,0 @@ |
|||||||
machine: |
|
||||||
node: |
|
||||||
version: 8.1.4 |
|
||||||
test: |
|
||||||
override: |
|
||||||
- "npm test" |
|
||||||
dependencies: |
|
||||||
pre: |
|
||||||
- sudo apt-get update |
|
||||||
# get latest stable firefox |
|
||||||
- sudo apt-get install firefox |
|
||||||
- firefox_cmd=`which firefox`; sudo rm -f $firefox_cmd; sudo ln -s `which firefox.ubuntu` $firefox_cmd |
|
||||||
# get latest stable chrome |
|
||||||
- wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | sudo apt-key add - |
|
||||||
- sudo sh -c 'echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google.list' |
|
||||||
- sudo apt-get update |
|
||||||
- sudo apt-get install google-chrome-stable |
|
@ -1,18 +1,21 @@ |
|||||||
const fs = require('fs') |
const fs = require('fs') |
||||||
const path = require('path') |
const path = require('path') |
||||||
|
const { promisify } = require('util') |
||||||
|
|
||||||
const statesPath = path.join(__dirname, 'states') |
start().catch(console.error) |
||||||
const stateNames = fs.readdirSync(statesPath) |
|
||||||
|
|
||||||
const states = stateNames.reduce((result, stateFileName) => { |
async function start () { |
||||||
const statePath = path.join(__dirname, 'states', stateFileName) |
const statesPath = path.join(__dirname, 'states') |
||||||
const stateFile = fs.readFileSync(statePath).toString() |
const stateFilesNames = await promisify(fs.readdir)(statesPath) |
||||||
const state = JSON.parse(stateFile) |
const states = {} |
||||||
result[stateFileName.split('.')[0].replace(/-/g, ' ', 'g')] = state |
await Promise.all(stateFilesNames.map(async (stateFileName) => { |
||||||
return result |
const stateFilePath = path.join(__dirname, 'states', stateFileName) |
||||||
}, {}) |
const stateFileContent = await promisify(fs.readFile)(stateFilePath, 'utf8') |
||||||
|
const state = JSON.parse(stateFileContent) |
||||||
const result = `module.exports = ${JSON.stringify(states)}` |
const stateName = stateFileName.split('.')[0].replace(/-/g, ' ', 'g') |
||||||
|
states[stateName] = state |
||||||
const statesJsonPath = path.join(__dirname, 'states.js') |
})) |
||||||
fs.writeFileSync(statesJsonPath, result) |
const generatedFileContent = `module.exports = ${JSON.stringify(states)}` |
||||||
|
const generatedFilePath = path.join(__dirname, 'states.js') |
||||||
|
await promisify(fs.writeFile)(generatedFilePath, generatedFileContent) |
||||||
|
} |
||||||
|
@ -0,0 +1,18 @@ |
|||||||
|
# MetaMask Translation Guide |
||||||
|
|
||||||
|
The MetaMask browser extension supports new translations added in the form of new locales files added in `app/_locales`. |
||||||
|
|
||||||
|
- [The MDN Guide to Internationalizing Extensions](https://developer.mozilla.org/en-US/Add-ons/WebExtensions/Internationalization) |
||||||
|
|
||||||
|
## Adding a new Language |
||||||
|
|
||||||
|
Each supported language is represented by a folder in `app/_locales` whose name is that language's subtag ([look up a language subtag using this tool](https://r12a.github.io/app-subtags/)). |
||||||
|
|
||||||
|
Inside that folder there should be a `messages.json` file that follows the specified format. An easy way to start your translation is to first duplicate `app/_locales/en/messages.json` (the english translation), and then update the `message` key for each in-app message. |
||||||
|
|
||||||
|
That's it! When MetaMask is loaded on a computer with that language set as the system language, they will see your translation instead of the default one. |
||||||
|
|
||||||
|
## Testing |
||||||
|
|
||||||
|
To verify that your translation works, you will need to [build a local copy](https://github.com/MetaMask/metamask-extension#building-locally) of MetaMask. |
||||||
|
|
@ -1,100 +1,122 @@ |
|||||||
const inherits = require('util').inherits |
|
||||||
const Component = require('react').Component |
const Component = require('react').Component |
||||||
const h = require('react-hyperscript') |
const h = require('react-hyperscript') |
||||||
const connect = require('react-redux').connect |
const connect = require('react-redux').connect |
||||||
const actions = require('../../../../ui/app/actions') |
const actions = require('../../../../ui/app/actions') |
||||||
const FileInput = require('react-simple-file-input').default |
const FileInput = require('react-simple-file-input').default |
||||||
|
const PropTypes = require('prop-types') |
||||||
|
|
||||||
const HELP_LINK = 'https://github.com/MetaMask/faq/blob/master/README.md#q-i-cant-use-the-import-feature-for-uploading-a-json-file-the-window-keeps-closing-when-i-try-to-select-a-file' |
const HELP_LINK = 'https://github.com/MetaMask/faq/blob/master/README.md#q-i-cant-use-the-import-feature-for-uploading-a-json-file-the-window-keeps-closing-when-i-try-to-select-a-file' |
||||||
|
|
||||||
module.exports = connect(mapStateToProps)(JsonImportSubview) |
class JsonImportSubview extends Component { |
||||||
|
constructor (props) { |
||||||
|
super(props) |
||||||
|
|
||||||
function mapStateToProps (state) { |
this.state = { |
||||||
return { |
file: null, |
||||||
error: state.appState.warning, |
fileContents: '', |
||||||
|
} |
||||||
} |
} |
||||||
} |
|
||||||
|
|
||||||
inherits(JsonImportSubview, Component) |
render () { |
||||||
function JsonImportSubview () { |
const { error } = this.props |
||||||
Component.call(this) |
|
||||||
} |
|
||||||
|
|
||||||
JsonImportSubview.prototype.render = function () { |
return ( |
||||||
const { error } = this.props |
h('div', { |
||||||
|
|
||||||
return ( |
|
||||||
h('div', { |
|
||||||
style: { |
|
||||||
display: 'flex', |
|
||||||
flexDirection: 'column', |
|
||||||
alignItems: 'center', |
|
||||||
padding: '5px 15px 0px 15px', |
|
||||||
}, |
|
||||||
}, [ |
|
||||||
|
|
||||||
h('p', 'Used by a variety of different clients'), |
|
||||||
h('a.warning', { href: HELP_LINK, target: '_blank' }, 'File import not working? Click here!'), |
|
||||||
|
|
||||||
h(FileInput, { |
|
||||||
readAs: 'text', |
|
||||||
onLoad: this.onLoad.bind(this), |
|
||||||
style: { |
style: { |
||||||
margin: '20px 0px 12px 20px', |
display: 'flex', |
||||||
fontSize: '15px', |
flexDirection: 'column', |
||||||
|
alignItems: 'center', |
||||||
|
padding: '5px 15px 0px 15px', |
||||||
}, |
}, |
||||||
}), |
}, [ |
||||||
|
|
||||||
|
h('p', 'Used by a variety of different clients'), |
||||||
|
h('a.warning', { |
||||||
|
href: HELP_LINK, |
||||||
|
target: '_blank', |
||||||
|
}, 'File import not working? Click here!'), |
||||||
|
|
||||||
|
h(FileInput, { |
||||||
|
readAs: 'text', |
||||||
|
onLoad: this.onLoad.bind(this), |
||||||
|
style: { |
||||||
|
margin: '20px 0px 12px 20px', |
||||||
|
fontSize: '15px', |
||||||
|
}, |
||||||
|
}), |
||||||
|
|
||||||
|
h('input.large-input.letter-spacey', { |
||||||
|
type: 'password', |
||||||
|
placeholder: 'Enter password', |
||||||
|
id: 'json-password-box', |
||||||
|
onKeyPress: this.createKeyringOnEnter.bind(this), |
||||||
|
style: { |
||||||
|
width: 260, |
||||||
|
marginTop: 12, |
||||||
|
}, |
||||||
|
}), |
||||||
|
|
||||||
|
h('button.primary', { |
||||||
|
onClick: this.createNewKeychain.bind(this), |
||||||
|
style: { |
||||||
|
margin: 12, |
||||||
|
}, |
||||||
|
}, 'Import'), |
||||||
|
|
||||||
|
error ? h('span.error', error) : null, |
||||||
|
]) |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
h('input.large-input.letter-spacey', { |
onLoad (event, file) { |
||||||
type: 'password', |
this.setState({file: file, fileContents: event.target.result}) |
||||||
placeholder: 'Enter password', |
} |
||||||
id: 'json-password-box', |
|
||||||
onKeyPress: this.createKeyringOnEnter.bind(this), |
|
||||||
style: { |
|
||||||
width: 260, |
|
||||||
marginTop: 12, |
|
||||||
}, |
|
||||||
}), |
|
||||||
|
|
||||||
h('button.primary', { |
createKeyringOnEnter (event) { |
||||||
onClick: this.createNewKeychain.bind(this), |
if (event.key === 'Enter') { |
||||||
style: { |
event.preventDefault() |
||||||
margin: 12, |
this.createNewKeychain() |
||||||
}, |
} |
||||||
}, 'Import'), |
} |
||||||
|
|
||||||
error ? h('span.error', error) : null, |
createNewKeychain () { |
||||||
]) |
const { fileContents } = this.state |
||||||
) |
|
||||||
} |
|
||||||
|
|
||||||
JsonImportSubview.prototype.onLoad = function (event, file) { |
if (!fileContents) { |
||||||
this.setState({file: file, fileContents: event.target.result}) |
const message = 'You must select a file to import.' |
||||||
} |
return this.props.displayWarning(message) |
||||||
|
} |
||||||
|
|
||||||
JsonImportSubview.prototype.createKeyringOnEnter = function (event) { |
const passwordInput = document.getElementById('json-password-box') |
||||||
if (event.key === 'Enter') { |
const password = passwordInput.value |
||||||
event.preventDefault() |
|
||||||
this.createNewKeychain() |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
JsonImportSubview.prototype.createNewKeychain = function () { |
if (!password) { |
||||||
const state = this.state |
const message = 'You must enter a password for the selected file.' |
||||||
const { fileContents } = state |
return this.props.displayWarning(message) |
||||||
|
} |
||||||
|
|
||||||
if (!fileContents) { |
this.props.importNewAccount([ fileContents, password ]) |
||||||
const message = 'You must select a file to import.' |
|
||||||
return this.props.dispatch(actions.displayWarning(message)) |
|
||||||
} |
} |
||||||
|
} |
||||||
|
|
||||||
const passwordInput = document.getElementById('json-password-box') |
JsonImportSubview.propTypes = { |
||||||
const password = passwordInput.value |
error: PropTypes.string, |
||||||
|
displayWarning: PropTypes.func, |
||||||
|
importNewAccount: PropTypes.func, |
||||||
|
} |
||||||
|
|
||||||
if (!password) { |
const mapStateToProps = state => { |
||||||
const message = 'You must enter a password for the selected file.' |
return { |
||||||
return this.props.dispatch(actions.displayWarning(message)) |
error: state.appState.warning, |
||||||
} |
} |
||||||
|
} |
||||||
|
|
||||||
this.props.dispatch(actions.importNewAccount('JSON File', [ fileContents, password ])) |
const mapDispatchToProps = dispatch => { |
||||||
|
return { |
||||||
|
goHome: () => dispatch(actions.goHome()), |
||||||
|
displayWarning: warning => dispatch(actions.displayWarning(warning)), |
||||||
|
importNewAccount: options => dispatch(actions.importNewAccount('JSON File', options)), |
||||||
|
} |
||||||
} |
} |
||||||
|
|
||||||
|
module.exports = connect(mapStateToProps, mapDispatchToProps)(JsonImportSubview) |
||||||
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,53 @@ |
|||||||
|
module.exports = { |
||||||
|
timeout, |
||||||
|
queryAsync, |
||||||
|
findAsync, |
||||||
|
pollUntilTruthy, |
||||||
|
} |
||||||
|
|
||||||
|
function timeout (time) { |
||||||
|
return new Promise((resolve, reject) => { |
||||||
|
setTimeout(resolve, time || 1500) |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
async function findAsync(container, selector, opts) { |
||||||
|
try { |
||||||
|
return await pollUntilTruthy(() => { |
||||||
|
const result = container.find(selector) |
||||||
|
if (result.length > 0) return result |
||||||
|
}, opts) |
||||||
|
} catch (err) { |
||||||
|
throw new Error(`Failed to find element within interval: "${selector}"`) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
async function queryAsync(jQuery, selector, opts) { |
||||||
|
try { |
||||||
|
return await pollUntilTruthy(() => { |
||||||
|
const result = jQuery(selector) |
||||||
|
if (result.length > 0) return result |
||||||
|
}, opts) |
||||||
|
} catch (err) { |
||||||
|
throw new Error(`Failed to find element within interval: "${selector}"`) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
async function pollUntilTruthy(fn, opts = {}){ |
||||||
|
const pollingInterval = opts.pollingInterval || 100 |
||||||
|
const timeoutInterval = opts.timeoutInterval || 5000 |
||||||
|
const start = Date.now() |
||||||
|
let result |
||||||
|
while (!result) { |
||||||
|
// check if timedout
|
||||||
|
const now = Date.now() |
||||||
|
if ((now - start) > timeoutInterval) { |
||||||
|
throw new Error(`pollUntilTruthy - failed to return truthy within interval`) |
||||||
|
} |
||||||
|
// check for result
|
||||||
|
result = fn() |
||||||
|
// run again after timeout
|
||||||
|
await timeout(pollingInterval, timeoutInterval) |
||||||
|
} |
||||||
|
return result |
||||||
|
} |
@ -1,106 +1,131 @@ |
|||||||
const inherits = require('util').inherits |
|
||||||
const Component = require('react').Component |
const Component = require('react').Component |
||||||
|
const PropTypes = require('prop-types') |
||||||
const h = require('react-hyperscript') |
const h = require('react-hyperscript') |
||||||
const connect = require('react-redux').connect |
const connect = require('react-redux').connect |
||||||
const actions = require('../../actions') |
const actions = require('../../actions') |
||||||
const FileInput = require('react-simple-file-input').default |
const FileInput = require('react-simple-file-input').default |
||||||
|
const t = require('../../../i18n') |
||||||
|
|
||||||
const HELP_LINK = 'https://support.metamask.io/kb/article/7-importing-accounts' |
const HELP_LINK = 'https://support.metamask.io/kb/article/7-importing-accounts' |
||||||
|
|
||||||
module.exports = connect(mapStateToProps)(JsonImportSubview) |
class JsonImportSubview extends Component { |
||||||
|
constructor (props) { |
||||||
|
super(props) |
||||||
|
|
||||||
function mapStateToProps (state) { |
this.state = { |
||||||
return { |
file: null, |
||||||
error: state.appState.warning, |
fileContents: '', |
||||||
|
} |
||||||
} |
} |
||||||
} |
|
||||||
|
|
||||||
inherits(JsonImportSubview, Component) |
render () { |
||||||
function JsonImportSubview () { |
const { error } = this.props |
||||||
Component.call(this) |
|
||||||
} |
return ( |
||||||
|
h('div.new-account-import-form__json', [ |
||||||
|
|
||||||
|
h('p', t('usedByClients')), |
||||||
|
h('a.warning', { |
||||||
|
href: HELP_LINK, |
||||||
|
target: '_blank', |
||||||
|
}, t('fileImportFail')), |
||||||
|
|
||||||
|
h(FileInput, { |
||||||
|
readAs: 'text', |
||||||
|
onLoad: this.onLoad.bind(this), |
||||||
|
style: { |
||||||
|
margin: '20px 0px 12px 34%', |
||||||
|
fontSize: '15px', |
||||||
|
display: 'flex', |
||||||
|
justifyContent: 'center', |
||||||
|
}, |
||||||
|
}), |
||||||
|
|
||||||
|
h('input.new-account-import-form__input-password', { |
||||||
|
type: 'password', |
||||||
|
placeholder: t('enterPassword'), |
||||||
|
id: 'json-password-box', |
||||||
|
onKeyPress: this.createKeyringOnEnter.bind(this), |
||||||
|
}), |
||||||
|
|
||||||
|
h('div.new-account-create-form__buttons', {}, [ |
||||||
|
|
||||||
|
h('button.new-account-create-form__button-cancel', { |
||||||
|
onClick: () => this.props.goHome(), |
||||||
|
}, [ |
||||||
|
t('cancel'), |
||||||
|
]), |
||||||
|
|
||||||
|
h('button.new-account-create-form__button-create', { |
||||||
|
onClick: () => this.createNewKeychain(), |
||||||
|
}, [ |
||||||
|
t('import'), |
||||||
|
]), |
||||||
|
|
||||||
JsonImportSubview.prototype.render = function () { |
|
||||||
const { error } = this.props |
|
||||||
|
|
||||||
return ( |
|
||||||
h('div.new-account-import-form__json', [ |
|
||||||
|
|
||||||
h('p', 'Used by a variety of different clients'), |
|
||||||
h('a.warning', { href: HELP_LINK, target: '_blank' }, 'File import not working? Click here!'), |
|
||||||
|
|
||||||
h(FileInput, { |
|
||||||
readAs: 'text', |
|
||||||
onLoad: this.onLoad.bind(this), |
|
||||||
style: { |
|
||||||
margin: '20px 0px 12px 34%', |
|
||||||
fontSize: '15px', |
|
||||||
display: 'flex', |
|
||||||
justifyContent: 'center', |
|
||||||
}, |
|
||||||
}), |
|
||||||
|
|
||||||
h('input.new-account-import-form__input-password', { |
|
||||||
type: 'password', |
|
||||||
placeholder: 'Enter password', |
|
||||||
id: 'json-password-box', |
|
||||||
onKeyPress: this.createKeyringOnEnter.bind(this), |
|
||||||
}), |
|
||||||
|
|
||||||
h('div.new-account-create-form__buttons', {}, [ |
|
||||||
|
|
||||||
h('button.new-account-create-form__button-cancel', { |
|
||||||
onClick: () => this.props.goHome(), |
|
||||||
}, [ |
|
||||||
'CANCEL', |
|
||||||
]), |
]), |
||||||
|
|
||||||
h('button.new-account-create-form__button-create', { |
error ? h('span.error', error) : null, |
||||||
onClick: () => this.createNewKeychain.bind(this), |
]) |
||||||
}, [ |
) |
||||||
'IMPORT', |
} |
||||||
]), |
|
||||||
|
|
||||||
]), |
onLoad (event, file) { |
||||||
|
this.setState({file: file, fileContents: event.target.result}) |
||||||
|
} |
||||||
|
|
||||||
error ? h('span.error', error) : null, |
createKeyringOnEnter (event) { |
||||||
]) |
if (event.key === 'Enter') { |
||||||
) |
event.preventDefault() |
||||||
} |
this.createNewKeychain() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
JsonImportSubview.prototype.onLoad = function (event, file) { |
createNewKeychain () { |
||||||
this.setState({file: file, fileContents: event.target.result}) |
const state = this.state |
||||||
} |
|
||||||
|
|
||||||
JsonImportSubview.prototype.createKeyringOnEnter = function (event) { |
if (!state) { |
||||||
if (event.key === 'Enter') { |
const message = 'You must select a valid file to import.' |
||||||
event.preventDefault() |
return this.props.displayWarning(message) |
||||||
this.createNewKeychain() |
} |
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
JsonImportSubview.prototype.createNewKeychain = function () { |
const { fileContents } = state |
||||||
const state = this.state |
|
||||||
|
|
||||||
if (!state) { |
if (!fileContents) { |
||||||
const message = 'You must select a valid file to import.' |
const message = t('needImportFile') |
||||||
return this.props.dispatch(actions.displayWarning(message)) |
return this.props.displayWarning(message) |
||||||
} |
} |
||||||
|
|
||||||
const { fileContents } = state |
const passwordInput = document.getElementById('json-password-box') |
||||||
|
const password = passwordInput.value |
||||||
|
|
||||||
if (!fileContents) { |
if (!password) { |
||||||
const message = 'You must select a file to import.' |
const message = t('needImportPassword') |
||||||
return this.props.dispatch(actions.displayWarning(message)) |
return this.props.displayWarning(message) |
||||||
|
} |
||||||
|
|
||||||
|
this.props.importNewJsonAccount([ fileContents, password ]) |
||||||
} |
} |
||||||
|
} |
||||||
|
|
||||||
const passwordInput = document.getElementById('json-password-box') |
JsonImportSubview.propTypes = { |
||||||
const password = passwordInput.value |
error: PropTypes.string, |
||||||
|
goHome: PropTypes.func, |
||||||
|
displayWarning: PropTypes.func, |
||||||
|
importNewJsonAccount: PropTypes.func, |
||||||
|
} |
||||||
|
|
||||||
if (!password) { |
const mapStateToProps = state => { |
||||||
const message = 'You must enter a password for the selected file.' |
return { |
||||||
return this.props.dispatch(actions.displayWarning(message)) |
error: state.appState.warning, |
||||||
} |
} |
||||||
|
} |
||||||
|
|
||||||
this.props.dispatch(actions.importNewAccount('JSON File', [ fileContents, password ])) |
const mapDispatchToProps = dispatch => { |
||||||
|
return { |
||||||
|
goHome: () => dispatch(actions.goHome()), |
||||||
|
displayWarning: warning => dispatch(actions.displayWarning(warning)), |
||||||
|
importNewJsonAccount: options => dispatch(actions.importNewAccount('JSON File', options)), |
||||||
|
} |
||||||
} |
} |
||||||
|
|
||||||
|
module.exports = connect(mapStateToProps, mapDispatchToProps)(JsonImportSubview) |
||||||
|
@ -0,0 +1,59 @@ |
|||||||
|
.welcome-screen { |
||||||
|
display: flex; |
||||||
|
flex-flow: column; |
||||||
|
justify-content: center; |
||||||
|
align-items: center; |
||||||
|
font-family: Roboto; |
||||||
|
font-weight: 400; |
||||||
|
width: 100%; |
||||||
|
flex: 1 0 auto; |
||||||
|
padding: 70px 0; |
||||||
|
background: $white; |
||||||
|
|
||||||
|
@media screen and (max-width: 575px) { |
||||||
|
padding: 0; |
||||||
|
} |
||||||
|
|
||||||
|
&__info { |
||||||
|
display: flex; |
||||||
|
flex-flow: column; |
||||||
|
width: 100%; |
||||||
|
height: 100%; |
||||||
|
align-items: center; |
||||||
|
|
||||||
|
&__header { |
||||||
|
font-size: 1.65em; |
||||||
|
margin-bottom: 14px; |
||||||
|
|
||||||
|
@media screen and (max-width: 575px) { |
||||||
|
font-size: 1.5em; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
&__copy { |
||||||
|
font-size: 1em; |
||||||
|
width: 400px; |
||||||
|
max-width: 90vw; |
||||||
|
text-align: center; |
||||||
|
|
||||||
|
@media screen and (max-width: 575px) { |
||||||
|
font-size: 0.9em; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
&__button { |
||||||
|
height: 54px; |
||||||
|
width: 198px; |
||||||
|
box-shadow: 0 2px 4px 0 rgba(0,0,0,0.14); |
||||||
|
color: #FFFFFF; |
||||||
|
font-size: 20px; |
||||||
|
font-weight: 500; |
||||||
|
line-height: 26px; |
||||||
|
text-align: center; |
||||||
|
text-transform: uppercase; |
||||||
|
margin: 35px 0 14px; |
||||||
|
transition: 200ms ease-in-out; |
||||||
|
background-color: rgba(247, 134, 28, 0.9); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,56 @@ |
|||||||
|
import EventEmitter from 'events' |
||||||
|
import h from 'react-hyperscript' |
||||||
|
import { Component } from 'react' |
||||||
|
import PropTypes from 'prop-types' |
||||||
|
import {connect} from 'react-redux' |
||||||
|
import {closeWelcomeScreen} from './actions' |
||||||
|
import Mascot from './components/mascot' |
||||||
|
|
||||||
|
class WelcomeScreen extends Component { |
||||||
|
static propTypes = { |
||||||
|
closeWelcomeScreen: PropTypes.func.isRequired, |
||||||
|
} |
||||||
|
|
||||||
|
constructor(props) { |
||||||
|
super(props) |
||||||
|
this.animationEventEmitter = new EventEmitter() |
||||||
|
} |
||||||
|
|
||||||
|
initiateAccountCreation = () => { |
||||||
|
this.props.closeWelcomeScreen() |
||||||
|
} |
||||||
|
|
||||||
|
render () { |
||||||
|
return h('div.welcome-screen', [ |
||||||
|
|
||||||
|
h('div.welcome-screen__info', [ |
||||||
|
|
||||||
|
h(Mascot, { |
||||||
|
animationEventEmitter: this.animationEventEmitter, |
||||||
|
width: '225', |
||||||
|
height: '225', |
||||||
|
}), |
||||||
|
|
||||||
|
h('div.welcome-screen__info__header', 'Welcome to MetaMask Beta'), |
||||||
|
|
||||||
|
h('div.welcome-screen__info__copy', 'MetaMask is a secure identity vault for Ethereum.'), |
||||||
|
|
||||||
|
h('div.welcome-screen__info__copy', `It allows you to hold ether & tokens,
|
||||||
|
and serves as your bridge to decentralized applications.`),
|
||||||
|
|
||||||
|
h('button.welcome-screen__button', { |
||||||
|
onClick: this.initiateAccountCreation, |
||||||
|
}, 'Continue'), |
||||||
|
|
||||||
|
]), |
||||||
|
|
||||||
|
]) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
export default connect( |
||||||
|
null, |
||||||
|
dispatch => ({ |
||||||
|
closeWelcomeScreen: () => dispatch(closeWelcomeScreen()), |
||||||
|
}) |
||||||
|
)(WelcomeScreen) |
@ -0,0 +1,33 @@ |
|||||||
|
|
||||||
|
// cross-browser connection to extension i18n API
|
||||||
|
|
||||||
|
const chrome = chrome || null |
||||||
|
const browser = browser || null |
||||||
|
const extension = require('extensionizer') |
||||||
|
var log = require('loglevel') |
||||||
|
window.log = log |
||||||
|
let getMessage |
||||||
|
|
||||||
|
if (extension.i18n && extension.i18n.getMessage) { |
||||||
|
getMessage = extension.i18n.getMessage |
||||||
|
} else { |
||||||
|
// fallback function
|
||||||
|
log.warn('browser.i18n API not available, calling back to english.') |
||||||
|
const msg = require('../app/_locales/en/messages.json') |
||||||
|
getMessage = function (key, substitutions) { |
||||||
|
if (!msg[key]) { |
||||||
|
log.error(key) |
||||||
|
throw new Error(key) |
||||||
|
} |
||||||
|
let phrase = msg[key].message |
||||||
|
if (substitutions && substitutions.length) { |
||||||
|
phrase = phrase.replace(/\$1/g, substitutions[0]) |
||||||
|
if (substitutions.length > 1) { |
||||||
|
phrase = phrase.replace(/\$2/g, substitutions[1]) |
||||||
|
} |
||||||
|
} |
||||||
|
return phrase |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
module.exports = getMessage |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue