Browse Source

Merge pull request #1 from loeck/feature/choose-device

Feature/choose device
master
Loëck Vézien 7 years ago
committed by GitHub
parent
commit
4c660d0d07
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 65
      src/actions/devices.js
  2. 26
      src/components/Home.js
  3. 107
      src/components/TopBar.js
  4. 31
      src/components/Wrapper.js
  5. 4
      src/components/base/Overlay.js
  6. 2
      src/i18n/en/translation.yml
  7. 2
      src/main/app.js
  8. 18
      src/main/ledger.js
  9. 43
      src/reducers/devices.js
  10. 5
      src/renderer/i18n.js
  11. 16
      src/renderer/initEvents.js
  12. 5
      src/types/common.js

65
src/actions/devices.js

@ -2,22 +2,59 @@
// eslint-disable import/prefer-default-export // eslint-disable import/prefer-default-export
import type { Device } from 'types/common' import type { Dispatch } from 'redux'
type devicesUpdateType = (Array<Device>) => { type: string, payload: Array<Device> } import { getDevices, getCurrentDevice } from 'reducers/devices'
export const devicesUpdate: devicesUpdateType = payload => ({
type: 'DEVICES_UPDATE',
payload,
})
type devicesAddType = Device => { type: string, payload: Device } import type { Device, Devices } from 'types/common'
export const deviceAdd: devicesAddType = payload => ({
type: 'DEVICE_ADD',
payload,
})
type devicesRemoveType = Device => { type: string, payload: Device } export type deviceChooseType = (?Device) => { type: string, payload: ?Device }
export const deviceRemove: devicesRemoveType = payload => ({ export const deviceChoose: deviceChooseType = payload => ({
type: 'DEVICE_REMOVE', type: 'DEVICE_CHOOSE',
payload, payload,
}) })
type deviceChooseFirstType = () => (Dispatch<any>, () => { devices: Devices }) => void
export const deviceChooseFirst: deviceChooseFirstType = () => (dispatch, getState) => {
const devices = getDevices(getState())
// If we detect only 1 device, we choose it
if (devices.length === 1) {
dispatch(deviceChoose(devices[0]))
}
}
type devicesAddType = Device => (Dispatch<any>) => void
export const deviceAdd: devicesAddType = payload => dispatch => {
dispatch({
type: 'DEVICE_ADD',
payload,
})
dispatch(deviceChooseFirst())
}
type devicesRemoveType = Device => (Dispatch<any>, () => { devices: Devices }) => void
export const deviceRemove: devicesRemoveType = payload => (dispatch, getState) => {
dispatch({
type: 'DEVICE_REMOVE',
payload,
})
const currentDevice = getCurrentDevice(getState())
// If we disconnect the currentDevice we reset it
if (currentDevice.path === payload.path) {
dispatch(deviceChoose(null))
}
}
type devicesUpdateType = Devices => (Dispatch<any>) => void
export const devicesUpdate: devicesUpdateType = payload => dispatch => {
dispatch({
type: 'DEVICES_UPDATE',
payload,
})
dispatch(deviceChooseFirst())
}

26
src/components/Home.js

@ -1,20 +1,32 @@
// @flow // @flow
import React, { PureComponent } from 'react' import React, { PureComponent } from 'react'
import { compose } from 'redux'
import { connect } from 'react-redux' import { connect } from 'react-redux'
import { translate } from 'react-i18next'
import type { MapStateToProps } from 'react-redux'
import type { Device } from 'types/common'
import { getCurrentDevice } from 'reducers/devices'
import Box from 'components/base/Box'
const mapStateToProps: MapStateToProps<*, *, *> = state => ({
currentDevice: getCurrentDevice(state),
})
type Props = { type Props = {
devices: Array<Object>, currentDevice: Device | null,
t: (string, ?Object) => string,
} }
class Home extends PureComponent<Props> { class Home extends PureComponent<Props> {
render() { render() {
const { devices, t } = this.props const { currentDevice } = this.props
return <div>{t('common.connectedDevices', { count: devices.length })}</div> return currentDevice !== null ? (
<Box style={{ wordBreak: 'break-word' }} p={20}>
Your current device: {currentDevice.path}
</Box>
) : null
} }
} }
export default compose(connect(({ devices }: Props): Object => ({ devices })), translate())(Home) export default connect(mapStateToProps)(Home)

107
src/components/TopBar.js

