diff --git a/build/background.png b/build/background.png new file mode 100644 index 00000000..f7c32016 Binary files /dev/null and b/build/background.png differ diff --git a/build/background@2x.png b/build/background@2x.png new file mode 100644 index 00000000..0f6577c7 Binary files /dev/null and b/build/background@2x.png differ diff --git a/build/icon.icns b/build/icon.icns deleted file mode 100644 index e5e51d5c..00000000 Binary files a/build/icon.icns and /dev/null differ diff --git a/build/icon.ico b/build/icon.ico deleted file mode 100644 index 802c5397..00000000 Binary files a/build/icon.ico and /dev/null differ diff --git a/build/icon.png b/build/icon.png index 314953dd..e4888528 100644 Binary files a/build/icon.png and b/build/icon.png differ diff --git a/build/icons/512x512.png b/build/icons/512x512.png deleted file mode 100644 index 314953dd..00000000 Binary files a/build/icons/512x512.png and /dev/null differ diff --git a/build/installerSidebar.bmp b/build/installerSidebar.bmp new file mode 100644 index 00000000..57ec352a Binary files /dev/null and b/build/installerSidebar.bmp differ diff --git a/build/uninstallerSidebar.bmp b/build/uninstallerSidebar.bmp new file mode 100644 index 00000000..43760d56 Binary files /dev/null and b/build/uninstallerSidebar.bmp differ diff --git a/src/actions/accounts.js b/src/actions/accounts.js index 99ffe0c6..d5b58660 100644 --- a/src/actions/accounts.js +++ b/src/actions/accounts.js @@ -6,7 +6,7 @@ import db from 'helpers/db' export type AddAccount = Account => * export const addAccount: AddAccount = payload => ({ - type: 'ADD_ACCOUNT', + type: 'DB:ADD_ACCOUNT', payload, }) @@ -48,4 +48,4 @@ export const updateAccount: UpdateAccount = payload => ({ }, }) -export const cleanAccountsCache = () => ({ type: 'CLEAN_ACCOUNTS_CACHE' }) +export const cleanAccountsCache = () => ({ type: 'DB:CLEAN_ACCOUNTS_CACHE' }) diff --git a/src/api/Fees.js b/src/api/Fees.js index 21063c9d..7967f9a7 100644 --- a/src/api/Fees.js +++ b/src/api/Fees.js @@ -1,5 +1,6 @@ // @flow import invariant from 'invariant' +import LRU from 'lru-cache' import type { Currency } from '@ledgerhq/live-common/lib/types' import createCustomErrorClass from 'helpers/createCustomErrorClass' import { blockchainBaseURL } from './Ledger' @@ -11,10 +12,20 @@ export type Fees = { [_: string]: number, } +const cache = LRU({ + maxAge: 5 * 60 * 1000, +}) + export const getEstimatedFees = async (currency: Currency): Promise => { + const key = currency.id + let promise = cache.get(key) + if (promise) return promise.then(r => r.data) const baseURL = blockchainBaseURL(currency) invariant(baseURL, `Fees for ${currency.id} are not supported`) - const { data, status } = await network({ method: 'GET', url: `${baseURL}/fees` }) + promise = network({ method: 'GET', url: `${baseURL}/fees` }) + cache.set(key, promise) + const { data, status } = await promise + if (status < 200 || status >= 300) cache.del(key) if (data) { return data } diff --git a/src/bridge/BridgeSyncContext.js b/src/bridge/BridgeSyncContext.js index 83f71842..dcba6613 100644 --- a/src/bridge/BridgeSyncContext.js +++ b/src/bridge/BridgeSyncContext.js @@ -6,6 +6,7 @@ import invariant from 'invariant' import logger from 'logger' import shuffle from 'lodash/shuffle' +import { timeout } from 'rxjs/operators/timeout' import React, { Component } from 'react' import priorityQueue from 'async/priorityQueue' import { connect } from 'react-redux' @@ -16,7 +17,12 @@ import { setAccountSyncState } from 'actions/bridgeSync' import { bridgeSyncSelector, syncStateLocalSelector } from 'reducers/bridgeSync' import type { BridgeSyncState } from 'reducers/bridgeSync' import { accountsSelector } from 'reducers/accounts' -import { SYNC_BOOT_DELAY, SYNC_ALL_INTERVAL, SYNC_MAX_CONCURRENT } from 'config/constants' +import { + SYNC_BOOT_DELAY, + SYNC_ALL_INTERVAL, + SYNC_MAX_CONCURRENT, + SYNC_TIMEOUT, +} from 'config/constants' import { getBridgeForCurrency } from '.' type BridgeSyncProviderProps = { @@ -73,19 +79,22 @@ class Provider extends Component { this.props.setAccountSyncState(accountId, { pending: true, error: null }) // TODO use Subscription to unsubscribe at relevant time - bridge.synchronize(account).subscribe({ - next: accountUpdater => { - this.props.updateAccountWithUpdater(accountId, accountUpdater) - }, - complete: () => { - this.props.setAccountSyncState(accountId, { pending: false, error: null }) - next() - }, - error: error => { - this.props.setAccountSyncState(accountId, { pending: false, error }) - next() - }, - }) + bridge + .synchronize(account) + .pipe(timeout(SYNC_TIMEOUT)) + .subscribe({ + next: accountUpdater => { + this.props.updateAccountWithUpdater(accountId, accountUpdater) + }, + complete: () => { + this.props.setAccountSyncState(accountId, { pending: false, error: null }) + next() + }, + error: error => { + this.props.setAccountSyncState(accountId, { pending: false, error }) + next() + }, + }) } const syncQueue = priorityQueue(synchronize, SYNC_MAX_CONCURRENT) diff --git a/src/bridge/EthereumJSBridge.js b/src/bridge/EthereumJSBridge.js index 8bf3006e..7bf2854f 100644 --- a/src/bridge/EthereumJSBridge.js +++ b/src/bridge/EthereumJSBridge.js @@ -170,7 +170,7 @@ const EthereumBridge: WalletBridge = { async function stepAddress( index, - { address, path: freshAddressPath }, + { address, path: freshAddressPath, publicKey }, isStandard, ): { account?: Account, complete?: boolean } { const balance = await api.getAccountBalance(address) @@ -181,7 +181,7 @@ const EthereumBridge: WalletBridge = { if (finished) return { complete: true } const freshAddress = address - const accountId = `ethereumjs:${currency.id}:${address}` + const accountId = `ethereumjs:${currency.id}:${address}:${publicKey}` if (txs.length === 0) { // this is an empty account diff --git a/src/bridge/RippleJSBridge.js b/src/bridge/RippleJSBridge.js index a8fb4621..cf1233b5 100644 --- a/src/bridge/RippleJSBridge.js +++ b/src/bridge/RippleJSBridge.js @@ -256,14 +256,15 @@ const RippleJSBridge: WalletBridge = { const derivations = getDerivations(currency) for (const derivation of derivations) { + const legacy = derivation !== derivations[derivations.length - 1] for (let index = 0; index < 255; index++) { const freshAddressPath = derivation({ currency, x: index, segwit: false }) - const { address } = await await getAddress + const { address, publicKey } = await await getAddress .send({ currencyId: currency.id, devicePath: deviceId, path: freshAddressPath }) .toPromise() if (finished) return - const accountId = `ripplejs:${currency.id}:${address}` + const accountId = `ripplejs:${currency.id}:${address}:${publicKey}` let info try { @@ -280,22 +281,24 @@ const RippleJSBridge: WalletBridge = { if (!info) { // account does not exist in Ripple server // we are generating a new account locally - next({ - id: accountId, - xpub: '', - name: getNewAccountPlaceholderName(currency, index), - freshAddress, - freshAddressPath, - balance: 0, - blockHeight: maxLedgerVersion, - index, - currency, - operations: [], - pendingOperations: [], - unit: currency.units[0], - archived: false, - lastSyncDate: new Date(), - }) + if (!legacy) { + next({ + id: accountId, + xpub: '', + name: getNewAccountPlaceholderName(currency, index), + freshAddress, + freshAddressPath, + balance: 0, + blockHeight: maxLedgerVersion, + index, + currency, + operations: [], + pendingOperations: [], + unit: currency.units[0], + archived: false, + lastSyncDate: new Date(), + }) + } break } @@ -315,7 +318,7 @@ const RippleJSBridge: WalletBridge = { const account: $Exact = { id: accountId, xpub: '', - name: getAccountPlaceholderName(currency, index), + name: getAccountPlaceholderName(currency, index, legacy), freshAddress, freshAddressPath, balance, diff --git a/src/commands/getCurrentFirmware.js b/src/commands/getCurrentFirmware.js new file mode 100644 index 00000000..d0dfc329 --- /dev/null +++ b/src/commands/getCurrentFirmware.js @@ -0,0 +1,19 @@ +// @flow + +import { createCommand, Command } from 'helpers/ipc' +import { fromPromise } from 'rxjs/observable/fromPromise' + +import getCurrentFirmware from 'helpers/devices/getCurrentFirmware' + +type Input = { + deviceId: string | number, + version: string, +} + +type Result = * + +const cmd: Command = createCommand('getCurrentFirmware', data => + fromPromise(getCurrentFirmware(data)), +) + +export default cmd diff --git a/src/commands/getFirmwareInfo.js b/src/commands/getFirmwareInfo.js deleted file mode 100644 index dd1bdd42..00000000 --- a/src/commands/getFirmwareInfo.js +++ /dev/null @@ -1,19 +0,0 @@ -// @flow - -import { createCommand, Command } from 'helpers/ipc' -import { fromPromise } from 'rxjs/observable/fromPromise' - -import getFirmwareInfo from 'helpers/devices/getFirmwareInfo' - -type Input = { - targetId: string | number, - version: string, -} - -type Result = * - -const cmd: Command = createCommand('getFirmwareInfo', data => - fromPromise(getFirmwareInfo(data)), -) - -export default cmd diff --git a/src/commands/getIsGenuine.js b/src/commands/getIsGenuine.js index c0b66b8b..c34f4693 100644 --- a/src/commands/getIsGenuine.js +++ b/src/commands/getIsGenuine.js @@ -6,11 +6,19 @@ import { fromPromise } from 'rxjs/observable/fromPromise' import getIsGenuine from 'helpers/devices/getIsGenuine' import { withDevice } from 'helpers/deviceAccess' -type Input = * // FIXME ! +type Input = { + devicePath: string, + targetId: string | number, + version: string, +} type Result = string -const cmd: Command = createCommand('getIsGenuine', ({ devicePath, targetId }) => - fromPromise(withDevice(devicePath)(transport => getIsGenuine(transport, { targetId }))), +const cmd: Command = createCommand( + 'getIsGenuine', + ({ devicePath, targetId, version }) => + fromPromise( + withDevice(devicePath)(transport => getIsGenuine(transport, { targetId, version })), + ), ) export default cmd diff --git a/src/commands/index.js b/src/commands/index.js index e32c6639..7f37b4bb 100644 --- a/src/commands/index.js +++ b/src/commands/index.js @@ -5,7 +5,7 @@ import type { Command } from 'helpers/ipc' import getAddress from 'commands/getAddress' import getDeviceInfo from 'commands/getDeviceInfo' -import getFirmwareInfo from 'commands/getFirmwareInfo' +import getCurrentFirmware from 'commands/getCurrentFirmware' import getIsGenuine from 'commands/getIsGenuine' import getLatestFirmwareForDevice from 'commands/getLatestFirmwareForDevice' import getMemInfo from 'commands/getMemInfo' @@ -32,7 +32,7 @@ import uninstallApp from 'commands/uninstallApp' const all: Array> = [ getAddress, getDeviceInfo, - getFirmwareInfo, + getCurrentFirmware, getIsGenuine, getLatestFirmwareForDevice, getMemInfo, diff --git a/src/commands/installApp.js b/src/commands/installApp.js index c4f1df13..88844ae9 100644 --- a/src/commands/installApp.js +++ b/src/commands/installApp.js @@ -9,14 +9,17 @@ import installApp from 'helpers/apps/installApp' import type { LedgerScriptParams } from 'helpers/common' type Input = { - appParams: LedgerScriptParams, + app: LedgerScriptParams, devicePath: string, + targetId: string | number, } type Result = * -const cmd: Command = createCommand('installApp', ({ devicePath, ...rest }) => - fromPromise(withDevice(devicePath)(transport => installApp(transport, rest))), +const cmd: Command = createCommand( + 'installApp', + ({ devicePath, targetId, ...app }) => + fromPromise(withDevice(devicePath)(transport => installApp(transport, targetId, app))), ) export default cmd diff --git a/src/commands/installMcu.js b/src/commands/installMcu.js index c16387dc..7e49ea16 100644 --- a/src/commands/installMcu.js +++ b/src/commands/installMcu.js @@ -3,24 +3,21 @@ import { createCommand, Command } from 'helpers/ipc' import { fromPromise } from 'rxjs/observable/fromPromise' -// import { withDevice } from 'helpers/deviceAccess' +import { withDevice } from 'helpers/deviceAccess' import installMcu from 'helpers/firmware/installMcu' -// type Input = { -// devicePath: string, -// firmware: Object, -// } +type Input = { + devicePath: string, + targetId: string | number, + version: string, +} -// type Result = { -// targetId: number | string, -// version: string, -// final: boolean, -// mcu: boolean, -// } - -type Input = * type Result = * -const cmd: Command = createCommand('installMcu', () => fromPromise(installMcu())) +const cmd: Command = createCommand( + 'installMcu', + ({ devicePath, targetId, version }) => + fromPromise(withDevice(devicePath)(transport => installMcu(transport, { targetId, version }))), +) export default cmd diff --git a/src/commands/installOsuFirmware.js b/src/commands/installOsuFirmware.js index 416badf0..02767210 100644 --- a/src/commands/installOsuFirmware.js +++ b/src/commands/installOsuFirmware.js @@ -6,17 +6,22 @@ import { fromPromise } from 'rxjs/observable/fromPromise' import { withDevice } from 'helpers/deviceAccess' import installOsuFirmware from 'helpers/firmware/installOsuFirmware' +import type { LedgerScriptParams } from 'helpers/common' + type Input = { devicePath: string, - firmware: Object, + targetId: string | number, + firmware: LedgerScriptParams, } type Result = * const cmd: Command = createCommand( 'installOsuFirmware', - ({ devicePath, firmware }) => - fromPromise(withDevice(devicePath)(transport => installOsuFirmware(transport, firmware))), + ({ devicePath, firmware, targetId }) => + fromPromise( + withDevice(devicePath)(transport => installOsuFirmware(transport, targetId, firmware)), + ), ) export default cmd diff --git a/src/commands/listApps.js b/src/commands/listApps.js index b600aef4..e4d5a153 100644 --- a/src/commands/listApps.js +++ b/src/commands/listApps.js @@ -7,12 +7,13 @@ import listApps from 'helpers/apps/listApps' type Input = { targetId: string | number, + version: string, } type Result = * -const cmd: Command = createCommand('listApps', ({ targetId }) => - fromPromise(listApps(targetId)), +const cmd: Command = createCommand('listApps', ({ targetId, version }) => + fromPromise(listApps(targetId, version)), ) export default cmd diff --git a/src/commands/uninstallApp.js b/src/commands/uninstallApp.js index e8e3b3f7..573439fa 100644 --- a/src/commands/uninstallApp.js +++ b/src/commands/uninstallApp.js @@ -9,14 +9,17 @@ import uninstallApp from 'helpers/apps/uninstallApp' import type { LedgerScriptParams } from 'helpers/common' type Input = { - appParams: LedgerScriptParams, + app: LedgerScriptParams, devicePath: string, + targetId: string | number, } type Result = * -const cmd: Command = createCommand('uninstallApp', ({ devicePath, ...rest }) => - fromPromise(withDevice(devicePath)(transport => uninstallApp(transport, rest))), +const cmd: Command = createCommand( + 'uninstallApp', + ({ devicePath, targetId, ...rest }) => + fromPromise(withDevice(devicePath)(transport => uninstallApp(transport, targetId, rest))), ) export default cmd diff --git a/src/components/AccountPage/index.js b/src/components/AccountPage/index.js index 5aadb530..464fb3a4 100644 --- a/src/components/AccountPage/index.js +++ b/src/components/AccountPage/index.js @@ -129,11 +129,9 @@ class AccountPage extends PureComponent { )} t('app:account.settings.title')}> openModal(MODAL_SETTINGS_ACCOUNT, { account })}> - + + + @@ -148,10 +146,14 @@ class AccountPage extends PureComponent { counterValue={counterValue} daysCount={daysCount} selectedTimeRange={selectedTimeRange} - renderHeader={({ totalBalance, sinceBalance, refBalance }) => ( + renderHeader={({ isAvailable, totalBalance, sinceBalance, refBalance }) => ( - + { { since={selectedTimeRange} /> ( + + + + + + + +) + +export default App diff --git a/src/components/BalanceSummary/BalanceInfos.js b/src/components/BalanceSummary/BalanceInfos.js index fec8e7b6..b2f50a85 100644 --- a/src/components/BalanceSummary/BalanceInfos.js +++ b/src/components/BalanceSummary/BalanceInfos.js @@ -9,6 +9,7 @@ import type { T } from 'types/common' import Box from 'components/base/Box' import FormattedVal from 'components/base/FormattedVal' import DeltaChange from '../DeltaChange' +import { PlaceholderLine } from '../Placeholder' const Sub = styled(Box).attrs({ ff: 'Open Sans', @@ -22,12 +23,14 @@ type BalanceSinceProps = { totalBalance: number, sinceBalance: number, refBalance: number, + isAvailable: boolean, t: T, } type BalanceTotalProps = { children?: any, unit: Unit, + isAvailable: boolean, totalBalance: number, } @@ -36,53 +39,73 @@ type Props = { } & BalanceSinceProps export function BalanceSincePercent(props: BalanceSinceProps) { - const { t, totalBalance, sinceBalance, refBalance, since, ...otherProps } = props + const { t, totalBalance, sinceBalance, refBalance, since, isAvailable, ...otherProps } = props return ( - - {t(`app:time.since.${since}`)} + {!isAvailable ? ( + + ) : ( + + )} + {!isAvailable ? ( + + ) : ( + {t(`app:time.since.${since}`)} + )} ) } export function BalanceSinceDiff(props: Props) { - const { t, totalBalance, sinceBalance, since, counterValue, ...otherProps } = props + const { t, totalBalance, sinceBalance, since, counterValue, isAvailable, ...otherProps } = props return ( - - {t(`app:time.since.${since}`)} + {!isAvailable ? ( + + ) : ( + + )} + {!isAvailable ? ( + + ) : ( + {t(`app:time.since.${since}`)} + )} ) } export function BalanceTotal(props: BalanceTotalProps) { - const { unit, totalBalance, children } = props + const { unit, totalBalance, isAvailable, children } = props return ( - - {children} + {!isAvailable ? ( + + ) : ( + + )} + {!isAvailable ? : children} ) } @@ -93,16 +116,21 @@ BalanceTotal.defaultProps = { } function BalanceInfos(props: Props) { - const { t, totalBalance, since, sinceBalance, refBalance, counterValue } = props + const { t, totalBalance, since, sinceBalance, refBalance, isAvailable, counterValue } = props return ( - + {t('app:dashboard.totalBalance')} *, } @@ -37,54 +38,67 @@ const BalanceSummary = ({ return ( - {({ isAvailable, balanceHistory, balanceStart, balanceEnd }) => - !isAvailable ? null : ( - - {renderHeader ? ( - - {renderHeader({ - selectedTimeRange, - // FIXME refactor these - totalBalance: balanceEnd, - sinceBalance: balanceStart, - refBalance: balanceStart, - })} - - ) : null} - - formatShort(counterValue.units[0], val)} - renderTooltip={ - isAvailable && !account - ? d => ( - - - - {d.date.toISOString().substr(0, 10)} - - - ) - : undefined - } - /> + {({ isAvailable, balanceHistory, balanceStart, balanceEnd }) => ( + + {renderHeader ? ( + + {renderHeader({ + isAvailable, + selectedTimeRange, + // FIXME refactor these + totalBalance: balanceEnd, + sinceBalance: balanceStart, + refBalance: balanceStart, + })} - - ) - } + ) : null} + + ({ + ...i, + value: + 10000 * + (1 + + 0.1 * Math.sin(i.date * Math.cos(i.date)) + // random-ish + 0.5 * Math.cos(i.date / 2000000000 + Math.sin(i.date / 1000000000))), // general curve trend + })) + } + height={200} + currency={counterValue} + tickXScale={selectedTimeRange} + renderTickY={ + isAvailable ? val => formatShort(counterValue.units[0], val) : () => '' + } + isInteractive={isAvailable} + renderTooltip={ + isAvailable && !account + ? d => ( + + + + {d.date.toISOString().substr(0, 10)} + + + ) + : undefined + } + /> + + + )} ) diff --git a/src/components/DashboardPage/index.js b/src/components/DashboardPage/index.js index 4267a429..c92116ce 100644 --- a/src/components/DashboardPage/index.js +++ b/src/components/DashboardPage/index.js @@ -120,10 +120,17 @@ class DashboardPage extends PureComponent { accounts={accounts} selectedTimeRange={selectedTimeRange} daysCount={daysCount} - renderHeader={({ totalBalance, selectedTimeRange, sinceBalance, refBalance }) => ( + renderHeader={({ + isAvailable, + totalBalance, + selectedTimeRange, + sinceBalance, + refBalance, + }) => ( (p.busy ? 0.1 : 0)}; + width: 6px; + height: 6px; + border-radius: 3px; + background-color: black; + position: fixed; + top: 4px; + right: 4px; + z-index: 999; +` + +// NB this is done like this to be extremely performant. we don't want redux for this.. +const perPaths = {} +const instances = [] +export const onSetDeviceBusy = (path, busy) => { + perPaths[path] = busy + instances.forEach(i => i.forceUpdate()) +} + +class DeviceBusyIndicator extends PureComponent<{}> { + componentDidMount() { + instances.push(this) + } + componentWillUnmount() { + const i = instances.indexOf(this) + instances.splice(i, 1) + } + render() { + const busy = Object.values(perPaths).reduce((busy, b) => busy || b, false) + return + } +} + +export default DeviceBusyIndicator diff --git a/src/components/LibcoreBusyIndicator.js b/src/components/LibcoreBusyIndicator.js new file mode 100644 index 00000000..2a13ca9a --- /dev/null +++ b/src/components/LibcoreBusyIndicator.js @@ -0,0 +1,37 @@ +import React, { PureComponent } from 'react' +import styled from 'styled-components' + +const Indicator = styled.div` + opacity: ${p => (p.busy ? 0.1 : 0)}; + width: 6px; + height: 6px; + border-radius: 3px; + background-color: black; + position: fixed; + bottom: 4px; + right: 4px; + z-index: 999; +` + +// NB this is done like this to be extremely performant. we don't want redux for this.. +let busy = false +const instances = [] +export const onSetLibcoreBusy = b => { + busy = b + instances.forEach(i => i.forceUpdate()) +} + +class LibcoreBusyIndicator extends PureComponent<{}> { + componentDidMount() { + instances.push(this) + } + componentWillUnmount() { + const i = instances.indexOf(this) + instances.splice(i, 1) + } + render() { + return + } +} + +export default LibcoreBusyIndicator diff --git a/src/components/ManagerPage/AppSearchBar.js b/src/components/ManagerPage/AppSearchBar.js index 4dcdd81b..e728a91a 100644 --- a/src/components/ManagerPage/AppSearchBar.js +++ b/src/components/ManagerPage/AppSearchBar.js @@ -4,6 +4,8 @@ import styled from 'styled-components' import { color, fontSize, space } from 'styled-system' import fontFamily from 'styles/styled/fontFamily' +import type { LedgerScriptParams } from 'helpers/common' + import { ff } from 'styles/helpers' import Box from 'components/base/Box' @@ -12,20 +14,9 @@ import Search from 'components/base/Search' import SearchIcon from 'icons/Search' import CrossIcon from 'icons/Cross' -type LedgerApp = { - name: string, - version: string, - icon: string, - app: Object, - bolos_version: { - min: number, - max: number, - }, -} - type Props = { - list: Array, - children: (list: Array) => React$Node, + list: Array, + children: (list: Array) => React$Node, } type State = { diff --git a/src/components/ManagerPage/AppsList.js b/src/components/ManagerPage/AppsList.js index 7e07312b..ee87e6a6 100644 --- a/src/components/ManagerPage/AppsList.js +++ b/src/components/ManagerPage/AppsList.js @@ -6,6 +6,7 @@ import styled from 'styled-components' import { translate } from 'react-i18next' import type { Device, T } from 'types/common' +import type { LedgerScriptParams } from 'helpers/common' import listApps from 'commands/listApps' import installApp from 'commands/installApp' @@ -43,27 +44,17 @@ const ICONS_FALLBACK = { type Status = 'loading' | 'idle' | 'busy' | 'success' | 'error' type Mode = 'home' | 'installing' | 'uninstalling' -type LedgerApp = { - name: string, - version: string, - icon: string, - app: Object, - bolos_version: { - min: number, - max: number, - }, -} - type Props = { device: Device, targetId: string | number, t: T, + version: string, } type State = { status: Status, error: string | null, - appsList: LedgerApp[], + appsList: LedgerScriptParams[] | Array<*>, app: string, mode: Mode, } @@ -89,8 +80,8 @@ class AppsList extends PureComponent { async fetchAppList() { try { - const { targetId } = this.props - const appsList = CACHED_APPS || (await listApps.send({ targetId }).toPromise()) + const { targetId, version } = this.props + const appsList = CACHED_APPS || (await listApps.send({ targetId, version }).toPromise()) CACHED_APPS = appsList if (!this._unmounted) { this.setState({ appsList, status: 'idle' }) @@ -100,14 +91,14 @@ class AppsList extends PureComponent { } } - handleInstallApp = (args: { app: any, name: string }) => async () => { - const { app: appParams, name } = args - this.setState({ status: 'busy', app: name, mode: 'installing' }) + handleInstallApp = (app: LedgerScriptParams) => async () => { + this.setState({ status: 'busy', app: app.name, mode: 'installing' }) try { const { device: { path: devicePath }, + targetId, } = this.props - const data = { appParams, devicePath } + const data = { app, devicePath, targetId } await installApp.send(data).toPromise() this.setState({ status: 'success', app: '' }) } catch (err) { @@ -115,14 +106,14 @@ class AppsList extends PureComponent { } } - handleUninstallApp = (args: { app: any, name: string }) => async () => { - const { app: appParams, name } = args - this.setState({ status: 'busy', app: name, mode: 'uninstalling' }) + handleUninstallApp = (app: LedgerScriptParams) => async () => { + this.setState({ status: 'busy', app: app.name, mode: 'uninstalling' }) try { const { device: { path: devicePath }, + targetId, } = this.props - const data = { appParams, devicePath } + const data = { app, devicePath, targetId } await uninstallApp.send(data).toPromise() this.setState({ status: 'success', app: '' }) } catch (err) { @@ -184,15 +175,15 @@ class AppsList extends PureComponent { } renderList() { - const { appsList } = this.state - return appsList.length > 0 ? ( + const { appsList, status } = this.state + return status === 'idle' ? ( {items => ( {items.map(c => ( ( /> - + ) diff --git a/src/components/ManagerPage/FirmwareUpdate.js b/src/components/ManagerPage/FirmwareUpdate.js index 61954fab..a9ab1e2f 100644 --- a/src/components/ManagerPage/FirmwareUpdate.js +++ b/src/components/ManagerPage/FirmwareUpdate.js @@ -10,6 +10,8 @@ import logger from 'logger' import type { Device, T } from 'types/common' +import type { LedgerScriptParams } from 'helpers/common' + import getLatestFirmwareForDevice from 'commands/getLatestFirmwareForDevice' import installOsuFirmware from 'commands/installOsuFirmware' @@ -23,11 +25,6 @@ import UpdateFirmwareButton from './UpdateFirmwareButton' let CACHED_LATEST_FIRMWARE = null -type FirmwareInfos = { - name: string, - notes: string, -} - type DeviceInfos = { targetId: number | string, version: string, @@ -40,7 +37,7 @@ type Props = { } type State = { - latestFirmware: ?FirmwareInfos, + latestFirmware: ?LedgerScriptParams, } class FirmwareUpdate extends PureComponent { @@ -84,12 +81,13 @@ class FirmwareUpdate extends PureComponent { installFirmware = async () => { try { const { latestFirmware } = this.state + const { infos } = this.props invariant(latestFirmware, 'did not find a new firmware or firmware is not set') const { device: { path: devicePath }, } = this.props const { success } = await installOsuFirmware - .send({ devicePath, firmware: latestFirmware }) + .send({ devicePath, firmware: latestFirmware, targetId: infos.targetId }) .toPromise() if (success) { this.fetchLatestFirmware() @@ -119,7 +117,9 @@ class FirmwareUpdate extends PureComponent { - {t('app:manager.firmware.installed', { version: infos.version })} + {t('app:manager.firmware.installed', { + version: infos.version, + })} diff --git a/src/components/ManagerPage/FlashMcu.js b/src/components/ManagerPage/FlashMcu.js index e69de29b..5edeacd4 100644 --- a/src/components/ManagerPage/FlashMcu.js +++ b/src/components/ManagerPage/FlashMcu.js @@ -0,0 +1,55 @@ +// @flow +import React, { PureComponent } from 'react' + +import type { Device } from 'types/common' +import installMcu from 'commands/installMcu' + +type DeviceInfo = { + targetId: number | string, + version: string, + final: boolean, + mcu: boolean, +} + +type Props = { + device: Device, + deviceInfo: DeviceInfo, +} + +type State = { + flashing: boolean, +} + +class FlashMcu extends PureComponent { + state = { + flashing: false, + } + + flashMCU = async () => { + const { device, deviceInfo } = this.props + const { flashing } = this.state + + if (!flashing) { + this.setState({ flashing: true }) + await installMcu + .send({ + devicePath: device.path, + targetId: deviceInfo.targetId, + version: deviceInfo.version, + }) + .toPromise() + this.setState({ flashing: false }) + } + } + + render() { + return ( +
+

