diff --git a/README.md b/README.md index 67a2b924..8e1052f5 100644 --- a/README.md +++ b/README.md @@ -1,32 +1,38 @@ -### requirements +# Ledger Wallet Desktop -#### Linux +## Requirements -``` -libicns -graphicsmagick -``` +* nodejs v8.x (https://nodejs.org/en/) +* yarn latest (https://yarnpkg.com/fr/docs/install) -install dependencies +## Installation ``` yarn ``` -launch development version +## Development ``` yarn start ``` -build, but not package for distribution +## Build + +> Not package for distribution ``` yarn dist:dir ``` -build and package everything +> Package everything ``` yarn dist ``` + +## Release + +``` +yarn release +``` diff --git a/package.json b/package.json index bd4cee76..ec7f6725 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "ledger-wallet-desktop", "description": "Ledger Wallet Desktop", - "repository": "https://github.com/meriadec/ledger-wallet-desktop-releases-DEV", + "repository": "https://github.com/LedgerHQ/ledger-wallet-desktop", "version": "0.1.0", "author": "Ledger", "license": "BSD-2-Clause", diff --git a/src/actions/devices.js b/src/actions/devices.js index c5af4ad5..a30ecede 100644 --- a/src/actions/devices.js +++ b/src/actions/devices.js @@ -1,29 +1,29 @@ // @flow -// eslint-disable import/prefer-default-export +/* eslint-disable import/prefer-default-export */ import type { Device, Devices } from 'types/common' -export type deviceChooseType = (Device | null) => { type: string, payload: Device | null } -export const deviceChoose: deviceChooseType = payload => ({ - type: 'DEVICE_CHOOSE', +export type SetCurrentDevice = (Device | null) => { type: string, payload: Device | null } +export const setCurrentDevice: SetCurrentDevice = payload => ({ + type: 'SET_CURRENT_DEVICE', payload, }) -type devicesAddType = Device => { type: string, payload: Device } -export const deviceAdd: devicesAddType = payload => ({ - type: 'DEVICE_ADD', +type AddDevice = Device => { type: string, payload: Device } +export const addDevice: AddDevice = payload => ({ + type: 'ADD_DEVICE', payload, }) -type devicesRemoveType = Device => { type: string, payload: Device } -export const deviceRemove: devicesRemoveType = payload => ({ - type: 'DEVICE_REMOVE', +type RemoveDevice = Device => { type: string, payload: Device } +export const removeDevice: RemoveDevice = payload => ({ + type: 'REMOVE_DEVICE', payload, }) -type devicesUpdateType = Devices => { type: string, payload: Devices } -export const devicesUpdate: devicesUpdateType = payload => ({ - type: 'DEVICES_UPDATE', +type UpdateDevices = Devices => { type: string, payload: Devices } +export const updateDevices: UpdateDevices = payload => ({ + type: 'UPDATE_DEVICES', payload, }) diff --git a/src/actions/wallets.js b/src/actions/wallets.js new file mode 100644 index 00000000..c46603bf --- /dev/null +++ b/src/actions/wallets.js @@ -0,0 +1,9 @@ +// @flow + +/* eslint-disable import/prefer-default-export */ + +export type SetCurrentWalletType = (Object | null) => { type: string, payload: Object | null } +export const setCurrentWallet: SetCurrentWalletType = payload => ({ + type: 'SET_CURRENT_WALLET', + payload, +}) diff --git a/src/components/AccountPage.js b/src/components/AccountPage.js index 50b14595..22d44551 100644 --- a/src/components/AccountPage.js +++ b/src/components/AccountPage.js @@ -1,15 +1,73 @@ // @flow import React, { PureComponent } from 'react' +import { connect } from 'react-redux' + +import type { MapStateToProps } from 'react-redux' +import type { Device } from 'types/common' + +import { getCurrentDevice } from 'reducers/devices' + +import { sendEvent } from 'renderer/events' import Box from 'components/base/Box' -type Props = {} +const mapStateToProps: MapStateToProps<*, *, *> = state => ({ + currentDevice: getCurrentDevice(state), + currentWallet: state.wallets.currentWallet, +}) + +type Props = { + currentDevice: Device | null, + currentWallet: Object | null, +} +type State = { + address: Object | null, +} + +class AccountPage extends PureComponent { + state = { + address: null, + } + + componentDidMount() { + this.getWalletInfos() + } + + componentWillReceiveProps(nextProps) { + const { currentWallet } = nextProps + + if (currentWallet !== null && !currentWallet.err) { + this.setState({ + address: currentWallet.data.bitcoinAddress, + }) + } else { + this._timeout = setTimeout(() => this.getWalletInfos(), 2e3) + } + } + + componentWillUnmount() { + clearTimeout(this._timeout) + } + + getWalletInfos() { + const { currentDevice } = this.props + + if (currentDevice !== null) { + sendEvent('usb', 'wallet.infos.request', { + path: currentDevice.path, + wallet: 'btc', + }) + } + } + + _timeout = undefined -class AccountPage extends PureComponent { render() { - return {'account'} + const { address } = this.state + + return {address === null ? 'Select Bitcoin App on your Ledger' : address} } } -export default AccountPage +export default connect(mapStateToProps)(AccountPage) diff --git a/src/components/AppRegionDrag.js b/src/components/AppRegionDrag.js new file mode 100644 index 00000000..37f0a504 --- /dev/null +++ b/src/components/AppRegionDrag.js @@ -0,0 +1,12 @@ +// @flow + +import styled from 'styled-components' + +export default styled.div` + -webkit-app-region: drag; + height: 40px; + left: 0; + position: absolute; + right: 0; + top: 0; +` diff --git a/src/components/SideBar/index.js b/src/components/SideBar/index.js index 97998fb6..6009cb0e 100644 --- a/src/components/SideBar/index.js +++ b/src/components/SideBar/index.js @@ -17,18 +17,19 @@ const CapsSubtitle = styled(Box).attrs({ font-weight: bold; ` -const Container = styled(GrowScroll).attrs({ - flow: 4, - py: 4, +const Container = styled(Box).attrs({ + noShrink: true, })` background-color: ${p => rgba(p.theme.colors[p.bg], 0.4)}; + padding-top: 40px; + width: 250px; ` class SideBar extends PureComponent<{}> { render() { return ( - - + + {'Menu'}
@@ -55,8 +56,8 @@ class SideBar extends PureComponent<{}> {
-
-
+ + ) } } diff --git a/src/components/TopBar.js b/src/components/TopBar.js index 80ad0f9b..581e86aa 100644 --- a/src/components/TopBar.js +++ b/src/components/TopBar.js @@ -5,11 +5,11 @@ import { connect } from 'react-redux' import type { MapStateToProps, MapDispatchToProps } from 'react-redux' import type { Device, Devices } from 'types/common' -import type { deviceChooseType } from 'actions/devices' +import type { SetCurrentDevice } from 'actions/devices' import { getDevices, getCurrentDevice } from 'reducers/devices' -import { deviceChoose } from 'actions/devices' +import { setCurrentDevice } from 'actions/devices' import Box from 'components/base/Box' import Overlay from 'components/base/Overlay' @@ -20,13 +20,13 @@ const mapStateToProps: MapStateToProps<*, *, *> = state => ({ }) const mapDispatchToProps: MapDispatchToProps<*, *, *> = { - deviceChoose, + setCurrentDevice, } type Props = { devices: Devices, currentDevice: Device, - deviceChoose: deviceChooseType, + setCurrentDevice: SetCurrentDevice, } type State = { @@ -59,9 +59,9 @@ class TopBar extends PureComponent { } handleSelectDevice = device => () => { - const { deviceChoose } = this.props + const { setCurrentDevice } = this.props - deviceChoose(device) + setCurrentDevice(device) this.setState({ changeDevice: false, diff --git a/src/components/Wrapper.js b/src/components/Wrapper.js index 62fbe7c5..db5aefdb 100644 --- a/src/components/Wrapper.js +++ b/src/components/Wrapper.js @@ -1,6 +1,6 @@ // @flow -import React, { PureComponent } from 'react' +import React, { Fragment, PureComponent } from 'react' import { ipcRenderer } from 'electron' import { Route } from 'react-router' import { translate } from 'react-i18next' @@ -14,6 +14,7 @@ import SendModal from 'components/SendModal' import ReceiveModal from 'components/ReceiveModal' import UpdateNotifier from 'components/UpdateNotifier' +import AppRegionDrag from 'components/AppRegionDrag' import SideBar from 'components/SideBar' import TopBar from 'components/TopBar' @@ -24,20 +25,25 @@ class Wrapper extends PureComponent<{}> { render() { return ( - - - - - - - - + + - + + + + + + + + + + + + ) } } diff --git a/src/internals/usb/index.js b/src/internals/usb/index.js index dd99da77..e9a57995 100644 --- a/src/internals/usb/index.js +++ b/src/internals/usb/index.js @@ -7,13 +7,13 @@ import wallet from './wallet' process.title = 'ledger-wallet-desktop-usb' -function send(type: string, data: any, options: Object = { kill: true }) { +function sendEvent(type: string, data: any, options: Object = { kill: true }) { process.send({ type, data, options }) } const handlers = { - devices: devices(send), - wallet: wallet(send), + devices: devices(sendEvent), + wallet: wallet(sendEvent), } process.on('message', payload => { diff --git a/src/internals/usb/wallet.js b/src/internals/usb/wallet.js index 1f6eee8c..59433b89 100644 --- a/src/internals/usb/wallet.js +++ b/src/internals/usb/wallet.js @@ -13,14 +13,14 @@ async function getWalletInfos(path, wallet) { throw new Error('invalid wallet') } -export default (send: Function) => ({ +export default (sendEvent: Function) => ({ infos: { request: async ({ path, wallet }: { path: string, wallet: string }) => { try { - const publicKey = await getWalletInfos(path, wallet) - send('wallet.infos.success', { path, publicKey }) + const data = await getWalletInfos(path, wallet) + sendEvent('wallet.infos.success', { path, wallet, data }) } catch (err) { - send('wallet.infos.fail', { path, err: err.stack || err }) + sendEvent('wallet.infos.fail', { path, wallet, err: err.stack || err }) } }, }, diff --git a/src/reducers/devices.js b/src/reducers/devices.js index 97385d21..e0d9e419 100644 --- a/src/reducers/devices.js +++ b/src/reducers/devices.js @@ -22,19 +22,19 @@ function setCurrentDevice(state) { } const handlers: Object = { - DEVICES_UPDATE: (state: DevicesState, { payload: devices }: { payload: Devices }) => + UPDATE_DEVICES: (state: DevicesState, { payload: devices }: { payload: Devices }) => setCurrentDevice({ ...state, devices, }), - DEVICE_ADD: (state: DevicesState, { payload: device }: { payload: Device }) => + ADD_DEVICE: (state: DevicesState, { payload: device }: { payload: Device }) => setCurrentDevice({ ...state, devices: [...state.devices, device].filter( (v, i, s) => s.findIndex(t => t.path === v.path) === i, ), }), - DEVICE_REMOVE: (state: DevicesState, { payload: device }: { payload: Device }) => ({ + REMOVE_DEVICE: (state: DevicesState, { payload: device }: { payload: Device }) => ({ ...state, currentDevice: state.currentDevice !== null && state.currentDevice.path === device.path @@ -42,7 +42,7 @@ const handlers: Object = { : state.currentDevice, devices: state.devices.filter(d => d.path !== device.path), }), - DEVICE_CHOOSE: (state: DevicesState, { payload: currentDevice }: { payload: Device }) => ({ + SET_CURRENT_DEVICE: (state: DevicesState, { payload: currentDevice }: { payload: Device }) => ({ ...state, currentDevice, }), diff --git a/src/reducers/index.js b/src/reducers/index.js index 30b572fc..0de33286 100644 --- a/src/reducers/index.js +++ b/src/reducers/index.js @@ -8,21 +8,25 @@ import type { LocationShape } from 'react-router' import devices from './devices' import modals from './modals' import update from './update' +import wallets from './wallets' import type { DevicesState } from './devices' import type { ModalsState } from './modals' import type { UpdateState } from './update' +import type { WalletsState } from './wallets' export type State = { router: LocationShape, devices: DevicesState, modals: ModalsState, update: UpdateState, + wallets: WalletsState, } export default combineReducers({ - router, devices, modals, + router, update, + wallets, }) diff --git a/src/reducers/wallets.js b/src/reducers/wallets.js new file mode 100644 index 00000000..d0f0f91f --- /dev/null +++ b/src/reducers/wallets.js @@ -0,0 +1,20 @@ +// @flow + +import { handleActions } from 'redux-actions' + +export type WalletsState = { + currentWallet: Object | null, +} + +const state: WalletsState = { + currentWallet: null, +} + +const handlers: Object = { + SET_CURRENT_WALLET: (state: Object, { payload: currentWallet }: { payload: WalletsState }) => ({ + ...state, + currentWallet, + }), +} + +export default handleActions(handlers, state) diff --git a/src/renderer/initEvents.js b/src/renderer/events.js similarity index 64% rename from src/renderer/initEvents.js rename to src/renderer/events.js index af5d2d01..7b6c5e12 100644 --- a/src/renderer/initEvents.js +++ b/src/renderer/events.js @@ -3,7 +3,8 @@ import { ipcRenderer } from 'electron' import objectPath from 'object-path' -import { devicesUpdate, deviceAdd, deviceRemove } from 'actions/devices' +import { updateDevices, addDevice, removeDevice } from 'actions/devices' +import { setCurrentWallet } from 'actions/wallets' import { setUpdateStatus } from 'reducers/update' type MsgPayload = { @@ -14,7 +15,7 @@ type MsgPayload = { // wait a bit before launching update check const CHECK_UPDATE_TIMEOUT = 3e3 -function send(channel: string, msgType: string, data: *) { +export function sendEvent(channel: string, msgType: string, data: any) { ipcRenderer.send(channel, { type: msgType, data, @@ -25,27 +26,17 @@ export default (store: Object) => { const handlers = { devices: { update: devices => { - store.dispatch(devicesUpdate(devices)) - if (devices.length) { - send('usb', 'wallet.infos.request', { - path: devices[0].path, - wallet: 'btc', - }) - } + store.dispatch(updateDevices(devices)) }, }, device: { - add: device => store.dispatch(deviceAdd(device)), - remove: device => store.dispatch(deviceRemove(device)), + add: device => store.dispatch(addDevice(device)), + remove: device => store.dispatch(removeDevice(device)), }, wallet: { infos: { - success: ({ path, publicKey }) => { - console.log({ path, publicKey }) - }, - fail: ({ path, err }) => { - console.log({ path, err }) - }, + success: ({ wallet, data }) => store.dispatch(setCurrentWallet({ wallet, data })), + fail: ({ wallet, err }) => store.dispatch(setCurrentWallet({ wallet, err })), }, }, updater: { @@ -70,13 +61,13 @@ export default (store: Object) => { }) // First time, we get all devices - send('usb', 'devices.all') + sendEvent('usb', 'devices.all') // Start detection when we plug/unplug devices - send('usb', 'devices.listen') + sendEvent('usb', 'devices.listen') if (__PROD__) { // Start check of eventual updates - setTimeout(() => send('msg', 'updater.init'), CHECK_UPDATE_TIMEOUT) + setTimeout(() => sendEvent('msg', 'updater.init'), CHECK_UPDATE_TIMEOUT) } } diff --git a/src/renderer/index.js b/src/renderer/index.js index 9ed2731a..9579b0fc 100644 --- a/src/renderer/index.js +++ b/src/renderer/index.js @@ -6,7 +6,7 @@ import { AppContainer } from 'react-hot-loader' import createHistory from 'history/createHashHistory' import createStore from 'renderer/createStore' -import initEvents from 'renderer/initEvents' +import events from 'renderer/events' import App from 'components/App' @@ -16,7 +16,7 @@ const history = createHistory() const store = createStore(history) const rootNode = document.getElementById('app') -initEvents(store) +events(store) function r(Comp) { if (rootNode) { diff --git a/src/styles/global.js b/src/styles/global.js index 1d9214be..2a5342d7 100644 --- a/src/styles/global.js +++ b/src/styles/global.js @@ -22,7 +22,6 @@ injectGlobal` line-height: 1.5; font-size: 16px; font-family: "Open Sans", Arial, Helvetica, sans-serif; - -webkit-app-region: drag; } #app {