@ -1,17 +1,112 @@
// @flow // @flow
import React, { PureComponent } from 'react' import React, { PureComponent, Fragment } from 'react'
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 { getDevices, getCurrentDevice } from 'reducers/devices'
import { deviceChoose } from 'actions/devices'
import Box from 'components/base/Box' import Box from 'components/base/Box'
import Overlay from 'components/base/Overlay'
const mapStateToProps: MapStateToProps<*, *, *> = state => ({
devices: getDevices(state),
currentDevice: getCurrentDevice(state),
})
const mapDispatchToProps: MapDispatchToProps<*, *, *> = {
deviceChoose,
}
type Props = {
devices: Devices,
currentDevice: Device | null,
deviceChoose: deviceChooseType,
}
type State = {
changeDevice: boolean,
}
const hasDevices = props => props.currentDevice === null && props.devices.length > 0
class TopBar extends PureComponent<Props, State> {
state = {
changeDevice: hasDevices(this.props),
}
componentWillReceiveProps(nextProps) {
if (hasDevices(nextProps) && this.props.currentDevice !== null) {
this.setState({
changeDevice: true,
})
}
}
handleChangeDevice = () =>
this.setState({
changeDevice: true,
})
handleSelectDevice = device => () => {
const { deviceChoose } = this.props
deviceChoose(device)
this.setState({
changeDevice: false,
})
}
class TopBar extends PureComponent<{}> {
render() { render() {
const { devices } = this.props
const { changeDevice } = this.state
return ( return (
<Box bg="white" noShrink style={{ height: 60 }}> <Fragment>
{''} {changeDevice && (
</Box> <Overlay p={20}>
{devices.map(device => (
<Box
key={device.path}
color="white"
bg="night"
onClick={this.handleSelectDevice(device)}
>
{device.path}
</Box>
))}
</Overlay>
)}
<Box bg="white" noShrink style={{ height: 60 }} justify="center" align="flex-end">
<CountDevices count={devices.length} onChangeDevice={this.handleChangeDevice} />
</Box>
</Fragment>
) )
} }
} }
export default TopBar const CountDevices = ({ count, onChangeDevice } = { count: Number, onChangeDevice: Function }) => (
<Box color="night" mr={20} horizontal flow={10} onClick={onChangeDevice}>
<Box>
<DeviceIcon height={20} width={20} />
</Box>
<Box>{count}</Box>
</Box>
)
const DeviceIcon = props => (
<svg {...props} viewBox="0 0 19.781 19.781">
<path
d="M14.507 0L9.8 4.706a2.92 2.92 0 0 0-1.991.854l-6.89 6.889a2.93 2.93 0 0 0 0 4.143l2.33 2.33a2.925 2.925 0 0 0 4.141 0l6.89-6.891c.613-.612.895-1.43.851-2.232l4.589-4.588L14.507 0zm.386 8.792a2.927 2.927 0 0 0-.611-.902l-2.33-2.331a2.945 2.945 0 0 0-1.08-.682l3.637-3.636 3.968 3.969-3.584 3.582zm.693-5.381l-.949.949-1.26-1.26.949-.949 1.26 1.26zm1.881 1.882l-.949.949-1.26-1.26.948-.949 1.261 1.26z"
fill="currentColor"
/>
</svg>
)
export default connect(mapStateToProps, mapDispatchToProps)(TopBar)

31
src/components/Wrapper.js

@ -1,34 +1,23 @@
// @flow // @flow
import React, { Fragment } from 'react' import React from 'react'
import { compose } from 'redux'
import { connect } from 'react-redux'
import { Route } from 'react-router' import { Route } from 'react-router'
import { translate } from 'react-i18next' import { translate } from 'react-i18next'
import Box from 'components/base/Box' import Box from 'components/base/Box'
import Overlay from 'components/base/Overlay'
import Home from 'components/Home' import Home from 'components/Home'
import SideBar from 'components/SideBar' import SideBar from 'components/SideBar'
import TopBar from 'components/TopBar' import TopBar from 'components/TopBar'
const Wrapper = ({ devices, t }: { devices: Array<Object>, t: string => string }) => ( const Wrapper = () => (
<Fragment> <Box grow horizontal>
{devices.length === 0 ? ( <SideBar />
<Overlay align="center" justify="center"> <Box grow bg="cream">
<Box color="white">{t('common.connectDevice')}</Box> <TopBar />
</Overlay> <Route path="/" component={Home} />
) : ( </Box>
<Box grow horizontal> </Box>
<SideBar />
<Box grow bg="cream">
<TopBar />
<Route path="/" component={Home} />
</Box>
</Box>
)}
</Fragment>
) )
export default compose(connect(({ devices }): Object => ({ devices })), translate())(Wrapper) export default translate()(Wrapper)

4
src/components/base/Overlay.js

@ -3,10 +3,12 @@
import React from 'react' import React from 'react'
import styled from 'styled-components' import styled from 'styled-components'
import { rgba } from 'styles/helpers'
import Box from 'components/base/Box' import Box from 'components/base/Box'
const Overlay = styled(({ sticky, ...props }) => <Box sticky {...props} />)` const Overlay = styled(({ sticky, ...props }) => <Box sticky {...props} />)`
background-color: ${p => p.theme.colors.night}; background-color: ${p => rgba(p.theme.colors.night, 0.4)};
position: fixed; position: fixed;
` `