Flashing MCU

+ +
+ ) + } +} + +export default FlashMcu diff --git a/src/components/ManagerPage/UpdateFirmwareButton.js b/src/components/ManagerPage/UpdateFirmwareButton.js index 5feea0e3..f5b4c562 100644 --- a/src/components/ManagerPage/UpdateFirmwareButton.js +++ b/src/components/ManagerPage/UpdateFirmwareButton.js @@ -18,11 +18,14 @@ type Props = { installFirmware: () => void, } +const getCleanVersion = (input: string): string => + input.endsWith('-osu') ? input.replace('-osu', '') : input + const UpdateFirmwareButton = ({ t, firmware, installFirmware }: Props) => firmware ? ( - {t('app:manager.firmware.latest', { version: firmware.name })} + {t('app:manager.firmware.latest', { version: getCleanVersion(firmware.name) })} + + + + + + ) : ( + + )} + { return ( - {t('onboarding:writeSeed.blue.title')} - {t('onboarding:writeSeed.blue.desc')} + {t('onboarding:writeSeed.title')} + {t('onboarding:writeSeed.desc')} diff --git a/src/components/Onboarding/steps/WriteSeed/WriteSeedNano.js b/src/components/Onboarding/steps/WriteSeed/WriteSeedNano.js index d1e76f4d..2a412a31 100644 --- a/src/components/Onboarding/steps/WriteSeed/WriteSeedNano.js +++ b/src/components/Onboarding/steps/WriteSeed/WriteSeedNano.js @@ -69,8 +69,8 @@ class WriteSeedNano extends PureComponent { return ( - {t('onboarding:writeSeed.nano.title')} - {t('onboarding:writeSeed.nano.desc')} + {t('onboarding:writeSeed.title')} + {t('onboarding:writeSeed.desc')} diff --git a/src/components/OperationsList/index.js b/src/components/OperationsList/index.js index 92aacc4d..6fdcece6 100644 --- a/src/components/OperationsList/index.js +++ b/src/components/OperationsList/index.js @@ -119,7 +119,7 @@ export class OperationsList extends PureComponent { (p.dark ? '#C2C2C2' : '#D6D6D6')}; + width: ${p => p.width}px; + height: 10px; + border-radius: 5px; + margin: 5px 0; +` diff --git a/src/components/RenderError.js b/src/components/RenderError.js new file mode 100644 index 00000000..1ffa1b80 --- /dev/null +++ b/src/components/RenderError.js @@ -0,0 +1,131 @@ +// @flow + +import React, { PureComponent } from 'react' +import { shell, remote } from 'electron' +import qs from 'querystring' +import { translate } from 'react-i18next' + +import { i } from 'helpers/staticPath' +import hardReset from 'helpers/hardReset' + +import type { T } from 'types/common' + +import Spoiler from 'components/base/Spoiler' +import ExportLogsBtn from 'components/ExportLogsBtn' +import Box from 'components/base/Box' +import Space from 'components/base/Space' +import Button from 'components/base/Button' +import TranslatedError from './TranslatedError' + +type Props = { + error: Error, + t: T, + disableExport?: boolean, + children?: *, +} + +class RenderError extends PureComponent { + state = { + isHardResetting: false, + } + + handleCreateIssue = () => { + const { error } = this.props + if (!error) { + return + } + const q = qs.stringify({ + title: `Error: ${error.message}`, + body: `Error was thrown: + +\`\`\` +${error.stack} +\`\`\` +`, + }) + shell.openExternal(`https://github.com/LedgerHQ/ledger-live-desktop/issues/new?${q}`) + } + + handleRestart = () => { + remote.getCurrentWindow().webContents.reloadIgnoringCache() + } + + handleHardReset = async () => { + this.setState({ isHardResetting: true }) + try { + await hardReset() + remote.getCurrentWindow().webContents.reloadIgnoringCache() + } catch (err) { + this.setState({ isHardResetting: false }) + } + } + + render() { + const { error, t, disableExport, children } = this.props + const { isHardResetting } = this.state + return ( + + + + + + {t('app:crash.oops')} + + + + {t('app:crash.uselessText')} + + + + + {!disableExport ? : null} + + + + + + + + + + + + {error.stack} + + + {children} + + ) + } +} + +const ErrContainer = ({ children }: { children: any }) => ( +
+    {children}
+  
+) + +export default translate()(RenderError) diff --git a/src/components/SelectAccount/index.js b/src/components/SelectAccount/index.js index 3c1fe001..7e683cd9 100644 --- a/src/components/SelectAccount/index.js +++ b/src/components/SelectAccount/index.js @@ -69,6 +69,9 @@ const RawSelectAccount = ({ accounts, onChange, value, t, ...props }: Props) => renderValue={renderOption} renderOption={renderOption} placeholder={t('app:common.selectAccount')} + noOptionsMessage={({ inputValue }) => + t('app:common.selectAccountNoOption', { accountName: inputValue }) + } onChange={onChange} /> ) diff --git a/src/components/SelectCurrency/index.js b/src/components/SelectCurrency/index.js index 3d23dd91..6c7d9a54 100644 --- a/src/components/SelectCurrency/index.js +++ b/src/components/SelectCurrency/index.js @@ -40,6 +40,9 @@ const SelectCurrency = ({ onChange, value, t, placeholder, currencies, ...props renderValue={renderOption} options={options} placeholder={placeholder || t('app:common.selectCurrency')} + noOptionsMessage={({ inputValue }: { inputValue: string }) => + t('app:common.selectCurrencyNoOption', { currencyName: inputValue }) + } onChange={item => onChange(item ? item.currency : null)} {...props} /> diff --git a/src/components/SelectExchange.js b/src/components/SelectExchange.js index 168d509e..7872e598 100644 --- a/src/components/SelectExchange.js +++ b/src/components/SelectExchange.js @@ -1,6 +1,7 @@ // @flow import React, { Component } from 'react' import { translate } from 'react-i18next' +import LRU from 'lru-cache' import type { Currency } from '@ledgerhq/live-common/lib/types' import type { Exchange } from '@ledgerhq/live-common/lib/countervalues/types' import logger from 'logger' @@ -10,6 +11,18 @@ import Text from 'components/base/Text' import CounterValues from 'helpers/countervalues' import type { T } from 'types/common' +const cache = LRU({ max: 100 }) + +const getExchanges = (from: Currency, to: Currency) => { + const key = `${from.ticker}_${to.ticker}` + let promise = cache.get(key) + if (promise) return promise + promise = CounterValues.fetchExchangesForPair(from, to) + promise.catch(() => cache.del(key)) // if it's a failure, we don't want to keep the cache + cache.set(key, promise) + return promise +} + class SelectExchange extends Component< { from: Currency, @@ -65,7 +78,7 @@ class SelectExchange extends Component< const { _loadId } = this const { from, to } = this.props try { - const exchanges = await CounterValues.fetchExchangesForPair(from, to) + const exchanges = await getExchanges(from, to) if (!this._unmounted && this._loadId === _loadId) { this.setState({ exchanges }) } @@ -93,6 +106,10 @@ class SelectExchange extends Component< options={options} onChange={onChange} isLoading={options.length === 0} + placeholder={t('app:common.selectExchange')} + noOptionsMessage={({ inputValue }) => + t('app:common.selectExchangeNoOption', { exchangeName: inputValue }) + } {...props} /> ) diff --git a/src/components/SettingsPage/sections/Display.js b/src/components/SettingsPage/sections/Display.js index 1b3bd457..0a19b62a 100644 --- a/src/components/SettingsPage/sections/Display.js +++ b/src/components/SettingsPage/sections/Display.js @@ -169,7 +169,7 @@ class TabProfile extends PureComponent { to={counterValueCurrency} exchangeId={counterValueExchange} onChange={this.handleChangeExchange} - minWidth={150} + minWidth={200} />
diff --git a/src/components/SyncAgo.js b/src/components/SyncAgo.js new file mode 100644 index 00000000..320732cf --- /dev/null +++ b/src/components/SyncAgo.js @@ -0,0 +1,16 @@ +// @flow + +import React, { PureComponent } from 'react' +import moment from 'moment' +import { translate } from 'react-i18next' +import type { T } from 'types/common' +import Box from './base/Box' + +class SyncAgo extends PureComponent<{ t: T, date: Date }> { + render() { + const { t, date } = this.props + return {t('app:common.sync.ago', { time: moment(date).fromNow() })} + } +} + +export default translate()(SyncAgo) diff --git a/src/components/ThrowBlock.js b/src/components/ThrowBlock.js index 4ca6a52e..89b0d497 100644 --- a/src/components/ThrowBlock.js +++ b/src/components/ThrowBlock.js @@ -1,49 +1,16 @@ // @flow - import logger from 'logger' import React, { PureComponent } from 'react' -import styled from 'styled-components' -import { shell, remote } from 'electron' -import qs from 'querystring' -import { translate } from 'react-i18next' - -import { rgba } from 'styles/helpers' -import db from 'helpers/db' - -import type { T } from 'types/common' - -import ExportLogsBtn from 'components/ExportLogsBtn' -import Box from 'components/base/Box' -import Button from 'components/base/Button' -import TranslatedError from './TranslatedError' +import RenderError from 'components/RenderError' type Props = { children: any, - t: T, } type State = { error: ?Error, } -const Container = styled(Box).attrs({ - grow: true, - align: 'center', - justify: 'center', - bg: 'lightGraphite', - color: 'alertRed', - ff: 'Museo Sans|Bold', - flow: 2, -})`` - -const Inner = styled(Box).attrs({ - p: 2, - bg: p => rgba(p.theme.colors.alertRed, 0.05), - borderRadius: 1, -})` - border: ${p => `1px solid ${rgba(p.theme.colors.alertRed, 0.1)}`}; -` - class ThrowBlock extends PureComponent { state = { error: null, @@ -54,59 +21,13 @@ class ThrowBlock extends PureComponent { this.setState({ error }) } - handleCreateIssue = () => { - const { error } = this.state - if (!error) { - return - } - const q = qs.stringify({ - title: `Error: ${error.message}`, - body: `Error was thrown: - -\`\`\` -${error.stack} -\`\`\` -`, - }) - shell.openExternal(`https://github.com/LedgerHQ/ledger-live-desktop/issues/new?${q}`) - } - - handleRestart = () => { - remote.app.relaunch() - remote.app.exit() - } - - handleReset = () => { - db.resetAll() - this.handleRestart() - } - render() { const { error } = this.state - const { t } = this.props if (error) { - return ( - - - - - - - - - - - - ) + return } return this.props.children } } -export default translate()(ThrowBlock) +export default ThrowBlock diff --git a/src/components/TopBar/ActivityIndicator.js b/src/components/TopBar/ActivityIndicator.js index a2c9ff31..fb7b1e58 100644 --- a/src/components/TopBar/ActivityIndicator.js +++ b/src/components/TopBar/ActivityIndicator.js @@ -10,7 +10,6 @@ import type { T } from 'types/common' import type { AsyncState } from 'reducers/bridgeSync' import { globalSyncStateSelector } from 'reducers/bridgeSync' -import { hasAccountsSelector } from 'reducers/accounts' import { BridgeSyncConsumer } from 'bridge/BridgeSyncContext' import CounterValues from 'helpers/countervalues' @@ -23,7 +22,6 @@ import ItemContainer from './ItemContainer' const mapStateToProps = createStructuredSelector({ globalSyncState: globalSyncStateSelector, - hasAccounts: hasAccountsSelector, }) type Props = { @@ -128,37 +126,28 @@ class ActivityIndicatorInner extends PureComponent { } } -const ActivityIndicator = ({ - globalSyncState, - hasAccounts, - t, -}: { - globalSyncState: AsyncState, - hasAccounts: boolean, - t: T, -}) => - !hasAccounts ? null : ( - - {setSyncBehavior => ( - - {cvPolling => { - const isPending = cvPolling.pending || globalSyncState.pending - const isError = cvPolling.error || globalSyncState.error - return ( - - ) - }} - - )} - - ) +const ActivityIndicator = ({ globalSyncState, t }: { globalSyncState: AsyncState, t: T }) => ( + + {setSyncBehavior => ( + + {cvPolling => { + const isPending = cvPolling.pending || globalSyncState.pending + const isError = cvPolling.error || globalSyncState.error + return ( + + ) + }} + + )} + +) export default compose( translate(), diff --git a/src/components/TopBar/index.js b/src/components/TopBar/index.js index 11b4bd1c..13d88557 100644 --- a/src/components/TopBar/index.js +++ b/src/components/TopBar/index.js @@ -12,6 +12,7 @@ import type { T } from 'types/common' import { lock } from 'reducers/application' import { hasPassword } from 'reducers/settings' +import { hasAccountsSelector } from 'reducers/accounts' import { openModal } from 'reducers/modals' import IconLock from 'icons/Lock' @@ -54,6 +55,7 @@ const Bar = styled.div` const mapStateToProps = state => ({ hasPassword: hasPassword(state), + hasAccounts: hasAccountsSelector(state), }) const mapDispatchToProps = { @@ -63,6 +65,7 @@ const mapDispatchToProps = { type Props = { hasPassword: boolean, + hasAccounts: boolean, history: RouterHistory, location: Location, lock: Function, @@ -91,17 +94,21 @@ class TopBar extends PureComponent { } } render() { - const { hasPassword, t } = this.props + const { hasPassword, hasAccounts, t } = this.props return ( - - - - + {hasAccounts && ( + + + + + + + )} t('app:settings.title')}> diff --git a/src/components/TranslatedError.js b/src/components/TranslatedError.js index 7cb125ca..ff2ec07d 100644 --- a/src/components/TranslatedError.js +++ b/src/components/TranslatedError.js @@ -4,6 +4,7 @@ // - an error can have parameters, to use them, just use field of the Error object, that's what we give to `t()` // - returned value is intentially not styled (is universal). wrap this in whatever you need +import logger from 'logger' import { PureComponent } from 'react' import { translate } from 'react-i18next' import type { T } from 'types/common' @@ -18,7 +19,12 @@ class TranslatedError extends PureComponent { const { t, error } = this.props if (!error) return null if (typeof error === 'string') return error - return t(`errors:${error.name}`, error) + const translation = t(`errors:${error.name}`, error) + if (translation) { + return translation + } + logger.warn(`TranslatedError: no translation for '${error.name}'`, error) + return error.message || error.name || t('errors:generic') } } diff --git a/src/components/TriggerAppReady.js b/src/components/TriggerAppReady.js new file mode 100644 index 00000000..e47f2ed2 --- /dev/null +++ b/src/components/TriggerAppReady.js @@ -0,0 +1,16 @@ +// @flow + +import { PureComponent } from 'react' + +export default class TriggerAppReady extends PureComponent<{}> { + componentDidMount() { + window.requestAnimationFrame(() => (this._timeout = setTimeout(() => window.onAppReady(), 300))) + } + componentWillUnmount() { + clearTimeout(this._timeout) + } + _timeout: * + render() { + return null + } +} diff --git a/src/components/TriggerOnMount/index.js b/src/components/TriggerOnMount/index.js deleted file mode 100644 index 36923b0e..00000000 --- a/src/components/TriggerOnMount/index.js +++ /dev/null @@ -1,15 +0,0 @@ -// @flow -import { PureComponent } from 'react' - -type Props = { - callback: () => void, -} - -class TriggerOnMount extends PureComponent { - componentDidMount() { - const { callback } = this.props - callback() - } -} - -export default TriggerOnMount diff --git a/src/components/Workflow/EnsureGenuine.js b/src/components/Workflow/EnsureGenuine.js index 831d60c8..a1890480 100644 --- a/src/components/Workflow/EnsureGenuine.js +++ b/src/components/Workflow/EnsureGenuine.js @@ -1,7 +1,9 @@ // @flow +import { timeout } from 'rxjs/operators/timeout' import { PureComponent } from 'react' import isEqual from 'lodash/isEqual' +import { GENUINE_TIMEOUT } from 'config/constants' import type { Device } from 'types/common' import getIsGenuine from 'commands/getIsGenuine' @@ -59,7 +61,8 @@ class EnsureGenuine extends PureComponent { this._checking = true try { const res = await getIsGenuine - .send({ devicePath: device.path, targetId: infos.targetId }) + .send({ devicePath: device.path, targetId: infos.targetId, version: infos.version }) + .pipe(timeout(GENUINE_TIMEOUT)) .toPromise() if (this._unmounting) return const isGenuine = res === '0000' diff --git a/src/components/Workflow/index.js b/src/components/Workflow/index.js index 9c5edc0a..e3db974f 100644 --- a/src/components/Workflow/index.js +++ b/src/components/Workflow/index.js @@ -31,9 +31,10 @@ type Props = { genuineError: ?Error, }, ) => Node, - renderMcuUpdate?: (deviceInfo: DeviceInfo) => Node, - renderFinalUpdate?: (deviceInfo: DeviceInfo) => Node, + renderMcuUpdate?: (device: Device, deviceInfo: DeviceInfo) => Node, + renderFinalUpdate?: (device: Device, deviceInfo: DeviceInfo) => Node, renderDashboard?: (device: Device, deviceInfo: DeviceInfo, isGenuine: boolean) => Node, + onGenuineCheck?: (isGenuine: boolean) => void, renderError?: (dashboardError: ?Error, genuineError: ?Error) => Node, } type State = {} @@ -47,42 +48,47 @@ class Workflow extends PureComponent { renderMcuUpdate, renderError, renderDefault, + onGenuineCheck, } = this.props return ( {(device: Device) => ( - {(deviceInfo: ?DeviceInfo, dashboardError: ?Error) => ( - - {(isGenuine: ?boolean, genuineError: ?Error) => { - if (dashboardError || genuineError) { - return renderError - ? renderError(dashboardError, genuineError) - : renderDefault(device, deviceInfo, isGenuine, { - genuineError, - dashboardError, - }) - } + {(deviceInfo: ?DeviceInfo, dashboardError: ?Error) => { + if (deviceInfo && deviceInfo.mcu && renderMcuUpdate) { + return renderMcuUpdate(device, deviceInfo) + } - if (deviceInfo && deviceInfo.mcu && renderMcuUpdate) { - return renderMcuUpdate(deviceInfo) - } + if (deviceInfo && deviceInfo.final && renderFinalUpdate) { + return renderFinalUpdate(device, deviceInfo) + } - if (deviceInfo && deviceInfo.final && renderFinalUpdate) { - return renderFinalUpdate(deviceInfo) - } + return ( + + {(isGenuine: ?boolean, genuineError: ?Error) => { + if (dashboardError || genuineError) { + return renderError + ? renderError(dashboardError, genuineError) + : renderDefault(device, deviceInfo, isGenuine, { + genuineError, + dashboardError, + }) + } - if (isGenuine && deviceInfo && device && !dashboardError && !genuineError) { - if (renderDashboard) return renderDashboard(device, deviceInfo, isGenuine) - } + if (isGenuine && deviceInfo && device && !dashboardError && !genuineError) { + if (onGenuineCheck) onGenuineCheck(isGenuine) - return renderDefault(device, deviceInfo, isGenuine, { - genuineError, - dashboardError, - }) - }} - - )} + if (renderDashboard) return renderDashboard(device, deviceInfo, isGenuine) + } + + return renderDefault(device, deviceInfo, isGenuine, { + genuineError, + dashboardError, + }) + }} + + ) + }} )} diff --git a/src/components/base/Button/index.js b/src/components/base/Button/index.js index dc7f2515..8953eb31 100644 --- a/src/components/base/Button/index.js +++ b/src/components/base/Button/index.js @@ -16,8 +16,12 @@ type Style = any // FIXME const buttonStyles: { [_: string]: Style } = { default: { default: noop, - active: noop, - hover: noop, + active: p => ` + background: ${rgba(p.theme.colors.fog, 0.3)}; + `, + hover: p => ` + background: ${rgba(p.theme.colors.fog, 0.2)}; + `, focus: () => ` box-shadow: ${focusedShadowStyle}; `, diff --git a/src/components/base/Modal/ModalTitle.js b/src/components/base/Modal/ModalTitle.js index c4f4c7ed..cc812853 100644 --- a/src/components/base/Modal/ModalTitle.js +++ b/src/components/base/Modal/ModalTitle.js @@ -30,6 +30,7 @@ const Back = styled(Box).attrs({ })` cursor: pointer; position: absolute; + line-height: 1; top: 0; left: 0; diff --git a/src/components/base/Spoiler/index.js b/src/components/base/Spoiler/index.js index 4cb21c01..1810223f 100644 --- a/src/components/base/Spoiler/index.js +++ b/src/components/base/Spoiler/index.js @@ -17,12 +17,12 @@ type State = { } const Title = styled(Text).attrs({ - ff: 'Museo Sans|Bold', - fontSize: 2, - color: 'dark', + ff: p => (p.ff ? p.ff : 'Museo Sans|Bold'), + fontSize: p => (p.fontSize ? p.fontSize : 2), + color: p => (p.color ? p.color : 'dark'), tabIndex: 0, })` - text-transform: uppercase; + text-transform: ${p => (!p.textTransform ? 'auto' : 'uppercase')}; letter-spacing: 1px; cursor: pointer; outline: none; @@ -41,15 +41,17 @@ class Spoiler extends PureComponent { toggle = () => this.setState({ isOpened: !this.state.isOpened }) render() { - const { title, children } = this.props + const { title, children, ...p } = this.props const { isOpened } = this.state return ( - + - {title} + + {title} + {isOpened && children} diff --git a/src/components/layout/Default.js b/src/components/layout/Default.js index e7286fa0..c107392e 100644 --- a/src/components/layout/Default.js +++ b/src/components/layout/Default.js @@ -17,6 +17,9 @@ import DashboardPage from 'components/DashboardPage' import ManagerPage from 'components/ManagerPage' import ExchangePage from 'components/ExchangePage' import SettingsPage from 'components/SettingsPage' +import LibcoreBusyIndicator from 'components/LibcoreBusyIndicator' +import DeviceBusyIndicator from 'components/DeviceBusyIndicator' +import TriggerAppReady from 'components/TriggerAppReady' import AppRegionDrag from 'components/AppRegionDrag' import IsUnlocked from 'components/IsUnlocked' @@ -39,7 +42,6 @@ type Props = { class Default extends Component { componentDidMount() { - window.requestAnimationFrame(() => (this._timeout = setTimeout(() => window.onAppReady(), 300))) window.addEventListener('keydown', this.kbShortcut) } @@ -57,7 +59,6 @@ class Default extends Component { } componentWillUnmount() { - clearTimeout(this._timeout) window.removeEventListener('keydown', this.kbShortcut) // Prevents adding multiple listeners when hot reloading } @@ -67,12 +68,12 @@ class Default extends Component { } } - _timeout = undefined _scrollContainer = null render() { return ( + {process.platform === 'darwin' && } @@ -96,6 +97,9 @@ class Default extends Component {
+ + +
) diff --git a/src/components/modals/AccountSettingRenderBody.js b/src/components/modals/AccountSettingRenderBody.js index ee8594e8..c4aef19d 100644 --- a/src/components/modals/AccountSettingRenderBody.js +++ b/src/components/modals/AccountSettingRenderBody.js @@ -22,6 +22,7 @@ import Box from 'components/base/Box' import Button from 'components/base/Button' import Input from 'components/base/Input' import Select from 'components/base/Select' +import SyncAgo from 'components/SyncAgo' import { ModalBody, @@ -258,12 +259,12 @@ class HelperComp extends PureComponent { ) : null} +