mirror of
https://github.com/kremalicious/blowfish.git
synced 2025-02-14 21:10:35 +01:00
reafactor for css modules
This commit is contained in:
parent
e07b8be25c
commit
3745051637
@ -8,6 +8,3 @@ end_of_line = lf
|
|||||||
insert_final_newline = true
|
insert_final_newline = true
|
||||||
charset = utf-8
|
charset = utf-8
|
||||||
trim_trailing_whitespace = true
|
trim_trailing_whitespace = true
|
||||||
|
|
||||||
[*.css,*.scss]
|
|
||||||
indent_size = 4
|
|
||||||
|
@ -3,10 +3,5 @@
|
|||||||
"stylelint-config-standard",
|
"stylelint-config-standard",
|
||||||
"stylelint-config-css-modules",
|
"stylelint-config-css-modules",
|
||||||
"./node_modules/prettier-stylelint/config.js"
|
"./node_modules/prettier-stylelint/config.js"
|
||||||
],
|
]
|
||||||
"rules": {
|
|
||||||
"indentation": 4,
|
|
||||||
"declaration-empty-line-before": null,
|
|
||||||
"number-leading-zero": "never"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,9 @@
|
|||||||
"package": "electron-builder build -mwl -p never && open ./dist",
|
"package": "electron-builder build -mwl -p never && open ./dist",
|
||||||
"dist": "rm -rf {dist,build}/ && npm run build && npm run package",
|
"dist": "rm -rf {dist,build}/ && npm run build && npm run package",
|
||||||
"release": "release-it --non-interactive",
|
"release": "release-it --non-interactive",
|
||||||
"changelog": "auto-changelog -p"
|
"changelog": "auto-changelog -p",
|
||||||
|
"format": "prettier --write 'src/**/*.{js,jsx}' && npm run format:css",
|
||||||
|
"format:css": "prettier-stylelint --write --quiet 'src/**/*.{css,scss}'"
|
||||||
},
|
},
|
||||||
"repository": "https://github.com/kremalicious/blowfish.git",
|
"repository": "https://github.com/kremalicious/blowfish.git",
|
||||||
"homepage": "https://github.com/kremalicious/blowfish",
|
"homepage": "https://github.com/kremalicious/blowfish",
|
||||||
@ -38,6 +40,7 @@
|
|||||||
"auto-changelog": "^1.16.1",
|
"auto-changelog": "^1.16.1",
|
||||||
"babel-eslint": "^10.0.3",
|
"babel-eslint": "^10.0.3",
|
||||||
"babel-loader": "^8.0.6",
|
"babel-loader": "^8.0.6",
|
||||||
|
"classnames": "^2.2.6",
|
||||||
"copy-webpack-plugin": "^5.0.4",
|
"copy-webpack-plugin": "^5.0.4",
|
||||||
"cross-env": "^5.2.1",
|
"cross-env": "^5.2.1",
|
||||||
"css-loader": "^3.2.0",
|
"css-loader": "^3.2.0",
|
||||||
@ -51,14 +54,12 @@
|
|||||||
"file-loader": "^4.2.0",
|
"file-loader": "^4.2.0",
|
||||||
"html-webpack-plugin": "^3.2.0",
|
"html-webpack-plugin": "^3.2.0",
|
||||||
"mini-css-extract-plugin": "^0.8.0",
|
"mini-css-extract-plugin": "^0.8.0",
|
||||||
"node-sass": "^4.12.0",
|
|
||||||
"prettier": "^1.18.2",
|
"prettier": "^1.18.2",
|
||||||
"prettier-stylelint": "^0.4.2",
|
"prettier-stylelint": "^0.4.2",
|
||||||
"react": "^16.9.0",
|
"react": "^16.9.0",
|
||||||
"react-dom": "^16.9.0",
|
"react-dom": "^16.9.0",
|
||||||
"react-pose": "^4.0.8",
|
"react-pose": "^4.0.8",
|
||||||
"release-it": "^12.3.6",
|
"release-it": "^12.3.6",
|
||||||
"sass-loader": "^8.0.0",
|
|
||||||
"style-loader": "^1.0.0",
|
"style-loader": "^1.0.0",
|
||||||
"stylelint": "^10.1.0",
|
"stylelint": "^10.1.0",
|
||||||
"stylelint-config-css-modules": "^1.4.0",
|
"stylelint-config-css-modules": "^1.4.0",
|
||||||
|
@ -132,22 +132,22 @@ const installDevTools = async mainWindow => {
|
|||||||
const createWindowEvents = mainWindow => {
|
const createWindowEvents = mainWindow => {
|
||||||
mainWindow.on('enter-full-screen', () =>
|
mainWindow.on('enter-full-screen', () =>
|
||||||
mainWindow.webContents.executeJavaScript(
|
mainWindow.webContents.executeJavaScript(
|
||||||
'document.getElementsByTagName(\'html\')[0].classList.add(\'fullscreen\')'
|
"document.getElementsByTagName('html')[0].classList.add('fullscreen')"
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
mainWindow.on('leave-full-screen', () =>
|
mainWindow.on('leave-full-screen', () =>
|
||||||
mainWindow.webContents.executeJavaScript(
|
mainWindow.webContents.executeJavaScript(
|
||||||
'document.getElementsByTagName(\'html\')[0].classList.remove(\'fullscreen\')'
|
"document.getElementsByTagName('html')[0].classList.remove('fullscreen')"
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
mainWindow.on('blur', () =>
|
mainWindow.on('blur', () =>
|
||||||
mainWindow.webContents.executeJavaScript(
|
mainWindow.webContents.executeJavaScript(
|
||||||
'document.getElementsByTagName(\'html\')[0].classList.add(\'blur\')'
|
"document.getElementsByTagName('html')[0].classList.add('blur')"
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
mainWindow.on('focus', () =>
|
mainWindow.on('focus', () =>
|
||||||
mainWindow.webContents.executeJavaScript(
|
mainWindow.webContents.executeJavaScript(
|
||||||
'document.getElementsByTagName(\'html\')[0].classList.remove(\'blur\')'
|
"document.getElementsByTagName('html')[0].classList.remove('blur')"
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -191,10 +191,10 @@ const switchTheme = () => {
|
|||||||
|
|
||||||
isDarkMode
|
isDarkMode
|
||||||
? mainWindow.webContents.executeJavaScript(
|
? mainWindow.webContents.executeJavaScript(
|
||||||
'document.getElementsByTagName(\'html\')[0].classList.add(\'dark\')'
|
"document.getElementsByTagName('html')[0].classList.add('dark')"
|
||||||
)
|
)
|
||||||
: mainWindow.webContents.executeJavaScript(
|
: mainWindow.webContents.executeJavaScript(
|
||||||
'document.getElementsByTagName(\'html\')[0].classList.remove(\'dark\')'
|
"document.getElementsByTagName('html')[0].classList.remove('dark')"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,96 +0,0 @@
|
|||||||
*,
|
|
||||||
*::before,
|
|
||||||
*::after {
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
html,
|
|
||||||
body {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
background: #fcfcfc !important;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
html.dark,
|
|
||||||
.dark body {
|
|
||||||
background: #141414 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
html {
|
|
||||||
font-size: 13px;
|
|
||||||
}
|
|
||||||
|
|
||||||
html.fullscreen {
|
|
||||||
font-size: 24px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#root {
|
|
||||||
height: 100%;
|
|
||||||
position: relative;
|
|
||||||
font-size: 1rem;
|
|
||||||
line-height: 1.3;
|
|
||||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Ubuntu,
|
|
||||||
Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji',
|
|
||||||
'Segoe UI Symbol';
|
|
||||||
font-feature-settings: 'kern' 1, 'liga' 1, 'calt' 1, 'pnum' 1, 'tnum' 0,
|
|
||||||
'onum' 0, 'lnum' 0, 'dlig' 1;
|
|
||||||
color: #303030;
|
|
||||||
transform: translate3d(0, 0, 0);
|
|
||||||
-webkit-app-region: drag;
|
|
||||||
-webkit-user-select: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dark #root {
|
|
||||||
color: #e2e2e2;
|
|
||||||
}
|
|
||||||
|
|
||||||
h1,
|
|
||||||
h2,
|
|
||||||
h3,
|
|
||||||
h4,
|
|
||||||
h5 {
|
|
||||||
font-weight: 700;
|
|
||||||
}
|
|
||||||
|
|
||||||
a,
|
|
||||||
button {
|
|
||||||
text-decoration: none;
|
|
||||||
cursor: default;
|
|
||||||
}
|
|
||||||
|
|
||||||
a h1 {
|
|
||||||
color: unset;
|
|
||||||
}
|
|
||||||
|
|
||||||
.app {
|
|
||||||
padding: 10% 7% 5% 7%;
|
|
||||||
cursor: default;
|
|
||||||
transition: .15s ease-out;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
overflow-y: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.darwin .app {
|
|
||||||
padding-top: calc(35px + 10%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.fullscreen.darwin .app {
|
|
||||||
transform: translate3d(0, -36px, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
.box {
|
|
||||||
width: 100%;
|
|
||||||
padding: 5%;
|
|
||||||
background: #fff;
|
|
||||||
border-radius: 5px;
|
|
||||||
border: .1rem solid #e2e2e2;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dark .box {
|
|
||||||
background: #222;
|
|
||||||
border-color: #303030;
|
|
||||||
}
|
|
@ -10,10 +10,10 @@ import {
|
|||||||
import { webFrame, ipcRenderer } from 'electron'
|
import { webFrame, ipcRenderer } from 'electron'
|
||||||
import posed, { PoseGroup } from 'react-pose'
|
import posed, { PoseGroup } from 'react-pose'
|
||||||
import Titlebar from './components/Titlebar'
|
import Titlebar from './components/Titlebar'
|
||||||
|
import { defaultAnimation } from './components/Animations'
|
||||||
import Home from './screens/Home'
|
import Home from './screens/Home'
|
||||||
import Preferences from './screens/Preferences'
|
import Preferences from './screens/Preferences'
|
||||||
import './App.css'
|
import styles from './App.module.css'
|
||||||
import { defaultAnimation } from './components/Animations'
|
|
||||||
|
|
||||||
//
|
//
|
||||||
// Disable zooming
|
// Disable zooming
|
||||||
@ -55,7 +55,7 @@ export default class App extends PureComponent {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{process.platform === 'darwin' && <Titlebar />}
|
{process.platform === 'darwin' && <Titlebar />}
|
||||||
<div className="app">
|
<div className={styles.app}>
|
||||||
<PosedRouter>
|
<PosedRouter>
|
||||||
<Home path="/" default />
|
<Home path="/" default />
|
||||||
<Preferences path="/preferences" />
|
<Preferences path="/preferences" />
|
||||||
|
16
src/renderer/App.module.css
Normal file
16
src/renderer/App.module.css
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
.app {
|
||||||
|
padding: 10% 7% 5% 7%;
|
||||||
|
cursor: default;
|
||||||
|
transition: 0.15s ease-out;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.darwin) .app {
|
||||||
|
padding-top: calc(35px + 10%);
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.fullscreen.darwin) .app {
|
||||||
|
transform: translate3d(0, -36px, 0);
|
||||||
|
}
|
@ -1,33 +0,0 @@
|
|||||||
import React, { PureComponent } from 'react'
|
|
||||||
import PropTypes from 'prop-types'
|
|
||||||
import { openUrl } from '../../utils'
|
|
||||||
import Balance from './Balance'
|
|
||||||
|
|
||||||
export default class Account extends PureComponent {
|
|
||||||
static propTypes = {
|
|
||||||
account: PropTypes.shape({
|
|
||||||
address: PropTypes.string.isRequired,
|
|
||||||
balance: PropTypes.object.isRequired
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const { account } = this.props
|
|
||||||
const { balance, address } = account
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="number-unit">
|
|
||||||
<Balance balance={balance} />
|
|
||||||
<a
|
|
||||||
className="label"
|
|
||||||
onClick={() =>
|
|
||||||
openUrl(`https://etherscan.io/address/${address}#tokentxns`)
|
|
||||||
}
|
|
||||||
title="Click to view on Etherscan"
|
|
||||||
>
|
|
||||||
{address.substring(0, 12)}…
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,30 +1,36 @@
|
|||||||
import React, { PureComponent } from 'react'
|
import React, { useContext } from 'react'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import posed, { PoseGroup } from 'react-pose'
|
import posed, { PoseGroup } from 'react-pose'
|
||||||
import { AppContext } from '../store/createContext'
|
import { AppContext } from '../store/createContext'
|
||||||
import { cryptoFormatter } from '../../utils'
|
import { cryptoFormatter } from '../../utils'
|
||||||
import styles from './Balance.module.scss'
|
|
||||||
import { fadeIn } from './Animations'
|
import { fadeIn } from './Animations'
|
||||||
|
import Label from './Label'
|
||||||
|
import styles from './Balance.module.css'
|
||||||
|
|
||||||
const Animation = posed.h1(fadeIn)
|
const Animation = posed.h1(fadeIn)
|
||||||
|
|
||||||
export default class Balance extends PureComponent {
|
const Balance = ({ balance, label, large }) => {
|
||||||
static contextType = AppContext
|
const { currency } = useContext(AppContext)
|
||||||
|
|
||||||
static propTypes = {
|
|
||||||
balance: PropTypes.object.isRequired
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const { currency } = this.context
|
|
||||||
const { balance } = this.props
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<div className={styles.balance}>
|
||||||
<PoseGroup animateOnMount>
|
<PoseGroup animateOnMount>
|
||||||
<Animation key={currency} className={styles.number}>
|
<Animation
|
||||||
|
key={currency}
|
||||||
|
className={large ? styles.numberLarge : styles.number}
|
||||||
|
>
|
||||||
{cryptoFormatter(balance[currency], currency)}
|
{cryptoFormatter(balance[currency], currency)}
|
||||||
</Animation>
|
</Animation>
|
||||||
</PoseGroup>
|
</PoseGroup>
|
||||||
|
{label && <Label>{label}</Label>}
|
||||||
|
</div>
|
||||||
)
|
)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Balance.propTypes = {
|
||||||
|
balance: PropTypes.object.isRequired,
|
||||||
|
label: PropTypes.string,
|
||||||
|
large: PropTypes.bool
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Balance
|
||||||
|
33
src/renderer/components/Balance.module.css
Normal file
33
src/renderer/components/Balance.module.css
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
.balance {
|
||||||
|
text-align: center;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.number {
|
||||||
|
margin: 0;
|
||||||
|
-webkit-app-region: no-drag;
|
||||||
|
-webkit-user-select: text;
|
||||||
|
font-size: 1rem;
|
||||||
|
display: inline-block;
|
||||||
|
padding: 0 0.3rem;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.numberLarge {
|
||||||
|
composes: number;
|
||||||
|
font-size: 2.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.updated {
|
||||||
|
animation: updated 0.5s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes updated {
|
||||||
|
0% {
|
||||||
|
background: rgba(255, 255, 255, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
background: rgba(255, 255, 255, 0);
|
||||||
|
}
|
||||||
|
}
|
@ -1,27 +0,0 @@
|
|||||||
.number {
|
|
||||||
margin: 0;
|
|
||||||
-webkit-app-region: no-drag;
|
|
||||||
-webkit-user-select: text;
|
|
||||||
font-size: 1rem;
|
|
||||||
display: inline-block;
|
|
||||||
padding: 0 .3rem;
|
|
||||||
border-radius: 4px;
|
|
||||||
|
|
||||||
:global(.number-unit--main) & {
|
|
||||||
font-size: 2.5rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.updated {
|
|
||||||
animation: updated .5s ease-out;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes updated {
|
|
||||||
0% {
|
|
||||||
background: rgba(255, 255, 255, .2);
|
|
||||||
}
|
|
||||||
|
|
||||||
100% {
|
|
||||||
background: rgba(255, 255, 255, 0);
|
|
||||||
}
|
|
||||||
}
|
|
6
src/renderer/components/Divider.jsx
Normal file
6
src/renderer/components/Divider.jsx
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import styles from './Divider.module.css'
|
||||||
|
|
||||||
|
const Divider = () => <hr className={styles.divider} />
|
||||||
|
|
||||||
|
export default Divider
|
11
src/renderer/components/Divider.module.css
Normal file
11
src/renderer/components/Divider.module.css
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
.divider {
|
||||||
|
width: 100%;
|
||||||
|
height: 1px;
|
||||||
|
background: #e2e2e2;
|
||||||
|
margin-top: 5%;
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.dark) .divider {
|
||||||
|
background: #303030;
|
||||||
|
}
|
15
src/renderer/components/Label.jsx
Normal file
15
src/renderer/components/Label.jsx
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
|
import styles from './Label.module.css'
|
||||||
|
|
||||||
|
const Label = ({ children, ...props }) => (
|
||||||
|
<span className={styles.label} {...props}>
|
||||||
|
{children}
|
||||||
|
</span>
|
||||||
|
)
|
||||||
|
|
||||||
|
Label.propTypes = {
|
||||||
|
children: PropTypes.any.isRequired
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Label
|
7
src/renderer/components/Label.module.css
Normal file
7
src/renderer/components/Label.module.css
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
.label {
|
||||||
|
color: #8b98a9;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
display: block;
|
||||||
|
white-space: nowrap;
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
}
|
@ -1,5 +1,5 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import styles from './Spinner.module.scss'
|
import styles from './Spinner.module.css'
|
||||||
|
|
||||||
const Spinner = () => <div className={styles.spinner} />
|
const Spinner = () => <div className={styles.spinner} />
|
||||||
|
|
||||||
|
28
src/renderer/components/Spinner.module.css
Normal file
28
src/renderer/components/Spinner.module.css
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
.spinner {
|
||||||
|
position: relative;
|
||||||
|
text-align: center;
|
||||||
|
margin: auto;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spinner::before {
|
||||||
|
content: '';
|
||||||
|
box-sizing: border-box;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 50%;
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
margin-top: -20px;
|
||||||
|
margin-left: -10px;
|
||||||
|
border-radius: 50%;
|
||||||
|
border: 2px solid #7b1173;
|
||||||
|
border-top-color: #e000cf;
|
||||||
|
animation: spinner 0.6s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spinner {
|
||||||
|
to {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
@ -1,28 +0,0 @@
|
|||||||
.spinner {
|
|
||||||
position: relative;
|
|
||||||
text-align: center;
|
|
||||||
margin: auto;
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
&::before {
|
|
||||||
content: '';
|
|
||||||
box-sizing: border-box;
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 50%;
|
|
||||||
width: 20px;
|
|
||||||
height: 20px;
|
|
||||||
margin-top: -20px;
|
|
||||||
margin-left: -10px;
|
|
||||||
border-radius: 50%;
|
|
||||||
border: 2px solid #7b1173;
|
|
||||||
border-top-color: #e000cf;
|
|
||||||
animation: spinner .6s linear infinite;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes spinner {
|
|
||||||
to {
|
|
||||||
transform: rotate(360deg);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,56 +0,0 @@
|
|||||||
.ticker {
|
|
||||||
margin-top: 3rem;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ticker .number-unit {
|
|
||||||
margin-top: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ticker button {
|
|
||||||
background: none;
|
|
||||||
border: 1px solid #e2e2e2;
|
|
||||||
box-shadow: none;
|
|
||||||
margin: 0 auto;
|
|
||||||
outline: 0;
|
|
||||||
font-size: .75rem;
|
|
||||||
border-radius: .3rem;
|
|
||||||
padding: .3rem .4rem;
|
|
||||||
display: block;
|
|
||||||
width: 100%;
|
|
||||||
max-width: 12rem;
|
|
||||||
transition: border .2s ease-out;
|
|
||||||
color: #41474e;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ticker button:disabled {
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dark .ticker button {
|
|
||||||
border-color: #303030;
|
|
||||||
color: #8b98a9;
|
|
||||||
}
|
|
||||||
|
|
||||||
.label--price {
|
|
||||||
font-size: .95rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.change {
|
|
||||||
font-size: .65rem;
|
|
||||||
display: inline-block;
|
|
||||||
margin-left: .25rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.change--positive {
|
|
||||||
color: forestgreen;
|
|
||||||
}
|
|
||||||
|
|
||||||
.change--negative {
|
|
||||||
color: crimson;
|
|
||||||
}
|
|
||||||
|
|
||||||
.active .change--positive,
|
|
||||||
.active .change--negative {
|
|
||||||
color: #fff !important;
|
|
||||||
}
|
|
@ -1,75 +0,0 @@
|
|||||||
import React, { PureComponent } from 'react'
|
|
||||||
import PropTypes from 'prop-types'
|
|
||||||
import posed, { PoseGroup } from 'react-pose'
|
|
||||||
import { AppContext } from '../store/createContext'
|
|
||||||
import { cryptoFormatter } from '../../utils'
|
|
||||||
import './Ticker.css'
|
|
||||||
import { fadeIn } from './Animations'
|
|
||||||
|
|
||||||
const Item = posed.div(fadeIn)
|
|
||||||
|
|
||||||
const Change = ({ currency }) => (
|
|
||||||
<AppContext.Consumer>
|
|
||||||
{({ priceChanges }) => {
|
|
||||||
const isNegative = JSON.stringify(priceChanges[currency]).startsWith('-')
|
|
||||||
let classes = isNegative ? 'change--negative' : 'change--positive'
|
|
||||||
|
|
||||||
return (
|
|
||||||
<span className={`change ${classes}`} title="24hr change">
|
|
||||||
{!isNegative && '+'}
|
|
||||||
{Number(priceChanges[currency]).toFixed(1)}%
|
|
||||||
</span>
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
</AppContext.Consumer>
|
|
||||||
)
|
|
||||||
|
|
||||||
Change.propTypes = {
|
|
||||||
currency: PropTypes.string.isRequired
|
|
||||||
}
|
|
||||||
|
|
||||||
export default class Ticker extends PureComponent {
|
|
||||||
static contextType = AppContext
|
|
||||||
|
|
||||||
items = () => {
|
|
||||||
const {
|
|
||||||
prices,
|
|
||||||
needsConfig,
|
|
||||||
currency,
|
|
||||||
toggleCurrencies,
|
|
||||||
accentColor
|
|
||||||
} = this.context
|
|
||||||
|
|
||||||
const activeStyle = {
|
|
||||||
backgroundColor: accentColor,
|
|
||||||
color: '#fff',
|
|
||||||
borderColor: accentColor
|
|
||||||
}
|
|
||||||
|
|
||||||
// convert Map to array first, cause for...of or forEach returns
|
|
||||||
// undefined, so it cannot be mapped to a collection of elements
|
|
||||||
return [...prices.entries()].map(([key, value]) => (
|
|
||||||
<Item key={key} className="number-unit">
|
|
||||||
<button
|
|
||||||
className={`label label--price ${key === currency && 'active'}`}
|
|
||||||
onClick={() => toggleCurrencies(key)}
|
|
||||||
disabled={needsConfig}
|
|
||||||
style={key === currency && !needsConfig ? activeStyle : {}}
|
|
||||||
>
|
|
||||||
{cryptoFormatter(value, key)}
|
|
||||||
{key !== 'ocean' && <Change currency={key} />}
|
|
||||||
</button>
|
|
||||||
</Item>
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<footer className="ticker" {...this.props}>
|
|
||||||
<div className="number-unit-wrap">
|
|
||||||
<PoseGroup animateOnMount>{this.items()}</PoseGroup>
|
|
||||||
</div>
|
|
||||||
</footer>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,6 +1,6 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import pkg from '../../../package.json'
|
import pkg from '../../../package.json'
|
||||||
import styles from './Titlebar.module.scss'
|
import styles from './Titlebar.module.css'
|
||||||
|
|
||||||
const Titlebar = () => (
|
const Titlebar = () => (
|
||||||
<header className={styles.titlebar}>
|
<header className={styles.titlebar}>
|
||||||
|
48
src/renderer/components/Titlebar.module.css
Normal file
48
src/renderer/components/Titlebar.module.css
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
.titlebar {
|
||||||
|
position: fixed;
|
||||||
|
width: 100%;
|
||||||
|
height: 35px;
|
||||||
|
line-height: 35px;
|
||||||
|
text-align: center;
|
||||||
|
user-select: none;
|
||||||
|
background: linear-gradient(to top, #ccc 0%, #d6d6d6 1px, #ebebeb 100%);
|
||||||
|
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.5);
|
||||||
|
transition: opacity 0.15s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.dark) .titlebar {
|
||||||
|
background: linear-gradient(to top, #141416 0%, #38383c 1px, #3f3f44 100%);
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.fullscreen) .titlebar {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.blur) .titlebar {
|
||||||
|
background: #f6f6f6;
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.blur.dark) .titlebar {
|
||||||
|
background: linear-gradient(to top, #141416 0%, #2d2a32 1px, #2d2a32 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.titlebarTitle {
|
||||||
|
line-height: 35px;
|
||||||
|
height: 35px;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 13px;
|
||||||
|
color: #555;
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.dark) .titlebarTitle {
|
||||||
|
color: #b6b3ba;
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.blur) .titlebarTitle {
|
||||||
|
color: #b6b6b6;
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.blur.dark) .titlebarTitle {
|
||||||
|
color: #67666e;
|
||||||
|
}
|
@ -1,58 +0,0 @@
|
|||||||
.titlebar {
|
|
||||||
position: fixed;
|
|
||||||
width: 100%;
|
|
||||||
height: 35px;
|
|
||||||
line-height: 35px;
|
|
||||||
text-align: center;
|
|
||||||
user-select: none;
|
|
||||||
background: linear-gradient(to top, #ccc 0%, #d6d6d6 1px, #ebebeb 100%);
|
|
||||||
box-shadow: inset 0 1px 0 rgba(255, 255, 255, .5);
|
|
||||||
transition: opacity .15s ease-out;
|
|
||||||
|
|
||||||
:global(.dark) & {
|
|
||||||
background: linear-gradient(
|
|
||||||
to top,
|
|
||||||
#141416 0%,
|
|
||||||
#38383c 1px,
|
|
||||||
#3f3f44 100%
|
|
||||||
);
|
|
||||||
box-shadow: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
:global(.fullscreen) & {
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
:global(.blur) & {
|
|
||||||
background: #f6f6f6;
|
|
||||||
}
|
|
||||||
|
|
||||||
:global(.blur.dark) & {
|
|
||||||
background: linear-gradient(
|
|
||||||
to top,
|
|
||||||
#141416 0%,
|
|
||||||
#2d2a32 1px,
|
|
||||||
#2d2a32 100%
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.titlebarTitle {
|
|
||||||
line-height: 35px;
|
|
||||||
height: 35px;
|
|
||||||
font-weight: 400;
|
|
||||||
font-size: 13px;
|
|
||||||
color: #555;
|
|
||||||
|
|
||||||
:global(.dark) & {
|
|
||||||
color: #b6b3ba;
|
|
||||||
}
|
|
||||||
|
|
||||||
:global(.blur) & {
|
|
||||||
color: #b6b6b6;
|
|
||||||
}
|
|
||||||
|
|
||||||
:global(.blur.dark) & {
|
|
||||||
color: #67666e;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,44 +0,0 @@
|
|||||||
import React, { PureComponent } from 'react'
|
|
||||||
import { AppContext } from '../store/createContext'
|
|
||||||
import Balance from './Balance'
|
|
||||||
import { conversions } from '../../config'
|
|
||||||
|
|
||||||
const calculateTotalBalance = (accounts, currency) => {
|
|
||||||
const balanceTotalArray = []
|
|
||||||
|
|
||||||
for (const account of accounts) {
|
|
||||||
balanceTotalArray.push(account.balance[currency])
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert array to numbers and add numbers together
|
|
||||||
const balanceTotal = balanceTotalArray.reduce(
|
|
||||||
(a, b) => Number(a) + Number(b),
|
|
||||||
0
|
|
||||||
)
|
|
||||||
|
|
||||||
return balanceTotal
|
|
||||||
}
|
|
||||||
|
|
||||||
export default class Total extends PureComponent {
|
|
||||||
static contextType = AppContext
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const conversionsBalance = Object.assign(
|
|
||||||
...conversions.map(key => ({
|
|
||||||
[key]: calculateTotalBalance(this.context.accounts, key)
|
|
||||||
}))
|
|
||||||
)
|
|
||||||
|
|
||||||
const balanceNew = {
|
|
||||||
ocean: calculateTotalBalance(this.context.accounts, 'ocean'),
|
|
||||||
...conversionsBalance
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="number-unit number-unit--main">
|
|
||||||
<Balance balance={balanceNew} />
|
|
||||||
<span className="label">Total Balance</span>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,20 +1,20 @@
|
|||||||
import React from 'react'
|
import React, { useContext } from 'react'
|
||||||
import { Link } from '@reach/router'
|
import { Link } from '@reach/router'
|
||||||
import IconRocket from '../images/rocket.svg'
|
|
||||||
import styles from './Welcome.module.scss'
|
|
||||||
import { AppContext } from '../store/createContext'
|
import { AppContext } from '../store/createContext'
|
||||||
|
import IconRocket from '../images/rocket.svg'
|
||||||
|
import styles from './Welcome.module.css'
|
||||||
|
|
||||||
const Welcome = () => (
|
const Welcome = () => {
|
||||||
|
const { accentColor } = useContext(AppContext)
|
||||||
|
|
||||||
|
return (
|
||||||
<div className={styles.welcome}>
|
<div className={styles.welcome}>
|
||||||
<IconRocket />
|
<IconRocket />
|
||||||
<AppContext.Consumer>
|
<Link style={{ color: accentColor }} to="preferences">
|
||||||
{context => (
|
|
||||||
<Link style={{ color: context.accentColor }} to="preferences">
|
|
||||||
Add your first address to get started.
|
Add your first address to get started.
|
||||||
</Link>
|
</Link>
|
||||||
)}
|
|
||||||
</AppContext.Consumer>
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
|
||||||
export default Welcome
|
export default Welcome
|
||||||
|
18
src/renderer/components/Welcome.module.css
Normal file
18
src/renderer/components/Welcome.module.css
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
.welcome {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
text-align: center;
|
||||||
|
margin-top: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.welcome svg {
|
||||||
|
display: block;
|
||||||
|
width: 5rem;
|
||||||
|
height: 5rem;
|
||||||
|
margin: 0 auto 2rem auto;
|
||||||
|
fill: #e2e2e2;
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.dark) .welcome svg {
|
||||||
|
fill: #41474e;
|
||||||
|
}
|
@ -1,18 +0,0 @@
|
|||||||
.welcome {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
text-align: center;
|
|
||||||
margin-top: 2rem;
|
|
||||||
|
|
||||||
svg {
|
|
||||||
display: block;
|
|
||||||
width: 5rem;
|
|
||||||
height: 5rem;
|
|
||||||
margin: 0 auto 2rem auto;
|
|
||||||
fill: #e2e2e2;
|
|
||||||
}
|
|
||||||
|
|
||||||
:global(.dark) & svg {
|
|
||||||
fill: #41474e;
|
|
||||||
}
|
|
||||||
}
|
|
79
src/renderer/index.css
Normal file
79
src/renderer/index.css
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
*,
|
||||||
|
*::before,
|
||||||
|
*::after {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
html,
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: #fcfcfc !important;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
html.dark,
|
||||||
|
.dark body {
|
||||||
|
background: #141414 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
html {
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
html.fullscreen {
|
||||||
|
font-size: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#root {
|
||||||
|
height: 100%;
|
||||||
|
position: relative;
|
||||||
|
font-size: 1rem;
|
||||||
|
line-height: 1.3;
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Ubuntu,
|
||||||
|
Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji',
|
||||||
|
'Segoe UI Symbol';
|
||||||
|
font-feature-settings: 'kern' 1, 'liga' 1, 'calt' 1, 'pnum' 1, 'tnum' 0,
|
||||||
|
'onum' 0, 'lnum' 0, 'dlig' 1;
|
||||||
|
color: #303030;
|
||||||
|
transform: translate3d(0, 0, 0);
|
||||||
|
-webkit-app-region: drag;
|
||||||
|
-webkit-user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark #root {
|
||||||
|
color: #e2e2e2;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1,
|
||||||
|
h2,
|
||||||
|
h3,
|
||||||
|
h4,
|
||||||
|
h5 {
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
a,
|
||||||
|
button {
|
||||||
|
text-decoration: none;
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
|
||||||
|
a h1 {
|
||||||
|
color: unset;
|
||||||
|
}
|
||||||
|
|
||||||
|
.box {
|
||||||
|
width: 100%;
|
||||||
|
padding: 5%;
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 5px;
|
||||||
|
border: 0.1rem solid #e2e2e2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .box {
|
||||||
|
background: #222;
|
||||||
|
border-color: #303030;
|
||||||
|
}
|
@ -3,6 +3,7 @@ import { render } from 'react-dom'
|
|||||||
import AppProvider from './store/AppProvider'
|
import AppProvider from './store/AppProvider'
|
||||||
import App from './App'
|
import App from './App'
|
||||||
import pkg from '../../package.json'
|
import pkg from '../../package.json'
|
||||||
|
import './index.css'
|
||||||
|
|
||||||
document.body.style.backgroundColor = '#141414'
|
document.body.style.backgroundColor = '#141414'
|
||||||
|
|
||||||
|
@ -1,60 +0,0 @@
|
|||||||
.main {
|
|
||||||
min-height: 222px;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.preferences-link {
|
|
||||||
position: absolute;
|
|
||||||
right: 5%;
|
|
||||||
top: 1.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.preferences-link svg {
|
|
||||||
fill: #8b98a9;
|
|
||||||
transition: .2s ease-out;
|
|
||||||
width: 1.25rem;
|
|
||||||
height: 1.25rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dark .preferences-link svg {
|
|
||||||
fill: #8b98a9;
|
|
||||||
opacity: .5;
|
|
||||||
}
|
|
||||||
|
|
||||||
.number-unit-wrap {
|
|
||||||
display: grid;
|
|
||||||
grid-gap: .5rem;
|
|
||||||
grid-template-columns: repeat(auto-fit, minmax(7rem, 1fr));
|
|
||||||
justify-items: start;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.number-unit {
|
|
||||||
text-align: center;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.label {
|
|
||||||
color: #8b98a9;
|
|
||||||
font-size: .85rem;
|
|
||||||
display: block;
|
|
||||||
white-space: nowrap;
|
|
||||||
margin-top: .5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.number-unit-wrap--accounts {
|
|
||||||
min-height: 55px;
|
|
||||||
padding-top: 2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.number-unit--main {
|
|
||||||
padding-bottom: 5%;
|
|
||||||
border-bottom: 1px solid #e2e2e2;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dark .number-unit--main {
|
|
||||||
border-bottom-color: #303030;
|
|
||||||
}
|
|
@ -1,46 +0,0 @@
|
|||||||
import React, { PureComponent } from 'react'
|
|
||||||
import { Link } from '@reach/router'
|
|
||||||
import { AppContext } from '../store/createContext'
|
|
||||||
import Welcome from '../components/Welcome'
|
|
||||||
import Total from '../components/Total'
|
|
||||||
import Account from '../components/Account'
|
|
||||||
import Ticker from '../components/Ticker'
|
|
||||||
import Spinner from '../components/Spinner'
|
|
||||||
import IconCog from '../images/cog.svg'
|
|
||||||
import './Home.css'
|
|
||||||
|
|
||||||
export default class Home extends PureComponent {
|
|
||||||
static contextType = AppContext
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const { isLoading, accounts, needsConfig } = this.context
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<main className="main box">
|
|
||||||
<Link className="preferences-link" to="/preferences">
|
|
||||||
<IconCog />
|
|
||||||
</Link>
|
|
||||||
|
|
||||||
{needsConfig ? (
|
|
||||||
<Welcome />
|
|
||||||
) : isLoading ? (
|
|
||||||
<Spinner />
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<Total />
|
|
||||||
|
|
||||||
<div className="number-unit-wrap number-unit-wrap--accounts">
|
|
||||||
{accounts.map((account, i) => (
|
|
||||||
<Account key={i} account={account} />
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</main>
|
|
||||||
|
|
||||||
<Ticker style={isLoading ? { opacity: 0 } : null} />
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
27
src/renderer/screens/Home/Account.jsx
Normal file
27
src/renderer/screens/Home/Account.jsx
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
|
import { openUrl } from '../../../utils'
|
||||||
|
import Balance from '../../components/Balance'
|
||||||
|
|
||||||
|
const Account = ({ account }) => {
|
||||||
|
const { balance, address } = account
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Balance
|
||||||
|
balance={balance}
|
||||||
|
label={`${address.substring(0, 12)}...`}
|
||||||
|
labelOnClick={() =>
|
||||||
|
openUrl(`https://etherscan.io/address/${address}#tokentxns`)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Account.propTypes = {
|
||||||
|
account: PropTypes.shape({
|
||||||
|
address: PropTypes.string.isRequired,
|
||||||
|
balance: PropTypes.object.isRequired
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Account
|
71
src/renderer/screens/Home/Ticker.jsx
Normal file
71
src/renderer/screens/Home/Ticker.jsx
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
import React, { useContext } from 'react'
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
|
import posed, { PoseGroup } from 'react-pose'
|
||||||
|
import { AppContext } from '../../store/createContext'
|
||||||
|
import { cryptoFormatter } from '../../../utils'
|
||||||
|
import stylesIndex from './index.module.css'
|
||||||
|
import styles from './Ticker.module.css'
|
||||||
|
import { fadeIn } from '../../components/Animations'
|
||||||
|
|
||||||
|
const Item = posed.div(fadeIn)
|
||||||
|
|
||||||
|
const Change = ({ currency }) => {
|
||||||
|
const { priceChanges } = useContext(AppContext)
|
||||||
|
const isNegative = JSON.stringify(priceChanges[currency]).startsWith('-')
|
||||||
|
let classes = isNegative ? styles.negative : styles.positive
|
||||||
|
|
||||||
|
return (
|
||||||
|
<span className={classes} title="24hr change">
|
||||||
|
{!isNegative && '+'}
|
||||||
|
{Number(priceChanges[currency]).toFixed(1)}%
|
||||||
|
</span>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Change.propTypes = {
|
||||||
|
currency: PropTypes.string.isRequired
|
||||||
|
}
|
||||||
|
|
||||||
|
const Items = () => {
|
||||||
|
const {
|
||||||
|
prices,
|
||||||
|
needsConfig,
|
||||||
|
currency,
|
||||||
|
toggleCurrencies,
|
||||||
|
accentColor
|
||||||
|
} = useContext(AppContext)
|
||||||
|
|
||||||
|
const activeStyle = {
|
||||||
|
backgroundColor: accentColor,
|
||||||
|
color: '#fff',
|
||||||
|
borderColor: accentColor
|
||||||
|
}
|
||||||
|
|
||||||
|
// convert Map to array first, cause for...of or forEach returns
|
||||||
|
// undefined, so it cannot be mapped to a collection of elements
|
||||||
|
return [...prices.entries()].map(([key, value]) => (
|
||||||
|
<Item key={key} className={styles.number}>
|
||||||
|
<button
|
||||||
|
className={key === currency ? styles.labelActive : styles.label}
|
||||||
|
onClick={() => toggleCurrencies(key)}
|
||||||
|
disabled={needsConfig}
|
||||||
|
style={key === currency && !needsConfig ? activeStyle : {}}
|
||||||
|
>
|
||||||
|
{cryptoFormatter(value, key)}
|
||||||
|
{key !== 'ocean' && <Change currency={key} />}
|
||||||
|
</button>
|
||||||
|
</Item>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
const Ticker = props => (
|
||||||
|
<footer className={styles.ticker} {...props}>
|
||||||
|
<div className={stylesIndex.balanceWrap}>
|
||||||
|
<PoseGroup animateOnMount>
|
||||||
|
<Items key="items" />
|
||||||
|
</PoseGroup>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
)
|
||||||
|
|
||||||
|
export default Ticker
|
76
src/renderer/screens/Home/Ticker.module.css
Normal file
76
src/renderer/screens/Home/Ticker.module.css
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
.ticker {
|
||||||
|
margin-top: 3rem;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ticker button {
|
||||||
|
background: none;
|
||||||
|
border: 1px solid #e2e2e2;
|
||||||
|
box-shadow: none;
|
||||||
|
margin: 0 auto;
|
||||||
|
outline: 0;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
border-radius: 0.3rem;
|
||||||
|
padding: 0.3rem 0.4rem;
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 12rem;
|
||||||
|
transition: border 0.2s ease-out;
|
||||||
|
color: #41474e;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ticker button:disabled {
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.dark) .ticker button {
|
||||||
|
border-color: #303030;
|
||||||
|
color: #8b98a9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.label--price {
|
||||||
|
font-size: 0.95rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.change {
|
||||||
|
font-size: 0.65rem;
|
||||||
|
display: inline-block;
|
||||||
|
margin-left: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.positive {
|
||||||
|
composes: change;
|
||||||
|
color: forestgreen;
|
||||||
|
}
|
||||||
|
|
||||||
|
.negative {
|
||||||
|
composes: change;
|
||||||
|
color: crimson;
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.active) .positive,
|
||||||
|
:global(.active) .negative {
|
||||||
|
color: #fff !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.number {
|
||||||
|
text-align: center;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.label {
|
||||||
|
color: #8b98a9;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
display: block;
|
||||||
|
white-space: nowrap;
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.labelActive {
|
||||||
|
composes: label;
|
||||||
|
}
|
||||||
|
|
||||||
|
.labelActive .positive,
|
||||||
|
.labelActive .negative {
|
||||||
|
color: #fff !important;
|
||||||
|
}
|
39
src/renderer/screens/Home/Total.jsx
Normal file
39
src/renderer/screens/Home/Total.jsx
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
import React, { useContext } from 'react'
|
||||||
|
import { AppContext } from '../../store/createContext'
|
||||||
|
import Balance from '../../components/Balance'
|
||||||
|
import { conversions } from '../../../config'
|
||||||
|
|
||||||
|
const calculateTotalBalance = (accounts, currency) => {
|
||||||
|
const balanceTotalArray = []
|
||||||
|
|
||||||
|
for (const account of accounts) {
|
||||||
|
balanceTotalArray.push(account.balance[currency])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert array to numbers and add numbers together
|
||||||
|
const balanceTotal = balanceTotalArray.reduce(
|
||||||
|
(a, b) => Number(a) + Number(b),
|
||||||
|
0
|
||||||
|
)
|
||||||
|
|
||||||
|
return balanceTotal
|
||||||
|
}
|
||||||
|
|
||||||
|
const Total = () => {
|
||||||
|
const { accounts } = useContext(AppContext)
|
||||||
|
|
||||||
|
const conversionsBalance = Object.assign(
|
||||||
|
...conversions.map(key => ({
|
||||||
|
[key]: calculateTotalBalance(accounts, key)
|
||||||
|
}))
|
||||||
|
)
|
||||||
|
|
||||||
|
const balanceNew = {
|
||||||
|
ocean: calculateTotalBalance(accounts, 'ocean'),
|
||||||
|
...conversionsBalance
|
||||||
|
}
|
||||||
|
|
||||||
|
return <Balance balance={balanceNew} label="Total Balance" large />
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Total
|
54
src/renderer/screens/Home/index.jsx
Normal file
54
src/renderer/screens/Home/index.jsx
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
import React, { useContext } from 'react'
|
||||||
|
import { Link } from '@reach/router'
|
||||||
|
import classnames from 'classnames'
|
||||||
|
import { AppContext } from '../../store/createContext'
|
||||||
|
import Welcome from '../../components/Welcome'
|
||||||
|
import Spinner from '../../components/Spinner'
|
||||||
|
import Divider from '../../components/Divider'
|
||||||
|
import Total from './Total'
|
||||||
|
import Account from './Account'
|
||||||
|
import Ticker from './Ticker'
|
||||||
|
import IconCog from '../../images/cog.svg'
|
||||||
|
import styles from './index.module.css'
|
||||||
|
|
||||||
|
const Accounts = () => {
|
||||||
|
const { accounts } = useContext(AppContext)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.balanceWrapAccounts}>
|
||||||
|
{accounts.map((account, i) => (
|
||||||
|
<Account key={i} account={account} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const Home = () => {
|
||||||
|
const { isLoading, needsConfig } = useContext(AppContext)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<main className={classnames(styles.main, 'box')}>
|
||||||
|
<Link className={styles.preferences} to="/preferences">
|
||||||
|
<IconCog />
|
||||||
|
</Link>
|
||||||
|
|
||||||
|
{needsConfig ? (
|
||||||
|
<Welcome />
|
||||||
|
) : isLoading ? (
|
||||||
|
<Spinner />
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<Total />
|
||||||
|
<Divider />
|
||||||
|
<Accounts />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<Ticker style={isLoading ? { opacity: 0 } : null} />
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Home
|
39
src/renderer/screens/Home/index.module.css
Normal file
39
src/renderer/screens/Home/index.module.css
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
.main {
|
||||||
|
min-height: 222px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preferences {
|
||||||
|
position: absolute;
|
||||||
|
right: 5%;
|
||||||
|
top: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preferences svg {
|
||||||
|
fill: #8b98a9;
|
||||||
|
transition: 0.2s ease-out;
|
||||||
|
width: 1.25rem;
|
||||||
|
height: 1.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.dark) .preferences svg {
|
||||||
|
fill: #8b98a9;
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.balanceWrap {
|
||||||
|
display: grid;
|
||||||
|
grid-gap: 0.5rem;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(7rem, 1fr));
|
||||||
|
justify-items: start;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.balanceWrapAccounts {
|
||||||
|
composes: balanceWrap;
|
||||||
|
min-height: 55px;
|
||||||
|
padding-top: 2rem;
|
||||||
|
}
|
@ -99,7 +99,9 @@ export default class Accounts extends PureComponent {
|
|||||||
this.setState({ error: 'Address already added. Try another one.' })
|
this.setState({ error: 'Address already added. Try another one.' })
|
||||||
return
|
return
|
||||||
} else if (!isAddress) {
|
} else if (!isAddress) {
|
||||||
this.setState({ error: 'Not an Ethereum address. Try another one.' })
|
this.setState({
|
||||||
|
error: 'Not an Ethereum address. Try another one.'
|
||||||
|
})
|
||||||
return
|
return
|
||||||
} else {
|
} else {
|
||||||
const joined = [...accounts, input]
|
const joined = [...accounts, input]
|
||||||
|
@ -40,8 +40,8 @@
|
|||||||
list-style: none;
|
list-style: none;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
border-bottom: 1px solid #e2e2e2;
|
border-bottom: 1px solid #e2e2e2;
|
||||||
padding-top: .25rem;
|
padding-top: 0.25rem;
|
||||||
padding-bottom: .25rem;
|
padding-bottom: 0.25rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .preference__list li {
|
.dark .preference__list li {
|
||||||
@ -61,9 +61,9 @@
|
|||||||
button.delete {
|
button.delete {
|
||||||
position: relative;
|
position: relative;
|
||||||
font-size: 2rem;
|
font-size: 2rem;
|
||||||
top: -.2rem;
|
top: -0.2rem;
|
||||||
color: #41474e;
|
color: #41474e;
|
||||||
transition: color .5s ease-out;
|
transition: color 0.5s ease-out;
|
||||||
}
|
}
|
||||||
|
|
||||||
.preference {
|
.preference {
|
||||||
@ -75,7 +75,7 @@ button.delete {
|
|||||||
.preference__help {
|
.preference__help {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
margin-bottom: .5rem;
|
margin-bottom: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.preference__title {
|
.preference__title {
|
||||||
@ -84,14 +84,14 @@ button.delete {
|
|||||||
|
|
||||||
.preference__help {
|
.preference__help {
|
||||||
color: #8b98a9;
|
color: #8b98a9;
|
||||||
margin-left: .5rem;
|
margin-left: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.preference .identicon {
|
.preference .identicon {
|
||||||
width: 1.5rem !important;
|
width: 1.5rem !important;
|
||||||
height: 1.5rem !important;
|
height: 1.5rem !important;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
margin-right: .75rem;
|
margin-right: 0.75rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.preference__input {
|
.preference__input {
|
||||||
@ -101,8 +101,8 @@ button.delete {
|
|||||||
border: 0;
|
border: 0;
|
||||||
width: 80%;
|
width: 80%;
|
||||||
color: #303030;
|
color: #303030;
|
||||||
margin-top: .75rem;
|
margin-top: 0.75rem;
|
||||||
margin-bottom: .75rem;
|
margin-bottom: 0.75rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .preference__input {
|
.dark .preference__input {
|
||||||
@ -110,5 +110,5 @@ button.delete {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.preference__error {
|
.preference__error {
|
||||||
font-size: .9rem;
|
font-size: 0.9rem;
|
||||||
}
|
}
|
||||||
|
@ -1,13 +1,9 @@
|
|||||||
import React, { PureComponent } from 'react'
|
import React from 'react'
|
||||||
import { Link } from '@reach/router'
|
import { Link } from '@reach/router'
|
||||||
|
import Accounts from './Accounts'
|
||||||
import './index.css'
|
import './index.css'
|
||||||
|
|
||||||
import Accounts from './Accounts'
|
const Preferences = () => (
|
||||||
|
|
||||||
export default class Preferences extends PureComponent {
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<div className="preferences">
|
<div className="preferences">
|
||||||
<h1 className="preferences__title">Preferences</h1>{' '}
|
<h1 className="preferences__title">Preferences</h1>{' '}
|
||||||
<Link className="preferences__close" title="Close Preferences" to="/">
|
<Link className="preferences__close" title="Close Preferences" to="/">
|
||||||
@ -15,6 +11,6 @@ export default class Preferences extends PureComponent {
|
|||||||
</Link>
|
</Link>
|
||||||
<Accounts />
|
<Accounts />
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
|
||||||
}
|
export default Preferences
|
||||||
|
@ -24,11 +24,12 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
test: /\.css$/,
|
test: /\.css$/,
|
||||||
|
exclude: /\.module\.css$/,
|
||||||
use: ['style-loader', 'css-loader'],
|
use: ['style-loader', 'css-loader'],
|
||||||
include: defaultInclude
|
include: defaultInclude
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
test: /\.module\.(scss|sass)$/,
|
test: /\.module\.css$/,
|
||||||
include: defaultInclude,
|
include: defaultInclude,
|
||||||
loader: [
|
loader: [
|
||||||
isDevelopment ? 'style-loader' : MiniCssExtractPlugin.loader,
|
isDevelopment ? 'style-loader' : MiniCssExtractPlugin.loader,
|
||||||
@ -41,26 +42,6 @@ module.exports = {
|
|||||||
localsConvention: 'camelCase',
|
localsConvention: 'camelCase',
|
||||||
sourceMap: isDevelopment
|
sourceMap: isDevelopment
|
||||||
}
|
}
|
||||||
},
|
|
||||||
{
|
|
||||||
loader: 'sass-loader',
|
|
||||||
options: {
|
|
||||||
sourceMap: isDevelopment
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
test: /\.(scss|sass)$/,
|
|
||||||
exclude: /\.module.(scss|sass)$/,
|
|
||||||
loader: [
|
|
||||||
isDevelopment ? 'style-loader' : MiniCssExtractPlugin.loader,
|
|
||||||
'css-loader',
|
|
||||||
{
|
|
||||||
loader: 'sass-loader',
|
|
||||||
options: {
|
|
||||||
sourceMap: isDevelopment
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
Loading…
x
Reference in New Issue
Block a user