2
src/i18n/en/translation.yml

@ -1,6 +1,6 @@
common: common:
ok: Okay ok: Okay
cancel: Cancel cancel: Cancel
connectDevice: Please connect your device
connectedDevices: You have {{count}} device connected connectedDevices: You have {{count}} device connected
connectedDevices_0: You don't have device connected
connectedDevices_plural: You have {{count}} devices connected connectedDevices_plural: You have {{count}} devices connected

2
src/main/app.js

@ -35,7 +35,7 @@ function createMainWindow() {
return window return window
} }
// dsq
// Quit application when all windows are closed // Quit application when all windows are closed
app.on('window-all-closed', () => { app.on('window-all-closed', () => {
// On macOS it is common for applications to stay open // On macOS it is common for applications to stay open

18
src/main/ledger.js

@ -41,13 +41,17 @@ const handlers = {
}, },
all: send => send('devices.update', HID.devices().filter(isLedgerDevice)), all: send => send('devices.update', HID.devices().filter(isLedgerDevice)),
}, },
requestWalletInfos: async (send, { path, wallet }) => { wallet: {
try { infos: {
const publicKey = await getWalletInfos(path, wallet) request: async (send, { path, wallet }) => {
send('receiveWalletInfos', { path, publicKey }) try {
} catch (err) { const publicKey = await getWalletInfos(path, wallet)
send('failWalletInfos', { path, err: err.stack }) send('wallet.infos.success', { path, publicKey })
} } catch (err) {
send('wallet.infos.fail', { path, err: err.stack || err })
}
},
},
}, },
} }

43
src/reducers/devices.js

@ -2,13 +2,44 @@
import { handleActions } from 'redux-actions' import { handleActions } from 'redux-actions'
const state = [] import type { Device, Devices } from 'types/common'
const handlers = { type stateType = {
DEVICES_UPDATE: (state, { payload: devices }) => devices, currentDevice: ?Device,
DEVICE_ADD: (state, { payload: device }) => devices: Devices,
[...state, device].filter((v, i, s) => s.findIndex(t => t.path === v.path) === i), }
DEVICE_REMOVE: (state, { payload: device }) => state.filter(d => d.path !== device.path), const state = {
currentDevice: null,
devices: [],
}
const handlers: Object = {
DEVICES_UPDATE: (state: stateType, { payload: devices }: { payload: Devices }) => ({
...state,
devices,
}),
DEVICE_ADD: (state: stateType, { payload: device }: { payload: Device }) => ({
...state,
devices: [...state.devices, device].filter(
(v, i, s) => s.findIndex(t => t.path === v.path) === i,
),
}),
DEVICE_REMOVE: (state: stateType, { payload: device }: { payload: Device }) => ({
...state,
devices: state.devices.filter(d => d.path !== device.path),
}),
DEVICE_CHOOSE: (state: stateType, { payload: currentDevice }: { payload: Device }) => ({
...state,
currentDevice,
}),
}
export function getCurrentDevice(state: Object) {
return state.devices.currentDevice
}
export function getDevices(state: Object) {
return state.devices.devices
} }
export default handleActions(handlers, state) export default handleActions(handlers, state)

5
src/renderer/i18n.js

@ -16,4 +16,9 @@ i18n.use(Backend).init({
}, },
}) })
i18n.services.pluralResolver.addRule('en', {
numbers: [0, 1, 'plural'],
plurals: n => Number(n >= 2 ? 2 : n),
})
export default i18n export default i18n

16
src/renderer/initEvents.js

@ -23,7 +23,7 @@ export default (store: Object) => {
update: devices => { update: devices => {
store.dispatch(devicesUpdate(devices)) store.dispatch(devicesUpdate(devices))
if (devices.length) { if (devices.length) {
send('requestWalletInfos', { send('wallet.infos.request', {
path: devices[0].path, path: devices[0].path,
wallet: 'btc', wallet: 'btc',
}) })
@ -34,11 +34,15 @@ export default (store: Object) => {
add: device => store.dispatch(deviceAdd(device)), add: device => store.dispatch(deviceAdd(device)),
remove: device => store.dispatch(deviceRemove(device)), remove: device => store.dispatch(deviceRemove(device)),
}, },
receiveWalletInfos: ({ path, publicKey }) => { wallet: {
console.log({ path, publicKey }) infos: {
}, success: ({ path, publicKey }) => {
failWalletInfos: ({ path, err }) => { console.log({ path, publicKey })
console.log({ path, err }) },
fail: ({ path, err }) => {
console.log({ path, err })
},
},
}, },
} }

5
src/types/common.js

@ -3,4 +3,9 @@
export type Device = { export type Device = {
vendorId: string, vendorId: string,
productId: string, productId: string,
path: string,
} }
export type Devices = Array<Device>
export type T = (string, ?Object) => string

Loading…
Cancel
Save