Merge branch 'NewUI-flat' into merge
3
.gitignore
vendored
@ -24,6 +24,9 @@ test/background.js
|
||||
test/bundle.js
|
||||
test/test-bundle.js
|
||||
|
||||
#ignore css output and sourcemaps
|
||||
ui/app/css/output/
|
||||
|
||||
notes.txt
|
||||
|
||||
.coveralls.yml
|
||||
|
10
.stylelintignore
Normal file
@ -0,0 +1,10 @@
|
||||
app/
|
||||
development/
|
||||
dist/
|
||||
docs/
|
||||
fonts/
|
||||
images/
|
||||
mascara/
|
||||
node_modules/
|
||||
notices/
|
||||
test/
|
50
.stylelintrc
Normal file
@ -0,0 +1,50 @@
|
||||
{
|
||||
"extends": "stylelint-config-standard",
|
||||
"rules": {
|
||||
"color-named": "never",
|
||||
"font-family-name-quotes": "always-where-recommended",
|
||||
"font-weight-notation": "numeric",
|
||||
"function-url-quotes": "always",
|
||||
"number-leading-zero": "never",
|
||||
"value-no-vendor-prefix": true,
|
||||
"value-list-comma-newline-before": "never-multi-line",
|
||||
"custom-property-empty-line-before": "never",
|
||||
"property-no-unknown": [
|
||||
true,
|
||||
{
|
||||
"ignoreProperties": [
|
||||
"composes",
|
||||
"all",
|
||||
"-webkit-appearance"
|
||||
]
|
||||
}
|
||||
],
|
||||
"declaration-block-semicolon-newline-after": "always",
|
||||
"block-opening-brace-newline-after": "always",
|
||||
"selector-attribute-quotes": "always",
|
||||
"selector-max-specificity": "0,5,2",
|
||||
"selector-pseudo-class-no-unknown": [
|
||||
true,
|
||||
{
|
||||
"ignorePseudoClasses": ["local", "global"]
|
||||
}
|
||||
],
|
||||
"at-rule-empty-line-before": [
|
||||
"always",
|
||||
{
|
||||
"ignore": [
|
||||
"after-comment",
|
||||
]
|
||||
}
|
||||
],
|
||||
"indentation": [
|
||||
2,
|
||||
{
|
||||
"indentInsideParens": "once-at-root-twice-in-block"
|
||||
}
|
||||
],
|
||||
"max-nesting-depth": 3,
|
||||
"no-duplicate-selectors": true,
|
||||
"no-unknown-animations": true
|
||||
}
|
||||
}
|
BIN
app/fonts/DIN Next/DIN Next W01 Bold.otf
Normal file
BIN
app/fonts/DIN Next/DIN Next W01 Regular.otf
Normal file
BIN
app/fonts/DIN Next/DIN Next W10 Black.otf
Normal file
BIN
app/fonts/DIN Next/DIN Next W10 Italic.otf
Normal file
BIN
app/fonts/DIN Next/DIN Next W10 Light.otf
Normal file
BIN
app/fonts/DIN Next/DIN Next W10 Medium.otf
Normal file
BIN
app/fonts/DIN_OT/DINOT-2.otf
Normal file
BIN
app/fonts/DIN_OT/DINOT-Bold 2.otf
Normal file
BIN
app/fonts/DIN_OT/DINOT-BoldItalic.otf
Normal file
BIN
app/fonts/DIN_OT/DINOT-Italic 2.otf
Normal file
BIN
app/fonts/DIN_OT/DINOT-Medium 2.otf
Normal file
BIN
app/fonts/DIN_OT/DINOT-MediumItalic 2.otf
Normal file
BIN
app/fonts/Lato/Lato-Black.ttf
Executable file
BIN
app/fonts/Lato/Lato-BlackItalic.ttf
Executable file
BIN
app/fonts/Lato/Lato-Bold.ttf
Executable file
BIN
app/fonts/Lato/Lato-BoldItalic.ttf
Executable file
BIN
app/fonts/Lato/Lato-Hairline.ttf
Executable file
BIN
app/fonts/Lato/Lato-HairlineItalic.ttf
Executable file
BIN
app/fonts/Lato/Lato-Italic.ttf
Executable file
BIN
app/fonts/Lato/Lato-Light.ttf
Executable file
BIN
app/fonts/Lato/Lato-LightItalic.ttf
Executable file
BIN
app/fonts/Lato/Lato-Regular.ttf
Executable file
93
app/fonts/Lato/OFL.txt
Executable file
@ -0,0 +1,93 @@
|
||||
Copyright (c) 2010-2014 by tyPoland Lukasz Dziedzic (team@latofonts.com) with Reserved Font Name "Lato"
|
||||
|
||||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||
This license is copied below, and is also available with a FAQ at:
|
||||
http://scripts.sil.org/OFL
|
||||
|
||||
|
||||
-----------------------------------------------------------
|
||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||
-----------------------------------------------------------
|
||||
|
||||
PREAMBLE
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||
development of collaborative font projects, to support the font creation
|
||||
efforts of academic and linguistic communities, and to provide a free and
|
||||
open framework in which fonts may be shared and improved in partnership
|
||||
with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely as long as they are not sold by themselves. The
|
||||
fonts, including any derivative works, can be bundled, embedded,
|
||||
redistributed and/or sold with any software provided that any reserved
|
||||
names are not used by derivative works. The fonts and derivatives,
|
||||
however, cannot be released under any other type of license. The
|
||||
requirement for fonts to remain under this license does not apply
|
||||
to any document created using the fonts or their derivatives.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this license and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the
|
||||
copyright statement(s).
|
||||
|
||||
"Original Version" refers to the collection of Font Software components as
|
||||
distributed by the Copyright Holder(s).
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||
or substituting -- in part or in whole -- any of the components of the
|
||||
Original Version, by changing formats or by porting the Font Software to a
|
||||
new environment.
|
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical
|
||||
writer or other person who contributed to the Font Software.
|
||||
|
||||
PERMISSION & CONDITIONS
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||
redistribute, and sell modified and unmodified copies of the Font
|
||||
Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components,
|
||||
in Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled,
|
||||
redistributed and/or sold with any software, provided that each copy
|
||||
contains the above copyright notice and this license. These can be
|
||||
included either as stand-alone text files, human-readable headers or
|
||||
in the appropriate machine-readable metadata fields within text or
|
||||
binary files as long as those fields can be easily viewed by the user.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font
|
||||
Name(s) unless explicit written permission is granted by the corresponding
|
||||
Copyright Holder. This restriction only applies to the primary font name as
|
||||
presented to the users.
|
||||
|
||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||
Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except to acknowledge the contribution(s) of the
|
||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||
permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole,
|
||||
must be distributed entirely under this license, and must not be
|
||||
distributed under any other license. The requirement for fonts to
|
||||
remain under this license does not apply to any document created
|
||||
using the Font Software.
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
BIN
app/fonts/Roboto/Roboto-Black.ttf
Normal file
BIN
app/fonts/Roboto/Roboto-BlackItalic.ttf
Normal file
BIN
app/fonts/Roboto/Roboto-Bold.ttf
Normal file
BIN
app/fonts/Roboto/Roboto-BoldItalic.ttf
Normal file
BIN
app/fonts/Roboto/Roboto-Italic.ttf
Normal file
BIN
app/fonts/Roboto/Roboto-Light.ttf
Normal file
BIN
app/fonts/Roboto/Roboto-LightItalic.ttf
Normal file
BIN
app/fonts/Roboto/Roboto-Medium.ttf
Normal file
BIN
app/fonts/Roboto/Roboto-MediumItalic.ttf
Normal file
BIN
app/fonts/Roboto/Roboto-Regular.ttf
Normal file
BIN
app/fonts/Roboto/Roboto-Thin.ttf
Normal file
BIN
app/fonts/Roboto/Roboto-ThinItalic.ttf
Normal file
BIN
app/fonts/Roboto/RobotoCondensed-Bold.ttf
Normal file
BIN
app/fonts/Roboto/RobotoCondensed-BoldItalic.ttf
Normal file
BIN
app/fonts/Roboto/RobotoCondensed-Italic.ttf
Normal file
BIN
app/fonts/Roboto/RobotoCondensed-Light.ttf
Normal file
BIN
app/fonts/Roboto/RobotoCondensed-LightItalic.ttf
Normal file
BIN
app/fonts/Roboto/RobotoCondensed-Regular.ttf
Normal file
BIN
app/images/.DS_Store
vendored
11
app/images/eth_logo.svg
Normal file
@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg width="256px" height="417px" viewBox="0 0 256 417" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" preserveAspectRatio="xMidYMid">
|
||||
<g>
|
||||
<polygon fill="#343434" points="127.9611 0 125.1661 9.5 125.1661 285.168 127.9611 287.958 255.9231 212.32"/>
|
||||
<polygon fill="#8C8C8C" points="127.962 0 0 212.32 127.962 287.959 127.962 154.158"/>
|
||||
<polygon fill="#3C3C3B" points="127.9611 312.1866 126.3861 314.1066 126.3861 412.3056 127.9611 416.9066 255.9991 236.5866"/>
|
||||
<polygon fill="#8C8C8C" points="127.962 416.9052 127.962 312.1852 0 236.5852"/>
|
||||
<polygon fill="#141414" points="127.9611 287.9577 255.9211 212.3207 127.9611 154.1587"/>
|
||||
<polygon fill="#393939" points="0.0009 212.3208 127.9609 287.9578 127.9609 154.1588"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 854 B |
18
app/images/import-account.svg
Normal file
@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="15px" height="15px" viewBox="0 0 15 15" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<!-- Generator: Sketch 47 (45396) - http://www.bohemiancoding.com/sketch -->
|
||||
<title>import-account</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<defs></defs>
|
||||
<g id="MetaMascara-v2" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="account-dropdown-top-bar-IXD" transform="translate(-25.000000, -718.000000)">
|
||||
<g id="Group-6" transform="translate(4.000000, 646.000000)">
|
||||
<g id="import-account" transform="translate(21.000000, 72.000000)">
|
||||
<rect id="Rectangle-49" fill="#FFFFFF" x="0" y="13.1721326" width="14.4893459" height="1.08397642"></rect>
|
||||
<rect id="Rectangle" fill="#FFFFFF" x="6.5860663" y="0" width="1.08397642" height="10.5377061"></rect>
|
||||
<polyline id="Path-12" stroke="#FFFFFF" points="2.63442652 6.5860663 7.24467293 10.5377061 11.8549193 6.5860663"></polyline>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.1 KiB |
11
app/images/mm-bolt.svg
Normal file
@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 21.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 252 251.7" style="enable-background:new 0 0 252 251.7;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#757575;}
|
||||
</style>
|
||||
<path class="st0" d="M211.3,103.9h-60.7c-2,0-3.6-1.6-3.6-3.6V3.6c0-3.5-4.5-5-6.6-2.2l-102.7,140c-1.8,2.4,0,5.8,2.9,5.8h60.7
|
||||
c2,0,3.6,1.6,3.6,3.6v96.6c0,3.5,4.5,5,6.6,2.2l102.7-140C216,107.3,214.3,103.9,211.3,103.9z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 732 B |
11
app/images/mm-info-icon.svg
Normal file
@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 21.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 10 10" style="enable-background:new 0 0 10 10;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#B8B8B8;}
|
||||
</style>
|
||||
<path class="st0" d="M5,0C2.2,0,0,2.2,0,5s2.2,5,5,5s5-2.2,5-5S7.8,0,5,0z M5,2c0.4,0,0.7,0.3,0.7,0.7c0,0.4-0.3,0.7-0.7,0.7
|
||||
S4.3,3.2,4.3,2.8C4.3,2.4,4.6,2,5,2z M5.7,8H4.3V4.3h1.5V8z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 689 B |
17
app/images/plus-btn-white.svg
Normal file
@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<!-- Generator: Sketch 47 (45396) - http://www.bohemiancoding.com/sketch -->
|
||||
<title>plus-btn-white</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<defs></defs>
|
||||
<g id="MetaMascara-v2" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="account-dropdown-top-bar-IXD" transform="translate(-24.000000, -669.000000)" fill="#FFFFFF">
|
||||
<g id="Group-6" transform="translate(4.000000, 646.000000)">
|
||||
<g id="plus-btn-white" transform="translate(20.000000, 23.000000)">
|
||||
<rect id="Rectangle-48" x="7.38461538" y="0" width="1.23076923" height="16"></rect>
|
||||
<rect id="Rectangle-48" transform="translate(8.000000, 8.000000) rotate(-90.000000) translate(-8.000000, -8.000000) " x="7.38461538" y="0" width="1.23076923" height="16"></rect>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.1 KiB |
@ -1,24 +1,22 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
width="24.088px" height="24px" viewBox="0 0 24.088 24" enable-background="new 0 0 24.088 24" xml:space="preserve">
|
||||
<path d="M21.525,10.147c-0.41-0.059-0.847-0.428-0.974-0.82l-0.608-1.481c-0.191-0.365-0.146-0.935,0.1-1.264l0.99-1.318
|
||||
c0.246-0.33,0.227-0.854-0.047-1.162l-1.084-1.086c-0.31-0.272-0.832-0.293-1.164-0.045l-1.316,0.988
|
||||
c-0.33,0.248-0.898,0.293-1.264,0.101l-1.48-0.609c-0.395-0.126-0.764-0.562-0.82-0.971l-0.233-1.629
|
||||
c-0.058-0.409-0.44-0.778-0.851-0.822c0,0-0.254-0.026-0.77-0.026c-0.514,0-0.77,0.026-0.77,0.026
|
||||
c-0.41,0.044-0.793,0.413-0.852,0.822L10.15,2.48c-0.059,0.409-0.428,0.845-0.82,0.971L7.85,4.06
|
||||
C7.484,4.251,6.916,4.207,6.586,3.959L5.268,2.97c-0.33-0.248-0.854-0.228-1.162,0.045L3.021,4.101
|
||||
C2.749,4.41,2.727,4.933,2.975,5.263l0.988,1.318c0.249,0.33,0.293,0.899,0.102,1.264l-0.61,1.482
|
||||
c-0.125,0.393-0.562,0.762-0.972,0.82l-1.629,0.231c-0.408,0.059-0.776,0.442-0.82,0.853c0,0-0.026,0.255-0.026,0.77
|
||||
c0,0.516,0.026,0.77,0.026,0.77c0.044,0.412,0.412,0.793,0.82,0.853l1.629,0.231c0.408,0.06,0.847,0.429,0.972,0.82l0.61,1.48
|
||||
c0.191,0.365,0.146,0.936-0.102,1.264l-0.988,1.318c-0.248,0.33-0.308,0.779-0.132,0.994c0.175,0.217,0.677,0.752,0.679,0.754
|
||||
c0,0.002,0.17,0.156,0.375,0.344c0.203,0.188,1.041,0.449,1.371,0.203l1.317-0.99c0.33-0.246,0.897-0.293,1.265-0.1l1.479,0.608
|
||||
c0.394,0.125,0.763,0.562,0.819,0.972l0.233,1.629c0.058,0.408,0.44,0.779,0.853,0.822c0,0,0.254,0.026,0.769,0.026
|
||||
s0.771-0.026,0.771-0.026c0.408-0.043,0.793-0.414,0.85-0.822l0.234-1.629c0.057-0.408,0.426-0.847,0.819-0.972l1.479-0.61
|
||||
c0.365-0.191,0.935-0.146,1.265,0.102l1.317,0.99c0.332,0.246,0.854,0.227,1.164-0.047l1.082-1.084
|
||||
c0.273-0.312,0.293-0.834,0.047-1.164l-0.989-1.318c-0.246-0.328-0.291-0.898-0.101-1.264l0.609-1.48
|
||||
c0.127-0.393,0.562-0.762,0.973-0.82l1.627-0.231c0.41-0.06,0.779-0.44,0.822-0.853c0,0,0.027-0.254,0.027-0.77
|
||||
c0-0.515-0.027-0.77-0.027-0.77c-0.043-0.41-0.412-0.794-0.822-0.853L21.525,10.147z M12.004,15.001c-1.657,0-3-1.344-3-3
|
||||
c0-1.657,1.343-3,3-3s3,1.344,3,3S13.66,15.001,12.004,15.001z"/>
|
||||
</svg>
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="20px" height="20px" viewBox="0 0 20 20" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<!-- Generator: Sketch 47 (45396) - http://www.bohemiancoding.com/sketch -->
|
||||
<title>settings</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<defs>
|
||||
<polygon id="path-1" points="20 10 20 19.9998 0 19.9998 0 10 0 0.0002 20 0.0002"></polygon>
|
||||
</defs>
|
||||
<g id="MetaMascara-v2" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="account-dropdown-top-bar-IXD" transform="translate(-25.000000, -826.000000)">
|
||||
<g id="Group-6" transform="translate(4.000000, 646.000000)">
|
||||
<g id="settings" transform="translate(21.000000, 180.000000)">
|
||||
<mask id="mask-2" fill="white">
|
||||
<use xlink:href="#path-1"></use>
|
||||
</mask>
|
||||
<g id="Clip-2"></g>
|
||||
<path d="M10,13.6602 C7.979,13.6602 6.34,12.0212 6.34,10.0002 C6.34,7.9782 7.979,6.3402 10,6.3402 C12.021,6.3402 13.66,7.9782 13.66,10.0002 C13.66,12.0212 12.021,13.6602 10,13.6602 L10,13.6602 Z M19.157,11.8112 C19.53,11.8112 19.878,11.5092 19.929,11.1392 C19.929,11.1392 20,10.6182 20,10.0002 C20,9.3822 19.929,8.8622 19.929,8.8622 C19.878,8.4922 19.53,8.1892 19.157,8.1892 L17.228,8.1892 C16.854,8.1892 16.466,7.9512 16.365,7.6602 C16.265,7.3682 16.127,6.4352 16.391,6.1712 L17.755,4.8072 C18.019,4.5432 18.039,4.0922 17.8,3.8052 L16.195,2.2002 C15.908,1.9602 15.458,1.9812 15.193,2.2452 L13.829,3.6092 C13.565,3.8732 13.125,3.9802 12.852,3.8462 C12.578,3.7122 11.812,3.1462 11.812,2.7732 L11.812,0.8432 C11.812,0.4702 11.509,0.1222 11.139,0.0722 C11.139,0.0722 10.619,0.0002 10,0.0002 C9.382,0.0002 8.862,0.0722 8.862,0.0722 C8.492,0.1222 8.189,0.4702 8.189,0.8432 L8.189,2.7732 C8.189,3.1462 7.951,3.5352 7.66,3.6352 C7.369,3.7352 6.435,3.8732 6.171,3.6092 L4.807,2.2452 C4.542,1.9812 4.092,1.9612 3.805,2.2002 L2.2,3.8052 C1.96,4.0922 1.981,4.5432 2.245,4.8072 L3.609,6.1712 C3.873,6.4352 3.98,6.8752 3.846,7.1482 C3.711,7.4222 3.146,8.1892 2.773,8.1892 L0.843,8.1892 C0.47,8.1892 0.123,8.4922 0.072,8.8622 C0.072,8.8622 0,9.3822 0,10.0002 C0,10.6182 0.072,11.1392 0.072,11.1392 C0.123,11.5092 0.47,11.8112 0.843,11.8112 L2.773,11.8112 C3.146,11.8112 3.535,12.0502 3.635,12.3412 C3.735,12.6322 3.874,13.5642 3.609,13.8292 L2.246,15.1932 C1.981,15.4572 1.961,15.9082 2.2,16.1952 L3.805,17.8002 C4.092,18.0392 4.542,18.0192 4.807,17.7552 L6.171,16.3902 C6.435,16.1272 6.875,16.0202 7.148,16.1542 C7.422,16.2882 8.189,16.8532 8.189,17.2272 L8.189,19.1572 C8.189,19.5302 8.492,19.8782 8.862,19.9292 C8.862,19.9292 9.382,20.0002 10,20.0002 C10.619,20.0002 11.139,19.9292 11.139,19.9292 C11.509,19.8772 11.812,19.5302 11.812,19.1572 L11.812,17.2272 C11.812,16.8532 12.05,16.4662 12.341,16.3652 C12.632,16.2642 13.565,16.1272 13.829,16.3902 L15.193,17.7552 C15.458,18.0182 15.908,18.0392 16.195,17.8002 L17.8,16.1952 C18.039,15.9082 18.02,15.4582 17.755,15.1932 L16.391,13.8292 C16.127,13.5652 16.021,13.1252 16.154,12.8512 C16.288,12.5782 16.854,11.8112 17.228,11.8112 L19.157,11.8112 Z" id="Fill-1" fill="#B3B3B3" mask="url(#mask-2)"></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 3.2 KiB |
@ -9,7 +9,7 @@
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<body style="width:350px; height:500px;">
|
||||
<div id="app-content"></div>
|
||||
<script src="./scripts/popup.js" type="text/javascript" charset="utf-8"></script>
|
||||
</body>
|
||||
|
@ -1,11 +1,11 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<html style="width:350px; height:600px;">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1 user-scalable=no">
|
||||
<title>MetaMask Plugin</title>
|
||||
</head>
|
||||
<body style="width:357px; height:500px;">
|
||||
<body style="width:350px; height:600px;">
|
||||
<div id="app-content"></div>
|
||||
<script src="./scripts/popup.js" type="text/javascript" charset="utf-8"></script>
|
||||
</body>
|
||||
|
@ -42,7 +42,18 @@ class PreferencesController {
|
||||
}
|
||||
|
||||
this.store.updateState({ tokens })
|
||||
return Promise.resolve()
|
||||
return Promise.resolve(tokens)
|
||||
}
|
||||
|
||||
removeToken (rawAddress) {
|
||||
const address = normalizeAddress(rawAddress)
|
||||
|
||||
const tokens = this.store.getState().tokens
|
||||
|
||||
const updatedTokens = tokens.filter(token => token.address !== rawAddress)
|
||||
|
||||
this.store.updateState({ tokens: updatedTokens })
|
||||
return Promise.resolve(updatedTokens)
|
||||
}
|
||||
|
||||
getTokens () {
|
||||
|
10
app/scripts/lib/environment-type.js
Normal file
@ -0,0 +1,10 @@
|
||||
module.exports = function environmentType () {
|
||||
const url = window.location.href
|
||||
if (url.match(/popup.html$/)) {
|
||||
return 'popup'
|
||||
} else if (url.match(/home.html$/)) {
|
||||
return 'responsive'
|
||||
} else {
|
||||
return 'notification'
|
||||
}
|
||||
}
|
@ -1,6 +1,9 @@
|
||||
module.exports = function isPopupOrNotification () {
|
||||
const url = window.location.href
|
||||
if (url.match(/popup.html$/)) {
|
||||
// if (url.match(/popup.html$/) || url.match(/home.html$/)) {
|
||||
// Below regexes needed for feature toggles (e.g. see line ~340 in ui/app/app.js)
|
||||
// Revert below regexes to above commented out regexes before merge to master
|
||||
if (url.match(/popup.html(?:\?.+)*$/) || url.match(/home.html(?:\?.+)*$/)) {
|
||||
return 'popup'
|
||||
} else {
|
||||
return 'notification'
|
||||
|
@ -344,6 +344,7 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
// PreferencesController
|
||||
setSelectedAddress: nodeify(preferencesController.setSelectedAddress, preferencesController),
|
||||
addToken: nodeify(preferencesController.addToken, preferencesController),
|
||||
removeToken: nodeify(preferencesController.removeToken, preferencesController),
|
||||
setCurrentAccountTab: nodeify(preferencesController.setCurrentAccountTab, preferencesController),
|
||||
|
||||
// AddressController
|
||||
|
@ -1,6 +1,7 @@
|
||||
const EventEmitter = require('events').EventEmitter
|
||||
const async = require('async')
|
||||
const Dnode = require('dnode')
|
||||
const Eth = require('ethjs')
|
||||
const EthQuery = require('eth-query')
|
||||
const launchMetamaskUi = require('../../ui')
|
||||
const StreamProvider = require('web3-stream-provider')
|
||||
@ -34,6 +35,7 @@ function setupWeb3Connection (connectionStream) {
|
||||
providerStream.on('error', console.error.bind(console))
|
||||
global.ethereumProvider = providerStream
|
||||
global.ethQuery = new EthQuery(providerStream)
|
||||
global.eth = new Eth(providerStream)
|
||||
}
|
||||
|
||||
function setupControllerConnection (connectionStream, cb) {
|
||||
|
51
gulpfile.js
@ -19,10 +19,16 @@ var manifest = require('./app/manifest.json')
|
||||
var gulpif = require('gulp-if')
|
||||
var replace = require('gulp-replace')
|
||||
var mkdirp = require('mkdirp')
|
||||
var sass = require('gulp-sass')
|
||||
var autoprefixer = require('gulp-autoprefixer')
|
||||
var gulpStylelint = require('gulp-stylelint')
|
||||
var stylefmt = require('gulp-stylefmt')
|
||||
|
||||
|
||||
var disableDebugTools = gutil.env.disableDebugTools
|
||||
var debug = gutil.env.debug
|
||||
|
||||
|
||||
// browser reload
|
||||
|
||||
gulp.task('dev:reload', function() {
|
||||
@ -159,13 +165,7 @@ gulp.task('lint', function () {
|
||||
// To have the process exit with an error code (1) on
|
||||
// lint error, return the stream and pipe to failAfterError last.
|
||||
.pipe(eslint.failAfterError())
|
||||
});
|
||||
|
||||
/*
|
||||
gulp.task('default', ['lint'], function () {
|
||||
// This will only run if the lint task is successful...
|
||||
});
|
||||
*/
|
||||
})
|
||||
|
||||
// build js
|
||||
|
||||
@ -176,6 +176,37 @@ const jsFiles = [
|
||||
'popup',
|
||||
]
|
||||
|
||||
// scss compilation and autoprefixing tasks
|
||||
|
||||
gulp.task('build:scss', function () {
|
||||
return gulp.src('ui/app/css/index.scss')
|
||||
.pipe(sourcemaps.init())
|
||||
.pipe(sass().on('error', sass.logError))
|
||||
.pipe(sourcemaps.write())
|
||||
.pipe(autoprefixer())
|
||||
.pipe(gulp.dest('ui/app/css/output'))
|
||||
})
|
||||
gulp.task('watch:scss', function() {
|
||||
gulp.watch(['ui/app/css/**/*.scss'], gulp.series(['build:scss']))
|
||||
})
|
||||
|
||||
gulp.task('lint-scss', function() {
|
||||
return gulp
|
||||
.src('ui/app/css/itcss/**/*.scss')
|
||||
.pipe(gulpStylelint({
|
||||
reporters: [
|
||||
{formatter: 'string', console: true}
|
||||
],
|
||||
fix: true,
|
||||
}));
|
||||
});
|
||||
|
||||
gulp.task('fmt-scss', function () {
|
||||
return gulp.src('ui/app/css/itcss/**/*.scss')
|
||||
.pipe(stylefmt())
|
||||
.pipe(gulp.dest('ui/app/css/itcss'));
|
||||
});
|
||||
|
||||
// bundle tasks
|
||||
|
||||
var jsDevStrings = jsFiles.map(jsFile => `dev:js:${jsFile}`)
|
||||
@ -219,9 +250,9 @@ gulp.task('zip', gulp.parallel('zip:chrome', 'zip:firefox', 'zip:edge', 'zip:ope
|
||||
|
||||
// high level tasks
|
||||
|
||||
gulp.task('dev', gulp.series('dev:js', 'copy', gulp.parallel('copy:watch', 'dev:reload')))
|
||||
gulp.task('dev', gulp.series('build:scss', 'dev:js', 'copy', gulp.parallel('watch:scss', 'copy:watch', 'dev:reload')))
|
||||
|
||||
gulp.task('build', gulp.series('clean', gulp.parallel('build:js', 'copy')))
|
||||
gulp.task('build', gulp.series('clean', 'build:scss', gulp.parallel('build:js', 'copy')))
|
||||
gulp.task('dist', gulp.series('build', 'zip'))
|
||||
|
||||
// task generators
|
||||
@ -249,7 +280,7 @@ function zipTask(target) {
|
||||
return () => {
|
||||
return gulp.src(`dist/${target}/**`)
|
||||
.pipe(zip(`metamask-${target}-${manifest.version}.zip`))
|
||||
.pipe(gulp.dest('builds'));
|
||||
.pipe(gulp.dest('builds'))
|
||||
}
|
||||
}
|
||||
|
||||
|
21
package.json
@ -45,17 +45,24 @@
|
||||
]
|
||||
}
|
||||
],
|
||||
"reactify",
|
||||
"envify",
|
||||
"brfs"
|
||||
]
|
||||
},
|
||||
"dependencies": {
|
||||
"abi-decoder": "^1.0.8",
|
||||
"async": "^2.5.0",
|
||||
"await-semaphore": "^0.1.1",
|
||||
"babel-runtime": "^6.23.0",
|
||||
"bignumber.js": "^4.0.4",
|
||||
"bip39": "^2.2.0",
|
||||
"bluebird": "^3.5.0",
|
||||
"bn.js": "^4.11.7",
|
||||
"boron": "^0.2.3",
|
||||
"browser-passworder": "^2.0.3",
|
||||
"browserify-derequire": "^0.9.4",
|
||||
"classnames": "^2.2.5",
|
||||
"client-sw-ready-event": "^3.3.0",
|
||||
"clone": "^2.1.1",
|
||||
"copy-to-clipboard": "^3.0.8",
|
||||
@ -68,19 +75,21 @@
|
||||
"ensnare": "^1.0.0",
|
||||
"eth-bin-to-ops": "^1.0.1",
|
||||
"eth-block-tracker": "^2.2.0",
|
||||
"eth-contract-metadata": "^1.1.4",
|
||||
"eth-hd-keyring": "^1.2.1",
|
||||
"eth-json-rpc-filters": "^1.2.2",
|
||||
"eth-keyring-controller": "^2.1.0",
|
||||
"eth-contract-metadata": "^1.1.5",
|
||||
"eth-phishing-detect": "^1.1.4",
|
||||
"eth-query": "^2.1.2",
|
||||
"eth-sig-util": "^1.4.0",
|
||||
"eth-simple-keyring": "^1.2.0",
|
||||
"eth-token-tracker": "^1.1.4",
|
||||
"ethereumjs-abi": "^0.6.4",
|
||||
"ethereumjs-tx": "^1.3.0",
|
||||
"ethereumjs-util": "github:ethereumjs/ethereumjs-util#ac5d0908536b447083ea422b435da27f26615de9",
|
||||
"ethereumjs-wallet": "^0.6.0",
|
||||
"etherscan-link": "^1.0.2",
|
||||
"ethjs": "^0.2.8",
|
||||
"ethjs-contract": "^0.1.9",
|
||||
"ethjs-ens": "^2.0.0",
|
||||
"ethjs-query": "^0.2.9",
|
||||
@ -89,8 +98,11 @@
|
||||
"extensionizer": "^1.0.0",
|
||||
"fast-json-patch": "^2.0.4",
|
||||
"fast-levenshtein": "^2.0.6",
|
||||
"fuse.js": "^3.1.0",
|
||||
"gulp": "github:gulpjs/gulp#4.0",
|
||||
"gulp-autoprefixer": "^4.0.0",
|
||||
"gulp-eslint": "^4.0.0",
|
||||
"gulp-sass": "^3.1.0",
|
||||
"hat": "0.0.3",
|
||||
"human-standard-token-abi": "^1.0.2",
|
||||
"idb-global": "^2.1.0",
|
||||
@ -120,6 +132,7 @@
|
||||
"pump": "^1.0.2",
|
||||
"pumpify": "^1.3.4",
|
||||
"qrcode-npm": "0.0.3",
|
||||
"ramda": "^0.24.1",
|
||||
"react": "^15.0.2",
|
||||
"react-addons-css-transition-group": "^15.6.0",
|
||||
"react-dom": "^15.5.4",
|
||||
@ -129,6 +142,8 @@
|
||||
"react-select": "^1.0.0-rc.2",
|
||||
"react-simple-file-input": "^2.0.0",
|
||||
"react-tooltip-component": "^0.3.0",
|
||||
"react-transition-group": "^2.2.0",
|
||||
"reactify": "^1.1.1",
|
||||
"readable-stream": "^2.3.3",
|
||||
"redux": "^3.0.5",
|
||||
"redux-logger": "^3.0.6",
|
||||
@ -136,6 +151,7 @@
|
||||
"request-promise": "^4.2.1",
|
||||
"sandwich-expando": "^1.0.5",
|
||||
"semaphore": "^1.0.5",
|
||||
"shallow-copy": "0.0.1",
|
||||
"sw-stream": "^2.0.0",
|
||||
"textarea-caret": "^3.0.1",
|
||||
"through2": "^2.0.3",
|
||||
@ -174,6 +190,8 @@
|
||||
"gulp-livereload": "^3.8.1",
|
||||
"gulp-replace": "^0.6.1",
|
||||
"gulp-sourcemaps": "^2.6.0",
|
||||
"gulp-stylefmt": "^1.1.0",
|
||||
"gulp-stylelint": "^4.0.0",
|
||||
"gulp-util": "^3.0.7",
|
||||
"gulp-watch": "^4.3.5",
|
||||
"gulp-zip": "^4.0.0",
|
||||
@ -201,6 +219,7 @@
|
||||
"react-test-renderer": "^15.5.4",
|
||||
"react-testutils-additions": "^15.2.0",
|
||||
"sinon": "^4.0.0",
|
||||
"stylelint-config-standard": "^17.0.0",
|
||||
"tape": "^4.5.1",
|
||||
"testem": "^1.10.3",
|
||||
"uglifyify": "^4.0.2",
|
||||
|
32
test/unit/components/balance-component-test.js
Normal file
@ -0,0 +1,32 @@
|
||||
var assert = require('assert')
|
||||
var BalanceComponent = require('../../../ui/app/components/balance-component')
|
||||
|
||||
describe('BalanceComponent', function () {
|
||||
let balanceComponent
|
||||
|
||||
beforeEach(function () {
|
||||
balanceComponent = new BalanceComponent()
|
||||
})
|
||||
|
||||
it('shows token balance and convert to fiat value based on conversion rate', function () {
|
||||
const formattedBalance = '1.23 ETH'
|
||||
|
||||
const tokenBalance = balanceComponent.getTokenBalance(formattedBalance, false)
|
||||
const fiatDisplayNumber = balanceComponent.getFiatDisplayNumber(formattedBalance, 2)
|
||||
|
||||
assert.equal('1.23 ETH', tokenBalance)
|
||||
assert.equal(2.46, fiatDisplayNumber)
|
||||
})
|
||||
|
||||
it('shows only the token balance when conversion rate is not available', function () {
|
||||
const formattedBalance = '1.23 ETH'
|
||||
|
||||
const tokenBalance = balanceComponent.getTokenBalance(formattedBalance, false)
|
||||
const fiatDisplayNumber = balanceComponent.getFiatDisplayNumber(formattedBalance, 0)
|
||||
|
||||
assert.equal('1.23 ETH', tokenBalance)
|
||||
assert.equal('N/A', fiatDisplayNumber)
|
||||
})
|
||||
|
||||
})
|
||||
|
38
ui/app/account-and-transaction-details.js
Normal file
@ -0,0 +1,38 @@
|
||||
const Component = require('react').Component
|
||||
const h = require('react-hyperscript')
|
||||
const inherits = require('util').inherits
|
||||
// Main Views
|
||||
const TxView = require('./components/tx-view')
|
||||
const WalletView = require('./components/wallet-view')
|
||||
|
||||
module.exports = AccountAndTransactionDetails
|
||||
|
||||
inherits(AccountAndTransactionDetails, Component)
|
||||
function AccountAndTransactionDetails () {
|
||||
Component.call(this)
|
||||
}
|
||||
|
||||
AccountAndTransactionDetails.prototype.render = function () {
|
||||
return h('div', {
|
||||
style: {
|
||||
display: 'flex',
|
||||
flex: '1 0 auto',
|
||||
},
|
||||
}, [
|
||||
// wallet
|
||||
h(WalletView, {
|
||||
style: {
|
||||
},
|
||||
responsiveDisplayClassname: '.lap-visible',
|
||||
}, [
|
||||
]),
|
||||
|
||||
// transaction
|
||||
h(TxView, {
|
||||
style: {
|
||||
},
|
||||
}, [
|
||||
]),
|
||||
])
|
||||
}
|
||||
|
@ -5,15 +5,10 @@ const h = require('react-hyperscript')
|
||||
const connect = require('react-redux').connect
|
||||
const actions = require('./actions')
|
||||
const valuesFor = require('./util').valuesFor
|
||||
const Identicon = require('./components/identicon')
|
||||
const EthBalance = require('./components/eth-balance')
|
||||
const TransactionList = require('./components/transaction-list')
|
||||
const ExportAccountView = require('./components/account-export')
|
||||
const ethUtil = require('ethereumjs-util')
|
||||
const EditableLabel = require('./components/editable-label')
|
||||
const TabBar = require('./components/tab-bar')
|
||||
const TokenList = require('./components/token-list')
|
||||
const AccountDropdowns = require('./components/account-dropdowns').AccountDropdowns
|
||||
|
||||
module.exports = connect(mapStateToProps)(AccountDetailScreen)
|
||||
|
||||
@ -41,180 +36,11 @@ function AccountDetailScreen () {
|
||||
Component.call(this)
|
||||
}
|
||||
|
||||
AccountDetailScreen.prototype.render = function () {
|
||||
var props = this.props
|
||||
var selected = props.address || Object.keys(props.accounts)[0]
|
||||
var checksumAddress = selected && ethUtil.toChecksumAddress(selected)
|
||||
var identity = props.identities[selected]
|
||||
var account = props.accounts[selected]
|
||||
const { network, conversionRate, currentCurrency } = props
|
||||
|
||||
return (
|
||||
|
||||
h('.account-detail-section.full-flex-height', [
|
||||
|
||||
// identicon, label, balance, etc
|
||||
h('.account-data-subsection', {
|
||||
style: {
|
||||
margin: '0 20px',
|
||||
flex: '1 0 auto',
|
||||
},
|
||||
}, [
|
||||
|
||||
// header - identicon + nav
|
||||
h('div', {
|
||||
style: {
|
||||
paddingTop: '20px',
|
||||
display: 'flex',
|
||||
justifyContent: 'flex-start',
|
||||
alignItems: 'flex-start',
|
||||
},
|
||||
}, [
|
||||
|
||||
// large identicon and addresses
|
||||
h('.identicon-wrapper.select-none', [
|
||||
h(Identicon, {
|
||||
diameter: 62,
|
||||
address: selected,
|
||||
}),
|
||||
]),
|
||||
h('flex-column', {
|
||||
style: {
|
||||
lineHeight: '10px',
|
||||
marginLeft: '15px',
|
||||
width: '100%',
|
||||
},
|
||||
}, [
|
||||
h(EditableLabel, {
|
||||
textValue: identity ? identity.name : '',
|
||||
state: {
|
||||
isEditingLabel: false,
|
||||
},
|
||||
saveText: (text) => {
|
||||
props.dispatch(actions.saveAccountLabel(selected, text))
|
||||
},
|
||||
}, [
|
||||
|
||||
// What is shown when not editing + edit text:
|
||||
h('label.editing-label', [h('.edit-text', 'edit')]),
|
||||
h(
|
||||
'div',
|
||||
{
|
||||
style: {
|
||||
display: 'flex',
|
||||
justifyContent: 'flex-start',
|
||||
alignItems: 'center',
|
||||
},
|
||||
},
|
||||
[
|
||||
h(
|
||||
'div.font-medium.color-forest',
|
||||
{
|
||||
name: 'edit',
|
||||
style: {
|
||||
},
|
||||
},
|
||||
[
|
||||
h('h2', {
|
||||
style: {
|
||||
maxWidth: '180px',
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
padding: '5px 0px',
|
||||
lineHeight: '25px',
|
||||
},
|
||||
}, [
|
||||
identity && identity.name,
|
||||
]),
|
||||
]
|
||||
),
|
||||
h(
|
||||
AccountDropdowns,
|
||||
{
|
||||
style: {
|
||||
marginRight: '8px',
|
||||
marginLeft: 'auto',
|
||||
cursor: 'pointer',
|
||||
},
|
||||
selected,
|
||||
network,
|
||||
identities: props.identities,
|
||||
enableAccountOptions: true,
|
||||
},
|
||||
),
|
||||
]
|
||||
),
|
||||
]),
|
||||
h('.flex-row', {
|
||||
style: {
|
||||
width: '15em',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'baseline',
|
||||
},
|
||||
}, [
|
||||
|
||||
// address
|
||||
|
||||
h('div', {
|
||||
style: {
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
paddingTop: '3px',
|
||||
width: '5em',
|
||||
fontSize: '13px',
|
||||
fontFamily: 'Montserrat Light',
|
||||
textRendering: 'geometricPrecision',
|
||||
marginBottom: '15px',
|
||||
color: '#AEAEAE',
|
||||
},
|
||||
}, checksumAddress),
|
||||
]),
|
||||
|
||||
// account ballence
|
||||
|
||||
]),
|
||||
]),
|
||||
h('.flex-row', {
|
||||
style: {
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'flex-start',
|
||||
},
|
||||
}, [
|
||||
|
||||
h(EthBalance, {
|
||||
value: account && account.balance,
|
||||
conversionRate,
|
||||
currentCurrency,
|
||||
style: {
|
||||
lineHeight: '7px',
|
||||
marginTop: '10px',
|
||||
},
|
||||
}),
|
||||
|
||||
h('.flex-grow'),
|
||||
|
||||
h('button', {
|
||||
onClick: () => props.dispatch(actions.buyEthView(selected)),
|
||||
style: { marginRight: '10px' },
|
||||
}, 'BUY'),
|
||||
|
||||
h('button', {
|
||||
onClick: () => props.dispatch(actions.showSendPage()),
|
||||
style: {
|
||||
marginBottom: '20px',
|
||||
marginRight: '8px',
|
||||
},
|
||||
}, 'SEND'),
|
||||
|
||||
]),
|
||||
]),
|
||||
|
||||
// subview (tx history, pk export confirm, buy eth warning)
|
||||
this.subview(),
|
||||
|
||||
])
|
||||
)
|
||||
}
|
||||
// Note: This component is no longer used. Leaving the file for reference:
|
||||
// - structuring routing for add token
|
||||
// - state required for TxList
|
||||
// Delete file when those features are complete
|
||||
AccountDetailScreen.prototype.render = function () {}
|
||||
|
||||
AccountDetailScreen.prototype.subview = function () {
|
||||
var subview
|
||||
|
@ -34,8 +34,10 @@ AccountImportSubview.prototype.render = function () {
|
||||
const { type } = state
|
||||
|
||||
return (
|
||||
h('div', {
|
||||
h('div.flex-center', {
|
||||
style: {
|
||||
flexDirection: 'column',
|
||||
marginTop: '32px',
|
||||
},
|
||||
}, [
|
||||
h('.section-title.flex-row.flex-center', [
|
||||
@ -48,7 +50,8 @@ AccountImportSubview.prototype.render = function () {
|
||||
]),
|
||||
h('div', {
|
||||
style: {
|
||||
padding: '10px',
|
||||
padding: '10px 0',
|
||||
width: '260px',
|
||||
color: 'rgb(174, 174, 174)',
|
||||
},
|
||||
}, [
|
||||
|
@ -1,3 +1,4 @@
|
||||
const abi = require('human-standard-token-abi')
|
||||
const getBuyEthUrl = require('../../app/scripts/lib/buy-eth-url')
|
||||
|
||||
var actions = {
|
||||
@ -5,6 +6,21 @@ var actions = {
|
||||
|
||||
GO_HOME: 'GO_HOME',
|
||||
goHome: goHome,
|
||||
// modal state
|
||||
MODAL_OPEN: 'UI_MODAL_OPEN',
|
||||
MODAL_CLOSE: 'UI_MODAL_CLOSE',
|
||||
showModal: showModal,
|
||||
hideModal: hideModal,
|
||||
// sidebar state
|
||||
SIDEBAR_OPEN: 'UI_SIDEBAR_OPEN',
|
||||
SIDEBAR_CLOSE: 'UI_SIDEBAR_CLOSE',
|
||||
showSidebar: showSidebar,
|
||||
hideSidebar: hideSidebar,
|
||||
// network dropdown open
|
||||
NETWORK_DROPDOWN_OPEN: 'UI_NETWORK_DROPDOWN_OPEN',
|
||||
NETWORK_DROPDOWN_CLOSE: 'UI_NETWORK_DROPDOWN_CLOSE',
|
||||
showNetworkDropdown: showNetworkDropdown,
|
||||
hideNetworkDropdown: hideNetworkDropdown,
|
||||
// menu state
|
||||
getNetworkStatus: 'getNetworkStatus',
|
||||
// transition state
|
||||
@ -68,6 +84,8 @@ var actions = {
|
||||
hideWarning: hideWarning,
|
||||
// accounts screen
|
||||
SET_SELECTED_ACCOUNT: 'SET_SELECTED_ACCOUNT',
|
||||
SET_SELECTED_TOKEN: 'SET_SELECTED_TOKEN',
|
||||
setSelectedToken,
|
||||
SHOW_ACCOUNT_DETAIL: 'SHOW_ACCOUNT_DETAIL',
|
||||
SHOW_ACCOUNTS_PAGE: 'SHOW_ACCOUNTS_PAGE',
|
||||
SHOW_CONF_TX_PAGE: 'SHOW_CONF_TX_PAGE',
|
||||
@ -78,6 +96,8 @@ var actions = {
|
||||
// account detail screen
|
||||
SHOW_SEND_PAGE: 'SHOW_SEND_PAGE',
|
||||
showSendPage: showSendPage,
|
||||
SHOW_SEND_TOKEN_PAGE: 'SHOW_SEND_TOKEN_PAGE',
|
||||
showSendTokenPage,
|
||||
ADD_TO_ADDRESS_BOOK: 'ADD_TO_ADDRESS_BOOK',
|
||||
addToAddressBook: addToAddressBook,
|
||||
REQUEST_ACCOUNT_EXPORT: 'REQUEST_ACCOUNT_EXPORT',
|
||||
@ -86,6 +106,7 @@ var actions = {
|
||||
exportAccount: exportAccount,
|
||||
SHOW_PRIVATE_KEY: 'SHOW_PRIVATE_KEY',
|
||||
showPrivateKey: showPrivateKey,
|
||||
exportAccountComplete,
|
||||
SAVE_ACCOUNT_LABEL: 'SAVE_ACCOUNT_LABEL',
|
||||
saveAccountLabel: saveAccountLabel,
|
||||
// tx conf screen
|
||||
@ -99,7 +120,9 @@ var actions = {
|
||||
cancelPersonalMsg,
|
||||
signTypedMsg,
|
||||
cancelTypedMsg,
|
||||
sendTx: sendTx,
|
||||
signTx: signTx,
|
||||
signTokenTx: signTokenTx,
|
||||
updateAndApproveTx,
|
||||
cancelTx: cancelTx,
|
||||
completedTx: completedTx,
|
||||
@ -109,6 +132,26 @@ var actions = {
|
||||
cancelAllTx: cancelAllTx,
|
||||
viewPendingTx: viewPendingTx,
|
||||
VIEW_PENDING_TX: 'VIEW_PENDING_TX',
|
||||
// send screen
|
||||
estimateGas,
|
||||
getGasPrice,
|
||||
UPDATE_GAS_LIMIT: 'UPDATE_GAS_LIMIT',
|
||||
UPDATE_GAS_PRICE: 'UPDATE_GAS_PRICE',
|
||||
UPDATE_GAS_TOTAL: 'UPDATE_GAS_TOTAL',
|
||||
UPDATE_SEND_FROM: 'UPDATE_SEND_FROM',
|
||||
UPDATE_SEND_TO: 'UPDATE_SEND_TO',
|
||||
UPDATE_SEND_AMOUNT: 'UPDATE_SEND_AMOUNT',
|
||||
UPDATE_SEND_MEMO: 'UPDATE_SEND_MEMO',
|
||||
UPDATE_SEND_ERRORS: 'UPDATE_SEND_ERRORS',
|
||||
updateGasLimit,
|
||||
updateGasPrice,
|
||||
updateGasTotal,
|
||||
updateSendFrom,
|
||||
updateSendTo,
|
||||
updateSendAmount,
|
||||
updateSendMemo,
|
||||
updateSendErrors,
|
||||
setSelectedAddress,
|
||||
// app messages
|
||||
confirmSeedWords: confirmSeedWords,
|
||||
showAccountDetail: showAccountDetail,
|
||||
@ -125,8 +168,13 @@ var actions = {
|
||||
SHOW_ADD_TOKEN_PAGE: 'SHOW_ADD_TOKEN_PAGE',
|
||||
showAddTokenPage,
|
||||
addToken,
|
||||
addTokens,
|
||||
removeToken,
|
||||
updateTokens,
|
||||
UPDATE_TOKENS: 'UPDATE_TOKENS',
|
||||
setRpcTarget: setRpcTarget,
|
||||
setProviderType: setProviderType,
|
||||
updateProviderType,
|
||||
// loading overlay
|
||||
SHOW_LOADING: 'SHOW_LOADING_INDICATION',
|
||||
HIDE_LOADING: 'HIDE_LOADING_INDICATION',
|
||||
@ -141,6 +189,8 @@ var actions = {
|
||||
coinBaseSubview: coinBaseSubview,
|
||||
SHAPESHIFT_SUBVIEW: 'SHAPESHIFT_SUBVIEW',
|
||||
shapeShiftSubview: shapeShiftSubview,
|
||||
UPDATE_TOKEN_EXCHANGE_RATE: 'UPDATE_TOKEN_EXCHANGE_RATE',
|
||||
updateTokenExchangeRate,
|
||||
PAIR_UPDATE: 'PAIR_UPDATE',
|
||||
pairUpdate: pairUpdate,
|
||||
coinShiftRquest: coinShiftRquest,
|
||||
@ -165,6 +215,9 @@ var actions = {
|
||||
|
||||
callBackgroundThenUpdate,
|
||||
forceUpdateMetamaskState,
|
||||
|
||||
TOGGLE_ACCOUNT_MENU: 'TOGGLE_ACCOUNT_MENU',
|
||||
toggleAccountMenu,
|
||||
}
|
||||
|
||||
module.exports = actions
|
||||
@ -325,7 +378,24 @@ function navigateToNewAccountScreen () {
|
||||
|
||||
function addNewAccount () {
|
||||
log.debug(`background.addNewAccount`)
|
||||
return callBackgroundThenUpdate(background.addNewAccount)
|
||||
return (dispatch, getState) => {
|
||||
const oldIdentities = getState().metamask.identities
|
||||
dispatch(actions.showLoadingIndication())
|
||||
return new Promise((resolve, reject) => {
|
||||
background.addNewAccount((err, { identities: newIdentities}) => {
|
||||
if (err) {
|
||||
dispatch(actions.displayWarning(err.message))
|
||||
return reject(err)
|
||||
}
|
||||
const newAccountAddress = Object.keys(newIdentities).find(address => !oldIdentities[address])
|
||||
|
||||
dispatch(actions.hideLoadingIndication())
|
||||
|
||||
forceUpdateMetamaskState(dispatch)
|
||||
return resolve(newAccountAddress)
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function showInfoPage () {
|
||||
@ -336,16 +406,16 @@ function showInfoPage () {
|
||||
|
||||
function setCurrentCurrency (currencyCode) {
|
||||
return (dispatch) => {
|
||||
dispatch(this.showLoadingIndication())
|
||||
dispatch(actions.showLoadingIndication())
|
||||
log.debug(`background.setCurrentCurrency`)
|
||||
background.setCurrentCurrency(currencyCode, (err, data) => {
|
||||
dispatch(this.hideLoadingIndication())
|
||||
dispatch(actions.hideLoadingIndication())
|
||||
if (err) {
|
||||
log.error(err.stack)
|
||||
return dispatch(actions.displayWarning(err.message))
|
||||
}
|
||||
dispatch({
|
||||
type: this.SET_CURRENT_FIAT,
|
||||
type: actions.SET_CURRENT_FIAT,
|
||||
value: {
|
||||
currentCurrency: data.currentCurrency,
|
||||
conversionRate: data.conversionRate,
|
||||
@ -418,10 +488,127 @@ function signTx (txData) {
|
||||
dispatch(actions.showLoadingIndication())
|
||||
global.ethQuery.sendTransaction(txData, (err, data) => {
|
||||
dispatch(actions.hideLoadingIndication())
|
||||
if (err) dispatch(actions.displayWarning(err.message))
|
||||
dispatch(this.goHome())
|
||||
if (err) return dispatch(actions.displayWarning(err.message))
|
||||
dispatch(actions.hideWarning())
|
||||
})
|
||||
dispatch(actions.showConfTxPage())
|
||||
dispatch(actions.showConfTxPage({}))
|
||||
}
|
||||
}
|
||||
|
||||
function estimateGas (params = {}) {
|
||||
return (dispatch) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
global.ethQuery.estimateGas(params, (err, data) => {
|
||||
if (err) {
|
||||
dispatch(actions.displayWarning(err.message))
|
||||
return reject(err)
|
||||
}
|
||||
dispatch(actions.hideWarning())
|
||||
dispatch(actions.updateGasLimit(data))
|
||||
return resolve(data)
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function updateGasLimit (gasLimit) {
|
||||
return {
|
||||
type: actions.UPDATE_GAS_LIMIT,
|
||||
value: gasLimit,
|
||||
}
|
||||
}
|
||||
|
||||
function getGasPrice () {
|
||||
return (dispatch) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
global.ethQuery.gasPrice((err, data) => {
|
||||
if (err) {
|
||||
dispatch(actions.displayWarning(err.message))
|
||||
return reject(err)
|
||||
}
|
||||
dispatch(actions.hideWarning())
|
||||
dispatch(actions.updateGasPrice(data))
|
||||
return resolve(data)
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function updateGasPrice (gasPrice) {
|
||||
return {
|
||||
type: actions.UPDATE_GAS_PRICE,
|
||||
value: gasPrice,
|
||||
}
|
||||
}
|
||||
|
||||
function updateGasTotal (gasTotal) {
|
||||
return {
|
||||
type: actions.UPDATE_GAS_TOTAL,
|
||||
value: gasTotal,
|
||||
}
|
||||
}
|
||||
|
||||
function updateSendFrom (from) {
|
||||
return {
|
||||
type: actions.UPDATE_SEND_FROM,
|
||||
value: from,
|
||||
}
|
||||
}
|
||||
|
||||
function updateSendTo (to) {
|
||||
return {
|
||||
type: actions.UPDATE_SEND_TO,
|
||||
value: to,
|
||||
}
|
||||
}
|
||||
|
||||
function updateSendAmount (amount) {
|
||||
return {
|
||||
type: actions.UPDATE_SEND_AMOUNT,
|
||||
value: amount,
|
||||
}
|
||||
}
|
||||
|
||||
function updateSendMemo (memo) {
|
||||
return {
|
||||
type: actions.UPDATE_SEND_MEMO,
|
||||
value: memo,
|
||||
}
|
||||
}
|
||||
|
||||
function updateSendErrors (error) {
|
||||
console.log(`updateSendErrors error`, error);
|
||||
return {
|
||||
type: actions.UPDATE_SEND_ERRORS,
|
||||
value: error,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function sendTx (txData) {
|
||||
log.info(`actions - sendTx: ${JSON.stringify(txData.txParams)}`)
|
||||
return (dispatch) => {
|
||||
log.debug(`actions calling background.approveTransaction`)
|
||||
background.approveTransaction(txData.id, (err) => {
|
||||
if (err) {
|
||||
dispatch(actions.txError(err))
|
||||
return log.error(err.message)
|
||||
}
|
||||
dispatch(actions.completedTx(txData.id))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function signTokenTx (tokenAddress, toAddress, amount, txData) {
|
||||
return dispatch => {
|
||||
dispatch(actions.showLoadingIndication())
|
||||
const token = global.eth.contract(abi).at(tokenAddress)
|
||||
token.transfer(toAddress, amount, txData)
|
||||
.catch(err => {
|
||||
dispatch(actions.hideLoadingIndication())
|
||||
dispatch(actions.displayWarning(err.message))
|
||||
})
|
||||
dispatch(actions.showConfTxPage({}))
|
||||
}
|
||||
}
|
||||
|
||||
@ -592,6 +779,26 @@ function setCurrentAccountTab (newTabName) {
|
||||
return callBackgroundThenUpdateNoSpinner(background.setCurrentAccountTab, newTabName)
|
||||
}
|
||||
|
||||
function setSelectedToken (tokenAddress) {
|
||||
return {
|
||||
type: actions.SET_SELECTED_TOKEN,
|
||||
value: tokenAddress || null,
|
||||
}
|
||||
}
|
||||
|
||||
function setSelectedAddress (address) {
|
||||
return (dispatch) => {
|
||||
dispatch(actions.showLoadingIndication())
|
||||
log.debug(`background.setSelectedAddress`)
|
||||
background.setSelectedAddress(address, (err) => {
|
||||
dispatch(actions.hideLoadingIndication())
|
||||
if (err) {
|
||||
return dispatch(actions.displayWarning(err.message))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function showAccountDetail (address) {
|
||||
return (dispatch) => {
|
||||
dispatch(actions.showLoadingIndication())
|
||||
@ -605,6 +812,7 @@ function showAccountDetail (address) {
|
||||
type: actions.SHOW_ACCOUNT_DETAIL,
|
||||
value: address,
|
||||
})
|
||||
dispatch(actions.setSelectedToken())
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -622,10 +830,11 @@ function showAccountsPage () {
|
||||
}
|
||||
}
|
||||
|
||||
function showConfTxPage (transForward = true) {
|
||||
function showConfTxPage ({transForward = true, id}) {
|
||||
return {
|
||||
type: actions.SHOW_CONF_TX_PAGE,
|
||||
transForward: transForward,
|
||||
transForward,
|
||||
id,
|
||||
}
|
||||
}
|
||||
|
||||
@ -655,28 +864,71 @@ function showConfigPage (transitionForward = true) {
|
||||
}
|
||||
}
|
||||
|
||||
function showAddTokenPage (transitionForward = true) {
|
||||
function showAddTokenPage () {
|
||||
return {
|
||||
type: actions.SHOW_ADD_TOKEN_PAGE,
|
||||
value: transitionForward,
|
||||
}
|
||||
}
|
||||
|
||||
function addToken (address, symbol, decimals) {
|
||||
return (dispatch) => {
|
||||
dispatch(actions.showLoadingIndication())
|
||||
background.addToken(address, symbol, decimals, (err) => {
|
||||
dispatch(actions.hideLoadingIndication())
|
||||
if (err) {
|
||||
return dispatch(actions.displayWarning(err.message))
|
||||
}
|
||||
setTimeout(() => {
|
||||
dispatch(actions.goHome())
|
||||
}, 250)
|
||||
return new Promise((resolve, reject) => {
|
||||
background.addToken(address, symbol, decimals, (err, tokens) => {
|
||||
dispatch(actions.hideLoadingIndication())
|
||||
if (err) {
|
||||
dispatch(actions.displayWarning(err.message))
|
||||
reject(err)
|
||||
}
|
||||
dispatch(actions.updateTokens(tokens))
|
||||
resolve(tokens)
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function removeToken (address) {
|
||||
return (dispatch) => {
|
||||
dispatch(actions.showLoadingIndication())
|
||||
return new Promise((resolve, reject) => {
|
||||
background.removeToken(address, (err, tokens) => {
|
||||
dispatch(actions.hideLoadingIndication())
|
||||
if (err) {
|
||||
dispatch(actions.displayWarning(err.message))
|
||||
reject(err)
|
||||
}
|
||||
dispatch(actions.updateTokens(tokens))
|
||||
resolve(tokens)
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function addTokens (tokens) {
|
||||
return dispatch => {
|
||||
if (Array.isArray(tokens)) {
|
||||
return Promise.all(tokens.map(({ address, symbol, decimals }) => (
|
||||
dispatch(addToken(address, symbol, decimals))
|
||||
)))
|
||||
} else {
|
||||
return Promise.all(
|
||||
Object
|
||||
.entries(tokens)
|
||||
.map(([_, { address, symbol, decimals }]) => (
|
||||
dispatch(addToken(address, symbol, decimals))
|
||||
))
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function updateTokens(newTokens) {
|
||||
return {
|
||||
type: actions.UPDATE_TOKENS,
|
||||
newTokens
|
||||
}
|
||||
}
|
||||
|
||||
function goBackToInitView () {
|
||||
return {
|
||||
type: actions.BACK_TO_INIT_MENU,
|
||||
@ -738,11 +990,17 @@ function setProviderType (type) {
|
||||
log.error(err)
|
||||
return dispatch(self.displayWarning('Had a problem changing networks!'))
|
||||
}
|
||||
dispatch(actions.updateProviderType(type))
|
||||
dispatch(actions.setSelectedToken())
|
||||
})
|
||||
return {
|
||||
type: actions.SET_PROVIDER_TYPE,
|
||||
value: type,
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
function updateProviderType(type) {
|
||||
return {
|
||||
type: actions.SET_PROVIDER_TYPE,
|
||||
value: type,
|
||||
}
|
||||
}
|
||||
|
||||
@ -759,7 +1017,7 @@ function setRpcTarget (newRpc) {
|
||||
}
|
||||
|
||||
// Calls the addressBookController to add a new address.
|
||||
function addToAddressBook (recipient, nickname) {
|
||||
function addToAddressBook (recipient, nickname = '') {
|
||||
log.debug(`background.addToAddressBook`)
|
||||
return (dispatch) => {
|
||||
background.setAddressBook(recipient, nickname, (err, result) => {
|
||||
@ -771,6 +1029,54 @@ function addToAddressBook (recipient, nickname) {
|
||||
}
|
||||
}
|
||||
|
||||
function useEtherscanProvider () {
|
||||
log.debug(`background.useEtherscanProvider`)
|
||||
background.useEtherscanProvider()
|
||||
return {
|
||||
type: actions.USE_ETHERSCAN_PROVIDER,
|
||||
}
|
||||
}
|
||||
|
||||
function showNetworkDropdown () {
|
||||
return {
|
||||
type: actions.NETWORK_DROPDOWN_OPEN,
|
||||
}
|
||||
}
|
||||
|
||||
function hideNetworkDropdown () {
|
||||
return {
|
||||
type: actions.NETWORK_DROPDOWN_CLOSE,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function showModal (payload) {
|
||||
return {
|
||||
type: actions.MODAL_OPEN,
|
||||
payload,
|
||||
}
|
||||
}
|
||||
|
||||
function hideModal (payload) {
|
||||
return {
|
||||
type: actions.MODAL_CLOSE,
|
||||
payload,
|
||||
}
|
||||
}
|
||||
|
||||
function showSidebar () {
|
||||
return {
|
||||
type: actions.SIDEBAR_OPEN,
|
||||
}
|
||||
}
|
||||
|
||||
function hideSidebar () {
|
||||
return {
|
||||
type: actions.SIDEBAR_CLOSE,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function showLoadingIndication (message) {
|
||||
return {
|
||||
type: actions.SHOW_LOADING,
|
||||
@ -822,27 +1128,39 @@ function exportAccount (password, address) {
|
||||
dispatch(self.showLoadingIndication())
|
||||
|
||||
log.debug(`background.submitPassword`)
|
||||
background.submitPassword(password, function (err) {
|
||||
if (err) {
|
||||
log.error('Error in submiting password.')
|
||||
dispatch(self.hideLoadingIndication())
|
||||
return dispatch(self.displayWarning('Incorrect Password.'))
|
||||
}
|
||||
log.debug(`background.exportAccount`)
|
||||
background.exportAccount(address, function (err, result) {
|
||||
dispatch(self.hideLoadingIndication())
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
background.submitPassword(password, function (err) {
|
||||
if (err) {
|
||||
log.error(err)
|
||||
return dispatch(self.displayWarning('Had a problem exporting the account.'))
|
||||
log.error('Error in submiting password.')
|
||||
dispatch(self.hideLoadingIndication())
|
||||
dispatch(self.displayWarning('Incorrect Password.'))
|
||||
return reject(err)
|
||||
}
|
||||
log.debug(`background.exportAccount`)
|
||||
return background.exportAccount(address, function (err, result) {
|
||||
dispatch(self.hideLoadingIndication())
|
||||
|
||||
dispatch(self.showPrivateKey(result))
|
||||
if (err) {
|
||||
log.error(err)
|
||||
dispatch(self.displayWarning('Had a problem exporting the account.'))
|
||||
return reject(err)
|
||||
}
|
||||
|
||||
dispatch(self.exportAccountComplete())
|
||||
|
||||
return resolve(result)
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function exportAccountComplete() {
|
||||
return {
|
||||
type: actions.EXPORT_ACCOUNT,
|
||||
}
|
||||
}
|
||||
|
||||
function showPrivateKey (key) {
|
||||
return {
|
||||
type: actions.SHOW_PRIVATE_KEY,
|
||||
@ -873,6 +1191,12 @@ function showSendPage () {
|
||||
}
|
||||
}
|
||||
|
||||
function showSendTokenPage () {
|
||||
return {
|
||||
type: actions.SHOW_SEND_TOKEN_PAGE,
|
||||
}
|
||||
}
|
||||
|
||||
function buyEth (opts) {
|
||||
return (dispatch) => {
|
||||
const url = getBuyEthUrl(opts)
|
||||
@ -970,7 +1294,10 @@ function reshowQrCode (data, coin) {
|
||||
]
|
||||
|
||||
dispatch(actions.hideLoadingIndication())
|
||||
return dispatch(actions.showQrView(data, message))
|
||||
return dispatch(actions.showModal({
|
||||
name: 'SHAPESHIFT_DEPOSIT_TX',
|
||||
Qr: { data, message },
|
||||
}))
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -999,6 +1326,28 @@ function shapeShiftRequest (query, options, cb) {
|
||||
}
|
||||
}
|
||||
|
||||
function updateTokenExchangeRate (token = '') {
|
||||
const pair = `${token.toLowerCase()}_eth`
|
||||
|
||||
return dispatch => {
|
||||
if (!token) {
|
||||
return
|
||||
}
|
||||
|
||||
shapeShiftRequest('marketinfo', { pair }, marketinfo => {
|
||||
if (!marketinfo.error) {
|
||||
dispatch({
|
||||
type: actions.UPDATE_TOKEN_EXCHANGE_RATE,
|
||||
payload: {
|
||||
pair,
|
||||
marketinfo,
|
||||
},
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Call Background Then Update
|
||||
//
|
||||
// A function generator for a common pattern wherein:
|
||||
@ -1040,3 +1389,9 @@ function forceUpdateMetamaskState (dispatch) {
|
||||
dispatch(actions.updateMetamaskState(newState))
|
||||
})
|
||||
}
|
||||
|
||||
function toggleAccountMenu () {
|
||||
return {
|
||||
type: actions.TOGGLE_ACCOUNT_MENU,
|
||||
}
|
||||
}
|
||||
|
@ -1,169 +1,65 @@
|
||||
const inherits = require('util').inherits
|
||||
const Component = require('react').Component
|
||||
const classnames = require('classnames')
|
||||
const h = require('react-hyperscript')
|
||||
const connect = require('react-redux').connect
|
||||
const Fuse = require('fuse.js')
|
||||
const contractMap = require('eth-contract-metadata')
|
||||
const TokenBalance = require('./components/token-balance')
|
||||
const Identicon = require('./components/identicon')
|
||||
const contractList = Object.entries(contractMap).map(([ _, tokenData]) => tokenData)
|
||||
const fuse = new Fuse(contractList, {
|
||||
shouldSort: true,
|
||||
threshold: 0.45,
|
||||
location: 0,
|
||||
distance: 100,
|
||||
maxPatternLength: 32,
|
||||
minMatchCharLength: 1,
|
||||
keys: ['address', 'name', 'symbol'],
|
||||
})
|
||||
const actions = require('./actions')
|
||||
const Tooltip = require('./components/tooltip.js')
|
||||
|
||||
|
||||
const ethUtil = require('ethereumjs-util')
|
||||
const abi = require('human-standard-token-abi')
|
||||
const Eth = require('ethjs-query')
|
||||
const EthContract = require('ethjs-contract')
|
||||
const R = require('ramda')
|
||||
|
||||
const emptyAddr = '0x0000000000000000000000000000000000000000'
|
||||
|
||||
module.exports = connect(mapStateToProps)(AddTokenScreen)
|
||||
module.exports = connect(mapStateToProps, mapDispatchToProps)(AddTokenScreen)
|
||||
|
||||
function mapStateToProps (state) {
|
||||
const { identities, tokens } = state.metamask
|
||||
return {
|
||||
identities: state.metamask.identities,
|
||||
identities,
|
||||
tokens,
|
||||
}
|
||||
}
|
||||
|
||||
function mapDispatchToProps (dispatch) {
|
||||
return {
|
||||
goHome: () => dispatch(actions.goHome()),
|
||||
addTokens: tokens => dispatch(actions.addTokens(tokens)),
|
||||
}
|
||||
}
|
||||
|
||||
inherits(AddTokenScreen, Component)
|
||||
function AddTokenScreen () {
|
||||
this.state = {
|
||||
warning: null,
|
||||
address: null,
|
||||
symbol: 'TOKEN',
|
||||
decimals: 18,
|
||||
isShowingConfirmation: false,
|
||||
customAddress: '',
|
||||
customSymbol: '',
|
||||
customDecimals: 0,
|
||||
searchQuery: '',
|
||||
isCollapsed: true,
|
||||
selectedTokens: {},
|
||||
errors: {},
|
||||
}
|
||||
this.tokenAddressDidChange = this.tokenAddressDidChange.bind(this)
|
||||
this.onNext = this.onNext.bind(this)
|
||||
Component.call(this)
|
||||
}
|
||||
|
||||
AddTokenScreen.prototype.render = function () {
|
||||
const state = this.state
|
||||
const props = this.props
|
||||
const { warning, symbol, decimals } = state
|
||||
|
||||
return (
|
||||
h('.flex-column.flex-grow', [
|
||||
|
||||
// subtitle and nav
|
||||
h('.section-title.flex-row.flex-center', [
|
||||
h('i.fa.fa-arrow-left.fa-lg.cursor-pointer', {
|
||||
onClick: (event) => {
|
||||
props.dispatch(actions.goHome())
|
||||
},
|
||||
}),
|
||||
h('h2.page-subtitle', 'Add Token'),
|
||||
]),
|
||||
|
||||
h('.error', {
|
||||
style: {
|
||||
display: warning ? 'block' : 'none',
|
||||
padding: '0 20px',
|
||||
textAlign: 'center',
|
||||
},
|
||||
}, warning),
|
||||
|
||||
// conf view
|
||||
h('.flex-column.flex-justify-center.flex-grow.select-none', [
|
||||
h('.flex-space-around', {
|
||||
style: {
|
||||
padding: '20px',
|
||||
},
|
||||
}, [
|
||||
|
||||
h('div', [
|
||||
h(Tooltip, {
|
||||
position: 'top',
|
||||
title: 'The contract of the actual token contract. Click for more info.',
|
||||
}, [
|
||||
h('a', {
|
||||
style: { fontWeight: 'bold', paddingRight: '10px'},
|
||||
href: 'https://support.metamask.io/kb/article/24-what-is-a-token-contract-address',
|
||||
target: '_blank',
|
||||
}, [
|
||||
h('span', 'Token Contract Address '),
|
||||
h('i.fa.fa-question-circle'),
|
||||
]),
|
||||
]),
|
||||
]),
|
||||
|
||||
h('section.flex-row.flex-center', [
|
||||
h('input#token-address', {
|
||||
name: 'address',
|
||||
placeholder: 'Token Contract Address',
|
||||
onChange: this.tokenAddressDidChange.bind(this),
|
||||
style: {
|
||||
width: 'inherit',
|
||||
flex: '1 0 auto',
|
||||
height: '30px',
|
||||
margin: '8px',
|
||||
},
|
||||
}),
|
||||
]),
|
||||
|
||||
h('div', [
|
||||
h('span', {
|
||||
style: { fontWeight: 'bold', paddingRight: '10px'},
|
||||
}, 'Token Symbol'),
|
||||
]),
|
||||
|
||||
h('div', { style: {display: 'flex'} }, [
|
||||
h('input#token_symbol', {
|
||||
placeholder: `Like "ETH"`,
|
||||
value: symbol,
|
||||
style: {
|
||||
width: 'inherit',
|
||||
flex: '1 0 auto',
|
||||
height: '30px',
|
||||
margin: '8px',
|
||||
},
|
||||
onChange: (event) => {
|
||||
var element = event.target
|
||||
var symbol = element.value
|
||||
this.setState({ symbol })
|
||||
},
|
||||
}),
|
||||
]),
|
||||
|
||||
h('div', [
|
||||
h('span', {
|
||||
style: { fontWeight: 'bold', paddingRight: '10px'},
|
||||
}, 'Decimals of Precision'),
|
||||
]),
|
||||
|
||||
h('div', { style: {display: 'flex'} }, [
|
||||
h('input#token_decimals', {
|
||||
value: decimals,
|
||||
type: 'number',
|
||||
min: 0,
|
||||
max: 36,
|
||||
style: {
|
||||
width: 'inherit',
|
||||
flex: '1 0 auto',
|
||||
height: '30px',
|
||||
margin: '8px',
|
||||
},
|
||||
onChange: (event) => {
|
||||
var element = event.target
|
||||
var decimals = element.value.trim()
|
||||
this.setState({ decimals })
|
||||
},
|
||||
}),
|
||||
]),
|
||||
|
||||
h('button', {
|
||||
style: {
|
||||
alignSelf: 'center',
|
||||
},
|
||||
onClick: (event) => {
|
||||
const valid = this.validateInputs()
|
||||
if (!valid) return
|
||||
|
||||
const { address, symbol, decimals } = this.state
|
||||
this.props.dispatch(actions.addToken(address.trim(), symbol.trim(), decimals))
|
||||
},
|
||||
}, 'Add'),
|
||||
]),
|
||||
]),
|
||||
])
|
||||
)
|
||||
}
|
||||
|
||||
AddTokenScreen.prototype.componentWillMount = function () {
|
||||
if (typeof global.ethereumProvider === 'undefined') return
|
||||
|
||||
@ -172,54 +68,96 @@ AddTokenScreen.prototype.componentWillMount = function () {
|
||||
this.TokenContract = this.contract(abi)
|
||||
}
|
||||
|
||||
AddTokenScreen.prototype.tokenAddressDidChange = function (event) {
|
||||
const el = event.target
|
||||
const address = el.value.trim()
|
||||
if (ethUtil.isValidAddress(address) && address !== emptyAddr) {
|
||||
this.setState({ address })
|
||||
this.attemptToAutoFillTokenParams(address)
|
||||
AddTokenScreen.prototype.toggleToken = function (address, token) {
|
||||
const { selectedTokens, errors } = this.state
|
||||
const { [address]: selectedToken } = selectedTokens
|
||||
this.setState({
|
||||
selectedTokens: {
|
||||
...selectedTokens,
|
||||
[address]: selectedToken ? null : token,
|
||||
},
|
||||
errors: {
|
||||
...errors,
|
||||
tokenSelector: null,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
AddTokenScreen.prototype.onNext = function () {
|
||||
const { isValid, errors } = this.validate()
|
||||
|
||||
return !isValid
|
||||
? this.setState({ errors })
|
||||
: this.setState({ isShowingConfirmation: true })
|
||||
}
|
||||
|
||||
AddTokenScreen.prototype.tokenAddressDidChange = function (e) {
|
||||
const customAddress = e.target.value.trim()
|
||||
this.setState({ customAddress })
|
||||
if (ethUtil.isValidAddress(customAddress) && customAddress !== emptyAddr) {
|
||||
this.attemptToAutoFillTokenParams(customAddress)
|
||||
} else {
|
||||
this.setState({
|
||||
customSymbol: '',
|
||||
customDecimals: 0,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
AddTokenScreen.prototype.validateInputs = function () {
|
||||
let msg = ''
|
||||
const state = this.state
|
||||
AddTokenScreen.prototype.checkExistingAddresses = function (address) {
|
||||
const tokensList = this.props.tokens
|
||||
const matchesAddress = existingToken => {
|
||||
return existingToken.address.toLowerCase() === address.toLowerCase()
|
||||
}
|
||||
|
||||
return R.any(matchesAddress)(tokensList)
|
||||
}
|
||||
|
||||
AddTokenScreen.prototype.validate = function () {
|
||||
const errors = {}
|
||||
const identitiesList = Object.keys(this.props.identities)
|
||||
const { address, symbol, decimals } = state
|
||||
const standardAddress = ethUtil.addHexPrefix(address).toLowerCase()
|
||||
const { customAddress, customSymbol, customDecimals, selectedTokens } = this.state
|
||||
const standardAddress = ethUtil.addHexPrefix(customAddress).toLowerCase()
|
||||
|
||||
const validAddress = ethUtil.isValidAddress(address)
|
||||
if (!validAddress) {
|
||||
msg += 'Address is invalid. '
|
||||
if (customAddress) {
|
||||
const validAddress = ethUtil.isValidAddress(customAddress)
|
||||
if (!validAddress) {
|
||||
errors.customAddress = 'Address is invalid. '
|
||||
}
|
||||
|
||||
const validDecimals = customDecimals >= 0 && customDecimals < 36
|
||||
if (!validDecimals) {
|
||||
errors.customDecimals = 'Decimals must be at least 0, and not over 36.'
|
||||
}
|
||||
|
||||
const symbolLen = customSymbol.trim().length
|
||||
const validSymbol = symbolLen > 0 && symbolLen < 10
|
||||
if (!validSymbol) {
|
||||
errors.customSymbol = 'Symbol must be between 0 and 10 characters.'
|
||||
}
|
||||
|
||||
const ownAddress = identitiesList.includes(standardAddress)
|
||||
if (ownAddress) {
|
||||
errors.customAddress = 'Personal address detected. Input the token contract address.'
|
||||
}
|
||||
|
||||
const tokenAlreadyAdded = this.checkExistingAddresses(customAddress)
|
||||
if (tokenAlreadyAdded) {
|
||||
errors.customAddress = 'Token has already been added.'
|
||||
}
|
||||
} else if (
|
||||
Object.entries(selectedTokens)
|
||||
.reduce((isEmpty, [ symbol, isSelected ]) => (
|
||||
isEmpty && !isSelected
|
||||
), true)
|
||||
) {
|
||||
errors.tokenSelector = 'Must select at least 1 token.'
|
||||
}
|
||||
|
||||
const validDecimals = decimals >= 0 && decimals < 36
|
||||
if (!validDecimals) {
|
||||
msg += 'Decimals must be at least 0, and not over 36. '
|
||||
return {
|
||||
isValid: !Object.keys(errors).length,
|
||||
errors,
|
||||
}
|
||||
|
||||
const symbolLen = symbol.trim().length
|
||||
const validSymbol = symbolLen > 0 && symbolLen < 10
|
||||
if (!validSymbol) {
|
||||
msg += 'Symbol must be between 0 and 10 characters.'
|
||||
}
|
||||
|
||||
const ownAddress = identitiesList.includes(standardAddress)
|
||||
if (ownAddress) {
|
||||
msg = 'Personal address detected. Input the token contract address.'
|
||||
}
|
||||
|
||||
const isValid = validAddress && validDecimals && !ownAddress
|
||||
|
||||
if (!isValid) {
|
||||
this.setState({
|
||||
warning: msg,
|
||||
})
|
||||
} else {
|
||||
this.setState({ warning: null })
|
||||
}
|
||||
|
||||
return isValid
|
||||
}
|
||||
|
||||
AddTokenScreen.prototype.attemptToAutoFillTokenParams = async function (address) {
|
||||
@ -232,7 +170,195 @@ AddTokenScreen.prototype.attemptToAutoFillTokenParams = async function (address)
|
||||
|
||||
const [ symbol, decimals ] = results
|
||||
if (symbol && decimals) {
|
||||
console.log('SETTING SYMBOL AND DECIMALS', { symbol, decimals })
|
||||
this.setState({ symbol: symbol[0], decimals: decimals[0].toString() })
|
||||
this.setState({
|
||||
customSymbol: symbol[0],
|
||||
customDecimals: decimals[0].toString(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
AddTokenScreen.prototype.renderCustomForm = function () {
|
||||
const { customAddress, customSymbol, customDecimals, errors } = this.state
|
||||
|
||||
return !this.state.isCollapsed && (
|
||||
h('div.add-token__add-custom-form', [
|
||||
h('div', {
|
||||
className: classnames('add-token__add-custom-field', {
|
||||
'add-token__add-custom-field--error': errors.customAddress,
|
||||
}),
|
||||
}, [
|
||||
h('div.add-token__add-custom-label', 'Token Address'),
|
||||
h('input.add-token__add-custom-input', {
|
||||
type: 'text',
|
||||
onChange: this.tokenAddressDidChange,
|
||||
value: customAddress,
|
||||
}),
|
||||
h('div.add-token__add-custom-error-message', errors.customAddress),
|
||||
]),
|
||||
h('div', {
|
||||
className: classnames('add-token__add-custom-field', {
|
||||
'add-token__add-custom-field--error': errors.customSymbol,
|
||||
}),
|
||||
}, [
|
||||
h('div.add-token__add-custom-label', 'Token Symbol'),
|
||||
h('input.add-token__add-custom-input', {
|
||||
type: 'text',
|
||||
value: customSymbol,
|
||||
disabled: true,
|
||||
}),
|
||||
h('div.add-token__add-custom-error-message', errors.customSymbol),
|
||||
]),
|
||||
h('div', {
|
||||
className: classnames('add-token__add-custom-field', {
|
||||
'add-token__add-custom-field--error': errors.customDecimals,
|
||||
}),
|
||||
}, [
|
||||
h('div.add-token__add-custom-label', 'Decimals of Precision'),
|
||||
h('input.add-token__add-custom-input', {
|
||||
type: 'number',
|
||||
value: customDecimals,
|
||||
disabled: true,
|
||||
}),
|
||||
h('div.add-token__add-custom-error-message', errors.customDecimals),
|
||||
]),
|
||||
])
|
||||
)
|
||||
}
|
||||
|
||||
AddTokenScreen.prototype.renderTokenList = function () {
|
||||
const { searchQuery = '', selectedTokens } = this.state
|
||||
const results = searchQuery
|
||||
? fuse.search(searchQuery) || []
|
||||
: contractList
|
||||
|
||||
return Array(6).fill(undefined)
|
||||
.map((_, i) => {
|
||||
const { logo, symbol, name, address } = results[i] || {}
|
||||
const tokenAlreadyAdded = this.checkExistingAddresses(address)
|
||||
return Boolean(logo || symbol || name) && (
|
||||
h('div.add-token__token-wrapper', {
|
||||
className: classnames({
|
||||
'add-token__token-wrapper--selected': selectedTokens[address],
|
||||
'add-token__token-wrapper--disabled': tokenAlreadyAdded,
|
||||
}),
|
||||
onClick: () => !tokenAlreadyAdded && this.toggleToken(address, results[i]),
|
||||
}, [
|
||||
h('div.add-token__token-icon', {
|
||||
style: {
|
||||
backgroundImage: `url(images/contract/${logo})`,
|
||||
},
|
||||
}),
|
||||
h('div.add-token__token-data', [
|
||||
h('div.add-token__token-symbol', symbol),
|
||||
h('div.add-token__token-name', name),
|
||||
]),
|
||||
tokenAlreadyAdded && (
|
||||
h('div.add-token__token-message', 'Already added')
|
||||
),
|
||||
])
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
AddTokenScreen.prototype.renderConfirmation = function () {
|
||||
const {
|
||||
customAddress: address,
|
||||
customSymbol: symbol,
|
||||
customDecimals: decimals,
|
||||
selectedTokens,
|
||||
} = this.state
|
||||
|
||||
const { addTokens, goHome } = this.props
|
||||
|
||||
const customToken = {
|
||||
address,
|
||||
symbol,
|
||||
decimals,
|
||||
}
|
||||
|
||||
const tokens = address && symbol && decimals
|
||||
? { ...selectedTokens, [address]: customToken }
|
||||
: selectedTokens
|
||||
|
||||
return (
|
||||
h('div.add-token', [
|
||||
h('div.add-token__wrapper', [
|
||||
h('div.add-token__title-container.add-token__confirmation-title', [
|
||||
h('div.add-token__title', 'Add Token'),
|
||||
h('div.add-token__description', 'Would you like to add these tokens?'),
|
||||
]),
|
||||
h('div.add-token__content-container.add-token__confirmation-content', [
|
||||
h('div.add-token__description.add-token__confirmation-description', 'Your balances'),
|
||||
h('div.add-token__confirmation-token-list',
|
||||
Object.entries(tokens)
|
||||
.map(([ address, token ]) => (
|
||||
h('span.add-token__confirmation-token-list-item', [
|
||||
h(Identicon, {
|
||||
className: 'add-token__confirmation-token-icon',
|
||||
diameter: 75,
|
||||
address,
|
||||
}),
|
||||
h(TokenBalance, { token }),
|
||||
])
|
||||
))
|
||||
),
|
||||
]),
|
||||
]),
|
||||
h('div.add-token__buttons', [
|
||||
h('button.btn-secondary', {
|
||||
onClick: () => addTokens(tokens).then(goHome),
|
||||
}, 'Add Tokens'),
|
||||
h('button.btn-tertiary', {
|
||||
onClick: () => this.setState({ isShowingConfirmation: false }),
|
||||
}, 'Back'),
|
||||
]),
|
||||
])
|
||||
)
|
||||
}
|
||||
|
||||
AddTokenScreen.prototype.render = function () {
|
||||
const { isCollapsed, errors, isShowingConfirmation } = this.state
|
||||
const { goHome } = this.props
|
||||
|
||||
return isShowingConfirmation
|
||||
? this.renderConfirmation()
|
||||
: (
|
||||
h('div.add-token', [
|
||||
h('div.add-token__wrapper', [
|
||||
h('div.add-token__title-container', [
|
||||
h('div.add-token__title', 'Add Token'),
|
||||
h('div.add-token__description', 'Keep track of the tokens you’ve bought with your MetaMask account. If you bought tokens using a different account, those tokens will not appear here.'),
|
||||
h('div.add-token__description', 'Search for tokens or select from our list of popular tokens.'),
|
||||
]),
|
||||
h('div.add-token__content-container', [
|
||||
h('div.add-token__input-container', [
|
||||
h('input.add-token__input', {
|
||||
type: 'text',
|
||||
placeholder: 'Search',
|
||||
onChange: e => this.setState({ searchQuery: e.target.value }),
|
||||
}),
|
||||
h('div.add-token__search-input-error-message', errors.tokenSelector),
|
||||
]),
|
||||
h(
|
||||
'div.add-token__token-icons-container',
|
||||
this.renderTokenList(),
|
||||
),
|
||||
]),
|
||||
h('div.add-token__footers', [
|
||||
h('div.add-token__add-custom', {
|
||||
onClick: () => this.setState({ isCollapsed: !isCollapsed }),
|
||||
}, 'Add custom token'),
|
||||
this.renderCustomForm(),
|
||||
]),
|
||||
]),
|
||||
h('div.add-token__buttons', [
|
||||
h('button.btn-secondary', {
|
||||
onClick: this.onNext,
|
||||
}, 'Next'),
|
||||
h('button.btn-tertiary', {
|
||||
onClick: goHome,
|
||||
}, 'Cancel'),
|
||||
]),
|
||||
])
|
||||
)
|
||||
}
|
||||
|
563
ui/app/app.js
@ -2,37 +2,44 @@ const inherits = require('util').inherits
|
||||
const Component = require('react').Component
|
||||
const connect = require('react-redux').connect
|
||||
const h = require('react-hyperscript')
|
||||
const { checkFeatureToggle } = require('../lib/feature-toggle-utils')
|
||||
const actions = require('./actions')
|
||||
// init
|
||||
const InitializeMenuScreen = require('./first-time/init-menu')
|
||||
const NewKeyChainScreen = require('./new-keychain')
|
||||
// unlock
|
||||
const UnlockScreen = require('./unlock')
|
||||
// accounts
|
||||
const AccountDetailScreen = require('./account-detail')
|
||||
const MainContainer = require('./main-container')
|
||||
const SendTransactionScreen = require('./send')
|
||||
const SendTransactionScreen2 = require('./components/send/send-v2-container')
|
||||
const SendTokenScreen = require('./components/send-token')
|
||||
const ConfirmTxScreen = require('./conf-tx')
|
||||
// notice
|
||||
const NoticeScreen = require('./components/notice')
|
||||
const generateLostAccountsNotice = require('../lib/lost-accounts-notice')
|
||||
|
||||
// slideout menu
|
||||
const WalletView = require('./components/wallet-view')
|
||||
|
||||
// other views
|
||||
const ConfigScreen = require('./config')
|
||||
const AddTokenScreen = require('./add-token')
|
||||
const Import = require('./accounts/import')
|
||||
const InfoScreen = require('./info')
|
||||
const Loading = require('./components/loading')
|
||||
const SandwichExpando = require('sandwich-expando')
|
||||
const Dropdown = require('./components/dropdown').Dropdown
|
||||
const DropdownMenuItem = require('./components/dropdown').DropdownMenuItem
|
||||
const NetworkIndicator = require('./components/network')
|
||||
const Identicon = require('./components/identicon')
|
||||
const BuyView = require('./components/buy-button-subview')
|
||||
const QrView = require('./components/qr-code')
|
||||
const HDCreateVaultComplete = require('./keychains/hd/create-vault-complete')
|
||||
const HDRestoreVaultScreen = require('./keychains/hd/restore-vault')
|
||||
const RevealSeedConfirmation = require('./keychains/hd/recover-seed/confirmation')
|
||||
const AccountDropdowns = require('./components/account-dropdowns').AccountDropdowns
|
||||
const ReactCSSTransitionGroup = require('react-addons-css-transition-group')
|
||||
const NetworkDropdown = require('./components/dropdowns/network-dropdown')
|
||||
const AccountMenu = require('./components/account-menu')
|
||||
|
||||
module.exports = connect(mapStateToProps)(App)
|
||||
// Global Modals
|
||||
const Modal = require('./components/modals/index').Modal
|
||||
|
||||
module.exports = connect(mapStateToProps, mapDispatchToProps)(App)
|
||||
|
||||
inherits(App, Component)
|
||||
function App () { Component.call(this) }
|
||||
@ -48,11 +55,14 @@ function mapStateToProps (state) {
|
||||
|
||||
return {
|
||||
// state from plugin
|
||||
networkDropdownOpen: state.appState.networkDropdownOpen,
|
||||
sidebarOpen: state.appState.sidebarOpen,
|
||||
isLoading: state.appState.isLoading,
|
||||
loadingMessage: state.appState.loadingMessage,
|
||||
noActiveNotices: state.metamask.noActiveNotices,
|
||||
isInitialized: state.metamask.isInitialized,
|
||||
isUnlocked: state.metamask.isUnlocked,
|
||||
selectedAddress: state.metamask.selectedAddress,
|
||||
currentView: state.appState.currentView,
|
||||
activeAddress: state.appState.activeAddress,
|
||||
transForward: state.appState.transForward,
|
||||
@ -74,9 +84,24 @@ function mapStateToProps (state) {
|
||||
}
|
||||
}
|
||||
|
||||
function mapDispatchToProps (dispatch, ownProps) {
|
||||
return {
|
||||
dispatch,
|
||||
hideSidebar: () => dispatch(actions.hideSidebar()),
|
||||
showNetworkDropdown: () => dispatch(actions.showNetworkDropdown()),
|
||||
hideNetworkDropdown: () => dispatch(actions.hideNetworkDropdown()),
|
||||
setCurrentCurrencyToUSD: () => dispatch(actions.setCurrentCurrency('usd')),
|
||||
toggleAccountMenu: () => dispatch(actions.toggleAccountMenu()),
|
||||
}
|
||||
}
|
||||
|
||||
App.prototype.componentWillMount = function () {
|
||||
this.props.setCurrentCurrencyToUSD()
|
||||
}
|
||||
|
||||
App.prototype.render = function () {
|
||||
var props = this.props
|
||||
const { isLoading, loadingMessage, transForward, network } = props
|
||||
const { isLoading, loadingMessage, network } = props
|
||||
const isLoadingNetwork = network === 'loading' && props.currentView.name !== 'config'
|
||||
const loadMessage = loadingMessage || isLoadingNetwork ?
|
||||
`Connecting to ${this.getNetworkName()}` : null
|
||||
@ -86,307 +111,166 @@ App.prototype.render = function () {
|
||||
|
||||
h('.flex-column.full-height', {
|
||||
style: {
|
||||
// Windows was showing a vertical scroll bar:
|
||||
overflow: 'hidden',
|
||||
overflowX: 'hidden',
|
||||
position: 'relative',
|
||||
alignItems: 'center',
|
||||
},
|
||||
}, [
|
||||
|
||||
// global modal
|
||||
h(Modal, {}, []),
|
||||
|
||||
// app bar
|
||||
this.renderAppBar(),
|
||||
this.renderNetworkDropdown(),
|
||||
this.renderDropdown(),
|
||||
|
||||
// sidebar
|
||||
this.renderSidebar(),
|
||||
|
||||
// network dropdown
|
||||
h(NetworkDropdown, {
|
||||
provider: this.props.provider,
|
||||
frequentRpcList: this.props.frequentRpcList,
|
||||
}, []),
|
||||
|
||||
h(AccountMenu),
|
||||
|
||||
h(Loading, {
|
||||
isLoading: isLoading || isLoadingNetwork,
|
||||
loadingMessage: loadMessage,
|
||||
}),
|
||||
|
||||
// panel content
|
||||
h('.app-primary' + (transForward ? '.from-right' : '.from-left'), {
|
||||
style: {
|
||||
width: '100%',
|
||||
},
|
||||
}, [
|
||||
this.renderPrimary(),
|
||||
]),
|
||||
// content
|
||||
this.renderPrimary(),
|
||||
])
|
||||
)
|
||||
}
|
||||
|
||||
App.prototype.renderGlobalModal = function () {
|
||||
return h(Modal, {
|
||||
ref: 'modalRef',
|
||||
}, [
|
||||
// h(BuyOptions, {}, []),
|
||||
])
|
||||
}
|
||||
|
||||
App.prototype.renderSidebar = function () {
|
||||
|
||||
return h('div', {
|
||||
}, [
|
||||
h('style', `
|
||||
.sidebar-enter {
|
||||
transition: transform 300ms ease-in-out;
|
||||
transform: translateX(-100%);
|
||||
}
|
||||
.sidebar-enter.sidebar-enter-active {
|
||||
transition: transform 300ms ease-in-out;
|
||||
transform: translateX(0%);
|
||||
}
|
||||
.sidebar-leave {
|
||||
transition: transform 200ms ease-out;
|
||||
transform: translateX(0%);
|
||||
}
|
||||
.sidebar-leave.sidebar-leave-active {
|
||||
transition: transform 200ms ease-out;
|
||||
transform: translateX(-100%);
|
||||
}
|
||||
`),
|
||||
|
||||
h(ReactCSSTransitionGroup, {
|
||||
transitionName: 'sidebar',
|
||||
transitionEnterTimeout: 300,
|
||||
transitionLeaveTimeout: 200,
|
||||
}, [
|
||||
// A second instance of Walletview is used for non-mobile viewports
|
||||
this.props.sidebarOpen ? h(WalletView, {
|
||||
responsiveDisplayClassname: '.sidebar',
|
||||
style: {},
|
||||
}) : undefined,
|
||||
|
||||
]),
|
||||
|
||||
// overlay
|
||||
// TODO: add onClick for overlay to close sidebar
|
||||
this.props.sidebarOpen ? h('div.sidebar-overlay', {
|
||||
style: {},
|
||||
onClick: () => {
|
||||
this.props.hideSidebar()
|
||||
},
|
||||
}, []) : undefined,
|
||||
])
|
||||
}
|
||||
|
||||
App.prototype.renderAppBar = function () {
|
||||
if (window.METAMASK_UI_TYPE === 'notification') {
|
||||
return null
|
||||
}
|
||||
|
||||
const props = this.props
|
||||
const state = this.state || {}
|
||||
const isNetworkMenuOpen = state.isNetworkMenuOpen || false
|
||||
|
||||
console.log(this.props)
|
||||
return (
|
||||
|
||||
h('.full-width', {
|
||||
height: '38px',
|
||||
style: {},
|
||||
}, [
|
||||
|
||||
h('.app-header.flex-row.flex-space-between', {
|
||||
style: {
|
||||
alignItems: 'center',
|
||||
visibility: props.isUnlocked ? 'visible' : 'none',
|
||||
background: props.isUnlocked ? 'white' : 'none',
|
||||
height: '38px',
|
||||
position: 'relative',
|
||||
zIndex: 12,
|
||||
},
|
||||
style: {},
|
||||
}, [
|
||||
|
||||
h('div.left-menu-section', {
|
||||
style: {
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
},
|
||||
}, [
|
||||
|
||||
// mini logo
|
||||
h('img', {
|
||||
height: 24,
|
||||
width: 24,
|
||||
src: '/images/icon-128.png',
|
||||
}),
|
||||
|
||||
h(NetworkIndicator, {
|
||||
network: this.props.network,
|
||||
provider: this.props.provider,
|
||||
onClick: (event) => {
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
this.setState({ isNetworkMenuOpen: !isNetworkMenuOpen })
|
||||
},
|
||||
}),
|
||||
]),
|
||||
|
||||
props.isUnlocked && h('div', {
|
||||
style: {
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
},
|
||||
}, [
|
||||
|
||||
props.isUnlocked && h(AccountDropdowns, {
|
||||
h('div.app-header-contents', {}, [
|
||||
h('div.left-menu-wrapper', {
|
||||
style: {},
|
||||
enableAccountsSelector: true,
|
||||
identities: this.props.identities,
|
||||
selected: this.props.currentView.context,
|
||||
network: this.props.network,
|
||||
keyrings: this.props.keyrings,
|
||||
}, []),
|
||||
}, [
|
||||
// mini logo
|
||||
h('img', {
|
||||
height: 24,
|
||||
width: 24,
|
||||
src: '/images/icon-128.png',
|
||||
}),
|
||||
|
||||
// hamburger
|
||||
props.isUnlocked && h(SandwichExpando, {
|
||||
className: 'sandwich-expando',
|
||||
width: 16,
|
||||
barHeight: 2,
|
||||
padding: 0,
|
||||
isOpen: state.isMainMenuOpen,
|
||||
color: 'rgb(247,146,30)',
|
||||
onClick: () => {
|
||||
this.setState({
|
||||
isMainMenuOpen: !state.isMainMenuOpen,
|
||||
})
|
||||
},
|
||||
}),
|
||||
// metamask name
|
||||
h('h1', {
|
||||
style: {
|
||||
position: 'relative',
|
||||
paddingLeft: '9px',
|
||||
color: '#5B5D67',
|
||||
},
|
||||
}, 'MetaMask'),
|
||||
|
||||
]),
|
||||
|
||||
h('div.header__right-actions', [
|
||||
h('div.network-component-wrapper', {
|
||||
style: {},
|
||||
}, [
|
||||
// Network Indicator
|
||||
h(NetworkIndicator, {
|
||||
network: this.props.network,
|
||||
provider: this.props.provider,
|
||||
onClick: (event) => {
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
if (this.props.networkDropdownOpen === false) {
|
||||
this.props.showNetworkDropdown()
|
||||
} else {
|
||||
this.props.hideNetworkDropdown()
|
||||
}
|
||||
},
|
||||
}),
|
||||
|
||||
]),
|
||||
|
||||
h('div.account-menu__icon', { onClick: this.props.toggleAccountMenu }, [
|
||||
h(Identicon, {
|
||||
address: this.props.selectedAddress,
|
||||
diameter: 32,
|
||||
}),
|
||||
]),
|
||||
]),
|
||||
]),
|
||||
]),
|
||||
|
||||
])
|
||||
)
|
||||
}
|
||||
|
||||
App.prototype.renderNetworkDropdown = function () {
|
||||
const props = this.props
|
||||
const { provider: { type: providerType, rpcTarget: activeNetwork } } = props
|
||||
const rpcList = props.frequentRpcList
|
||||
const state = this.state || {}
|
||||
const isOpen = state.isNetworkMenuOpen
|
||||
|
||||
return h(Dropdown, {
|
||||
useCssTransition: true,
|
||||
isOpen,
|
||||
onClickOutside: (event) => {
|
||||
const { classList } = event.target
|
||||
const isNotToggleElement = [
|
||||
classList.contains('menu-icon'),
|
||||
classList.contains('network-name'),
|
||||
classList.contains('network-indicator'),
|
||||
].filter(bool => bool).length === 0
|
||||
// classes from three constituent nodes of the toggle element
|
||||
|
||||
if (isNotToggleElement) {
|
||||
this.setState({ isNetworkMenuOpen: false })
|
||||
}
|
||||
},
|
||||
zIndex: 11,
|
||||
style: {
|
||||
position: 'absolute',
|
||||
left: '2px',
|
||||
top: '36px',
|
||||
},
|
||||
innerStyle: {
|
||||
padding: '2px 16px 2px 0px',
|
||||
},
|
||||
}, [
|
||||
|
||||
h(
|
||||
DropdownMenuItem,
|
||||
{
|
||||
key: 'main',
|
||||
closeMenu: () => this.setState({ isNetworkMenuOpen: !isOpen }),
|
||||
onClick: () => props.dispatch(actions.setProviderType('mainnet')),
|
||||
style: {
|
||||
fontSize: '18px',
|
||||
},
|
||||
},
|
||||
[
|
||||
h('.menu-icon.diamond'),
|
||||
'Main Ethereum Network',
|
||||
providerType === 'mainnet' ? h('.check', '✓') : null,
|
||||
]
|
||||
),
|
||||
|
||||
h(
|
||||
DropdownMenuItem,
|
||||
{
|
||||
key: 'ropsten',
|
||||
closeMenu: () => this.setState({ isNetworkMenuOpen: !isOpen }),
|
||||
onClick: () => props.dispatch(actions.setProviderType('ropsten')),
|
||||
style: {
|
||||
fontSize: '18px',
|
||||
},
|
||||
},
|
||||
[
|
||||
h('.menu-icon.red-dot'),
|
||||
'Ropsten Test Network',
|
||||
providerType === 'ropsten' ? h('.check', '✓') : null,
|
||||
]
|
||||
),
|
||||
|
||||
h(
|
||||
DropdownMenuItem,
|
||||
{
|
||||
key: 'kovan',
|
||||
closeMenu: () => this.setState({ isNetworkMenuOpen: !isOpen }),
|
||||
onClick: () => props.dispatch(actions.setProviderType('kovan')),
|
||||
style: {
|
||||
fontSize: '18px',
|
||||
},
|
||||
},
|
||||
[
|
||||
h('.menu-icon.hollow-diamond'),
|
||||
'Kovan Test Network',
|
||||
providerType === 'kovan' ? h('.check', '✓') : null,
|
||||
]
|
||||
),
|
||||
|
||||
h(
|
||||
DropdownMenuItem,
|
||||
{
|
||||
key: 'rinkeby',
|
||||
closeMenu: () => this.setState({ isNetworkMenuOpen: !isOpen }),
|
||||
onClick: () => props.dispatch(actions.setProviderType('rinkeby')),
|
||||
style: {
|
||||
fontSize: '18px',
|
||||
},
|
||||
},
|
||||
[
|
||||
h('.menu-icon.golden-square'),
|
||||
'Rinkeby Test Network',
|
||||
providerType === 'rinkeby' ? h('.check', '✓') : null,
|
||||
]
|
||||
),
|
||||
|
||||
h(
|
||||
DropdownMenuItem,
|
||||
{
|
||||
key: 'default',
|
||||
closeMenu: () => this.setState({ isNetworkMenuOpen: !isOpen }),
|
||||
onClick: () => props.dispatch(actions.setProviderType('localhost')),
|
||||
style: {
|
||||
fontSize: '18px',
|
||||
},
|
||||
},
|
||||
[
|
||||
h('i.fa.fa-question-circle.fa-lg.menu-icon'),
|
||||
'Localhost 8545',
|
||||
activeNetwork === 'http://localhost:8545' ? h('.check', '✓') : null,
|
||||
]
|
||||
),
|
||||
|
||||
this.renderCustomOption(props.provider),
|
||||
this.renderCommonRpc(rpcList, props.provider),
|
||||
|
||||
h(
|
||||
DropdownMenuItem,
|
||||
{
|
||||
closeMenu: () => this.setState({ isNetworkMenuOpen: !isOpen }),
|
||||
onClick: () => this.props.dispatch(actions.showConfigPage()),
|
||||
style: {
|
||||
fontSize: '18px',
|
||||
},
|
||||
},
|
||||
[
|
||||
h('i.fa.fa-question-circle.fa-lg.menu-icon'),
|
||||
'Custom RPC',
|
||||
activeNetwork === 'custom' ? h('.check', '✓') : null,
|
||||
]
|
||||
),
|
||||
|
||||
])
|
||||
}
|
||||
|
||||
App.prototype.renderDropdown = function () {
|
||||
const state = this.state || {}
|
||||
const isOpen = state.isMainMenuOpen
|
||||
|
||||
return h(Dropdown, {
|
||||
useCssTransition: true,
|
||||
isOpen: isOpen,
|
||||
zIndex: 11,
|
||||
onClickOutside: (event) => {
|
||||
const classList = event.target.classList
|
||||
const parentClassList = event.target.parentElement.classList
|
||||
|
||||
const isToggleElement = classList.contains('sandwich-expando') ||
|
||||
parentClassList.contains('sandwich-expando')
|
||||
|
||||
if (isOpen && !isToggleElement) {
|
||||
this.setState({ isMainMenuOpen: false })
|
||||
}
|
||||
},
|
||||
style: {
|
||||
position: 'absolute',
|
||||
right: '2px',
|
||||
top: '38px',
|
||||
},
|
||||
innerStyle: {},
|
||||
}, [
|
||||
h(DropdownMenuItem, {
|
||||
closeMenu: () => this.setState({ isMainMenuOpen: !isOpen }),
|
||||
onClick: () => { this.props.dispatch(actions.showConfigPage()) },
|
||||
}, 'Settings'),
|
||||
|
||||
h(DropdownMenuItem, {
|
||||
closeMenu: () => this.setState({ isMainMenuOpen: !isOpen }),
|
||||
onClick: () => { this.props.dispatch(actions.lockMetamask()) },
|
||||
}, 'Lock'),
|
||||
|
||||
h(DropdownMenuItem, {
|
||||
closeMenu: () => this.setState({ isMainMenuOpen: !isOpen }),
|
||||
onClick: () => { this.props.dispatch(actions.showInfoPage()) },
|
||||
}, 'Info/Help'),
|
||||
])
|
||||
}
|
||||
|
||||
App.prototype.renderBackButton = function (style, justArrow = false) {
|
||||
var props = this.props
|
||||
@ -451,32 +335,38 @@ App.prototype.renderPrimary = function () {
|
||||
|
||||
// show unlock screen
|
||||
if (!props.isUnlocked) {
|
||||
switch (props.currentView.name) {
|
||||
|
||||
case 'restoreVault':
|
||||
log.debug('rendering restore vault screen')
|
||||
return h(HDRestoreVaultScreen, {key: 'HDRestoreVaultScreen'})
|
||||
|
||||
case 'config':
|
||||
log.debug('rendering config screen from unlock screen.')
|
||||
return h(ConfigScreen, {key: 'config'})
|
||||
|
||||
default:
|
||||
log.debug('rendering locked screen')
|
||||
return h(UnlockScreen, {key: 'locked'})
|
||||
}
|
||||
return h(MainContainer, {
|
||||
currentViewName: props.currentView.name,
|
||||
isUnlocked: props.isUnlocked,
|
||||
})
|
||||
}
|
||||
|
||||
// show current view
|
||||
switch (props.currentView.name) {
|
||||
|
||||
case 'accountDetail':
|
||||
log.debug('rendering account detail screen')
|
||||
return h(AccountDetailScreen, {key: 'account-detail'})
|
||||
log.debug('rendering main container')
|
||||
return h(MainContainer, {key: 'account-detail'})
|
||||
|
||||
case 'sendTransaction':
|
||||
log.debug('rendering send tx screen')
|
||||
return h(SendTransactionScreen, {key: 'send-transaction'})
|
||||
|
||||
// Going to leave this here until we are ready to delete SendTransactionScreen v1
|
||||
// const SendComponentToRender = checkFeatureToggle('send-v2')
|
||||
// ? SendTransactionScreen2
|
||||
// : SendTransactionScreen
|
||||
|
||||
return h(SendTransactionScreen2, {key: 'send-transaction'})
|
||||
|
||||
case 'sendToken':
|
||||
log.debug('rendering send token screen')
|
||||
|
||||
// Going to leave this here until we are ready to delete SendTransactionScreen v1
|
||||
// const SendTokenComponentToRender = checkFeatureToggle('send-v2')
|
||||
// ? SendTransactionScreen2
|
||||
// : SendTokenScreen
|
||||
|
||||
return h(SendTransactionScreen2, {key: 'sendToken'})
|
||||
|
||||
case 'newKeychain':
|
||||
log.debug('rendering new keychain screen')
|
||||
@ -510,37 +400,9 @@ App.prototype.renderPrimary = function () {
|
||||
log.debug('rendering buy ether screen')
|
||||
return h(BuyView, {key: 'buyEthView'})
|
||||
|
||||
case 'qr':
|
||||
log.debug('rendering show qr screen')
|
||||
return h('div', {
|
||||
style: {
|
||||
position: 'absolute',
|
||||
height: '100%',
|
||||
top: '0px',
|
||||
left: '0px',
|
||||
},
|
||||
}, [
|
||||
h('i.fa.fa-arrow-left.fa-lg.cursor-pointer.color-orange', {
|
||||
onClick: () => props.dispatch(actions.backToAccountDetail(props.activeAddress)),
|
||||
style: {
|
||||
marginLeft: '10px',
|
||||
marginTop: '50px',
|
||||
},
|
||||
}),
|
||||
h('div', {
|
||||
style: {
|
||||
position: 'absolute',
|
||||
left: '44px',
|
||||
width: '285px',
|
||||
},
|
||||
}, [
|
||||
h(QrView, {key: 'qr'}),
|
||||
]),
|
||||
])
|
||||
|
||||
default:
|
||||
log.debug('rendering default, account detail screen')
|
||||
return h(AccountDetailScreen, {key: 'account-detail'})
|
||||
return h(MainContainer, {key: 'account-detail'})
|
||||
}
|
||||
}
|
||||
|
||||
@ -556,40 +418,6 @@ App.prototype.toggleMetamaskActive = function () {
|
||||
}
|
||||
}
|
||||
|
||||
App.prototype.renderCustomOption = function (provider) {
|
||||
const { rpcTarget, type } = provider
|
||||
const props = this.props
|
||||
|
||||
if (type !== 'rpc') return null
|
||||
|
||||
// Concatenate long URLs
|
||||
let label = rpcTarget
|
||||
if (rpcTarget.length > 31) {
|
||||
label = label.substr(0, 34) + '...'
|
||||
}
|
||||
|
||||
switch (rpcTarget) {
|
||||
|
||||
case 'http://localhost:8545':
|
||||
return null
|
||||
|
||||
default:
|
||||
return h(
|
||||
DropdownMenuItem,
|
||||
{
|
||||
key: rpcTarget,
|
||||
onClick: () => props.dispatch(actions.setRpcTarget(rpcTarget)),
|
||||
closeMenu: () => this.setState({ isNetworkMenuOpen: false }),
|
||||
},
|
||||
[
|
||||
h('i.fa.fa-question-circle.fa-lg.menu-icon'),
|
||||
label,
|
||||
h('.check', '✓'),
|
||||
]
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
App.prototype.getNetworkName = function () {
|
||||
const { provider } = this.props
|
||||
const providerName = provider.type
|
||||
@ -610,28 +438,3 @@ App.prototype.getNetworkName = function () {
|
||||
|
||||
return name
|
||||
}
|
||||
|
||||
App.prototype.renderCommonRpc = function (rpcList, provider) {
|
||||
const props = this.props
|
||||
const rpcTarget = provider.rpcTarget
|
||||
|
||||
return rpcList.map((rpc) => {
|
||||
if ((rpc === 'http://localhost:8545') || (rpc === rpcTarget)) {
|
||||
return null
|
||||
} else {
|
||||
return h(
|
||||
DropdownMenuItem,
|
||||
{
|
||||
key: `common${rpc}`,
|
||||
closeMenu: () => this.setState({ isNetworkMenuOpen: false }),
|
||||
onClick: () => props.dispatch(actions.setRpcTarget(rpc)),
|
||||
},
|
||||
[
|
||||
h('i.fa.fa-question-circle.fa-lg.menu-icon'),
|
||||
rpc,
|
||||
rpcTarget === rpc ? h('.check', '✓') : null,
|
||||
]
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
154
ui/app/components/account-menu/index.js
Normal file
@ -0,0 +1,154 @@
|
||||
const inherits = require('util').inherits
|
||||
const Component = require('react').Component
|
||||
const connect = require('react-redux').connect
|
||||
const h = require('react-hyperscript')
|
||||
const actions = require('../../actions')
|
||||
const { Menu, Item, Divider, CloseArea } = require('../dropdowns/components/menu')
|
||||
const Identicon = require('../identicon')
|
||||
const { formatBalance } = require('../../util')
|
||||
|
||||
module.exports = connect(mapStateToProps, mapDispatchToProps)(AccountMenu)
|
||||
|
||||
inherits(AccountMenu, Component)
|
||||
function AccountMenu () { Component.call(this) }
|
||||
|
||||
function mapStateToProps (state) {
|
||||
return {
|
||||
selectedAddress: state.metamask.selectedAddress,
|
||||
isAccountMenuOpen: state.metamask.isAccountMenuOpen,
|
||||
keyrings: state.metamask.keyrings,
|
||||
identities: state.metamask.identities,
|
||||
accounts: state.metamask.accounts,
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
function mapDispatchToProps (dispatch) {
|
||||
return {
|
||||
toggleAccountMenu: () => dispatch(actions.toggleAccountMenu()),
|
||||
showAccountDetail: address => {
|
||||
dispatch(actions.showAccountDetail(address))
|
||||
dispatch(actions.toggleAccountMenu())
|
||||
},
|
||||
lockMetamask: () => {
|
||||
dispatch(actions.lockMetamask())
|
||||
dispatch(actions.toggleAccountMenu())
|
||||
},
|
||||
showConfigPage: () => {
|
||||
console.log('hihihih')
|
||||
dispatch(actions.showConfigPage())
|
||||
dispatch(actions.toggleAccountMenu())
|
||||
},
|
||||
showNewAccountModal: () => {
|
||||
dispatch(actions.showModal({ name: 'NEW_ACCOUNT' }))
|
||||
dispatch(actions.toggleAccountMenu())
|
||||
},
|
||||
showImportPage: () => {
|
||||
dispatch(actions.showImportPage())
|
||||
dispatch(actions.toggleAccountMenu())
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
AccountMenu.prototype.render = function () {
|
||||
const {
|
||||
isAccountMenuOpen,
|
||||
toggleAccountMenu,
|
||||
showNewAccountModal,
|
||||
showImportPage,
|
||||
lockMetamask,
|
||||
showConfigPage,
|
||||
} = this.props
|
||||
|
||||
console.log(showConfigPage)
|
||||
return h(Menu, { className: 'account-menu', isShowing: isAccountMenuOpen }, [
|
||||
h(CloseArea, { onClick: toggleAccountMenu }),
|
||||
h(Item, {
|
||||
className: 'account-menu__header',
|
||||
onClick: lockMetamask,
|
||||
}, [
|
||||
'My Accounts',
|
||||
h('button.account-menu__logout-button', 'Log out'),
|
||||
]),
|
||||
h(Divider),
|
||||
h('div.account-menu__accounts', this.renderAccounts()),
|
||||
h(Divider),
|
||||
h(Item, {
|
||||
onClick: showNewAccountModal,
|
||||
icon: h('img', { src: 'images/plus-btn-white.svg' }),
|
||||
text: 'Create Account',
|
||||
}),
|
||||
h(Item, {
|
||||
onClick: showImportPage,
|
||||
icon: h('img', { src: 'images/import-account.svg' }),
|
||||
text: 'Import Account',
|
||||
}),
|
||||
h(Divider),
|
||||
h(Item, {
|
||||
icon: h('img', { src: 'images/mm-info-icon.svg' }),
|
||||
text: 'Info & Help',
|
||||
}),
|
||||
h(Item, {
|
||||
onClick: showConfigPage,
|
||||
icon: h('img', { src: 'images/settings.svg' }),
|
||||
text: 'Settings',
|
||||
}),
|
||||
])
|
||||
}
|
||||
|
||||
AccountMenu.prototype.renderAccounts = function () {
|
||||
const {
|
||||
identities,
|
||||
accounts,
|
||||
selected,
|
||||
keyrings,
|
||||
showAccountDetail,
|
||||
} = this.props
|
||||
|
||||
return Object.keys(identities).map((key, index) => {
|
||||
const identity = identities[key]
|
||||
const isSelected = identity.address === selected
|
||||
|
||||
const balanceValue = accounts[key].balance
|
||||
const formattedBalance = balanceValue ? formatBalance(balanceValue, 6) : '...'
|
||||
const simpleAddress = identity.address.substring(2).toLowerCase()
|
||||
|
||||
const keyring = keyrings.find((kr) => {
|
||||
return kr.accounts.includes(simpleAddress) ||
|
||||
kr.accounts.includes(identity.address)
|
||||
})
|
||||
|
||||
return h(
|
||||
'div.account-menu__account.menu__item--clickable',
|
||||
{ onClick: () => showAccountDetail(identity.address) },
|
||||
[
|
||||
h('div.account-menu__check-mark', [
|
||||
isSelected ? h('i.fa.fa-check') : null,
|
||||
]),
|
||||
|
||||
h(
|
||||
Identicon,
|
||||
{
|
||||
address: identity.address,
|
||||
diameter: 24,
|
||||
},
|
||||
),
|
||||
|
||||
h('div.account-menu__account-info', [
|
||||
h('div.account-menu__name', identity.name || ''),
|
||||
h('div.account-menu__balance', formattedBalance),
|
||||
]),
|
||||
|
||||
this.indicateIfLoose(keyring),
|
||||
],
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
AccountMenu.prototype.indicateIfLoose = function (keyring) {
|
||||
try { // Sometimes keyrings aren't loaded yet:
|
||||
const type = keyring.type
|
||||
const isLoose = type !== 'HD Key Tree'
|
||||
return isLoose ? h('.keyring-label', 'LOOSE') : null
|
||||
} catch (e) { return }
|
||||
}
|
120
ui/app/components/balance-component.js
Normal file
@ -0,0 +1,120 @@
|
||||
const Component = require('react').Component
|
||||
const connect = require('react-redux').connect
|
||||
const h = require('react-hyperscript')
|
||||
const inherits = require('util').inherits
|
||||
const TokenBalance = require('./token-balance')
|
||||
const Identicon = require('./identicon')
|
||||
|
||||
const { formatBalance, generateBalanceObject } = require('../util')
|
||||
|
||||
module.exports = connect(mapStateToProps)(BalanceComponent)
|
||||
|
||||
function mapStateToProps (state) {
|
||||
const accounts = state.metamask.accounts
|
||||
const network = state.metamask.network
|
||||
const selectedAddress = state.metamask.selectedAddress || Object.keys(accounts)[0]
|
||||
const account = accounts[selectedAddress]
|
||||
|
||||
return {
|
||||
account,
|
||||
network,
|
||||
conversionRate: state.metamask.conversionRate,
|
||||
currentCurrency: state.metamask.currentCurrency,
|
||||
}
|
||||
}
|
||||
|
||||
inherits(BalanceComponent, Component)
|
||||
function BalanceComponent () {
|
||||
Component.call(this)
|
||||
}
|
||||
|
||||
BalanceComponent.prototype.render = function () {
|
||||
const props = this.props
|
||||
const { token, network } = props
|
||||
|
||||
return h('div.balance-container', {}, [
|
||||
|
||||
// TODO: balance icon needs to be passed in
|
||||
// h('img.balance-icon', {
|
||||
// src: '../images/eth_logo.svg',
|
||||
// style: {},
|
||||
// }),
|
||||
h(Identicon, {
|
||||
diameter: 45,
|
||||
address: token && token.address,
|
||||
network,
|
||||
}),
|
||||
|
||||
token ? this.renderTokenBalance() : this.renderBalance(),
|
||||
])
|
||||
}
|
||||
|
||||
BalanceComponent.prototype.renderTokenBalance = function () {
|
||||
const { token } = this.props
|
||||
|
||||
return h('div.flex-column.balance-display', [
|
||||
h('div.token-amount', [ h(TokenBalance, { token }) ]),
|
||||
])
|
||||
}
|
||||
|
||||
BalanceComponent.prototype.renderBalance = function () {
|
||||
const props = this.props
|
||||
const { shorten, account } = props
|
||||
const balanceValue = account && account.balance
|
||||
const needsParse = 'needsParse' in props ? props.needsParse : true
|
||||
const formattedBalance = balanceValue ? formatBalance(balanceValue, 6, needsParse) : '...'
|
||||
const showFiat = 'showFiat' in props ? props.showFiat : true
|
||||
|
||||
if (formattedBalance === 'None' || formattedBalance === '...') {
|
||||
return h('div.flex-column.balance-display', {}, [
|
||||
h('div.token-amount', {
|
||||
style: {},
|
||||
}, formattedBalance),
|
||||
])
|
||||
}
|
||||
|
||||
return h('div.flex-column.balance-display', {}, [
|
||||
h('div.token-amount', {
|
||||
style: {},
|
||||
}, this.getTokenBalance(formattedBalance, shorten)),
|
||||
|
||||
showFiat ? this.renderFiatValue(formattedBalance) : null,
|
||||
])
|
||||
}
|
||||
|
||||
BalanceComponent.prototype.renderFiatValue = function (formattedBalance) {
|
||||
|
||||
const { conversionRate, currentCurrency } = this.props
|
||||
|
||||
const fiatDisplayNumber = this.getFiatDisplayNumber(formattedBalance, conversionRate)
|
||||
|
||||
const fiatPrefix = currentCurrency === 'USD' ? '$' : ''
|
||||
|
||||
return this.renderFiatAmount(fiatDisplayNumber, currentCurrency, fiatPrefix)
|
||||
}
|
||||
|
||||
BalanceComponent.prototype.renderFiatAmount = function (fiatDisplayNumber, fiatSuffix, fiatPrefix) {
|
||||
if (fiatDisplayNumber === 'N/A') return null
|
||||
|
||||
return h('div.fiat-amount', {
|
||||
style: {},
|
||||
}, `${fiatPrefix}${fiatDisplayNumber} ${fiatSuffix}`)
|
||||
}
|
||||
|
||||
BalanceComponent.prototype.getTokenBalance = function (formattedBalance, shorten) {
|
||||
const balanceObj = generateBalanceObject(formattedBalance, shorten ? 1 : 3)
|
||||
|
||||
const balanceValue = shorten ? balanceObj.shortBalance : balanceObj.balance
|
||||
const label = balanceObj.label
|
||||
|
||||
return `${balanceValue} ${label}`
|
||||
}
|
||||
|
||||
BalanceComponent.prototype.getFiatDisplayNumber = function (formattedBalance, conversionRate) {
|
||||
if (formattedBalance === 'None') return formattedBalance
|
||||
if (conversionRate === 0) return 'N/A'
|
||||
|
||||
const splitBalance = formattedBalance.split(' ')
|
||||
|
||||
return (Number(splitBalance[0]) * conversionRate).toFixed(2)
|
||||
}
|
@ -245,7 +245,7 @@ BuyButtonSubview.prototype.navigateTo = function (url) {
|
||||
|
||||
BuyButtonSubview.prototype.backButtonContext = function () {
|
||||
if (this.props.context === 'confTx') {
|
||||
this.props.dispatch(actions.showConfTxPage(false))
|
||||
this.props.dispatch(actions.showConfTxPage({transForward: false}))
|
||||
} else {
|
||||
this.props.dispatch(actions.goHome())
|
||||
}
|
||||
|
55
ui/app/components/customize-gas-modal/gas-modal-card.js
Normal file
@ -0,0 +1,55 @@
|
||||
const Component = require('react').Component
|
||||
const h = require('react-hyperscript')
|
||||
const inherits = require('util').inherits
|
||||
const InputNumber = require('../input-number.js')
|
||||
const GasSlider = require('./gas-slider.js')
|
||||
|
||||
module.exports = GasModalCard
|
||||
|
||||
inherits(GasModalCard, Component)
|
||||
function GasModalCard () {
|
||||
Component.call(this)
|
||||
}
|
||||
|
||||
GasModalCard.prototype.render = function () {
|
||||
const {
|
||||
memo,
|
||||
identities,
|
||||
onChange,
|
||||
unitLabel,
|
||||
value,
|
||||
min,
|
||||
max,
|
||||
step,
|
||||
title,
|
||||
copy
|
||||
} = this.props
|
||||
|
||||
return h('div.send-v2__gas-modal-card', [
|
||||
|
||||
h('div.send-v2__gas-modal-card__title', {}, title),
|
||||
|
||||
h('div.send-v2__gas-modal-card__copy', {}, copy),
|
||||
|
||||
h(InputNumber, {
|
||||
unitLabel,
|
||||
step,
|
||||
max,
|
||||
min,
|
||||
placeholder: '0',
|
||||
value,
|
||||
onChange,
|
||||
}),
|
||||
|
||||
h(GasSlider, {
|
||||
value,
|
||||
step,
|
||||
max,
|
||||
min,
|
||||
onChange,
|
||||
}),
|
||||
|
||||
])
|
||||
|
||||
}
|
||||
|
50
ui/app/components/customize-gas-modal/gas-slider.js
Normal file
@ -0,0 +1,50 @@
|
||||
const Component = require('react').Component
|
||||
const h = require('react-hyperscript')
|
||||
const inherits = require('util').inherits
|
||||
|
||||
module.exports = GasSlider
|
||||
|
||||
inherits(GasSlider, Component)
|
||||
function GasSlider () {
|
||||
Component.call(this)
|
||||
}
|
||||
|
||||
GasSlider.prototype.render = function () {
|
||||
const {
|
||||
memo,
|
||||
identities,
|
||||
onChange,
|
||||
unitLabel,
|
||||
value,
|
||||
id,
|
||||
step,
|
||||
max,
|
||||
min,
|
||||
} = this.props
|
||||
|
||||
return h('div.gas-slider', [
|
||||
|
||||
h('input.gas-slider__input', {
|
||||
type: 'range',
|
||||
step,
|
||||
max,
|
||||
min,
|
||||
value,
|
||||
id: 'gasSlider',
|
||||
onChange: event => onChange(event.target.value),
|
||||
}, []),
|
||||
|
||||
h('div.gas-slider__bar', [
|
||||
|
||||
h('div.gas-slider__low'),
|
||||
|
||||
h('div.gas-slider__mid'),
|
||||
|
||||
h('div.gas-slider__high'),
|
||||
|
||||
]),
|
||||
|
||||
])
|
||||
|
||||
}
|
||||
|
158
ui/app/components/customize-gas-modal/index.js
Normal file
@ -0,0 +1,158 @@
|
||||
const Component = require('react').Component
|
||||
const h = require('react-hyperscript')
|
||||
const inherits = require('util').inherits
|
||||
const connect = require('react-redux').connect
|
||||
const actions = require('../../actions')
|
||||
const GasModalCard = require('./gas-modal-card')
|
||||
|
||||
const { conversionUtil, multiplyCurrencies } = require('../../conversion-util')
|
||||
|
||||
const {
|
||||
getGasPrice,
|
||||
getGasLimit,
|
||||
conversionRateSelector,
|
||||
} = require('../../selectors')
|
||||
|
||||
function mapStateToProps (state) {
|
||||
return {
|
||||
gasPrice: getGasPrice(state),
|
||||
gasLimit: getGasLimit(state),
|
||||
conversionRate: conversionRateSelector(state),
|
||||
}
|
||||
}
|
||||
|
||||
function mapDispatchToProps (dispatch) {
|
||||
return {
|
||||
hideModal: () => dispatch(actions.hideModal()),
|
||||
updateGasPrice: newGasPrice => dispatch(actions.updateGasPrice(newGasPrice)),
|
||||
updateGasLimit: newGasLimit => dispatch(actions.updateGasLimit(newGasLimit)),
|
||||
updateGasTotal: newGasTotal => dispatch(actions.updateGasTotal(newGasTotal)),
|
||||
}
|
||||
}
|
||||
|
||||
inherits(CustomizeGasModal, Component)
|
||||
function CustomizeGasModal (props) {
|
||||
Component.call(this)
|
||||
|
||||
this.state = {
|
||||
gasPrice: props.gasPrice,
|
||||
gasLimit: props.gasLimit,
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = connect(mapStateToProps, mapDispatchToProps)(CustomizeGasModal)
|
||||
|
||||
CustomizeGasModal.prototype.save = function (gasPrice, gasLimit) {
|
||||
const {
|
||||
updateGasPrice,
|
||||
updateGasLimit,
|
||||
hideModal,
|
||||
updateGasTotal
|
||||
} = this.props
|
||||
|
||||
const newGasTotal = multiplyCurrencies(gasLimit, gasPrice, {
|
||||
toNumericBase: 'hex',
|
||||
multiplicandBase: 16,
|
||||
multiplierBase: 16,
|
||||
})
|
||||
|
||||
updateGasPrice(gasPrice)
|
||||
updateGasLimit(gasLimit)
|
||||
updateGasTotal(newGasTotal)
|
||||
hideModal()
|
||||
}
|
||||
|
||||
CustomizeGasModal.prototype.convertAndSetGasLimit = function (newGasLimit) {
|
||||
const convertedGasLimit = conversionUtil(newGasLimit, {
|
||||
fromNumericBase: 'dec',
|
||||
toNumericBase: 'hex',
|
||||
})
|
||||
|
||||
this.setState({ gasLimit: convertedGasLimit })
|
||||
}
|
||||
|
||||
CustomizeGasModal.prototype.convertAndSetGasPrice = function (newGasPrice) {
|
||||
const convertedGasPrice = conversionUtil(newGasPrice, {
|
||||
fromNumericBase: 'dec',
|
||||
toNumericBase: 'hex',
|
||||
fromDenomination: 'GWEI',
|
||||
toDenomination: 'WEI',
|
||||
})
|
||||
|
||||
this.setState({ gasPrice: convertedGasPrice })
|
||||
}
|
||||
|
||||
CustomizeGasModal.prototype.render = function () {
|
||||
const { hideModal, conversionRate } = this.props
|
||||
const { gasPrice, gasLimit } = this.state
|
||||
|
||||
const convertedGasPrice = conversionUtil(gasPrice, {
|
||||
fromNumericBase: 'hex',
|
||||
toNumericBase: 'dec',
|
||||
fromDenomination: 'WEI',
|
||||
toDenomination: 'GWEI',
|
||||
})
|
||||
|
||||
const convertedGasLimit = conversionUtil(gasLimit, {
|
||||
fromNumericBase: 'hex',
|
||||
toNumericBase: 'dec',
|
||||
})
|
||||
|
||||
return h('div.send-v2__customize-gas', {}, [
|
||||
h('div', {
|
||||
}, [
|
||||
h('div.send-v2__customize-gas__header', {}, [
|
||||
|
||||
h('div.send-v2__customize-gas__title', 'Customize Gas'),
|
||||
|
||||
h('div.send-v2__customize-gas__close', {
|
||||
onClick: hideModal,
|
||||
}),
|
||||
|
||||
]),
|
||||
|
||||
h('div.send-v2__customize-gas__body', {}, [
|
||||
|
||||
h(GasModalCard, {
|
||||
value: convertedGasPrice,
|
||||
min: 0,
|
||||
max: 1000,
|
||||
step: 1,
|
||||
onChange: value => this.convertAndSetGasPrice(value),
|
||||
title: 'Gas Price',
|
||||
copy: 'We calculate the suggested gas prices based on network success rates.',
|
||||
}),
|
||||
|
||||
h(GasModalCard, {
|
||||
value: convertedGasLimit,
|
||||
min: 20000,
|
||||
max: 100000,
|
||||
step: 1,
|
||||
onChange: value => this.convertAndSetGasLimit(value),
|
||||
title: 'Gas Limit',
|
||||
copy: 'We calculate the suggested gas limit based on network success rates.',
|
||||
}),
|
||||
|
||||
]),
|
||||
|
||||
h('div.send-v2__customize-gas__footer', {}, [
|
||||
|
||||
h('div.send-v2__customize-gas__revert', {
|
||||
onClick: () => console.log('Revert'),
|
||||
}, ['Revert']),
|
||||
|
||||
h('div.send-v2__customize-gas__buttons', [
|
||||
h('div.send-v2__customize-gas__cancel', {
|
||||
onClick: this.props.hideModal,
|
||||
}, ['CANCEL']),
|
||||
|
||||
h('div.send-v2__customize-gas__save', {
|
||||
onClick: () => this.save(gasPrice, gasLimit),
|
||||
}, ['SAVE']),
|
||||
])
|
||||
|
||||
]),
|
||||
|
||||
]),
|
||||
])
|
||||
}
|
29
ui/app/components/dropdowns/account-options-dropdown.js
Normal file
@ -0,0 +1,29 @@
|
||||
const Component = require('react').Component
|
||||
const h = require('react-hyperscript')
|
||||
const inherits = require('util').inherits
|
||||
const AccountDropdowns = require('./components/account-dropdowns')
|
||||
|
||||
inherits(AccountOptionsDropdown, Component)
|
||||
function AccountOptionsDropdown () {
|
||||
Component.call(this)
|
||||
}
|
||||
|
||||
module.exports = AccountOptionsDropdown
|
||||
|
||||
// TODO: specify default props and proptypes
|
||||
// TODO: hook up to state, connect to redux to clean up API
|
||||
// TODO: selectedAddress is not defined... should we use selected?
|
||||
AccountOptionsDropdown.prototype.render = function () {
|
||||
const { selected, network, identities, style, dropdownWrapperStyle, menuItemStyles } = this.props
|
||||
|
||||
return h(AccountDropdowns, {
|
||||
enableAccountOptions: true,
|
||||
enableAccountsSelector: false,
|
||||
selected: selectedAddress,
|
||||
network,
|
||||
identities,
|
||||
style: style || {},
|
||||
dropdownWrapperStyle: dropdownWrapperStyle || {},
|
||||
menuItemStyles: menuItemStyles || {},
|
||||
}, [])
|
||||
}
|
29
ui/app/components/dropdowns/account-selection-dropdown.js
Normal file
@ -0,0 +1,29 @@
|
||||
const Component = require('react').Component
|
||||
const h = require('react-hyperscript')
|
||||
const inherits = require('util').inherits
|
||||
const AccountDropdowns = require('./components/account-dropdowns')
|
||||
|
||||
inherits(AccountSelectionDropdown, Component)
|
||||
function AccountSelectionDropdown () {
|
||||
Component.call(this)
|
||||
}
|
||||
|
||||
module.exports = AccountSelectionDropdown
|
||||
|
||||
// TODO: specify default props and proptypes
|
||||
// TODO: hook up to state, connect to redux to clean up API
|
||||
// TODO: selectedAddress is not defined... should we use selected?
|
||||
AccountSelectionDropdown.prototype.render = function () {
|
||||
const { selected, network, identities, style, dropdownWrapperStyle, menuItemStyles } = this.props
|
||||
|
||||
return h(AccountDropdowns, {
|
||||
enableAccountOptions: false,
|
||||
enableAccountsSelector: true,
|
||||
selected: selectedAddress,
|
||||
network,
|
||||
identities,
|
||||
style: style || {},
|
||||
dropdownWrapperStyle: dropdownWrapperStyle || {},
|
||||
menuItemStyles: menuItemStyles || {},
|
||||
}, [])
|
||||
}
|
469
ui/app/components/dropdowns/components/account-dropdowns.js
Normal file
@ -0,0 +1,469 @@
|
||||
const Component = require('react').Component
|
||||
const PropTypes = require('react').PropTypes
|
||||
const h = require('react-hyperscript')
|
||||
const actions = require('../../../actions')
|
||||
const genAccountLink = require('../../../../lib/account-link.js')
|
||||
const connect = require('react-redux').connect
|
||||
const Dropdown = require('./dropdown').Dropdown
|
||||
const DropdownMenuItem = require('./dropdown').DropdownMenuItem
|
||||
const Identicon = require('../../identicon')
|
||||
const ethUtil = require('ethereumjs-util')
|
||||
const copyToClipboard = require('copy-to-clipboard')
|
||||
const { formatBalance } = require('../../../util')
|
||||
|
||||
class AccountDropdowns extends Component {
|
||||
constructor (props) {
|
||||
super(props)
|
||||
this.state = {
|
||||
accountSelectorActive: false,
|
||||
optionsMenuActive: false,
|
||||
}
|
||||
// Used for orangeaccount selector icon
|
||||
// this.accountSelectorToggleClassName = 'accounts-selector'
|
||||
this.accountSelectorToggleClassName = 'fa-angle-down'
|
||||
this.optionsMenuToggleClassName = 'fa-ellipsis-h'
|
||||
}
|
||||
|
||||
renderAccounts () {
|
||||
const { identities, accounts, selected, menuItemStyles, actions, keyrings } = this.props
|
||||
|
||||
return Object.keys(identities).map((key, index) => {
|
||||
const identity = identities[key]
|
||||
const isSelected = identity.address === selected
|
||||
|
||||
const balanceValue = accounts[key].balance
|
||||
const formattedBalance = balanceValue ? formatBalance(balanceValue, 6) : '...'
|
||||
const simpleAddress = identity.address.substring(2).toLowerCase()
|
||||
|
||||
const keyring = keyrings.find((kr) => {
|
||||
return kr.accounts.includes(simpleAddress) ||
|
||||
kr.accounts.includes(identity.address)
|
||||
})
|
||||
|
||||
return h(
|
||||
DropdownMenuItem,
|
||||
{
|
||||
closeMenu: () => {},
|
||||
onClick: () => {
|
||||
this.props.actions.showAccountDetail(identity.address)
|
||||
},
|
||||
style: Object.assign(
|
||||
{
|
||||
marginTop: index === 0 ? '5px' : '',
|
||||
fontSize: '24px',
|
||||
width: '260px',
|
||||
},
|
||||
menuItemStyles,
|
||||
),
|
||||
},
|
||||
[
|
||||
h('div.flex-row.flex-center', {}, [
|
||||
|
||||
h('span', {
|
||||
style: {
|
||||
flex: '1 1 0',
|
||||
minWidth: '20px',
|
||||
minHeight: '30px',
|
||||
},
|
||||
}, [
|
||||
h('span', {
|
||||
style: {
|
||||
flex: '1 1 auto',
|
||||
fontSize: '14px',
|
||||
},
|
||||
}, isSelected ? h('i.fa.fa-check') : null),
|
||||
]),
|
||||
|
||||
h(
|
||||
Identicon,
|
||||
{
|
||||
address: identity.address,
|
||||
diameter: 24,
|
||||
style: {
|
||||
flex: '1 1 auto',
|
||||
marginLeft: '10px',
|
||||
},
|
||||
},
|
||||
),
|
||||
|
||||
h('span.flex-column', {
|
||||
style: {
|
||||
flex: '10 10 auto',
|
||||
width: '175px',
|
||||
alignItems: 'flex-start',
|
||||
justifyContent: 'center',
|
||||
marginLeft: '10px',
|
||||
position: 'relative',
|
||||
},
|
||||
}, [
|
||||
this.indicateIfLoose(keyring),
|
||||
h('span.account-dropdown-name', {
|
||||
style: {
|
||||
fontSize: '18px',
|
||||
maxWidth: '145px',
|
||||
whiteSpace: 'nowrap',
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
},
|
||||
}, identity.name || ''),
|
||||
|
||||
h('span.account-dropdown-balance', {
|
||||
style: {
|
||||
fontSize: '14px',
|
||||
fontFamily: 'Avenir',
|
||||
fontWeight: 500,
|
||||
},
|
||||
}, formattedBalance),
|
||||
]),
|
||||
|
||||
h('span', {
|
||||
style: {
|
||||
flex: '3 3 auto',
|
||||
},
|
||||
}, [
|
||||
h('span.account-dropdown-edit-button', {
|
||||
style: {
|
||||
fontSize: '16px',
|
||||
},
|
||||
onClick: () => {
|
||||
actions.showEditAccountModal(identity)
|
||||
},
|
||||
}, [
|
||||
'Edit',
|
||||
]),
|
||||
]),
|
||||
|
||||
]),
|
||||
// =======
|
||||
// },
|
||||
// ),
|
||||
// this.indicateIfLoose(keyring),
|
||||
// h('span', {
|
||||
// style: {
|
||||
// marginLeft: '20px',
|
||||
// fontSize: '24px',
|
||||
// maxWidth: '145px',
|
||||
// whiteSpace: 'nowrap',
|
||||
// overflow: 'hidden',
|
||||
// textOverflow: 'ellipsis',
|
||||
// },
|
||||
// }, identity.name || ''),
|
||||
// h('span', { style: { marginLeft: '20px', fontSize: '24px' } }, isSelected ? h('.check', '✓') : null),
|
||||
// >>>>>>> master:ui/app/components/account-dropdowns.js
|
||||
]
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
indicateIfLoose (keyring) {
|
||||
try { // Sometimes keyrings aren't loaded yet:
|
||||
const type = keyring.type
|
||||
const isLoose = type !== 'HD Key Tree'
|
||||
return isLoose ? h('.keyring-label', 'LOOSE') : null
|
||||
} catch (e) { return }
|
||||
}
|
||||
|
||||
renderAccountSelector () {
|
||||
const { actions, useCssTransition, innerStyle, sidebarOpen } = this.props
|
||||
const { accountSelectorActive, menuItemStyles } = this.state
|
||||
|
||||
return h(
|
||||
Dropdown,
|
||||
{
|
||||
useCssTransition,
|
||||
style: {
|
||||
marginLeft: '-185px',
|
||||
marginTop: '50px',
|
||||
minWidth: '180px',
|
||||
overflowY: 'auto',
|
||||
maxHeight: '300px',
|
||||
width: '300px',
|
||||
},
|
||||
innerStyle,
|
||||
isOpen: accountSelectorActive,
|
||||
onClickOutside: (event) => {
|
||||
const { classList } = event.target
|
||||
const isNotToggleElement = !classList.contains(this.accountSelectorToggleClassName)
|
||||
if (accountSelectorActive && isNotToggleElement) {
|
||||
this.setState({ accountSelectorActive: false })
|
||||
}
|
||||
},
|
||||
},
|
||||
[
|
||||
...this.renderAccounts(),
|
||||
h(
|
||||
DropdownMenuItem,
|
||||
{
|
||||
closeMenu: () => {},
|
||||
style: Object.assign(
|
||||
{},
|
||||
menuItemStyles,
|
||||
),
|
||||
onClick: () => actions.showNewAccountModal(),
|
||||
},
|
||||
[
|
||||
h(
|
||||
'i.fa.fa-plus.fa-lg',
|
||||
{
|
||||
style: {
|
||||
marginLeft: '8px',
|
||||
},
|
||||
}
|
||||
),
|
||||
h('span', {
|
||||
style: {
|
||||
marginLeft: '14px',
|
||||
fontFamily: 'DIN OT',
|
||||
fontSize: '16px',
|
||||
lineHeight: '23px',
|
||||
},
|
||||
}, 'Create Account'),
|
||||
],
|
||||
),
|
||||
h(
|
||||
DropdownMenuItem,
|
||||
{
|
||||
closeMenu: () => {
|
||||
if (sidebarOpen) {
|
||||
actions.hideSidebar()
|
||||
}
|
||||
},
|
||||
onClick: () => actions.showImportPage(),
|
||||
style: Object.assign(
|
||||
{},
|
||||
menuItemStyles,
|
||||
),
|
||||
},
|
||||
[
|
||||
h(
|
||||
'i.fa.fa-download.fa-lg',
|
||||
{
|
||||
style: {
|
||||
marginLeft: '8px',
|
||||
},
|
||||
}
|
||||
),
|
||||
h('span', {
|
||||
style: {
|
||||
marginLeft: '20px',
|
||||
marginBottom: '5px',
|
||||
fontFamily: 'DIN OT',
|
||||
fontSize: '16px',
|
||||
lineHeight: '23px',
|
||||
},
|
||||
}, 'Import Account'),
|
||||
]
|
||||
),
|
||||
]
|
||||
)
|
||||
}
|
||||
|
||||
renderAccountOptions () {
|
||||
const { actions, dropdownWrapperStyle, useCssTransition } = this.props
|
||||
const { optionsMenuActive, menuItemStyles } = this.state
|
||||
const dropdownMenuItemStyle = {
|
||||
fontFamily: 'DIN OT',
|
||||
fontSize: 16,
|
||||
lineHeight: '24px',
|
||||
padding: '8px',
|
||||
}
|
||||
|
||||
return h(
|
||||
Dropdown,
|
||||
{
|
||||
useCssTransition,
|
||||
style: Object.assign(
|
||||
{
|
||||
marginLeft: '-10px',
|
||||
position: 'absolute',
|
||||
width: '29vh', // affects both mobile and laptop views
|
||||
},
|
||||
dropdownWrapperStyle,
|
||||
),
|
||||
isOpen: optionsMenuActive,
|
||||
onClickOutside: () => {
|
||||
const { classList } = event.target
|
||||
const isNotToggleElement = !classList.contains(this.optionsMenuToggleClassName)
|
||||
if (optionsMenuActive && isNotToggleElement) {
|
||||
this.setState({ optionsMenuActive: false })
|
||||
}
|
||||
},
|
||||
},
|
||||
[
|
||||
h(
|
||||
DropdownMenuItem,
|
||||
{
|
||||
closeMenu: () => {},
|
||||
onClick: () => {
|
||||
this.props.actions.showAccountDetailModal()
|
||||
},
|
||||
style: Object.assign(
|
||||
dropdownMenuItemStyle,
|
||||
menuItemStyles,
|
||||
),
|
||||
},
|
||||
'Account Details',
|
||||
),
|
||||
h(
|
||||
DropdownMenuItem,
|
||||
{
|
||||
closeMenu: () => {},
|
||||
onClick: () => {
|
||||
const { selected, network } = this.props
|
||||
const url = genAccountLink(selected, network)
|
||||
global.platform.openWindow({ url })
|
||||
},
|
||||
style: Object.assign(
|
||||
dropdownMenuItemStyle,
|
||||
menuItemStyles,
|
||||
),
|
||||
},
|
||||
'View account on Etherscan',
|
||||
),
|
||||
h(
|
||||
DropdownMenuItem,
|
||||
{
|
||||
closeMenu: () => {},
|
||||
onClick: () => {
|
||||
const { selected } = this.props
|
||||
const checkSumAddress = selected && ethUtil.toChecksumAddress(selected)
|
||||
copyToClipboard(checkSumAddress)
|
||||
},
|
||||
style: Object.assign(
|
||||
dropdownMenuItemStyle,
|
||||
menuItemStyles,
|
||||
),
|
||||
},
|
||||
'Copy Address to clipboard',
|
||||
),
|
||||
h(
|
||||
DropdownMenuItem,
|
||||
{
|
||||
closeMenu: () => {},
|
||||
onClick: () => this.props.actions.showExportPrivateKeyModal(),
|
||||
style: Object.assign(
|
||||
dropdownMenuItemStyle,
|
||||
menuItemStyles,
|
||||
),
|
||||
},
|
||||
'Export Private Key',
|
||||
),
|
||||
h(
|
||||
DropdownMenuItem,
|
||||
{
|
||||
closeMenu: () => {},
|
||||
onClick: () => {
|
||||
actions.hideSidebar()
|
||||
actions.showAddTokenPage()
|
||||
},
|
||||
style: Object.assign(
|
||||
dropdownMenuItemStyle,
|
||||
menuItemStyles,
|
||||
),
|
||||
},
|
||||
'Add Token',
|
||||
),
|
||||
|
||||
]
|
||||
)
|
||||
}
|
||||
|
||||
render () {
|
||||
const { style, enableAccountsSelector, enableAccountOptions } = this.props
|
||||
const { optionsMenuActive, accountSelectorActive } = this.state
|
||||
|
||||
return h(
|
||||
'span',
|
||||
{
|
||||
style: style,
|
||||
},
|
||||
[
|
||||
enableAccountsSelector && h(
|
||||
'i.fa.fa-angle-down',
|
||||
{
|
||||
style: {
|
||||
cursor: 'pointer',
|
||||
},
|
||||
onClick: (event) => {
|
||||
event.stopPropagation()
|
||||
this.setState({
|
||||
accountSelectorActive: !accountSelectorActive,
|
||||
optionsMenuActive: false,
|
||||
})
|
||||
},
|
||||
},
|
||||
this.renderAccountSelector(),
|
||||
),
|
||||
enableAccountOptions && h(
|
||||
'i.fa.fa-ellipsis-h',
|
||||
{
|
||||
style: {
|
||||
fontSize: '135%',
|
||||
cursor: 'pointer',
|
||||
},
|
||||
onClick: (event) => {
|
||||
event.stopPropagation()
|
||||
this.setState({
|
||||
accountSelectorActive: false,
|
||||
optionsMenuActive: !optionsMenuActive,
|
||||
})
|
||||
},
|
||||
},
|
||||
this.renderAccountOptions()
|
||||
),
|
||||
]
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
AccountDropdowns.defaultProps = {
|
||||
enableAccountsSelector: false,
|
||||
enableAccountOptions: false,
|
||||
}
|
||||
|
||||
AccountDropdowns.propTypes = {
|
||||
identities: PropTypes.objectOf(PropTypes.object),
|
||||
selected: PropTypes.string,
|
||||
keyrings: PropTypes.array,
|
||||
}
|
||||
|
||||
const mapDispatchToProps = (dispatch) => {
|
||||
return {
|
||||
actions: {
|
||||
hideSidebar: () => dispatch(actions.hideSidebar()),
|
||||
showConfigPage: () => dispatch(actions.showConfigPage()),
|
||||
showAccountDetail: (address) => dispatch(actions.showAccountDetail(address)),
|
||||
showAccountDetailModal: () => {
|
||||
dispatch(actions.showModal({ name: 'ACCOUNT_DETAILS' }))
|
||||
},
|
||||
showEditAccountModal: (identity) => {
|
||||
dispatch(actions.showModal({
|
||||
name: 'EDIT_ACCOUNT_NAME',
|
||||
identity,
|
||||
}))
|
||||
},
|
||||
showNewAccountModal: () => {
|
||||
dispatch(actions.showModal({ name: 'NEW_ACCOUNT' }))
|
||||
},
|
||||
showExportPrivateKeyModal: () => {
|
||||
dispatch(actions.showModal({ name: 'EXPORT_PRIVATE_KEY' }))
|
||||
},
|
||||
showAddTokenPage: () => {
|
||||
dispatch(actions.showAddTokenPage())
|
||||
},
|
||||
addNewAccount: () => dispatch(actions.addNewAccount()),
|
||||
showImportPage: () => dispatch(actions.showImportPage()),
|
||||
showQrView: (selected, identity) => dispatch(actions.showQrView(selected, identity)),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
function mapStateToProps (state) {
|
||||
return {
|
||||
keyrings: state.metamask.keyrings,
|
||||
sidebarOpen: state.appState.sidebarOpen,
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = connect(mapStateToProps, mapDispatchToProps)(AccountDropdowns)
|
||||
|
@ -1,14 +1,22 @@
|
||||
const Component = require('react').Component
|
||||
const PropTypes = require('react').PropTypes
|
||||
const h = require('react-hyperscript')
|
||||
const MenuDroppo = require('./menu-droppo')
|
||||
const MenuDroppo = require('../../menu-droppo')
|
||||
const extend = require('xtend')
|
||||
|
||||
const noop = () => {}
|
||||
|
||||
class Dropdown extends Component {
|
||||
render () {
|
||||
const { isOpen, onClickOutside, style, innerStyle, children, useCssTransition } = this.props
|
||||
const {
|
||||
containerClassName,
|
||||
isOpen,
|
||||
onClickOutside,
|
||||
style,
|
||||
innerStyle,
|
||||
children,
|
||||
useCssTransition,
|
||||
} = this.props
|
||||
|
||||
const innerStyleDefaults = extend({
|
||||
borderRadius: '4px',
|
||||
@ -20,9 +28,10 @@ class Dropdown extends Component {
|
||||
return h(
|
||||
MenuDroppo,
|
||||
{
|
||||
containerClassName,
|
||||
useCssTransition,
|
||||
isOpen,
|
||||
zIndex: 11,
|
||||
zIndex: 30,
|
||||
onClickOutside,
|
||||
style,
|
||||
innerStyle: innerStyleDefaults,
|
||||
@ -31,8 +40,12 @@ class Dropdown extends Component {
|
||||
h(
|
||||
'style',
|
||||
`
|
||||
li.dropdown-menu-item:hover { color:rgb(225, 225, 225); }
|
||||
li.dropdown-menu-item { color: rgb(185, 185, 185); position: relative }
|
||||
li.dropdown-menu-item:hover {
|
||||
color:rgb(225, 225, 225);
|
||||
background-color: rgba(255, 255, 255, 0.05);
|
||||
border-radius: 4px;
|
||||
}
|
||||
li.dropdown-menu-item { color: rgb(185, 185, 185); }
|
||||
`
|
||||
),
|
||||
...children,
|
||||
@ -67,7 +80,7 @@ class DropdownMenuItem extends Component {
|
||||
},
|
||||
style: Object.assign({
|
||||
listStyle: 'none',
|
||||
padding: '8px 0px 8px 0px',
|
||||
padding: '8px 0px',
|
||||
fontSize: '18px',
|
||||
fontStyle: 'normal',
|
||||
fontFamily: 'Montserrat Regular',
|
||||
@ -75,6 +88,7 @@ class DropdownMenuItem extends Component {
|
||||
display: 'flex',
|
||||
justifyContent: 'flex-start',
|
||||
alignItems: 'center',
|
||||
color: 'white',
|
||||
}, style),
|
||||
},
|
||||
children
|
51
ui/app/components/dropdowns/components/menu.js
Normal file
@ -0,0 +1,51 @@
|
||||
const inherits = require('util').inherits
|
||||
const Component = require('react').Component
|
||||
const h = require('react-hyperscript')
|
||||
|
||||
inherits(Menu, Component)
|
||||
function Menu () { Component.call(this) }
|
||||
|
||||
Menu.prototype.render = function () {
|
||||
const { className = '', children, isShowing } = this.props
|
||||
return isShowing
|
||||
? h('div', { className: `menu ${className}` }, children)
|
||||
: h('noscript')
|
||||
}
|
||||
|
||||
inherits(Item, Component)
|
||||
function Item () { Component.call(this) }
|
||||
|
||||
Item.prototype.render = function () {
|
||||
const {
|
||||
icon,
|
||||
children,
|
||||
text,
|
||||
className = '',
|
||||
onClick,
|
||||
} = this.props
|
||||
const itemClassName = `menu__item ${className} ${onClick ? 'menu__item--clickable' : ''}`
|
||||
const iconComponent = icon ? h('div.menu__item__icon', [icon]) : null
|
||||
const textComponent = text ? h('div.menu__item__text', text) : null
|
||||
|
||||
return children
|
||||
? h('div', { className: itemClassName, onClick }, children)
|
||||
: h('div.menu__item', { className: itemClassName, onClick }, [ iconComponent, textComponent ]
|
||||
.filter(d => Boolean(d))
|
||||
)
|
||||
}
|
||||
|
||||
inherits(Divider, Component)
|
||||
function Divider () { Component.call(this) }
|
||||
|
||||
Divider.prototype.render = function () {
|
||||
return h('div.menu__divider')
|
||||
}
|
||||
|
||||
inherits(CloseArea, Component)
|
||||
function CloseArea () { Component.call(this) }
|
||||
|
||||
CloseArea.prototype.render = function () {
|
||||
return h('div.menu__close-area', { onClick: this.props.onClick })
|
||||
}
|
||||
|
||||
module.exports = { Menu, Item, Divider, CloseArea }
|
@ -0,0 +1,28 @@
|
||||
const inherits = require('util').inherits
|
||||
const Component = require('react').Component
|
||||
const h = require('react-hyperscript')
|
||||
|
||||
|
||||
inherits(NetworkDropdownIcon, Component)
|
||||
module.exports = NetworkDropdownIcon
|
||||
|
||||
function NetworkDropdownIcon () {
|
||||
Component.call(this)
|
||||
}
|
||||
|
||||
NetworkDropdownIcon.prototype.render = function () {
|
||||
const {
|
||||
backgroundColor,
|
||||
isSelected,
|
||||
innerBorder = 'none',
|
||||
} = this.props
|
||||
|
||||
return h(`.menu-icon-circle${isSelected ? '--active' : ''}`, {},
|
||||
h('div', {
|
||||
style: {
|
||||
background: backgroundColor,
|
||||
border: innerBorder,
|
||||
},
|
||||
})
|
||||
)
|
||||
}
|
17
ui/app/components/dropdowns/index.js
Normal file
@ -0,0 +1,17 @@
|
||||
// Reusable Dropdown Components
|
||||
// TODO: Refactor into separate components
|
||||
const Dropdown = require('./components/dropdown').Dropdown
|
||||
const AccountDropdowns = require('./components/account-dropdowns')
|
||||
|
||||
// App-Specific Instances
|
||||
const AccountSelectionDropdown = require('./account-selection-dropdown')
|
||||
const AccountOptionsDropdown = require('./account-options-dropdown')
|
||||
const NetworkDropdown = require('./network-dropdown').default
|
||||
|
||||
module.exports = {
|
||||
AccountSelectionDropdown,
|
||||
AccountOptionsDropdown,
|
||||
NetworkDropdown,
|
||||
Dropdown,
|
||||
AccountDropdowns,
|
||||
}
|
316
ui/app/components/dropdowns/network-dropdown.js
Normal file
@ -0,0 +1,316 @@
|
||||
const Component = require('react').Component
|
||||
const h = require('react-hyperscript')
|
||||
const inherits = require('util').inherits
|
||||
const connect = require('react-redux').connect
|
||||
const actions = require('../../actions')
|
||||
const Dropdown = require('./components/dropdown').Dropdown
|
||||
const DropdownMenuItem = require('./components/dropdown').DropdownMenuItem
|
||||
const NetworkDropdownIcon = require('./components/network-dropdown-icon')
|
||||
|
||||
function mapStateToProps (state) {
|
||||
return {
|
||||
provider: state.metamask.provider,
|
||||
frequentRpcList: state.metamask.frequentRpcList || [],
|
||||
networkDropdownOpen: state.appState.networkDropdownOpen,
|
||||
}
|
||||
}
|
||||
|
||||
function mapDispatchToProps (dispatch) {
|
||||
return {
|
||||
hideModal: () => {
|
||||
dispatch(actions.hideModal())
|
||||
},
|
||||
setProviderType: (type) => {
|
||||
dispatch(actions.setProviderType(type))
|
||||
},
|
||||
setDefaultRpcTarget: type => {
|
||||
dispatch(actions.setDefaultRpcTarget(type))
|
||||
},
|
||||
setRpcTarget: (target) => {
|
||||
dispatch(actions.setRpcTarget(target))
|
||||
},
|
||||
showConfigPage: () => {
|
||||
dispatch(actions.showConfigPage())
|
||||
},
|
||||
showNetworkDropdown: () => { dispatch(actions.showNetworkDropdown()) },
|
||||
hideNetworkDropdown: () => { dispatch(actions.hideNetworkDropdown()) },
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
inherits(NetworkDropdown, Component)
|
||||
function NetworkDropdown () {
|
||||
Component.call(this)
|
||||
}
|
||||
|
||||
module.exports = connect(mapStateToProps, mapDispatchToProps)(NetworkDropdown)
|
||||
|
||||
// TODO: specify default props and proptypes
|
||||
NetworkDropdown.prototype.render = function () {
|
||||
const props = this.props
|
||||
const { provider: { type: providerType, rpcTarget: activeNetwork } } = props
|
||||
const rpcList = props.frequentRpcList
|
||||
const isOpen = this.props.networkDropdownOpen
|
||||
const dropdownMenuItemStyle = {
|
||||
fontFamily: 'DIN OT',
|
||||
fontSize: '16px',
|
||||
lineHeight: '20px',
|
||||
padding: '12px 0',
|
||||
}
|
||||
|
||||
return h(Dropdown, {
|
||||
useCssTransition: true,
|
||||
isOpen,
|
||||
onClickOutside: (event) => {
|
||||
const { classList } = event.target
|
||||
const isNotToggleElement = [
|
||||
classList.contains('menu-icon'),
|
||||
classList.contains('network-name'),
|
||||
classList.contains('network-indicator'),
|
||||
].filter(bool => bool).length === 0
|
||||
// classes from three constituent nodes of the toggle element
|
||||
|
||||
if (isNotToggleElement) {
|
||||
this.props.hideNetworkDropdown()
|
||||
}
|
||||
},
|
||||
containerClassName: 'network-droppo',
|
||||
zIndex: 11,
|
||||
style: {
|
||||
position: 'absolute',
|
||||
top: '38px',
|
||||
minWidth: '309px',
|
||||
},
|
||||
innerStyle: {
|
||||
padding: '18px 8px',
|
||||
},
|
||||
}, [
|
||||
|
||||
h('div.network-dropdown-header', {}, [
|
||||
h('div.network-dropdown-title', {}, 'Networks'),
|
||||
|
||||
h('div.network-dropdown-divider'),
|
||||
|
||||
h('div.network-dropdown-content',
|
||||
{},
|
||||
'The default network for Ether transactions is Main Net.'
|
||||
),
|
||||
]),
|
||||
|
||||
h(
|
||||
DropdownMenuItem,
|
||||
{
|
||||
key: 'main',
|
||||
closeMenu: () => this.props.hideNetworkDropdown(),
|
||||
onClick: () => props.setProviderType('mainnet'),
|
||||
style: { ...dropdownMenuItemStyle, borderColor: '#038789' },
|
||||
},
|
||||
[
|
||||
providerType === 'mainnet' ? h('i.fa.fa-check') : h('.network-check__transparent', '✓'),
|
||||
h(NetworkDropdownIcon, {
|
||||
backgroundColor: '#038789', // $blue-lagoon
|
||||
isSelected: providerType === 'mainnet',
|
||||
}),
|
||||
h('span.network-name-item', {
|
||||
style: {
|
||||
color: providerType === 'mainnet' ? '#ffffff' : '#9b9b9b',
|
||||
},
|
||||
}, 'Main Ethereum Network'),
|
||||
]
|
||||
),
|
||||
|
||||
h(
|
||||
DropdownMenuItem,
|
||||
{
|
||||
key: 'ropsten',
|
||||
closeMenu: () => this.props.hideNetworkDropdown(),
|
||||
onClick: () => props.setProviderType('ropsten'),
|
||||
style: dropdownMenuItemStyle,
|
||||
},
|
||||
[
|
||||
providerType === 'ropsten' ? h('i.fa.fa-check') : h('.network-check__transparent', '✓'),
|
||||
h(NetworkDropdownIcon, {
|
||||
backgroundColor: '#e91550', // $crimson
|
||||
isSelected: providerType === 'ropsten',
|
||||
}),
|
||||
h('span.network-name-item', {
|
||||
style: {
|
||||
color: providerType === 'ropsten' ? '#ffffff' : '#9b9b9b',
|
||||
},
|
||||
}, 'Ropsten Test Network'),
|
||||
]
|
||||
),
|
||||
|
||||
h(
|
||||
DropdownMenuItem,
|
||||
{
|
||||
key: 'kovan',
|
||||
closeMenu: () => this.props.hideNetworkDropdown(),
|
||||
onClick: () => props.setProviderType('kovan'),
|
||||
style: dropdownMenuItemStyle,
|
||||
},
|
||||
[
|
||||
providerType === 'kovan' ? h('i.fa.fa-check') : h('.network-check__transparent', '✓'),
|
||||
h(NetworkDropdownIcon, {
|
||||
backgroundColor: '#690496', // $purple
|
||||
isSelected: providerType === 'kovan',
|
||||
}),
|
||||
h('span.network-name-item', {
|
||||
style: {
|
||||
color: providerType === 'kovan' ? '#ffffff' : '#9b9b9b',
|
||||
},
|
||||
}, 'Kovan Test Network'),
|
||||
]
|
||||
),
|
||||
|
||||
h(
|
||||
DropdownMenuItem,
|
||||
{
|
||||
key: 'rinkeby',
|
||||
closeMenu: () => this.props.hideNetworkDropdown(),
|
||||
onClick: () => props.setProviderType('rinkeby'),
|
||||
style: dropdownMenuItemStyle,
|
||||
},
|
||||
[
|
||||
providerType === 'rinkeby' ? h('i.fa.fa-check') : h('.network-check__transparent', '✓'),
|
||||
h(NetworkDropdownIcon, {
|
||||
backgroundColor: '#ebb33f', // $tulip-tree
|
||||
isSelected: providerType === 'rinkeby',
|
||||
}),
|
||||
h('span.network-name-item', {
|
||||
style: {
|
||||
color: providerType === 'rinkeby' ? '#ffffff' : '#9b9b9b',
|
||||
},
|
||||
}, 'Rinkeby Test Network'),
|
||||
]
|
||||
),
|
||||
|
||||
h(
|
||||
DropdownMenuItem,
|
||||
{
|
||||
key: 'default',
|
||||
closeMenu: () => this.props.hideNetworkDropdown(),
|
||||
onClick: () => props.setRpcTarget('http://localhost:8545'),
|
||||
style: dropdownMenuItemStyle,
|
||||
},
|
||||
[
|
||||
activeNetwork === 'http://localhost:8545' ? h('i.fa.fa-check') : h('.network-check__transparent', '✓'),
|
||||
h(NetworkDropdownIcon, {
|
||||
isSelected: activeNetwork === 'http://localhost:8545',
|
||||
innerBorder: '1px solid #9b9b9b',
|
||||
}),
|
||||
h('span.network-name-item', {
|
||||
style: {
|
||||
color: activeNetwork === 'http://localhost:8545' ? '#ffffff' : '#9b9b9b',
|
||||
},
|
||||
}, 'Localhost 8545'),
|
||||
]
|
||||
),
|
||||
|
||||
this.renderCustomOption(props.provider),
|
||||
this.renderCommonRpc(rpcList, props.provider),
|
||||
|
||||
h(
|
||||
DropdownMenuItem,
|
||||
{
|
||||
closeMenu: () => this.props.hideNetworkDropdown(),
|
||||
onClick: () => this.props.showConfigPage(),
|
||||
style: dropdownMenuItemStyle,
|
||||
},
|
||||
[
|
||||
activeNetwork === 'custom' ? h('i.fa.fa-check') : h('.network-check__transparent', '✓'),
|
||||
h(NetworkDropdownIcon, {
|
||||
isSelected: activeNetwork === 'custom',
|
||||
innerBorder: '1px solid #9b9b9b',
|
||||
}),
|
||||
h('span.network-name-item', {
|
||||
style: {
|
||||
color: activeNetwork === 'custom' ? '#ffffff' : '#9b9b9b',
|
||||
},
|
||||
}, 'Custom RPC'),
|
||||
]
|
||||
),
|
||||
|
||||
])
|
||||
}
|
||||
|
||||
|
||||
NetworkDropdown.prototype.getNetworkName = function () {
|
||||
const { provider } = this.props
|
||||
const providerName = provider.type
|
||||
|
||||
let name
|
||||
|
||||
if (providerName === 'mainnet') {
|
||||
name = 'Main Ethereum Network'
|
||||
} else if (providerName === 'ropsten') {
|
||||
name = 'Ropsten Test Network'
|
||||
} else if (providerName === 'kovan') {
|
||||
name = 'Kovan Test Network'
|
||||
} else if (providerName === 'rinkeby') {
|
||||
name = 'Rinkeby Test Network'
|
||||
} else {
|
||||
name = 'Unknown Private Network'
|
||||
}
|
||||
|
||||
return name
|
||||
}
|
||||
|
||||
NetworkDropdown.prototype.renderCommonRpc = function (rpcList, provider) {
|
||||
const props = this.props
|
||||
const rpcTarget = provider.rpcTarget
|
||||
|
||||
return rpcList.map((rpc) => {
|
||||
if ((rpc === 'http://localhost:8545') || (rpc === rpcTarget)) {
|
||||
return null
|
||||
} else {
|
||||
return h(
|
||||
DropdownMenuItem,
|
||||
{
|
||||
key: `common${rpc}`,
|
||||
closeMenu: () => this.props.hideNetworkDropdown(),
|
||||
onClick: () => props.setRpcTarget(rpc),
|
||||
},
|
||||
[
|
||||
h('i.fa.fa-question-circle.fa-lg.menu-icon'),
|
||||
rpc,
|
||||
rpcTarget === rpc ? h('.check', '✓') : null,
|
||||
]
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
NetworkDropdown.prototype.renderCustomOption = function (provider) {
|
||||
const { rpcTarget, type } = provider
|
||||
const props = this.props
|
||||
|
||||
if (type !== 'rpc') return null
|
||||
|
||||
// Concatenate long URLs
|
||||
let label = rpcTarget
|
||||
if (rpcTarget.length > 31) {
|
||||
label = label.substr(0, 34) + '...'
|
||||
}
|
||||
|
||||
switch (rpcTarget) {
|
||||
|
||||
case 'http://localhost:8545':
|
||||
return null
|
||||
|
||||
default:
|
||||
return h(
|
||||
DropdownMenuItem,
|
||||
{
|
||||
key: rpcTarget,
|
||||
onClick: () => props.setRpcTarget(rpcTarget),
|
||||
closeMenu: () => this.props.hideNetworkDropdown(),
|
||||
},
|
||||
[
|
||||
h('i.fa.fa-question-circle.fa-lg.menu-icon'),
|
||||
label,
|
||||
h('.check', '✓'),
|
||||
]
|
||||
)
|
||||
}
|
||||
}
|
51
ui/app/components/dropdowns/token-menu-dropdown.js
Normal file
@ -0,0 +1,51 @@
|
||||
const Component = require('react').Component
|
||||
const h = require('react-hyperscript')
|
||||
const inherits = require('util').inherits
|
||||
const connect = require('react-redux').connect
|
||||
const actions = require('../../actions')
|
||||
|
||||
module.exports = connect(null, mapDispatchToProps)(TokenMenuDropdown)
|
||||
|
||||
function mapDispatchToProps (dispatch) {
|
||||
return {
|
||||
showHideTokenConfirmationModal: (token) => {
|
||||
dispatch(actions.showModal({ name: 'HIDE_TOKEN_CONFIRMATION', token }))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
inherits(TokenMenuDropdown, Component)
|
||||
function TokenMenuDropdown () {
|
||||
Component.call(this)
|
||||
|
||||
this.onClose = this.onClose.bind(this)
|
||||
}
|
||||
|
||||
TokenMenuDropdown.prototype.onClose = function (e) {
|
||||
e.stopPropagation()
|
||||
this.props.onClose()
|
||||
}
|
||||
|
||||
TokenMenuDropdown.prototype.render = function () {
|
||||
const { showHideTokenConfirmationModal } = this.props
|
||||
|
||||
return h('div.token-menu-dropdown', {}, [
|
||||
h('div.token-menu-dropdown__close-area', {
|
||||
onClick: this.onClose,
|
||||
}),
|
||||
h('div.token-menu-dropdown__container', {}, [
|
||||
h('div.token-menu-dropdown__options', {}, [
|
||||
|
||||
h('div.token-menu-dropdown__option', {
|
||||
onClick: (e) => {
|
||||
e.stopPropagation()
|
||||
showHideTokenConfirmationModal(this.props.token)
|
||||
this.props.onClose()
|
||||
},
|
||||
}, 'Hide Token')
|
||||
|
||||
]),
|
||||
]),
|
||||
])
|
||||
}
|
@ -44,7 +44,7 @@ EnsInput.prototype.render = function () {
|
||||
return h('div', {
|
||||
style: { width: '100%' },
|
||||
}, [
|
||||
h('input.large-input', opts),
|
||||
h('input.large-input.send-screen-input', opts),
|
||||
// The address book functionality.
|
||||
h('datalist#addresses',
|
||||
[
|
||||
@ -125,7 +125,7 @@ EnsInput.prototype.componentDidUpdate = function (prevProps, prevState) {
|
||||
|
||||
EnsInput.prototype.ensIcon = function (recipient) {
|
||||
const { hoverText } = this.state || {}
|
||||
return h('span', {
|
||||
return h('span.#ensIcon', {
|
||||
title: hoverText,
|
||||
style: {
|
||||
position: 'absolute',
|
||||
|
@ -1,8 +1,10 @@
|
||||
const Component = require('react').Component
|
||||
const { Component } = require('react')
|
||||
const h = require('react-hyperscript')
|
||||
const inherits = require('util').inherits
|
||||
const formatBalance = require('../util').formatBalance
|
||||
const generateBalanceObject = require('../util').generateBalanceObject
|
||||
const { inherits } = require('util')
|
||||
const {
|
||||
formatBalance,
|
||||
generateBalanceObject,
|
||||
} = require('../util')
|
||||
const Tooltip = require('./tooltip.js')
|
||||
const FiatValue = require('./fiat-value.js')
|
||||
|
||||
@ -14,11 +16,10 @@ function EthBalanceComponent () {
|
||||
}
|
||||
|
||||
EthBalanceComponent.prototype.render = function () {
|
||||
var props = this.props
|
||||
let { value } = props
|
||||
const { style, width } = props
|
||||
var needsParse = this.props.needsParse !== undefined ? this.props.needsParse : true
|
||||
value = value ? formatBalance(value, 6, needsParse) : '...'
|
||||
const props = this.props
|
||||
const { value, style, width, needsParse = true } = props
|
||||
|
||||
const formattedValue = value ? formatBalance(value, 6, needsParse) : '...'
|
||||
|
||||
return (
|
||||
|
||||
@ -30,60 +31,66 @@ EthBalanceComponent.prototype.render = function () {
|
||||
display: 'inline',
|
||||
width,
|
||||
},
|
||||
}, this.renderBalance(value)),
|
||||
}, this.renderBalance(formattedValue)),
|
||||
])
|
||||
|
||||
)
|
||||
}
|
||||
EthBalanceComponent.prototype.renderBalance = function (value) {
|
||||
var props = this.props
|
||||
const { conversionRate, shorten, incoming, currentCurrency } = props
|
||||
if (value === 'None') return value
|
||||
if (value === '...') return value
|
||||
var balanceObj = generateBalanceObject(value, shorten ? 1 : 3)
|
||||
var balance
|
||||
var splitBalance = value.split(' ')
|
||||
var ethNumber = splitBalance[0]
|
||||
var ethSuffix = splitBalance[1]
|
||||
const showFiat = 'showFiat' in props ? props.showFiat : true
|
||||
|
||||
if (shorten) {
|
||||
balance = balanceObj.shortBalance
|
||||
} else {
|
||||
balance = balanceObj.balance
|
||||
const {
|
||||
conversionRate,
|
||||
shorten,
|
||||
incoming,
|
||||
currentCurrency,
|
||||
hideTooltip,
|
||||
styleOveride,
|
||||
showFiat = true,
|
||||
} = this.props
|
||||
const { fontSize, color, fontFamily, lineHeight } = styleOveride
|
||||
|
||||
const { shortBalance, balance, label } = generateBalanceObject(value, shorten ? 1 : 3)
|
||||
const balanceToRender = shorten ? shortBalance : balance
|
||||
|
||||
const [ethNumber, ethSuffix] = value.split(' ')
|
||||
const containerProps = hideTooltip ? {} : {
|
||||
position: 'bottom',
|
||||
title: `${ethNumber} ${ethSuffix}`,
|
||||
}
|
||||
|
||||
var label = balanceObj.label
|
||||
|
||||
return (
|
||||
h(Tooltip, {
|
||||
position: 'bottom',
|
||||
title: `${ethNumber} ${ethSuffix}`,
|
||||
}, h('div.flex-column', [
|
||||
h('.flex-row', {
|
||||
style: {
|
||||
alignItems: 'flex-end',
|
||||
lineHeight: '13px',
|
||||
fontFamily: 'Montserrat Light',
|
||||
textRendering: 'geometricPrecision',
|
||||
},
|
||||
}, [
|
||||
h('div', {
|
||||
h(hideTooltip ? 'div' : Tooltip,
|
||||
containerProps,
|
||||
h('div.flex-column', [
|
||||
h('.flex-row', {
|
||||
style: {
|
||||
width: '100%',
|
||||
textAlign: 'right',
|
||||
alignItems: 'flex-end',
|
||||
lineHeight: lineHeight || '13px',
|
||||
fontFamily: fontFamily || 'Montserrat Light',
|
||||
textRendering: 'geometricPrecision',
|
||||
},
|
||||
}, incoming ? `+${balance}` : balance),
|
||||
h('div', {
|
||||
style: {
|
||||
color: ' #AEAEAE',
|
||||
fontSize: '12px',
|
||||
marginLeft: '5px',
|
||||
},
|
||||
}, label),
|
||||
]),
|
||||
}, [
|
||||
h('div', {
|
||||
style: {
|
||||
width: '100%',
|
||||
textAlign: 'right',
|
||||
fontSize: fontSize || 'inherit',
|
||||
color: color || 'inherit',
|
||||
},
|
||||
}, incoming ? `+${balanceToRender}` : balanceToRender),
|
||||
h('div', {
|
||||
style: {
|
||||
color: color || '#AEAEAE',
|
||||
fontSize: fontSize || '12px',
|
||||
marginLeft: '5px',
|
||||
},
|
||||
}, label),
|
||||
]),
|
||||
|
||||
showFiat ? h(FiatValue, { value: props.value, conversionRate, currentCurrency }) : null,
|
||||
]))
|
||||
showFiat ? h(FiatValue, { value: this.props.value, conversionRate, currentCurrency }) : null,
|
||||
])
|
||||
)
|
||||
)
|
||||
}
|
||||
|
@ -12,7 +12,7 @@ function FiatValue () {
|
||||
|
||||
FiatValue.prototype.render = function () {
|
||||
const props = this.props
|
||||
const { conversionRate, currentCurrency } = props
|
||||
const { conversionRate, currentCurrency, style } = props
|
||||
const renderedCurrency = currentCurrency || ''
|
||||
|
||||
const value = formatBalance(props.value, 6)
|
||||
@ -29,16 +29,18 @@ FiatValue.prototype.render = function () {
|
||||
fiatTooltipNumber = 'Unknown'
|
||||
}
|
||||
|
||||
return fiatDisplay(fiatDisplayNumber, renderedCurrency.toUpperCase())
|
||||
return fiatDisplay(fiatDisplayNumber, renderedCurrency.toUpperCase(), style)
|
||||
}
|
||||
|
||||
function fiatDisplay (fiatDisplayNumber, fiatSuffix) {
|
||||
function fiatDisplay (fiatDisplayNumber, fiatSuffix, styleOveride = {}) {
|
||||
const { fontSize, color, fontFamily, lineHeight } = styleOveride
|
||||
|
||||
if (fiatDisplayNumber !== 'N/A') {
|
||||
return h('.flex-row', {
|
||||
style: {
|
||||
alignItems: 'flex-end',
|
||||
lineHeight: '13px',
|
||||
fontFamily: 'Montserrat Light',
|
||||
lineHeight: lineHeight || '13px',
|
||||
fontFamily: fontFamily || 'Montserrat Light',
|
||||
textRendering: 'geometricPrecision',
|
||||
},
|
||||
}, [
|
||||
@ -46,15 +48,15 @@ function fiatDisplay (fiatDisplayNumber, fiatSuffix) {
|
||||
style: {
|
||||
width: '100%',
|
||||
textAlign: 'right',
|
||||
fontSize: '12px',
|
||||
color: '#333333',
|
||||
fontSize: fontSize || '12px',
|
||||
color: color || '#333333',
|
||||
},
|
||||
}, fiatDisplayNumber),
|
||||
h('div', {
|
||||
style: {
|
||||
color: '#AEAEAE',
|
||||
color: color || '#AEAEAE',
|
||||
marginLeft: '5px',
|
||||
fontSize: '12px',
|
||||
fontSize: fontSize || '12px',
|
||||
},
|
||||
}, fiatSuffix),
|
||||
])
|
||||
|
@ -18,21 +18,35 @@ function IdenticonComponent () {
|
||||
|
||||
IdenticonComponent.prototype.render = function () {
|
||||
var props = this.props
|
||||
const { className = '', address } = props
|
||||
var diameter = props.diameter || this.defaultDiameter
|
||||
return (
|
||||
h('div', {
|
||||
key: 'identicon-' + this.props.address,
|
||||
style: {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
height: diameter,
|
||||
width: diameter,
|
||||
borderRadius: diameter / 2,
|
||||
overflow: 'hidden',
|
||||
},
|
||||
})
|
||||
)
|
||||
|
||||
return address
|
||||
? (
|
||||
h('div', {
|
||||
className: `${className} identicon`,
|
||||
key: 'identicon-' + address,
|
||||
style: {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
height: diameter,
|
||||
width: diameter,
|
||||
borderRadius: diameter / 2,
|
||||
overflow: 'hidden',
|
||||
},
|
||||
})
|
||||
)
|
||||
: (
|
||||
h('img.balance-icon', {
|
||||
src: '../images/eth_logo.svg',
|
||||
style: {
|
||||
height: diameter,
|
||||
width: diameter,
|
||||
borderRadius: diameter / 2,
|
||||
},
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
IdenticonComponent.prototype.componentDidMount = function () {
|
||||
|
46
ui/app/components/input-number.js
Normal file
@ -0,0 +1,46 @@
|
||||
const Component = require('react').Component
|
||||
const h = require('react-hyperscript')
|
||||
const inherits = require('util').inherits
|
||||
const { addCurrencies } = require('../conversion-util')
|
||||
|
||||
module.exports = InputNumber
|
||||
|
||||
inherits(InputNumber, Component)
|
||||
function InputNumber () {
|
||||
Component.call(this)
|
||||
|
||||
this.setValue = this.setValue.bind(this)
|
||||
}
|
||||
|
||||
InputNumber.prototype.setValue = function (newValue) {
|
||||
const { fixed, min = -1, max = Infinity, onChange } = this.props
|
||||
|
||||
newValue = Number(fixed ? newValue.toFixed(4) : newValue)
|
||||
|
||||
if (newValue >= min && newValue <= max) {
|
||||
onChange(newValue)
|
||||
}
|
||||
}
|
||||
|
||||
InputNumber.prototype.render = function () {
|
||||
const { unitLabel, step = 1, placeholder, value = 0 } = this.props
|
||||
|
||||
return h('div.customize-gas-input-wrapper', {}, [
|
||||
h('input.customize-gas-input', {
|
||||
placeholder,
|
||||
type: 'number',
|
||||
value: value,
|
||||
onChange: (e) => this.setValue(e.target.value),
|
||||
}),
|
||||
h('span.gas-tooltip-input-detail', {}, [unitLabel]),
|
||||
h('div.gas-tooltip-input-arrows', {}, [
|
||||
h('i.fa.fa-angle-up', {
|
||||
onClick: () => this.setValue(addCurrencies(value, step)),
|
||||
}),
|
||||
h('i.fa.fa-angle-down', {
|
||||
style: { cursor: 'pointer' },
|
||||
onClick: () => this.setValue(addCurrencies(value, step * -1)),
|
||||
}),
|
||||
]),
|
||||
])
|
||||
}
|
@ -13,21 +13,23 @@ function MenuDroppoComponent () {
|
||||
}
|
||||
|
||||
MenuDroppoComponent.prototype.render = function () {
|
||||
const { containerClassName = '' } = this.props
|
||||
const speed = this.props.speed || '300ms'
|
||||
const useCssTransition = this.props.useCssTransition
|
||||
const zIndex = ('zIndex' in this.props) ? this.props.zIndex : 0
|
||||
|
||||
this.manageListeners()
|
||||
|
||||
let style = this.props.style || {}
|
||||
const style = this.props.style || {}
|
||||
if (!('position' in style)) {
|
||||
style.position = 'fixed'
|
||||
}
|
||||
style.zIndex = zIndex
|
||||
|
||||
return (
|
||||
h('.menu-droppo-container', {
|
||||
h('div', {
|
||||
style,
|
||||
className: `.menu-droppo-container ${containerClassName}`,
|
||||
}, [
|
||||
h('style', `
|
||||
.menu-droppo-enter {
|
||||
|
70
ui/app/components/modals/account-details-modal.js
Normal file
@ -0,0 +1,70 @@
|
||||
const Component = require('react').Component
|
||||
const h = require('react-hyperscript')
|
||||
const inherits = require('util').inherits
|
||||
const connect = require('react-redux').connect
|
||||
const actions = require('../../actions')
|
||||
const AccountModalContainer = require('./account-modal-container')
|
||||
const { getSelectedIdentity, getSelectedAddress } = require('../../selectors')
|
||||
const genAccountLink = require('../../../lib/account-link.js')
|
||||
const QrView = require('../qr-code')
|
||||
|
||||
function mapStateToProps (state) {
|
||||
return {
|
||||
network: state.metamask.network,
|
||||
selectedIdentity: getSelectedIdentity(state),
|
||||
}
|
||||
}
|
||||
|
||||
function mapDispatchToProps (dispatch) {
|
||||
return {
|
||||
// Is this supposed to be used somewhere?
|
||||
showQrView: (selected, identity) => dispatch(actions.showQrView(selected, identity)),
|
||||
showExportPrivateKeyModal: () => {
|
||||
dispatch(actions.showModal({ name: 'EXPORT_PRIVATE_KEY' }))
|
||||
},
|
||||
hideModal: () => dispatch(actions.hideModal()),
|
||||
}
|
||||
}
|
||||
|
||||
inherits(AccountDetailsModal, Component)
|
||||
function AccountDetailsModal () {
|
||||
Component.call(this)
|
||||
}
|
||||
|
||||
module.exports = connect(mapStateToProps, mapDispatchToProps)(AccountDetailsModal)
|
||||
|
||||
// Not yet pixel perfect todos:
|
||||
// fonts of qr-header
|
||||
|
||||
AccountDetailsModal.prototype.render = function () {
|
||||
const {
|
||||
selectedIdentity,
|
||||
network,
|
||||
showExportPrivateKeyModal,
|
||||
hideModal,
|
||||
} = this.props
|
||||
const { name, address } = selectedIdentity
|
||||
|
||||
return h(AccountModalContainer, {}, [
|
||||
h(QrView, {
|
||||
Qr: {
|
||||
message: name,
|
||||
data: address,
|
||||
},
|
||||
}),
|
||||
|
||||
h('div.account-modal-divider'),
|
||||
|
||||
h('button.btn-clear', {
|
||||
onClick: () => global.platform.openWindow({ url: genAccountLink(address, network) }),
|
||||
}, [ 'View account on Etherscan' ]),
|
||||
|
||||
// Holding on redesign for Export Private Key functionality
|
||||
h('button.btn-clear', {
|
||||
onClick: () => {
|
||||
showExportPrivateKeyModal()
|
||||
},
|
||||
}, [ 'Export private key' ]),
|
||||
|
||||
])
|
||||
}
|
74
ui/app/components/modals/account-modal-container.js
Normal file
@ -0,0 +1,74 @@
|
||||
const Component = require('react').Component
|
||||
const h = require('react-hyperscript')
|
||||
const inherits = require('util').inherits
|
||||
const connect = require('react-redux').connect
|
||||
const actions = require('../../actions')
|
||||
const { getSelectedIdentity } = require('../../selectors')
|
||||
const Identicon = require('../identicon')
|
||||
|
||||
function mapStateToProps (state) {
|
||||
return {
|
||||
selectedIdentity: getSelectedIdentity(state),
|
||||
}
|
||||
}
|
||||
|
||||
function mapDispatchToProps (dispatch) {
|
||||
return {
|
||||
hideModal: () => {
|
||||
dispatch(actions.hideModal())
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
inherits(AccountModalContainer, Component)
|
||||
function AccountModalContainer () {
|
||||
Component.call(this)
|
||||
}
|
||||
|
||||
module.exports = connect(mapStateToProps, mapDispatchToProps)(AccountModalContainer)
|
||||
|
||||
AccountModalContainer.prototype.render = function () {
|
||||
const {
|
||||
selectedIdentity,
|
||||
showBackButton = false,
|
||||
backButtonAction,
|
||||
} = this.props
|
||||
let { children } = this.props
|
||||
|
||||
if (children.constructor !== Array) {
|
||||
children = [children]
|
||||
}
|
||||
|
||||
return h('div', { style: { borderRadius: '4px' }}, [
|
||||
h('div.account-modal-container', [
|
||||
|
||||
h('div', [
|
||||
|
||||
// Needs a border; requires changes to svg
|
||||
h(Identicon, {
|
||||
address: selectedIdentity.address,
|
||||
diameter: 64,
|
||||
style: {},
|
||||
}),
|
||||
|
||||
]),
|
||||
|
||||
showBackButton && h('div.account-modal-back', {
|
||||
onClick: backButtonAction,
|
||||
}, [
|
||||
|
||||
h('i.fa.fa-angle-left.fa-lg'),
|
||||
|
||||
h('span.account-modal-back__text', ' Back'),
|
||||
|
||||
]),
|
||||
|
||||
h('div.account-modal-close', {
|
||||
onClick: this.props.hideModal,
|
||||
}),
|
||||
|
||||
...children,
|
||||
|
||||
]),
|
||||
])
|
||||
}
|
87
ui/app/components/modals/buy-options-modal.js
Normal file
@ -0,0 +1,87 @@
|
||||
const Component = require('react').Component
|
||||
const h = require('react-hyperscript')
|
||||
const inherits = require('util').inherits
|
||||
const connect = require('react-redux').connect
|
||||
const actions = require('../../actions')
|
||||
|
||||
function mapStateToProps (state) {
|
||||
return {
|
||||
network: state.metamask.network,
|
||||
address: state.metamask.selectedAddress,
|
||||
}
|
||||
}
|
||||
|
||||
function mapDispatchToProps (dispatch) {
|
||||
return {
|
||||
toCoinbase: (address) => {
|
||||
dispatch(actions.buyEth({ network: '1', address, amount: 0 }))
|
||||
},
|
||||
hideModal: () => {
|
||||
dispatch(actions.hideModal())
|
||||
},
|
||||
showAccountDetailModal: () => {
|
||||
dispatch(actions.showModal({ name: 'ACCOUNT_DETAILS' }))
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
inherits(BuyOptions, Component)
|
||||
function BuyOptions () {
|
||||
Component.call(this)
|
||||
}
|
||||
|
||||
module.exports = connect(mapStateToProps, mapDispatchToProps)(BuyOptions)
|
||||
|
||||
BuyOptions.prototype.render = function () {
|
||||
return h('div', {}, [
|
||||
h('div.buy-modal-content.transfers-subview', {
|
||||
}, [
|
||||
h('div.buy-modal-content-title-wrapper.flex-column.flex-center', {
|
||||
style: {},
|
||||
}, [
|
||||
h('div.buy-modal-content-title', {
|
||||
style: {},
|
||||
}, 'Transfers'),
|
||||
h('div', {}, 'How would you like to buy Ether?'),
|
||||
]),
|
||||
|
||||
h('div.buy-modal-content-options.flex-column.flex-center', {}, [
|
||||
|
||||
h('div.buy-modal-content-option', {
|
||||
onClick: () => {
|
||||
const { toCoinbase, address } = this.props
|
||||
toCoinbase(address)
|
||||
},
|
||||
}, [
|
||||
h('div.buy-modal-content-option-title', {}, 'Coinbase'),
|
||||
h('div.buy-modal-content-option-subtitle', {}, 'Buy with Fiat'),
|
||||
]),
|
||||
|
||||
// h('div.buy-modal-content-option', {}, [
|
||||
// h('div.buy-modal-content-option-title', {}, 'Shapeshift'),
|
||||
// h('div.buy-modal-content-option-subtitle', {}, 'Trade any digital asset for any other'),
|
||||
// ]),
|
||||
|
||||
h('div.buy-modal-content-option', {
|
||||
onClick: () => this.goToAccountDetailsModal(),
|
||||
}, [
|
||||
h('div.buy-modal-content-option-title', {}, 'Direct Deposit'),
|
||||
h('div.buy-modal-content-option-subtitle', {}, 'Deposit from another account'),
|
||||
]),
|
||||
|
||||
]),
|
||||
|
||||
h('button', {
|
||||
style: {
|
||||
background: 'white',
|
||||
},
|
||||
onClick: () => { this.props.hideModal() },
|
||||
}, h('div.buy-modal-content-footer#buy-modal-content-footer-text', {}, 'Cancel')),
|
||||
]),
|
||||
])
|
||||
}
|
||||
|
||||
BuyOptions.prototype.goToAccountDetailsModal = function () {
|
||||
this.props.hideModal()
|
||||
this.props.showAccountDetailModal()
|
||||
}
|
77
ui/app/components/modals/edit-account-name-modal.js
Normal file
@ -0,0 +1,77 @@
|
||||
const Component = require('react').Component
|
||||
const h = require('react-hyperscript')
|
||||
const inherits = require('util').inherits
|
||||
const connect = require('react-redux').connect
|
||||
const actions = require('../../actions')
|
||||
const { getSelectedAccount } = require('../../selectors')
|
||||
|
||||
function mapStateToProps (state) {
|
||||
return {
|
||||
selectedAccount: getSelectedAccount(state),
|
||||
identity: state.appState.modal.modalState.identity,
|
||||
}
|
||||
}
|
||||
|
||||
function mapDispatchToProps (dispatch) {
|
||||
return {
|
||||
hideModal: () => {
|
||||
dispatch(actions.hideModal())
|
||||
},
|
||||
saveAccountLabel: (account, label) => {
|
||||
dispatch(actions.saveAccountLabel(account, label))
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
inherits(EditAccountNameModal, Component)
|
||||
function EditAccountNameModal (props) {
|
||||
Component.call(this)
|
||||
|
||||
this.state = {
|
||||
inputText: props.identity.name,
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = connect(mapStateToProps, mapDispatchToProps)(EditAccountNameModal)
|
||||
|
||||
EditAccountNameModal.prototype.render = function () {
|
||||
const { hideModal, saveAccountLabel, identity } = this.props
|
||||
|
||||
return h('div', {}, [
|
||||
h('div.flex-column.edit-account-name-modal-content', {
|
||||
}, [
|
||||
|
||||
h('div.edit-account-name-modal-cancel', {
|
||||
onClick: () => {
|
||||
hideModal()
|
||||
},
|
||||
}, [
|
||||
h('i.fa.fa-times'),
|
||||
]),
|
||||
|
||||
h('div.edit-account-name-modal-title', {
|
||||
}, ['Edit Account Name']),
|
||||
|
||||
h('input.edit-account-name-modal-input', {
|
||||
placeholder: identity.name,
|
||||
onChange: (event) => {
|
||||
this.setState({ inputText: event.target.value })
|
||||
},
|
||||
value: this.state.inputText,
|
||||
}, []),
|
||||
|
||||
h('button.btn-clear.edit-account-name-modal-save-button', {
|
||||
onClick: () => {
|
||||
if (this.state.inputText.length !== 0) {
|
||||
saveAccountLabel(identity.address, this.state.inputText)
|
||||
hideModal()
|
||||
}
|
||||
},
|
||||
disabled: this.state.inputText.length === 0,
|
||||
}, [
|
||||
'SAVE',
|
||||
]),
|
||||
|
||||
]),
|
||||
])
|
||||
}
|
139
ui/app/components/modals/export-private-key-modal.js
Normal file
@ -0,0 +1,139 @@
|
||||
const Component = require('react').Component
|
||||
const h = require('react-hyperscript')
|
||||
const inherits = require('util').inherits
|
||||
const connect = require('react-redux').connect
|
||||
const ethUtil = require('ethereumjs-util')
|
||||
const actions = require('../../actions')
|
||||
const AccountModalContainer = require('./account-modal-container')
|
||||
const { getSelectedIdentity } = require('../../selectors')
|
||||
const ReadOnlyInput = require('../readonly-input')
|
||||
const copyToClipboard = require('copy-to-clipboard')
|
||||
|
||||
function mapStateToProps (state) {
|
||||
return {
|
||||
warning: state.appState.warning,
|
||||
privateKey: state.appState.accountDetail.privateKey,
|
||||
network: state.metamask.network,
|
||||
selectedIdentity: getSelectedIdentity(state),
|
||||
previousModalState: state.appState.modal.previousModalState.name,
|
||||
}
|
||||
}
|
||||
|
||||
function mapDispatchToProps (dispatch) {
|
||||
return {
|
||||
exportAccount: (password, address) => dispatch(actions.exportAccount(password, address)),
|
||||
showAccountDetailModal: () => dispatch(actions.showModal({ name: 'ACCOUNT_DETAILS' })),
|
||||
hideModal: () => dispatch(actions.hideModal()),
|
||||
}
|
||||
}
|
||||
|
||||
inherits(ExportPrivateKeyModal, Component)
|
||||
function ExportPrivateKeyModal () {
|
||||
Component.call(this)
|
||||
|
||||
this.state = {
|
||||
password: '',
|
||||
privateKey: null,
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = connect(mapStateToProps, mapDispatchToProps)(ExportPrivateKeyModal)
|
||||
|
||||
ExportPrivateKeyModal.prototype.exportAccountAndGetPrivateKey = function (password, address) {
|
||||
const { exportAccount } = this.props
|
||||
|
||||
exportAccount(password, address)
|
||||
.then(privateKey => this.setState({ privateKey }))
|
||||
}
|
||||
|
||||
ExportPrivateKeyModal.prototype.renderPasswordLabel = function (privateKey) {
|
||||
return h('span.private-key-password-label', privateKey
|
||||
? 'This is your private key (click to copy)'
|
||||
: 'Type Your Password'
|
||||
)
|
||||
}
|
||||
|
||||
ExportPrivateKeyModal.prototype.renderPasswordInput = function (privateKey) {
|
||||
const plainKey = privateKey && ethUtil.stripHexPrefix(privateKey)
|
||||
|
||||
return privateKey
|
||||
? h(ReadOnlyInput, {
|
||||
wrapperClass: 'private-key-password-display-wrapper',
|
||||
inputClass: 'private-key-password-display-textarea',
|
||||
textarea: true,
|
||||
value: plainKey,
|
||||
onClick: () => copyToClipboard(plainKey),
|
||||
})
|
||||
: h('input.private-key-password-input', {
|
||||
type: 'password',
|
||||
placeholder: 'Type password',
|
||||
onChange: event => this.setState({ password: event.target.value }),
|
||||
})
|
||||
}
|
||||
|
||||
ExportPrivateKeyModal.prototype.renderButton = function (className, onClick, label) {
|
||||
return h('button', {
|
||||
className,
|
||||
onClick,
|
||||
}, label)
|
||||
}
|
||||
|
||||
ExportPrivateKeyModal.prototype.renderButtons = function (privateKey, password, address, hideModal) {
|
||||
return h('div.export-private-key-buttons', {}, [
|
||||
!privateKey && this.renderButton('btn-clear btn-cancel', () => hideModal(), 'Cancel'),
|
||||
|
||||
(privateKey
|
||||
? this.renderButton('btn-clear', () => hideModal(), 'Done')
|
||||
: this.renderButton('btn-clear', () => this.exportAccountAndGetPrivateKey(this.state.password, address), 'Download')
|
||||
),
|
||||
|
||||
])
|
||||
}
|
||||
|
||||
ExportPrivateKeyModal.prototype.render = function () {
|
||||
const {
|
||||
selectedIdentity,
|
||||
network,
|
||||
warning,
|
||||
showAccountDetailModal,
|
||||
hideModal,
|
||||
previousModalState,
|
||||
} = this.props
|
||||
const { name, address } = selectedIdentity
|
||||
|
||||
const { privateKey } = this.state
|
||||
|
||||
return h(AccountModalContainer, {
|
||||
showBackButton: previousModalState === 'ACCOUNT_DETAILS',
|
||||
backButtonAction: () => showAccountDetailModal(),
|
||||
}, [
|
||||
|
||||
h('span.account-name', name),
|
||||
|
||||
h(ReadOnlyInput, {
|
||||
wrapperClass: 'ellip-address-wrapper',
|
||||
inputClass: 'qr-ellip-address ellip-address',
|
||||
value: address,
|
||||
}),
|
||||
|
||||
h('div.account-modal-divider'),
|
||||
|
||||
h('span.modal-body-title', 'Download Private Keys'),
|
||||
|
||||
h('div.private-key-password', {}, [
|
||||
this.renderPasswordLabel(privateKey),
|
||||
|
||||
this.renderPasswordInput(privateKey),
|
||||
|
||||
!warning ? null : h('span.private-key-password-error', warning),
|
||||
]),
|
||||
|
||||
h('div.private-key-password-warning', `Warning: Never disclose this key.
|
||||
Anyone with your private keys can take steal any assets held in your
|
||||
account.`
|
||||
),
|
||||
|
||||
this.renderButtons(privateKey, this.state.password, address, hideModal),
|
||||
|
||||
])
|
||||
}
|
74
ui/app/components/modals/hide-token-confirmation-modal.js
Normal file
@ -0,0 +1,74 @@
|
||||
const Component = require('react').Component
|
||||
const h = require('react-hyperscript')
|
||||
const inherits = require('util').inherits
|
||||
const connect = require('react-redux').connect
|
||||
const actions = require('../../actions')
|
||||
const Identicon = require('../identicon')
|
||||
|
||||
function mapStateToProps (state) {
|
||||
return {
|
||||
network: state.metamask.network,
|
||||
token: state.appState.modal.modalState.token,
|
||||
}
|
||||
}
|
||||
|
||||
function mapDispatchToProps (dispatch) {
|
||||
return {
|
||||
hideModal: () => dispatch(actions.hideModal()),
|
||||
hideToken: address => {
|
||||
dispatch(actions.removeToken(address))
|
||||
.then(() => {
|
||||
dispatch(actions.hideModal())
|
||||
})
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
inherits(HideTokenConfirmationModal, Component)
|
||||
function HideTokenConfirmationModal () {
|
||||
Component.call(this)
|
||||
|
||||
this.state = {}
|
||||
}
|
||||
|
||||
module.exports = connect(mapStateToProps, mapDispatchToProps)(HideTokenConfirmationModal)
|
||||
|
||||
HideTokenConfirmationModal.prototype.render = function () {
|
||||
const { token, network, hideToken, hideModal } = this.props
|
||||
const { symbol, address } = token
|
||||
|
||||
return h('div.hide-token-confirmation', {}, [
|
||||
h('div.hide-token-confirmation__container', {
|
||||
}, [
|
||||
h('div.hide-token-confirmation__title', {}, [
|
||||
'Hide Token?',
|
||||
]),
|
||||
|
||||
h(Identicon, {
|
||||
className: 'hide-token-confirmation__identicon',
|
||||
diameter: 45,
|
||||
address,
|
||||
network,
|
||||
}),
|
||||
|
||||
h('div.hide-token-confirmation__symbol', {}, symbol),
|
||||
|
||||
h('div.hide-token-confirmation__copy', {}, [
|
||||
'You can add this token back in the future by going go to “Add token” in your accounts options menu.',
|
||||
]),
|
||||
|
||||
h('div.hide-token-confirmation__buttons', {}, [
|
||||
h('button.btn-clear', {
|
||||
onClick: () => hideModal(),
|
||||
}, [
|
||||
'CANCEL',
|
||||
]),
|
||||
h('button.btn-clear', {
|
||||
onClick: () => hideToken(address),
|
||||
}, [
|
||||
'HIDE',
|
||||
]),
|
||||
]),
|
||||
]),
|
||||
])
|
||||
}
|
5
ui/app/components/modals/index.js
Normal file
@ -0,0 +1,5 @@
|
||||
const Modal = require('./modal')
|
||||
|
||||
module.exports = {
|
||||
Modal,
|
||||
}
|
263
ui/app/components/modals/modal.js
Normal file
@ -0,0 +1,263 @@
|
||||
const Component = require('react').Component
|
||||
const h = require('react-hyperscript')
|
||||
const inherits = require('util').inherits
|
||||
const connect = require('react-redux').connect
|
||||
const FadeModal = require('boron').FadeModal
|
||||
const actions = require('../../actions')
|
||||
const isMobileView = require('../../../lib/is-mobile-view')
|
||||
const isPopupOrNotification = require('../../../../app/scripts/lib/is-popup-or-notification')
|
||||
|
||||
// Modal Components
|
||||
const BuyOptions = require('./buy-options-modal')
|
||||
const AccountDetailsModal = require('./account-details-modal')
|
||||
const EditAccountNameModal = require('./edit-account-name-modal')
|
||||
const ExportPrivateKeyModal = require('./export-private-key-modal')
|
||||
const NewAccountModal = require('./new-account-modal')
|
||||
const ShapeshiftDepositTxModal = require('./shapeshift-deposit-tx-modal.js')
|
||||
const HideTokenConfirmationModal = require('./hide-token-confirmation-modal')
|
||||
const CustomizeGasModal = require('../customize-gas-modal')
|
||||
|
||||
const accountModalStyle = {
|
||||
mobileModalStyle: {
|
||||
width: '95%',
|
||||
// top: isPopupOrNotification() === 'popup' ? '52vh' : '36.5vh',
|
||||
boxShadow: 'rgba(0, 0, 0, 0.15) 0px 2px 2px 2px',
|
||||
borderRadius: '4px',
|
||||
top: '10%',
|
||||
transform: 'none',
|
||||
left: '0',
|
||||
right: '0',
|
||||
margin: '0 auto',
|
||||
},
|
||||
laptopModalStyle: {
|
||||
width: '360px',
|
||||
// top: 'calc(33% + 45px)',
|
||||
boxShadow: 'rgba(0, 0, 0, 0.15) 0px 2px 2px 2px',
|
||||
borderRadius: '4px',
|
||||
top: '10%',
|
||||
transform: 'none',
|
||||
left: '0',
|
||||
right: '0',
|
||||
margin: '0 auto',
|
||||
},
|
||||
contentStyle: {
|
||||
borderRadius: '4px',
|
||||
},
|
||||
}
|
||||
|
||||
const MODALS = {
|
||||
BUY: {
|
||||
contents: [
|
||||
h(BuyOptions, {}, []),
|
||||
],
|
||||
mobileModalStyle: {
|
||||
width: '95%',
|
||||
// top: isPopupOrNotification() === 'popup' ? '48vh' : '36.5vh',
|
||||
transform: 'none',
|
||||
left: '0',
|
||||
right: '0',
|
||||
margin: '0 auto',
|
||||
boxShadow: '0 0 7px 0 rgba(0,0,0,0.08)',
|
||||
top: '10%',
|
||||
},
|
||||
laptopModalStyle: {
|
||||
width: '66%',
|
||||
maxWidth: '550px',
|
||||
top: 'calc(10% + 10px)',
|
||||
left: '0',
|
||||
right: '0',
|
||||
margin: '0 auto',
|
||||
boxShadow: '0 0 7px 0 rgba(0,0,0,0.08)',
|
||||
transform: 'none',
|
||||
},
|
||||
},
|
||||
|
||||
EDIT_ACCOUNT_NAME: {
|
||||
contents: [
|
||||
h(EditAccountNameModal, {}, []),
|
||||
],
|
||||
mobileModalStyle: {
|
||||
width: '95%',
|
||||
// top: isPopupOrNotification() === 'popup' ? '48vh' : '36.5vh',
|
||||
top: '10%',
|
||||
boxShadow: 'rgba(0, 0, 0, 0.15) 0px 2px 2px 2px',
|
||||
transform: 'none',
|
||||
left: '0',
|
||||
right: '0',
|
||||
margin: '0 auto',
|
||||
},
|
||||
laptopModalStyle: {
|
||||
width: '375px',
|
||||
// top: 'calc(30% + 10px)',
|
||||
top: '10%',
|
||||
boxShadow: 'rgba(0, 0, 0, 0.15) 0px 2px 2px 2px',
|
||||
transform: 'none',
|
||||
left: '0',
|
||||
right: '0',
|
||||
margin: '0 auto',
|
||||
},
|
||||
},
|
||||
|
||||
ACCOUNT_DETAILS: {
|
||||
contents: [
|
||||
h(AccountDetailsModal, {}, []),
|
||||
],
|
||||
...accountModalStyle,
|
||||
},
|
||||
|
||||
EXPORT_PRIVATE_KEY: {
|
||||
contents: [
|
||||
h(ExportPrivateKeyModal, {}, []),
|
||||
],
|
||||
...accountModalStyle,
|
||||
},
|
||||
|
||||
SHAPESHIFT_DEPOSIT_TX: {
|
||||
contents: [
|
||||
h(ShapeshiftDepositTxModal),
|
||||
],
|
||||
...accountModalStyle,
|
||||
},
|
||||
|
||||
HIDE_TOKEN_CONFIRMATION: {
|
||||
contents: [
|
||||
h(HideTokenConfirmationModal, {}, []),
|
||||
],
|
||||
mobileModalStyle: {
|
||||
width: '95%',
|
||||
top: isPopupOrNotification() === 'popup' ? '52vh' : '36.5vh',
|
||||
},
|
||||
laptopModalStyle: {
|
||||
width: '449px',
|
||||
top: 'calc(33% + 45px)',
|
||||
},
|
||||
},
|
||||
|
||||
NEW_ACCOUNT: {
|
||||
contents: [
|
||||
h(NewAccountModal, {}, []),
|
||||
],
|
||||
mobileModalStyle: {
|
||||
width: '95%',
|
||||
// top: isPopupOrNotification() === 'popup' ? '52vh' : '36.5vh',
|
||||
top: '10%',
|
||||
transform: 'none',
|
||||
left: '0',
|
||||
right: '0',
|
||||
margin: '0 auto',
|
||||
},
|
||||
laptopModalStyle: {
|
||||
width: '449px',
|
||||
// top: 'calc(33% + 45px)',
|
||||
top: '10%',
|
||||
transform: 'none',
|
||||
left: '0',
|
||||
right: '0',
|
||||
margin: '0 auto',
|
||||
},
|
||||
},
|
||||
|
||||
CUSTOMIZE_GAS: {
|
||||
contents: [
|
||||
h(CustomizeGasModal, {}, []),
|
||||
],
|
||||
mobileModalStyle: {
|
||||
width: '355px',
|
||||
height: '598px',
|
||||
// top: isPopupOrNotification() === 'popup' ? '52vh' : '36.5vh',
|
||||
top: '5%',
|
||||
transform: 'none',
|
||||
left: '0',
|
||||
right: '0',
|
||||
margin: '0 auto',
|
||||
},
|
||||
laptopModalStyle: {
|
||||
width: '720px',
|
||||
height: '377px',
|
||||
top: '80px',
|
||||
transform: 'none',
|
||||
left: '0',
|
||||
right: '0',
|
||||
margin: '0 auto',
|
||||
},
|
||||
},
|
||||
|
||||
DEFAULT: {
|
||||
contents: [],
|
||||
mobileModalStyle: {},
|
||||
laptopModalStyle: {},
|
||||
},
|
||||
}
|
||||
|
||||
const BACKDROPSTYLE = {
|
||||
backgroundColor: 'rgba(245, 245, 245, 0.85)',
|
||||
}
|
||||
|
||||
function mapStateToProps (state) {
|
||||
return {
|
||||
active: state.appState.modal.open,
|
||||
modalState: state.appState.modal.modalState,
|
||||
}
|
||||
}
|
||||
|
||||
function mapDispatchToProps (dispatch) {
|
||||
return {
|
||||
hideModal: () => {
|
||||
dispatch(actions.hideModal())
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Global Modal Component
|
||||
inherits(Modal, Component)
|
||||
function Modal () {
|
||||
Component.call(this)
|
||||
}
|
||||
|
||||
module.exports = connect(mapStateToProps, mapDispatchToProps)(Modal)
|
||||
|
||||
Modal.prototype.render = function () {
|
||||
const modal = MODALS[this.props.modalState.name || 'DEFAULT']
|
||||
|
||||
const children = modal.contents
|
||||
const modalStyle = modal[isMobileView() ? 'mobileModalStyle' : 'laptopModalStyle']
|
||||
const contentStyle = modal.contentStyle || {};
|
||||
|
||||
return h(FadeModal,
|
||||
{
|
||||
className: 'modal',
|
||||
keyboard: false,
|
||||
onHide: () => { this.onHide() },
|
||||
ref: (ref) => {
|
||||
this.modalRef = ref
|
||||
},
|
||||
modalStyle,
|
||||
contentStyle,
|
||||
backdropStyle: BACKDROPSTYLE,
|
||||
},
|
||||
children,
|
||||
)
|
||||
}
|
||||
|
||||
Modal.prototype.componentWillReceiveProps = function (nextProps) {
|
||||
if (nextProps.active) {
|
||||
this.show()
|
||||
} else if (this.props.active) {
|
||||
this.hide()
|
||||
}
|
||||
}
|
||||
|
||||
Modal.prototype.onHide = function () {
|
||||
if (this.props.onHideCallback) {
|
||||
this.props.onHideCallback()
|
||||
}
|
||||
this.props.hideModal()
|
||||
}
|
||||
|
||||
Modal.prototype.hide = function () {
|
||||
this.modalRef.hide()
|
||||
}
|
||||
|
||||
Modal.prototype.show = function () {
|
||||
this.modalRef.show()
|
||||
}
|
87
ui/app/components/modals/new-account-modal.js
Normal file
@ -0,0 +1,87 @@
|
||||
const Component = require('react').Component
|
||||
const h = require('react-hyperscript')
|
||||
const inherits = require('util').inherits
|
||||
const connect = require('react-redux').connect
|
||||
const actions = require('../../actions')
|
||||
|
||||
function mapStateToProps (state) {
|
||||
return {
|
||||
network: state.metamask.network,
|
||||
address: state.metamask.selectedAddress,
|
||||
}
|
||||
}
|
||||
|
||||
function mapDispatchToProps (dispatch) {
|
||||
return {
|
||||
toCoinbase: (address) => {
|
||||
dispatch(actions.buyEth({ network: '1', address, amount: 0 }))
|
||||
},
|
||||
hideModal: () => {
|
||||
dispatch(actions.hideModal())
|
||||
},
|
||||
createAccount: (newAccountName) => {
|
||||
dispatch(actions.addNewAccount())
|
||||
.then((newAccountAddress) => {
|
||||
if (newAccountName) {
|
||||
dispatch(actions.saveAccountLabel(newAccountAddress, newAccountName))
|
||||
}
|
||||
dispatch(actions.hideModal())
|
||||
})
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
inherits(NewAccountModal, Component)
|
||||
function NewAccountModal () {
|
||||
Component.call(this)
|
||||
|
||||
this.state = {
|
||||
newAccountName: ''
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = connect(mapStateToProps, mapDispatchToProps)(NewAccountModal)
|
||||
|
||||
NewAccountModal.prototype.render = function () {
|
||||
const { newAccountName } = this.state
|
||||
|
||||
return h('div', {}, [
|
||||
h('div.new-account-modal-wrapper', {
|
||||
}, [
|
||||
h('div.new-account-modal-header', {}, [
|
||||
'New Account',
|
||||
]),
|
||||
|
||||
h('div.modal-close-x', {
|
||||
onClick: this.props.hideModal,
|
||||
}),
|
||||
|
||||
h('div.new-account-modal-content', {}, [
|
||||
'Account Name',
|
||||
]),
|
||||
|
||||
h('div.new-account-input-wrapper', {}, [
|
||||
h('input.new-account-input', {
|
||||
placeholder: 'E.g. My new account',
|
||||
onChange: (event) => this.setState({ newAccountName: event.target.value })
|
||||
}, []),
|
||||
]),
|
||||
|
||||
h('div.new-account-modal-content.after-input', {}, [
|
||||
'or',
|
||||
]),
|
||||
|
||||
h('div.new-account-modal-content.after-input', {}, [
|
||||
'Import an account',
|
||||
]),
|
||||
|
||||
h('div.new-account-modal-content.button', {}, [
|
||||
h('button.btn-clear', {
|
||||
onClick: () => this.props.createAccount(newAccountName)
|
||||
}, [
|
||||
'SAVE',
|
||||
]),
|
||||
]),
|
||||
]),
|
||||
])
|
||||
}
|
40
ui/app/components/modals/shapeshift-deposit-tx-modal.js
Normal file
@ -0,0 +1,40 @@
|
||||
const Component = require('react').Component
|
||||
const h = require('react-hyperscript')
|
||||
const inherits = require('util').inherits
|
||||
const connect = require('react-redux').connect
|
||||
const actions = require('../../actions')
|
||||
const QrView = require('../qr-code')
|
||||
const AccountModalContainer = require('./account-modal-container')
|
||||
|
||||
function mapStateToProps (state) {
|
||||
return {
|
||||
Qr: state.appState.modal.modalState.Qr,
|
||||
}
|
||||
}
|
||||
|
||||
function mapDispatchToProps (dispatch) {
|
||||
return {
|
||||
hideModal: () => {
|
||||
dispatch(actions.hideModal())
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
inherits(ShapeshiftDepositTxModal, Component)
|
||||
function ShapeshiftDepositTxModal () {
|
||||
Component.call(this)
|
||||
|
||||
}
|
||||
|
||||
module.exports = connect(mapStateToProps, mapDispatchToProps)(ShapeshiftDepositTxModal)
|
||||
|
||||
ShapeshiftDepositTxModal.prototype.render = function () {
|
||||
const { Qr } = this.props
|
||||
|
||||
return h(AccountModalContainer, {
|
||||
}, [
|
||||
h('div', {}, [
|
||||
h(QrView, {key: 'qr', Qr}),
|
||||
])
|
||||
])
|
||||
}
|
@ -1,6 +1,8 @@
|
||||
const Component = require('react').Component
|
||||
const h = require('react-hyperscript')
|
||||
const classnames = require('classnames');
|
||||
const inherits = require('util').inherits
|
||||
const NetworkDropdownIcon = require('./dropdowns/components/network-dropdown-icon')
|
||||
|
||||
module.exports = Network
|
||||
|
||||
@ -60,7 +62,13 @@ Network.prototype.render = function () {
|
||||
}
|
||||
|
||||
return (
|
||||
h('#network_component.pointer', {
|
||||
h('div.network-component.pointer', {
|
||||
className: classnames('network-component pointer', {
|
||||
'ethereum-network': providerName === 'mainnet',
|
||||
'ropsten-test-network': providerName === 'ropsten' || parseInt(networkNumber) === 3,
|
||||
'kovan-test-network': providerName === 'kovan',
|
||||
'rinkeby-test-network': providerName === 'rinkeby',
|
||||
}),
|
||||
title: hoverText,
|
||||
onClick: (event) => this.props.onClick(event),
|
||||
}, [
|
||||
@ -68,7 +76,10 @@ Network.prototype.render = function () {
|
||||
switch (iconName) {
|
||||
case 'ethereum-network':
|
||||
return h('.network-indicator', [
|
||||
h('.menu-icon.diamond'),
|
||||
h(NetworkDropdownIcon, {
|
||||
backgroundColor: '#038789', // $blue-lagoon
|
||||
nonSelectBackgroundColor: '#15afb2',
|
||||
}),
|
||||
h('.network-name', {
|
||||
style: {
|
||||
color: '#039396',
|
||||
@ -78,7 +89,10 @@ Network.prototype.render = function () {
|
||||
])
|
||||
case 'ropsten-test-network':
|
||||
return h('.network-indicator', [
|
||||
h('.menu-icon.red-dot'),
|
||||
h(NetworkDropdownIcon, {
|
||||
backgroundColor: '#e91550', // $crimson
|
||||
nonSelectBackgroundColor: '#ec2c50',
|
||||
}),
|
||||
h('.network-name', {
|
||||
style: {
|
||||
color: '#ff6666',
|
||||
@ -88,7 +102,10 @@ Network.prototype.render = function () {
|
||||
])
|
||||
case 'kovan-test-network':
|
||||
return h('.network-indicator', [
|
||||
h('.menu-icon.hollow-diamond'),
|
||||
h(NetworkDropdownIcon, {
|
||||
backgroundColor: '#690496', // $purple
|
||||
nonSelectBackgroundColor: '#b039f3',
|
||||
}),
|
||||
h('.network-name', {
|
||||
style: {
|
||||
color: '#690496',
|
||||
@ -98,7 +115,10 @@ Network.prototype.render = function () {
|
||||
])
|
||||
case 'rinkeby-test-network':
|
||||
return h('.network-indicator', [
|
||||
h('.menu-icon.golden-square'),
|
||||
h(NetworkDropdownIcon, {
|
||||
backgroundColor: '#ebb33f', // $tulip-tree
|
||||
nonSelectBackgroundColor: '#ecb23e',
|
||||
}),
|
||||
h('.network-name', {
|
||||
style: {
|
||||
color: '#e7a218',
|
||||
|
@ -102,7 +102,7 @@ Notice.prototype.render = function () {
|
||||
}),
|
||||
]),
|
||||
|
||||
h('button', {
|
||||
h('button.primary', {
|
||||
disabled,
|
||||
onClick: () => {
|
||||
this.setState({disclaimerDisabled: true})
|
||||
|