Gaëtan Renaudeau
7 years ago
committed by
GitHub
223 changed files with 5069 additions and 3183 deletions
@ -1,47 +1,26 @@ |
|||||
version: 2 |
version: 2 |
||||
|
|
||||
docker_defaults: &docker_defaults |
defaults: &defaults |
||||
|
working_directory: ~/ledger-live-desktop |
||||
docker: |
docker: |
||||
- image: circleci/node:9.5 |
- image: circleci/node:8.11.3-stretch-browsers |
||||
|
|
||||
jobs: |
jobs: |
||||
build: |
build: |
||||
<<: *docker_defaults |
<<: *defaults |
||||
branches: |
|
||||
ignore: |
|
||||
- gh-pages |
|
||||
steps: |
steps: |
||||
|
- run: sudo apt-get install -y libudev-dev |
||||
- checkout |
- checkout |
||||
- restore_cache: |
- restore_cache: |
||||
name: Restore Yarn Package Cache |
|
||||
keys: |
keys: |
||||
- v2-yarn-packages-{{ checksum "yarn.lock" }} |
- v7-yarn-packages-{{ checksum "yarn.lock" }} |
||||
- run: |
- run: yarn install |
||||
name: Install Dependencies |
|
||||
command: bash scripts/install-ci-deps.sh |
|
||||
- save_cache: |
- save_cache: |
||||
name: Save Yarn Package Cache |
key: v7-yarn-packages-{{ checksum "yarn.lock" }} |
||||
key: v2-yarn-packages-{{ checksum "yarn.lock" }} |
|
||||
paths: |
paths: |
||||
- node_modules/ |
- node_modules |
||||
- run: |
- run: yarn lint |
||||
name: Lint |
- run: ./node_modules/.bin/prettier -l "{src,webpack,.storybook,static/i18n}/**/*.js" |
||||
command: yarn lint |
- run: yarn flow --quiet |
||||
- run: |
- run: yarn test |
||||
name: Prettier |
- run: yarn release |
||||
command: ./node_modules/.bin/prettier -l "{src,webpack,.storybook}/**/*.js" |
|
||||
- run: |
|
||||
name: Flow |
|
||||
command: yarn flow --quiet |
|
||||
# - run: |
|
||||
# name: Test |
|
||||
# command: yarn test |
|
||||
# - run: |
|
||||
# name: Build |
|
||||
# command: yarn dist:dir |
|
||||
# - run: |
|
||||
# name: Generate build stats |
|
||||
# command: "du -h dist | sort -h > /tmp/build-stats.txt" |
|
||||
# - store_artifacts: |
|
||||
# path: /tmp/build-stats.txt |
|
||||
# destination: build-stats.txt |
|
||||
|
@ -1,11 +1,20 @@ |
|||||
.DS_Store |
|
||||
*.log |
|
||||
/.env |
/.env |
||||
/dist/ |
/dist/ |
||||
/flow-typed/ |
/flow-typed/ |
||||
/node_modules/ |
/node_modules/ |
||||
/static/fonts/museosans/ |
/static/fonts/museosans/ |
||||
/storybook-static/ |
/storybook-static/ |
||||
thumbs.db |
|
||||
|
/build/linux/arch/.SRCINFO |
||||
|
/build/linux/arch/pkg |
||||
|
/build/linux/arch/src |
||||
|
/build/linux/arch/*.tar.gz |
||||
|
/build/linux/arch/*.tar.xz |
||||
|
|
||||
|
# TODO this should be in devs global gitignore |
||||
|
# it makes no sense to have it here |
||||
|
*.log |
||||
|
.DS_Store |
||||
.vscode |
.vscode |
||||
|
thumbs.db |
||||
jsconfig.json |
jsconfig.json |
@ -0,0 +1,53 @@ |
|||||
|
# Maintainer: Meriadec Pillet <meriadec.pillet@gmail.com> |
||||
|
# shellcheck disable=SC2154,SC2034,SC2164 |
||||
|
|
||||
|
pkgname=ledger-live |
||||
|
pkgver=1.0.7 |
||||
|
pkgrel=1 |
||||
|
pkgdesc="Open source companion app for your Ledger devices" |
||||
|
arch=('x86_64') |
||||
|
url="https://www.ledgerwallet.com/live" |
||||
|
license=('MIT') |
||||
|
makedepends=(yarn python2) |
||||
|
|
||||
|
# TODO generate changelog from release notes |
||||
|
changelog= |
||||
|
|
||||
|
source=("https://github.com/LedgerHQ/ledger-live-desktop/archive/v${pkgver}.tar.gz" |
||||
|
"ledger-live.desktop") |
||||
|
md5sums=('d60d772a03c0a1c59df07f93b0268a4c' |
||||
|
'52705147909a0a988907a23a71199092') |
||||
|
# TODO sign with ledger pgp |
||||
|
validpgpkeys=() |
||||
|
|
||||
|
extractedFolder=ledger-live-desktop-$pkgver |
||||
|
|
||||
|
prepare() { |
||||
|
cd $extractedFolder |
||||
|
export JOBS=max |
||||
|
yarn --ignore-scripts |
||||
|
} |
||||
|
|
||||
|
build() { |
||||
|
cd $extractedFolder |
||||
|
export GIT_REVISION=$pkgver |
||||
|
export JOBS=max |
||||
|
yarn dist |
||||
|
} |
||||
|
|
||||
|
package() { |
||||
|
install -D -m644 \ |
||||
|
"${pkgname}.desktop" \ |
||||
|
"${pkgdir}/usr/share/applications/${pkgname}.desktop" |
||||
|
|
||||
|
cd $extractedFolder |
||||
|
|
||||
|
install -dm755 "${pkgdir}/opt" |
||||
|
cp -r "dist/linux-unpacked" "${pkgdir}/opt/ledger-live" |
||||
|
install -dm755 "${pkgdir}/usr/bin" |
||||
|
ln -s "/opt/${pkgname}/ledger-live-desktop" "${pkgdir}/usr/bin/${pkgname}" |
||||
|
|
||||
|
install -D -m644 \ |
||||
|
"static/images/browser-window-icon-512x512.png" \ |
||||
|
"${pkgdir}/usr/share/icons/hicolor/512x512/apps/ledger-live.png" |
||||
|
} |
@ -0,0 +1,10 @@ |
|||||
|
[Desktop Entry] |
||||
|
Name=ledger-live |
||||
|
Comment=Open source companion app for your Ledger devices |
||||
|
Path=/opt/ledger-live |
||||
|
Exec=ledger-live |
||||
|
Icon=ledger-live |
||||
|
Type=Application |
||||
|
StartupNotify=true |
||||
|
Categories=Utility; |
||||
|
StartupWMClass=ledger-live |
@ -0,0 +1,132 @@ |
|||||
|
/* eslint-disable no-console */ |
||||
|
|
||||
|
const { spawn } = require('child_process') |
||||
|
|
||||
|
// those wordings are dynamically created, so they are detected
|
||||
|
// as false positive
|
||||
|
const WHITELIST = [ |
||||
|
'app:operation.type.IN', |
||||
|
'app:operation.type.OUT', |
||||
|
'app:exchange.coinhouse', |
||||
|
'app:exchange.changelly', |
||||
|
'app:exchange.coinmama', |
||||
|
'app:exchange.simplex', |
||||
|
'app:exchange.paybis', |
||||
|
'app:addAccounts.accountToImportSubtitle_plural', |
||||
|
'app:dashboard.summary_plural', |
||||
|
'app:addAccounts.success_plural', |
||||
|
'app:addAccounts.successDescription_plural', |
||||
|
'app:time.since.day', |
||||
|
'app:time.since.week', |
||||
|
'app:time.since.month', |
||||
|
'app:time.since.year', |
||||
|
'app:time.day', |
||||
|
'app:time.week', |
||||
|
'app:time.month', |
||||
|
'app:time.year', |
||||
|
'app:addAccounts.cta.add_plural', |
||||
|
'app:manager.apps.installing', |
||||
|
'app:manager.apps.uninstalling', |
||||
|
'app:manager.apps.installSuccess', |
||||
|
'app:manager.apps.uninstallSuccess', |
||||
|
] |
||||
|
|
||||
|
const WORDINGS = { |
||||
|
app: require('../static/i18n/en/app.json'), |
||||
|
onboarding: require('../static/i18n/en/onboarding.json'), |
||||
|
// errors: require('../static/i18n/en/errors.json'),
|
||||
|
// language: require('../static/i18n/en/language.json'),
|
||||
|
} |
||||
|
|
||||
|
async function main() { |
||||
|
console.log(`>> Checking for unused wordings...`) |
||||
|
for (const ns in WORDINGS) { |
||||
|
if (WORDINGS.hasOwnProperty(ns)) { |
||||
|
try { |
||||
|
const root = WORDINGS[ns] |
||||
|
await checkForUsage(root, ns, ':') |
||||
|
} catch (err) { |
||||
|
console.log(err) |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
console.log(`>> Checking for duplicates...`) |
||||
|
for (const ns in WORDINGS) { |
||||
|
if (WORDINGS.hasOwnProperty(ns)) { |
||||
|
try { |
||||
|
const root = WORDINGS[ns] |
||||
|
checkForDuplicate(root, ns, {}, ':') |
||||
|
} catch (err) { |
||||
|
console.log(err) |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
function checkForDuplicate(v, key, values, delimiter = '.') { |
||||
|
if (typeof v === 'object') { |
||||
|
for (const k in v) { |
||||
|
if (v.hasOwnProperty(k)) { |
||||
|
checkForDuplicate(v[k], `${key}${delimiter}${k}`, values) |
||||
|
} |
||||
|
} |
||||
|
} else if (typeof v === 'string') { |
||||
|
if (values[v]) { |
||||
|
console.log(`duplicate value [${v}] for key ${key} (exists in [${values[v].join(', ')}])`) |
||||
|
values[v].push(key) |
||||
|
} else { |
||||
|
values[v] = [key] |
||||
|
} |
||||
|
} else { |
||||
|
console.log(v) |
||||
|
throw new Error('invalid input') |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
async function checkForUsage(v, key, delimiter = '.') { |
||||
|
if (WHITELIST.includes(key)) { |
||||
|
return |
||||
|
} |
||||
|
if (typeof v === 'object') { |
||||
|
for (const k in v) { |
||||
|
if (v.hasOwnProperty(k)) { |
||||
|
await checkForUsage(v[k], `${key}${delimiter}${k}`) |
||||
|
} |
||||
|
} |
||||
|
} else if (typeof v === 'string') { |
||||
|
try { |
||||
|
const hasOccurences = await getHasOccurrences(key) |
||||
|
if (!hasOccurences) { |
||||
|
console.log(key) |
||||
|
} |
||||
|
} catch (err) { |
||||
|
console.log(err) |
||||
|
} |
||||
|
} else { |
||||
|
throw new Error('invalid input') |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
function getHasOccurrences(key) { |
||||
|
return new Promise(resolve => { |
||||
|
const childProcess = spawn('rg', [key, 'src']) |
||||
|
let data |
||||
|
childProcess.stdout.on('data', d => { |
||||
|
data = d.toString() |
||||
|
}) |
||||
|
childProcess.on('close', () => { |
||||
|
if (!data) return resolve(false) |
||||
|
const rows = data.split('\n').filter(Boolean) |
||||
|
return resolve(rows.length > 0) |
||||
|
}) |
||||
|
childProcess.on('error', err => { |
||||
|
if (err.code === 'ENOENT') { |
||||
|
console.log(`You need to install ripgrep first`) |
||||
|
console.log(`see: https://github.com/BurntSushi/ripgrep`) |
||||
|
process.exit(1) |
||||
|
} |
||||
|
}) |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
main() |
@ -1,3 +1,18 @@ |
|||||
#!/bin/bash |
#!/bin/bash |
||||
|
|
||||
yarn compile && DEBUG=electron-builder electron-builder --dir -c.compression=store -c.mac.identity=null |
set -e |
||||
|
|
||||
|
# shellcheck disable=SC1091 |
||||
|
source scripts/helpers/run-job.sh |
||||
|
|
||||
|
# shellcheck disable=SC1091 |
||||
|
source scripts/helpers/display-env.sh |
||||
|
|
||||
|
yarn compile |
||||
|
|
||||
|
runJob \ |
||||
|
"DEBUG=electron-builder electron-builder --dir -c.compression=store -c.mac.identity=null" \ |
||||
|
"building app..." \ |
||||
|
"app built successfully" \ |
||||
|
"failed to build app" \ |
||||
|
"verbose" |
||||
|
@ -1,16 +1,41 @@ |
|||||
#!/bin/bash |
#!/bin/bash |
||||
|
|
||||
|
set -e |
||||
|
|
||||
|
export JOBS=max |
||||
|
|
||||
|
# shellcheck disable=SC1091 |
||||
|
source scripts/helpers/run-job.sh |
||||
|
|
||||
|
# shellcheck disable=SC1091 |
||||
|
source scripts/helpers/display-env.sh |
||||
|
|
||||
# hilarious fix: to make linux icon we have to remove icon.png from build folder |
# hilarious fix: to make linux icon we have to remove icon.png from build folder |
||||
# some context: |
# some context: |
||||
# - https://github.com/electron-userland/electron-builder/issues/2577 |
# - https://github.com/electron-userland/electron-builder/issues/2577 |
||||
# - https://github.com/electron-userland/electron-builder/issues/2269 |
# - https://github.com/electron-userland/electron-builder/issues/2269 |
||||
if [[ $(uname) == 'Linux' ]]; then |
if [[ $(uname) == 'Linux' ]]; then |
||||
mv build/icon.png /tmp |
runJob \ |
||||
|
"mv build/icon.png /tmp" \ |
||||
|
"dirty fix to handle linux icon..." \ |
||||
|
"successfully applied dirty fix to handle linux icon" \ |
||||
|
"failed to apply dirty fix to handle linux icon" |
||||
fi |
fi |
||||
|
|
||||
yarn compile && DEBUG=electron-builder electron-builder |
yarn compile |
||||
|
|
||||
|
runJob \ |
||||
|
"DEBUG=electron-builder electron-builder" \ |
||||
|
"building and packaging app..." \ |
||||
|
"app built and packaged successfully" \ |
||||
|
"failed to build app" \ |
||||
|
"verbose" |
||||
|
|
||||
# hilarious fix continuation: put back the icon where it was |
# hilarious fix continuation: put back the icon where it was |
||||
if [[ $(uname) == 'Linux' ]]; then |
if [[ $(uname) == 'Linux' ]]; then |
||||
mv /tmp/icon.png build |
runJob \ |
||||
|
"mv /tmp/icon.png build" \ |
||||
|
"cleaning dirty fix to handle linux icon..." \ |
||||
|
"successfully applied clean dirty fix to handle linux icon" \ |
||||
|
"failed to apply clean dirty fix to handle linux icon" |
||||
fi |
fi |
||||
|
@ -1,26 +0,0 @@ |
|||||
#!/bin/bash |
|
||||
|
|
||||
function GET_HASH_PATH { |
|
||||
HASH_NAME=$1 |
|
||||
echo "./node_modules/.cache/LEDGER_HASH_$HASH_NAME.hash" |
|
||||
} |
|
||||
|
|
||||
function GET_HASH { |
|
||||
HASH_NAME=$1 |
|
||||
HASH_PATH=$(GET_HASH_PATH "$HASH_NAME") |
|
||||
if [ ! -e "$HASH_PATH" ]; then |
|
||||
echo '' |
|
||||
else |
|
||||
HASH_CONTENT=$(cat "$HASH_PATH") |
|
||||
echo "$HASH_CONTENT" |
|
||||
fi |
|
||||
} |
|
||||
|
|
||||
function SET_HASH { |
|
||||
HASH_NAME=$1 |
|
||||
HASH_CONTENT=$2 |
|
||||
echo "setting hash $HASH_NAME to $HASH_CONTENT" |
|
||||
HASH_PATH=$(GET_HASH_PATH "$HASH_NAME") |
|
||||
mkdir -p ./node_modules/.cache |
|
||||
echo "$HASH_CONTENT" > "$HASH_PATH" |
|
||||
} |
|
@ -0,0 +1,22 @@ |
|||||
|
#!/bin/bash |
||||
|
|
||||
|
# shellcheck disable=SC1091 |
||||
|
source scripts/helpers/format.sh |
||||
|
|
||||
|
if [ "$GIT_REVISION" == "" ]; then |
||||
|
GIT_REVISION=$(git rev-parse HEAD) |
||||
|
fi |
||||
|
|
||||
|
echo |
||||
|
printf " │ \\e[4;1m%s\\e[0;0m\\n" "Ledger Live Desktop - ${GIT_REVISION}" |
||||
|
printf " │ \\e[1;30m%s\\e[1;0m\\n" "$(uname -srmo)" |
||||
|
printf " │ \\e[2;1mcommit \\e[0;33m%s\\e[0;0m\\n" "$(git rev-parse HEAD)" |
||||
|
echo |
||||
|
|
||||
|
formatEnvVar "CI" |
||||
|
formatEnvVar "NODE_ENV" |
||||
|
formatEnvVar "JOBS" |
||||
|
|
||||
|
echo |
||||
|
formatGeneric "node" "$(node --version)" |
||||
|
echo |
@ -0,0 +1,48 @@ |
|||||
|
#!/bin/bash |
||||
|
|
||||
|
colSize=20 |
||||
|
|
||||
|
function formatJobTitle { |
||||
|
echo "[$1]" |
||||
|
echo |
||||
|
} |
||||
|
|
||||
|
function formatEnvVar { |
||||
|
key=$1 |
||||
|
value=$(eval echo \$"${key}") |
||||
|
color="32" |
||||
|
if [ "$value" == "" ]; then color="34"; value="unset" |
||||
|
elif [ "$value" == "1" ]; then color="32" |
||||
|
elif [ "$value" == "0" ]; then color="35" |
||||
|
else value="'$value'" |
||||
|
fi |
||||
|
printf " %-${colSize}s\\e[2;${color}m%s\\e[1;0m\\n" "$key" "$value" |
||||
|
} |
||||
|
|
||||
|
function formatGeneric { |
||||
|
printf " %-${colSize}s\\e[0;2m%s\\e[0m\\n" "$1" "$2" |
||||
|
} |
||||
|
|
||||
|
function formatDiscret { |
||||
|
printf "\\e[2;34m%s\\e[2;0m\\n" "$1" |
||||
|
} |
||||
|
|
||||
|
function formatSkip { |
||||
|
printf "\\e[2;34m[-] skipping %s (%s)\\e[0;0m\\n" "$1" "$2" |
||||
|
} |
||||
|
|
||||
|
function clearLine { |
||||
|
echo -en "\\r\\e[0K" |
||||
|
} |
||||
|
|
||||
|
function formatError { |
||||
|
printf "\\e[0;31m[✘] %s\\e[0;0m\\n" "$1" |
||||
|
} |
||||
|
|
||||
|
function formatProgress { |
||||
|
printf "\\e[0;35m[⬇] %s\\e[0;0m" "$1" |
||||
|
} |
||||
|
|
||||
|
function formatSuccess { |
||||
|
printf "\\e[0;36m[✔] %s\\e[0;0m\\n" "$1" |
||||
|
} |
@ -0,0 +1,39 @@ |
|||||
|
#!/bin/bash |
||||
|
|
||||
|
# shellcheck disable=SC1091 |
||||
|
source scripts/helpers/format.sh |
||||
|
|
||||
|
function _getHashPath { |
||||
|
HASH_NAME=$1 |
||||
|
echo "./node_modules/.cache/LEDGER_HASH_$HASH_NAME.hash" |
||||
|
} |
||||
|
|
||||
|
function getHash { |
||||
|
HASH_NAME=$1 |
||||
|
HASH_PATH=$(_getHashPath "$HASH_NAME") |
||||
|
if [ ! -e "$HASH_PATH" ]; then |
||||
|
echo '' |
||||
|
else |
||||
|
HASH_CONTENT=$(cat "$HASH_PATH") |
||||
|
echo "$HASH_CONTENT" |
||||
|
fi |
||||
|
} |
||||
|
|
||||
|
function setHash { |
||||
|
HASH_NAME=$1 |
||||
|
HASH_CONTENT=$2 |
||||
|
formatSuccess "$HASH_NAME hash set to $HASH_CONTENT" |
||||
|
HASH_PATH=$(_getHashPath "$HASH_NAME") |
||||
|
mkdir -p ./node_modules/.cache |
||||
|
echo "$HASH_CONTENT" > "$HASH_PATH" |
||||
|
} |
||||
|
|
||||
|
function hashDiffers { |
||||
|
cachedHash=$(getHash "$1") |
||||
|
hash=$2 |
||||
|
if [ "$cachedHash" == "$hash" ]; then |
||||
|
return 1 |
||||
|
else |
||||
|
return 0 |
||||
|
fi |
||||
|
} |
@ -0,0 +1,62 @@ |
|||||
|
#!/bin/bash |
||||
|
|
||||
|
# shellcheck disable=SC1091 |
||||
|
source scripts/helpers/format.sh |
||||
|
|
||||
|
operatingSystem=$(uname -s) |
||||
|
if [ "$operatingSystem" != "Linux" ] && [ "$operatingSystem" != "Darwin" ]; then |
||||
|
operatingSystem="Windows" |
||||
|
fi |
||||
|
|
||||
|
function runJob { |
||||
|
|
||||
|
local job=$1 |
||||
|
local progressMsg=$2 |
||||
|
local successMsg=$3 |
||||
|
local errMsg=$4 |
||||
|
local logLevel=$5 |
||||
|
|
||||
|
local tmpScript |
||||
|
local tmpErrFile |
||||
|
local childPid |
||||
|
local returnCode |
||||
|
|
||||
|
# let's absolutely don't take care of this fake os |
||||
|
if [ "$operatingSystem" == "Windows" ]; then |
||||
|
tmpScript=$(mktemp) |
||||
|
echo "$job" > "$tmpScript" |
||||
|
bash "$tmpScript" |
||||
|
rm "$tmpScript" |
||||
|
return $? |
||||
|
fi |
||||
|
|
||||
|
tmpErrFile=$(mktemp) |
||||
|
|
||||
|
formatProgress "$progressMsg" |
||||
|
|
||||
|
if [ "$logLevel" == "verbose" ]; then |
||||
|
echo |
||||
|
echo "$job" | bash & |
||||
|
else |
||||
|
echo "$job" | bash >/dev/null 2>"$tmpErrFile" & |
||||
|
fi |
||||
|
|
||||
|
childPid=$! |
||||
|
|
||||
|
# prevent set -e to exit if child fail |
||||
|
wait $childPid && returnCode=$? || returnCode=$? |
||||
|
|
||||
|
if [ "$logLevel" != "verbose" ]; then |
||||
|
clearLine |
||||
|
fi |
||||
|
|
||||
|
if [ $returnCode -eq 0 ]; then |
||||
|
formatSuccess "$successMsg" |
||||
|
else |
||||
|
formatError "$errMsg" |
||||
|
formatError "$(cat "$tmpErrFile")" |
||||
|
fi |
||||
|
|
||||
|
rm "$tmpErrFile" |
||||
|
return $returnCode |
||||
|
} |
@ -1,14 +0,0 @@ |
|||||
#!/bin/bash |
|
||||
|
|
||||
# shellcheck disable=SC1091 |
|
||||
source scripts/hash-utils.sh |
|
||||
|
|
||||
PACKAGE_JSON_HASH=$(md5sum package.json | cut -d ' ' -f 1) |
|
||||
CACHED_PACKAGE_JSON_HASH=$(GET_HASH 'package.json') |
|
||||
|
|
||||
if [ "$CACHED_PACKAGE_JSON_HASH" == "$PACKAGE_JSON_HASH" ]; then |
|
||||
echo "> Skipping yarn install" |
|
||||
else |
|
||||
yarn install |
|
||||
SET_HASH 'package.json' "$PACKAGE_JSON_HASH" |
|
||||
fi |
|
@ -1,135 +0,0 @@ |
|||||
// This is a work in progress
|
|
||||
// The goal is to provide a cli which allow interact
|
|
||||
// with device & libcore for faster iterations
|
|
||||
|
|
||||
require('babel-polyfill') |
|
||||
require('babel-register') |
|
||||
|
|
||||
const chalk = require('chalk') |
|
||||
const inquirer = require('inquirer') |
|
||||
const path = require('path') |
|
||||
const TransportNodeHid = require('@ledgerhq/hw-transport-node-hid').default |
|
||||
|
|
||||
const { serializeAccounts, encodeAccount, decodeAccount } = require('../src/reducers/accounts') |
|
||||
const { doSignAndBroadcast } = require('../src/commands/libcoreSignAndBroadcast') |
|
||||
|
|
||||
const coreHelper = require('../src/helpers/libcore') |
|
||||
const withLibcore = require('../src/helpers/withLibcore').default |
|
||||
|
|
||||
if (!process.env.LEDGER_LIVE_SQLITE_PATH) { |
|
||||
throw new Error('you must define process.env.LEDGER_LIVE_SQLITE_PATH first') |
|
||||
} |
|
||||
|
|
||||
const LOCAL_DIRECTORY_PATH = path.resolve(process.env.LEDGER_LIVE_SQLITE_PATH, '../') |
|
||||
|
|
||||
gimmeDeviceAndLibCore(async ({ device, core, njsWalletPool }) => { |
|
||||
const raw = require(path.join(LOCAL_DIRECTORY_PATH, 'accounts.json')) // eslint-disable-line import/no-dynamic-require
|
|
||||
const accounts = serializeAccounts(raw.data) |
|
||||
const accountToUse = await chooseAccount('Which account to use?', accounts) |
|
||||
await actionLoop({ account: accountToUse, accounts, core, njsWalletPool, device }) |
|
||||
process.exit(0) |
|
||||
}) |
|
||||
|
|
||||
async function actionLoop(props) { |
|
||||
try { |
|
||||
const { account, accounts, core, njsWalletPool, device } = props |
|
||||
const actionToDo = await chooseAction(`What do you want to do with [${account.name}] ?`) |
|
||||
if (actionToDo === 'send funds') { |
|
||||
const transport = await TransportNodeHid.open(device.path) |
|
||||
const accountToReceive = await chooseAccount('To which account?', accounts) |
|
||||
const receiveAddress = await getFreshAddress({ |
|
||||
account: accountToReceive, |
|
||||
core, |
|
||||
njsWalletPool, |
|
||||
}) |
|
||||
console.log(`the receive address is ${receiveAddress}`) |
|
||||
const rawAccount = encodeAccount(account) |
|
||||
console.log(`trying to sign and broadcast...`) |
|
||||
const rawOp = await doSignAndBroadcast({ |
|
||||
account: rawAccount, |
|
||||
transaction: { |
|
||||
amount: 4200000, |
|
||||
recipient: receiveAddress, |
|
||||
feePerByte: 16, |
|
||||
isRBF: false, |
|
||||
}, |
|
||||
deviceId: device.path, |
|
||||
core, |
|
||||
transport, |
|
||||
}) |
|
||||
console.log(rawOp) |
|
||||
} else if (actionToDo === 'sync') { |
|
||||
console.log(`\nLaunch sync...\n`) |
|
||||
const rawAccount = encodeAccount(account) |
|
||||
const syncedAccount = await coreHelper.syncAccount({ rawAccount, core, njsWalletPool }) |
|
||||
console.log(`\nEnd sync...\n`) |
|
||||
console.log(`updated account: `, displayAccount(syncedAccount, 'red')) |
|
||||
} else if (actionToDo === 'quit') { |
|
||||
return true |
|
||||
} |
|
||||
} catch (err) { |
|
||||
console.log(`x Something went wrong`) |
|
||||
console.log(err) |
|
||||
process.exit(1) |
|
||||
} |
|
||||
return actionLoop(props) |
|
||||
} |
|
||||
|
|
||||
async function chooseInList(msg, list, formatItem = i => i) { |
|
||||
const choices = list.map(formatItem) |
|
||||
const { choice } = await inquirer.prompt([ |
|
||||
{ |
|
||||
type: 'list', |
|
||||
name: 'choice', |
|
||||
message: msg, |
|
||||
choices, |
|
||||
}, |
|
||||
]) |
|
||||
const index = choices.indexOf(choice) |
|
||||
return list[index] |
|
||||
} |
|
||||
|
|
||||
async function chooseAction(msg) { |
|
||||
return chooseInList(msg, ['sync', 'send funds', 'quit']) |
|
||||
} |
|
||||
|
|
||||
function chooseAccount(msg, accounts) { |
|
||||
return chooseInList(msg, accounts, acc => displayAccount(acc)) |
|
||||
} |
|
||||
|
|
||||
async function gimmeDeviceAndLibCore(cb) { |
|
||||
withLibcore((core, njsWalletPool) => { |
|
||||
TransportNodeHid.listen({ |
|
||||
error: () => {}, |
|
||||
complete: () => {}, |
|
||||
next: async e => { |
|
||||
if (!e.device) { |
|
||||
return |
|
||||
} |
|
||||
if (e.type === 'add') { |
|
||||
const { device } = e |
|
||||
cb({ device, core, njsWalletPool }) |
|
||||
} |
|
||||
}, |
|
||||
}) |
|
||||
}) |
|
||||
} |
|
||||
|
|
||||
function displayAccount(acc, color = null) { |
|
||||
const isRawAccount = typeof acc.lastSyncDate === 'string' |
|
||||
if (isRawAccount) { |
|
||||
acc = decodeAccount(acc) |
|
||||
} |
|
||||
const str = `[${acc.name}] ${acc.isSegwit ? '' : '(legacy) '}${acc.unit.code} ${acc.balance} - ${ |
|
||||
acc.operations.length |
|
||||
} txs` |
|
||||
return color ? chalk[color](str) : str |
|
||||
} |
|
||||
|
|
||||
async function getFreshAddress({ account, core, njsWalletPool }) { |
|
||||
const njsAccount = await coreHelper.getNJSAccount({ account, njsWalletPool }) |
|
||||
const unsub = await core.syncAccount(njsAccount) |
|
||||
unsub() |
|
||||
const rawAddresses = await njsAccount.getFreshPublicAddresses() |
|
||||
return rawAddresses[0] |
|
||||
} |
|
@ -1,63 +0,0 @@ |
|||||
// Utility to human-read the accounts.json file
|
|
||||
// You have to pass it in parameter, because the location
|
|
||||
// differ depending on the OS.
|
|
||||
|
|
||||
const { |
|
||||
formatCurrencyUnit, |
|
||||
getCryptoCurrencyById, |
|
||||
} = require('@ledgerhq/live-common/lib/helpers/currencies') |
|
||||
const chalk = require('chalk') |
|
||||
const padStart = require('lodash/padStart') |
|
||||
const padEnd = require('lodash/padEnd') |
|
||||
|
|
||||
const { argv } = process |
|
||||
|
|
||||
const [, , FILE_PATH] = argv |
|
||||
|
|
||||
if (!FILE_PATH) { |
|
||||
console.log(`You need to specify a file`) |
|
||||
process.exit(1) |
|
||||
} |
|
||||
|
|
||||
const { data: wrappedAccounts } = require(FILE_PATH) // eslint-disable-line
|
|
||||
|
|
||||
const str = wrappedAccounts |
|
||||
.map(({ data: account }) => { |
|
||||
const currency = getCryptoCurrencyById(account.currencyId) |
|
||||
const unit = currency.units[0] |
|
||||
const headline = `${account.isSegwit ? '[SEGWIT]' : '[NOT SEGWIT]'} ${account.name} | ${ |
|
||||
account.id |
|
||||
} | ${account.path} | balance: ${formatCurrencyUnit(unit, account.balance, { |
|
||||
showCode: true, |
|
||||
alwaysShowSign: true, |
|
||||
})}` |
|
||||
return [ |
|
||||
headline, |
|
||||
headline |
|
||||
.split('') |
|
||||
.map(() => '-') |
|
||||
.join(''), |
|
||||
account.operations |
|
||||
.map(op => { |
|
||||
const opType = op.amount < 0 ? 'SEND' : 'RECEIVE' |
|
||||
return [ |
|
||||
padEnd(opType, 8), |
|
||||
op.date.substr(0, 10), |
|
||||
chalk[opType === 'SEND' ? 'red' : 'green']( |
|
||||
padStart( |
|
||||
formatCurrencyUnit(unit, op.amount, { |
|
||||
showCode: true, |
|
||||
alwaysShowSign: true, |
|
||||
}), |
|
||||
15, |
|
||||
), |
|
||||
), |
|
||||
op.hash, |
|
||||
].join(' ') |
|
||||
}) |
|
||||
.join('\n'), |
|
||||
].join('\n') |
|
||||
}) |
|
||||
.join('\n\n') |
|
||||
|
|
||||
console.log(str) |
|
@ -1,45 +1,85 @@ |
|||||
#!/bin/bash |
#!/bin/bash |
||||
|
|
||||
|
set -e |
||||
|
|
||||
|
export JOBS=max |
||||
|
|
||||
|
# shellcheck disable=SC1091 |
||||
|
source scripts/helpers/display-env.sh |
||||
# shellcheck disable=SC1091 |
# shellcheck disable=SC1091 |
||||
source scripts/hash-utils.sh |
source scripts/helpers/format.sh |
||||
|
# shellcheck disable=SC1091 |
||||
|
source scripts/helpers/hash.sh |
||||
|
# shellcheck disable=SC1091 |
||||
|
source scripts/helpers/run-job.sh |
||||
|
|
||||
|
latestFlowTypedCommitHash='' |
||||
|
|
||||
function MAIN { |
function main { |
||||
if ! $CI; then |
|
||||
REBUILD_ELECTRON_NATIVE_DEPS |
# native dependencies |
||||
|
|
||||
|
if hashDiffers yarn.lock "$(getYarnHash)"; then |
||||
|
rebuildElectronNativeDeps |
||||
|
else |
||||
|
formatSkip "native module build" "already up-to-date" |
||||
fi |
fi |
||||
INSTALL_FLOW_TYPED |
|
||||
} |
|
||||
|
|
||||
function INSTALL_FLOW_TYPED { |
# flow-typed |
||||
LATEST_FLOW_TYPED_COMMIT_HASH=$(curl --silent --header "Accept: application/vnd.github.VERSION.sha" https://api.github.com/repos/flowtype/flow-typed/commits/master) |
|
||||
CURRENT_FLOW_TYPED_HASH=$(GET_HASH 'flow-typed') |
formatProgress "Checking if flow-typed definitions are up-to-date..." |
||||
if [ "$LATEST_FLOW_TYPED_COMMIT_HASH" == "$CURRENT_FLOW_TYPED_HASH" ]; then |
latestFlowTypedCommitHash=$(curl --silent --header "Accept: application/vnd.github.VERSION.sha" --location https://api.github.com/repos/flowtype/flow-typed/commits/master) |
||||
echo "> Flow-typed definitions are up to date. Skipping" |
clearLine |
||||
|
|
||||
|
if [[ $latestFlowTypedCommitHash =~ ^\{ ]]; then |
||||
|
formatError "Failed to retrieve flow-typed definitions" |
||||
|
echo "$latestFlowTypedCommitHash" |
||||
|
exit 1 |
||||
else |
else |
||||
echo "> Installing flow-typed defs" |
if hashDiffers flow-typed "$latestFlowTypedCommitHash"; then |
||||
flow-typed install -s --overwrite |
installFlowTyped |
||||
echo "> Removing broken flow definitions" |
else |
||||
rm flow-typed/npm/{react-i18next_v7.x.x.js,styled-components_v3.x.x.js,redux_*,winston*} |
formatSkip "flow-typed installation" "already up-to-date" |
||||
SET_HASH 'flow-typed' "$LATEST_FLOW_TYPED_COMMIT_HASH" |
fi |
||||
fi |
fi |
||||
|
|
||||
|
echo |
||||
|
|
||||
} |
} |
||||
|
|
||||
function REBUILD_ELECTRON_NATIVE_DEPS { |
function installFlowTyped { |
||||
# for strange/fancy os-es |
runJob \ |
||||
|
"flow-typed install -s --overwrite" \ |
||||
|
"Installing flow-typed definitions..." \ |
||||
|
"Installed flow-typed definitions" \ |
||||
|
"Failed installing flow-typed definitions" |
||||
|
|
||||
|
runJob \ |
||||
|
"rm flow-typed/npm/{react-i18next_v7.x.x.js,styled-components_v3.x.x.js,redux_*,winston*}" \ |
||||
|
"Removing broken flow-typed definitions" \ |
||||
|
"Removed broken flow-typed definitions" \ |
||||
|
"Failed removing broken flow-typed definitions" |
||||
|
|
||||
|
setHash flow-typed "$latestFlowTypedCommitHash" |
||||
|
} |
||||
|
|
||||
|
function rebuildElectronNativeDeps { |
||||
|
runJob \ |
||||
|
"DEBUG=electron-builder electron-builder install-app-deps" \ |
||||
|
"Building native electron dependencies..." \ |
||||
|
"Successfully builded native modules for electron" \ |
||||
|
"Build failed" \ |
||||
|
"verbose" |
||||
|
setHash yarn.lock "$(getYarnHash)" |
||||
|
} |
||||
|
|
||||
|
function getYarnHash { |
||||
if [[ $(uname) == 'Darwin' ]]; then |
if [[ $(uname) == 'Darwin' ]]; then |
||||
PACKAGE_JSON_HASH=$(md5 package.json | cut -d ' ' -f 1) |
yarnHash=$(md5 yarn.lock | cut -d ' ' -f 1) |
||||
else |
|
||||
# for normal os-es |
|
||||
PACKAGE_JSON_HASH=$(md5sum package.json | cut -d ' ' -f 1) |
|
||||
fi |
|
||||
CACHED_PACKAGE_JSON_HASH=$(GET_HASH 'package.json') |
|
||||
if [ "$CACHED_PACKAGE_JSON_HASH" == "$PACKAGE_JSON_HASH" ]; then |
|
||||
echo "> Electron native deps are up to date. Skipping" |
|
||||
else |
else |
||||
echo "> Installing electron native deps" |
yarnHash=$(md5sum yarn.lock | cut -d ' ' -f 1) |
||||
DEBUG=electron-builder electron-builder install-app-deps |
|
||||
SET_HASH 'package.json' "$PACKAGE_JSON_HASH" |
|
||||
fi |
fi |
||||
|
echo "$yarnHash" |
||||
} |
} |
||||
|
|
||||
MAIN |
main |
||||
|
@ -0,0 +1,42 @@ |
|||||
|
#!/bin/bash |
||||
|
|
||||
|
set -e |
||||
|
|
||||
|
# shellcheck disable=SC1091 |
||||
|
source scripts/helpers/run-job.sh |
||||
|
|
||||
|
# shellcheck disable=SC1091 |
||||
|
source scripts/helpers/display-env.sh |
||||
|
|
||||
|
gitTag=$(git describe --tags) |
||||
|
tmpDir=$(mktemp -d) |
||||
|
|
||||
|
runJob \ |
||||
|
"pushd build/linux/arch >/dev/null; makepkg --printsrcinfo > .SRCINFO; popd >/dev/null" \ |
||||
|
"creating .SRCINFO" \ |
||||
|
"successfully created .SRCINFO" \ |
||||
|
"error creating .SRCINFO" |
||||
|
|
||||
|
runJob \ |
||||
|
"git clone ssh://aur@aur.archlinux.org/ledger-live.git ${tmpDir}" \ |
||||
|
"cloning AUR repository" \ |
||||
|
"cloned AUR repository" \ |
||||
|
"error cloning AUR repository" |
||||
|
|
||||
|
runJob \ |
||||
|
"cp build/linux/arch/{ledger-live.desktop,PKGBUILD,.SRCINFO} \"${tmpDir}\"" \ |
||||
|
"copying files" \ |
||||
|
"copied files" \ |
||||
|
"error copying files" |
||||
|
|
||||
|
# shellcheck disable=SC2164 |
||||
|
cd "$tmpDir" |
||||
|
|
||||
|
git add . |
||||
|
git commit -m "Build for ${gitTag}" |
||||
|
|
||||
|
runJob \ |
||||
|
"git push origin master" \ |
||||
|
"pushing package" \ |
||||
|
"successfully pushed package" \ |
||||
|
"error pushing package" |
@ -1,5 +1,8 @@ |
|||||
#!/bin/bash |
#!/bin/bash |
||||
|
|
||||
|
# shellcheck disable=SC1091 |
||||
|
source scripts/helpers/display-env.sh |
||||
|
|
||||
concurrently --raw --kill-others \ |
concurrently --raw --kill-others \ |
||||
"cross-env NODE_ENV=development webpack-cli --mode development --watch --config webpack/internals.config.js" \ |
"cross-env NODE_ENV=development webpack-cli --mode development --watch --config webpack/internals.config.js" \ |
||||
"cross-env NODE_ENV=development electron-webpack dev" |
"cross-env NODE_ENV=development electron-webpack dev" |
||||
|
@ -0,0 +1,43 @@ |
|||||
|
// @flow
|
||||
|
|
||||
|
import { createSelector, createStructuredSelector } from 'reselect' |
||||
|
import CounterValues from 'helpers/countervalues' |
||||
|
import { |
||||
|
intermediaryCurrency, |
||||
|
currencySettingsForAccountSelector, |
||||
|
getOrderAccounts, |
||||
|
} from 'reducers/settings' |
||||
|
import { accountsSelector } from 'reducers/accounts' |
||||
|
import { sortAccounts } from 'helpers/accountOrdering' |
||||
|
|
||||
|
const accountsBtcBalanceSelector = createSelector( |
||||
|
accountsSelector, |
||||
|
state => state, |
||||
|
(accounts, state) => |
||||
|
accounts.map(account => { |
||||
|
const { exchange } = currencySettingsForAccountSelector(state, { account }) |
||||
|
return CounterValues.calculateSelector(state, { |
||||
|
from: account.currency, |
||||
|
to: intermediaryCurrency, |
||||
|
exchange, |
||||
|
value: account.balance, |
||||
|
}) |
||||
|
}), |
||||
|
) |
||||
|
|
||||
|
const selectAccountsBalanceAndOrder = createStructuredSelector({ |
||||
|
accounts: accountsSelector, |
||||
|
accountsBtcBalance: accountsBtcBalanceSelector, |
||||
|
orderAccounts: getOrderAccounts, |
||||
|
}) |
||||
|
|
||||
|
export const refreshAccountsOrdering = () => (dispatch: *, getState: *) => { |
||||
|
const all = selectAccountsBalanceAndOrder(getState()) |
||||
|
const allRatesAvailable = all.accountsBtcBalance.every(b => !!b) |
||||
|
if (allRatesAvailable) { |
||||
|
dispatch({ |
||||
|
type: 'DB:REORDER_ACCOUNTS', |
||||
|
payload: sortAccounts(all), |
||||
|
}) |
||||
|
} |
||||
|
} |
@ -0,0 +1,28 @@ |
|||||
|
// @flow
|
||||
|
|
||||
|
import { getCryptoCurrencyById } from '@ledgerhq/live-common/lib/helpers/currencies' |
||||
|
import { createCommand, Command } from 'helpers/ipc' |
||||
|
import { fromPromise } from 'rxjs/observable/fromPromise' |
||||
|
import { withDevice } from 'helpers/deviceAccess' |
||||
|
import debugAppInfosForCurrency from 'helpers/debugAppInfosForCurrency' |
||||
|
|
||||
|
type Input = { |
||||
|
currencyId: string, |
||||
|
devicePath: string, |
||||
|
} |
||||
|
|
||||
|
type Result = { |
||||
|
version?: string, |
||||
|
} |
||||
|
|
||||
|
const cmd: Command<Input, Result> = createCommand( |
||||
|
'debugAppInfosForCurrency', |
||||
|
({ currencyId, devicePath }) => |
||||
|
fromPromise( |
||||
|
withDevice(devicePath)(transport => |
||||
|
debugAppInfosForCurrency(transport, getCryptoCurrencyById(currencyId)), |
||||
|
), |
||||
|
), |
||||
|
) |
||||
|
|
||||
|
export default cmd |
@ -1,103 +0,0 @@ |
|||||
// @flow
|
|
||||
|
|
||||
import React, { PureComponent } from 'react' |
|
||||
import styled from 'styled-components' |
|
||||
|
|
||||
import type { Account, Currency } from '@ledgerhq/live-common/lib/types' |
|
||||
|
|
||||
import Chart from 'components/base/Chart' |
|
||||
import Bar from 'components/base/Bar' |
|
||||
import Box, { Card } from 'components/base/Box' |
|
||||
import CalculateBalance from 'components/CalculateBalance' |
|
||||
import FormattedVal from 'components/base/FormattedVal' |
|
||||
import Ellipsis from 'components/base/Ellipsis' |
|
||||
import CryptoCurrencyIcon from 'components/CryptoCurrencyIcon' |
|
||||
import DeltaChange from '../DeltaChange' |
|
||||
|
|
||||
const Wrapper = styled(Card).attrs({ |
|
||||
p: 4, |
|
||||
flex: 1, |
|
||||
})` |
|
||||
cursor: ${p => (p.onClick ? 'pointer' : 'default')}; |
|
||||
` |
|
||||
|
|
||||
class AccountCard extends PureComponent<{ |
|
||||
counterValue: Currency, |
|
||||
account: Account, |
|
||||
onClick?: Account => void, |
|
||||
daysCount: number, |
|
||||
}> { |
|
||||
render() { |
|
||||
const { counterValue, account, onClick, daysCount, ...props } = this.props |
|
||||
return ( |
|
||||
<Wrapper onClick={onClick ? () => onClick(account) : null} {...props}> |
|
||||
<Box flow={4}> |
|
||||
<Box horizontal ff="Open Sans|SemiBold" flow={3} alignItems="center"> |
|
||||
<Box |
|
||||
alignItems="center" |
|
||||
justifyContent="center" |
|
||||
style={{ color: account.currency.color }} |
|
||||
> |
|
||||
<CryptoCurrencyIcon currency={account.currency} size={20} /> |
|
||||
</Box> |
|
||||
<Box grow> |
|
||||
<Box style={{ textTransform: 'uppercase' }} fontSize={0} color="graphite"> |
|
||||
{account.currency.name} |
|
||||
</Box> |
|
||||
<Ellipsis fontSize={4} color="dark"> |
|
||||
{account.name} |
|
||||
</Ellipsis> |
|
||||
</Box> |
|
||||
</Box> |
|
||||
<Bar size={1} color="fog" /> |
|
||||
<Box justifyContent="center"> |
|
||||
<FormattedVal |
|
||||
alwaysShowSign={false} |
|
||||
color="dark" |
|
||||
unit={account.unit} |
|
||||
showCode |
|
||||
val={account.balance} |
|
||||
/> |
|
||||
</Box> |
|
||||
</Box> |
|
||||
<CalculateBalance counterValue={counterValue} accounts={[account]} daysCount={daysCount}> |
|
||||
{({ isAvailable, balanceHistory, balanceStart, balanceEnd }) => ( |
|
||||
<Box flow={4}> |
|
||||
<Box flow={2} horizontal> |
|
||||
<Box justifyContent="center"> |
|
||||
{isAvailable ? ( |
|
||||
<FormattedVal |
|
||||
animateTicker |
|
||||
unit={counterValue.units[0]} |
|
||||
val={balanceEnd} |
|
||||
alwaysShowSign={false} |
|
||||
showCode |
|
||||
fontSize={3} |
|
||||
color="graphite" |
|
||||
/> |
|
||||
) : null} |
|
||||
</Box> |
|
||||
<Box grow justifyContent="center"> |
|
||||
{balanceStart && isAvailable ? ( |
|
||||
<DeltaChange from={balanceStart} to={balanceEnd} alwaysShowSign fontSize={3} /> |
|
||||
) : null} |
|
||||
</Box> |
|
||||
</Box> |
|
||||
<Chart |
|
||||
data={balanceHistory} |
|
||||
color={account.currency.color} |
|
||||
height={52} |
|
||||
hideAxis |
|
||||
isInteractive={false} |
|
||||
id={`account-chart-${account.id}`} |
|
||||
unit={account.unit} |
|
||||
/> |
|
||||
</Box> |
|
||||
)} |
|
||||
</CalculateBalance> |
|
||||
</Wrapper> |
|
||||
) |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
export default AccountCard |
|
@ -0,0 +1,33 @@ |
|||||
|
// @flow
|
||||
|
|
||||
|
import React, { PureComponent } from 'react' |
||||
|
import type { CryptoCurrency } from '@ledgerhq/live-common/lib/types' |
||||
|
import Box from 'components/base/Box' |
||||
|
import Ellipsis from 'components/base/Ellipsis' |
||||
|
import CryptoCurrencyIcon from 'components/CryptoCurrencyIcon' |
||||
|
|
||||
|
class AccountCardHeader extends PureComponent<{ |
||||
|
currency: CryptoCurrency, |
||||
|
accountName: string, |
||||
|
}> { |
||||
|
render() { |
||||
|
const { currency, accountName } = this.props |
||||
|
return ( |
||||
|
<Box horizontal ff="Open Sans|SemiBold" flow={3} alignItems="center"> |
||||
|
<Box alignItems="center" justifyContent="center" style={{ color: currency.color }}> |
||||
|
<CryptoCurrencyIcon currency={currency} size={20} /> |
||||
|
</Box> |
||||
|
<Box grow> |
||||
|
<Box style={{ textTransform: 'uppercase' }} fontSize={0} color="graphite"> |
||||
|
{currency.name} |
||||
|
</Box> |
||||
|
<Ellipsis fontSize={4} color="dark"> |
||||
|
{accountName} |
||||
|
</Ellipsis> |
||||
|
</Box> |
||||
|
</Box> |
||||
|
) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export default AccountCardHeader |
@ -0,0 +1,94 @@ |
|||||
|
// @flow
|
||||
|
|
||||
|
import React, { PureComponent } from 'react' |
||||
|
import styled from 'styled-components' |
||||
|
|
||||
|
import type { Account, Currency } from '@ledgerhq/live-common/lib/types' |
||||
|
|
||||
|
import Chart from 'components/base/Chart' |
||||
|
import Bar from 'components/base/Bar' |
||||
|
import Box, { Card } from 'components/base/Box' |
||||
|
import CalculateBalance from 'components/CalculateBalance' |
||||
|
import FormattedVal from 'components/base/FormattedVal' |
||||
|
import DeltaChange from 'components/DeltaChange' |
||||
|
import AccountCardHeader from './Header' |
||||
|
|
||||
|
const Wrapper = styled(Card).attrs({ |
||||
|
p: 4, |
||||
|
flex: 1, |
||||
|
})` |
||||
|
cursor: ${p => (p.onClick ? 'pointer' : 'default')}; |
||||
|
` |
||||
|
|
||||
|
class AccountCard extends PureComponent<{ |
||||
|
counterValue: Currency, |
||||
|
account: Account, |
||||
|
onClick: Account => void, |
||||
|
daysCount: number, |
||||
|
}> { |
||||
|
renderBody = ({ isAvailable, balanceHistory, balanceStart, balanceEnd }: *) => { |
||||
|
const { counterValue, account } = this.props |
||||
|
return ( |
||||
|
<Box flow={4}> |
||||
|
<Box flow={2} horizontal> |
||||
|
<Box justifyContent="center"> |
||||
|
{isAvailable ? ( |
||||
|
<FormattedVal |
||||
|
animateTicker |
||||
|
unit={counterValue.units[0]} |
||||
|
val={balanceEnd} |
||||
|
alwaysShowSign={false} |
||||
|
showCode |
||||
|
fontSize={3} |
||||
|
color="graphite" |
||||
|
/> |
||||
|
) : null} |
||||
|
</Box> |
||||
|
<Box grow justifyContent="center"> |
||||
|
{isAvailable && !balanceStart.isZero() ? ( |
||||
|
<DeltaChange from={balanceStart} to={balanceEnd} alwaysShowSign fontSize={3} /> |
||||
|
) : null} |
||||
|
</Box> |
||||
|
</Box> |
||||
|
<Chart |
||||
|
data={balanceHistory} |
||||
|
color={account.currency.color} |
||||
|
height={52} |
||||
|
hideAxis |
||||
|
isInteractive={false} |
||||
|
id={`account-chart-${account.id}`} |
||||
|
unit={account.unit} |
||||
|
/> |
||||
|
</Box> |
||||
|
) |
||||
|
} |
||||
|
onClick = () => { |
||||
|
const { account, onClick } = this.props |
||||
|
onClick(account) |
||||
|
} |
||||
|
render() { |
||||
|
const { counterValue, account, onClick, daysCount, ...props } = this.props |
||||
|
return ( |
||||
|
<Wrapper onClick={this.onClick} {...props}> |
||||
|
<Box flow={4}> |
||||
|
<AccountCardHeader accountName={account.name} currency={account.currency} /> |
||||
|
<Bar size={1} color="fog" /> |
||||
|
<Box justifyContent="center"> |
||||
|
<FormattedVal |
||||
|
alwaysShowSign={false} |
||||
|
color="dark" |
||||
|
unit={account.unit} |
||||
|
showCode |
||||
|
val={account.balance} |
||||
|
/> |
||||
|
</Box> |
||||
|
</Box> |
||||
|
<CalculateBalance counterValue={counterValue} accounts={[account]} daysCount={daysCount}> |
||||
|
{this.renderBody} |
||||
|
</CalculateBalance> |
||||
|
</Wrapper> |
||||
|
) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export default AccountCard |
@ -0,0 +1,66 @@ |
|||||
|
// @flow
|
||||
|
|
||||
|
import React, { Component } from 'react' |
||||
|
import type { Account, Currency } from '@ledgerhq/live-common/lib/types' |
||||
|
|
||||
|
import Box from 'components/base/Box' |
||||
|
import AccountCard from './AccountCard' |
||||
|
import AccountCardListHeader from './AccountCardListHeader' |
||||
|
import AccountCardPlaceholder from './AccountCardPlaceholder' |
||||
|
|
||||
|
type Props = { |
||||
|
accounts: Account[], |
||||
|
onAccountClick: Account => void, |
||||
|
counterValue: Currency, |
||||
|
daysCount: number, |
||||
|
} |
||||
|
|
||||
|
class AccountCardList extends Component<Props> { |
||||
|
render() { |
||||
|
const { accounts, counterValue, daysCount, onAccountClick } = this.props |
||||
|
|
||||
|
return ( |
||||
|
<Box flow={4}> |
||||
|
<AccountCardListHeader accountsLength={accounts.length} /> |
||||
|
<Box |
||||
|
horizontal |
||||
|
flexWrap="wrap" |
||||
|
justifyContent="flex-start" |
||||
|
alignItems="center" |
||||
|
style={{ margin: '0 -16px' }} |
||||
|
> |
||||
|
{accounts |
||||
|
.map(account => ({ |
||||
|
key: account.id, |
||||
|
account, |
||||
|
})) |
||||
|
.concat( |
||||
|
Array(3 - (accounts.length % 3)) |
||||
|
.fill(null) |
||||
|
.map((_, i) => ({ |
||||
|
key: `placeholder_${i}`, |
||||
|
withPlaceholder: i === 0, |
||||
|
})), |
||||
|
) |
||||
|
.map(item => ( |
||||
|
<Box key={item.key} flex="33%" p={16}> |
||||
|
{item.account ? ( |
||||
|
<AccountCard |
||||
|
key={item.account.id} |
||||
|
counterValue={counterValue} |
||||
|
account={item.account} |
||||
|
daysCount={daysCount} |
||||
|
onClick={onAccountClick} |
||||
|
/> |
||||
|
) : item.withPlaceholder ? ( |
||||
|
<AccountCardPlaceholder /> |
||||
|
) : null} |
||||
|
</Box> |
||||
|
))} |
||||
|
</Box> |
||||
|
</Box> |
||||
|
) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export default AccountCardList |
@ -0,0 +1,33 @@ |
|||||
|
// @flow
|
||||
|
|
||||
|
import React, { PureComponent } from 'react' |
||||
|
import { translate } from 'react-i18next' |
||||
|
import type { T } from 'types/common' |
||||
|
|
||||
|
import Box from 'components/base/Box' |
||||
|
import Text from 'components/base/Text' |
||||
|
import AccountsOrder from './AccountsOrder' |
||||
|
|
||||
|
type Props = { |
||||
|
t: T, |
||||
|
accountsLength: number, |
||||
|
} |
||||
|
|
||||
|
class AccountCardListHeader extends PureComponent<Props> { |
||||
|
render() { |
||||
|
const { accountsLength, t } = this.props |
||||
|
|
||||
|
return ( |
||||
|
<Box horizontal alignItems="flex-end"> |
||||
|
<Text color="dark" ff="Museo Sans" fontSize={6}> |
||||
|
{t('app:dashboard.accounts.title', { count: accountsLength })} |
||||
|
</Text> |
||||
|
<Box ml="auto" horizontal flow={1}> |
||||
|
<AccountsOrder /> |
||||
|
</Box> |
||||
|
</Box> |
||||
|
) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export default translate()(AccountCardListHeader) |
@ -0,0 +1,64 @@ |
|||||
|
// @flow
|
||||
|
|
||||
|
import React, { PureComponent } from 'react' |
||||
|
import { connect } from 'react-redux' |
||||
|
import { translate } from 'react-i18next' |
||||
|
import styled from 'styled-components' |
||||
|
|
||||
|
import { openModal } from 'reducers/modals' |
||||
|
import { MODAL_ADD_ACCOUNTS } from 'config/constants' |
||||
|
import type { T } from 'types/common' |
||||
|
import { i } from 'helpers/staticPath' |
||||
|
import Box from 'components/base/Box' |
||||
|
import Button from 'components/base/Button' |
||||
|
|
||||
|
const Wrapper = styled(Box).attrs({ |
||||
|
p: 4, |
||||
|
flex: 1, |
||||
|
alignItems: 'center', |
||||
|
})` |
||||
|
border: 1px dashed ${p => p.theme.colors.fog}; |
||||
|
border-radius: 4px; |
||||
|
height: 215px; |
||||
|
` |
||||
|
|
||||
|
class AccountCardPlaceholder extends PureComponent<{ |
||||
|
t: T, |
||||
|
openModal: string => void, |
||||
|
}> { |
||||
|
onAddAccounts = () => this.props.openModal(MODAL_ADD_ACCOUNTS) |
||||
|
|
||||
|
render() { |
||||
|
const { t } = this.props |
||||
|
return ( |
||||
|
<Wrapper> |
||||
|
<Box mt={2}> |
||||
|
<img alt="" src={i('empty-account-tile.svg')} /> |
||||
|
</Box> |
||||
|
<Box |
||||
|
ff="Open Sans" |
||||
|
fontSize={3} |
||||
|
color="grey" |
||||
|
pb={2} |
||||
|
mt={3} |
||||
|
textAlign="center" |
||||
|
style={{ maxWidth: 150 }} |
||||
|
> |
||||
|
{t('app:dashboard.emptyAccountTile.desc')} |
||||
|
</Box> |
||||
|
<Button primary onClick={this.onAddAccounts}> |
||||
|
{t('app:dashboard.emptyAccountTile.createAccount')} |
||||
|
</Button> |
||||
|
</Wrapper> |
||||
|
) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export default translate()( |
||||
|
connect( |
||||
|
null, |
||||
|
{ |
||||
|
openModal, |
||||
|
}, |
||||
|
)(AccountCardPlaceholder), |
||||
|
) |
@ -0,0 +1,31 @@ |
|||||
|
// @flow
|
||||
|
import React, { PureComponent } from 'react' |
||||
|
import { translate } from 'react-i18next' |
||||
|
import type { T } from 'types/common' |
||||
|
|
||||
|
import Text from 'components/base/Text' |
||||
|
|
||||
|
const getCurrentGreetings = () => { |
||||
|
const localTimeHour = new Date().getHours() |
||||
|
const afternoon_breakpoint = 12 |
||||
|
const evening_breakpoint = 17 |
||||
|
if (localTimeHour >= afternoon_breakpoint && localTimeHour < evening_breakpoint) { |
||||
|
return 'app:dashboard.greeting.afternoon' |
||||
|
} else if (localTimeHour >= evening_breakpoint) { |
||||
|
return 'app:dashboard.greeting.evening' |
||||
|
} |
||||
|
return 'app:dashboard.greeting.morning' |
||||
|
} |
||||
|
|
||||
|
class CurrentGettings extends PureComponent<{ t: T }> { |
||||
|
render() { |
||||
|
const { t } = this.props |
||||
|
return ( |
||||
|
<Text color="dark" ff="Museo Sans" fontSize={7}> |
||||
|
{t(getCurrentGreetings())} |
||||
|
</Text> |
||||
|
) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export default translate()(CurrentGettings) |
@ -0,0 +1,22 @@ |
|||||
|
// @flow
|
||||
|
|
||||
|
import React, { PureComponent } from 'react' |
||||
|
import { translate } from 'react-i18next' |
||||
|
import type { T } from 'types/common' |
||||
|
import Text from 'components/base/Text' |
||||
|
|
||||
|
class SummaryDesc extends PureComponent<{ |
||||
|
t: T, |
||||
|
totalAccounts: number, |
||||
|
}> { |
||||
|
render() { |
||||
|
const { totalAccounts, t } = this.props |
||||
|
return ( |
||||
|
<Text color="grey" fontSize={5} ff="Museo Sans|Light"> |
||||
|
{t('app:dashboard.summary', { count: totalAccounts })} |
||||
|
</Text> |
||||
|
) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export default translate()(SummaryDesc) |
@ -0,0 +1,51 @@ |
|||||
|
// @flow
|
||||
|
import { Component } from 'react' |
||||
|
import { connect } from 'react-redux' |
||||
|
import { createStructuredSelector } from 'reselect' |
||||
|
import { getCurrentDevice } from 'reducers/devices' |
||||
|
import debugAppInfosForCurrency from 'commands/debugAppInfosForCurrency' |
||||
|
|
||||
|
class DebugAppInfosForCurrency extends Component< |
||||
|
{ |
||||
|
children?: (?string) => React$Node, |
||||
|
currencyId: string, |
||||
|
device: *, |
||||
|
}, |
||||
|
{ |
||||
|
version: ?string, |
||||
|
}, |
||||
|
> { |
||||
|
state = { |
||||
|
version: null, |
||||
|
} |
||||
|
componentDidMount() { |
||||
|
const { device, currencyId } = this.props |
||||
|
if (device) { |
||||
|
debugAppInfosForCurrency |
||||
|
.send({ currencyId, devicePath: device.path }) |
||||
|
.toPromise() |
||||
|
.then( |
||||
|
({ version }) => { |
||||
|
if (this.unmounted) return |
||||
|
this.setState({ version }) |
||||
|
}, |
||||
|
() => {}, |
||||
|
) |
||||
|
} |
||||
|
} |
||||
|
componentWillUnmount() { |
||||
|
this.unmounted = true |
||||
|
} |
||||
|
unmounted = false |
||||
|
render() { |
||||
|
const { children } = this.props |
||||
|
const { version } = this.state |
||||
|
return children ? children(version) : null |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export default connect( |
||||
|
createStructuredSelector({ |
||||
|
device: getCurrentDevice, |
||||
|
}), |
||||
|
)(DebugAppInfosForCurrency) |
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue