Browse Source

Merge pull request #271 from loeck/master

Simple implementation of Macbook TouchBar
master
Meriadec Pillet 7 years ago
committed by GitHub
parent
commit
633b2c2d9a
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      .circleci/config.yml
  2. 4
      package.json
  3. 13
      src/actions/accounts.js
  4. 33
      src/components/AccountPage/index.js
  5. 11
      src/components/BalanceSummary/index.js
  6. 20
      src/components/CalculateBalance.js
  7. 28
      src/components/DashboardPage/index.js
  8. 4
      src/components/DeviceConnect/index.js
  9. 4
      src/components/ManagerPage/index.js
  10. 2
      src/components/OperationsList/index.js
  11. 4
      src/components/ReceiveBox.js
  12. 1
      src/components/RecipientAddress/index.js
  13. 8
      src/components/SideBar/Item.js
  14. 4
      src/components/TopBar.js
  15. 4
      src/components/base/FormattedVal/__tests__/__snapshots__/FormattedVal.test.js.snap
  16. 8
      src/components/base/GrowScroll/index.js
  17. 16
      src/components/base/Search/index.js
  18. 4
      src/components/base/Tabs/index.js
  19. 7
      src/components/layout/Default.js
  20. 2
      src/components/modals/Send/index.js
  21. 4
      src/helpers/balance.js
  22. 4
      src/helpers/staticPath.js
  23. 3
      src/index.ejs
  24. 78
      src/main/app.js
  25. 9
      src/reducers/modals.js
  26. 2
      src/renderer/events.js
  27. 1
      src/renderer/index.js
  28. 4
      src/styles/reset.js
  29. 2
      src/types/common.js
  30. 22
      yarn.lock

2
.circleci/config.yml

@ -20,7 +20,7 @@ jobs:
command: yarn flow-typed
- run:
name: Temporary remove broken flow definitions
command: rm flow-typed/npm/{react-redux_v5.x.x.js,redux_v3.x.x.js}
command: rm flow-typed/npm/{react-i18next_v7.x.x.js,react-redux_v5.x.x.js,redux_v3.x.x.js}
- run:
name: Lint
command: yarn lint

4
package.json

@ -129,7 +129,7 @@
"eslint-config-prettier": "^2.9.0",
"eslint-import-resolver-babel-module": "^5.0.0-beta.0",
"eslint-plugin-flowtype": "^2.46.0",
"eslint-plugin-import": "^2.10.0",
"eslint-plugin-import": "^2.11.0",
"eslint-plugin-jsx-a11y": "^6.0.3",
"eslint-plugin-react": "^7.7.0",
"flow-bin": "^0.69.0",
@ -140,7 +140,7 @@
"js-yaml": "^3.10.0",
"lint-staged": "^7.0.4",
"node-loader": "^0.6.0",
"prettier": "^1.11.1",
"prettier": "^1.12.0",
"react-hot-loader": "^4.0.1",
"react-test-renderer": "^16.3.1",
"webpack": "^4.5.0",

13
src/actions/accounts.js

