diff --git a/electron-builder.yml b/electron-builder.yml index 95f2ea53..320c75ea 100644 --- a/electron-builder.yml +++ b/electron-builder.yml @@ -48,7 +48,7 @@ files: # Exclude files - "!report-*.html" - "!node_modules/**/{CONTRIBUTORS,License,CNAME,AUTHOR,TODO,CONTRIBUTING,COPYING,INSTALL,NEWS,PORTING,Makefile,htdocs,CHANGELOG,ChangeLog,changelog,README,Readme,readme,test,sample,example,demo,composer.json,tsconfig.json,jsdoc.json,tslint.json,typings.json,gulpfile,bower.json,package-lock,Gruntfile,CMakeLists,karma.conf,yarn.lock}*" - - "!node_modules/**/{man,flow-typed,benchmark,node_modules,spec,cmake,browser,vagrant,doxy*,bin,obj,obj.target,example,examples,test,tests,__tests__,mocks,__mocks__,doc,docs,msvc,Xcode,CVS,RCS,SCCS,.storybook}{,/**/*}" + - "!node_modules/**/{man,flow-typed,benchmark,spec,cmake,browser,vagrant,doxy*,bin,obj,obj.target,example,examples,test,tests,__tests__,mocks,__mocks__,doc,docs,msvc,Xcode,CVS,RCS,SCCS,.storybook}{,/**/*}" - "!node_modules/**/*.{conf,png,pc,coffee,txt,spec.js,ts,js.flow,html,def,jst,xml,ico,in,ac,sln,dsp,dsw,cmd,vcproj,vcxproj,vcxproj.filters,pdb,exp,obj,lib,map,md,sh,gypi,gyp,h,cpp,yml,log,tlog,Makefile,mk,c,cc,rc,xcodeproj,xcconfig,d.ts,yaml,hpp}" # Exclude modules diff --git a/package.json b/package.json index db27f11b..7d0272e8 100644 --- a/package.json +++ b/package.json @@ -55,6 +55,7 @@ "bs58check": "^2.1.1", "color": "^3.0.0", "cross-env": "^5.1.3", + "d3": "^4.13.0", "debug": "^3.1.0", "downshift": "^1.30.0", "electron-store": "^1.3.0", diff --git a/src/components/BalanceSummary/index.js b/src/components/BalanceSummary/index.js index 236d5084..543f4481 100644 --- a/src/components/BalanceSummary/index.js +++ b/src/components/BalanceSummary/index.js @@ -7,9 +7,7 @@ import { formatShort, getFiatUnit } from '@ledgerhq/currencies' import type { Accounts } from 'types/common' -import { space } from 'styles/theme' - -import { AreaChart } from 'components/base/Chart' +import Chart from 'components/base/NewChart' import Box, { Card } from 'components/base/Box' import CalculateBalance from 'components/CalculateBalance' import FormattedVal from 'components/base/FormattedVal' @@ -76,33 +74,24 @@ const BalanceSummary = ({ })} )} - - + d.y} + nbTicksX={getTickCountX(selectedTime)} + renderTickX={renderTickX(selectedTime)} + renderTickY={t => formatShort(unit, t)} renderTooltip={d => ( )} - renderTickX={renderTickX(selectedTime)} - renderTickY={t => formatShort(unit, t)} - tickCountX={getTickCountX(selectedTime)} - tickCountY={4} /> diff --git a/src/components/DashboardPage/AccountCard.js b/src/components/DashboardPage/AccountCard.js index fcb9bf89..d7be5911 100644 --- a/src/components/DashboardPage/AccountCard.js +++ b/src/components/DashboardPage/AccountCard.js @@ -5,7 +5,7 @@ import { getIconByCoinType } from '@ledgerhq/currencies/react' import type { Account } from 'types/common' -import { SimpleAreaChart } from 'components/base/Chart' +import Chart from 'components/base/NewChart' import Bar from 'components/base/Bar' import Box, { Card } from 'components/base/Box' import CalculateBalance from 'components/CalculateBalance' @@ -84,14 +84,13 @@ const AccountCard = ({ /> - )} diff --git a/src/components/TransactionsList/ConfirmationCheck.js b/src/components/TransactionsList/ConfirmationCheck.js index e9bc0e60..ce0c5b45 100644 --- a/src/components/TransactionsList/ConfirmationCheck.js +++ b/src/components/TransactionsList/ConfirmationCheck.js @@ -5,6 +5,8 @@ import styled from 'styled-components' import { rgba } from 'styles/helpers' +import type { T } from 'types/common' + import Box from 'components/base/Box' import Tooltip from 'components/base/Tooltip' import IconCheck from 'icons/Check' @@ -24,13 +26,19 @@ const Container = styled(Box).attrs({ const ConfirmationCheck = ({ confirmations, minConfirmations, + t, }: { confirmations: number, minConfirmations: number, + t: T, }) => { const isConfirmed = confirmations >= minConfirmations return ( - (isConfirmed ? 'Confirmed' : 'Not confirmed')}> + + isConfirmed ? t('transactionsList:confirmed') : t('transactionsList:notConfirmed') + } + > {isConfirmed ? : } diff --git a/src/components/TransactionsList/index.js b/src/components/TransactionsList/index.js index 51d6f727..16106acf 100644 --- a/src/components/TransactionsList/index.js +++ b/src/components/TransactionsList/index.js @@ -175,7 +175,11 @@ const Transaction = ({ /> - + ) diff --git a/src/components/base/NewChart/Tooltip.js b/src/components/base/NewChart/Tooltip.js new file mode 100644 index 00000000..ace1533c --- /dev/null +++ b/src/components/base/NewChart/Tooltip.js @@ -0,0 +1,67 @@ +// @flow + +import React, { Fragment } from 'react' + +import { colors as themeColors } from 'styles/theme' +import { TooltipContainer } from 'components/base/Tooltip' + +import type { Item } from './types' + +/** + * we use inline style for more perfs, as tooltip may re-render numerous times + */ + +const Arrow = () => ( + + + +) + +const Tooltip = ({ d, renderTooltip }: { d: Item, renderTooltip?: Function }) => ( +
+
+ + {renderTooltip ? ( + renderTooltip(d) + ) : ( + +
+ {Math.round(d.value)} +
+ {d.date} +
+ )} +
+
+ +
+
+
+) + +Tooltip.defaultProps = { + renderTooltip: undefined, +} + +export default Tooltip diff --git a/src/components/base/NewChart/handleMouseEvents.js b/src/components/base/NewChart/handleMouseEvents.js new file mode 100644 index 00000000..656a404e --- /dev/null +++ b/src/components/base/NewChart/handleMouseEvents.js @@ -0,0 +1,116 @@ +// @flow + +import React from 'react' +import * as d3 from 'd3' +import { renderToString } from 'react-dom/server' +import { ThemeProvider } from 'styled-components' + +import theme from 'styles/theme' + +import type { Props } from '.' +import type { CTX } from './types' + +import Tooltip from './Tooltip' + +export default function handleMouseEvents({ + ctx, + props, + shouldTooltipUpdate, + onTooltipUpdate, + renderTooltip, +}: { + ctx: CTX, + props: Props, + shouldTooltipUpdate: Function, + onTooltipUpdate: Function, + renderTooltip?: Function, +}) { + const { MARGINS, HEIGHT, WIDTH, NODES, DATA, x, y } = ctx + const { hideAxis } = props + + const bisectDate = d3.bisector(d => d.parsedDate).left + + const node = NODES.wrapper + .append('rect') + .attr('fill', 'none') + .attr('pointer-events', 'all') + .attr('class', 'overlay') + .attr('width', WIDTH) + .attr('height', HEIGHT) + + node + .on('mousemove', mouseMove) + .on('mouseenter', mouseEnter) + .on('mouseout', mouseOut) + + function getStep() { + const x0 = x.invert(d3.mouse(this)[0]) + const i = bisectDate(DATA, x0, 1) + const d0 = DATA[i - 1] + const d1 = DATA[i] + if (!d0 || !d1) { + return null + } + return x0 - d0.parsedDate > d1.parsedDate - x0 ? d1 : d0 + } + + function mouseEnter() { + const d = getStep.call(this) + if (!d) { + return + } + NODES.tooltip + .style('transition', '100ms cubic-bezier(.61,1,.53,1) opacity') + .style('opacity', 1) + .style('transform', `translate3d(${MARGINS.left + x(d.parsedDate)}px, ${y(d.value)}px, 0)`) + NODES.focus.style('opacity', 1) + if (!hideAxis) { + NODES.xBar.style('opacity', 1) + NODES.yBar.style('opacity', 1) + } + } + + function mouseOut() { + NODES.tooltip.style('opacity', 0).style('transition', '100ms linear opacity') + NODES.focus.style('opacity', 0) + if (!hideAxis) { + NODES.xBar.style('opacity', 0) + NODES.yBar.style('opacity', 0) + } + } + + function mouseMove() { + const d = getStep.call(this) + if (!d) { + return + } + if (!shouldTooltipUpdate(d)) { + return + } + onTooltipUpdate(d) + NODES.focus.attr('transform', `translate(${x(d.parsedDate)},${y(d.value)})`) + NODES.tooltip + .html( + renderToString( + + + , + ), + ) + .style('transform', `translate3d(${MARGINS.left + x(d.parsedDate)}px, ${y(d.value)}px, 0)`) + if (!hideAxis) { + NODES.xBar + .attr('x1', x(d.parsedDate)) + .attr('x2', x(d.parsedDate)) + .attr('y1', HEIGHT) + .attr('y2', y(d.value)) + NODES.yBar + .attr('x1', 0) + .attr('x2', x(d.parsedDate)) + .attr('y1', y(d.value)) + .attr('y2', y(d.value)) + } + } + + return node +} diff --git a/src/components/base/NewChart/helpers.js b/src/components/base/NewChart/helpers.js new file mode 100644 index 00000000..0f2559ef --- /dev/null +++ b/src/components/base/NewChart/helpers.js @@ -0,0 +1,49 @@ +import c from 'color' + +export function enrichData(data, parseTime) { + return data.map((d, i) => ({ + ...d, + ref: d, + index: i, + parsedDate: parseTime(d.date), + })) +} + +export function generateColors(color) { + const cColor = c(color) + return { + line: color, + focus: color, + gradientStart: cColor.fade(0.7), + gradientStop: cColor.fade(1), + focusBar: cColor.fade(0.5), + } +} + +export function generateMargins(hideAxis) { + const margins = { + top: hideAxis ? 5 : 10, + bottom: hideAxis ? 5 : 30, + right: hideAxis ? 5 : 40, + left: hideAxis ? 5 : 80, + } + + // FIXME: Forced to "use" margins here to prevent babel/uglify to believe + // there is a constant variable re-assignment. I don't get it, but it + // works, so, eh. + void margins + + return margins +} + +export function observeResize(node, cb) { + const ro = new ResizeObserver(() => { + if (!node) { + return + } + const { width } = node.getBoundingClientRect() + cb(width) + }) + + ro.observe(node) +} diff --git a/src/components/base/NewChart/index.js b/src/components/base/NewChart/index.js new file mode 100644 index 00000000..2f19ac8f --- /dev/null +++ b/src/components/base/NewChart/index.js @@ -0,0 +1,184 @@ +// @flow + +/** + * Chart + * ----- + * + * XX + * XXXX + * X XX X + * XXXX XX X + * XX X XX X + * X XXXX X XX X + * XX XX X XX XX XX + * XX XX XX XXXX + * XX + * XX + * Usage: + * + * + * + * `data` looks like: + * + * [ + * { date: '2018-01-01', value: 10 }, + * { date: '2018-01-02', value: 25 }, + * { date: '2018-01-03', value: 50 }, + * ] + * + */ + +import React, { PureComponent } from 'react' +import * as d3 from 'd3' +import noop from 'lodash/noop' + +import refreshNodes from './refreshNodes' +import refreshDraw from './refreshDraw' +import handleMouseEvents from './handleMouseEvents' +import { enrichData, generateColors, generateMargins, observeResize } from './helpers' + +import type { Data } from './types' + +export type Props = { + data: Data, // eslint-disable-line react/no-unused-prop-types + color?: string, // eslint-disable-line react/no-unused-prop-types + hideAxis?: boolean, // eslint-disable-line react/no-unused-prop-types + interactive?: boolean, // eslint-disable-line react/no-unused-prop-types + height: number, + dateFormat?: string, // eslint-disable-line react/no-unused-prop-types + id?: string, // eslint-disable-line react/no-unused-prop-types + nbTicksX: number, // eslint-disable-line react/no-unused-prop-types + renderTooltip?: Function, // eslint-disable-line react/no-unused-prop-types + renderTickX?: Function, // eslint-disable-line react/no-unused-prop-types + renderTickY?: Function, // eslint-disable-line react/no-unused-prop-types +} + +class Chart extends PureComponent { + static defaultProps = { + color: '#000', + hideAxis: false, + interactive: true, + height: 400, + dateFormat: '%Y-%m-%d', + id: 'chart', + nbTicksX: 5, + } + + componentDidMount() { + const { width } = this._ruler.getBoundingClientRect() + this._width = width + this.createChart() + observeResize(this._ruler, width => { + if (width !== this._width) { + this._width = width + this.refreshChart(this.props) + } + }) + } + + componentDidUpdate(prevProps: Props) { + this.refreshChart(prevProps) + } + + _ruler: any + _node: any + _width: number + refreshChart: Function + + createChart() { + const ctx = { + NODES: {}, + INVALIDATED: {}, + MARGINS: {}, + COLORS: {}, + DATA: [], + WIDTH: 0, + HEIGHT: 0, + x: noop, + y: noop, + } + + let firstRender = true + + // Keep reference to mouse handler to allow destroy when refresh + let mouseHandler = null + + this.refreshChart = prevProps => { + const { _node: node, props } = this + const { data: raw, color, dateFormat, height, hideAxis, interactive, renderTooltip } = props + + ctx.DATA = enrichData(raw, d3.timeParse(dateFormat)) + + // Detect what needs to be updated + ctx.INVALIDATED = { + color: firstRender || (prevProps && color !== prevProps.color), + margin: firstRender || (prevProps && hideAxis !== prevProps.hideAxis), + } + firstRender = false + + // Reset color if needed + if (ctx.INVALIDATED.color) { + ctx.COLORS = generateColors(color) + } + + // Reset margins if needed + if (ctx.INVALIDATED.margin) { + ctx.MARGINS = generateMargins(hideAxis) + } + + // Derived draw variables + ctx.HEIGHT = Math.max(0, height - ctx.MARGINS.top - ctx.MARGINS.bottom) + ctx.WIDTH = Math.max(0, this._width - ctx.MARGINS.left - ctx.MARGINS.right) + + // Scales and areas + const x = d3.scaleTime().range([0, ctx.WIDTH]) + const y = d3.scaleLinear().range([ctx.HEIGHT, 0]) + x.domain(d3.extent(ctx.DATA, d => d.parsedDate)) + y.domain([0, d3.max(ctx.DATA, d => d.value)]) + ctx.x = x + ctx.y = y + + // Reference to last tooltip, to prevent un-necessary re-render + let lastDisplayedTooltip = null + + // Add/remove nodes depending on props + refreshNodes({ ctx, node, props }) + + // Redraw + refreshDraw({ ctx, props }) + + // Mouse handler + mouseHandler && mouseHandler.remove() // eslint-disable-line no-unused-expressions + if (interactive) { + mouseHandler = handleMouseEvents({ + ctx, + props, + shouldTooltipUpdate: d => d !== lastDisplayedTooltip, + onTooltipUpdate: d => (lastDisplayedTooltip = d), + renderTooltip, + }) + } + } + + this.refreshChart() + } + + render() { + const { height } = this.props + return ( +
(this._ruler = n)}> +
(this._node = n)} + /> +
+ ) + } +} + +export default Chart diff --git a/src/components/base/NewChart/refreshDraw.js b/src/components/base/NewChart/refreshDraw.js new file mode 100644 index 00000000..a44bba93 --- /dev/null +++ b/src/components/base/NewChart/refreshDraw.js @@ -0,0 +1,116 @@ +// @flow + +import * as d3 from 'd3' + +import { colors as themeColors } from 'styles/theme' + +import type { Props } from '.' +import type { CTX } from './types' + +export default function refreshDraw({ ctx, props }: { ctx: CTX, props: Props }) { + const { NODES, WIDTH, HEIGHT, MARGINS, COLORS, INVALIDATED, DATA, x, y } = ctx + const { hideAxis, interactive, renderTickX, renderTickY, nbTicksX } = props + + const area = d3 + .area() + .x(d => x(d.parsedDate)) + .y0(HEIGHT) + .y1(d => y(d.value)) + + const valueline = d3 + .line() + .x(d => x(d.parsedDate)) + .y(d => y(d.value)) + + // Resize container + NODES.svg + .attr('width', WIDTH + MARGINS.left + MARGINS.right) + .attr('height', HEIGHT + MARGINS.top + MARGINS.bottom) + + // Resize wrapper & axis + NODES.wrapper.attr('transform', `translate(${MARGINS.left},${MARGINS.top})`) + + // Resize axis + if (!hideAxis) { + NODES.axisBot.attr('transform', `translate(0,${HEIGHT + 10})`) + } + + if (INVALIDATED.color) { + if (interactive) { + // Update focus bar colors + NODES.xBar.attr('stroke', COLORS.focusBar) + NODES.yBar.attr('stroke', COLORS.focusBar) + // Update dot color + NODES.focus.attr('fill', COLORS.focus) + } + // Update gradient color + NODES.gradientStart.attr('stop-color', COLORS.gradientStart) + NODES.gradientStop.attr('stop-color', COLORS.gradientStop) + + // Update line color + NODES.line.attr('stroke', COLORS.line) + } + + // Hide interactive things + if (interactive) { + NODES.focus.style('opacity', 0) + NODES.tooltip.style('opacity', 0) + NODES.xBar.style('opacity', 0) + NODES.yBar.style('opacity', 0) + } + + // Draw axis + if (!hideAxis) { + NODES.axisLeft.call( + d3 + .axisLeft(y) + .ticks(3) + .tickFormat(val => (renderTickY ? renderTickY(val) : val)), + ) + NODES.axisBot.call( + d3 + .axisBottom(x) + .ticks(nbTicksX) + .tickFormat(val => (renderTickX ? renderTickX(val) : val)), + ) + stylizeAxis(NODES.axisLeft) + stylizeAxis(NODES.axisBot) + } + + // Draw ticks + if (!hideAxis) { + const yTicks = d3 + .axisLeft(y) + .ticks(3) + .tickSize(-WIDTH) + .tickFormat('') + + NODES.yTicks.call(yTicks) + NODES.yTicks.select('.domain').remove() + + NODES.yTicks + .selectAll('.tick line') + .attr('stroke', 'rgba(0, 0, 0, 0.1)') + .attr('stroke-dasharray', '5, 5') + + NODES.yTicks + .selectAll('.tick:first-of-type line') + .attr('stroke-width', '2px') + .attr('stroke-dasharray', 'none') + } + + // Draw line and gradient + NODES.fillArea.data([DATA]).attr('d', area) + NODES.line.data([DATA]).attr('d', valueline) +} + +function stylizeAxis(axis) { + axis.selectAll('.tick line').attr('stroke', 'none') + axis.selectAll('path').attr('stroke', 'none') + axis + .selectAll('text') + .attr('stroke', themeColors.grey) + .style('font-size', '12px') + .style('font-family', 'Open Sans') + .style('font-weight', 300) +} diff --git a/src/components/base/NewChart/refreshNodes.js b/src/components/base/NewChart/refreshNodes.js new file mode 100644 index 00000000..856bba10 --- /dev/null +++ b/src/components/base/NewChart/refreshNodes.js @@ -0,0 +1,129 @@ +// @flow + +import * as d3 from 'd3' +import d from 'debug' + +import type { Props } from '.' +import type { CTX } from './types' + +const debug = d('Chart') + +export default function refreshNodes({ ctx, node, props }: { ctx: CTX, node: any, props: Props }) { + const { NODES, COLORS } = ctx + const { hideAxis, interactive, id } = props + + // Container + + ensure({ NODES, key: 'svg' }, () => d3.select(node).append('svg')) + ensure({ NODES, key: 'wrapper' }, () => NODES.svg.append('g')) + ensure({ NODES, key: 'defs' }, () => NODES.wrapper.append('defs')) + + // Axis & ticks + + ensure({ onlyIf: !hideAxis, NODES, key: 'axisBot' }, () => NODES.wrapper.append('g')) + ensure({ onlyIf: !hideAxis, NODES, key: 'axisLeft' }, () => NODES.wrapper.append('g')) + ensure({ onlyIf: !hideAxis, NODES, key: 'yTicks' }, () => NODES.wrapper.append('g')) + + // Focus bars + + ensure({ onlyIf: interactive, NODES, key: 'xBar' }, () => + NODES.wrapper + .append('line') + .attr('stroke', COLORS.focusBar) + .attr('stroke-width', '1px') + .attr('stroke-dasharray', '3, 2'), + ) + + ensure({ onlyIf: interactive, NODES, key: 'yBar' }, () => + NODES.wrapper + .append('line') + .attr('stroke', COLORS.focusBar) + .attr('stroke-width', '1px') + .attr('stroke-dasharray', '3, 2'), + ) + + // Gradient + + ensure({ NODES, key: 'gradient' }, () => + NODES.defs + .append('linearGradient') + .attr('id', `gradient-${id || ''}`) + .attr('x1', '0%') + .attr('x2', '0%') + .attr('y1', '0%') + .attr('y2', '100%'), + ) + + ensure({ NODES, key: 'gradientStart' }, () => + NODES.gradient + .append('stop') + .attr('stop-color', COLORS.gradientStart) + .attr('offset', '0'), + ) + + ensure({ NODES, key: 'gradientStop' }, () => + NODES.gradient + .append('stop') + .attr('stop-color', COLORS.gradientStop) + .attr('offset', '1'), + ) + + ensure({ NODES, key: 'fillArea' }, () => + NODES.wrapper.append('path').attr('fill', `url(#gradient-${id || ''})`), + ) + + // Line + + ensure({ NODES, key: 'line' }, () => + NODES.wrapper + .append('path') + .attr('fill', 'none') + .attr('stroke', COLORS.line) + .attr('stroke-width', '2px'), + ) + + // Tooltip & focus point + + ensure({ onlyIf: interactive, NODES, key: 'tooltip' }, () => + d3 + .select(node) + .append('div') + .style('position', 'absolute') + .style('top', '0') + .style('left', '0') + .style('pointer-events', 'none'), + ) + + ensure({ onlyIf: interactive, NODES, key: 'focus' }, () => + NODES.wrapper + .append('g') + .append('circle') + .attr('fill', COLORS.focus) + .attr('r', 4), + ) + + ctx.NODES = NODES +} + +function ensure( + { onlyIf: condition = true, NODES, key }: { onlyIf?: boolean, NODES: Object, key: string }, + create, +) { + if (!condition && NODES[key]) { + remove(NODES, key) + } + if (condition && !NODES[key]) { + append(NODES, key, create()) + } +} + +function remove(NODES, key) { + debug(`Destroying ${key}`) + NODES[key].remove() + NODES[key] = null +} + +function append(NODES, key, node) { + debug(`Appending ${key}`) + NODES[key] = node +} diff --git a/src/components/base/NewChart/stories.js b/src/components/base/NewChart/stories.js new file mode 100644 index 00000000..738a5c6e --- /dev/null +++ b/src/components/base/NewChart/stories.js @@ -0,0 +1,70 @@ +// @flow + +import React, { Component, Fragment } from 'react' +import Chance from 'chance' +import moment from 'moment' +import { storiesOf } from '@storybook/react' +import { boolean, number } from '@storybook/addon-knobs' +import { color } from '@storybook/addon-knobs/react' + +import Chart from 'components/base/NewChart' + +const stories = storiesOf('Components/Chart', module) + +const data = generateRandomData(365) + +type State = { + start: number, + stop: number, +} + +class Wrapper extends Component { + state = { + start: 0, + stop: 365, + } + + handleChange = key => e => this.setState({ [key]: Number(e.target.value) }) + + render() { + const { start, stop } = this.state + return ( + + + + + + + ) + } +} + +stories.add('default', () => ) + +function generateRandomData(n) { + const today = moment() + const day = moment(today).subtract(n, 'days') + const data = [] + const chance = new Chance() + while (!day.isSame(today)) { + data.push({ + date: day.format('YYYY-MM-DD'), + value: chance.integer({ min: 0, max: 50e3 }), + }) + day.add(1, 'day') + } + return data +} diff --git a/src/components/base/NewChart/types.js b/src/components/base/NewChart/types.js new file mode 100644 index 00000000..1c6de152 --- /dev/null +++ b/src/components/base/NewChart/types.js @@ -0,0 +1,28 @@ +// @flow + +export type Item = { + date: string, + value: number, +} + +type EnrichedItem = { + date: string, + value: number, + parsedDate: Date, + ref: Item, +} + +export type Data = Array +export type EnrichedData = Array + +export type CTX = { + NODES: Object, + MARGINS: Object, + COLORS: Object, + INVALIDATED: Object, + HEIGHT: number, + WIDTH: number, + DATA: EnrichedData, + x: Function, + y: Function, +} diff --git a/src/components/base/Tooltip/index.js b/src/components/base/Tooltip/index.js index b4895513..eac95f5f 100644 --- a/src/components/base/Tooltip/index.js +++ b/src/components/base/Tooltip/index.js @@ -4,7 +4,7 @@ import React, { PureComponent } from 'react' import styled from 'styled-components' import tippy from 'tippy.js' -import { space } from 'styles/theme' +import { space, colors } from 'styles/theme' import Box from 'components/base/Box' @@ -16,15 +16,36 @@ const Template = styled.div` display: none; ` -export const TooltipContainer = styled(Box).attrs({ - bg: 'dark', - borderRadius: 1, - color: 'white', - ff: 'Open Sans|SemiBold', - fontSize: 2, - px: 2, - py: 1, -})`` +export const TooltipContainer = ({ + children, + innerRef, + style, +}: { + children: any, + innerRef?: Function, + style?: Object, +}) => ( +
+ {children} +
+) + +TooltipContainer.defaultProps = { + innerRef: undefined, + style: undefined, +} type Props = { offset?: Array, diff --git a/src/helpers/balance.js b/src/helpers/balance.js index 77d4e034..c51279b6 100644 --- a/src/helpers/balance.js +++ b/src/helpers/balance.js @@ -140,7 +140,7 @@ export default function calculateBalance(props: CalculateBalance) { accounts: props.accounts, counterValues: props.counterValues, interval, - }).map(e => ({ name: e.date, value: e.balance })) + }).map(e => ({ date: e.date, value: e.balance })) const firstNonEmptyDay = find(allBalances, e => e.value) const refBalance = firstNonEmptyDay ? firstNonEmptyDay.value : 0 diff --git a/src/index.ejs b/src/index.ejs index f87f3eb1..db8f5c25 100644 --- a/src/index.ejs +++ b/src/index.ejs @@ -53,8 +53,6 @@ const initApp = (options = {}) => { const { force = false } = options - appEl.style.display = 'flex' - if (force) { preloadEl.remove() } else { diff --git a/src/styles/reset.js b/src/styles/reset.js index ebceaf00..6426099e 100644 --- a/src/styles/reset.js +++ b/src/styles/reset.js @@ -21,7 +21,7 @@ body { } #app { - display: none; + display: flex; flex-direction: column; min-height: 100vh; } diff --git a/static/i18n/en/transactionsList.yml b/static/i18n/en/transactionsList.yml index 07ce6d37..8147e850 100644 --- a/static/i18n/en/transactionsList.yml +++ b/static/i18n/en/transactionsList.yml @@ -5,3 +5,5 @@ amount: Amount from: From to: To showMore: Show more +confirmed: Confirmed +notConfirmed: Not confirmed diff --git a/yarn.lock b/yarn.lock index b48a3009..487d9750 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2655,6 +2655,10 @@ combined-stream@1.0.6, combined-stream@^1.0.5, combined-stream@~1.0.5: dependencies: delayed-stream "~1.0.0" +commander@2, commander@^2.13.0: + version "2.15.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.15.0.tgz#ad2a23a1c3b036e392469b8012cec6b33b4c1322" + commander@2.14.x, commander@^2.11.0, commander@^2.12.2, commander@^2.14.1, commander@^2.9.0, commander@~2.14.1: version "2.14.1" resolved "https://registry.yarnpkg.com/commander/-/commander-2.14.1.tgz#2235123e37af8ca3c65df45b026dbd357b01b9aa" @@ -2663,10 +2667,6 @@ commander@2.6.0: version "2.6.0" resolved "https://registry.yarnpkg.com/commander/-/commander-2.6.0.tgz#9df7e52fb2a0cb0fb89058ee80c3104225f37e1d" -commander@^2.13.0: - version "2.15.0" - resolved "https://registry.yarnpkg.com/commander/-/commander-2.15.0.tgz#ad2a23a1c3b036e392469b8012cec6b33b4c1322" - commander@~2.13.0: version "2.13.0" resolved "https://registry.yarnpkg.com/commander/-/commander-2.13.0.tgz#6964bca67685df7c1f1430c584f07d7597885b9c" @@ -3095,37 +3095,121 @@ cyclist@~0.2.2: version "0.2.2" resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-0.2.2.tgz#1b33792e11e914a2fd6d6ed6447464444e5fa640" -d3-array@^1.2.0: +d3-array@1, d3-array@1.2.1, d3-array@^1.2.0: version "1.2.1" resolved "https://registry.yarnpkg.com/d3-array/-/d3-array-1.2.1.tgz#d1ca33de2f6ac31efadb8e050a021d7e2396d5dc" -d3-collection@1: +d3-axis@1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/d3-axis/-/d3-axis-1.0.8.tgz#31a705a0b535e65759de14173a31933137f18efa" + +d3-brush@1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/d3-brush/-/d3-brush-1.0.4.tgz#00c2f238019f24f6c0a194a26d41a1530ffe7bc4" + dependencies: + d3-dispatch "1" + d3-drag "1" + d3-interpolate "1" + d3-selection "1" + d3-transition "1" + +d3-chord@1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/d3-chord/-/d3-chord-1.0.4.tgz#7dec4f0ba886f713fe111c45f763414f6f74ca2c" + dependencies: + d3-array "1" + d3-path "1" + +d3-collection@1, d3-collection@1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/d3-collection/-/d3-collection-1.0.4.tgz#342dfd12837c90974f33f1cc0a785aea570dcdc2" -d3-color@1: +d3-color@1, d3-color@1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/d3-color/-/d3-color-1.0.3.tgz#bc7643fca8e53a8347e2fbdaffa236796b58509b" -d3-ease@^1.0.0: +d3-dispatch@1, d3-dispatch@1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/d3-dispatch/-/d3-dispatch-1.0.3.tgz#46e1491eaa9b58c358fce5be4e8bed626e7871f8" + +d3-drag@1, d3-drag@1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/d3-drag/-/d3-drag-1.2.1.tgz#df8dd4c502fb490fc7462046a8ad98a5c479282d" + dependencies: + d3-dispatch "1" + d3-selection "1" + +d3-dsv@1, d3-dsv@1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/d3-dsv/-/d3-dsv-1.0.8.tgz#907e240d57b386618dc56468bacfe76bf19764ae" + dependencies: + commander "2" + iconv-lite "0.4" + rw "1" + +d3-ease@1, d3-ease@1.0.3, d3-ease@^1.0.0: version "1.0.3" resolved "https://registry.yarnpkg.com/d3-ease/-/d3-ease-1.0.3.tgz#68bfbc349338a380c44d8acc4fbc3304aa2d8c0e" -d3-format@1: +d3-force@1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/d3-force/-/d3-force-1.1.0.tgz#cebf3c694f1078fcc3d4daf8e567b2fbd70d4ea3" + dependencies: + d3-collection "1" + d3-dispatch "1" + d3-quadtree "1" + d3-timer "1" + +d3-format@1, d3-format@1.2.2: version "1.2.2" resolved "https://registry.yarnpkg.com/d3-format/-/d3-format-1.2.2.tgz#1a39c479c8a57fe5051b2e67a3bee27061a74e7a" -d3-interpolate@1, d3-interpolate@^1.1.1: +d3-geo@1.9.1: + version "1.9.1" + resolved "https://registry.yarnpkg.com/d3-geo/-/d3-geo-1.9.1.tgz#157e3b0f917379d0f73bebfff3be537f49fa7356" + dependencies: + d3-array "1" + +d3-hierarchy@1.1.5: + version "1.1.5" + resolved "https://registry.yarnpkg.com/d3-hierarchy/-/d3-hierarchy-1.1.5.tgz#a1c845c42f84a206bcf1c01c01098ea4ddaa7a26" + +d3-interpolate@1, d3-interpolate@1.1.6, d3-interpolate@^1.1.1: version "1.1.6" resolved "https://registry.yarnpkg.com/d3-interpolate/-/d3-interpolate-1.1.6.tgz#2cf395ae2381804df08aa1bf766b7f97b5f68fb6" dependencies: d3-color "1" -d3-path@1: +d3-path@1, d3-path@1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/d3-path/-/d3-path-1.0.5.tgz#241eb1849bd9e9e8021c0d0a799f8a0e8e441764" -d3-scale@^1.0.0: +d3-polygon@1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/d3-polygon/-/d3-polygon-1.0.3.tgz#16888e9026460933f2b179652ad378224d382c62" + +d3-quadtree@1, d3-quadtree@1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/d3-quadtree/-/d3-quadtree-1.0.3.tgz#ac7987e3e23fe805a990f28e1b50d38fcb822438" + +d3-queue@3.0.7: + version "3.0.7" + resolved "https://registry.yarnpkg.com/d3-queue/-/d3-queue-3.0.7.tgz#c93a2e54b417c0959129d7d73f6cf7d4292e7618" + +d3-random@1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/d3-random/-/d3-random-1.1.0.tgz#6642e506c6fa3a648595d2b2469788a8d12529d3" + +d3-request@1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/d3-request/-/d3-request-1.0.6.tgz#a1044a9ef4ec28c824171c9379fae6d79474b19f" + dependencies: + d3-collection "1" + d3-dispatch "1" + d3-dsv "1" + xmlhttprequest "1" + +d3-scale@1.0.7, d3-scale@^1.0.0: version "1.0.7" resolved "https://registry.yarnpkg.com/d3-scale/-/d3-scale-1.0.7.tgz#fa90324b3ea8a776422bd0472afab0b252a0945d" dependencies: @@ -3137,30 +3221,90 @@ d3-scale@^1.0.0: d3-time "1" d3-time-format "2" -d3-shape@^1.0.0, d3-shape@^1.2.0: +d3-selection@1, d3-selection@1.3.0, d3-selection@^1.1.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/d3-selection/-/d3-selection-1.3.0.tgz#d53772382d3dc4f7507bfb28bcd2d6aed2a0ad6d" + +d3-shape@1.2.0, d3-shape@^1.0.0, d3-shape@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/d3-shape/-/d3-shape-1.2.0.tgz#45d01538f064bafd05ea3d6d2cb748fd8c41f777" dependencies: d3-path "1" -d3-time-format@2: +d3-time-format@2, d3-time-format@2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/d3-time-format/-/d3-time-format-2.1.1.tgz#85b7cdfbc9ffca187f14d3c456ffda268081bb31" dependencies: d3-time "1" -d3-time@1: +d3-time@1, d3-time@1.0.8: version "1.0.8" resolved "https://registry.yarnpkg.com/d3-time/-/d3-time-1.0.8.tgz#dbd2d6007bf416fe67a76d17947b784bffea1e84" -d3-timer@^1.0.0: +d3-timer@1, d3-timer@1.0.7, d3-timer@^1.0.0: version "1.0.7" resolved "https://registry.yarnpkg.com/d3-timer/-/d3-timer-1.0.7.tgz#df9650ca587f6c96607ff4e60cc38229e8dd8531" -d3-voronoi@^1.1.2: +d3-transition@1, d3-transition@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/d3-transition/-/d3-transition-1.1.1.tgz#d8ef89c3b848735b060e54a39b32aaebaa421039" + dependencies: + d3-color "1" + d3-dispatch "1" + d3-ease "1" + d3-interpolate "1" + d3-selection "^1.1.0" + d3-timer "1" + +d3-voronoi@1.1.2, d3-voronoi@^1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/d3-voronoi/-/d3-voronoi-1.1.2.tgz#1687667e8f13a2d158c80c1480c5a29cb0d8973c" +d3-zoom@1.7.1: + version "1.7.1" + resolved "https://registry.yarnpkg.com/d3-zoom/-/d3-zoom-1.7.1.tgz#02f43b3c3e2db54f364582d7e4a236ccc5506b63" + dependencies: + d3-dispatch "1" + d3-drag "1" + d3-interpolate "1" + d3-selection "1" + d3-transition "1" + +d3@^4.13.0: + version "4.13.0" + resolved "https://registry.yarnpkg.com/d3/-/d3-4.13.0.tgz#ab236ff8cf0cfc27a81e69bf2fb7518bc9b4f33d" + dependencies: + d3-array "1.2.1" + d3-axis "1.0.8" + d3-brush "1.0.4" + d3-chord "1.0.4" + d3-collection "1.0.4" + d3-color "1.0.3" + d3-dispatch "1.0.3" + d3-drag "1.2.1" + d3-dsv "1.0.8" + d3-ease "1.0.3" + d3-force "1.1.0" + d3-format "1.2.2" + d3-geo "1.9.1" + d3-hierarchy "1.1.5" + d3-interpolate "1.1.6" + d3-path "1.0.5" + d3-polygon "1.0.3" + d3-quadtree "1.0.3" + d3-queue "3.0.7" + d3-random "1.1.0" + d3-request "1.0.6" + d3-scale "1.0.7" + d3-selection "1.3.0" + d3-shape "1.2.0" + d3-time "1.0.8" + d3-time-format "2.1.1" + d3-timer "1.0.7" + d3-transition "1.1.1" + d3-voronoi "1.1.2" + d3-zoom "1.7.1" + d@1: version "1.0.0" resolved "https://registry.yarnpkg.com/d/-/d-1.0.0.tgz#754bb5bfe55451da69a58b94d45f4c5b0462d58f" @@ -3572,7 +3716,7 @@ electron-builder-lib@~20.2.0: semver "^5.5.0" temp-file "^3.1.1" -electron-builder@^20.0.4, electron-builder@^20.4.0: +electron-builder@^20.4.0: version "20.4.0" resolved "https://registry.yarnpkg.com/electron-builder/-/electron-builder-20.4.0.tgz#d1393719339c17dd7c2dd16d58b4e138ca6646ce" dependencies: @@ -3736,7 +3880,7 @@ electron-webpack@1.13.0: webpack-merge "^4.1.1" yargs "^11.0.0" -electron@1.8.3, electron@^1.8.2: +electron@1.8.3: version "1.8.3" resolved "https://registry.yarnpkg.com/electron/-/electron-1.8.3.tgz#001416ea3a25ce594e317cb5531bc41eadd22f7f" dependencies: @@ -5269,7 +5413,7 @@ i18next@^10.5.0: version "10.5.0" resolved "https://registry.yarnpkg.com/i18next/-/i18next-10.5.0.tgz#a2a90e67774fa85b8ff9cd0063e697e482440de7" -iconv-lite@0.4.19, iconv-lite@^0.4.17, iconv-lite@^0.4.19, iconv-lite@~0.4.13: +iconv-lite@0.4, iconv-lite@0.4.19, iconv-lite@^0.4.17, iconv-lite@^0.4.19, iconv-lite@~0.4.13: version "0.4.19" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.19.tgz#f7468f60135f5e5dad3399c0a81be9a1603a082b" @@ -6326,9 +6470,6 @@ ledger-test-library@KhalilBellakrid/ledger-test-library-nodejs#7d37482: dependencies: axios "^0.17.1" bindings "^1.3.0" - electron "^1.8.2" - electron-builder "^20.0.4" - electron-rebuild "^1.7.3" nan "^2.6.2" prebuild-install "^2.2.2" @@ -8949,6 +9090,10 @@ run-queue@^1.0.0, run-queue@^1.0.3: dependencies: aproba "^1.1.1" +rw@1: + version "1.3.3" + resolved "https://registry.yarnpkg.com/rw/-/rw-1.3.3.tgz#3f862dfa91ab766b14885ef4d01124bfda074fb4" + rx-lite-aggregates@^4.0.8: version "4.0.8" resolved "https://registry.yarnpkg.com/rx-lite-aggregates/-/rx-lite-aggregates-4.0.8.tgz#753b87a89a11c95467c4ac1626c4efc4e05c67be" @@ -10686,6 +10831,10 @@ xmldom@0.1.x: version "0.1.27" resolved "https://registry.yarnpkg.com/xmldom/-/xmldom-0.1.27.tgz#d501f97b3bdb403af8ef9ecc20573187aadac0e9" +xmlhttprequest@1: + version "1.8.0" + resolved "https://registry.yarnpkg.com/xmlhttprequest/-/xmlhttprequest-1.8.0.tgz#67fe075c5c24fef39f9d65f5f7b7fe75171968fc" + xpipe@*: version "1.0.5" resolved "https://registry.yarnpkg.com/xpipe/-/xpipe-1.0.5.tgz#8dd8bf45fc3f7f55f0e054b878f43a62614dafdf"