@ -43,7 +43,10 @@ export const updateOrderAccounts: UpdateOrderAccounts = (orderAccounts: string)
export type AddAccount = Account => (Function, Function) => void
export const addAccount: AddAccount = payload => (dispatch, getState) => {
const { settings: { counterValue, orderAccounts }, accounts } = getState()
const {
settings: { counterValue, orderAccounts },
accounts,
} = getState()
dispatch({ type: 'ADD_ACCOUNT', payload })
dispatch(updateOrderAccounts(orderAccounts))
@ -63,7 +66,9 @@ export const removeAccount: RemoveAccount = payload => ({
export type FetchAccounts = () => (Function, Function) => Promise<*, *>
export const fetchAccounts: FetchAccounts = () => (dispatch, getState) => {
const { settings: { orderAccounts } } = getState()
const {
settings: { orderAccounts },
} = getState()
const accounts = db.get('accounts')
dispatch({
type: 'SET_ACCOUNTS',
@ -74,7 +79,9 @@ export const fetchAccounts: FetchAccounts = () => (dispatch, getState) => {
export type UpdateAccount = Account => (Function, Function) => void
export const updateAccount: UpdateAccount = payload => (dispatch, getState) => {
const { settings: { orderAccounts } } = getState()
const {
settings: { orderAccounts },
} = getState()
dispatch({
type: 'UPDATE_ACCOUNT',
payload,

33
src/components/AccountPage/index.js

@ -1,11 +1,14 @@
// @flow
import React, { PureComponent } from 'react'
import { ipcRenderer } from 'electron'
import { compose } from 'redux'
import { connect } from 'react-redux'
import { translate } from 'react-i18next'
import { Redirect } from 'react-router'
import styled from 'styled-components'
import { formatCurrencyUnit, getFiatUnit } from '@ledgerhq/currencies'
import type { Account } from '@ledgerhq/wallet-common/lib/types'
import { MODAL_SEND, MODAL_RECEIVE, MODAL_SETTINGS_ACCOUNT } from 'config/constants'
@ -75,6 +78,29 @@ class AccountPage extends PureComponent<Props, State> {
daysCount: 7,
}
handleCalculateBalance = data => {
const { counterValue, account } = this.props
if (!account) {
return
}
if (process.platform === 'darwin') {
ipcRenderer.send('touch-bar-update', {
text: account.name,
color: account.currency.color,
balance: {
currency: formatCurrencyUnit(account.unit, account.balance, {
showCode: true,
}),
counterValue: formatCurrencyUnit(getFiatUnit(counterValue), data.totalBalance, {
showCode: true,
}),
},
})
}
}
handleChangeSelectedTime = item =>
this.setState({
selectedTime: item.key,
@ -116,12 +142,13 @@ class AccountPage extends PureComponent<Props, State> {
</Box>
<Box mb={7}>
<BalanceSummary
counterValue={counterValue}
accounts={[account]}
chartColor={account.currency.color}
chartId={`account-chart-${account.id}`}
accounts={[account]}
selectedTime={selectedTime}
counterValue={counterValue}
daysCount={daysCount}
onCalculate={this.handleCalculateBalance}
selectedTime={selectedTime}
renderHeader={({ totalBalance, sinceBalance, refBalance }) => (
<Box flow={4} mb={2}>
<Box horizontal>

11
src/components/BalanceSummary/index.js

@ -10,6 +10,7 @@ import CalculateBalance from 'components/CalculateBalance'
import FormattedVal from 'components/base/FormattedVal'
type Props = {
onCalculate: Function,
counterValue: string,
chartColor: string,
chartId: string,
@ -20,21 +21,23 @@ type Props = {
}
const BalanceSummary = ({
counterValue,
accounts,
chartColor,
chartId,
accounts,
selectedTime,
counterValue,
daysCount,
onCalculate,
renderHeader,
selectedTime,
}: Props) => {
const unit = getFiatUnit(counterValue)
return (
<Card p={0} py={6}>
<CalculateBalance
counterValue={counterValue}
accounts={accounts}
counterValue={counterValue}
daysCount={daysCount}
onCalculate={onCalculate}
render={({ allBalances, totalBalance, sinceBalance, refBalance }) => (
<Fragment>
{renderHeader !== null && (

20
src/components/CalculateBalance.js

@ -2,6 +2,9 @@
import { PureComponent } from 'react'
import { connect } from 'react-redux'
import noop from 'lodash/noop'
import type { Account } from '@ledgerhq/wallet-common/lib/types'
import calculateBalance from 'helpers/balance'
@ -14,6 +17,7 @@ type Props = {
accounts: Account[],
counterValues: Object,
daysCount: number,
onCalculate: Function,
render: Function,
}
@ -33,14 +37,26 @@ function calculateBalanceToState(props: Object) {
}
class CalculateBalance extends PureComponent<Props, State> {
state = calculateBalanceToState(this.props)
static defaultProps = {
onCalculate: noop,
}
constructor(props) {
super(props)
const state = calculateBalanceToState(props)
props.onCalculate(state)
this.state = state
}
componentWillReceiveProps(nextProps) {
const sameAccounts = this.props.accounts === nextProps.accounts
const sameCounterValues = this.props.counterValues === nextProps.counterValues
const sameDaysCount = this.props.daysCount === nextProps.daysCount
if (!sameAccounts || !sameCounterValues || !sameDaysCount) {
this.setState(calculateBalanceToState(nextProps))
const state = calculateBalanceToState(nextProps)
nextProps.onCalculate(state)
this.setState(state)
}
}

28
src/components/DashboardPage/index.js

@ -1,16 +1,21 @@
// @flow
import React, { PureComponent, Fragment } from 'react'
import { ipcRenderer } from 'electron'
import { compose } from 'redux'
import { translate } from 'react-i18next'
import { connect } from 'react-redux'
import { push } from 'react-router-redux'
import { formatCurrencyUnit, getFiatUnit } from '@ledgerhq/currencies'
import type { Account } from '@ledgerhq/wallet-common/lib/types'
import chunk from 'lodash/chunk'
import type { T } from 'types/common'
import { colors } from 'styles/theme'
import { getVisibleAccounts } from 'reducers/accounts'
import { getCounterValueCode } from 'reducers/settings'
@ -77,12 +82,32 @@ class DashboardPage extends PureComponent<Props, State> {
}
}
handleCalculateBalance = data => {
const { counterValue } = this.props
if (process.platform === 'darwin' && this._cacheBalance !== data.totalBalance) {
this._cacheBalance = data.totalBalance
ipcRenderer.send('touch-bar-update', {
text: 'Total balance',
color: colors.wallet,
balance: {
counterValue: formatCurrencyUnit(getFiatUnit(counterValue), data.totalBalance, {
showCode: true,
}),
},
})
}
}
handleChangeSelectedTime = item =>
this.setState({
selectedTime: item.key,
daysCount: item.value,
})
_cacheBalance = null
render() {
const { push, accounts, t, counterValue } = this.props
const { accountsChunk, selectedTime, daysCount } = this.state
@ -109,9 +134,10 @@ class DashboardPage extends PureComponent<Props, State> {
{totalAccounts > 0 && (
<Fragment>
<BalanceSummary
onCalculate={this.handleCalculateBalance}
counterValue={counterValue}
chartId="dashboard-chart"
chartColor="#5286f7"
chartColor={colors.wallet}
accounts={accounts}
selectedTime={selectedTime}
daysCount={daysCount}

4
src/components/DeviceConnect/index.js

@ -29,7 +29,9 @@ const Step = styled(Box).attrs({
${p =>
p.validated
? p.theme.colors.wallet
: p.hasErrors ? p.theme.colors.alertRed : p.theme.colors.fog};
: p.hasErrors
? p.theme.colors.alertRed
: p.theme.colors.fog};
`
const StepIcon = styled(Box).attrs({
alignItems: 'center',

4
src/components/ManagerPage/index.js

@ -83,7 +83,9 @@ class ManagerPage extends PureComponent<Props, State> {
this.setState({ status: 'busy' })
try {
const { job, successResponse, errorResponse } = options
const { device: { path: devicePath } } = this.props
const {
device: { path: devicePath },
} = this.props
const data = { appParams, devicePath }
await runJob({ channel: 'usb', job, successResponse, errorResponse, data })
this.setState({ status: 'success' })

2
src/components/OperationsList/index.js

@ -293,7 +293,7 @@ export class OperationsList extends PureComponent<Props> {
}
return (
<Operation
key={op.hash}
key={`${account.id}-${op.hash}`}
account={account}
minConfirmations={account.minConfirmations}
onAccountClick={onAccountClick}

4
src/components/ReceiveBox.js

@ -144,7 +144,9 @@ class ReceiveBox extends PureComponent<Props, State> {
isVerified:{' '}
{isVerified === null
? 'not yet...'
: isVerified === true ? 'ok!' : '/!\\ contact support'}
: isVerified === true
? 'ok!'
: '/!\\ contact support'}
</Box>
<Box alignItems="center">
<QRCode size={150} data={`bitcoin:${address}${amount ? `?amount=${amount}` : ''}`} />

1
src/components/RecipientAddress/index.js

@ -28,6 +28,7 @@ const WrapperQrCode = styled(Box)`
position: absolute;
right: 0;
top: 100%;
z-index: 2;
`
type Props = {

8
src/components/SideBar/Item.js

@ -85,8 +85,12 @@ function Item({
isActive={isActive}
onClick={
linkTo
? isActive ? undefined : () => push(linkTo)
: modal ? () => openModal(modal) : void 0
? isActive
? undefined
: () => push(linkTo)
: modal
? () => openModal(modal)
: void 0
}
>
{icon && <Box color={isActive ? iconActiveColor : void 0}>{icon}</Box>}

4
src/components/TopBar.js

@ -52,7 +52,9 @@ const Activity = styled.div`
background: ${p =>
p.progress === true
? p.theme.colors.wallet
: p.fail === true ? p.theme.colors.alertRed : p.theme.colors.positiveGreen};
: p.fail === true
? p.theme.colors.alertRed
: p.theme.colors.positiveGreen};
border-radius: 50%;
bottom: 20px;
height: 4px;

4
src/components/base/FormattedVal/__tests__/__snapshots__/FormattedVal.test.js.snap

@ -47,8 +47,8 @@ exports[`components FormattedVal shows sign 1`] = `
exports[`components FormattedVal shows sign 2`] = `
<span
className="s1c17x4y-0 fJKscS s8vzclq-0 gqakkd"
color="#fa4352"
className="s1c17x4y-0 fJKscS s8vzclq-0 hENkfw"
color="#ea2e49"
>
- 4
</span>

8
src/components/base/GrowScroll/index.js

@ -32,16 +32,16 @@ class GrowScroll extends PureComponent<Props> {
}
}
componentWillReceiveProps(nextProps: Props) {
this.handleUpdate(nextProps)
}
componentWillUnmount() {
if (this._scrollbar) {
this._scrollbar.removeListener(this.props.onScroll)
}
}
componenDidUpdate() {
this.handleUpdate(this.props)
}
handleUpdate = (props: Props) => {
if (this._scrollbar) {
props.onUpdate(this._scrollbar)

16
src/components/base/Search/index.js

@ -39,18 +39,18 @@ class Search extends PureComponent<Props, State> {
this.initFuse(this.props)
}
componentWillReceiveProps(nextProps: Props) {
if (nextProps.value !== this.props.value) {
componentDidUpdate(prevProps: Props) {
if (prevProps.value !== this.props.value) {
if (this._fuse) {
const results = this._fuse.search(nextProps.value)
this.formatResults(results, nextProps)
const results = this._fuse.search(this.props.value)
this.formatResults(results, this.props)
}
}
if (nextProps.highlight !== this.props.highlight) {
this.initFuse(nextProps)
if (prevProps.highlight !== this.props.highlight) {
this.initFuse(this.props)
}
if (nextProps.items !== this.props.items) {
this.initFuse(nextProps)
if (prevProps.items !== this.props.items) {
this.initFuse(this.props)
}
}

4
src/components/base/Tabs/index.js

@ -25,7 +25,9 @@ const Tab = styled(Tabbable).attrs({
color: ${p =>
p.isActive
? p.theme.colors.wallet
: p.isDisabled ? p.theme.colors.grey : p.theme.colors.graphite};
: p.isDisabled
? p.theme.colors.grey
: p.theme.colors.graphite};
margin-bottom: -1px;
outline: none;
cursor: ${p => (p.isActive ? 'default' : p.isDisabled ? 'not-allowed' : 'pointer')};

7
src/components/layout/Default.js

@ -2,6 +2,7 @@
import React, { Fragment, Component } from 'react'
import { compose } from 'redux'
import { ipcRenderer } from 'electron'
import styled from 'styled-components'
import { Route, withRouter } from 'react-router'
import { translate } from 'react-i18next'
@ -37,6 +38,12 @@ class Default extends Component<Props> {
window.requestAnimationFrame(() => (this._timeout = setTimeout(() => window.onAppReady(), 300)))
}
componentWillReceiveProps(nextProps: Props) {
if (process.platform === 'darwin' && nextProps.location !== this.props.location) {
ipcRenderer.send('touch-bar-update', { clear: true })
}
}
componentDidUpdate(prevProps) {
if (this.props.location !== prevProps.location) {
if (this._scrollContainer) {

2
src/components/modals/Send/index.js

@ -140,7 +140,7 @@ class SendModal extends PureComponent<Props, State> {
<ModalBody onClose={onClose} deferHeight={acc ? 630 : 355}>
<ModalTitle>{t('send:title')}</ModalTitle>
<ModalContent>
<Breadcrumb mb={5} mt={2} currentStep={stepIndex} items={this._steps} />
<Breadcrumb mb={5} currentStep={stepIndex} items={this._steps} />
{this.renderStep(acc)}
</ModalContent>
{acc && (

4
src/helpers/balance.js

@ -135,7 +135,9 @@ export function getBalanceHistoryForAccounts({
}
return { ...item, balance: b }
})
: balances.length > 0 ? balances[0] : []
: balances.length > 0
? balances[0]
: []
}
export default function calculateBalance(props: CalculateBalance) {

4
src/helpers/staticPath.js

@ -7,4 +7,6 @@ export default (__DEV__ && !STORYBOOK_ENV && NODE_ENV !== 'test'
? __static
: isRunningInAsar
? __dirname.replace(/app\.asar$/, 'static')
: !STORYBOOK_ENV ? `${__dirname}/../../static` : 'static')
: !STORYBOOK_ENV
? `${__dirname}/../../static`
: 'static')

3
src/index.ejs

@ -45,7 +45,8 @@
</div>
<div id="app"></div>
<script>
const { name } = require('electron').remote.getCurrentWindow()
const { remote } = require('electron')
const { name } = remote.getCurrentWindow()
const preloadEl = document.getElementById('preload')
const appEl = document.getElementById('app')

78
src/main/app.js

@ -1,11 +1,15 @@
// @flow
import { app, BrowserWindow, Menu, screen } from 'electron'
import { app, BrowserWindow, Menu, screen, TouchBar, ipcMain } from 'electron'
import debounce from 'lodash/debounce'
import menu from 'main/menu'
import db from 'helpers/db'
import { MODAL_RECEIVE, MODAL_SEND } from 'config/constants'
const { TouchBarButton, TouchBarGroup, TouchBarLabel } = TouchBar
// necessary to prevent win from being garbage collected
let mainWindow = null
@ -61,6 +65,70 @@ const saveWindowSettings = window => {
)
}
function configureTouchBar(w) {
const defaultItems = [
new TouchBarButton({
label: 'Send funds',
click: () =>
w.webContents.send('msg', {
type: 'dispatch',
data: { type: 'MODAL_OPEN', payload: { name: MODAL_SEND } },
}),
}),
new TouchBarButton({
label: 'Receive funds',
click: () =>
w.webContents.send('msg', {
type: 'dispatch',
data: { type: 'MODAL_OPEN', payload: { name: MODAL_RECEIVE } },
}),
}),
]
w.setTouchBar(new TouchBar(defaultItems))
ipcMain.on('touch-bar-update', (e, d) => {
if (d.clear) {
w.setTouchBar(new TouchBar(defaultItems))
return
}
const items = [
new TouchBarLabel({
textColor: d.color,
label: d.text,
}),
]
if (d.balance.currency) {
items.push(
new TouchBarLabel({
textColor: d.color,
label: d.balance.currency,
}),
)
}
if (d.balance.counterValue) {
items.push(
new TouchBarLabel({
textColor: d.color,
label: d.balance.counterValue,
}),
)
}
w.setTouchBar(
new TouchBar([
...defaultItems,
new TouchBarGroup({
items,
}),
]),
)
})
}
const defaultWindowOptions = {
backgroundColor: '#fff',
webPreferences: {
@ -129,6 +197,10 @@ function createMainWindow() {
})
})
if (process.platform === 'darwin') {
configureTouchBar(window)
}
return window
}
@ -169,9 +241,7 @@ function createDevWindow() {
window.on('close', handleCloseWindow(window))
window.on('ready-to-show', () => {
window.show()
})
window.on('ready-to-show', () => window.show())
// Don't want to use HTML <title>
window.on('page-title-updated', e => e.preventDefault())

9
src/reducers/modals.js

@ -24,7 +24,14 @@ const handlers = {
MODAL_OPEN: (state, { payload }: { payload: OpenPayload }) => {
const { name, data } = payload
return {
...state,
// Close all modal before
...Object.keys(state).reduce((result, key) => {
result[key] = {
isOpened: false,
data: undefined,
}
return result
}, {}),
[name]: {
isOpened: true,
data,

2
src/renderer/events.js

@ -127,7 +127,7 @@ export function checkUpdates() {
export default ({ store, locked }: { store: Object, locked: boolean }) => {
const handlers = {
dispatch: (type, payload) => store.dispatch({ type, payload }),
dispatch: ({ type, payload }) => store.dispatch({ type, payload }),
application: {
changeLanguage: lang => i18n.changeLanguage(lang),
},

1
src/renderer/index.js

@ -1,4 +1,5 @@
require('@babel/polyfill')
const Raven = require('raven-js')
require('../env')

4
src/styles/reset.js

@ -35,6 +35,10 @@ em {
font-style: italic;
}
#a11y-status-message {
display: none;
}
.scroll-content {
height: 100%;

2
src/types/common.js

@ -29,4 +29,4 @@ export type SettingsMoney = {
export type Settings = SettingsProfile & SettingsDisplay & SettingsMoney
export type T = (string, ?Object) => string
export type T = (?string, ?Object) => string

22
yarn.lock

@ -3149,7 +3149,7 @@ builder-util@5.7.4, builder-util@^5.6.7, builder-util@^5.7.0, builder-util@^5.7.
stat-mode "^0.2.2"
temp-file "^3.1.1"
builtin-modules@^1.0.0, builtin-modules@^1.1.1:
builtin-modules@^1.0.0:
version "1.1.1"
resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-1.1.1.tgz#270f076c5a72c02f5b65a47df94c5fe3a278892f"
@ -5223,11 +5223,10 @@ eslint-plugin-flowtype@^2.46.0:
dependencies:
lodash "^4.15.0"
eslint-plugin-import@^2.10.0:
version "2.10.0"
resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.10.0.tgz#fa09083d5a75288df9c6c7d09fe12255985655e7"
eslint-plugin-import@^2.11.0:
version "2.11.0"
resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.11.0.tgz#15aeea37a67499d848e8e981806d4627b5503816"
dependencies:
builtin-modules "^1.1.1"
contains-path "^0.1.0"
debug "^2.6.8"
doctrine "1.5.0"
@ -5237,6 +5236,7 @@ eslint-plugin-import@^2.10.0:
lodash "^4.17.4"
minimatch "^3.0.3"
read-pkg-up "^2.0.0"
resolve "^1.6.0"
eslint-plugin-jsx-a11y@^6.0.3:
version "6.0.3"
@ -9762,7 +9762,11 @@ preserve@^0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/preserve/-/preserve-0.2.0.tgz#815ed1f6ebc65926f865b310c0713bcb3315ce4b"
prettier@^1.11.1, prettier@^1.5.3:
prettier@^1.12.0:
version "1.12.0"
resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.12.0.tgz#d26fc5894b9230de97629b39cae225b503724ce8"
prettier@^1.5.3:
version "1.11.1"
resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.11.1.tgz#61e43fc4cd44e68f2b0dfc2c38cd4bb0fccdcc75"
@ -10831,6 +10835,12 @@ resolve@^1.3.2:
dependencies:
path-parse "^1.0.5"
resolve@^1.6.0:
version "1.7.1"
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.7.1.tgz#aadd656374fd298aee895bc026b8297418677fd3"
dependencies:
path-parse "^1.0.5"
responselike@1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/responselike/-/responselike-1.0.2.tgz#918720ef3b631c5642be068f15ade5a46f4ba1e7"

Loading…
Cancel
Save