From 3660ba28d73d70d08bf14c33ef680e5ef3ec7f3b Mon Sep 17 00:00:00 2001 From: Fabio Berger Date: Tue, 21 Nov 2017 14:03:08 -0600 Subject: Add website to mono repo, update packages to align with existing sub-packages, use new subscribeAsync 0x.js method --- .gitignore | 2 + packages/website/README.md | 60 + packages/website/contracts/Mintable.json | 189 + packages/website/less/all.less | 136 + packages/website/package.json | 105 + packages/website/public/css/atom-one-light.css | 96 + .../public/css/basscss_responsive_custom.css | 58 + .../public/css/basscss_responsive_margin.css | 160 + .../public/css/basscss_responsive_padding.css | 134 + .../public/css/basscss_responsive_type_scale.css | 35 + .../public/css/material-design-iconic-font.css | 5166 ++++++++++++++++++++ .../public/css/material-design-iconic-font.min.css | 1 + packages/website/public/css/roboto.css | 83 + packages/website/public/css/roboto_mono.css | 69 + .../public/fonts/Material-Design-Iconic-Font.eot | Bin 0 -> 42495 bytes .../public/fonts/Material-Design-Iconic-Font.svg | 787 +++ .../public/fonts/Material-Design-Iconic-Font.ttf | Bin 0 -> 99212 bytes .../public/fonts/Material-Design-Iconic-Font.woff | Bin 0 -> 50312 bytes .../public/fonts/Material-Design-Iconic-Font.woff2 | Bin 0 -> 38384 bytes packages/website/public/fonts/Roboto-Black.ttf | Bin 0 -> 171480 bytes .../website/public/fonts/Roboto-BlackItalic.ttf | Bin 0 -> 177552 bytes packages/website/public/fonts/Roboto-Bold.ttf | Bin 0 -> 170760 bytes .../website/public/fonts/Roboto-BoldItalic.ttf | Bin 0 -> 174952 bytes packages/website/public/fonts/Roboto-Italic.ttf | Bin 0 -> 173932 bytes packages/website/public/fonts/Roboto-Light.ttf | Bin 0 -> 170420 bytes .../website/public/fonts/Roboto-LightItalic.ttf | Bin 0 -> 176616 bytes packages/website/public/fonts/Roboto-Medium.ttf | Bin 0 -> 172064 bytes .../website/public/fonts/Roboto-MediumItalic.ttf | Bin 0 -> 176864 bytes packages/website/public/fonts/Roboto-Regular.ttf | Bin 0 -> 171676 bytes packages/website/public/fonts/Roboto-Thin.ttf | Bin 0 -> 171904 bytes .../website/public/fonts/Roboto-ThinItalic.ttf | Bin 0 -> 176300 bytes packages/website/public/fonts/RobotoMono-Bold.ttf | Bin 0 -> 114752 bytes .../website/public/fonts/RobotoMono-BoldItalic.ttf | Bin 0 -> 122808 bytes .../website/public/fonts/RobotoMono-Italic.ttf | Bin 0 -> 120832 bytes packages/website/public/fonts/RobotoMono-Light.ttf | Bin 0 -> 118976 bytes .../public/fonts/RobotoMono-LightItalic.ttf | Bin 0 -> 127568 bytes .../website/public/fonts/RobotoMono-Medium.ttf | Bin 0 -> 114696 bytes .../public/fonts/RobotoMono-MediumItalic.ttf | Bin 0 -> 123640 bytes .../website/public/fonts/RobotoMono-Regular.ttf | Bin 0 -> 114624 bytes packages/website/public/fonts/RobotoMono-Thin.ttf | Bin 0 -> 118132 bytes .../website/public/fonts/RobotoMono-ThinItalic.ttf | Bin 0 -> 121456 bytes packages/website/public/gifs/0xAnimation.gif | Bin 0 -> 909585 bytes packages/website/public/gifs/genesis.gif | Bin 0 -> 735849 bytes packages/website/public/images/0x_logo.png | Bin 0 -> 64503 bytes packages/website/public/images/advisors/fred.jpg | Bin 0 -> 4619 bytes packages/website/public/images/advisors/joey.jpg | Bin 0 -> 13493 bytes packages/website/public/images/advisors/linda.jpg | Bin 0 -> 6580 bytes packages/website/public/images/advisors/olaf.png | Bin 0 -> 26365 bytes packages/website/public/images/ether.png | Bin 0 -> 2312 bytes .../public/images/favicon/favicon-2-16x16.png | Bin 0 -> 684 bytes .../public/images/favicon/favicon-2-32x32.png | Bin 0 -> 1567 bytes packages/website/public/images/favicon/favicon.ico | Bin 0 -> 5430 bytes .../website/public/images/landing/0x_chips.png | Bin 0 -> 170875 bytes packages/website/public/images/landing/aragon.png | Bin 0 -> 4738 bytes packages/website/public/images/landing/augur.png | Bin 0 -> 4935 bytes .../website/public/images/landing/currency.png | Bin 0 -> 3348 bytes packages/website/public/images/landing/dharma.png | Bin 0 -> 4754 bytes .../public/images/landing/digital_goods.png | Bin 0 -> 3880 bytes .../public/images/landing/distributed_network.png | Bin 0 -> 37483 bytes .../website/public/images/landing/ethfinex.png | Bin 0 -> 6733 bytes .../public/images/landing/fund_management_icon.png | Bin 0 -> 5552 bytes packages/website/public/images/landing/gnosis.png | Bin 0 -> 4888 bytes .../public/images/landing/governance_icon.png | Bin 0 -> 6230 bytes .../public/images/landing/hero_chip_image.png | Bin 0 -> 256493 bytes .../website/public/images/landing/lendroid.png | Bin 0 -> 4305 bytes .../website/public/images/landing/liquidity.png | Bin 0 -> 22140 bytes .../website/public/images/landing/loans_icon.png | Bin 0 -> 5900 bytes packages/website/public/images/landing/maker.png | Bin 0 -> 3501 bytes .../website/public/images/landing/melonport.png | Bin 0 -> 4841 bytes .../website/public/images/landing/open_source.png | Bin 0 -> 14696 bytes packages/website/public/images/landing/paradex.png | Bin 0 -> 6904 bytes .../images/landing/prediction_market_icon.png | Bin 0 -> 6211 bytes .../public/images/landing/project_logos/anx.png | Bin 0 -> 5836 bytes .../public/images/landing/project_logos/aragon.png | Bin 0 -> 4642 bytes .../public/images/landing/project_logos/auctus.png | Bin 0 -> 3751 bytes .../public/images/landing/project_logos/augur.png | Bin 0 -> 4618 bytes .../images/landing/project_logos/blocknet.png | Bin 0 -> 4697 bytes .../images/landing/project_logos/chronobank.png | Bin 0 -> 6209 bytes .../public/images/landing/project_logos/dharma.png | Bin 0 -> 5429 bytes .../images/landing/project_logos/district0x.png | Bin 0 -> 5515 bytes .../public/images/landing/project_logos/dydx.png | Bin 0 -> 4191 bytes .../images/landing/project_logos/ethfinex-top.png | Bin 0 -> 4376 bytes .../public/images/landing/project_logos/ethix.png | Bin 0 -> 3438 bytes .../images/landing/project_logos/lendroid.png | Bin 0 -> 4866 bytes .../public/images/landing/project_logos/maker.png | Bin 0 -> 3951 bytes .../images/landing/project_logos/melonport.png | Bin 0 -> 5186 bytes .../images/landing/project_logos/paradex_top.png | Bin 0 -> 5109 bytes .../landing/project_logos/radar_relay_top.png | Bin 0 -> 4898 bytes .../public/images/landing/project_logos/status.png | Bin 0 -> 4287 bytes .../images/landing/project_logos/the_ocean.png | Bin 0 -> 4766 bytes .../website/public/images/landing/radar_relay.png | Bin 0 -> 6650 bytes .../public/images/landing/relayer_diagram.png | Bin 0 -> 111870 bytes .../public/images/landing/stable_tokens_icon.png | Bin 0 -> 5853 bytes packages/website/public/images/landing/stocks.png | Bin 0 -> 2098 bytes .../public/images/landing/tokenized_world.png | Bin 0 -> 109220 bytes packages/website/public/images/loading_poster.png | Bin 0 -> 2505 bytes packages/website/public/images/logos/FBG.png | Bin 0 -> 73781 bytes packages/website/public/images/logos/aragon.png | Bin 0 -> 5501 bytes packages/website/public/images/logos/augur.png | Bin 0 -> 5051 bytes .../public/images/logos/blockchain_capital.png | Bin 0 -> 12366 bytes .../website/public/images/logos/chronobank.png | Bin 0 -> 5615 bytes packages/website/public/images/logos/dharma.png | Bin 0 -> 5015 bytes .../website/public/images/logos/district0x.png | Bin 0 -> 5537 bytes .../website/public/images/logos/jen_advisors.png | Bin 0 -> 158434 bytes packages/website/public/images/logos/maker.png | Bin 0 -> 3791 bytes packages/website/public/images/logos/melonport.png | Bin 0 -> 5218 bytes packages/website/public/images/logos/openANX.png | Bin 0 -> 4973 bytes .../public/images/logos/pantera_capital.png | Bin 0 -> 8437 bytes .../public/images/logos/polychain_capital.png | Bin 0 -> 21279 bytes packages/website/public/images/og_image.png | Bin 0 -> 192500 bytes .../website/public/images/protocol_logo_black.png | Bin 0 -> 4031 bytes .../website/public/images/protocol_logo_white.png | Bin 0 -> 3931 bytes packages/website/public/images/social/github.png | Bin 0 -> 1154 bytes packages/website/public/images/social/medium.png | Bin 0 -> 890 bytes packages/website/public/images/social/reddit.png | Bin 0 -> 1168 bytes .../website/public/images/social/rocketchat.png | Bin 0 -> 1587 bytes packages/website/public/images/social/slack.png | Bin 0 -> 1311 bytes packages/website/public/images/social/twitter.png | Bin 0 -> 901 bytes packages/website/public/images/team/alex.jpg | Bin 0 -> 7271 bytes packages/website/public/images/team/amir.jpeg | Bin 0 -> 21098 bytes packages/website/public/images/team/anyone.png | Bin 0 -> 4794 bytes packages/website/public/images/team/ben.jpg | Bin 0 -> 25486 bytes packages/website/public/images/team/brandon.png | Bin 0 -> 30180 bytes packages/website/public/images/team/fabio.jpg | Bin 0 -> 17125 bytes packages/website/public/images/team/leonid.png | Bin 0 -> 35014 bytes packages/website/public/images/team/philippe.png | Bin 0 -> 51419 bytes packages/website/public/images/team/will.jpg | Bin 0 -> 11493 bytes .../website/public/images/token_icons/adtoken.png | Bin 0 -> 5150 bytes .../website/public/images/token_icons/aragon.png | Bin 0 -> 14012 bytes .../website/public/images/token_icons/augur.png | Bin 0 -> 146258 bytes .../website/public/images/token_icons/bancor.png | Bin 0 -> 2274 bytes .../images/token_icons/basicattentiontoken.png | Bin 0 -> 7082 bytes .../public/images/token_icons/bitquence.png | Bin 0 -> 14293 bytes packages/website/public/images/token_icons/btc.png | Bin 0 -> 5742 bytes .../website/public/images/token_icons/civic.png | Bin 0 -> 10700 bytes .../website/public/images/token_icons/clams.png | Bin 0 -> 18866 bytes .../public/images/token_icons/cofound-it.png | Bin 0 -> 5403 bytes .../website/public/images/token_icons/default.png | Bin 0 -> 17037 bytes .../website/public/images/token_icons/digixdao.png | Bin 0 -> 34397 bytes .../public/images/token_icons/district0x.png | Bin 0 -> 67267 bytes .../website/public/images/token_icons/edgeless.png | Bin 0 -> 2712 bytes packages/website/public/images/token_icons/eos.png | Bin 0 -> 2168 bytes .../public/images/token_icons/ether_erc20.png | Bin 0 -> 69772 bytes .../website/public/images/token_icons/etheroll.png | Bin 0 -> 22736 bytes .../public/images/token_icons/firstblood.jpg | Bin 0 -> 72909 bytes .../website/public/images/token_icons/funfair.png | Bin 0 -> 18800 bytes .../website/public/images/token_icons/gnosis.png | Bin 0 -> 7554 bytes .../website/public/images/token_icons/golem.png | Bin 0 -> 2990 bytes .../website/public/images/token_icons/iconomi.png | Bin 0 -> 810 bytes .../website/public/images/token_icons/iexec.png | Bin 0 -> 1910 bytes .../website/public/images/token_icons/lunyr.png | Bin 0 -> 5734 bytes .../website/public/images/token_icons/makerdao.png | Bin 0 -> 3161 bytes .../website/public/images/token_icons/melon.png | Bin 0 -> 3408 bytes .../website/public/images/token_icons/metal.png | Bin 0 -> 1295 bytes .../website/public/images/token_icons/monaco.png | Bin 0 -> 6984 bytes .../public/images/token_icons/numeraire.png | Bin 0 -> 10272 bytes .../website/public/images/token_icons/omisego.png | Bin 0 -> 5976 bytes .../website/public/images/token_icons/qtum.png | Bin 0 -> 169182 bytes .../public/images/token_icons/santiment.png | Bin 0 -> 4698 bytes .../public/images/token_icons/singularity.png | Bin 0 -> 3849 bytes .../website/public/images/token_icons/status.png | Bin 0 -> 3445 bytes .../public/images/token_icons/storjcoinx.png | Bin 0 -> 9453 bytes .../website/public/images/token_icons/taas.png | Bin 0 -> 7823 bytes .../website/public/images/token_icons/tenx.png | Bin 0 -> 7276 bytes .../public/images/token_icons/tokencard.png | Bin 0 -> 14586 bytes .../website/public/images/token_icons/trust.png | Bin 0 -> 14428 bytes .../website/public/images/token_icons/wings.png | Bin 0 -> 16143 bytes .../website/public/images/token_icons/zero_ex.png | Bin 0 -> 162879 bytes packages/website/public/images/trade_arrows.png | Bin 0 -> 1740 bytes packages/website/public/images/zrx_pie_chart.png | Bin 0 -> 54185 bytes packages/website/public/images/zrx_token.png | Bin 0 -> 16534 bytes packages/website/public/index.html | 78 + .../website/public/js/rollbar.umd.nojson.min.js | 1 + packages/website/public/pdfs/0x_white_paper.pdf | Bin 0 -> 319570 bytes packages/website/public/videos/0xAnimation.mp4 | Bin 0 -> 970342 bytes packages/website/ts/blockchain.ts | 770 +++ .../components/dialogs/blockchain_err_dialog.tsx | 158 + .../dialogs/eth_weth_conversion_dialog.tsx | 139 + .../ts/components/dialogs/ledger_config_dialog.tsx | 288 ++ .../dialogs/portal_disclaimer_dialog.tsx | 44 + .../website/ts/components/dialogs/send_dialog.tsx | 126 + .../dialogs/track_token_confirmation_dialog.tsx | 99 + .../dialogs/u2f_not_supported_dialog.tsx | 53 + .../ts/components/eth_weth_conversion_button.tsx | 101 + packages/website/ts/components/fill_order.tsx | 714 +++ packages/website/ts/components/fill_order_json.tsx | 69 + .../website/ts/components/fill_warning_dialog.tsx | 47 + .../flash_messages/token_send_completed.tsx | 38 + .../flash_messages/transaction_submitted.tsx | 29 + packages/website/ts/components/footer.tsx | 255 + .../ts/components/generate_order/asset_picker.tsx | 291 ++ .../generate_order/generate_order_form.tsx | 348 ++ .../components/generate_order/new_token_form.tsx | 237 + .../website/ts/components/inputs/address_input.tsx | 74 + .../ts/components/inputs/allowance_toggle.tsx | 94 + .../ts/components/inputs/balance_bounded_input.tsx | 160 + .../ts/components/inputs/eth_amount_input.tsx | 51 + .../ts/components/inputs/expiration_input.tsx | 108 + .../website/ts/components/inputs/hash_input.tsx | 65 + .../components/inputs/identicon_address_input.tsx | 56 + .../ts/components/inputs/token_amount_input.tsx | 69 + .../website/ts/components/inputs/token_input.tsx | 107 + packages/website/ts/components/order_json.tsx | 164 + packages/website/ts/components/portal.tsx | 344 ++ packages/website/ts/components/portal_menu.tsx | 68 + packages/website/ts/components/send_button.tsx | 89 + packages/website/ts/components/token_balances.tsx | 697 +++ packages/website/ts/components/top_bar.tsx | 370 ++ .../website/ts/components/top_bar_menu_item.tsx | 53 + .../ts/components/track_token_confirmation.tsx | 65 + .../ts/components/trade_history/trade_history.tsx | 115 + .../trade_history/trade_history_item.tsx | 178 + packages/website/ts/components/ui/alert.tsx | 27 + packages/website/ts/components/ui/badge.tsx | 58 + packages/website/ts/components/ui/copy_icon.tsx | 81 + .../ts/components/ui/drop_down_menu_item.tsx | 117 + .../website/ts/components/ui/ethereum_address.tsx | 35 + .../website/ts/components/ui/etherscan_icon.tsx | 50 + .../website/ts/components/ui/fake_text_field.tsx | 35 + .../website/ts/components/ui/flash_message.tsx | 40 + packages/website/ts/components/ui/help_tooltip.tsx | 22 + packages/website/ts/components/ui/identicon.tsx | 36 + packages/website/ts/components/ui/input_label.tsx | 27 + .../website/ts/components/ui/labeled_switcher.tsx | 76 + .../ts/components/ui/lifecycle_raised_button.tsx | 105 + packages/website/ts/components/ui/loading.tsx | 36 + packages/website/ts/components/ui/menu_item.tsx | 54 + packages/website/ts/components/ui/party.tsx | 150 + .../website/ts/components/ui/required_label.tsx | 15 + .../website/ts/components/ui/simple_loading.tsx | 23 + packages/website/ts/components/ui/swap_icon.tsx | 46 + packages/website/ts/components/ui/token_icon.tsx | 29 + packages/website/ts/components/visual_order.tsx | 77 + .../website/ts/containers/generate_order_form.tsx | 54 + packages/website/ts/containers/portal.tsx | 94 + .../containers/smart_contracts_documentation.tsx | 31 + .../ts/containers/zero_ex_js_documentation.tsx | 33 + packages/website/ts/globals.d.ts | 154 + packages/website/ts/index.tsx | 116 + packages/website/ts/lazy_component.tsx | 66 + packages/website/ts/local_storage/local_storage.ts | 35 + .../ts/local_storage/tracked_token_storage.ts | 56 + .../ts/local_storage/trade_history_storage.tsx | 82 + packages/website/ts/pages/about/about.tsx | 253 + packages/website/ts/pages/about/profile.tsx | 99 + .../website/ts/pages/documentation/comment.tsx | 24 + .../website/ts/pages/documentation/custom_enum.tsx | 31 + packages/website/ts/pages/documentation/enum.tsx | 26 + .../ts/pages/documentation/event_definition.tsx | 80 + .../website/ts/pages/documentation/interface.tsx | 54 + .../ts/pages/documentation/method_block.tsx | 174 + .../ts/pages/documentation/method_signature.tsx | 62 + .../smart_contracts_documentation.tsx | 401 ++ .../website/ts/pages/documentation/source_link.tsx | 27 + packages/website/ts/pages/documentation/type.tsx | 187 + .../ts/pages/documentation/type_definition.tsx | 135 + .../documentation/zero_ex_js_documentation.tsx | 340 ++ packages/website/ts/pages/faq/faq.tsx | 497 ++ packages/website/ts/pages/faq/question.tsx | 52 + packages/website/ts/pages/landing/landing.tsx | 843 ++++ packages/website/ts/pages/not_found.tsx | 46 + packages/website/ts/pages/shared/anchor_title.tsx | 98 + .../ts/pages/shared/markdown_code_block.tsx | 20 + .../website/ts/pages/shared/markdown_section.tsx | 77 + .../ts/pages/shared/nested_sidebar_menu.tsx | 163 + .../website/ts/pages/shared/section_header.tsx | 50 + .../website/ts/pages/shared/version_drop_down.tsx | 46 + packages/website/ts/pages/wiki/wiki.tsx | 210 + packages/website/ts/redux/dispatcher.ts | 244 + packages/website/ts/redux/reducer.ts | 363 ++ packages/website/ts/schemas/order_schema.ts | 24 + packages/website/ts/schemas/order_taker_schema.ts | 11 + .../website/ts/schemas/signature_data_schema.ts | 11 + packages/website/ts/schemas/token_schema.ts | 11 + packages/website/ts/schemas/validator.ts | 19 + .../ts/subproviders/injected_web3_subprovider.ts | 44 + .../ledger_wallet_subprovider_factory.ts | 172 + .../ts/subproviders/redundant_rpc_subprovider.ts | 41 + packages/website/ts/types.ts | 692 +++ packages/website/ts/utils/configs.ts | 18 + packages/website/ts/utils/constants.ts | 270 + packages/website/ts/utils/doc_utils.ts | 55 + packages/website/ts/utils/doxity_utils.ts | 162 + packages/website/ts/utils/error_reporter.ts | 52 + packages/website/ts/utils/typedoc_utils.ts | 356 ++ packages/website/ts/utils/utils.ts | 215 + packages/website/ts/vendor/u2f_api.js | 760 +++ packages/website/ts/web3_wrapper.ts | 146 + packages/website/tsconfig.json | 20 + packages/website/tslint.json | 9 + packages/website/webpack.config.js | 86 + yarn.lock | 2691 +++++++++- 292 files changed, 26119 insertions(+), 101 deletions(-) create mode 100644 packages/website/README.md create mode 100644 packages/website/contracts/Mintable.json create mode 100644 packages/website/less/all.less create mode 100644 packages/website/package.json create mode 100644 packages/website/public/css/atom-one-light.css create mode 100644 packages/website/public/css/basscss_responsive_custom.css create mode 100644 packages/website/public/css/basscss_responsive_margin.css create mode 100644 packages/website/public/css/basscss_responsive_padding.css create mode 100644 packages/website/public/css/basscss_responsive_type_scale.css create mode 100755 packages/website/public/css/material-design-iconic-font.css create mode 100755 packages/website/public/css/material-design-iconic-font.min.css create mode 100644 packages/website/public/css/roboto.css create mode 100644 packages/website/public/css/roboto_mono.css create mode 100755 packages/website/public/fonts/Material-Design-Iconic-Font.eot create mode 100755 packages/website/public/fonts/Material-Design-Iconic-Font.svg create mode 100755 packages/website/public/fonts/Material-Design-Iconic-Font.ttf create mode 100755 packages/website/public/fonts/Material-Design-Iconic-Font.woff create mode 100755 packages/website/public/fonts/Material-Design-Iconic-Font.woff2 create mode 100755 packages/website/public/fonts/Roboto-Black.ttf create mode 100755 packages/website/public/fonts/Roboto-BlackItalic.ttf create mode 100755 packages/website/public/fonts/Roboto-Bold.ttf create mode 100755 packages/website/public/fonts/Roboto-BoldItalic.ttf create mode 100755 packages/website/public/fonts/Roboto-Italic.ttf create mode 100755 packages/website/public/fonts/Roboto-Light.ttf create mode 100755 packages/website/public/fonts/Roboto-LightItalic.ttf create mode 100755 packages/website/public/fonts/Roboto-Medium.ttf create mode 100755 packages/website/public/fonts/Roboto-MediumItalic.ttf create mode 100755 packages/website/public/fonts/Roboto-Regular.ttf create mode 100755 packages/website/public/fonts/Roboto-Thin.ttf create mode 100755 packages/website/public/fonts/Roboto-ThinItalic.ttf create mode 100755 packages/website/public/fonts/RobotoMono-Bold.ttf create mode 100755 packages/website/public/fonts/RobotoMono-BoldItalic.ttf create mode 100755 packages/website/public/fonts/RobotoMono-Italic.ttf create mode 100755 packages/website/public/fonts/RobotoMono-Light.ttf create mode 100755 packages/website/public/fonts/RobotoMono-LightItalic.ttf create mode 100755 packages/website/public/fonts/RobotoMono-Medium.ttf create mode 100755 packages/website/public/fonts/RobotoMono-MediumItalic.ttf create mode 100755 packages/website/public/fonts/RobotoMono-Regular.ttf create mode 100755 packages/website/public/fonts/RobotoMono-Thin.ttf create mode 100755 packages/website/public/fonts/RobotoMono-ThinItalic.ttf create mode 100644 packages/website/public/gifs/0xAnimation.gif create mode 100644 packages/website/public/gifs/genesis.gif create mode 100644 packages/website/public/images/0x_logo.png create mode 100644 packages/website/public/images/advisors/fred.jpg create mode 100644 packages/website/public/images/advisors/joey.jpg create mode 100644 packages/website/public/images/advisors/linda.jpg create mode 100644 packages/website/public/images/advisors/olaf.png create mode 100644 packages/website/public/images/ether.png create mode 100755 packages/website/public/images/favicon/favicon-2-16x16.png create mode 100755 packages/website/public/images/favicon/favicon-2-32x32.png create mode 100755 packages/website/public/images/favicon/favicon.ico create mode 100644 packages/website/public/images/landing/0x_chips.png create mode 100644 packages/website/public/images/landing/aragon.png create mode 100644 packages/website/public/images/landing/augur.png create mode 100644 packages/website/public/images/landing/currency.png create mode 100644 packages/website/public/images/landing/dharma.png create mode 100644 packages/website/public/images/landing/digital_goods.png create mode 100644 packages/website/public/images/landing/distributed_network.png create mode 100644 packages/website/public/images/landing/ethfinex.png create mode 100644 packages/website/public/images/landing/fund_management_icon.png create mode 100644 packages/website/public/images/landing/gnosis.png create mode 100644 packages/website/public/images/landing/governance_icon.png create mode 100644 packages/website/public/images/landing/hero_chip_image.png create mode 100644 packages/website/public/images/landing/lendroid.png create mode 100644 packages/website/public/images/landing/liquidity.png create mode 100644 packages/website/public/images/landing/loans_icon.png create mode 100644 packages/website/public/images/landing/maker.png create mode 100644 packages/website/public/images/landing/melonport.png create mode 100644 packages/website/public/images/landing/open_source.png create mode 100644 packages/website/public/images/landing/paradex.png create mode 100644 packages/website/public/images/landing/prediction_market_icon.png create mode 100644 packages/website/public/images/landing/project_logos/anx.png create mode 100644 packages/website/public/images/landing/project_logos/aragon.png create mode 100644 packages/website/public/images/landing/project_logos/auctus.png create mode 100644 packages/website/public/images/landing/project_logos/augur.png create mode 100644 packages/website/public/images/landing/project_logos/blocknet.png create mode 100644 packages/website/public/images/landing/project_logos/chronobank.png create mode 100644 packages/website/public/images/landing/project_logos/dharma.png create mode 100644 packages/website/public/images/landing/project_logos/district0x.png create mode 100644 packages/website/public/images/landing/project_logos/dydx.png create mode 100644 packages/website/public/images/landing/project_logos/ethfinex-top.png create mode 100644 packages/website/public/images/landing/project_logos/ethix.png create mode 100644 packages/website/public/images/landing/project_logos/lendroid.png create mode 100644 packages/website/public/images/landing/project_logos/maker.png create mode 100644 packages/website/public/images/landing/project_logos/melonport.png create mode 100644 packages/website/public/images/landing/project_logos/paradex_top.png create mode 100644 packages/website/public/images/landing/project_logos/radar_relay_top.png create mode 100644 packages/website/public/images/landing/project_logos/status.png create mode 100644 packages/website/public/images/landing/project_logos/the_ocean.png create mode 100644 packages/website/public/images/landing/radar_relay.png create mode 100644 packages/website/public/images/landing/relayer_diagram.png create mode 100644 packages/website/public/images/landing/stable_tokens_icon.png create mode 100644 packages/website/public/images/landing/stocks.png create mode 100644 packages/website/public/images/landing/tokenized_world.png create mode 100644 packages/website/public/images/loading_poster.png create mode 100644 packages/website/public/images/logos/FBG.png create mode 100644 packages/website/public/images/logos/aragon.png create mode 100644 packages/website/public/images/logos/augur.png create mode 100644 packages/website/public/images/logos/blockchain_capital.png create mode 100644 packages/website/public/images/logos/chronobank.png create mode 100644 packages/website/public/images/logos/dharma.png create mode 100644 packages/website/public/images/logos/district0x.png create mode 100644 packages/website/public/images/logos/jen_advisors.png create mode 100644 packages/website/public/images/logos/maker.png create mode 100644 packages/website/public/images/logos/melonport.png create mode 100644 packages/website/public/images/logos/openANX.png create mode 100644 packages/website/public/images/logos/pantera_capital.png create mode 100644 packages/website/public/images/logos/polychain_capital.png create mode 100644 packages/website/public/images/og_image.png create mode 100644 packages/website/public/images/protocol_logo_black.png create mode 100644 packages/website/public/images/protocol_logo_white.png create mode 100644 packages/website/public/images/social/github.png create mode 100644 packages/website/public/images/social/medium.png create mode 100644 packages/website/public/images/social/reddit.png create mode 100644 packages/website/public/images/social/rocketchat.png create mode 100644 packages/website/public/images/social/slack.png create mode 100644 packages/website/public/images/social/twitter.png create mode 100644 packages/website/public/images/team/alex.jpg create mode 100644 packages/website/public/images/team/amir.jpeg create mode 100644 packages/website/public/images/team/anyone.png create mode 100644 packages/website/public/images/team/ben.jpg create mode 100644 packages/website/public/images/team/brandon.png create mode 100644 packages/website/public/images/team/fabio.jpg create mode 100644 packages/website/public/images/team/leonid.png create mode 100644 packages/website/public/images/team/philippe.png create mode 100644 packages/website/public/images/team/will.jpg create mode 100644 packages/website/public/images/token_icons/adtoken.png create mode 100644 packages/website/public/images/token_icons/aragon.png create mode 100644 packages/website/public/images/token_icons/augur.png create mode 100644 packages/website/public/images/token_icons/bancor.png create mode 100644 packages/website/public/images/token_icons/basicattentiontoken.png create mode 100644 packages/website/public/images/token_icons/bitquence.png create mode 100644 packages/website/public/images/token_icons/btc.png create mode 100644 packages/website/public/images/token_icons/civic.png create mode 100644 packages/website/public/images/token_icons/clams.png create mode 100644 packages/website/public/images/token_icons/cofound-it.png create mode 100644 packages/website/public/images/token_icons/default.png create mode 100644 packages/website/public/images/token_icons/digixdao.png create mode 100644 packages/website/public/images/token_icons/district0x.png create mode 100644 packages/website/public/images/token_icons/edgeless.png create mode 100644 packages/website/public/images/token_icons/eos.png create mode 100644 packages/website/public/images/token_icons/ether_erc20.png create mode 100644 packages/website/public/images/token_icons/etheroll.png create mode 100644 packages/website/public/images/token_icons/firstblood.jpg create mode 100644 packages/website/public/images/token_icons/funfair.png create mode 100644 packages/website/public/images/token_icons/gnosis.png create mode 100644 packages/website/public/images/token_icons/golem.png create mode 100644 packages/website/public/images/token_icons/iconomi.png create mode 100644 packages/website/public/images/token_icons/iexec.png create mode 100644 packages/website/public/images/token_icons/lunyr.png create mode 100644 packages/website/public/images/token_icons/makerdao.png create mode 100644 packages/website/public/images/token_icons/melon.png create mode 100644 packages/website/public/images/token_icons/metal.png create mode 100644 packages/website/public/images/token_icons/monaco.png create mode 100644 packages/website/public/images/token_icons/numeraire.png create mode 100644 packages/website/public/images/token_icons/omisego.png create mode 100644 packages/website/public/images/token_icons/qtum.png create mode 100644 packages/website/public/images/token_icons/santiment.png create mode 100644 packages/website/public/images/token_icons/singularity.png create mode 100644 packages/website/public/images/token_icons/status.png create mode 100644 packages/website/public/images/token_icons/storjcoinx.png create mode 100644 packages/website/public/images/token_icons/taas.png create mode 100644 packages/website/public/images/token_icons/tenx.png create mode 100644 packages/website/public/images/token_icons/tokencard.png create mode 100644 packages/website/public/images/token_icons/trust.png create mode 100644 packages/website/public/images/token_icons/wings.png create mode 100644 packages/website/public/images/token_icons/zero_ex.png create mode 100644 packages/website/public/images/trade_arrows.png create mode 100644 packages/website/public/images/zrx_pie_chart.png create mode 100644 packages/website/public/images/zrx_token.png create mode 100644 packages/website/public/index.html create mode 100644 packages/website/public/js/rollbar.umd.nojson.min.js create mode 100644 packages/website/public/pdfs/0x_white_paper.pdf create mode 100644 packages/website/public/videos/0xAnimation.mp4 create mode 100644 packages/website/ts/blockchain.ts create mode 100644 packages/website/ts/components/dialogs/blockchain_err_dialog.tsx create mode 100644 packages/website/ts/components/dialogs/eth_weth_conversion_dialog.tsx create mode 100644 packages/website/ts/components/dialogs/ledger_config_dialog.tsx create mode 100644 packages/website/ts/components/dialogs/portal_disclaimer_dialog.tsx create mode 100644 packages/website/ts/components/dialogs/send_dialog.tsx create mode 100644 packages/website/ts/components/dialogs/track_token_confirmation_dialog.tsx create mode 100644 packages/website/ts/components/dialogs/u2f_not_supported_dialog.tsx create mode 100644 packages/website/ts/components/eth_weth_conversion_button.tsx create mode 100644 packages/website/ts/components/fill_order.tsx create mode 100644 packages/website/ts/components/fill_order_json.tsx create mode 100644 packages/website/ts/components/fill_warning_dialog.tsx create mode 100644 packages/website/ts/components/flash_messages/token_send_completed.tsx create mode 100644 packages/website/ts/components/flash_messages/transaction_submitted.tsx create mode 100644 packages/website/ts/components/footer.tsx create mode 100644 packages/website/ts/components/generate_order/asset_picker.tsx create mode 100644 packages/website/ts/components/generate_order/generate_order_form.tsx create mode 100644 packages/website/ts/components/generate_order/new_token_form.tsx create mode 100644 packages/website/ts/components/inputs/address_input.tsx create mode 100644 packages/website/ts/components/inputs/allowance_toggle.tsx create mode 100644 packages/website/ts/components/inputs/balance_bounded_input.tsx create mode 100644 packages/website/ts/components/inputs/eth_amount_input.tsx create mode 100644 packages/website/ts/components/inputs/expiration_input.tsx create mode 100644 packages/website/ts/components/inputs/hash_input.tsx create mode 100644 packages/website/ts/components/inputs/identicon_address_input.tsx create mode 100644 packages/website/ts/components/inputs/token_amount_input.tsx create mode 100644 packages/website/ts/components/inputs/token_input.tsx create mode 100644 packages/website/ts/components/order_json.tsx create mode 100644 packages/website/ts/components/portal.tsx create mode 100644 packages/website/ts/components/portal_menu.tsx create mode 100644 packages/website/ts/components/send_button.tsx create mode 100644 packages/website/ts/components/token_balances.tsx create mode 100644 packages/website/ts/components/top_bar.tsx create mode 100644 packages/website/ts/components/top_bar_menu_item.tsx create mode 100644 packages/website/ts/components/track_token_confirmation.tsx create mode 100644 packages/website/ts/components/trade_history/trade_history.tsx create mode 100644 packages/website/ts/components/trade_history/trade_history_item.tsx create mode 100644 packages/website/ts/components/ui/alert.tsx create mode 100644 packages/website/ts/components/ui/badge.tsx create mode 100644 packages/website/ts/components/ui/copy_icon.tsx create mode 100644 packages/website/ts/components/ui/drop_down_menu_item.tsx create mode 100644 packages/website/ts/components/ui/ethereum_address.tsx create mode 100644 packages/website/ts/components/ui/etherscan_icon.tsx create mode 100644 packages/website/ts/components/ui/fake_text_field.tsx create mode 100644 packages/website/ts/components/ui/flash_message.tsx create mode 100644 packages/website/ts/components/ui/help_tooltip.tsx create mode 100644 packages/website/ts/components/ui/identicon.tsx create mode 100644 packages/website/ts/components/ui/input_label.tsx create mode 100644 packages/website/ts/components/ui/labeled_switcher.tsx create mode 100644 packages/website/ts/components/ui/lifecycle_raised_button.tsx create mode 100644 packages/website/ts/components/ui/loading.tsx create mode 100644 packages/website/ts/components/ui/menu_item.tsx create mode 100644 packages/website/ts/components/ui/party.tsx create mode 100644 packages/website/ts/components/ui/required_label.tsx create mode 100644 packages/website/ts/components/ui/simple_loading.tsx create mode 100644 packages/website/ts/components/ui/swap_icon.tsx create mode 100644 packages/website/ts/components/ui/token_icon.tsx create mode 100644 packages/website/ts/components/visual_order.tsx create mode 100644 packages/website/ts/containers/generate_order_form.tsx create mode 100644 packages/website/ts/containers/portal.tsx create mode 100644 packages/website/ts/containers/smart_contracts_documentation.tsx create mode 100644 packages/website/ts/containers/zero_ex_js_documentation.tsx create mode 100644 packages/website/ts/globals.d.ts create mode 100644 packages/website/ts/index.tsx create mode 100644 packages/website/ts/lazy_component.tsx create mode 100644 packages/website/ts/local_storage/local_storage.ts create mode 100644 packages/website/ts/local_storage/tracked_token_storage.ts create mode 100644 packages/website/ts/local_storage/trade_history_storage.tsx create mode 100644 packages/website/ts/pages/about/about.tsx create mode 100644 packages/website/ts/pages/about/profile.tsx create mode 100644 packages/website/ts/pages/documentation/comment.tsx create mode 100644 packages/website/ts/pages/documentation/custom_enum.tsx create mode 100644 packages/website/ts/pages/documentation/enum.tsx create mode 100644 packages/website/ts/pages/documentation/event_definition.tsx create mode 100644 packages/website/ts/pages/documentation/interface.tsx create mode 100644 packages/website/ts/pages/documentation/method_block.tsx create mode 100644 packages/website/ts/pages/documentation/method_signature.tsx create mode 100644 packages/website/ts/pages/documentation/smart_contracts_documentation.tsx create mode 100644 packages/website/ts/pages/documentation/source_link.tsx create mode 100644 packages/website/ts/pages/documentation/type.tsx create mode 100644 packages/website/ts/pages/documentation/type_definition.tsx create mode 100644 packages/website/ts/pages/documentation/zero_ex_js_documentation.tsx create mode 100644 packages/website/ts/pages/faq/faq.tsx create mode 100644 packages/website/ts/pages/faq/question.tsx create mode 100644 packages/website/ts/pages/landing/landing.tsx create mode 100644 packages/website/ts/pages/not_found.tsx create mode 100644 packages/website/ts/pages/shared/anchor_title.tsx create mode 100644 packages/website/ts/pages/shared/markdown_code_block.tsx create mode 100644 packages/website/ts/pages/shared/markdown_section.tsx create mode 100644 packages/website/ts/pages/shared/nested_sidebar_menu.tsx create mode 100644 packages/website/ts/pages/shared/section_header.tsx create mode 100644 packages/website/ts/pages/shared/version_drop_down.tsx create mode 100644 packages/website/ts/pages/wiki/wiki.tsx create mode 100644 packages/website/ts/redux/dispatcher.ts create mode 100644 packages/website/ts/redux/reducer.ts create mode 100644 packages/website/ts/schemas/order_schema.ts create mode 100644 packages/website/ts/schemas/order_taker_schema.ts create mode 100644 packages/website/ts/schemas/signature_data_schema.ts create mode 100644 packages/website/ts/schemas/token_schema.ts create mode 100644 packages/website/ts/schemas/validator.ts create mode 100644 packages/website/ts/subproviders/injected_web3_subprovider.ts create mode 100644 packages/website/ts/subproviders/ledger_wallet_subprovider_factory.ts create mode 100644 packages/website/ts/subproviders/redundant_rpc_subprovider.ts create mode 100644 packages/website/ts/types.ts create mode 100644 packages/website/ts/utils/configs.ts create mode 100644 packages/website/ts/utils/constants.ts create mode 100644 packages/website/ts/utils/doc_utils.ts create mode 100644 packages/website/ts/utils/doxity_utils.ts create mode 100644 packages/website/ts/utils/error_reporter.ts create mode 100644 packages/website/ts/utils/typedoc_utils.ts create mode 100644 packages/website/ts/utils/utils.ts create mode 100644 packages/website/ts/vendor/u2f_api.js create mode 100644 packages/website/ts/web3_wrapper.ts create mode 100644 packages/website/tsconfig.json create mode 100644 packages/website/tslint.json create mode 100644 packages/website/webpack.config.js diff --git a/.gitignore b/.gitignore index 17219f0d9..b461f33de 100644 --- a/.gitignore +++ b/.gitignore @@ -64,3 +64,5 @@ _bundles # generated documentation docs/ + +TODO.md diff --git a/packages/website/README.md b/packages/website/README.md new file mode 100644 index 000000000..40df07cb7 --- /dev/null +++ b/packages/website/README.md @@ -0,0 +1,60 @@ + + +--- + +[0x][website-url] is an open protocol that facilitates trustless, low friction exchange of Ethereum-based assets. A full description of the protocol may be found in our [whitepaper][whitepaper-url]. + +This repository contains our website and [0x Portal DApp][portal-url] (over-the-counter exchange), facilitating trustless over-the-counter trading of Ethereum-based tokens using 0x protocol. + +[website-url]: https://0xproject.com/ +[whitepaper-url]: https://0xproject.com/pdfs/0x_white_paper.pdf +[portal-url]: https://0xproject.com/portal + +[![Join the chat at https://gitter.im/0xProject/contracts](https://badges.gitter.im/0xProject/contracts.svg)](https://gitter.im/0xProject/contracts?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) +[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) + +### Local Dev Setup + +Requires Node version 6.9.5 or higher. + +Add the following to your `/etc/hosts` file: + +``` +127.0.0.1 0xproject.dev +``` + +Clone the [0x contracts repo](https://github.com/0xProject/contracts) into the same parent directory as this project. + +Install [yarn](https://yarnpkg.com/lang/en/docs/install/) in order to install the project dependencies more deterministically. + +Install dependencies: + +``` +yarn +``` + +Import smart contract artifacts from `contracts` repo: + +``` +yarn run update_contracts +``` + +Start dev server: + +``` +yarn run dev +``` + +Visit [0xproject.dev:3572](http://0xproject.dev:3572) in your browser. + + +##### Recommended Atom packages: + +- [atom-typescript](https://atom.io/packages/atom-typescript) +- [linter-tslint](https://atom.io/packages/linter-tslint) + +##### Resources + +- [Material Design Icon Font](http://zavoloklom.github.io/material-design-iconic-font/icons.html#directional) +- [BassCSS toolkit](http://basscss.com/) +- [Material-UI](http://www.material-ui.com/#/) diff --git a/packages/website/contracts/Mintable.json b/packages/website/contracts/Mintable.json new file mode 100644 index 000000000..52dc7507f --- /dev/null +++ b/packages/website/contracts/Mintable.json @@ -0,0 +1,189 @@ +{ + "contract_name": "Mintable", + "abi": [ + { + "constant": false, + "inputs": [ + { + "name": "_spender", + "type": "address" + }, + { + "name": "_value", + "type": "uint256" + } + ], + "name": "approve", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "totalSupply", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_from", + "type": "address" + }, + { + "name": "_to", + "type": "address" + }, + { + "name": "_value", + "type": "uint256" + } + ], + "name": "transferFrom", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "_owner", + "type": "address" + } + ], + "name": "balanceOf", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_value", + "type": "uint256" + } + ], + "name": "mint", + "outputs": [], + "payable": false, + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_to", + "type": "address" + }, + { + "name": "_value", + "type": "uint256" + } + ], + "name": "transfer", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "_owner", + "type": "address" + }, + { + "name": "_spender", + "type": "address" + } + ], + "name": "allowance", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "type": "function" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "_from", + "type": "address" + }, + { + "indexed": true, + "name": "_to", + "type": "address" + }, + { + "indexed": false, + "name": "_value", + "type": "uint256" + } + ], + "name": "Transfer", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "_owner", + "type": "address" + }, + { + "indexed": true, + "name": "_spender", + "type": "address" + }, + { + "indexed": false, + "name": "_value", + "type": "uint256" + } + ], + "name": "Approval", + "type": "event" + } + ], + "unlinked_binary": "0x6060604052341561000c57fe5b5b6105018061001c6000396000f300606060405236156100675763ffffffff60e060020a600035041663095ea7b3811461006957806318160ddd1461009c57806323b872dd146100be57806370a08231146100f7578063a0712d6814610125578063a9059cbb1461013a578063dd62ed3e1461016d575bfe5b341561007157fe5b610088600160a060020a03600435166024356101a1565b604080519115158252519081900360200190f35b34156100a457fe5b6100ac61020c565b60408051918252519081900360200190f35b34156100c657fe5b610088600160a060020a0360043581169060243516604435610212565b604080519115158252519081900360200190f35b34156100ff57fe5b6100ac600160a060020a0360043516610335565b60408051918252519081900360200190f35b341561012d57fe5b610138600435610354565b005b341561014257fe5b610088600160a060020a03600435166024356103bc565b604080519115158252519081900360200190f35b341561017557fe5b6100ac600160a060020a036004358116906024351661046e565b60408051918252519081900360200190f35b600160a060020a03338116600081815260016020908152604080832094871680845294825280832086905580518681529051929493927f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925929181900390910190a35060015b92915050565b60025481565b600160a060020a03808416600081815260016020908152604080832033909516835293815283822054928252819052918220548390108015906102555750828110155b801561027b5750600160a060020a03841660009081526020819052604090205483810110155b1561032757600160a060020a03808516600090815260208190526040808220805487019055918716815220805484900390556000198110156102e557600160a060020a03808616600090815260016020908152604080832033909416835292905220805484900390555b83600160a060020a031685600160a060020a03166000805160206104b6833981519152856040518082815260200191505060405180910390a36001915061032c565b600091505b5b509392505050565b600160a060020a0381166000908152602081905260409020545b919050565b68056bc75e2d6310000081111561036b5760006000fd5b600160a060020a03331660009081526020819052604090205461038f90829061049b565b600160a060020a0333166000908152602081905260409020556002546103b5908261049b565b6002555b50565b600160a060020a0333166000908152602081905260408120548290108015906103ff5750600160a060020a03831660009081526020819052604090205482810110155b1561045f57600160a060020a0333811660008181526020818152604080832080548890039055938716808352918490208054870190558351868152935191936000805160206104b6833981519152929081900390910190a3506001610206565b506000610206565b5b92915050565b600160a060020a038083166000908152600160209081526040808320938516835292905220545b92915050565b6000828201838110156104aa57fe5b8091505b50929150505600ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa165627a7a72305820998c8326b9629e063eb4867166e72c68a8c2e3ebca6a9d35ebc78c041c7aa47b0029", + "networks": {}, + "schema_version": "0.0.5", + "updated_at": 1503413048892 +} \ No newline at end of file diff --git a/packages/website/less/all.less b/packages/website/less/all.less new file mode 100644 index 000000000..5212df959 --- /dev/null +++ b/packages/website/less/all.less @@ -0,0 +1,136 @@ +body { + font-family: 'Roboto'; +} + +.robotoMono { + font-family: 'Roboto Mono'; +} + +a { + color: black; +} + +#faq { + li { + padding-bottom: 5px; + } + + a { + color: rgb(66, 66, 66); + } +} + +#landing { + .h1, .h2, .h3, .h4 { + font-family: 'Roboto Mono'; + } +} + + +#portal { + h1, h2, h3, h4 { + font-weight: 100; + } +} + +#documentation { + p { + line-height: 1.5; + } + + .comment { + p { + margin: 0px; + } + } + + .typeTooltip { + border: 1px solid lightgray; + opacity: 1; + } +} + +/* + * Adds always visible scrollbars on OSX so that user knows the content is scrollable + * Source: https://davidwalsh.name/osx-overflow + */ +::-webkit-scrollbar { + -webkit-appearance: none; + width: 7px; +} +::-webkit-scrollbar-thumb { + border-radius: 4px; + background-color: rgba(0, 0, 0, .5); + -webkit-box-shadow: 0 0 1px rgba(255, 255, 255, .5); +} + +// Hack: For some reason the animation applied to the material-ui textfield causes the overflow +// applied to other elements to fail while the animation is underway. Adding this class to the +// affected component fixes the issue +// Source: http://stackoverflow.com/questions/14383632/webkit-border-radius-and-overflow-bug-when-using-any-animation-transition +.transitionFix { + -webkit-backface-visibility: hidden; + -moz-backface-visibility: hidden; + -webkit-transform: translate3d(0, 0, 0); + -moz-transform: translate3d(0, 0, 0); +} + +.thin { + font-weight: 100; +} + +code { + font-family: 'Roboto'; + background-color: #f3f4f4; + color: rgb(36, 41, 46); + padding: 3px; + + &.hljs { + background-color: #dde4e9 !important; // blue gray + border-left: 5px solid #0091EA !important; // colors.lightBlueA700 + padding: 30px; + } +} + +#wiki { + p, blockquote, ul, ol, dl, li, table, pre { + margin: 15px 0; + } + + ol, ul { + padding-bottom: 20px; + } + + table { + padding: 0; + border-collapse: collapse; + } + table tr { + border-top: 1px solid #cccccc; + background-color: white; + margin: 0; + padding: 0; + } + table tr:nth-child(2n) { + background-color: #f8f8f8; + } + table tr th { + font-weight: bold; + border: 1px solid #cccccc; + text-align: left; + margin: 0; + padding: 6px 13px; + } + table tr td { + border: 1px solid #cccccc; + text-align: left; + margin: 0; + padding: 6px 13px; + } + table tr th :first-child, table tr td :first-child { + margin-top: 0; + } + table tr th :last-child, table tr td :last-child { + margin-bottom: 0; + } +} diff --git a/packages/website/package.json b/packages/website/package.json new file mode 100644 index 000000000..ba71ee0eb --- /dev/null +++ b/packages/website/package.json @@ -0,0 +1,105 @@ +{ + "name": "website", + "version": "0.0.0", + "description": "Website and 0x portal dapp", + "scripts": { + "build": "NODE_ENV=production webpack", + "clean": "shx rm -f public/bundle*", + "dev": "webpack-dev-server --content-base public --https", + "update_contracts": "for i in ${npm_package_config_artifacts}; do copyfiles -u 4 ../contracts/build/contracts/$i.json ../website/contracts; done;", + "deploy_staging": "npm run build; aws s3 sync ./public/. s3://staging-0xproject --profile 0xproject --region us-east-1 --grants read=uri=http://acs.amazonaws.com/groups/global/AllUsers", + "deploy_live": "npm run build; aws s3 sync ./public/. s3://0xproject.com --profile 0xproject --region us-east-1 --grants read=uri=http://acs.amazonaws.com/groups/global/AllUsers" + }, + "config": { + "artifacts": "Mintable" + }, + "author": "Fabio Berger", + "license": "Apache-2.0", + "dependencies": { + "accounting": "^0.4.1", + "basscss": "^8.0.3", + "bignumber.js": "~4.1.0", + "blockies": "^0.0.2", + "compare-versions": "^3.0.1", + "dateformat": "^2.0.0", + "deep-equal": "^1.0.1", + "dharma-loan-frame": "^0.0.12", + "es6-promisify": "^5.0.0", + "ethereum-address": "^0.0.4", + "ethereumjs-tx": "^1.3.3", + "ethereumjs-util": "^5.1.1", + "find-versions": "^2.0.0", + "is-mobile": "^0.2.2", + "jsonschema": "^1.1.1", + "ledgerco": "0xProject/ledger-node-js-api", + "less": "^2.7.2", + "lodash": "^4.17.4", + "material-ui": "^0.17.1", + "moment": "^2.18.1", + "query-string": "^5.0.0", + "react": "15.6.1", + "react-copy-to-clipboard": "^4.2.3", + "react-document-title": "^2.0.3", + "react-dom": "15.6.1", + "react-highlight": "^0.10.0", + "react-html5video": "^2.1.0", + "react-inlinesvg": "^0.5.5", + "react-markdown": "^2.5.0", + "react-recaptcha": "^2.3.2", + "react-redux": "^5.0.3", + "react-router-dom": "^4.1.1", + "react-router-hash-link": "^1.1.0", + "react-scroll": "^1.5.2", + "react-tap-event-plugin": "^2.0.1", + "react-tooltip": "^3.2.7", + "react-waypoint": "^7.0.4", + "redux": "^3.6.0", + "scroll-to-element": "^2.0.0", + "semver-sort": "0.0.4", + "thenby": "^1.2.3", + "truffle-contract": "2.0.1", + "tslint-config-0xproject": "^0.0.2", + "typescript": "^2.4.1", + "web3": "^0.20.0", + "web3-provider-engine": "^11.0.0", + "whatwg-fetch": "^2.0.3", + "xml-js": "^1.3.2" + }, + "devDependencies": { + "@types/accounting": "^0.4.1", + "@types/dateformat": "^1.0.1", + "@types/deep-equal": "^1.0.0", + "@types/es6-promise": "0.0.32", + "@types/jsonschema": "^1.1.1", + "@types/lodash": "^4.14.55", + "@types/material-ui": "0.18.0", + "@types/moment": "^2.13.0", + "@types/node": "^7.0.8", + "@types/query-string": "^5.0.0", + "@types/react": "^15.0.15", + "@types/react-copy-to-clipboard": "^4.2.0", + "@types/react-dom": "^0.14.23", + "@types/react-redux": "^4.4.37", + "@types/react-router-dom": "^4.0.4", + "@types/react-scroll": "0.0.31", + "@types/react-tap-event-plugin": "0.0.30", + "@types/redux": "^3.6.0", + "awesome-typescript-loader": "^3.1.3", + "copy-webpack-plugin": "^4.0.1", + "copyfiles": "^1.2.0", + "css-loader": "0.23.x", + "exports-loader": "0.6.x", + "imports-loader": "0.6.x", + "json-loader": "^0.5.4", + "less-loader": "^2.2.3", + "raw-loader": "^0.5.1", + "shx": "^0.2.2", + "source-map-loader": "^0.1.6", + "style-loader": "0.13.x", + "tslint": "5.8.0", + "web3-typescript-typings": "^0.7.1", + "webpack": "3.1.0", + "webpack-dev-middleware": "^1.10.0", + "webpack-dev-server": "^2.5.0" + } +} diff --git a/packages/website/public/css/atom-one-light.css b/packages/website/public/css/atom-one-light.css new file mode 100644 index 000000000..d5bd1d2a9 --- /dev/null +++ b/packages/website/public/css/atom-one-light.css @@ -0,0 +1,96 @@ +/* + +Atom One Light by Daniel Gamage +Original One Light Syntax theme from https://github.com/atom/one-light-syntax + +base: #fafafa +mono-1: #383a42 +mono-2: #686b77 +mono-3: #a0a1a7 +hue-1: #0184bb +hue-2: #4078f2 +hue-3: #a626a4 +hue-4: #50a14f +hue-5: #e45649 +hue-5-2: #c91243 +hue-6: #986801 +hue-6-2: #c18401 + +*/ + +.hljs { + display: block; + overflow-x: auto; + padding: 0.5em; + color: #383a42; + background: #fafafa; +} + +.hljs-comment, +.hljs-quote { + color: #a0a1a7; + font-style: italic; +} + +.hljs-doctag, +.hljs-keyword, +.hljs-formula { + color: #a626a4; +} + +.hljs-section, +.hljs-name, +.hljs-selector-tag, +.hljs-deletion, +.hljs-subst { + color: #e45649; +} + +.hljs-literal { + color: #0184bb; +} + +.hljs-string, +.hljs-regexp, +.hljs-addition, +.hljs-attribute, +.hljs-meta-string { + color: #50a14f; +} + +.hljs-built_in, +.hljs-class .hljs-title { + color: #c18401; +} + +.hljs-attr, +.hljs-variable, +.hljs-template-variable, +.hljs-type, +.hljs-selector-class, +.hljs-selector-attr, +.hljs-selector-pseudo, +.hljs-number { + color: #986801; +} + +.hljs-symbol, +.hljs-bullet, +.hljs-link, +.hljs-meta, +.hljs-selector-id, +.hljs-title { + color: #4078f2; +} + +.hljs-emphasis { + font-style: italic; +} + +.hljs-strong { + font-weight: bold; +} + +.hljs-link { + text-decoration: underline; +} diff --git a/packages/website/public/css/basscss_responsive_custom.css b/packages/website/public/css/basscss_responsive_custom.css new file mode 100644 index 000000000..c2e802125 --- /dev/null +++ b/packages/website/public/css/basscss_responsive_custom.css @@ -0,0 +1,58 @@ +/* Custom Basscss Responsive Utilities */ + +@media (max-width: 52em) { + .sm-center { + text-align: center; + } + .sm-left-align { + text-align: left; + } + .sm-right-align { + text-align: right; + } + .sm-mx-auto { + margin-left: auto; + margin-right: auto; + } + .sm-right { + float: right; + } +} + +@media (min-width: 52em) { + .md-center { + text-align: center; + } + .md-left-align { + text-align: left; + } + .md-right-align { + text-align: right; + } + .md-mx-auto { + margin-left: auto; + margin-right: auto; + } + .md-right { + float: right; + } +} + +@media (min-width: 64em) { + .lg-center { + text-align: center; + } + .lg-left-align { + text-align: left; + } + .lg-right-align { + text-align: right; + } + .lg-mx-auto { + margin-left: auto; + margin-right: auto; + } + .lg-right { + float: right; + } +} diff --git a/packages/website/public/css/basscss_responsive_margin.css b/packages/website/public/css/basscss_responsive_margin.css new file mode 100644 index 000000000..b601bd491 --- /dev/null +++ b/packages/website/public/css/basscss_responsive_margin.css @@ -0,0 +1,160 @@ +/* Basscss Responsive Margin */ + +@media (max-width: 52em) { /* Modified by Fabio Berger to max-width from min-width */ + + .sm-m0 { margin: 0 } + .sm-mt0 { margin-top: 0 } + .sm-mr0 { margin-right: 0 } + .sm-mb0 { margin-bottom: 0 } + .sm-ml0 { margin-left: 0 } + .sm-mx0 { margin-left: 0; margin-right: 0 } + .sm-my0 { margin-top: 0; margin-bottom: 0 } + + .sm-m1 { margin: .5rem } + .sm-mt1 { margin-top: .5rem } + .sm-mr1 { margin-right: .5rem } + .sm-mb1 { margin-bottom: .5rem } + .sm-ml1 { margin-left: .5rem } + .sm-mx1 { margin-left: .5rem; margin-right: .5rem } + .sm-my1 { margin-top: .5rem; margin-bottom: .5rem } + + .sm-m2 { margin: 1rem } + .sm-mt2 { margin-top: 1rem } + .sm-mr2 { margin-right: 1rem } + .sm-mb2 { margin-bottom: 1rem } + .sm-ml2 { margin-left: 1rem } + .sm-mx2 { margin-left: 1rem; margin-right: 1rem } + .sm-my2 { margin-top: 1rem; margin-bottom: 1rem } + + .sm-m3 { margin: 2rem } + .sm-mt3 { margin-top: 2rem } + .sm-mr3 { margin-right: 2rem } + .sm-mb3 { margin-bottom: 2rem } + .sm-ml3 { margin-left: 2rem } + .sm-mx3 { margin-left: 2rem; margin-right: 2rem } + .sm-my3 { margin-top: 2rem; margin-bottom: 2rem } + + .sm-m4 { margin: 4rem } + .sm-mt4 { margin-top: 4rem } + .sm-mr4 { margin-right: 4rem } + .sm-mb4 { margin-bottom: 4rem } + .sm-ml4 { margin-left: 4rem } + .sm-mx4 { margin-left: 4rem; margin-right: 4rem } + .sm-my4 { margin-top: 4rem; margin-bottom: 4rem } + + .sm-mxn1 { margin-left: -.5rem; margin-right: -.5rem } + .sm-mxn2 { margin-left: -1rem; margin-right: -1rem } + .sm-mxn3 { margin-left: -2rem; margin-right: -2rem } + .sm-mxn4 { margin-left: -4rem; margin-right: -4rem } + + .sm-ml-auto { margin-left: auto } + .sm-mr-auto { margin-right: auto } + .sm-mx-auto { margin-left: auto; margin-right: auto } + +} + +@media (min-width: 52em) { + + .md-m0 { margin: 0 } + .md-mt0 { margin-top: 0 } + .md-mr0 { margin-right: 0 } + .md-mb0 { margin-bottom: 0 } + .md-ml0 { margin-left: 0 } + .md-mx0 { margin-left: 0; margin-right: 0 } + .md-my0 { margin-top: 0; margin-bottom: 0 } + + .md-m1 { margin: .5rem } + .md-mt1 { margin-top: .5rem } + .md-mr1 { margin-right: .5rem } + .md-mb1 { margin-bottom: .5rem } + .md-ml1 { margin-left: .5rem } + .md-mx1 { margin-left: .5rem; margin-right: .5rem } + .md-my1 { margin-top: .5rem; margin-bottom: .5rem } + + .md-m2 { margin: 1rem } + .md-mt2 { margin-top: 1rem } + .md-mr2 { margin-right: 1rem } + .md-mb2 { margin-bottom: 1rem } + .md-ml2 { margin-left: 1rem } + .md-mx2 { margin-left: 1rem; margin-right: 1rem } + .md-my2 { margin-top: 1rem; margin-bottom: 1rem } + + .md-m3 { margin: 2rem } + .md-mt3 { margin-top: 2rem } + .md-mr3 { margin-right: 2rem } + .md-mb3 { margin-bottom: 2rem } + .md-ml3 { margin-left: 2rem } + .md-mx3 { margin-left: 2rem; margin-right: 2rem } + .md-my3 { margin-top: 2rem; margin-bottom: 2rem } + + .md-m4 { margin: 4rem } + .md-mt4 { margin-top: 4rem } + .md-mr4 { margin-right: 4rem } + .md-mb4 { margin-bottom: 4rem } + .md-ml4 { margin-left: 4rem } + .md-mx4 { margin-left: 4rem; margin-right: 4rem } + .md-my4 { margin-top: 4rem; margin-bottom: 4rem } + + .md-mxn1 { margin-left: -.5rem; margin-right: -.5rem; } + .md-mxn2 { margin-left: -1rem; margin-right: -1rem; } + .md-mxn3 { margin-left: -2rem; margin-right: -2rem; } + .md-mxn4 { margin-left: -4rem; margin-right: -4rem; } + + .md-ml-auto { margin-left: auto } + .md-mr-auto { margin-right: auto } + .md-mx-auto { margin-left: auto; margin-right: auto; } + +} + +@media (min-width: 64em) { + + .lg-m0 { margin: 0 } + .lg-mt0 { margin-top: 0 } + .lg-mr0 { margin-right: 0 } + .lg-mb0 { margin-bottom: 0 } + .lg-ml0 { margin-left: 0 } + .lg-mx0 { margin-left: 0; margin-right: 0 } + .lg-my0 { margin-top: 0; margin-bottom: 0 } + + .lg-m1 { margin: .5rem } + .lg-mt1 { margin-top: .5rem } + .lg-mr1 { margin-right: .5rem } + .lg-mb1 { margin-bottom: .5rem } + .lg-ml1 { margin-left: .5rem } + .lg-mx1 { margin-left: .5rem; margin-right: .5rem } + .lg-my1 { margin-top: .5rem; margin-bottom: .5rem } + + .lg-m2 { margin: 1rem } + .lg-mt2 { margin-top: 1rem } + .lg-mr2 { margin-right: 1rem } + .lg-mb2 { margin-bottom: 1rem } + .lg-ml2 { margin-left: 1rem } + .lg-mx2 { margin-left: 1rem; margin-right: 1rem } + .lg-my2 { margin-top: 1rem; margin-bottom: 1rem } + + .lg-m3 { margin: 2rem } + .lg-mt3 { margin-top: 2rem } + .lg-mr3 { margin-right: 2rem } + .lg-mb3 { margin-bottom: 2rem } + .lg-ml3 { margin-left: 2rem } + .lg-mx3 { margin-left: 2rem; margin-right: 2rem } + .lg-my3 { margin-top: 2rem; margin-bottom: 2rem } + + .lg-m4 { margin: 4rem } + .lg-mt4 { margin-top: 4rem } + .lg-mr4 { margin-right: 4rem } + .lg-mb4 { margin-bottom: 4rem } + .lg-ml4 { margin-left: 4rem } + .lg-mx4 { margin-left: 4rem; margin-right: 4rem } + .lg-my4 { margin-top: 4rem; margin-bottom: 4rem } + + .lg-mxn1 { margin-left: -.5rem; margin-right: -.5rem; } + .lg-mxn2 { margin-left: -1rem; margin-right: -1rem; } + .lg-mxn3 { margin-left: -2rem; margin-right: -2rem; } + .lg-mxn4 { margin-left: -4rem; margin-right: -4rem; } + + .lg-ml-auto { margin-left: auto } + .lg-mr-auto { margin-right: auto } + .lg-mx-auto { margin-left: auto; margin-right: auto; } + +} diff --git a/packages/website/public/css/basscss_responsive_padding.css b/packages/website/public/css/basscss_responsive_padding.css new file mode 100644 index 000000000..e027c2d65 --- /dev/null +++ b/packages/website/public/css/basscss_responsive_padding.css @@ -0,0 +1,134 @@ +/* Basscss Responsive Padding */ +/* Modified by Fabio Berger to include xs prefix */ + +@media (max-width: 52em) { /* Modified by Fabio Berger to max-width from min-width */ + + .sm-p0 { padding: 0 } + .sm-pt0 { padding-top: 0 } + .sm-pr0 { padding-right: 0 } + .sm-pb0 { padding-bottom: 0 } + .sm-pl0 { padding-left: 0 } + .sm-px0 { padding-left: 0; padding-right: 0 } + .sm-py0 { padding-top: 0; padding-bottom: 0 } + + .sm-p1 { padding: .5rem } + .sm-pt1 { padding-top: .5rem } + .sm-pr1 { padding-right: .5rem } + .sm-pb1 { padding-bottom: .5rem } + .sm-pl1 { padding-left: .5rem } + .sm-px1 { padding-left: .5rem; padding-right: .5rem } + .sm-py1 { padding-top: .5rem; padding-bottom: .5rem } + + .sm-p2 { padding: 1rem } + .sm-pt2 { padding-top: 1rem } + .sm-pr2 { padding-right: 1rem } + .sm-pb2 { padding-bottom: 1rem } + .sm-pl2 { padding-left: 1rem } + .sm-px2 { padding-left: 1rem; padding-right: 1rem } + .sm-py2 { padding-top: 1rem; padding-bottom: 1rem } + + .sm-p3 { padding: 2rem } + .sm-pt3 { padding-top: 2rem } + .sm-pr3 { padding-right: 2rem } + .sm-pb3 { padding-bottom: 2rem } + .sm-pl3 { padding-left: 2rem } + .sm-px3 { padding-left: 2rem; padding-right: 2rem } + .sm-py3 { padding-top: 2rem; padding-bottom: 2rem } + + .sm-p4 { padding: 4rem } + .sm-pt4 { padding-top: 4rem } + .sm-pr4 { padding-right: 4rem } + .sm-pb4 { padding-bottom: 4rem } + .sm-pl4 { padding-left: 4rem } + .sm-px4 { padding-left: 4rem; padding-right: 4rem } + .sm-py4 { padding-top: 4rem; padding-bottom: 4rem } + +} + +@media (min-width: 52em) { + + .md-p0 { padding: 0 } + .md-pt0 { padding-top: 0 } + .md-pr0 { padding-right: 0 } + .md-pb0 { padding-bottom: 0 } + .md-pl0 { padding-left: 0 } + .md-px0 { padding-left: 0; padding-right: 0 } + .md-py0 { padding-top: 0; padding-bottom: 0 } + + .md-p1 { padding: .5rem } + .md-pt1 { padding-top: .5rem } + .md-pr1 { padding-right: .5rem } + .md-pb1 { padding-bottom: .5rem } + .md-pl1 { padding-left: .5rem } + .md-px1 { padding-left: .5rem; padding-right: .5rem } + .md-py1 { padding-top: .5rem; padding-bottom: .5rem } + + .md-p2 { padding: 1rem } + .md-pt2 { padding-top: 1rem } + .md-pr2 { padding-right: 1rem } + .md-pb2 { padding-bottom: 1rem } + .md-pl2 { padding-left: 1rem } + .md-px2 { padding-left: 1rem; padding-right: 1rem } + .md-py2 { padding-top: 1rem; padding-bottom: 1rem } + + .md-p3 { padding: 2rem } + .md-pt3 { padding-top: 2rem } + .md-pr3 { padding-right: 2rem } + .md-pb3 { padding-bottom: 2rem } + .md-pl3 { padding-left: 2rem } + .md-px3 { padding-left: 2rem; padding-right: 2rem } + .md-py3 { padding-top: 2rem; padding-bottom: 2rem } + + .md-p4 { padding: 4rem } + .md-pt4 { padding-top: 4rem } + .md-pr4 { padding-right: 4rem } + .md-pb4 { padding-bottom: 4rem } + .md-pl4 { padding-left: 4rem } + .md-px4 { padding-left: 4rem; padding-right: 4rem } + .md-py4 { padding-top: 4rem; padding-bottom: 4rem } + +} + +@media (min-width: 64em) { + + .lg-p0 { padding: 0 } + .lg-pt0 { padding-top: 0 } + .lg-pr0 { padding-right: 0 } + .lg-pb0 { padding-bottom: 0 } + .lg-pl0 { padding-left: 0 } + .lg-px0 { padding-left: 0; padding-right: 0 } + .lg-py0 { padding-top: 0; padding-bottom: 0 } + + .lg-p1 { padding: .5rem } + .lg-pt1 { padding-top: .5rem } + .lg-pr1 { padding-right: .5rem } + .lg-pb1 { padding-bottom: .5rem } + .lg-pl1 { padding-left: .5rem } + .lg-px1 { padding-left: .5rem; padding-right: .5rem } + .lg-py1 { padding-top: .5rem; padding-bottom: .5rem } + + .lg-p2 { padding: 1rem } + .lg-pt2 { padding-top: 1rem } + .lg-pr2 { padding-right: 1rem } + .lg-pb2 { padding-bottom: 1rem } + .lg-pl2 { padding-left: 1rem } + .lg-px2 { padding-left: 1rem; padding-right: 1rem } + .lg-py2 { padding-top: 1rem; padding-bottom: 1rem } + + .lg-p3 { padding: 2rem } + .lg-pt3 { padding-top: 2rem } + .lg-pr3 { padding-right: 2rem } + .lg-pb3 { padding-bottom: 2rem } + .lg-pl3 { padding-left: 2rem } + .lg-px3 { padding-left: 2rem; padding-right: 2rem } + .lg-py3 { padding-top: 2rem; padding-bottom: 2rem } + + .lg-p4 { padding: 4rem } + .lg-pt4 { padding-top: 4rem } + .lg-pr4 { padding-right: 4rem } + .lg-pb4 { padding-bottom: 4rem } + .lg-pl4 { padding-left: 4rem } + .lg-px4 { padding-left: 4rem; padding-right: 4rem } + .lg-py4 { padding-top: 4rem; padding-bottom: 4rem } + +} diff --git a/packages/website/public/css/basscss_responsive_type_scale.css b/packages/website/public/css/basscss_responsive_type_scale.css new file mode 100644 index 000000000..cae23b4e7 --- /dev/null +++ b/packages/website/public/css/basscss_responsive_type_scale.css @@ -0,0 +1,35 @@ +/* Basscss Responsive Type Scale */ +/* Modified by Fabio Berger to include xs prefix */ + +@media (max-width: 52em) { /* Modified by Fabio Berger to max-width from min-width */ + .sm-h00 { font-size: 4rem } + .sm-h0 { font-size: 3rem } + .sm-h1 { font-size: 2rem } + .sm-h2 { font-size: 1.5rem } + .sm-h3 { font-size: 1.25rem } + .sm-h4 { font-size: 1rem } + .sm-h5 { font-size: .875rem } + .sm-h6 { font-size: .75rem } +} + +@media (min-width: 52em) { + .md-h00 { font-size: 4rem } + .md-h0 { font-size: 3rem } + .md-h1 { font-size: 2rem } + .md-h2 { font-size: 1.5rem } + .md-h3 { font-size: 1.25rem } + .md-h4 { font-size: 1rem } + .md-h5 { font-size: .875rem } + .md-h6 { font-size: .75rem } +} + +@media (min-width: 64em) { + .lg-h00 { font-size: 4rem } + .lg-h0 { font-size: 3rem } + .lg-h1 { font-size: 2rem } + .lg-h2 { font-size: 1.5rem } + .lg-h3 { font-size: 1.25rem } + .lg-h4 { font-size: 1rem } + .lg-h5 { font-size: .875rem } + .lg-h6 { font-size: .75rem } +} diff --git a/packages/website/public/css/material-design-iconic-font.css b/packages/website/public/css/material-design-iconic-font.css new file mode 100755 index 000000000..81d090a8b --- /dev/null +++ b/packages/website/public/css/material-design-iconic-font.css @@ -0,0 +1,5166 @@ +/*! + * Material Design Iconic Font by Sergey Kupletsky (@zavoloklom) - http://zavoloklom.github.io/material-design-iconic-font/ + * License - http://zavoloklom.github.io/material-design-iconic-font/license (Font: SIL OFL 1.1, CSS: MIT License) + */ +@font-face { + font-family: 'Material-Design-Iconic-Font'; + src: url('../fonts/Material-Design-Iconic-Font.woff2?v=2.2.0') format('woff2'), url('../fonts/Material-Design-Iconic-Font.woff?v=2.2.0') format('woff'), url('../fonts/Material-Design-Iconic-Font.ttf?v=2.2.0') format('truetype'); + font-weight: normal; + font-style: normal; +} +.zmdi { + display: inline-block; + font: normal normal normal 14px/1 'Material-Design-Iconic-Font'; + font-size: inherit; + text-rendering: auto; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} +.zmdi-hc-lg { + font-size: 1.33333333em; + line-height: 0.75em; + vertical-align: -15%; +} +.zmdi-hc-2x { + font-size: 2em; +} +.zmdi-hc-3x { + font-size: 3em; +} +.zmdi-hc-4x { + font-size: 4em; +} +.zmdi-hc-5x { + font-size: 5em; +} +.zmdi-hc-fw { + width: 1.28571429em; + text-align: center; +} +.zmdi-hc-ul { + padding-left: 0; + margin-left: 2.14285714em; + list-style-type: none; +} +.zmdi-hc-ul > li { + position: relative; +} +.zmdi-hc-li { + position: absolute; + left: -2.14285714em; + width: 2.14285714em; + top: 0.14285714em; + text-align: center; +} +.zmdi-hc-li.zmdi-hc-lg { + left: -1.85714286em; +} +.zmdi-hc-border { + padding: .1em .25em; + border: solid 0.1em #9e9e9e; + border-radius: 2px; +} +.zmdi-hc-border-circle { + padding: .1em .25em; + border: solid 0.1em #9e9e9e; + border-radius: 50%; +} +.zmdi.pull-left { + float: left; + margin-right: .15em; +} +.zmdi.pull-right { + float: right; + margin-left: .15em; +} +.zmdi-hc-spin { + -webkit-animation: zmdi-spin 1.5s infinite linear; + animation: zmdi-spin 1.5s infinite linear; +} +.zmdi-hc-spin-reverse { + -webkit-animation: zmdi-spin-reverse 1.5s infinite linear; + animation: zmdi-spin-reverse 1.5s infinite linear; +} +@-webkit-keyframes zmdi-spin { + 0% { + -webkit-transform: rotate(0deg); + transform: rotate(0deg); + } + 100% { + -webkit-transform: rotate(359deg); + transform: rotate(359deg); + } +} +@keyframes zmdi-spin { + 0% { + -webkit-transform: rotate(0deg); + transform: rotate(0deg); + } + 100% { + -webkit-transform: rotate(359deg); + transform: rotate(359deg); + } +} +@-webkit-keyframes zmdi-spin-reverse { + 0% { + -webkit-transform: rotate(0deg); + transform: rotate(0deg); + } + 100% { + -webkit-transform: rotate(-359deg); + transform: rotate(-359deg); + } +} +@keyframes zmdi-spin-reverse { + 0% { + -webkit-transform: rotate(0deg); + transform: rotate(0deg); + } + 100% { + -webkit-transform: rotate(-359deg); + transform: rotate(-359deg); + } +} +.zmdi-hc-rotate-90 { + -webkit-transform: rotate(90deg); + -ms-transform: rotate(90deg); + transform: rotate(90deg); +} +.zmdi-hc-rotate-180 { + -webkit-transform: rotate(180deg); + -ms-transform: rotate(180deg); + transform: rotate(180deg); +} +.zmdi-hc-rotate-270 { + -webkit-transform: rotate(270deg); + -ms-transform: rotate(270deg); + transform: rotate(270deg); +} +.zmdi-hc-flip-horizontal { + -webkit-transform: scale(-1, 1); + -ms-transform: scale(-1, 1); + transform: scale(-1, 1); +} +.zmdi-hc-flip-vertical { + -webkit-transform: scale(1, -1); + -ms-transform: scale(1, -1); + transform: scale(1, -1); +} +.zmdi-hc-stack { + position: relative; + display: inline-block; + width: 2em; + height: 2em; + line-height: 2em; + vertical-align: middle; +} +.zmdi-hc-stack-1x, +.zmdi-hc-stack-2x { + position: absolute; + left: 0; + width: 100%; + text-align: center; +} +.zmdi-hc-stack-1x { + line-height: inherit; +} +.zmdi-hc-stack-2x { + font-size: 2em; +} +.zmdi-hc-inverse { + color: #ffffff; +} +/* Material Design Iconic Font uses the Unicode Private Use Area (PUA) to ensure screen + readers do not read off random characters that represent icons */ +.zmdi-3d-rotation:before { + content: '\f101'; +} +.zmdi-airplane-off:before { + content: '\f102'; +} +.zmdi-airplane:before { + content: '\f103'; +} +.zmdi-album:before { + content: '\f104'; +} +.zmdi-archive:before { + content: '\f105'; +} +.zmdi-assignment-account:before { + content: '\f106'; +} +.zmdi-assignment-alert:before { + content: '\f107'; +} +.zmdi-assignment-check:before { + content: '\f108'; +} +.zmdi-assignment-o:before { + content: '\f109'; +} +.zmdi-assignment-return:before { + content: '\f10a'; +} +.zmdi-assignment-returned:before { + content: '\f10b'; +} +.zmdi-assignment:before { + content: '\f10c'; +} +.zmdi-attachment-alt:before { + content: '\f10d'; +} +.zmdi-attachment:before { + content: '\f10e'; +} +.zmdi-audio:before { + content: '\f10f'; +} +.zmdi-badge-check:before { + content: '\f110'; +} +.zmdi-balance-wallet:before { + content: '\f111'; +} +.zmdi-balance:before { + content: '\f112'; +} +.zmdi-battery-alert:before { + content: '\f113'; +} +.zmdi-battery-flash:before { + content: '\f114'; +} +.zmdi-battery-unknown:before { + content: '\f115'; +} +.zmdi-battery:before { + content: '\f116'; +} +.zmdi-bike:before { + content: '\f117'; +} +.zmdi-block-alt:before { + content: '\f118'; +} +.zmdi-block:before { + content: '\f119'; +} +.zmdi-boat:before { + content: '\f11a'; +} +.zmdi-book-image:before { + content: '\f11b'; +} +.zmdi-book:before { + content: '\f11c'; +} +.zmdi-bookmark-outline:before { + content: '\f11d'; +} +.zmdi-bookmark:before { + content: '\f11e'; +} +.zmdi-brush:before { + content: '\f11f'; +} +.zmdi-bug:before { + content: '\f120'; +} +.zmdi-bus:before { + content: '\f121'; +} +.zmdi-cake:before { + content: '\f122'; +} +.zmdi-car-taxi:before { + content: '\f123'; +} +.zmdi-car-wash:before { + content: '\f124'; +} +.zmdi-car:before { + content: '\f125'; +} +.zmdi-card-giftcard:before { + content: '\f126'; +} +.zmdi-card-membership:before { + content: '\f127'; +} +.zmdi-card-travel:before { + content: '\f128'; +} +.zmdi-card:before { + content: '\f129'; +} +.zmdi-case-check:before { + content: '\f12a'; +} +.zmdi-case-download:before { + content: '\f12b'; +} +.zmdi-case-play:before { + content: '\f12c'; +} +.zmdi-case:before { + content: '\f12d'; +} +.zmdi-cast-connected:before { + content: '\f12e'; +} +.zmdi-cast:before { + content: '\f12f'; +} +.zmdi-chart-donut:before { + content: '\f130'; +} +.zmdi-chart:before { + content: '\f131'; +} +.zmdi-city-alt:before { + content: '\f132'; +} +.zmdi-city:before { + content: '\f133'; +} +.zmdi-close-circle-o:before { + content: '\f134'; +} +.zmdi-close-circle:before { + content: '\f135'; +} +.zmdi-close:before { + content: '\f136'; +} +.zmdi-cocktail:before { + content: '\f137'; +} +.zmdi-code-setting:before { + content: '\f138'; +} +.zmdi-code-smartphone:before { + content: '\f139'; +} +.zmdi-code:before { + content: '\f13a'; +} +.zmdi-coffee:before { + content: '\f13b'; +} +.zmdi-collection-bookmark:before { + content: '\f13c'; +} +.zmdi-collection-case-play:before { + content: '\f13d'; +} +.zmdi-collection-folder-image:before { + content: '\f13e'; +} +.zmdi-collection-image-o:before { + content: '\f13f'; +} +.zmdi-collection-image:before { + content: '\f140'; +} +.zmdi-collection-item-1:before { + content: '\f141'; +} +.zmdi-collection-item-2:before { + content: '\f142'; +} +.zmdi-collection-item-3:before { + content: '\f143'; +} +.zmdi-collection-item-4:before { + content: '\f144'; +} +.zmdi-collection-item-5:before { + content: '\f145'; +} +.zmdi-collection-item-6:before { + content: '\f146'; +} +.zmdi-collection-item-7:before { + content: '\f147'; +} +.zmdi-collection-item-8:before { + content: '\f148'; +} +.zmdi-collection-item-9-plus:before { + content: '\f149'; +} +.zmdi-collection-item-9:before { + content: '\f14a'; +} +.zmdi-collection-item:before { + content: '\f14b'; +} +.zmdi-collection-music:before { + content: '\f14c'; +} +.zmdi-collection-pdf:before { + content: '\f14d'; +} +.zmdi-collection-plus:before { + content: '\f14e'; +} +.zmdi-collection-speaker:before { + content: '\f14f'; +} +.zmdi-collection-text:before { + content: '\f150'; +} +.zmdi-collection-video:before { + content: '\f151'; +} +.zmdi-compass:before { + content: '\f152'; +} +.zmdi-cutlery:before { + content: '\f153'; +} +.zmdi-delete:before { + content: '\f154'; +} +.zmdi-dialpad:before { + content: '\f155'; +} +.zmdi-dns:before { + content: '\f156'; +} +.zmdi-drink:before { + content: '\f157'; +} +.zmdi-edit:before { + content: '\f158'; +} +.zmdi-email-open:before { + content: '\f159'; +} +.zmdi-email:before { + content: '\f15a'; +} +.zmdi-eye-off:before { + content: '\f15b'; +} +.zmdi-eye:before { + content: '\f15c'; +} +.zmdi-eyedropper:before { + content: '\f15d'; +} +.zmdi-favorite-outline:before { + content: '\f15e'; +} +.zmdi-favorite:before { + content: '\f15f'; +} +.zmdi-filter-list:before { + content: '\f160'; +} +.zmdi-fire:before { + content: '\f161'; +} +.zmdi-flag:before { + content: '\f162'; +} +.zmdi-flare:before { + content: '\f163'; +} +.zmdi-flash-auto:before { + content: '\f164'; +} +.zmdi-flash-off:before { + content: '\f165'; +} +.zmdi-flash:before { + content: '\f166'; +} +.zmdi-flip:before { + content: '\f167'; +} +.zmdi-flower-alt:before { + content: '\f168'; +} +.zmdi-flower:before { + content: '\f169'; +} +.zmdi-font:before { + content: '\f16a'; +} +.zmdi-fullscreen-alt:before { + content: '\f16b'; +} +.zmdi-fullscreen-exit:before { + content: '\f16c'; +} +.zmdi-fullscreen:before { + content: '\f16d'; +} +.zmdi-functions:before { + content: '\f16e'; +} +.zmdi-gas-station:before { + content: '\f16f'; +} +.zmdi-gesture:before { + content: '\f170'; +} +.zmdi-globe-alt:before { + content: '\f171'; +} +.zmdi-globe-lock:before { + content: '\f172'; +} +.zmdi-globe:before { + content: '\f173'; +} +.zmdi-graduation-cap:before { + content: '\f174'; +} +.zmdi-home:before { + content: '\f175'; +} +.zmdi-hospital-alt:before { + content: '\f176'; +} +.zmdi-hospital:before { + content: '\f177'; +} +.zmdi-hotel:before { + content: '\f178'; +} +.zmdi-hourglass-alt:before { + content: '\f179'; +} +.zmdi-hourglass-outline:before { + content: '\f17a'; +} +.zmdi-hourglass:before { + content: '\f17b'; +} +.zmdi-http:before { + content: '\f17c'; +} +.zmdi-image-alt:before { + content: '\f17d'; +} +.zmdi-image-o:before { + content: '\f17e'; +} +.zmdi-image:before { + content: '\f17f'; +} +.zmdi-inbox:before { + content: '\f180'; +} +.zmdi-invert-colors-off:before { + content: '\f181'; +} +.zmdi-invert-colors:before { + content: '\f182'; +} +.zmdi-key:before { + content: '\f183'; +} +.zmdi-label-alt-outline:before { + content: '\f184'; +} +.zmdi-label-alt:before { + content: '\f185'; +} +.zmdi-label-heart:before { + content: '\f186'; +} +.zmdi-label:before { + content: '\f187'; +} +.zmdi-labels:before { + content: '\f188'; +} +.zmdi-lamp:before { + content: '\f189'; +} +.zmdi-landscape:before { + content: '\f18a'; +} +.zmdi-layers-off:before { + content: '\f18b'; +} +.zmdi-layers:before { + content: '\f18c'; +} +.zmdi-library:before { + content: '\f18d'; +} +.zmdi-link:before { + content: '\f18e'; +} +.zmdi-lock-open:before { + content: '\f18f'; +} +.zmdi-lock-outline:before { + content: '\f190'; +} +.zmdi-lock:before { + content: '\f191'; +} +.zmdi-mail-reply-all:before { + content: '\f192'; +} +.zmdi-mail-reply:before { + content: '\f193'; +} +.zmdi-mail-send:before { + content: '\f194'; +} +.zmdi-mall:before { + content: '\f195'; +} +.zmdi-map:before { + content: '\f196'; +} +.zmdi-menu:before { + content: '\f197'; +} +.zmdi-money-box:before { + content: '\f198'; +} +.zmdi-money-off:before { + content: '\f199'; +} +.zmdi-money:before { + content: '\f19a'; +} +.zmdi-more-vert:before { + content: '\f19b'; +} +.zmdi-more:before { + content: '\f19c'; +} +.zmdi-movie-alt:before { + content: '\f19d'; +} +.zmdi-movie:before { + content: '\f19e'; +} +.zmdi-nature-people:before { + content: '\f19f'; +} +.zmdi-nature:before { + content: '\f1a0'; +} +.zmdi-navigation:before { + content: '\f1a1'; +} +.zmdi-open-in-browser:before { + content: '\f1a2'; +} +.zmdi-open-in-new:before { + content: '\f1a3'; +} +.zmdi-palette:before { + content: '\f1a4'; +} +.zmdi-parking:before { + content: '\f1a5'; +} +.zmdi-pin-account:before { + content: '\f1a6'; +} +.zmdi-pin-assistant:before { + content: '\f1a7'; +} +.zmdi-pin-drop:before { + content: '\f1a8'; +} +.zmdi-pin-help:before { + content: '\f1a9'; +} +.zmdi-pin-off:before { + content: '\f1aa'; +} +.zmdi-pin:before { + content: '\f1ab'; +} +.zmdi-pizza:before { + content: '\f1ac'; +} +.zmdi-plaster:before { + content: '\f1ad'; +} +.zmdi-power-setting:before { + content: '\f1ae'; +} +.zmdi-power:before { + content: '\f1af'; +} +.zmdi-print:before { + content: '\f1b0'; +} +.zmdi-puzzle-piece:before { + content: '\f1b1'; +} +.zmdi-quote:before { + content: '\f1b2'; +} +.zmdi-railway:before { + content: '\f1b3'; +} +.zmdi-receipt:before { + content: '\f1b4'; +} +.zmdi-refresh-alt:before { + content: '\f1b5'; +} +.zmdi-refresh-sync-alert:before { + content: '\f1b6'; +} +.zmdi-refresh-sync-off:before { + content: '\f1b7'; +} +.zmdi-refresh-sync:before { + content: '\f1b8'; +} +.zmdi-refresh:before { + content: '\f1b9'; +} +.zmdi-roller:before { + content: '\f1ba'; +} +.zmdi-ruler:before { + content: '\f1bb'; +} +.zmdi-scissors:before { + content: '\f1bc'; +} +.zmdi-screen-rotation-lock:before { + content: '\f1bd'; +} +.zmdi-screen-rotation:before { + content: '\f1be'; +} +.zmdi-search-for:before { + content: '\f1bf'; +} +.zmdi-search-in-file:before { + content: '\f1c0'; +} +.zmdi-search-in-page:before { + content: '\f1c1'; +} +.zmdi-search-replace:before { + content: '\f1c2'; +} +.zmdi-search:before { + content: '\f1c3'; +} +.zmdi-seat:before { + content: '\f1c4'; +} +.zmdi-settings-square:before { + content: '\f1c5'; +} +.zmdi-settings:before { + content: '\f1c6'; +} +.zmdi-shield-check:before { + content: '\f1c7'; +} +.zmdi-shield-security:before { + content: '\f1c8'; +} +.zmdi-shopping-basket:before { + content: '\f1c9'; +} +.zmdi-shopping-cart-plus:before { + content: '\f1ca'; +} +.zmdi-shopping-cart:before { + content: '\f1cb'; +} +.zmdi-sign-in:before { + content: '\f1cc'; +} +.zmdi-sort-amount-asc:before { + content: '\f1cd'; +} +.zmdi-sort-amount-desc:before { + content: '\f1ce'; +} +.zmdi-sort-asc:before { + content: '\f1cf'; +} +.zmdi-sort-desc:before { + content: '\f1d0'; +} +.zmdi-spellcheck:before { + content: '\f1d1'; +} +.zmdi-storage:before { + content: '\f1d2'; +} +.zmdi-store-24:before { + content: '\f1d3'; +} +.zmdi-store:before { + content: '\f1d4'; +} +.zmdi-subway:before { + content: '\f1d5'; +} +.zmdi-sun:before { + content: '\f1d6'; +} +.zmdi-tab-unselected:before { + content: '\f1d7'; +} +.zmdi-tab:before { + content: '\f1d8'; +} +.zmdi-tag-close:before { + content: '\f1d9'; +} +.zmdi-tag-more:before { + content: '\f1da'; +} +.zmdi-tag:before { + content: '\f1db'; +} +.zmdi-thumb-down:before { + content: '\f1dc'; +} +.zmdi-thumb-up-down:before { + content: '\f1dd'; +} +.zmdi-thumb-up:before { + content: '\f1de'; +} +.zmdi-ticket-star:before { + content: '\f1df'; +} +.zmdi-toll:before { + content: '\f1e0'; +} +.zmdi-toys:before { + content: '\f1e1'; +} +.zmdi-traffic:before { + content: '\f1e2'; +} +.zmdi-translate:before { + content: '\f1e3'; +} +.zmdi-triangle-down:before { + content: '\f1e4'; +} +.zmdi-triangle-up:before { + content: '\f1e5'; +} +.zmdi-truck:before { + content: '\f1e6'; +} +.zmdi-turning-sign:before { + content: '\f1e7'; +} +.zmdi-wallpaper:before { + content: '\f1e8'; +} +.zmdi-washing-machine:before { + content: '\f1e9'; +} +.zmdi-window-maximize:before { + content: '\f1ea'; +} +.zmdi-window-minimize:before { + content: '\f1eb'; +} +.zmdi-window-restore:before { + content: '\f1ec'; +} +.zmdi-wrench:before { + content: '\f1ed'; +} +.zmdi-zoom-in:before { + content: '\f1ee'; +} +.zmdi-zoom-out:before { + content: '\f1ef'; +} +.zmdi-alert-circle-o:before { + content: '\f1f0'; +} +.zmdi-alert-circle:before { + content: '\f1f1'; +} +.zmdi-alert-octagon:before { + content: '\f1f2'; +} +.zmdi-alert-polygon:before { + content: '\f1f3'; +} +.zmdi-alert-triangle:before { + content: '\f1f4'; +} +.zmdi-help-outline:before { + content: '\f1f5'; +} +.zmdi-help:before { + content: '\f1f6'; +} +.zmdi-info-outline:before { + content: '\f1f7'; +} +.zmdi-info:before { + content: '\f1f8'; +} +.zmdi-notifications-active:before { + content: '\f1f9'; +} +.zmdi-notifications-add:before { + content: '\f1fa'; +} +.zmdi-notifications-none:before { + content: '\f1fb'; +} +.zmdi-notifications-off:before { + content: '\f1fc'; +} +.zmdi-notifications-paused:before { + content: '\f1fd'; +} +.zmdi-notifications:before { + content: '\f1fe'; +} +.zmdi-account-add:before { + content: '\f1ff'; +} +.zmdi-account-box-mail:before { + content: '\f200'; +} +.zmdi-account-box-o:before { + content: '\f201'; +} +.zmdi-account-box-phone:before { + content: '\f202'; +} +.zmdi-account-box:before { + content: '\f203'; +} +.zmdi-account-calendar:before { + content: '\f204'; +} +.zmdi-account-circle:before { + content: '\f205'; +} +.zmdi-account-o:before { + content: '\f206'; +} +.zmdi-account:before { + content: '\f207'; +} +.zmdi-accounts-add:before { + content: '\f208'; +} +.zmdi-accounts-alt:before { + content: '\f209'; +} +.zmdi-accounts-list-alt:before { + content: '\f20a'; +} +.zmdi-accounts-list:before { + content: '\f20b'; +} +.zmdi-accounts-outline:before { + content: '\f20c'; +} +.zmdi-accounts:before { + content: '\f20d'; +} +.zmdi-face:before { + content: '\f20e'; +} +.zmdi-female:before { + content: '\f20f'; +} +.zmdi-male-alt:before { + content: '\f210'; +} +.zmdi-male-female:before { + content: '\f211'; +} +.zmdi-male:before { + content: '\f212'; +} +.zmdi-mood-bad:before { + content: '\f213'; +} +.zmdi-mood:before { + content: '\f214'; +} +.zmdi-run:before { + content: '\f215'; +} +.zmdi-walk:before { + content: '\f216'; +} +.zmdi-cloud-box:before { + content: '\f217'; +} +.zmdi-cloud-circle:before { + content: '\f218'; +} +.zmdi-cloud-done:before { + content: '\f219'; +} +.zmdi-cloud-download:before { + content: '\f21a'; +} +.zmdi-cloud-off:before { + content: '\f21b'; +} +.zmdi-cloud-outline-alt:before { + content: '\f21c'; +} +.zmdi-cloud-outline:before { + content: '\f21d'; +} +.zmdi-cloud-upload:before { + content: '\f21e'; +} +.zmdi-cloud:before { + content: '\f21f'; +} +.zmdi-download:before { + content: '\f220'; +} +.zmdi-file-plus:before { + content: '\f221'; +} +.zmdi-file-text:before { + content: '\f222'; +} +.zmdi-file:before { + content: '\f223'; +} +.zmdi-folder-outline:before { + content: '\f224'; +} +.zmdi-folder-person:before { + content: '\f225'; +} +.zmdi-folder-star-alt:before { + content: '\f226'; +} +.zmdi-folder-star:before { + content: '\f227'; +} +.zmdi-folder:before { + content: '\f228'; +} +.zmdi-gif:before { + content: '\f229'; +} +.zmdi-upload:before { + content: '\f22a'; +} +.zmdi-border-all:before { + content: '\f22b'; +} +.zmdi-border-bottom:before { + content: '\f22c'; +} +.zmdi-border-clear:before { + content: '\f22d'; +} +.zmdi-border-color:before { + content: '\f22e'; +} +.zmdi-border-horizontal:before { + content: '\f22f'; +} +.zmdi-border-inner:before { + content: '\f230'; +} +.zmdi-border-left:before { + content: '\f231'; +} +.zmdi-border-outer:before { + content: '\f232'; +} +.zmdi-border-right:before { + content: '\f233'; +} +.zmdi-border-style:before { + content: '\f234'; +} +.zmdi-border-top:before { + content: '\f235'; +} +.zmdi-border-vertical:before { + content: '\f236'; +} +.zmdi-copy:before { + content: '\f237'; +} +.zmdi-crop:before { + content: '\f238'; +} +.zmdi-format-align-center:before { + content: '\f239'; +} +.zmdi-format-align-justify:before { + content: '\f23a'; +} +.zmdi-format-align-left:before { + content: '\f23b'; +} +.zmdi-format-align-right:before { + content: '\f23c'; +} +.zmdi-format-bold:before { + content: '\f23d'; +} +.zmdi-format-clear-all:before { + content: '\f23e'; +} +.zmdi-format-clear:before { + content: '\f23f'; +} +.zmdi-format-color-fill:before { + content: '\f240'; +} +.zmdi-format-color-reset:before { + content: '\f241'; +} +.zmdi-format-color-text:before { + content: '\f242'; +} +.zmdi-format-indent-decrease:before { + content: '\f243'; +} +.zmdi-format-indent-increase:before { + content: '\f244'; +} +.zmdi-format-italic:before { + content: '\f245'; +} +.zmdi-format-line-spacing:before { + content: '\f246'; +} +.zmdi-format-list-bulleted:before { + content: '\f247'; +} +.zmdi-format-list-numbered:before { + content: '\f248'; +} +.zmdi-format-ltr:before { + content: '\f249'; +} +.zmdi-format-rtl:before { + content: '\f24a'; +} +.zmdi-format-size:before { + content: '\f24b'; +} +.zmdi-format-strikethrough-s:before { + content: '\f24c'; +} +.zmdi-format-strikethrough:before { + content: '\f24d'; +} +.zmdi-format-subject:before { + content: '\f24e'; +} +.zmdi-format-underlined:before { + content: '\f24f'; +} +.zmdi-format-valign-bottom:before { + content: '\f250'; +} +.zmdi-format-valign-center:before { + content: '\f251'; +} +.zmdi-format-valign-top:before { + content: '\f252'; +} +.zmdi-redo:before { + content: '\f253'; +} +.zmdi-select-all:before { + content: '\f254'; +} +.zmdi-space-bar:before { + content: '\f255'; +} +.zmdi-text-format:before { + content: '\f256'; +} +.zmdi-transform:before { + content: '\f257'; +} +.zmdi-undo:before { + content: '\f258'; +} +.zmdi-wrap-text:before { + content: '\f259'; +} +.zmdi-comment-alert:before { + content: '\f25a'; +} +.zmdi-comment-alt-text:before { + content: '\f25b'; +} +.zmdi-comment-alt:before { + content: '\f25c'; +} +.zmdi-comment-edit:before { + content: '\f25d'; +} +.zmdi-comment-image:before { + content: '\f25e'; +} +.zmdi-comment-list:before { + content: '\f25f'; +} +.zmdi-comment-more:before { + content: '\f260'; +} +.zmdi-comment-outline:before { + content: '\f261'; +} +.zmdi-comment-text-alt:before { + content: '\f262'; +} +.zmdi-comment-text:before { + content: '\f263'; +} +.zmdi-comment-video:before { + content: '\f264'; +} +.zmdi-comment:before { + content: '\f265'; +} +.zmdi-comments:before { + content: '\f266'; +} +.zmdi-check-all:before { + content: '\f267'; +} +.zmdi-check-circle-u:before { + content: '\f268'; +} +.zmdi-check-circle:before { + content: '\f269'; +} +.zmdi-check-square:before { + content: '\f26a'; +} +.zmdi-check:before { + content: '\f26b'; +} +.zmdi-circle-o:before { + content: '\f26c'; +} +.zmdi-circle:before { + content: '\f26d'; +} +.zmdi-dot-circle-alt:before { + content: '\f26e'; +} +.zmdi-dot-circle:before { + content: '\f26f'; +} +.zmdi-minus-circle-outline:before { + content: '\f270'; +} +.zmdi-minus-circle:before { + content: '\f271'; +} +.zmdi-minus-square:before { + content: '\f272'; +} +.zmdi-minus:before { + content: '\f273'; +} +.zmdi-plus-circle-o-duplicate:before { + content: '\f274'; +} +.zmdi-plus-circle-o:before { + content: '\f275'; +} +.zmdi-plus-circle:before { + content: '\f276'; +} +.zmdi-plus-square:before { + content: '\f277'; +} +.zmdi-plus:before { + content: '\f278'; +} +.zmdi-square-o:before { + content: '\f279'; +} +.zmdi-star-circle:before { + content: '\f27a'; +} +.zmdi-star-half:before { + content: '\f27b'; +} +.zmdi-star-outline:before { + content: '\f27c'; +} +.zmdi-star:before { + content: '\f27d'; +} +.zmdi-bluetooth-connected:before { + content: '\f27e'; +} +.zmdi-bluetooth-off:before { + content: '\f27f'; +} +.zmdi-bluetooth-search:before { + content: '\f280'; +} +.zmdi-bluetooth-setting:before { + content: '\f281'; +} +.zmdi-bluetooth:before { + content: '\f282'; +} +.zmdi-camera-add:before { + content: '\f283'; +} +.zmdi-camera-alt:before { + content: '\f284'; +} +.zmdi-camera-bw:before { + content: '\f285'; +} +.zmdi-camera-front:before { + content: '\f286'; +} +.zmdi-camera-mic:before { + content: '\f287'; +} +.zmdi-camera-party-mode:before { + content: '\f288'; +} +.zmdi-camera-rear:before { + content: '\f289'; +} +.zmdi-camera-roll:before { + content: '\f28a'; +} +.zmdi-camera-switch:before { + content: '\f28b'; +} +.zmdi-camera:before { + content: '\f28c'; +} +.zmdi-card-alert:before { + content: '\f28d'; +} +.zmdi-card-off:before { + content: '\f28e'; +} +.zmdi-card-sd:before { + content: '\f28f'; +} +.zmdi-card-sim:before { + content: '\f290'; +} +.zmdi-desktop-mac:before { + content: '\f291'; +} +.zmdi-desktop-windows:before { + content: '\f292'; +} +.zmdi-device-hub:before { + content: '\f293'; +} +.zmdi-devices-off:before { + content: '\f294'; +} +.zmdi-devices:before { + content: '\f295'; +} +.zmdi-dock:before { + content: '\f296'; +} +.zmdi-floppy:before { + content: '\f297'; +} +.zmdi-gamepad:before { + content: '\f298'; +} +.zmdi-gps-dot:before { + content: '\f299'; +} +.zmdi-gps-off:before { + content: '\f29a'; +} +.zmdi-gps:before { + content: '\f29b'; +} +.zmdi-headset-mic:before { + content: '\f29c'; +} +.zmdi-headset:before { + content: '\f29d'; +} +.zmdi-input-antenna:before { + content: '\f29e'; +} +.zmdi-input-composite:before { + content: '\f29f'; +} +.zmdi-input-hdmi:before { + content: '\f2a0'; +} +.zmdi-input-power:before { + content: '\f2a1'; +} +.zmdi-input-svideo:before { + content: '\f2a2'; +} +.zmdi-keyboard-hide:before { + content: '\f2a3'; +} +.zmdi-keyboard:before { + content: '\f2a4'; +} +.zmdi-laptop-chromebook:before { + content: '\f2a5'; +} +.zmdi-laptop-mac:before { + content: '\f2a6'; +} +.zmdi-laptop:before { + content: '\f2a7'; +} +.zmdi-mic-off:before { + content: '\f2a8'; +} +.zmdi-mic-outline:before { + content: '\f2a9'; +} +.zmdi-mic-setting:before { + content: '\f2aa'; +} +.zmdi-mic:before { + content: '\f2ab'; +} +.zmdi-mouse:before { + content: '\f2ac'; +} +.zmdi-network-alert:before { + content: '\f2ad'; +} +.zmdi-network-locked:before { + content: '\f2ae'; +} +.zmdi-network-off:before { + content: '\f2af'; +} +.zmdi-network-outline:before { + content: '\f2b0'; +} +.zmdi-network-setting:before { + content: '\f2b1'; +} +.zmdi-network:before { + content: '\f2b2'; +} +.zmdi-phone-bluetooth:before { + content: '\f2b3'; +} +.zmdi-phone-end:before { + content: '\f2b4'; +} +.zmdi-phone-forwarded:before { + content: '\f2b5'; +} +.zmdi-phone-in-talk:before { + content: '\f2b6'; +} +.zmdi-phone-locked:before { + content: '\f2b7'; +} +.zmdi-phone-missed:before { + content: '\f2b8'; +} +.zmdi-phone-msg:before { + content: '\f2b9'; +} +.zmdi-phone-paused:before { + content: '\f2ba'; +} +.zmdi-phone-ring:before { + content: '\f2bb'; +} +.zmdi-phone-setting:before { + content: '\f2bc'; +} +.zmdi-phone-sip:before { + content: '\f2bd'; +} +.zmdi-phone:before { + content: '\f2be'; +} +.zmdi-portable-wifi-changes:before { + content: '\f2bf'; +} +.zmdi-portable-wifi-off:before { + content: '\f2c0'; +} +.zmdi-portable-wifi:before { + content: '\f2c1'; +} +.zmdi-radio:before { + content: '\f2c2'; +} +.zmdi-reader:before { + content: '\f2c3'; +} +.zmdi-remote-control-alt:before { + content: '\f2c4'; +} +.zmdi-remote-control:before { + content: '\f2c5'; +} +.zmdi-router:before { + content: '\f2c6'; +} +.zmdi-scanner:before { + content: '\f2c7'; +} +.zmdi-smartphone-android:before { + content: '\f2c8'; +} +.zmdi-smartphone-download:before { + content: '\f2c9'; +} +.zmdi-smartphone-erase:before { + content: '\f2ca'; +} +.zmdi-smartphone-info:before { + content: '\f2cb'; +} +.zmdi-smartphone-iphone:before { + content: '\f2cc'; +} +.zmdi-smartphone-landscape-lock:before { + content: '\f2cd'; +} +.zmdi-smartphone-landscape:before { + content: '\f2ce'; +} +.zmdi-smartphone-lock:before { + content: '\f2cf'; +} +.zmdi-smartphone-portrait-lock:before { + content: '\f2d0'; +} +.zmdi-smartphone-ring:before { + content: '\f2d1'; +} +.zmdi-smartphone-setting:before { + content: '\f2d2'; +} +.zmdi-smartphone-setup:before { + content: '\f2d3'; +} +.zmdi-smartphone:before { + content: '\f2d4'; +} +.zmdi-speaker:before { + content: '\f2d5'; +} +.zmdi-tablet-android:before { + content: '\f2d6'; +} +.zmdi-tablet-mac:before { + content: '\f2d7'; +} +.zmdi-tablet:before { + content: '\f2d8'; +} +.zmdi-tv-alt-play:before { + content: '\f2d9'; +} +.zmdi-tv-list:before { + content: '\f2da'; +} +.zmdi-tv-play:before { + content: '\f2db'; +} +.zmdi-tv:before { + content: '\f2dc'; +} +.zmdi-usb:before { + content: '\f2dd'; +} +.zmdi-videocam-off:before { + content: '\f2de'; +} +.zmdi-videocam-switch:before { + content: '\f2df'; +} +.zmdi-videocam:before { + content: '\f2e0'; +} +.zmdi-watch:before { + content: '\f2e1'; +} +.zmdi-wifi-alt-2:before { + content: '\f2e2'; +} +.zmdi-wifi-alt:before { + content: '\f2e3'; +} +.zmdi-wifi-info:before { + content: '\f2e4'; +} +.zmdi-wifi-lock:before { + content: '\f2e5'; +} +.zmdi-wifi-off:before { + content: '\f2e6'; +} +.zmdi-wifi-outline:before { + content: '\f2e7'; +} +.zmdi-wifi:before { + content: '\f2e8'; +} +.zmdi-arrow-left-bottom:before { + content: '\f2e9'; +} +.zmdi-arrow-left:before { + content: '\f2ea'; +} +.zmdi-arrow-merge:before { + content: '\f2eb'; +} +.zmdi-arrow-missed:before { + content: '\f2ec'; +} +.zmdi-arrow-right-top:before { + content: '\f2ed'; +} +.zmdi-arrow-right:before { + content: '\f2ee'; +} +.zmdi-arrow-split:before { + content: '\f2ef'; +} +.zmdi-arrows:before { + content: '\f2f0'; +} +.zmdi-caret-down-circle:before { + content: '\f2f1'; +} +.zmdi-caret-down:before { + content: '\f2f2'; +} +.zmdi-caret-left-circle:before { + content: '\f2f3'; +} +.zmdi-caret-left:before { + content: '\f2f4'; +} +.zmdi-caret-right-circle:before { + content: '\f2f5'; +} +.zmdi-caret-right:before { + content: '\f2f6'; +} +.zmdi-caret-up-circle:before { + content: '\f2f7'; +} +.zmdi-caret-up:before { + content: '\f2f8'; +} +.zmdi-chevron-down:before { + content: '\f2f9'; +} +.zmdi-chevron-left:before { + content: '\f2fa'; +} +.zmdi-chevron-right:before { + content: '\f2fb'; +} +.zmdi-chevron-up:before { + content: '\f2fc'; +} +.zmdi-forward:before { + content: '\f2fd'; +} +.zmdi-long-arrow-down:before { + content: '\f2fe'; +} +.zmdi-long-arrow-left:before { + content: '\f2ff'; +} +.zmdi-long-arrow-return:before { + content: '\f300'; +} +.zmdi-long-arrow-right:before { + content: '\f301'; +} +.zmdi-long-arrow-tab:before { + content: '\f302'; +} +.zmdi-long-arrow-up:before { + content: '\f303'; +} +.zmdi-rotate-ccw:before { + content: '\f304'; +} +.zmdi-rotate-cw:before { + content: '\f305'; +} +.zmdi-rotate-left:before { + content: '\f306'; +} +.zmdi-rotate-right:before { + content: '\f307'; +} +.zmdi-square-down:before { + content: '\f308'; +} +.zmdi-square-right:before { + content: '\f309'; +} +.zmdi-swap-alt:before { + content: '\f30a'; +} +.zmdi-swap-vertical-circle:before { + content: '\f30b'; +} +.zmdi-swap-vertical:before { + content: '\f30c'; +} +.zmdi-swap:before { + content: '\f30d'; +} +.zmdi-trending-down:before { + content: '\f30e'; +} +.zmdi-trending-flat:before { + content: '\f30f'; +} +.zmdi-trending-up:before { + content: '\f310'; +} +.zmdi-unfold-less:before { + content: '\f311'; +} +.zmdi-unfold-more:before { + content: '\f312'; +} +.zmdi-apps:before { + content: '\f313'; +} +.zmdi-grid-off:before { + content: '\f314'; +} +.zmdi-grid:before { + content: '\f315'; +} +.zmdi-view-agenda:before { + content: '\f316'; +} +.zmdi-view-array:before { + content: '\f317'; +} +.zmdi-view-carousel:before { + content: '\f318'; +} +.zmdi-view-column:before { + content: '\f319'; +} +.zmdi-view-comfy:before { + content: '\f31a'; +} +.zmdi-view-compact:before { + content: '\f31b'; +} +.zmdi-view-dashboard:before { + content: '\f31c'; +} +.zmdi-view-day:before { + content: '\f31d'; +} +.zmdi-view-headline:before { + content: '\f31e'; +} +.zmdi-view-list-alt:before { + content: '\f31f'; +} +.zmdi-view-list:before { + content: '\f320'; +} +.zmdi-view-module:before { + content: '\f321'; +} +.zmdi-view-quilt:before { + content: '\f322'; +} +.zmdi-view-stream:before { + content: '\f323'; +} +.zmdi-view-subtitles:before { + content: '\f324'; +} +.zmdi-view-toc:before { + content: '\f325'; +} +.zmdi-view-web:before { + content: '\f326'; +} +.zmdi-view-week:before { + content: '\f327'; +} +.zmdi-widgets:before { + content: '\f328'; +} +.zmdi-alarm-check:before { + content: '\f329'; +} +.zmdi-alarm-off:before { + content: '\f32a'; +} +.zmdi-alarm-plus:before { + content: '\f32b'; +} +.zmdi-alarm-snooze:before { + content: '\f32c'; +} +.zmdi-alarm:before { + content: '\f32d'; +} +.zmdi-calendar-alt:before { + content: '\f32e'; +} +.zmdi-calendar-check:before { + content: '\f32f'; +} +.zmdi-calendar-close:before { + content: '\f330'; +} +.zmdi-calendar-note:before { + content: '\f331'; +} +.zmdi-calendar:before { + content: '\f332'; +} +.zmdi-time-countdown:before { + content: '\f333'; +} +.zmdi-time-interval:before { + content: '\f334'; +} +.zmdi-time-restore-setting:before { + content: '\f335'; +} +.zmdi-time-restore:before { + content: '\f336'; +} +.zmdi-time:before { + content: '\f337'; +} +.zmdi-timer-off:before { + content: '\f338'; +} +.zmdi-timer:before { + content: '\f339'; +} +.zmdi-android-alt:before { + content: '\f33a'; +} +.zmdi-android:before { + content: '\f33b'; +} +.zmdi-apple:before { + content: '\f33c'; +} +.zmdi-behance:before { + content: '\f33d'; +} +.zmdi-codepen:before { + content: '\f33e'; +} +.zmdi-dribbble:before { + content: '\f33f'; +} +.zmdi-dropbox:before { + content: '\f340'; +} +.zmdi-evernote:before { + content: '\f341'; +} +.zmdi-facebook-box:before { + content: '\f342'; +} +.zmdi-facebook:before { + content: '\f343'; +} +.zmdi-github-box:before { + content: '\f344'; +} +.zmdi-github:before { + content: '\f345'; +} +.zmdi-google-drive:before { + content: '\f346'; +} +.zmdi-google-earth:before { + content: '\f347'; +} +.zmdi-google-glass:before { + content: '\f348'; +} +.zmdi-google-maps:before { + content: '\f349'; +} +.zmdi-google-pages:before { + content: '\f34a'; +} +.zmdi-google-play:before { + content: '\f34b'; +} +.zmdi-google-plus-box:before { + content: '\f34c'; +} +.zmdi-google-plus:before { + content: '\f34d'; +} +.zmdi-google:before { + content: '\f34e'; +} +.zmdi-instagram:before { + content: '\f34f'; +} +.zmdi-language-css3:before { + content: '\f350'; +} +.zmdi-language-html5:before { + content: '\f351'; +} +.zmdi-language-javascript:before { + content: '\f352'; +} +.zmdi-language-python-alt:before { + content: '\f353'; +} +.zmdi-language-python:before { + content: '\f354'; +} +.zmdi-lastfm:before { + content: '\f355'; +} +.zmdi-linkedin-box:before { + content: '\f356'; +} +.zmdi-paypal:before { + content: '\f357'; +} +.zmdi-pinterest-box:before { + content: '\f358'; +} +.zmdi-pocket:before { + content: '\f359'; +} +.zmdi-polymer:before { + content: '\f35a'; +} +.zmdi-share:before { + content: '\f35b'; +} +.zmdi-stackoverflow:before { + content: '\f35c'; +} +.zmdi-steam-square:before { + content: '\f35d'; +} +.zmdi-steam:before { + content: '\f35e'; +} +.zmdi-twitter-box:before { + content: '\f35f'; +} +.zmdi-twitter:before { + content: '\f360'; +} +.zmdi-vk:before { + content: '\f361'; +} +.zmdi-wikipedia:before { + content: '\f362'; +} +.zmdi-windows:before { + content: '\f363'; +} +.zmdi-aspect-ratio-alt:before { + content: '\f364'; +} +.zmdi-aspect-ratio:before { + content: '\f365'; +} +.zmdi-blur-circular:before { + content: '\f366'; +} +.zmdi-blur-linear:before { + content: '\f367'; +} +.zmdi-blur-off:before { + content: '\f368'; +} +.zmdi-blur:before { + content: '\f369'; +} +.zmdi-brightness-2:before { + content: '\f36a'; +} +.zmdi-brightness-3:before { + content: '\f36b'; +} +.zmdi-brightness-4:before { + content: '\f36c'; +} +.zmdi-brightness-5:before { + content: '\f36d'; +} +.zmdi-brightness-6:before { + content: '\f36e'; +} +.zmdi-brightness-7:before { + content: '\f36f'; +} +.zmdi-brightness-auto:before { + content: '\f370'; +} +.zmdi-brightness-setting:before { + content: '\f371'; +} +.zmdi-broken-image:before { + content: '\f372'; +} +.zmdi-center-focus-strong:before { + content: '\f373'; +} +.zmdi-center-focus-weak:before { + content: '\f374'; +} +.zmdi-compare:before { + content: '\f375'; +} +.zmdi-crop-16-9:before { + content: '\f376'; +} +.zmdi-crop-3-2:before { + content: '\f377'; +} +.zmdi-crop-5-4:before { + content: '\f378'; +} +.zmdi-crop-7-5:before { + content: '\f379'; +} +.zmdi-crop-din:before { + content: '\f37a'; +} +.zmdi-crop-free:before { + content: '\f37b'; +} +.zmdi-crop-landscape:before { + content: '\f37c'; +} +.zmdi-crop-portrait:before { + content: '\f37d'; +} +.zmdi-crop-square:before { + content: '\f37e'; +} +.zmdi-exposure-alt:before { + content: '\f37f'; +} +.zmdi-exposure:before { + content: '\f380'; +} +.zmdi-filter-b-and-w:before { + content: '\f381'; +} +.zmdi-filter-center-focus:before { + content: '\f382'; +} +.zmdi-filter-frames:before { + content: '\f383'; +} +.zmdi-filter-tilt-shift:before { + content: '\f384'; +} +.zmdi-gradient:before { + content: '\f385'; +} +.zmdi-grain:before { + content: '\f386'; +} +.zmdi-graphic-eq:before { + content: '\f387'; +} +.zmdi-hdr-off:before { + content: '\f388'; +} +.zmdi-hdr-strong:before { + content: '\f389'; +} +.zmdi-hdr-weak:before { + content: '\f38a'; +} +.zmdi-hdr:before { + content: '\f38b'; +} +.zmdi-iridescent:before { + content: '\f38c'; +} +.zmdi-leak-off:before { + content: '\f38d'; +} +.zmdi-leak:before { + content: '\f38e'; +} +.zmdi-looks:before { + content: '\f38f'; +} +.zmdi-loupe:before { + content: '\f390'; +} +.zmdi-panorama-horizontal:before { + content: '\f391'; +} +.zmdi-panorama-vertical:before { + content: '\f392'; +} +.zmdi-panorama-wide-angle:before { + content: '\f393'; +} +.zmdi-photo-size-select-large:before { + content: '\f394'; +} +.zmdi-photo-size-select-small:before { + content: '\f395'; +} +.zmdi-picture-in-picture:before { + content: '\f396'; +} +.zmdi-slideshow:before { + content: '\f397'; +} +.zmdi-texture:before { + content: '\f398'; +} +.zmdi-tonality:before { + content: '\f399'; +} +.zmdi-vignette:before { + content: '\f39a'; +} +.zmdi-wb-auto:before { + content: '\f39b'; +} +.zmdi-eject-alt:before { + content: '\f39c'; +} +.zmdi-eject:before { + content: '\f39d'; +} +.zmdi-equalizer:before { + content: '\f39e'; +} +.zmdi-fast-forward:before { + content: '\f39f'; +} +.zmdi-fast-rewind:before { + content: '\f3a0'; +} +.zmdi-forward-10:before { + content: '\f3a1'; +} +.zmdi-forward-30:before { + content: '\f3a2'; +} +.zmdi-forward-5:before { + content: '\f3a3'; +} +.zmdi-hearing:before { + content: '\f3a4'; +} +.zmdi-pause-circle-outline:before { + content: '\f3a5'; +} +.zmdi-pause-circle:before { + content: '\f3a6'; +} +.zmdi-pause:before { + content: '\f3a7'; +} +.zmdi-play-circle-outline:before { + content: '\f3a8'; +} +.zmdi-play-circle:before { + content: '\f3a9'; +} +.zmdi-play:before { + content: '\f3aa'; +} +.zmdi-playlist-audio:before { + content: '\f3ab'; +} +.zmdi-playlist-plus:before { + content: '\f3ac'; +} +.zmdi-repeat-one:before { + content: '\f3ad'; +} +.zmdi-repeat:before { + content: '\f3ae'; +} +.zmdi-replay-10:before { + content: '\f3af'; +} +.zmdi-replay-30:before { + content: '\f3b0'; +} +.zmdi-replay-5:before { + content: '\f3b1'; +} +.zmdi-replay:before { + content: '\f3b2'; +} +.zmdi-shuffle:before { + content: '\f3b3'; +} +.zmdi-skip-next:before { + content: '\f3b4'; +} +.zmdi-skip-previous:before { + content: '\f3b5'; +} +.zmdi-stop:before { + content: '\f3b6'; +} +.zmdi-surround-sound:before { + content: '\f3b7'; +} +.zmdi-tune:before { + content: '\f3b8'; +} +.zmdi-volume-down:before { + content: '\f3b9'; +} +.zmdi-volume-mute:before { + content: '\f3ba'; +} +.zmdi-volume-off:before { + content: '\f3bb'; +} +.zmdi-volume-up:before { + content: '\f3bc'; +} +.zmdi-n-1-square:before { + content: '\f3bd'; +} +.zmdi-n-2-square:before { + content: '\f3be'; +} +.zmdi-n-3-square:before { + content: '\f3bf'; +} +.zmdi-n-4-square:before { + content: '\f3c0'; +} +.zmdi-n-5-square:before { + content: '\f3c1'; +} +.zmdi-n-6-square:before { + content: '\f3c2'; +} +.zmdi-neg-1:before { + content: '\f3c3'; +} +.zmdi-neg-2:before { + content: '\f3c4'; +} +.zmdi-plus-1:before { + content: '\f3c5'; +} +.zmdi-plus-2:before { + content: '\f3c6'; +} +.zmdi-sec-10:before { + content: '\f3c7'; +} +.zmdi-sec-3:before { + content: '\f3c8'; +} +.zmdi-zero:before { + content: '\f3c9'; +} +.zmdi-airline-seat-flat-angled:before { + content: '\f3ca'; +} +.zmdi-airline-seat-flat:before { + content: '\f3cb'; +} +.zmdi-airline-seat-individual-suite:before { + content: '\f3cc'; +} +.zmdi-airline-seat-legroom-extra:before { + content: '\f3cd'; +} +.zmdi-airline-seat-legroom-normal:before { + content: '\f3ce'; +} +.zmdi-airline-seat-legroom-reduced:before { + content: '\f3cf'; +} +.zmdi-airline-seat-recline-extra:before { + content: '\f3d0'; +} +.zmdi-airline-seat-recline-normal:before { + content: '\f3d1'; +} +.zmdi-airplay:before { + content: '\f3d2'; +} +.zmdi-closed-caption:before { + content: '\f3d3'; +} +.zmdi-confirmation-number:before { + content: '\f3d4'; +} +.zmdi-developer-board:before { + content: '\f3d5'; +} +.zmdi-disc-full:before { + content: '\f3d6'; +} +.zmdi-explicit:before { + content: '\f3d7'; +} +.zmdi-flight-land:before { + content: '\f3d8'; +} +.zmdi-flight-takeoff:before { + content: '\f3d9'; +} +.zmdi-flip-to-back:before { + content: '\f3da'; +} +.zmdi-flip-to-front:before { + content: '\f3db'; +} +.zmdi-group-work:before { + content: '\f3dc'; +} +.zmdi-hd:before { + content: '\f3dd'; +} +.zmdi-hq:before { + content: '\f3de'; +} +.zmdi-markunread-mailbox:before { + content: '\f3df'; +} +.zmdi-memory:before { + content: '\f3e0'; +} +.zmdi-nfc:before { + content: '\f3e1'; +} +.zmdi-play-for-work:before { + content: '\f3e2'; +} +.zmdi-power-input:before { + content: '\f3e3'; +} +.zmdi-present-to-all:before { + content: '\f3e4'; +} +.zmdi-satellite:before { + content: '\f3e5'; +} +.zmdi-tap-and-play:before { + content: '\f3e6'; +} +.zmdi-vibration:before { + content: '\f3e7'; +} +.zmdi-voicemail:before { + content: '\f3e8'; +} +.zmdi-group:before { + content: '\f3e9'; +} +.zmdi-rss:before { + content: '\f3ea'; +} +.zmdi-shape:before { + content: '\f3eb'; +} +.zmdi-spinner:before { + content: '\f3ec'; +} +.zmdi-ungroup:before { + content: '\f3ed'; +} +.zmdi-500px:before { + content: '\f3ee'; +} +.zmdi-8tracks:before { + content: '\f3ef'; +} +.zmdi-amazon:before { + content: '\f3f0'; +} +.zmdi-blogger:before { + content: '\f3f1'; +} +.zmdi-delicious:before { + content: '\f3f2'; +} +.zmdi-disqus:before { + content: '\f3f3'; +} +.zmdi-flattr:before { + content: '\f3f4'; +} +.zmdi-flickr:before { + content: '\f3f5'; +} +.zmdi-github-alt:before { + content: '\f3f6'; +} +.zmdi-google-old:before { + content: '\f3f7'; +} +.zmdi-linkedin:before { + content: '\f3f8'; +} +.zmdi-odnoklassniki:before { + content: '\f3f9'; +} +.zmdi-outlook:before { + content: '\f3fa'; +} +.zmdi-paypal-alt:before { + content: '\f3fb'; +} +.zmdi-pinterest:before { + content: '\f3fc'; +} +.zmdi-playstation:before { + content: '\f3fd'; +} +.zmdi-reddit:before { + content: '\f3fe'; +} +.zmdi-skype:before { + content: '\f3ff'; +} +.zmdi-slideshare:before { + content: '\f400'; +} +.zmdi-soundcloud:before { + content: '\f401'; +} +.zmdi-tumblr:before { + content: '\f402'; +} +.zmdi-twitch:before { + content: '\f403'; +} +.zmdi-vimeo:before { + content: '\f404'; +} +.zmdi-whatsapp:before { + content: '\f405'; +} +.zmdi-xbox:before { + content: '\f406'; +} +.zmdi-yahoo:before { + content: '\f407'; +} +.zmdi-youtube-play:before { + content: '\f408'; +} +.zmdi-youtube:before { + content: '\f409'; +} +.zmdi-3d-rotation:before { + content: '\f101'; +} +.zmdi-airplane-off:before { + content: '\f102'; +} +.zmdi-airplane:before { + content: '\f103'; +} +.zmdi-album:before { + content: '\f104'; +} +.zmdi-archive:before { + content: '\f105'; +} +.zmdi-assignment-account:before { + content: '\f106'; +} +.zmdi-assignment-alert:before { + content: '\f107'; +} +.zmdi-assignment-check:before { + content: '\f108'; +} +.zmdi-assignment-o:before { + content: '\f109'; +} +.zmdi-assignment-return:before { + content: '\f10a'; +} +.zmdi-assignment-returned:before { + content: '\f10b'; +} +.zmdi-assignment:before { + content: '\f10c'; +} +.zmdi-attachment-alt:before { + content: '\f10d'; +} +.zmdi-attachment:before { + content: '\f10e'; +} +.zmdi-audio:before { + content: '\f10f'; +} +.zmdi-badge-check:before { + content: '\f110'; +} +.zmdi-balance-wallet:before { + content: '\f111'; +} +.zmdi-balance:before { + content: '\f112'; +} +.zmdi-battery-alert:before { + content: '\f113'; +} +.zmdi-battery-flash:before { + content: '\f114'; +} +.zmdi-battery-unknown:before { + content: '\f115'; +} +.zmdi-battery:before { + content: '\f116'; +} +.zmdi-bike:before { + content: '\f117'; +} +.zmdi-block-alt:before { + content: '\f118'; +} +.zmdi-block:before { + content: '\f119'; +} +.zmdi-boat:before { + content: '\f11a'; +} +.zmdi-book-image:before { + content: '\f11b'; +} +.zmdi-book:before { + content: '\f11c'; +} +.zmdi-bookmark-outline:before { + content: '\f11d'; +} +.zmdi-bookmark:before { + content: '\f11e'; +} +.zmdi-brush:before { + content: '\f11f'; +} +.zmdi-bug:before { + content: '\f120'; +} +.zmdi-bus:before { + content: '\f121'; +} +.zmdi-cake:before { + content: '\f122'; +} +.zmdi-car-taxi:before { + content: '\f123'; +} +.zmdi-car-wash:before { + content: '\f124'; +} +.zmdi-car:before { + content: '\f125'; +} +.zmdi-card-giftcard:before { + content: '\f126'; +} +.zmdi-card-membership:before { + content: '\f127'; +} +.zmdi-card-travel:before { + content: '\f128'; +} +.zmdi-card:before { + content: '\f129'; +} +.zmdi-case-check:before { + content: '\f12a'; +} +.zmdi-case-download:before { + content: '\f12b'; +} +.zmdi-case-play:before { + content: '\f12c'; +} +.zmdi-case:before { + content: '\f12d'; +} +.zmdi-cast-connected:before { + content: '\f12e'; +} +.zmdi-cast:before { + content: '\f12f'; +} +.zmdi-chart-donut:before { + content: '\f130'; +} +.zmdi-chart:before { + content: '\f131'; +} +.zmdi-city-alt:before { + content: '\f132'; +} +.zmdi-city:before { + content: '\f133'; +} +.zmdi-close-circle-o:before { + content: '\f134'; +} +.zmdi-close-circle:before { + content: '\f135'; +} +.zmdi-close:before { + content: '\f136'; +} +.zmdi-cocktail:before { + content: '\f137'; +} +.zmdi-code-setting:before { + content: '\f138'; +} +.zmdi-code-smartphone:before { + content: '\f139'; +} +.zmdi-code:before { + content: '\f13a'; +} +.zmdi-coffee:before { + content: '\f13b'; +} +.zmdi-collection-bookmark:before { + content: '\f13c'; +} +.zmdi-collection-case-play:before { + content: '\f13d'; +} +.zmdi-collection-folder-image:before { + content: '\f13e'; +} +.zmdi-collection-image-o:before { + content: '\f13f'; +} +.zmdi-collection-image:before { + content: '\f140'; +} +.zmdi-collection-item-1:before { + content: '\f141'; +} +.zmdi-collection-item-2:before { + content: '\f142'; +} +.zmdi-collection-item-3:before { + content: '\f143'; +} +.zmdi-collection-item-4:before { + content: '\f144'; +} +.zmdi-collection-item-5:before { + content: '\f145'; +} +.zmdi-collection-item-6:before { + content: '\f146'; +} +.zmdi-collection-item-7:before { + content: '\f147'; +} +.zmdi-collection-item-8:before { + content: '\f148'; +} +.zmdi-collection-item-9-plus:before { + content: '\f149'; +} +.zmdi-collection-item-9:before { + content: '\f14a'; +} +.zmdi-collection-item:before { + content: '\f14b'; +} +.zmdi-collection-music:before { + content: '\f14c'; +} +.zmdi-collection-pdf:before { + content: '\f14d'; +} +.zmdi-collection-plus:before { + content: '\f14e'; +} +.zmdi-collection-speaker:before { + content: '\f14f'; +} +.zmdi-collection-text:before { + content: '\f150'; +} +.zmdi-collection-video:before { + content: '\f151'; +} +.zmdi-compass:before { + content: '\f152'; +} +.zmdi-cutlery:before { + content: '\f153'; +} +.zmdi-delete:before { + content: '\f154'; +} +.zmdi-dialpad:before { + content: '\f155'; +} +.zmdi-dns:before { + content: '\f156'; +} +.zmdi-drink:before { + content: '\f157'; +} +.zmdi-edit:before { + content: '\f158'; +} +.zmdi-email-open:before { + content: '\f159'; +} +.zmdi-email:before { + content: '\f15a'; +} +.zmdi-eye-off:before { + content: '\f15b'; +} +.zmdi-eye:before { + content: '\f15c'; +} +.zmdi-eyedropper:before { + content: '\f15d'; +} +.zmdi-favorite-outline:before { + content: '\f15e'; +} +.zmdi-favorite:before { + content: '\f15f'; +} +.zmdi-filter-list:before { + content: '\f160'; +} +.zmdi-fire:before { + content: '\f161'; +} +.zmdi-flag:before { + content: '\f162'; +} +.zmdi-flare:before { + content: '\f163'; +} +.zmdi-flash-auto:before { + content: '\f164'; +} +.zmdi-flash-off:before { + content: '\f165'; +} +.zmdi-flash:before { + content: '\f166'; +} +.zmdi-flip:before { + content: '\f167'; +} +.zmdi-flower-alt:before { + content: '\f168'; +} +.zmdi-flower:before { + content: '\f169'; +} +.zmdi-font:before { + content: '\f16a'; +} +.zmdi-fullscreen-alt:before { + content: '\f16b'; +} +.zmdi-fullscreen-exit:before { + content: '\f16c'; +} +.zmdi-fullscreen:before { + content: '\f16d'; +} +.zmdi-functions:before { + content: '\f16e'; +} +.zmdi-gas-station:before { + content: '\f16f'; +} +.zmdi-gesture:before { + content: '\f170'; +} +.zmdi-globe-alt:before { + content: '\f171'; +} +.zmdi-globe-lock:before { + content: '\f172'; +} +.zmdi-globe:before { + content: '\f173'; +} +.zmdi-graduation-cap:before { + content: '\f174'; +} +.zmdi-home:before { + content: '\f175'; +} +.zmdi-hospital-alt:before { + content: '\f176'; +} +.zmdi-hospital:before { + content: '\f177'; +} +.zmdi-hotel:before { + content: '\f178'; +} +.zmdi-hourglass-alt:before { + content: '\f179'; +} +.zmdi-hourglass-outline:before { + content: '\f17a'; +} +.zmdi-hourglass:before { + content: '\f17b'; +} +.zmdi-http:before { + content: '\f17c'; +} +.zmdi-image-alt:before { + content: '\f17d'; +} +.zmdi-image-o:before { + content: '\f17e'; +} +.zmdi-image:before { + content: '\f17f'; +} +.zmdi-inbox:before { + content: '\f180'; +} +.zmdi-invert-colors-off:before { + content: '\f181'; +} +.zmdi-invert-colors:before { + content: '\f182'; +} +.zmdi-key:before { + content: '\f183'; +} +.zmdi-label-alt-outline:before { + content: '\f184'; +} +.zmdi-label-alt:before { + content: '\f185'; +} +.zmdi-label-heart:before { + content: '\f186'; +} +.zmdi-label:before { + content: '\f187'; +} +.zmdi-labels:before { + content: '\f188'; +} +.zmdi-lamp:before { + content: '\f189'; +} +.zmdi-landscape:before { + content: '\f18a'; +} +.zmdi-layers-off:before { + content: '\f18b'; +} +.zmdi-layers:before { + content: '\f18c'; +} +.zmdi-library:before { + content: '\f18d'; +} +.zmdi-link:before { + content: '\f18e'; +} +.zmdi-lock-open:before { + content: '\f18f'; +} +.zmdi-lock-outline:before { + content: '\f190'; +} +.zmdi-lock:before { + content: '\f191'; +} +.zmdi-mail-reply-all:before { + content: '\f192'; +} +.zmdi-mail-reply:before { + content: '\f193'; +} +.zmdi-mail-send:before { + content: '\f194'; +} +.zmdi-mall:before { + content: '\f195'; +} +.zmdi-map:before { + content: '\f196'; +} +.zmdi-menu:before { + content: '\f197'; +} +.zmdi-money-box:before { + content: '\f198'; +} +.zmdi-money-off:before { + content: '\f199'; +} +.zmdi-money:before { + content: '\f19a'; +} +.zmdi-more-vert:before { + content: '\f19b'; +} +.zmdi-more:before { + content: '\f19c'; +} +.zmdi-movie-alt:before { + content: '\f19d'; +} +.zmdi-movie:before { + content: '\f19e'; +} +.zmdi-nature-people:before { + content: '\f19f'; +} +.zmdi-nature:before { + content: '\f1a0'; +} +.zmdi-navigation:before { + content: '\f1a1'; +} +.zmdi-open-in-browser:before { + content: '\f1a2'; +} +.zmdi-open-in-new:before { + content: '\f1a3'; +} +.zmdi-palette:before { + content: '\f1a4'; +} +.zmdi-parking:before { + content: '\f1a5'; +} +.zmdi-pin-account:before { + content: '\f1a6'; +} +.zmdi-pin-assistant:before { + content: '\f1a7'; +} +.zmdi-pin-drop:before { + content: '\f1a8'; +} +.zmdi-pin-help:before { + content: '\f1a9'; +} +.zmdi-pin-off:before { + content: '\f1aa'; +} +.zmdi-pin:before { + content: '\f1ab'; +} +.zmdi-pizza:before { + content: '\f1ac'; +} +.zmdi-plaster:before { + content: '\f1ad'; +} +.zmdi-power-setting:before { + content: '\f1ae'; +} +.zmdi-power:before { + content: '\f1af'; +} +.zmdi-print:before { + content: '\f1b0'; +} +.zmdi-puzzle-piece:before { + content: '\f1b1'; +} +.zmdi-quote:before { + content: '\f1b2'; +} +.zmdi-railway:before { + content: '\f1b3'; +} +.zmdi-receipt:before { + content: '\f1b4'; +} +.zmdi-refresh-alt:before { + content: '\f1b5'; +} +.zmdi-refresh-sync-alert:before { + content: '\f1b6'; +} +.zmdi-refresh-sync-off:before { + content: '\f1b7'; +} +.zmdi-refresh-sync:before { + content: '\f1b8'; +} +.zmdi-refresh:before { + content: '\f1b9'; +} +.zmdi-roller:before { + content: '\f1ba'; +} +.zmdi-ruler:before { + content: '\f1bb'; +} +.zmdi-scissors:before { + content: '\f1bc'; +} +.zmdi-screen-rotation-lock:before { + content: '\f1bd'; +} +.zmdi-screen-rotation:before { + content: '\f1be'; +} +.zmdi-search-for:before { + content: '\f1bf'; +} +.zmdi-search-in-file:before { + content: '\f1c0'; +} +.zmdi-search-in-page:before { + content: '\f1c1'; +} +.zmdi-search-replace:before { + content: '\f1c2'; +} +.zmdi-search:before { + content: '\f1c3'; +} +.zmdi-seat:before { + content: '\f1c4'; +} +.zmdi-settings-square:before { + content: '\f1c5'; +} +.zmdi-settings:before { + content: '\f1c6'; +} +.zmdi-shield-check:before { + content: '\f1c7'; +} +.zmdi-shield-security:before { + content: '\f1c8'; +} +.zmdi-shopping-basket:before { + content: '\f1c9'; +} +.zmdi-shopping-cart-plus:before { + content: '\f1ca'; +} +.zmdi-shopping-cart:before { + content: '\f1cb'; +} +.zmdi-sign-in:before { + content: '\f1cc'; +} +.zmdi-sort-amount-asc:before { + content: '\f1cd'; +} +.zmdi-sort-amount-desc:before { + content: '\f1ce'; +} +.zmdi-sort-asc:before { + content: '\f1cf'; +} +.zmdi-sort-desc:before { + content: '\f1d0'; +} +.zmdi-spellcheck:before { + content: '\f1d1'; +} +.zmdi-storage:before { + content: '\f1d2'; +} +.zmdi-store-24:before { + content: '\f1d3'; +} +.zmdi-store:before { + content: '\f1d4'; +} +.zmdi-subway:before { + content: '\f1d5'; +} +.zmdi-sun:before { + content: '\f1d6'; +} +.zmdi-tab-unselected:before { + content: '\f1d7'; +} +.zmdi-tab:before { + content: '\f1d8'; +} +.zmdi-tag-close:before { + content: '\f1d9'; +} +.zmdi-tag-more:before { + content: '\f1da'; +} +.zmdi-tag:before { + content: '\f1db'; +} +.zmdi-thumb-down:before { + content: '\f1dc'; +} +.zmdi-thumb-up-down:before { + content: '\f1dd'; +} +.zmdi-thumb-up:before { + content: '\f1de'; +} +.zmdi-ticket-star:before { + content: '\f1df'; +} +.zmdi-toll:before { + content: '\f1e0'; +} +.zmdi-toys:before { + content: '\f1e1'; +} +.zmdi-traffic:before { + content: '\f1e2'; +} +.zmdi-translate:before { + content: '\f1e3'; +} +.zmdi-triangle-down:before { + content: '\f1e4'; +} +.zmdi-triangle-up:before { + content: '\f1e5'; +} +.zmdi-truck:before { + content: '\f1e6'; +} +.zmdi-turning-sign:before { + content: '\f1e7'; +} +.zmdi-wallpaper:before { + content: '\f1e8'; +} +.zmdi-washing-machine:before { + content: '\f1e9'; +} +.zmdi-window-maximize:before { + content: '\f1ea'; +} +.zmdi-window-minimize:before { + content: '\f1eb'; +} +.zmdi-window-restore:before { + content: '\f1ec'; +} +.zmdi-wrench:before { + content: '\f1ed'; +} +.zmdi-zoom-in:before { + content: '\f1ee'; +} +.zmdi-zoom-out:before { + content: '\f1ef'; +} +.zmdi-alert-circle-o:before { + content: '\f1f0'; +} +.zmdi-alert-circle:before { + content: '\f1f1'; +} +.zmdi-alert-octagon:before { + content: '\f1f2'; +} +.zmdi-alert-polygon:before { + content: '\f1f3'; +} +.zmdi-alert-triangle:before { + content: '\f1f4'; +} +.zmdi-help-outline:before { + content: '\f1f5'; +} +.zmdi-help:before { + content: '\f1f6'; +} +.zmdi-info-outline:before { + content: '\f1f7'; +} +.zmdi-info:before { + content: '\f1f8'; +} +.zmdi-notifications-active:before { + content: '\f1f9'; +} +.zmdi-notifications-add:before { + content: '\f1fa'; +} +.zmdi-notifications-none:before { + content: '\f1fb'; +} +.zmdi-notifications-off:before { + content: '\f1fc'; +} +.zmdi-notifications-paused:before { + content: '\f1fd'; +} +.zmdi-notifications:before { + content: '\f1fe'; +} +.zmdi-account-add:before { + content: '\f1ff'; +} +.zmdi-account-box-mail:before { + content: '\f200'; +} +.zmdi-account-box-o:before { + content: '\f201'; +} +.zmdi-account-box-phone:before { + content: '\f202'; +} +.zmdi-account-box:before { + content: '\f203'; +} +.zmdi-account-calendar:before { + content: '\f204'; +} +.zmdi-account-circle:before { + content: '\f205'; +} +.zmdi-account-o:before { + content: '\f206'; +} +.zmdi-account:before { + content: '\f207'; +} +.zmdi-accounts-add:before { + content: '\f208'; +} +.zmdi-accounts-alt:before { + content: '\f209'; +} +.zmdi-accounts-list-alt:before { + content: '\f20a'; +} +.zmdi-accounts-list:before { + content: '\f20b'; +} +.zmdi-accounts-outline:before { + content: '\f20c'; +} +.zmdi-accounts:before { + content: '\f20d'; +} +.zmdi-face:before { + content: '\f20e'; +} +.zmdi-female:before { + content: '\f20f'; +} +.zmdi-male-alt:before { + content: '\f210'; +} +.zmdi-male-female:before { + content: '\f211'; +} +.zmdi-male:before { + content: '\f212'; +} +.zmdi-mood-bad:before { + content: '\f213'; +} +.zmdi-mood:before { + content: '\f214'; +} +.zmdi-run:before { + content: '\f215'; +} +.zmdi-walk:before { + content: '\f216'; +} +.zmdi-cloud-box:before { + content: '\f217'; +} +.zmdi-cloud-circle:before { + content: '\f218'; +} +.zmdi-cloud-done:before { + content: '\f219'; +} +.zmdi-cloud-download:before { + content: '\f21a'; +} +.zmdi-cloud-off:before { + content: '\f21b'; +} +.zmdi-cloud-outline-alt:before { + content: '\f21c'; +} +.zmdi-cloud-outline:before { + content: '\f21d'; +} +.zmdi-cloud-upload:before { + content: '\f21e'; +} +.zmdi-cloud:before { + content: '\f21f'; +} +.zmdi-download:before { + content: '\f220'; +} +.zmdi-file-plus:before { + content: '\f221'; +} +.zmdi-file-text:before { + content: '\f222'; +} +.zmdi-file:before { + content: '\f223'; +} +.zmdi-folder-outline:before { + content: '\f224'; +} +.zmdi-folder-person:before { + content: '\f225'; +} +.zmdi-folder-star-alt:before { + content: '\f226'; +} +.zmdi-folder-star:before { + content: '\f227'; +} +.zmdi-folder:before { + content: '\f228'; +} +.zmdi-gif:before { + content: '\f229'; +} +.zmdi-upload:before { + content: '\f22a'; +} +.zmdi-border-all:before { + content: '\f22b'; +} +.zmdi-border-bottom:before { + content: '\f22c'; +} +.zmdi-border-clear:before { + content: '\f22d'; +} +.zmdi-border-color:before { + content: '\f22e'; +} +.zmdi-border-horizontal:before { + content: '\f22f'; +} +.zmdi-border-inner:before { + content: '\f230'; +} +.zmdi-border-left:before { + content: '\f231'; +} +.zmdi-border-outer:before { + content: '\f232'; +} +.zmdi-border-right:before { + content: '\f233'; +} +.zmdi-border-style:before { + content: '\f234'; +} +.zmdi-border-top:before { + content: '\f235'; +} +.zmdi-border-vertical:before { + content: '\f236'; +} +.zmdi-copy:before { + content: '\f237'; +} +.zmdi-crop:before { + content: '\f238'; +} +.zmdi-format-align-center:before { + content: '\f239'; +} +.zmdi-format-align-justify:before { + content: '\f23a'; +} +.zmdi-format-align-left:before { + content: '\f23b'; +} +.zmdi-format-align-right:before { + content: '\f23c'; +} +.zmdi-format-bold:before { + content: '\f23d'; +} +.zmdi-format-clear-all:before { + content: '\f23e'; +} +.zmdi-format-clear:before { + content: '\f23f'; +} +.zmdi-format-color-fill:before { + content: '\f240'; +} +.zmdi-format-color-reset:before { + content: '\f241'; +} +.zmdi-format-color-text:before { + content: '\f242'; +} +.zmdi-format-indent-decrease:before { + content: '\f243'; +} +.zmdi-format-indent-increase:before { + content: '\f244'; +} +.zmdi-format-italic:before { + content: '\f245'; +} +.zmdi-format-line-spacing:before { + content: '\f246'; +} +.zmdi-format-list-bulleted:before { + content: '\f247'; +} +.zmdi-format-list-numbered:before { + content: '\f248'; +} +.zmdi-format-ltr:before { + content: '\f249'; +} +.zmdi-format-rtl:before { + content: '\f24a'; +} +.zmdi-format-size:before { + content: '\f24b'; +} +.zmdi-format-strikethrough-s:before { + content: '\f24c'; +} +.zmdi-format-strikethrough:before { + content: '\f24d'; +} +.zmdi-format-subject:before { + content: '\f24e'; +} +.zmdi-format-underlined:before { + content: '\f24f'; +} +.zmdi-format-valign-bottom:before { + content: '\f250'; +} +.zmdi-format-valign-center:before { + content: '\f251'; +} +.zmdi-format-valign-top:before { + content: '\f252'; +} +.zmdi-redo:before { + content: '\f253'; +} +.zmdi-select-all:before { + content: '\f254'; +} +.zmdi-space-bar:before { + content: '\f255'; +} +.zmdi-text-format:before { + content: '\f256'; +} +.zmdi-transform:before { + content: '\f257'; +} +.zmdi-undo:before { + content: '\f258'; +} +.zmdi-wrap-text:before { + content: '\f259'; +} +.zmdi-comment-alert:before { + content: '\f25a'; +} +.zmdi-comment-alt-text:before { + content: '\f25b'; +} +.zmdi-comment-alt:before { + content: '\f25c'; +} +.zmdi-comment-edit:before { + content: '\f25d'; +} +.zmdi-comment-image:before { + content: '\f25e'; +} +.zmdi-comment-list:before { + content: '\f25f'; +} +.zmdi-comment-more:before { + content: '\f260'; +} +.zmdi-comment-outline:before { + content: '\f261'; +} +.zmdi-comment-text-alt:before { + content: '\f262'; +} +.zmdi-comment-text:before { + content: '\f263'; +} +.zmdi-comment-video:before { + content: '\f264'; +} +.zmdi-comment:before { + content: '\f265'; +} +.zmdi-comments:before { + content: '\f266'; +} +.zmdi-check-all:before { + content: '\f267'; +} +.zmdi-check-circle-u:before { + content: '\f268'; +} +.zmdi-check-circle:before { + content: '\f269'; +} +.zmdi-check-square:before { + content: '\f26a'; +} +.zmdi-check:before { + content: '\f26b'; +} +.zmdi-circle-o:before { + content: '\f26c'; +} +.zmdi-circle:before { + content: '\f26d'; +} +.zmdi-dot-circle-alt:before { + content: '\f26e'; +} +.zmdi-dot-circle:before { + content: '\f26f'; +} +.zmdi-minus-circle-outline:before { + content: '\f270'; +} +.zmdi-minus-circle:before { + content: '\f271'; +} +.zmdi-minus-square:before { + content: '\f272'; +} +.zmdi-minus:before { + content: '\f273'; +} +.zmdi-plus-circle-o-duplicate:before { + content: '\f274'; +} +.zmdi-plus-circle-o:before { + content: '\f275'; +} +.zmdi-plus-circle:before { + content: '\f276'; +} +.zmdi-plus-square:before { + content: '\f277'; +} +.zmdi-plus:before { + content: '\f278'; +} +.zmdi-square-o:before { + content: '\f279'; +} +.zmdi-star-circle:before { + content: '\f27a'; +} +.zmdi-star-half:before { + content: '\f27b'; +} +.zmdi-star-outline:before { + content: '\f27c'; +} +.zmdi-star:before { + content: '\f27d'; +} +.zmdi-bluetooth-connected:before { + content: '\f27e'; +} +.zmdi-bluetooth-off:before { + content: '\f27f'; +} +.zmdi-bluetooth-search:before { + content: '\f280'; +} +.zmdi-bluetooth-setting:before { + content: '\f281'; +} +.zmdi-bluetooth:before { + content: '\f282'; +} +.zmdi-camera-add:before { + content: '\f283'; +} +.zmdi-camera-alt:before { + content: '\f284'; +} +.zmdi-camera-bw:before { + content: '\f285'; +} +.zmdi-camera-front:before { + content: '\f286'; +} +.zmdi-camera-mic:before { + content: '\f287'; +} +.zmdi-camera-party-mode:before { + content: '\f288'; +} +.zmdi-camera-rear:before { + content: '\f289'; +} +.zmdi-camera-roll:before { + content: '\f28a'; +} +.zmdi-camera-switch:before { + content: '\f28b'; +} +.zmdi-camera:before { + content: '\f28c'; +} +.zmdi-card-alert:before { + content: '\f28d'; +} +.zmdi-card-off:before { + content: '\f28e'; +} +.zmdi-card-sd:before { + content: '\f28f'; +} +.zmdi-card-sim:before { + content: '\f290'; +} +.zmdi-desktop-mac:before { + content: '\f291'; +} +.zmdi-desktop-windows:before { + content: '\f292'; +} +.zmdi-device-hub:before { + content: '\f293'; +} +.zmdi-devices-off:before { + content: '\f294'; +} +.zmdi-devices:before { + content: '\f295'; +} +.zmdi-dock:before { + content: '\f296'; +} +.zmdi-floppy:before { + content: '\f297'; +} +.zmdi-gamepad:before { + content: '\f298'; +} +.zmdi-gps-dot:before { + content: '\f299'; +} +.zmdi-gps-off:before { + content: '\f29a'; +} +.zmdi-gps:before { + content: '\f29b'; +} +.zmdi-headset-mic:before { + content: '\f29c'; +} +.zmdi-headset:before { + content: '\f29d'; +} +.zmdi-input-antenna:before { + content: '\f29e'; +} +.zmdi-input-composite:before { + content: '\f29f'; +} +.zmdi-input-hdmi:before { + content: '\f2a0'; +} +.zmdi-input-power:before { + content: '\f2a1'; +} +.zmdi-input-svideo:before { + content: '\f2a2'; +} +.zmdi-keyboard-hide:before { + content: '\f2a3'; +} +.zmdi-keyboard:before { + content: '\f2a4'; +} +.zmdi-laptop-chromebook:before { + content: '\f2a5'; +} +.zmdi-laptop-mac:before { + content: '\f2a6'; +} +.zmdi-laptop:before { + content: '\f2a7'; +} +.zmdi-mic-off:before { + content: '\f2a8'; +} +.zmdi-mic-outline:before { + content: '\f2a9'; +} +.zmdi-mic-setting:before { + content: '\f2aa'; +} +.zmdi-mic:before { + content: '\f2ab'; +} +.zmdi-mouse:before { + content: '\f2ac'; +} +.zmdi-network-alert:before { + content: '\f2ad'; +} +.zmdi-network-locked:before { + content: '\f2ae'; +} +.zmdi-network-off:before { + content: '\f2af'; +} +.zmdi-network-outline:before { + content: '\f2b0'; +} +.zmdi-network-setting:before { + content: '\f2b1'; +} +.zmdi-network:before { + content: '\f2b2'; +} +.zmdi-phone-bluetooth:before { + content: '\f2b3'; +} +.zmdi-phone-end:before { + content: '\f2b4'; +} +.zmdi-phone-forwarded:before { + content: '\f2b5'; +} +.zmdi-phone-in-talk:before { + content: '\f2b6'; +} +.zmdi-phone-locked:before { + content: '\f2b7'; +} +.zmdi-phone-missed:before { + content: '\f2b8'; +} +.zmdi-phone-msg:before { + content: '\f2b9'; +} +.zmdi-phone-paused:before { + content: '\f2ba'; +} +.zmdi-phone-ring:before { + content: '\f2bb'; +} +.zmdi-phone-setting:before { + content: '\f2bc'; +} +.zmdi-phone-sip:before { + content: '\f2bd'; +} +.zmdi-phone:before { + content: '\f2be'; +} +.zmdi-portable-wifi-changes:before { + content: '\f2bf'; +} +.zmdi-portable-wifi-off:before { + content: '\f2c0'; +} +.zmdi-portable-wifi:before { + content: '\f2c1'; +} +.zmdi-radio:before { + content: '\f2c2'; +} +.zmdi-reader:before { + content: '\f2c3'; +} +.zmdi-remote-control-alt:before { + content: '\f2c4'; +} +.zmdi-remote-control:before { + content: '\f2c5'; +} +.zmdi-router:before { + content: '\f2c6'; +} +.zmdi-scanner:before { + content: '\f2c7'; +} +.zmdi-smartphone-android:before { + content: '\f2c8'; +} +.zmdi-smartphone-download:before { + content: '\f2c9'; +} +.zmdi-smartphone-erase:before { + content: '\f2ca'; +} +.zmdi-smartphone-info:before { + content: '\f2cb'; +} +.zmdi-smartphone-iphone:before { + content: '\f2cc'; +} +.zmdi-smartphone-landscape-lock:before { + content: '\f2cd'; +} +.zmdi-smartphone-landscape:before { + content: '\f2ce'; +} +.zmdi-smartphone-lock:before { + content: '\f2cf'; +} +.zmdi-smartphone-portrait-lock:before { + content: '\f2d0'; +} +.zmdi-smartphone-ring:before { + content: '\f2d1'; +} +.zmdi-smartphone-setting:before { + content: '\f2d2'; +} +.zmdi-smartphone-setup:before { + content: '\f2d3'; +} +.zmdi-smartphone:before { + content: '\f2d4'; +} +.zmdi-speaker:before { + content: '\f2d5'; +} +.zmdi-tablet-android:before { + content: '\f2d6'; +} +.zmdi-tablet-mac:before { + content: '\f2d7'; +} +.zmdi-tablet:before { + content: '\f2d8'; +} +.zmdi-tv-alt-play:before { + content: '\f2d9'; +} +.zmdi-tv-list:before { + content: '\f2da'; +} +.zmdi-tv-play:before { + content: '\f2db'; +} +.zmdi-tv:before { + content: '\f2dc'; +} +.zmdi-usb:before { + content: '\f2dd'; +} +.zmdi-videocam-off:before { + content: '\f2de'; +} +.zmdi-videocam-switch:before { + content: '\f2df'; +} +.zmdi-videocam:before { + content: '\f2e0'; +} +.zmdi-watch:before { + content: '\f2e1'; +} +.zmdi-wifi-alt-2:before { + content: '\f2e2'; +} +.zmdi-wifi-alt:before { + content: '\f2e3'; +} +.zmdi-wifi-info:before { + content: '\f2e4'; +} +.zmdi-wifi-lock:before { + content: '\f2e5'; +} +.zmdi-wifi-off:before { + content: '\f2e6'; +} +.zmdi-wifi-outline:before { + content: '\f2e7'; +} +.zmdi-wifi:before { + content: '\f2e8'; +} +.zmdi-arrow-left-bottom:before { + content: '\f2e9'; +} +.zmdi-arrow-left:before { + content: '\f2ea'; +} +.zmdi-arrow-merge:before { + content: '\f2eb'; +} +.zmdi-arrow-missed:before { + content: '\f2ec'; +} +.zmdi-arrow-right-top:before { + content: '\f2ed'; +} +.zmdi-arrow-right:before { + content: '\f2ee'; +} +.zmdi-arrow-split:before { + content: '\f2ef'; +} +.zmdi-arrows:before { + content: '\f2f0'; +} +.zmdi-caret-down-circle:before { + content: '\f2f1'; +} +.zmdi-caret-down:before { + content: '\f2f2'; +} +.zmdi-caret-left-circle:before { + content: '\f2f3'; +} +.zmdi-caret-left:before { + content: '\f2f4'; +} +.zmdi-caret-right-circle:before { + content: '\f2f5'; +} +.zmdi-caret-right:before { + content: '\f2f6'; +} +.zmdi-caret-up-circle:before { + content: '\f2f7'; +} +.zmdi-caret-up:before { + content: '\f2f8'; +} +.zmdi-chevron-down:before { + content: '\f2f9'; +} +.zmdi-chevron-left:before { + content: '\f2fa'; +} +.zmdi-chevron-right:before { + content: '\f2fb'; +} +.zmdi-chevron-up:before { + content: '\f2fc'; +} +.zmdi-forward:before { + content: '\f2fd'; +} +.zmdi-long-arrow-down:before { + content: '\f2fe'; +} +.zmdi-long-arrow-left:before { + content: '\f2ff'; +} +.zmdi-long-arrow-return:before { + content: '\f300'; +} +.zmdi-long-arrow-right:before { + content: '\f301'; +} +.zmdi-long-arrow-tab:before { + content: '\f302'; +} +.zmdi-long-arrow-up:before { + content: '\f303'; +} +.zmdi-rotate-ccw:before { + content: '\f304'; +} +.zmdi-rotate-cw:before { + content: '\f305'; +} +.zmdi-rotate-left:before { + content: '\f306'; +} +.zmdi-rotate-right:before { + content: '\f307'; +} +.zmdi-square-down:before { + content: '\f308'; +} +.zmdi-square-right:before { + content: '\f309'; +} +.zmdi-swap-alt:before { + content: '\f30a'; +} +.zmdi-swap-vertical-circle:before { + content: '\f30b'; +} +.zmdi-swap-vertical:before { + content: '\f30c'; +} +.zmdi-swap:before { + content: '\f30d'; +} +.zmdi-trending-down:before { + content: '\f30e'; +} +.zmdi-trending-flat:before { + content: '\f30f'; +} +.zmdi-trending-up:before { + content: '\f310'; +} +.zmdi-unfold-less:before { + content: '\f311'; +} +.zmdi-unfold-more:before { + content: '\f312'; +} +.zmdi-apps:before { + content: '\f313'; +} +.zmdi-grid-off:before { + content: '\f314'; +} +.zmdi-grid:before { + content: '\f315'; +} +.zmdi-view-agenda:before { + content: '\f316'; +} +.zmdi-view-array:before { + content: '\f317'; +} +.zmdi-view-carousel:before { + content: '\f318'; +} +.zmdi-view-column:before { + content: '\f319'; +} +.zmdi-view-comfy:before { + content: '\f31a'; +} +.zmdi-view-compact:before { + content: '\f31b'; +} +.zmdi-view-dashboard:before { + content: '\f31c'; +} +.zmdi-view-day:before { + content: '\f31d'; +} +.zmdi-view-headline:before { + content: '\f31e'; +} +.zmdi-view-list-alt:before { + content: '\f31f'; +} +.zmdi-view-list:before { + content: '\f320'; +} +.zmdi-view-module:before { + content: '\f321'; +} +.zmdi-view-quilt:before { + content: '\f322'; +} +.zmdi-view-stream:before { + content: '\f323'; +} +.zmdi-view-subtitles:before { + content: '\f324'; +} +.zmdi-view-toc:before { + content: '\f325'; +} +.zmdi-view-web:before { + content: '\f326'; +} +.zmdi-view-week:before { + content: '\f327'; +} +.zmdi-widgets:before { + content: '\f328'; +} +.zmdi-alarm-check:before { + content: '\f329'; +} +.zmdi-alarm-off:before { + content: '\f32a'; +} +.zmdi-alarm-plus:before { + content: '\f32b'; +} +.zmdi-alarm-snooze:before { + content: '\f32c'; +} +.zmdi-alarm:before { + content: '\f32d'; +} +.zmdi-calendar-alt:before { + content: '\f32e'; +} +.zmdi-calendar-check:before { + content: '\f32f'; +} +.zmdi-calendar-close:before { + content: '\f330'; +} +.zmdi-calendar-note:before { + content: '\f331'; +} +.zmdi-calendar:before { + content: '\f332'; +} +.zmdi-time-countdown:before { + content: '\f333'; +} +.zmdi-time-interval:before { + content: '\f334'; +} +.zmdi-time-restore-setting:before { + content: '\f335'; +} +.zmdi-time-restore:before { + content: '\f336'; +} +.zmdi-time:before { + content: '\f337'; +} +.zmdi-timer-off:before { + content: '\f338'; +} +.zmdi-timer:before { + content: '\f339'; +} +.zmdi-android-alt:before { + content: '\f33a'; +} +.zmdi-android:before { + content: '\f33b'; +} +.zmdi-apple:before { + content: '\f33c'; +} +.zmdi-behance:before { + content: '\f33d'; +} +.zmdi-codepen:before { + content: '\f33e'; +} +.zmdi-dribbble:before { + content: '\f33f'; +} +.zmdi-dropbox:before { + content: '\f340'; +} +.zmdi-evernote:before { + content: '\f341'; +} +.zmdi-facebook-box:before { + content: '\f342'; +} +.zmdi-facebook:before { + content: '\f343'; +} +.zmdi-github-box:before { + content: '\f344'; +} +.zmdi-github:before { + content: '\f345'; +} +.zmdi-google-drive:before { + content: '\f346'; +} +.zmdi-google-earth:before { + content: '\f347'; +} +.zmdi-google-glass:before { + content: '\f348'; +} +.zmdi-google-maps:before { + content: '\f349'; +} +.zmdi-google-pages:before { + content: '\f34a'; +} +.zmdi-google-play:before { + content: '\f34b'; +} +.zmdi-google-plus-box:before { + content: '\f34c'; +} +.zmdi-google-plus:before { + content: '\f34d'; +} +.zmdi-google:before { + content: '\f34e'; +} +.zmdi-instagram:before { + content: '\f34f'; +} +.zmdi-language-css3:before { + content: '\f350'; +} +.zmdi-language-html5:before { + content: '\f351'; +} +.zmdi-language-javascript:before { + content: '\f352'; +} +.zmdi-language-python-alt:before { + content: '\f353'; +} +.zmdi-language-python:before { + content: '\f354'; +} +.zmdi-lastfm:before { + content: '\f355'; +} +.zmdi-linkedin-box:before { + content: '\f356'; +} +.zmdi-paypal:before { + content: '\f357'; +} +.zmdi-pinterest-box:before { + content: '\f358'; +} +.zmdi-pocket:before { + content: '\f359'; +} +.zmdi-polymer:before { + content: '\f35a'; +} +.zmdi-share:before { + content: '\f35b'; +} +.zmdi-stackoverflow:before { + content: '\f35c'; +} +.zmdi-steam-square:before { + content: '\f35d'; +} +.zmdi-steam:before { + content: '\f35e'; +} +.zmdi-twitter-box:before { + content: '\f35f'; +} +.zmdi-twitter:before { + content: '\f360'; +} +.zmdi-vk:before { + content: '\f361'; +} +.zmdi-wikipedia:before { + content: '\f362'; +} +.zmdi-windows:before { + content: '\f363'; +} +.zmdi-aspect-ratio-alt:before { + content: '\f364'; +} +.zmdi-aspect-ratio:before { + content: '\f365'; +} +.zmdi-blur-circular:before { + content: '\f366'; +} +.zmdi-blur-linear:before { + content: '\f367'; +} +.zmdi-blur-off:before { + content: '\f368'; +} +.zmdi-blur:before { + content: '\f369'; +} +.zmdi-brightness-2:before { + content: '\f36a'; +} +.zmdi-brightness-3:before { + content: '\f36b'; +} +.zmdi-brightness-4:before { + content: '\f36c'; +} +.zmdi-brightness-5:before { + content: '\f36d'; +} +.zmdi-brightness-6:before { + content: '\f36e'; +} +.zmdi-brightness-7:before { + content: '\f36f'; +} +.zmdi-brightness-auto:before { + content: '\f370'; +} +.zmdi-brightness-setting:before { + content: '\f371'; +} +.zmdi-broken-image:before { + content: '\f372'; +} +.zmdi-center-focus-strong:before { + content: '\f373'; +} +.zmdi-center-focus-weak:before { + content: '\f374'; +} +.zmdi-compare:before { + content: '\f375'; +} +.zmdi-crop-16-9:before { + content: '\f376'; +} +.zmdi-crop-3-2:before { + content: '\f377'; +} +.zmdi-crop-5-4:before { + content: '\f378'; +} +.zmdi-crop-7-5:before { + content: '\f379'; +} +.zmdi-crop-din:before { + content: '\f37a'; +} +.zmdi-crop-free:before { + content: '\f37b'; +} +.zmdi-crop-landscape:before { + content: '\f37c'; +} +.zmdi-crop-portrait:before { + content: '\f37d'; +} +.zmdi-crop-square:before { + content: '\f37e'; +} +.zmdi-exposure-alt:before { + content: '\f37f'; +} +.zmdi-exposure:before { + content: '\f380'; +} +.zmdi-filter-b-and-w:before { + content: '\f381'; +} +.zmdi-filter-center-focus:before { + content: '\f382'; +} +.zmdi-filter-frames:before { + content: '\f383'; +} +.zmdi-filter-tilt-shift:before { + content: '\f384'; +} +.zmdi-gradient:before { + content: '\f385'; +} +.zmdi-grain:before { + content: '\f386'; +} +.zmdi-graphic-eq:before { + content: '\f387'; +} +.zmdi-hdr-off:before { + content: '\f388'; +} +.zmdi-hdr-strong:before { + content: '\f389'; +} +.zmdi-hdr-weak:before { + content: '\f38a'; +} +.zmdi-hdr:before { + content: '\f38b'; +} +.zmdi-iridescent:before { + content: '\f38c'; +} +.zmdi-leak-off:before { + content: '\f38d'; +} +.zmdi-leak:before { + content: '\f38e'; +} +.zmdi-looks:before { + content: '\f38f'; +} +.zmdi-loupe:before { + content: '\f390'; +} +.zmdi-panorama-horizontal:before { + content: '\f391'; +} +.zmdi-panorama-vertical:before { + content: '\f392'; +} +.zmdi-panorama-wide-angle:before { + content: '\f393'; +} +.zmdi-photo-size-select-large:before { + content: '\f394'; +} +.zmdi-photo-size-select-small:before { + content: '\f395'; +} +.zmdi-picture-in-picture:before { + content: '\f396'; +} +.zmdi-slideshow:before { + content: '\f397'; +} +.zmdi-texture:before { + content: '\f398'; +} +.zmdi-tonality:before { + content: '\f399'; +} +.zmdi-vignette:before { + content: '\f39a'; +} +.zmdi-wb-auto:before { + content: '\f39b'; +} +.zmdi-eject-alt:before { + content: '\f39c'; +} +.zmdi-eject:before { + content: '\f39d'; +} +.zmdi-equalizer:before { + content: '\f39e'; +} +.zmdi-fast-forward:before { + content: '\f39f'; +} +.zmdi-fast-rewind:before { + content: '\f3a0'; +} +.zmdi-forward-10:before { + content: '\f3a1'; +} +.zmdi-forward-30:before { + content: '\f3a2'; +} +.zmdi-forward-5:before { + content: '\f3a3'; +} +.zmdi-hearing:before { + content: '\f3a4'; +} +.zmdi-pause-circle-outline:before { + content: '\f3a5'; +} +.zmdi-pause-circle:before { + content: '\f3a6'; +} +.zmdi-pause:before { + content: '\f3a7'; +} +.zmdi-play-circle-outline:before { + content: '\f3a8'; +} +.zmdi-play-circle:before { + content: '\f3a9'; +} +.zmdi-play:before { + content: '\f3aa'; +} +.zmdi-playlist-audio:before { + content: '\f3ab'; +} +.zmdi-playlist-plus:before { + content: '\f3ac'; +} +.zmdi-repeat-one:before { + content: '\f3ad'; +} +.zmdi-repeat:before { + content: '\f3ae'; +} +.zmdi-replay-10:before { + content: '\f3af'; +} +.zmdi-replay-30:before { + content: '\f3b0'; +} +.zmdi-replay-5:before { + content: '\f3b1'; +} +.zmdi-replay:before { + content: '\f3b2'; +} +.zmdi-shuffle:before { + content: '\f3b3'; +} +.zmdi-skip-next:before { + content: '\f3b4'; +} +.zmdi-skip-previous:before { + content: '\f3b5'; +} +.zmdi-stop:before { + content: '\f3b6'; +} +.zmdi-surround-sound:before { + content: '\f3b7'; +} +.zmdi-tune:before { + content: '\f3b8'; +} +.zmdi-volume-down:before { + content: '\f3b9'; +} +.zmdi-volume-mute:before { + content: '\f3ba'; +} +.zmdi-volume-off:before { + content: '\f3bb'; +} +.zmdi-volume-up:before { + content: '\f3bc'; +} +.zmdi-n-1-square:before { + content: '\f3bd'; +} +.zmdi-n-2-square:before { + content: '\f3be'; +} +.zmdi-n-3-square:before { + content: '\f3bf'; +} +.zmdi-n-4-square:before { + content: '\f3c0'; +} +.zmdi-n-5-square:before { + content: '\f3c1'; +} +.zmdi-n-6-square:before { + content: '\f3c2'; +} +.zmdi-neg-1:before { + content: '\f3c3'; +} +.zmdi-neg-2:before { + content: '\f3c4'; +} +.zmdi-plus-1:before { + content: '\f3c5'; +} +.zmdi-plus-2:before { + content: '\f3c6'; +} +.zmdi-sec-10:before { + content: '\f3c7'; +} +.zmdi-sec-3:before { + content: '\f3c8'; +} +.zmdi-zero:before { + content: '\f3c9'; +} +.zmdi-airline-seat-flat-angled:before { + content: '\f3ca'; +} +.zmdi-airline-seat-flat:before { + content: '\f3cb'; +} +.zmdi-airline-seat-individual-suite:before { + content: '\f3cc'; +} +.zmdi-airline-seat-legroom-extra:before { + content: '\f3cd'; +} +.zmdi-airline-seat-legroom-normal:before { + content: '\f3ce'; +} +.zmdi-airline-seat-legroom-reduced:before { + content: '\f3cf'; +} +.zmdi-airline-seat-recline-extra:before { + content: '\f3d0'; +} +.zmdi-airline-seat-recline-normal:before { + content: '\f3d1'; +} +.zmdi-airplay:before { + content: '\f3d2'; +} +.zmdi-closed-caption:before { + content: '\f3d3'; +} +.zmdi-confirmation-number:before { + content: '\f3d4'; +} +.zmdi-developer-board:before { + content: '\f3d5'; +} +.zmdi-disc-full:before { + content: '\f3d6'; +} +.zmdi-explicit:before { + content: '\f3d7'; +} +.zmdi-flight-land:before { + content: '\f3d8'; +} +.zmdi-flight-takeoff:before { + content: '\f3d9'; +} +.zmdi-flip-to-back:before { + content: '\f3da'; +} +.zmdi-flip-to-front:before { + content: '\f3db'; +} +.zmdi-group-work:before { + content: '\f3dc'; +} +.zmdi-hd:before { + content: '\f3dd'; +} +.zmdi-hq:before { + content: '\f3de'; +} +.zmdi-markunread-mailbox:before { + content: '\f3df'; +} +.zmdi-memory:before { + content: '\f3e0'; +} +.zmdi-nfc:before { + content: '\f3e1'; +} +.zmdi-play-for-work:before { + content: '\f3e2'; +} +.zmdi-power-input:before { + content: '\f3e3'; +} +.zmdi-present-to-all:before { + content: '\f3e4'; +} +.zmdi-satellite:before { + content: '\f3e5'; +} +.zmdi-tap-and-play:before { + content: '\f3e6'; +} +.zmdi-vibration:before { + content: '\f3e7'; +} +.zmdi-voicemail:before { + content: '\f3e8'; +} +.zmdi-group:before { + content: '\f3e9'; +} +.zmdi-rss:before { + content: '\f3ea'; +} +.zmdi-shape:before { + content: '\f3eb'; +} +.zmdi-spinner:before { + content: '\f3ec'; +} +.zmdi-ungroup:before { + content: '\f3ed'; +} +.zmdi-500px:before { + content: '\f3ee'; +} +.zmdi-8tracks:before { + content: '\f3ef'; +} +.zmdi-amazon:before { + content: '\f3f0'; +} +.zmdi-blogger:before { + content: '\f3f1'; +} +.zmdi-delicious:before { + content: '\f3f2'; +} +.zmdi-disqus:before { + content: '\f3f3'; +} +.zmdi-flattr:before { + content: '\f3f4'; +} +.zmdi-flickr:before { + content: '\f3f5'; +} +.zmdi-github-alt:before { + content: '\f3f6'; +} +.zmdi-google-old:before { + content: '\f3f7'; +} +.zmdi-linkedin:before { + content: '\f3f8'; +} +.zmdi-odnoklassniki:before { + content: '\f3f9'; +} +.zmdi-outlook:before { + content: '\f3fa'; +} +.zmdi-paypal-alt:before { + content: '\f3fb'; +} +.zmdi-pinterest:before { + content: '\f3fc'; +} +.zmdi-playstation:before { + content: '\f3fd'; +} +.zmdi-reddit:before { + content: '\f3fe'; +} +.zmdi-skype:before { + content: '\f3ff'; +} +.zmdi-slideshare:before { + content: '\f400'; +} +.zmdi-soundcloud:before { + content: '\f401'; +} +.zmdi-tumblr:before { + content: '\f402'; +} +.zmdi-twitch:before { + content: '\f403'; +} +.zmdi-vimeo:before { + content: '\f404'; +} +.zmdi-whatsapp:before { + content: '\f405'; +} +.zmdi-xbox:before { + content: '\f406'; +} +.zmdi-yahoo:before { + content: '\f407'; +} +.zmdi-youtube-play:before { + content: '\f408'; +} +.zmdi-youtube:before { + content: '\f409'; +} +.zmdi-import-export:before { + content: '\f30c'; +} +.zmdi-swap-vertical-:before { + content: '\f30c'; +} +.zmdi-airplanemode-inactive:before { + content: '\f102'; +} +.zmdi-airplanemode-active:before { + content: '\f103'; +} +.zmdi-rate-review:before { + content: '\f103'; +} +.zmdi-comment-sign:before { + content: '\f25a'; +} +.zmdi-network-warning:before { + content: '\f2ad'; +} +.zmdi-shopping-cart-add:before { + content: '\f1ca'; +} +.zmdi-file-add:before { + content: '\f221'; +} +.zmdi-network-wifi-scan:before { + content: '\f2e4'; +} +.zmdi-collection-add:before { + content: '\f14e'; +} +.zmdi-format-playlist-add:before { + content: '\f3ac'; +} +.zmdi-format-queue-music:before { + content: '\f3ab'; +} +.zmdi-plus-box:before { + content: '\f277'; +} +.zmdi-tag-backspace:before { + content: '\f1d9'; +} +.zmdi-alarm-add:before { + content: '\f32b'; +} +.zmdi-battery-charging:before { + content: '\f114'; +} +.zmdi-daydream-setting:before { + content: '\f217'; +} +.zmdi-more-horiz:before { + content: '\f19c'; +} +.zmdi-book-photo:before { + content: '\f11b'; +} +.zmdi-incandescent:before { + content: '\f189'; +} +.zmdi-wb-iridescent:before { + content: '\f38c'; +} +.zmdi-calendar-remove:before { + content: '\f330'; +} +.zmdi-refresh-sync-disabled:before { + content: '\f1b7'; +} +.zmdi-refresh-sync-problem:before { + content: '\f1b6'; +} +.zmdi-crop-original:before { + content: '\f17e'; +} +.zmdi-power-off:before { + content: '\f1af'; +} +.zmdi-power-off-setting:before { + content: '\f1ae'; +} +.zmdi-leak-remove:before { + content: '\f38d'; +} +.zmdi-star-border:before { + content: '\f27c'; +} +.zmdi-brightness-low:before { + content: '\f36d'; +} +.zmdi-brightness-medium:before { + content: '\f36e'; +} +.zmdi-brightness-high:before { + content: '\f36f'; +} +.zmdi-smartphone-portrait:before { + content: '\f2d4'; +} +.zmdi-live-tv:before { + content: '\f2d9'; +} +.zmdi-format-textdirection-l-to-r:before { + content: '\f249'; +} +.zmdi-format-textdirection-r-to-l:before { + content: '\f24a'; +} +.zmdi-arrow-back:before { + content: '\f2ea'; +} +.zmdi-arrow-forward:before { + content: '\f2ee'; +} +.zmdi-arrow-in:before { + content: '\f2e9'; +} +.zmdi-arrow-out:before { + content: '\f2ed'; +} +.zmdi-rotate-90-degrees-ccw:before { + content: '\f304'; +} +.zmdi-adb:before { + content: '\f33a'; +} +.zmdi-network-wifi:before { + content: '\f2e8'; +} +.zmdi-network-wifi-alt:before { + content: '\f2e3'; +} +.zmdi-network-wifi-lock:before { + content: '\f2e5'; +} +.zmdi-network-wifi-off:before { + content: '\f2e6'; +} +.zmdi-network-wifi-outline:before { + content: '\f2e7'; +} +.zmdi-network-wifi-info:before { + content: '\f2e4'; +} +.zmdi-layers-clear:before { + content: '\f18b'; +} +.zmdi-colorize:before { + content: '\f15d'; +} +.zmdi-format-paint:before { + content: '\f1ba'; +} +.zmdi-format-quote:before { + content: '\f1b2'; +} +.zmdi-camera-monochrome-photos:before { + content: '\f285'; +} +.zmdi-sort-by-alpha:before { + content: '\f1cf'; +} +.zmdi-folder-shared:before { + content: '\f225'; +} +.zmdi-folder-special:before { + content: '\f226'; +} +.zmdi-comment-dots:before { + content: '\f260'; +} +.zmdi-reorder:before { + content: '\f31e'; +} +.zmdi-dehaze:before { + content: '\f197'; +} +.zmdi-sort:before { + content: '\f1ce'; +} +.zmdi-pages:before { + content: '\f34a'; +} +.zmdi-stack-overflow:before { + content: '\f35c'; +} +.zmdi-calendar-account:before { + content: '\f204'; +} +.zmdi-paste:before { + content: '\f109'; +} +.zmdi-cut:before { + content: '\f1bc'; +} +.zmdi-save:before { + content: '\f297'; +} +.zmdi-smartphone-code:before { + content: '\f139'; +} +.zmdi-directions-bike:before { + content: '\f117'; +} +.zmdi-directions-boat:before { + content: '\f11a'; +} +.zmdi-directions-bus:before { + content: '\f121'; +} +.zmdi-directions-car:before { + content: '\f125'; +} +.zmdi-directions-railway:before { + content: '\f1b3'; +} +.zmdi-directions-run:before { + content: '\f215'; +} +.zmdi-directions-subway:before { + content: '\f1d5'; +} +.zmdi-directions-walk:before { + content: '\f216'; +} +.zmdi-local-hotel:before { + content: '\f178'; +} +.zmdi-local-activity:before { + content: '\f1df'; +} +.zmdi-local-play:before { + content: '\f1df'; +} +.zmdi-local-airport:before { + content: '\f103'; +} +.zmdi-local-atm:before { + content: '\f198'; +} +.zmdi-local-bar:before { + content: '\f137'; +} +.zmdi-local-cafe:before { + content: '\f13b'; +} +.zmdi-local-car-wash:before { + content: '\f124'; +} +.zmdi-local-convenience-store:before { + content: '\f1d3'; +} +.zmdi-local-dining:before { + content: '\f153'; +} +.zmdi-local-drink:before { + content: '\f157'; +} +.zmdi-local-florist:before { + content: '\f168'; +} +.zmdi-local-gas-station:before { + content: '\f16f'; +} +.zmdi-local-grocery-store:before { + content: '\f1cb'; +} +.zmdi-local-hospital:before { + content: '\f177'; +} +.zmdi-local-laundry-service:before { + content: '\f1e9'; +} +.zmdi-local-library:before { + content: '\f18d'; +} +.zmdi-local-mall:before { + content: '\f195'; +} +.zmdi-local-movies:before { + content: '\f19d'; +} +.zmdi-local-offer:before { + content: '\f187'; +} +.zmdi-local-parking:before { + content: '\f1a5'; +} +.zmdi-local-parking:before { + content: '\f1a5'; +} +.zmdi-local-pharmacy:before { + content: '\f176'; +} +.zmdi-local-phone:before { + content: '\f2be'; +} +.zmdi-local-pizza:before { + content: '\f1ac'; +} +.zmdi-local-post-office:before { + content: '\f15a'; +} +.zmdi-local-printshop:before { + content: '\f1b0'; +} +.zmdi-local-see:before { + content: '\f28c'; +} +.zmdi-local-shipping:before { + content: '\f1e6'; +} +.zmdi-local-store:before { + content: '\f1d4'; +} +.zmdi-local-taxi:before { + content: '\f123'; +} +.zmdi-local-wc:before { + content: '\f211'; +} +.zmdi-my-location:before { + content: '\f299'; +} +.zmdi-directions:before { + content: '\f1e7'; +} diff --git a/packages/website/public/css/material-design-iconic-font.min.css b/packages/website/public/css/material-design-iconic-font.min.css new file mode 100755 index 000000000..e1a58fe2f --- /dev/null +++ b/packages/website/public/css/material-design-iconic-font.min.css @@ -0,0 +1 @@ +@font-face{font-family:Material-Design-Iconic-Font;src:url(../fonts/Material-Design-Iconic-Font.woff2?v=2.2.0) format('woff2'),url(../fonts/Material-Design-Iconic-Font.woff?v=2.2.0) format('woff'),url(../fonts/Material-Design-Iconic-Font.ttf?v=2.2.0) format('truetype')}.zmdi{display:inline-block;font:normal normal normal 14px/1 'Material-Design-Iconic-Font';font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.zmdi-hc-lg{font-size:1.33333333em;line-height:.75em;vertical-align:-15%}.zmdi-hc-2x{font-size:2em}.zmdi-hc-3x{font-size:3em}.zmdi-hc-4x{font-size:4em}.zmdi-hc-5x{font-size:5em}.zmdi-hc-fw{width:1.28571429em;text-align:center}.zmdi-hc-ul{padding-left:0;margin-left:2.14285714em;list-style-type:none}.zmdi-hc-ul>li{position:relative}.zmdi-hc-li{position:absolute;left:-2.14285714em;width:2.14285714em;top:.14285714em;text-align:center}.zmdi-hc-li.zmdi-hc-lg{left:-1.85714286em}.zmdi-hc-border{padding:.1em .25em;border:solid .1em #9e9e9e;border-radius:2px}.zmdi-hc-border-circle{padding:.1em .25em;border:solid .1em #9e9e9e;border-radius:50%}.zmdi.pull-left{float:left;margin-right:.15em}.zmdi.pull-right{float:right;margin-left:.15em}.zmdi-hc-spin{-webkit-animation:zmdi-spin 1.5s infinite linear;animation:zmdi-spin 1.5s infinite linear}.zmdi-hc-spin-reverse{-webkit-animation:zmdi-spin-reverse 1.5s infinite linear;animation:zmdi-spin-reverse 1.5s infinite linear}@-webkit-keyframes zmdi-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes zmdi-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@-webkit-keyframes zmdi-spin-reverse{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(-359deg);transform:rotate(-359deg)}}@keyframes zmdi-spin-reverse{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(-359deg);transform:rotate(-359deg)}}.zmdi-hc-rotate-90{-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.zmdi-hc-rotate-180{-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.zmdi-hc-rotate-270{-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.zmdi-hc-flip-horizontal{-webkit-transform:scale(-1,1);-ms-transform:scale(-1,1);transform:scale(-1,1)}.zmdi-hc-flip-vertical{-webkit-transform:scale(1,-1);-ms-transform:scale(1,-1);transform:scale(1,-1)}.zmdi-hc-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.zmdi-hc-stack-1x,.zmdi-hc-stack-2x{position:absolute;left:0;width:100%;text-align:center}.zmdi-hc-stack-1x{line-height:inherit}.zmdi-hc-stack-2x{font-size:2em}.zmdi-hc-inverse{color:#fff}.zmdi-3d-rotation:before{content:'\f101'}.zmdi-airplane-off:before{content:'\f102'}.zmdi-airplane:before{content:'\f103'}.zmdi-album:before{content:'\f104'}.zmdi-archive:before{content:'\f105'}.zmdi-assignment-account:before{content:'\f106'}.zmdi-assignment-alert:before{content:'\f107'}.zmdi-assignment-check:before{content:'\f108'}.zmdi-assignment-o:before{content:'\f109'}.zmdi-assignment-return:before{content:'\f10a'}.zmdi-assignment-returned:before{content:'\f10b'}.zmdi-assignment:before{content:'\f10c'}.zmdi-attachment-alt:before{content:'\f10d'}.zmdi-attachment:before{content:'\f10e'}.zmdi-audio:before{content:'\f10f'}.zmdi-badge-check:before{content:'\f110'}.zmdi-balance-wallet:before{content:'\f111'}.zmdi-balance:before{content:'\f112'}.zmdi-battery-alert:before{content:'\f113'}.zmdi-battery-flash:before{content:'\f114'}.zmdi-battery-unknown:before{content:'\f115'}.zmdi-battery:before{content:'\f116'}.zmdi-bike:before{content:'\f117'}.zmdi-block-alt:before{content:'\f118'}.zmdi-block:before{content:'\f119'}.zmdi-boat:before{content:'\f11a'}.zmdi-book-image:before{content:'\f11b'}.zmdi-book:before{content:'\f11c'}.zmdi-bookmark-outline:before{content:'\f11d'}.zmdi-bookmark:before{content:'\f11e'}.zmdi-brush:before{content:'\f11f'}.zmdi-bug:before{content:'\f120'}.zmdi-bus:before{content:'\f121'}.zmdi-cake:before{content:'\f122'}.zmdi-car-taxi:before{content:'\f123'}.zmdi-car-wash:before{content:'\f124'}.zmdi-car:before{content:'\f125'}.zmdi-card-giftcard:before{content:'\f126'}.zmdi-card-membership:before{content:'\f127'}.zmdi-card-travel:before{content:'\f128'}.zmdi-card:before{content:'\f129'}.zmdi-case-check:before{content:'\f12a'}.zmdi-case-download:before{content:'\f12b'}.zmdi-case-play:before{content:'\f12c'}.zmdi-case:before{content:'\f12d'}.zmdi-cast-connected:before{content:'\f12e'}.zmdi-cast:before{content:'\f12f'}.zmdi-chart-donut:before{content:'\f130'}.zmdi-chart:before{content:'\f131'}.zmdi-city-alt:before{content:'\f132'}.zmdi-city:before{content:'\f133'}.zmdi-close-circle-o:before{content:'\f134'}.zmdi-close-circle:before{content:'\f135'}.zmdi-close:before{content:'\f136'}.zmdi-cocktail:before{content:'\f137'}.zmdi-code-setting:before{content:'\f138'}.zmdi-code-smartphone:before{content:'\f139'}.zmdi-code:before{content:'\f13a'}.zmdi-coffee:before{content:'\f13b'}.zmdi-collection-bookmark:before{content:'\f13c'}.zmdi-collection-case-play:before{content:'\f13d'}.zmdi-collection-folder-image:before{content:'\f13e'}.zmdi-collection-image-o:before{content:'\f13f'}.zmdi-collection-image:before{content:'\f140'}.zmdi-collection-item-1:before{content:'\f141'}.zmdi-collection-item-2:before{content:'\f142'}.zmdi-collection-item-3:before{content:'\f143'}.zmdi-collection-item-4:before{content:'\f144'}.zmdi-collection-item-5:before{content:'\f145'}.zmdi-collection-item-6:before{content:'\f146'}.zmdi-collection-item-7:before{content:'\f147'}.zmdi-collection-item-8:before{content:'\f148'}.zmdi-collection-item-9-plus:before{content:'\f149'}.zmdi-collection-item-9:before{content:'\f14a'}.zmdi-collection-item:before{content:'\f14b'}.zmdi-collection-music:before{content:'\f14c'}.zmdi-collection-pdf:before{content:'\f14d'}.zmdi-collection-plus:before{content:'\f14e'}.zmdi-collection-speaker:before{content:'\f14f'}.zmdi-collection-text:before{content:'\f150'}.zmdi-collection-video:before{content:'\f151'}.zmdi-compass:before{content:'\f152'}.zmdi-cutlery:before{content:'\f153'}.zmdi-delete:before{content:'\f154'}.zmdi-dialpad:before{content:'\f155'}.zmdi-dns:before{content:'\f156'}.zmdi-drink:before{content:'\f157'}.zmdi-edit:before{content:'\f158'}.zmdi-email-open:before{content:'\f159'}.zmdi-email:before{content:'\f15a'}.zmdi-eye-off:before{content:'\f15b'}.zmdi-eye:before{content:'\f15c'}.zmdi-eyedropper:before{content:'\f15d'}.zmdi-favorite-outline:before{content:'\f15e'}.zmdi-favorite:before{content:'\f15f'}.zmdi-filter-list:before{content:'\f160'}.zmdi-fire:before{content:'\f161'}.zmdi-flag:before{content:'\f162'}.zmdi-flare:before{content:'\f163'}.zmdi-flash-auto:before{content:'\f164'}.zmdi-flash-off:before{content:'\f165'}.zmdi-flash:before{content:'\f166'}.zmdi-flip:before{content:'\f167'}.zmdi-flower-alt:before{content:'\f168'}.zmdi-flower:before{content:'\f169'}.zmdi-font:before{content:'\f16a'}.zmdi-fullscreen-alt:before{content:'\f16b'}.zmdi-fullscreen-exit:before{content:'\f16c'}.zmdi-fullscreen:before{content:'\f16d'}.zmdi-functions:before{content:'\f16e'}.zmdi-gas-station:before{content:'\f16f'}.zmdi-gesture:before{content:'\f170'}.zmdi-globe-alt:before{content:'\f171'}.zmdi-globe-lock:before{content:'\f172'}.zmdi-globe:before{content:'\f173'}.zmdi-graduation-cap:before{content:'\f174'}.zmdi-home:before{content:'\f175'}.zmdi-hospital-alt:before{content:'\f176'}.zmdi-hospital:before{content:'\f177'}.zmdi-hotel:before{content:'\f178'}.zmdi-hourglass-alt:before{content:'\f179'}.zmdi-hourglass-outline:before{content:'\f17a'}.zmdi-hourglass:before{content:'\f17b'}.zmdi-http:before{content:'\f17c'}.zmdi-image-alt:before{content:'\f17d'}.zmdi-image-o:before{content:'\f17e'}.zmdi-image:before{content:'\f17f'}.zmdi-inbox:before{content:'\f180'}.zmdi-invert-colors-off:before{content:'\f181'}.zmdi-invert-colors:before{content:'\f182'}.zmdi-key:before{content:'\f183'}.zmdi-label-alt-outline:before{content:'\f184'}.zmdi-label-alt:before{content:'\f185'}.zmdi-label-heart:before{content:'\f186'}.zmdi-label:before{content:'\f187'}.zmdi-labels:before{content:'\f188'}.zmdi-lamp:before{content:'\f189'}.zmdi-landscape:before{content:'\f18a'}.zmdi-layers-off:before{content:'\f18b'}.zmdi-layers:before{content:'\f18c'}.zmdi-library:before{content:'\f18d'}.zmdi-link:before{content:'\f18e'}.zmdi-lock-open:before{content:'\f18f'}.zmdi-lock-outline:before{content:'\f190'}.zmdi-lock:before{content:'\f191'}.zmdi-mail-reply-all:before{content:'\f192'}.zmdi-mail-reply:before{content:'\f193'}.zmdi-mail-send:before{content:'\f194'}.zmdi-mall:before{content:'\f195'}.zmdi-map:before{content:'\f196'}.zmdi-menu:before{content:'\f197'}.zmdi-money-box:before{content:'\f198'}.zmdi-money-off:before{content:'\f199'}.zmdi-money:before{content:'\f19a'}.zmdi-more-vert:before{content:'\f19b'}.zmdi-more:before{content:'\f19c'}.zmdi-movie-alt:before{content:'\f19d'}.zmdi-movie:before{content:'\f19e'}.zmdi-nature-people:before{content:'\f19f'}.zmdi-nature:before{content:'\f1a0'}.zmdi-navigation:before{content:'\f1a1'}.zmdi-open-in-browser:before{content:'\f1a2'}.zmdi-open-in-new:before{content:'\f1a3'}.zmdi-palette:before{content:'\f1a4'}.zmdi-parking:before{content:'\f1a5'}.zmdi-pin-account:before{content:'\f1a6'}.zmdi-pin-assistant:before{content:'\f1a7'}.zmdi-pin-drop:before{content:'\f1a8'}.zmdi-pin-help:before{content:'\f1a9'}.zmdi-pin-off:before{content:'\f1aa'}.zmdi-pin:before{content:'\f1ab'}.zmdi-pizza:before{content:'\f1ac'}.zmdi-plaster:before{content:'\f1ad'}.zmdi-power-setting:before{content:'\f1ae'}.zmdi-power:before{content:'\f1af'}.zmdi-print:before{content:'\f1b0'}.zmdi-puzzle-piece:before{content:'\f1b1'}.zmdi-quote:before{content:'\f1b2'}.zmdi-railway:before{content:'\f1b3'}.zmdi-receipt:before{content:'\f1b4'}.zmdi-refresh-alt:before{content:'\f1b5'}.zmdi-refresh-sync-alert:before{content:'\f1b6'}.zmdi-refresh-sync-off:before{content:'\f1b7'}.zmdi-refresh-sync:before{content:'\f1b8'}.zmdi-refresh:before{content:'\f1b9'}.zmdi-roller:before{content:'\f1ba'}.zmdi-ruler:before{content:'\f1bb'}.zmdi-scissors:before{content:'\f1bc'}.zmdi-screen-rotation-lock:before{content:'\f1bd'}.zmdi-screen-rotation:before{content:'\f1be'}.zmdi-search-for:before{content:'\f1bf'}.zmdi-search-in-file:before{content:'\f1c0'}.zmdi-search-in-page:before{content:'\f1c1'}.zmdi-search-replace:before{content:'\f1c2'}.zmdi-search:before{content:'\f1c3'}.zmdi-seat:before{content:'\f1c4'}.zmdi-settings-square:before{content:'\f1c5'}.zmdi-settings:before{content:'\f1c6'}.zmdi-shield-check:before{content:'\f1c7'}.zmdi-shield-security:before{content:'\f1c8'}.zmdi-shopping-basket:before{content:'\f1c9'}.zmdi-shopping-cart-plus:before{content:'\f1ca'}.zmdi-shopping-cart:before{content:'\f1cb'}.zmdi-sign-in:before{content:'\f1cc'}.zmdi-sort-amount-asc:before{content:'\f1cd'}.zmdi-sort-amount-desc:before{content:'\f1ce'}.zmdi-sort-asc:before{content:'\f1cf'}.zmdi-sort-desc:before{content:'\f1d0'}.zmdi-spellcheck:before{content:'\f1d1'}.zmdi-storage:before{content:'\f1d2'}.zmdi-store-24:before{content:'\f1d3'}.zmdi-store:before{content:'\f1d4'}.zmdi-subway:before{content:'\f1d5'}.zmdi-sun:before{content:'\f1d6'}.zmdi-tab-unselected:before{content:'\f1d7'}.zmdi-tab:before{content:'\f1d8'}.zmdi-tag-close:before{content:'\f1d9'}.zmdi-tag-more:before{content:'\f1da'}.zmdi-tag:before{content:'\f1db'}.zmdi-thumb-down:before{content:'\f1dc'}.zmdi-thumb-up-down:before{content:'\f1dd'}.zmdi-thumb-up:before{content:'\f1de'}.zmdi-ticket-star:before{content:'\f1df'}.zmdi-toll:before{content:'\f1e0'}.zmdi-toys:before{content:'\f1e1'}.zmdi-traffic:before{content:'\f1e2'}.zmdi-translate:before{content:'\f1e3'}.zmdi-triangle-down:before{content:'\f1e4'}.zmdi-triangle-up:before{content:'\f1e5'}.zmdi-truck:before{content:'\f1e6'}.zmdi-turning-sign:before{content:'\f1e7'}.zmdi-wallpaper:before{content:'\f1e8'}.zmdi-washing-machine:before{content:'\f1e9'}.zmdi-window-maximize:before{content:'\f1ea'}.zmdi-window-minimize:before{content:'\f1eb'}.zmdi-window-restore:before{content:'\f1ec'}.zmdi-wrench:before{content:'\f1ed'}.zmdi-zoom-in:before{content:'\f1ee'}.zmdi-zoom-out:before{content:'\f1ef'}.zmdi-alert-circle-o:before{content:'\f1f0'}.zmdi-alert-circle:before{content:'\f1f1'}.zmdi-alert-octagon:before{content:'\f1f2'}.zmdi-alert-polygon:before{content:'\f1f3'}.zmdi-alert-triangle:before{content:'\f1f4'}.zmdi-help-outline:before{content:'\f1f5'}.zmdi-help:before{content:'\f1f6'}.zmdi-info-outline:before{content:'\f1f7'}.zmdi-info:before{content:'\f1f8'}.zmdi-notifications-active:before{content:'\f1f9'}.zmdi-notifications-add:before{content:'\f1fa'}.zmdi-notifications-none:before{content:'\f1fb'}.zmdi-notifications-off:before{content:'\f1fc'}.zmdi-notifications-paused:before{content:'\f1fd'}.zmdi-notifications:before{content:'\f1fe'}.zmdi-account-add:before{content:'\f1ff'}.zmdi-account-box-mail:before{content:'\f200'}.zmdi-account-box-o:before{content:'\f201'}.zmdi-account-box-phone:before{content:'\f202'}.zmdi-account-box:before{content:'\f203'}.zmdi-account-calendar:before{content:'\f204'}.zmdi-account-circle:before{content:'\f205'}.zmdi-account-o:before{content:'\f206'}.zmdi-account:before{content:'\f207'}.zmdi-accounts-add:before{content:'\f208'}.zmdi-accounts-alt:before{content:'\f209'}.zmdi-accounts-list-alt:before{content:'\f20a'}.zmdi-accounts-list:before{content:'\f20b'}.zmdi-accounts-outline:before{content:'\f20c'}.zmdi-accounts:before{content:'\f20d'}.zmdi-face:before{content:'\f20e'}.zmdi-female:before{content:'\f20f'}.zmdi-male-alt:before{content:'\f210'}.zmdi-male-female:before{content:'\f211'}.zmdi-male:before{content:'\f212'}.zmdi-mood-bad:before{content:'\f213'}.zmdi-mood:before{content:'\f214'}.zmdi-run:before{content:'\f215'}.zmdi-walk:before{content:'\f216'}.zmdi-cloud-box:before{content:'\f217'}.zmdi-cloud-circle:before{content:'\f218'}.zmdi-cloud-done:before{content:'\f219'}.zmdi-cloud-download:before{content:'\f21a'}.zmdi-cloud-off:before{content:'\f21b'}.zmdi-cloud-outline-alt:before{content:'\f21c'}.zmdi-cloud-outline:before{content:'\f21d'}.zmdi-cloud-upload:before{content:'\f21e'}.zmdi-cloud:before{content:'\f21f'}.zmdi-download:before{content:'\f220'}.zmdi-file-plus:before{content:'\f221'}.zmdi-file-text:before{content:'\f222'}.zmdi-file:before{content:'\f223'}.zmdi-folder-outline:before{content:'\f224'}.zmdi-folder-person:before{content:'\f225'}.zmdi-folder-star-alt:before{content:'\f226'}.zmdi-folder-star:before{content:'\f227'}.zmdi-folder:before{content:'\f228'}.zmdi-gif:before{content:'\f229'}.zmdi-upload:before{content:'\f22a'}.zmdi-border-all:before{content:'\f22b'}.zmdi-border-bottom:before{content:'\f22c'}.zmdi-border-clear:before{content:'\f22d'}.zmdi-border-color:before{content:'\f22e'}.zmdi-border-horizontal:before{content:'\f22f'}.zmdi-border-inner:before{content:'\f230'}.zmdi-border-left:before{content:'\f231'}.zmdi-border-outer:before{content:'\f232'}.zmdi-border-right:before{content:'\f233'}.zmdi-border-style:before{content:'\f234'}.zmdi-border-top:before{content:'\f235'}.zmdi-border-vertical:before{content:'\f236'}.zmdi-copy:before{content:'\f237'}.zmdi-crop:before{content:'\f238'}.zmdi-format-align-center:before{content:'\f239'}.zmdi-format-align-justify:before{content:'\f23a'}.zmdi-format-align-left:before{content:'\f23b'}.zmdi-format-align-right:before{content:'\f23c'}.zmdi-format-bold:before{content:'\f23d'}.zmdi-format-clear-all:before{content:'\f23e'}.zmdi-format-clear:before{content:'\f23f'}.zmdi-format-color-fill:before{content:'\f240'}.zmdi-format-color-reset:before{content:'\f241'}.zmdi-format-color-text:before{content:'\f242'}.zmdi-format-indent-decrease:before{content:'\f243'}.zmdi-format-indent-increase:before{content:'\f244'}.zmdi-format-italic:before{content:'\f245'}.zmdi-format-line-spacing:before{content:'\f246'}.zmdi-format-list-bulleted:before{content:'\f247'}.zmdi-format-list-numbered:before{content:'\f248'}.zmdi-format-ltr:before{content:'\f249'}.zmdi-format-rtl:before{content:'\f24a'}.zmdi-format-size:before{content:'\f24b'}.zmdi-format-strikethrough-s:before{content:'\f24c'}.zmdi-format-strikethrough:before{content:'\f24d'}.zmdi-format-subject:before{content:'\f24e'}.zmdi-format-underlined:before{content:'\f24f'}.zmdi-format-valign-bottom:before{content:'\f250'}.zmdi-format-valign-center:before{content:'\f251'}.zmdi-format-valign-top:before{content:'\f252'}.zmdi-redo:before{content:'\f253'}.zmdi-select-all:before{content:'\f254'}.zmdi-space-bar:before{content:'\f255'}.zmdi-text-format:before{content:'\f256'}.zmdi-transform:before{content:'\f257'}.zmdi-undo:before{content:'\f258'}.zmdi-wrap-text:before{content:'\f259'}.zmdi-comment-alert:before{content:'\f25a'}.zmdi-comment-alt-text:before{content:'\f25b'}.zmdi-comment-alt:before{content:'\f25c'}.zmdi-comment-edit:before{content:'\f25d'}.zmdi-comment-image:before{content:'\f25e'}.zmdi-comment-list:before{content:'\f25f'}.zmdi-comment-more:before{content:'\f260'}.zmdi-comment-outline:before{content:'\f261'}.zmdi-comment-text-alt:before{content:'\f262'}.zmdi-comment-text:before{content:'\f263'}.zmdi-comment-video:before{content:'\f264'}.zmdi-comment:before{content:'\f265'}.zmdi-comments:before{content:'\f266'}.zmdi-check-all:before{content:'\f267'}.zmdi-check-circle-u:before{content:'\f268'}.zmdi-check-circle:before{content:'\f269'}.zmdi-check-square:before{content:'\f26a'}.zmdi-check:before{content:'\f26b'}.zmdi-circle-o:before{content:'\f26c'}.zmdi-circle:before{content:'\f26d'}.zmdi-dot-circle-alt:before{content:'\f26e'}.zmdi-dot-circle:before{content:'\f26f'}.zmdi-minus-circle-outline:before{content:'\f270'}.zmdi-minus-circle:before{content:'\f271'}.zmdi-minus-square:before{content:'\f272'}.zmdi-minus:before{content:'\f273'}.zmdi-plus-circle-o-duplicate:before{content:'\f274'}.zmdi-plus-circle-o:before{content:'\f275'}.zmdi-plus-circle:before{content:'\f276'}.zmdi-plus-square:before{content:'\f277'}.zmdi-plus:before{content:'\f278'}.zmdi-square-o:before{content:'\f279'}.zmdi-star-circle:before{content:'\f27a'}.zmdi-star-half:before{content:'\f27b'}.zmdi-star-outline:before{content:'\f27c'}.zmdi-star:before{content:'\f27d'}.zmdi-bluetooth-connected:before{content:'\f27e'}.zmdi-bluetooth-off:before{content:'\f27f'}.zmdi-bluetooth-search:before{content:'\f280'}.zmdi-bluetooth-setting:before{content:'\f281'}.zmdi-bluetooth:before{content:'\f282'}.zmdi-camera-add:before{content:'\f283'}.zmdi-camera-alt:before{content:'\f284'}.zmdi-camera-bw:before{content:'\f285'}.zmdi-camera-front:before{content:'\f286'}.zmdi-camera-mic:before{content:'\f287'}.zmdi-camera-party-mode:before{content:'\f288'}.zmdi-camera-rear:before{content:'\f289'}.zmdi-camera-roll:before{content:'\f28a'}.zmdi-camera-switch:before{content:'\f28b'}.zmdi-camera:before{content:'\f28c'}.zmdi-card-alert:before{content:'\f28d'}.zmdi-card-off:before{content:'\f28e'}.zmdi-card-sd:before{content:'\f28f'}.zmdi-card-sim:before{content:'\f290'}.zmdi-desktop-mac:before{content:'\f291'}.zmdi-desktop-windows:before{content:'\f292'}.zmdi-device-hub:before{content:'\f293'}.zmdi-devices-off:before{content:'\f294'}.zmdi-devices:before{content:'\f295'}.zmdi-dock:before{content:'\f296'}.zmdi-floppy:before{content:'\f297'}.zmdi-gamepad:before{content:'\f298'}.zmdi-gps-dot:before{content:'\f299'}.zmdi-gps-off:before{content:'\f29a'}.zmdi-gps:before{content:'\f29b'}.zmdi-headset-mic:before{content:'\f29c'}.zmdi-headset:before{content:'\f29d'}.zmdi-input-antenna:before{content:'\f29e'}.zmdi-input-composite:before{content:'\f29f'}.zmdi-input-hdmi:before{content:'\f2a0'}.zmdi-input-power:before{content:'\f2a1'}.zmdi-input-svideo:before{content:'\f2a2'}.zmdi-keyboard-hide:before{content:'\f2a3'}.zmdi-keyboard:before{content:'\f2a4'}.zmdi-laptop-chromebook:before{content:'\f2a5'}.zmdi-laptop-mac:before{content:'\f2a6'}.zmdi-laptop:before{content:'\f2a7'}.zmdi-mic-off:before{content:'\f2a8'}.zmdi-mic-outline:before{content:'\f2a9'}.zmdi-mic-setting:before{content:'\f2aa'}.zmdi-mic:before{content:'\f2ab'}.zmdi-mouse:before{content:'\f2ac'}.zmdi-network-alert:before{content:'\f2ad'}.zmdi-network-locked:before{content:'\f2ae'}.zmdi-network-off:before{content:'\f2af'}.zmdi-network-outline:before{content:'\f2b0'}.zmdi-network-setting:before{content:'\f2b1'}.zmdi-network:before{content:'\f2b2'}.zmdi-phone-bluetooth:before{content:'\f2b3'}.zmdi-phone-end:before{content:'\f2b4'}.zmdi-phone-forwarded:before{content:'\f2b5'}.zmdi-phone-in-talk:before{content:'\f2b6'}.zmdi-phone-locked:before{content:'\f2b7'}.zmdi-phone-missed:before{content:'\f2b8'}.zmdi-phone-msg:before{content:'\f2b9'}.zmdi-phone-paused:before{content:'\f2ba'}.zmdi-phone-ring:before{content:'\f2bb'}.zmdi-phone-setting:before{content:'\f2bc'}.zmdi-phone-sip:before{content:'\f2bd'}.zmdi-phone:before{content:'\f2be'}.zmdi-portable-wifi-changes:before{content:'\f2bf'}.zmdi-portable-wifi-off:before{content:'\f2c0'}.zmdi-portable-wifi:before{content:'\f2c1'}.zmdi-radio:before{content:'\f2c2'}.zmdi-reader:before{content:'\f2c3'}.zmdi-remote-control-alt:before{content:'\f2c4'}.zmdi-remote-control:before{content:'\f2c5'}.zmdi-router:before{content:'\f2c6'}.zmdi-scanner:before{content:'\f2c7'}.zmdi-smartphone-android:before{content:'\f2c8'}.zmdi-smartphone-download:before{content:'\f2c9'}.zmdi-smartphone-erase:before{content:'\f2ca'}.zmdi-smartphone-info:before{content:'\f2cb'}.zmdi-smartphone-iphone:before{content:'\f2cc'}.zmdi-smartphone-landscape-lock:before{content:'\f2cd'}.zmdi-smartphone-landscape:before{content:'\f2ce'}.zmdi-smartphone-lock:before{content:'\f2cf'}.zmdi-smartphone-portrait-lock:before{content:'\f2d0'}.zmdi-smartphone-ring:before{content:'\f2d1'}.zmdi-smartphone-setting:before{content:'\f2d2'}.zmdi-smartphone-setup:before{content:'\f2d3'}.zmdi-smartphone:before{content:'\f2d4'}.zmdi-speaker:before{content:'\f2d5'}.zmdi-tablet-android:before{content:'\f2d6'}.zmdi-tablet-mac:before{content:'\f2d7'}.zmdi-tablet:before{content:'\f2d8'}.zmdi-tv-alt-play:before{content:'\f2d9'}.zmdi-tv-list:before{content:'\f2da'}.zmdi-tv-play:before{content:'\f2db'}.zmdi-tv:before{content:'\f2dc'}.zmdi-usb:before{content:'\f2dd'}.zmdi-videocam-off:before{content:'\f2de'}.zmdi-videocam-switch:before{content:'\f2df'}.zmdi-videocam:before{content:'\f2e0'}.zmdi-watch:before{content:'\f2e1'}.zmdi-wifi-alt-2:before{content:'\f2e2'}.zmdi-wifi-alt:before{content:'\f2e3'}.zmdi-wifi-info:before{content:'\f2e4'}.zmdi-wifi-lock:before{content:'\f2e5'}.zmdi-wifi-off:before{content:'\f2e6'}.zmdi-wifi-outline:before{content:'\f2e7'}.zmdi-wifi:before{content:'\f2e8'}.zmdi-arrow-left-bottom:before{content:'\f2e9'}.zmdi-arrow-left:before{content:'\f2ea'}.zmdi-arrow-merge:before{content:'\f2eb'}.zmdi-arrow-missed:before{content:'\f2ec'}.zmdi-arrow-right-top:before{content:'\f2ed'}.zmdi-arrow-right:before{content:'\f2ee'}.zmdi-arrow-split:before{content:'\f2ef'}.zmdi-arrows:before{content:'\f2f0'}.zmdi-caret-down-circle:before{content:'\f2f1'}.zmdi-caret-down:before{content:'\f2f2'}.zmdi-caret-left-circle:before{content:'\f2f3'}.zmdi-caret-left:before{content:'\f2f4'}.zmdi-caret-right-circle:before{content:'\f2f5'}.zmdi-caret-right:before{content:'\f2f6'}.zmdi-caret-up-circle:before{content:'\f2f7'}.zmdi-caret-up:before{content:'\f2f8'}.zmdi-chevron-down:before{content:'\f2f9'}.zmdi-chevron-left:before{content:'\f2fa'}.zmdi-chevron-right:before{content:'\f2fb'}.zmdi-chevron-up:before{content:'\f2fc'}.zmdi-forward:before{content:'\f2fd'}.zmdi-long-arrow-down:before{content:'\f2fe'}.zmdi-long-arrow-left:before{content:'\f2ff'}.zmdi-long-arrow-return:before{content:'\f300'}.zmdi-long-arrow-right:before{content:'\f301'}.zmdi-long-arrow-tab:before{content:'\f302'}.zmdi-long-arrow-up:before{content:'\f303'}.zmdi-rotate-ccw:before{content:'\f304'}.zmdi-rotate-cw:before{content:'\f305'}.zmdi-rotate-left:before{content:'\f306'}.zmdi-rotate-right:before{content:'\f307'}.zmdi-square-down:before{content:'\f308'}.zmdi-square-right:before{content:'\f309'}.zmdi-swap-alt:before{content:'\f30a'}.zmdi-swap-vertical-circle:before{content:'\f30b'}.zmdi-swap-vertical:before{content:'\f30c'}.zmdi-swap:before{content:'\f30d'}.zmdi-trending-down:before{content:'\f30e'}.zmdi-trending-flat:before{content:'\f30f'}.zmdi-trending-up:before{content:'\f310'}.zmdi-unfold-less:before{content:'\f311'}.zmdi-unfold-more:before{content:'\f312'}.zmdi-apps:before{content:'\f313'}.zmdi-grid-off:before{content:'\f314'}.zmdi-grid:before{content:'\f315'}.zmdi-view-agenda:before{content:'\f316'}.zmdi-view-array:before{content:'\f317'}.zmdi-view-carousel:before{content:'\f318'}.zmdi-view-column:before{content:'\f319'}.zmdi-view-comfy:before{content:'\f31a'}.zmdi-view-compact:before{content:'\f31b'}.zmdi-view-dashboard:before{content:'\f31c'}.zmdi-view-day:before{content:'\f31d'}.zmdi-view-headline:before{content:'\f31e'}.zmdi-view-list-alt:before{content:'\f31f'}.zmdi-view-list:before{content:'\f320'}.zmdi-view-module:before{content:'\f321'}.zmdi-view-quilt:before{content:'\f322'}.zmdi-view-stream:before{content:'\f323'}.zmdi-view-subtitles:before{content:'\f324'}.zmdi-view-toc:before{content:'\f325'}.zmdi-view-web:before{content:'\f326'}.zmdi-view-week:before{content:'\f327'}.zmdi-widgets:before{content:'\f328'}.zmdi-alarm-check:before{content:'\f329'}.zmdi-alarm-off:before{content:'\f32a'}.zmdi-alarm-plus:before{content:'\f32b'}.zmdi-alarm-snooze:before{content:'\f32c'}.zmdi-alarm:before{content:'\f32d'}.zmdi-calendar-alt:before{content:'\f32e'}.zmdi-calendar-check:before{content:'\f32f'}.zmdi-calendar-close:before{content:'\f330'}.zmdi-calendar-note:before{content:'\f331'}.zmdi-calendar:before{content:'\f332'}.zmdi-time-countdown:before{content:'\f333'}.zmdi-time-interval:before{content:'\f334'}.zmdi-time-restore-setting:before{content:'\f335'}.zmdi-time-restore:before{content:'\f336'}.zmdi-time:before{content:'\f337'}.zmdi-timer-off:before{content:'\f338'}.zmdi-timer:before{content:'\f339'}.zmdi-android-alt:before{content:'\f33a'}.zmdi-android:before{content:'\f33b'}.zmdi-apple:before{content:'\f33c'}.zmdi-behance:before{content:'\f33d'}.zmdi-codepen:before{content:'\f33e'}.zmdi-dribbble:before{content:'\f33f'}.zmdi-dropbox:before{content:'\f340'}.zmdi-evernote:before{content:'\f341'}.zmdi-facebook-box:before{content:'\f342'}.zmdi-facebook:before{content:'\f343'}.zmdi-github-box:before{content:'\f344'}.zmdi-github:before{content:'\f345'}.zmdi-google-drive:before{content:'\f346'}.zmdi-google-earth:before{content:'\f347'}.zmdi-google-glass:before{content:'\f348'}.zmdi-google-maps:before{content:'\f349'}.zmdi-google-pages:before{content:'\f34a'}.zmdi-google-play:before{content:'\f34b'}.zmdi-google-plus-box:before{content:'\f34c'}.zmdi-google-plus:before{content:'\f34d'}.zmdi-google:before{content:'\f34e'}.zmdi-instagram:before{content:'\f34f'}.zmdi-language-css3:before{content:'\f350'}.zmdi-language-html5:before{content:'\f351'}.zmdi-language-javascript:before{content:'\f352'}.zmdi-language-python-alt:before{content:'\f353'}.zmdi-language-python:before{content:'\f354'}.zmdi-lastfm:before{content:'\f355'}.zmdi-linkedin-box:before{content:'\f356'}.zmdi-paypal:before{content:'\f357'}.zmdi-pinterest-box:before{content:'\f358'}.zmdi-pocket:before{content:'\f359'}.zmdi-polymer:before{content:'\f35a'}.zmdi-share:before{content:'\f35b'}.zmdi-stackoverflow:before{content:'\f35c'}.zmdi-steam-square:before{content:'\f35d'}.zmdi-steam:before{content:'\f35e'}.zmdi-twitter-box:before{content:'\f35f'}.zmdi-twitter:before{content:'\f360'}.zmdi-vk:before{content:'\f361'}.zmdi-wikipedia:before{content:'\f362'}.zmdi-windows:before{content:'\f363'}.zmdi-aspect-ratio-alt:before{content:'\f364'}.zmdi-aspect-ratio:before{content:'\f365'}.zmdi-blur-circular:before{content:'\f366'}.zmdi-blur-linear:before{content:'\f367'}.zmdi-blur-off:before{content:'\f368'}.zmdi-blur:before{content:'\f369'}.zmdi-brightness-2:before{content:'\f36a'}.zmdi-brightness-3:before{content:'\f36b'}.zmdi-brightness-4:before{content:'\f36c'}.zmdi-brightness-5:before{content:'\f36d'}.zmdi-brightness-6:before{content:'\f36e'}.zmdi-brightness-7:before{content:'\f36f'}.zmdi-brightness-auto:before{content:'\f370'}.zmdi-brightness-setting:before{content:'\f371'}.zmdi-broken-image:before{content:'\f372'}.zmdi-center-focus-strong:before{content:'\f373'}.zmdi-center-focus-weak:before{content:'\f374'}.zmdi-compare:before{content:'\f375'}.zmdi-crop-16-9:before{content:'\f376'}.zmdi-crop-3-2:before{content:'\f377'}.zmdi-crop-5-4:before{content:'\f378'}.zmdi-crop-7-5:before{content:'\f379'}.zmdi-crop-din:before{content:'\f37a'}.zmdi-crop-free:before{content:'\f37b'}.zmdi-crop-landscape:before{content:'\f37c'}.zmdi-crop-portrait:before{content:'\f37d'}.zmdi-crop-square:before{content:'\f37e'}.zmdi-exposure-alt:before{content:'\f37f'}.zmdi-exposure:before{content:'\f380'}.zmdi-filter-b-and-w:before{content:'\f381'}.zmdi-filter-center-focus:before{content:'\f382'}.zmdi-filter-frames:before{content:'\f383'}.zmdi-filter-tilt-shift:before{content:'\f384'}.zmdi-gradient:before{content:'\f385'}.zmdi-grain:before{content:'\f386'}.zmdi-graphic-eq:before{content:'\f387'}.zmdi-hdr-off:before{content:'\f388'}.zmdi-hdr-strong:before{content:'\f389'}.zmdi-hdr-weak:before{content:'\f38a'}.zmdi-hdr:before{content:'\f38b'}.zmdi-iridescent:before{content:'\f38c'}.zmdi-leak-off:before{content:'\f38d'}.zmdi-leak:before{content:'\f38e'}.zmdi-looks:before{content:'\f38f'}.zmdi-loupe:before{content:'\f390'}.zmdi-panorama-horizontal:before{content:'\f391'}.zmdi-panorama-vertical:before{content:'\f392'}.zmdi-panorama-wide-angle:before{content:'\f393'}.zmdi-photo-size-select-large:before{content:'\f394'}.zmdi-photo-size-select-small:before{content:'\f395'}.zmdi-picture-in-picture:before{content:'\f396'}.zmdi-slideshow:before{content:'\f397'}.zmdi-texture:before{content:'\f398'}.zmdi-tonality:before{content:'\f399'}.zmdi-vignette:before{content:'\f39a'}.zmdi-wb-auto:before{content:'\f39b'}.zmdi-eject-alt:before{content:'\f39c'}.zmdi-eject:before{content:'\f39d'}.zmdi-equalizer:before{content:'\f39e'}.zmdi-fast-forward:before{content:'\f39f'}.zmdi-fast-rewind:before{content:'\f3a0'}.zmdi-forward-10:before{content:'\f3a1'}.zmdi-forward-30:before{content:'\f3a2'}.zmdi-forward-5:before{content:'\f3a3'}.zmdi-hearing:before{content:'\f3a4'}.zmdi-pause-circle-outline:before{content:'\f3a5'}.zmdi-pause-circle:before{content:'\f3a6'}.zmdi-pause:before{content:'\f3a7'}.zmdi-play-circle-outline:before{content:'\f3a8'}.zmdi-play-circle:before{content:'\f3a9'}.zmdi-play:before{content:'\f3aa'}.zmdi-playlist-audio:before{content:'\f3ab'}.zmdi-playlist-plus:before{content:'\f3ac'}.zmdi-repeat-one:before{content:'\f3ad'}.zmdi-repeat:before{content:'\f3ae'}.zmdi-replay-10:before{content:'\f3af'}.zmdi-replay-30:before{content:'\f3b0'}.zmdi-replay-5:before{content:'\f3b1'}.zmdi-replay:before{content:'\f3b2'}.zmdi-shuffle:before{content:'\f3b3'}.zmdi-skip-next:before{content:'\f3b4'}.zmdi-skip-previous:before{content:'\f3b5'}.zmdi-stop:before{content:'\f3b6'}.zmdi-surround-sound:before{content:'\f3b7'}.zmdi-tune:before{content:'\f3b8'}.zmdi-volume-down:before{content:'\f3b9'}.zmdi-volume-mute:before{content:'\f3ba'}.zmdi-volume-off:before{content:'\f3bb'}.zmdi-volume-up:before{content:'\f3bc'}.zmdi-n-1-square:before{content:'\f3bd'}.zmdi-n-2-square:before{content:'\f3be'}.zmdi-n-3-square:before{content:'\f3bf'}.zmdi-n-4-square:before{content:'\f3c0'}.zmdi-n-5-square:before{content:'\f3c1'}.zmdi-n-6-square:before{content:'\f3c2'}.zmdi-neg-1:before{content:'\f3c3'}.zmdi-neg-2:before{content:'\f3c4'}.zmdi-plus-1:before{content:'\f3c5'}.zmdi-plus-2:before{content:'\f3c6'}.zmdi-sec-10:before{content:'\f3c7'}.zmdi-sec-3:before{content:'\f3c8'}.zmdi-zero:before{content:'\f3c9'}.zmdi-airline-seat-flat-angled:before{content:'\f3ca'}.zmdi-airline-seat-flat:before{content:'\f3cb'}.zmdi-airline-seat-individual-suite:before{content:'\f3cc'}.zmdi-airline-seat-legroom-extra:before{content:'\f3cd'}.zmdi-airline-seat-legroom-normal:before{content:'\f3ce'}.zmdi-airline-seat-legroom-reduced:before{content:'\f3cf'}.zmdi-airline-seat-recline-extra:before{content:'\f3d0'}.zmdi-airline-seat-recline-normal:before{content:'\f3d1'}.zmdi-airplay:before{content:'\f3d2'}.zmdi-closed-caption:before{content:'\f3d3'}.zmdi-confirmation-number:before{content:'\f3d4'}.zmdi-developer-board:before{content:'\f3d5'}.zmdi-disc-full:before{content:'\f3d6'}.zmdi-explicit:before{content:'\f3d7'}.zmdi-flight-land:before{content:'\f3d8'}.zmdi-flight-takeoff:before{content:'\f3d9'}.zmdi-flip-to-back:before{content:'\f3da'}.zmdi-flip-to-front:before{content:'\f3db'}.zmdi-group-work:before{content:'\f3dc'}.zmdi-hd:before{content:'\f3dd'}.zmdi-hq:before{content:'\f3de'}.zmdi-markunread-mailbox:before{content:'\f3df'}.zmdi-memory:before{content:'\f3e0'}.zmdi-nfc:before{content:'\f3e1'}.zmdi-play-for-work:before{content:'\f3e2'}.zmdi-power-input:before{content:'\f3e3'}.zmdi-present-to-all:before{content:'\f3e4'}.zmdi-satellite:before{content:'\f3e5'}.zmdi-tap-and-play:before{content:'\f3e6'}.zmdi-vibration:before{content:'\f3e7'}.zmdi-voicemail:before{content:'\f3e8'}.zmdi-group:before{content:'\f3e9'}.zmdi-rss:before{content:'\f3ea'}.zmdi-shape:before{content:'\f3eb'}.zmdi-spinner:before{content:'\f3ec'}.zmdi-ungroup:before{content:'\f3ed'}.zmdi-500px:before{content:'\f3ee'}.zmdi-8tracks:before{content:'\f3ef'}.zmdi-amazon:before{content:'\f3f0'}.zmdi-blogger:before{content:'\f3f1'}.zmdi-delicious:before{content:'\f3f2'}.zmdi-disqus:before{content:'\f3f3'}.zmdi-flattr:before{content:'\f3f4'}.zmdi-flickr:before{content:'\f3f5'}.zmdi-github-alt:before{content:'\f3f6'}.zmdi-google-old:before{content:'\f3f7'}.zmdi-linkedin:before{content:'\f3f8'}.zmdi-odnoklassniki:before{content:'\f3f9'}.zmdi-outlook:before{content:'\f3fa'}.zmdi-paypal-alt:before{content:'\f3fb'}.zmdi-pinterest:before{content:'\f3fc'}.zmdi-playstation:before{content:'\f3fd'}.zmdi-reddit:before{content:'\f3fe'}.zmdi-skype:before{content:'\f3ff'}.zmdi-slideshare:before{content:'\f400'}.zmdi-soundcloud:before{content:'\f401'}.zmdi-tumblr:before{content:'\f402'}.zmdi-twitch:before{content:'\f403'}.zmdi-vimeo:before{content:'\f404'}.zmdi-whatsapp:before{content:'\f405'}.zmdi-xbox:before{content:'\f406'}.zmdi-yahoo:before{content:'\f407'}.zmdi-youtube-play:before{content:'\f408'}.zmdi-youtube:before{content:'\f409'}.zmdi-3d-rotation:before{content:'\f101'}.zmdi-airplane-off:before{content:'\f102'}.zmdi-airplane:before{content:'\f103'}.zmdi-album:before{content:'\f104'}.zmdi-archive:before{content:'\f105'}.zmdi-assignment-account:before{content:'\f106'}.zmdi-assignment-alert:before{content:'\f107'}.zmdi-assignment-check:before{content:'\f108'}.zmdi-assignment-o:before{content:'\f109'}.zmdi-assignment-return:before{content:'\f10a'}.zmdi-assignment-returned:before{content:'\f10b'}.zmdi-assignment:before{content:'\f10c'}.zmdi-attachment-alt:before{content:'\f10d'}.zmdi-attachment:before{content:'\f10e'}.zmdi-audio:before{content:'\f10f'}.zmdi-badge-check:before{content:'\f110'}.zmdi-balance-wallet:before{content:'\f111'}.zmdi-balance:before{content:'\f112'}.zmdi-battery-alert:before{content:'\f113'}.zmdi-battery-flash:before{content:'\f114'}.zmdi-battery-unknown:before{content:'\f115'}.zmdi-battery:before{content:'\f116'}.zmdi-bike:before{content:'\f117'}.zmdi-block-alt:before{content:'\f118'}.zmdi-block:before{content:'\f119'}.zmdi-boat:before{content:'\f11a'}.zmdi-book-image:before{content:'\f11b'}.zmdi-book:before{content:'\f11c'}.zmdi-bookmark-outline:before{content:'\f11d'}.zmdi-bookmark:before{content:'\f11e'}.zmdi-brush:before{content:'\f11f'}.zmdi-bug:before{content:'\f120'}.zmdi-bus:before{content:'\f121'}.zmdi-cake:before{content:'\f122'}.zmdi-car-taxi:before{content:'\f123'}.zmdi-car-wash:before{content:'\f124'}.zmdi-car:before{content:'\f125'}.zmdi-card-giftcard:before{content:'\f126'}.zmdi-card-membership:before{content:'\f127'}.zmdi-card-travel:before{content:'\f128'}.zmdi-card:before{content:'\f129'}.zmdi-case-check:before{content:'\f12a'}.zmdi-case-download:before{content:'\f12b'}.zmdi-case-play:before{content:'\f12c'}.zmdi-case:before{content:'\f12d'}.zmdi-cast-connected:before{content:'\f12e'}.zmdi-cast:before{content:'\f12f'}.zmdi-chart-donut:before{content:'\f130'}.zmdi-chart:before{content:'\f131'}.zmdi-city-alt:before{content:'\f132'}.zmdi-city:before{content:'\f133'}.zmdi-close-circle-o:before{content:'\f134'}.zmdi-close-circle:before{content:'\f135'}.zmdi-close:before{content:'\f136'}.zmdi-cocktail:before{content:'\f137'}.zmdi-code-setting:before{content:'\f138'}.zmdi-code-smartphone:before{content:'\f139'}.zmdi-code:before{content:'\f13a'}.zmdi-coffee:before{content:'\f13b'}.zmdi-collection-bookmark:before{content:'\f13c'}.zmdi-collection-case-play:before{content:'\f13d'}.zmdi-collection-folder-image:before{content:'\f13e'}.zmdi-collection-image-o:before{content:'\f13f'}.zmdi-collection-image:before{content:'\f140'}.zmdi-collection-item-1:before{content:'\f141'}.zmdi-collection-item-2:before{content:'\f142'}.zmdi-collection-item-3:before{content:'\f143'}.zmdi-collection-item-4:before{content:'\f144'}.zmdi-collection-item-5:before{content:'\f145'}.zmdi-collection-item-6:before{content:'\f146'}.zmdi-collection-item-7:before{content:'\f147'}.zmdi-collection-item-8:before{content:'\f148'}.zmdi-collection-item-9-plus:before{content:'\f149'}.zmdi-collection-item-9:before{content:'\f14a'}.zmdi-collection-item:before{content:'\f14b'}.zmdi-collection-music:before{content:'\f14c'}.zmdi-collection-pdf:before{content:'\f14d'}.zmdi-collection-plus:before{content:'\f14e'}.zmdi-collection-speaker:before{content:'\f14f'}.zmdi-collection-text:before{content:'\f150'}.zmdi-collection-video:before{content:'\f151'}.zmdi-compass:before{content:'\f152'}.zmdi-cutlery:before{content:'\f153'}.zmdi-delete:before{content:'\f154'}.zmdi-dialpad:before{content:'\f155'}.zmdi-dns:before{content:'\f156'}.zmdi-drink:before{content:'\f157'}.zmdi-edit:before{content:'\f158'}.zmdi-email-open:before{content:'\f159'}.zmdi-email:before{content:'\f15a'}.zmdi-eye-off:before{content:'\f15b'}.zmdi-eye:before{content:'\f15c'}.zmdi-eyedropper:before{content:'\f15d'}.zmdi-favorite-outline:before{content:'\f15e'}.zmdi-favorite:before{content:'\f15f'}.zmdi-filter-list:before{content:'\f160'}.zmdi-fire:before{content:'\f161'}.zmdi-flag:before{content:'\f162'}.zmdi-flare:before{content:'\f163'}.zmdi-flash-auto:before{content:'\f164'}.zmdi-flash-off:before{content:'\f165'}.zmdi-flash:before{content:'\f166'}.zmdi-flip:before{content:'\f167'}.zmdi-flower-alt:before{content:'\f168'}.zmdi-flower:before{content:'\f169'}.zmdi-font:before{content:'\f16a'}.zmdi-fullscreen-alt:before{content:'\f16b'}.zmdi-fullscreen-exit:before{content:'\f16c'}.zmdi-fullscreen:before{content:'\f16d'}.zmdi-functions:before{content:'\f16e'}.zmdi-gas-station:before{content:'\f16f'}.zmdi-gesture:before{content:'\f170'}.zmdi-globe-alt:before{content:'\f171'}.zmdi-globe-lock:before{content:'\f172'}.zmdi-globe:before{content:'\f173'}.zmdi-graduation-cap:before{content:'\f174'}.zmdi-home:before{content:'\f175'}.zmdi-hospital-alt:before{content:'\f176'}.zmdi-hospital:before{content:'\f177'}.zmdi-hotel:before{content:'\f178'}.zmdi-hourglass-alt:before{content:'\f179'}.zmdi-hourglass-outline:before{content:'\f17a'}.zmdi-hourglass:before{content:'\f17b'}.zmdi-http:before{content:'\f17c'}.zmdi-image-alt:before{content:'\f17d'}.zmdi-image-o:before{content:'\f17e'}.zmdi-image:before{content:'\f17f'}.zmdi-inbox:before{content:'\f180'}.zmdi-invert-colors-off:before{content:'\f181'}.zmdi-invert-colors:before{content:'\f182'}.zmdi-key:before{content:'\f183'}.zmdi-label-alt-outline:before{content:'\f184'}.zmdi-label-alt:before{content:'\f185'}.zmdi-label-heart:before{content:'\f186'}.zmdi-label:before{content:'\f187'}.zmdi-labels:before{content:'\f188'}.zmdi-lamp:before{content:'\f189'}.zmdi-landscape:before{content:'\f18a'}.zmdi-layers-off:before{content:'\f18b'}.zmdi-layers:before{content:'\f18c'}.zmdi-library:before{content:'\f18d'}.zmdi-link:before{content:'\f18e'}.zmdi-lock-open:before{content:'\f18f'}.zmdi-lock-outline:before{content:'\f190'}.zmdi-lock:before{content:'\f191'}.zmdi-mail-reply-all:before{content:'\f192'}.zmdi-mail-reply:before{content:'\f193'}.zmdi-mail-send:before{content:'\f194'}.zmdi-mall:before{content:'\f195'}.zmdi-map:before{content:'\f196'}.zmdi-menu:before{content:'\f197'}.zmdi-money-box:before{content:'\f198'}.zmdi-money-off:before{content:'\f199'}.zmdi-money:before{content:'\f19a'}.zmdi-more-vert:before{content:'\f19b'}.zmdi-more:before{content:'\f19c'}.zmdi-movie-alt:before{content:'\f19d'}.zmdi-movie:before{content:'\f19e'}.zmdi-nature-people:before{content:'\f19f'}.zmdi-nature:before{content:'\f1a0'}.zmdi-navigation:before{content:'\f1a1'}.zmdi-open-in-browser:before{content:'\f1a2'}.zmdi-open-in-new:before{content:'\f1a3'}.zmdi-palette:before{content:'\f1a4'}.zmdi-parking:before{content:'\f1a5'}.zmdi-pin-account:before{content:'\f1a6'}.zmdi-pin-assistant:before{content:'\f1a7'}.zmdi-pin-drop:before{content:'\f1a8'}.zmdi-pin-help:before{content:'\f1a9'}.zmdi-pin-off:before{content:'\f1aa'}.zmdi-pin:before{content:'\f1ab'}.zmdi-pizza:before{content:'\f1ac'}.zmdi-plaster:before{content:'\f1ad'}.zmdi-power-setting:before{content:'\f1ae'}.zmdi-power:before{content:'\f1af'}.zmdi-print:before{content:'\f1b0'}.zmdi-puzzle-piece:before{content:'\f1b1'}.zmdi-quote:before{content:'\f1b2'}.zmdi-railway:before{content:'\f1b3'}.zmdi-receipt:before{content:'\f1b4'}.zmdi-refresh-alt:before{content:'\f1b5'}.zmdi-refresh-sync-alert:before{content:'\f1b6'}.zmdi-refresh-sync-off:before{content:'\f1b7'}.zmdi-refresh-sync:before{content:'\f1b8'}.zmdi-refresh:before{content:'\f1b9'}.zmdi-roller:before{content:'\f1ba'}.zmdi-ruler:before{content:'\f1bb'}.zmdi-scissors:before{content:'\f1bc'}.zmdi-screen-rotation-lock:before{content:'\f1bd'}.zmdi-screen-rotation:before{content:'\f1be'}.zmdi-search-for:before{content:'\f1bf'}.zmdi-search-in-file:before{content:'\f1c0'}.zmdi-search-in-page:before{content:'\f1c1'}.zmdi-search-replace:before{content:'\f1c2'}.zmdi-search:before{content:'\f1c3'}.zmdi-seat:before{content:'\f1c4'}.zmdi-settings-square:before{content:'\f1c5'}.zmdi-settings:before{content:'\f1c6'}.zmdi-shield-check:before{content:'\f1c7'}.zmdi-shield-security:before{content:'\f1c8'}.zmdi-shopping-basket:before{content:'\f1c9'}.zmdi-shopping-cart-plus:before{content:'\f1ca'}.zmdi-shopping-cart:before{content:'\f1cb'}.zmdi-sign-in:before{content:'\f1cc'}.zmdi-sort-amount-asc:before{content:'\f1cd'}.zmdi-sort-amount-desc:before{content:'\f1ce'}.zmdi-sort-asc:before{content:'\f1cf'}.zmdi-sort-desc:before{content:'\f1d0'}.zmdi-spellcheck:before{content:'\f1d1'}.zmdi-storage:before{content:'\f1d2'}.zmdi-store-24:before{content:'\f1d3'}.zmdi-store:before{content:'\f1d4'}.zmdi-subway:before{content:'\f1d5'}.zmdi-sun:before{content:'\f1d6'}.zmdi-tab-unselected:before{content:'\f1d7'}.zmdi-tab:before{content:'\f1d8'}.zmdi-tag-close:before{content:'\f1d9'}.zmdi-tag-more:before{content:'\f1da'}.zmdi-tag:before{content:'\f1db'}.zmdi-thumb-down:before{content:'\f1dc'}.zmdi-thumb-up-down:before{content:'\f1dd'}.zmdi-thumb-up:before{content:'\f1de'}.zmdi-ticket-star:before{content:'\f1df'}.zmdi-toll:before{content:'\f1e0'}.zmdi-toys:before{content:'\f1e1'}.zmdi-traffic:before{content:'\f1e2'}.zmdi-translate:before{content:'\f1e3'}.zmdi-triangle-down:before{content:'\f1e4'}.zmdi-triangle-up:before{content:'\f1e5'}.zmdi-truck:before{content:'\f1e6'}.zmdi-turning-sign:before{content:'\f1e7'}.zmdi-wallpaper:before{content:'\f1e8'}.zmdi-washing-machine:before{content:'\f1e9'}.zmdi-window-maximize:before{content:'\f1ea'}.zmdi-window-minimize:before{content:'\f1eb'}.zmdi-window-restore:before{content:'\f1ec'}.zmdi-wrench:before{content:'\f1ed'}.zmdi-zoom-in:before{content:'\f1ee'}.zmdi-zoom-out:before{content:'\f1ef'}.zmdi-alert-circle-o:before{content:'\f1f0'}.zmdi-alert-circle:before{content:'\f1f1'}.zmdi-alert-octagon:before{content:'\f1f2'}.zmdi-alert-polygon:before{content:'\f1f3'}.zmdi-alert-triangle:before{content:'\f1f4'}.zmdi-help-outline:before{content:'\f1f5'}.zmdi-help:before{content:'\f1f6'}.zmdi-info-outline:before{content:'\f1f7'}.zmdi-info:before{content:'\f1f8'}.zmdi-notifications-active:before{content:'\f1f9'}.zmdi-notifications-add:before{content:'\f1fa'}.zmdi-notifications-none:before{content:'\f1fb'}.zmdi-notifications-off:before{content:'\f1fc'}.zmdi-notifications-paused:before{content:'\f1fd'}.zmdi-notifications:before{content:'\f1fe'}.zmdi-account-add:before{content:'\f1ff'}.zmdi-account-box-mail:before{content:'\f200'}.zmdi-account-box-o:before{content:'\f201'}.zmdi-account-box-phone:before{content:'\f202'}.zmdi-account-box:before{content:'\f203'}.zmdi-account-calendar:before{content:'\f204'}.zmdi-account-circle:before{content:'\f205'}.zmdi-account-o:before{content:'\f206'}.zmdi-account:before{content:'\f207'}.zmdi-accounts-add:before{content:'\f208'}.zmdi-accounts-alt:before{content:'\f209'}.zmdi-accounts-list-alt:before{content:'\f20a'}.zmdi-accounts-list:before{content:'\f20b'}.zmdi-accounts-outline:before{content:'\f20c'}.zmdi-accounts:before{content:'\f20d'}.zmdi-face:before{content:'\f20e'}.zmdi-female:before{content:'\f20f'}.zmdi-male-alt:before{content:'\f210'}.zmdi-male-female:before{content:'\f211'}.zmdi-male:before{content:'\f212'}.zmdi-mood-bad:before{content:'\f213'}.zmdi-mood:before{content:'\f214'}.zmdi-run:before{content:'\f215'}.zmdi-walk:before{content:'\f216'}.zmdi-cloud-box:before{content:'\f217'}.zmdi-cloud-circle:before{content:'\f218'}.zmdi-cloud-done:before{content:'\f219'}.zmdi-cloud-download:before{content:'\f21a'}.zmdi-cloud-off:before{content:'\f21b'}.zmdi-cloud-outline-alt:before{content:'\f21c'}.zmdi-cloud-outline:before{content:'\f21d'}.zmdi-cloud-upload:before{content:'\f21e'}.zmdi-cloud:before{content:'\f21f'}.zmdi-download:before{content:'\f220'}.zmdi-file-plus:before{content:'\f221'}.zmdi-file-text:before{content:'\f222'}.zmdi-file:before{content:'\f223'}.zmdi-folder-outline:before{content:'\f224'}.zmdi-folder-person:before{content:'\f225'}.zmdi-folder-star-alt:before{content:'\f226'}.zmdi-folder-star:before{content:'\f227'}.zmdi-folder:before{content:'\f228'}.zmdi-gif:before{content:'\f229'}.zmdi-upload:before{content:'\f22a'}.zmdi-border-all:before{content:'\f22b'}.zmdi-border-bottom:before{content:'\f22c'}.zmdi-border-clear:before{content:'\f22d'}.zmdi-border-color:before{content:'\f22e'}.zmdi-border-horizontal:before{content:'\f22f'}.zmdi-border-inner:before{content:'\f230'}.zmdi-border-left:before{content:'\f231'}.zmdi-border-outer:before{content:'\f232'}.zmdi-border-right:before{content:'\f233'}.zmdi-border-style:before{content:'\f234'}.zmdi-border-top:before{content:'\f235'}.zmdi-border-vertical:before{content:'\f236'}.zmdi-copy:before{content:'\f237'}.zmdi-crop:before{content:'\f238'}.zmdi-format-align-center:before{content:'\f239'}.zmdi-format-align-justify:before{content:'\f23a'}.zmdi-format-align-left:before{content:'\f23b'}.zmdi-format-align-right:before{content:'\f23c'}.zmdi-format-bold:before{content:'\f23d'}.zmdi-format-clear-all:before{content:'\f23e'}.zmdi-format-clear:before{content:'\f23f'}.zmdi-format-color-fill:before{content:'\f240'}.zmdi-format-color-reset:before{content:'\f241'}.zmdi-format-color-text:before{content:'\f242'}.zmdi-format-indent-decrease:before{content:'\f243'}.zmdi-format-indent-increase:before{content:'\f244'}.zmdi-format-italic:before{content:'\f245'}.zmdi-format-line-spacing:before{content:'\f246'}.zmdi-format-list-bulleted:before{content:'\f247'}.zmdi-format-list-numbered:before{content:'\f248'}.zmdi-format-ltr:before{content:'\f249'}.zmdi-format-rtl:before{content:'\f24a'}.zmdi-format-size:before{content:'\f24b'}.zmdi-format-strikethrough-s:before{content:'\f24c'}.zmdi-format-strikethrough:before{content:'\f24d'}.zmdi-format-subject:before{content:'\f24e'}.zmdi-format-underlined:before{content:'\f24f'}.zmdi-format-valign-bottom:before{content:'\f250'}.zmdi-format-valign-center:before{content:'\f251'}.zmdi-format-valign-top:before{content:'\f252'}.zmdi-redo:before{content:'\f253'}.zmdi-select-all:before{content:'\f254'}.zmdi-space-bar:before{content:'\f255'}.zmdi-text-format:before{content:'\f256'}.zmdi-transform:before{content:'\f257'}.zmdi-undo:before{content:'\f258'}.zmdi-wrap-text:before{content:'\f259'}.zmdi-comment-alert:before{content:'\f25a'}.zmdi-comment-alt-text:before{content:'\f25b'}.zmdi-comment-alt:before{content:'\f25c'}.zmdi-comment-edit:before{content:'\f25d'}.zmdi-comment-image:before{content:'\f25e'}.zmdi-comment-list:before{content:'\f25f'}.zmdi-comment-more:before{content:'\f260'}.zmdi-comment-outline:before{content:'\f261'}.zmdi-comment-text-alt:before{content:'\f262'}.zmdi-comment-text:before{content:'\f263'}.zmdi-comment-video:before{content:'\f264'}.zmdi-comment:before{content:'\f265'}.zmdi-comments:before{content:'\f266'}.zmdi-check-all:before{content:'\f267'}.zmdi-check-circle-u:before{content:'\f268'}.zmdi-check-circle:before{content:'\f269'}.zmdi-check-square:before{content:'\f26a'}.zmdi-check:before{content:'\f26b'}.zmdi-circle-o:before{content:'\f26c'}.zmdi-circle:before{content:'\f26d'}.zmdi-dot-circle-alt:before{content:'\f26e'}.zmdi-dot-circle:before{content:'\f26f'}.zmdi-minus-circle-outline:before{content:'\f270'}.zmdi-minus-circle:before{content:'\f271'}.zmdi-minus-square:before{content:'\f272'}.zmdi-minus:before{content:'\f273'}.zmdi-plus-circle-o-duplicate:before{content:'\f274'}.zmdi-plus-circle-o:before{content:'\f275'}.zmdi-plus-circle:before{content:'\f276'}.zmdi-plus-square:before{content:'\f277'}.zmdi-plus:before{content:'\f278'}.zmdi-square-o:before{content:'\f279'}.zmdi-star-circle:before{content:'\f27a'}.zmdi-star-half:before{content:'\f27b'}.zmdi-star-outline:before{content:'\f27c'}.zmdi-star:before{content:'\f27d'}.zmdi-bluetooth-connected:before{content:'\f27e'}.zmdi-bluetooth-off:before{content:'\f27f'}.zmdi-bluetooth-search:before{content:'\f280'}.zmdi-bluetooth-setting:before{content:'\f281'}.zmdi-bluetooth:before{content:'\f282'}.zmdi-camera-add:before{content:'\f283'}.zmdi-camera-alt:before{content:'\f284'}.zmdi-camera-bw:before{content:'\f285'}.zmdi-camera-front:before{content:'\f286'}.zmdi-camera-mic:before{content:'\f287'}.zmdi-camera-party-mode:before{content:'\f288'}.zmdi-camera-rear:before{content:'\f289'}.zmdi-camera-roll:before{content:'\f28a'}.zmdi-camera-switch:before{content:'\f28b'}.zmdi-camera:before{content:'\f28c'}.zmdi-card-alert:before{content:'\f28d'}.zmdi-card-off:before{content:'\f28e'}.zmdi-card-sd:before{content:'\f28f'}.zmdi-card-sim:before{content:'\f290'}.zmdi-desktop-mac:before{content:'\f291'}.zmdi-desktop-windows:before{content:'\f292'}.zmdi-device-hub:before{content:'\f293'}.zmdi-devices-off:before{content:'\f294'}.zmdi-devices:before{content:'\f295'}.zmdi-dock:before{content:'\f296'}.zmdi-floppy:before{content:'\f297'}.zmdi-gamepad:before{content:'\f298'}.zmdi-gps-dot:before{content:'\f299'}.zmdi-gps-off:before{content:'\f29a'}.zmdi-gps:before{content:'\f29b'}.zmdi-headset-mic:before{content:'\f29c'}.zmdi-headset:before{content:'\f29d'}.zmdi-input-antenna:before{content:'\f29e'}.zmdi-input-composite:before{content:'\f29f'}.zmdi-input-hdmi:before{content:'\f2a0'}.zmdi-input-power:before{content:'\f2a1'}.zmdi-input-svideo:before{content:'\f2a2'}.zmdi-keyboard-hide:before{content:'\f2a3'}.zmdi-keyboard:before{content:'\f2a4'}.zmdi-laptop-chromebook:before{content:'\f2a5'}.zmdi-laptop-mac:before{content:'\f2a6'}.zmdi-laptop:before{content:'\f2a7'}.zmdi-mic-off:before{content:'\f2a8'}.zmdi-mic-outline:before{content:'\f2a9'}.zmdi-mic-setting:before{content:'\f2aa'}.zmdi-mic:before{content:'\f2ab'}.zmdi-mouse:before{content:'\f2ac'}.zmdi-network-alert:before{content:'\f2ad'}.zmdi-network-locked:before{content:'\f2ae'}.zmdi-network-off:before{content:'\f2af'}.zmdi-network-outline:before{content:'\f2b0'}.zmdi-network-setting:before{content:'\f2b1'}.zmdi-network:before{content:'\f2b2'}.zmdi-phone-bluetooth:before{content:'\f2b3'}.zmdi-phone-end:before{content:'\f2b4'}.zmdi-phone-forwarded:before{content:'\f2b5'}.zmdi-phone-in-talk:before{content:'\f2b6'}.zmdi-phone-locked:before{content:'\f2b7'}.zmdi-phone-missed:before{content:'\f2b8'}.zmdi-phone-msg:before{content:'\f2b9'}.zmdi-phone-paused:before{content:'\f2ba'}.zmdi-phone-ring:before{content:'\f2bb'}.zmdi-phone-setting:before{content:'\f2bc'}.zmdi-phone-sip:before{content:'\f2bd'}.zmdi-phone:before{content:'\f2be'}.zmdi-portable-wifi-changes:before{content:'\f2bf'}.zmdi-portable-wifi-off:before{content:'\f2c0'}.zmdi-portable-wifi:before{content:'\f2c1'}.zmdi-radio:before{content:'\f2c2'}.zmdi-reader:before{content:'\f2c3'}.zmdi-remote-control-alt:before{content:'\f2c4'}.zmdi-remote-control:before{content:'\f2c5'}.zmdi-router:before{content:'\f2c6'}.zmdi-scanner:before{content:'\f2c7'}.zmdi-smartphone-android:before{content:'\f2c8'}.zmdi-smartphone-download:before{content:'\f2c9'}.zmdi-smartphone-erase:before{content:'\f2ca'}.zmdi-smartphone-info:before{content:'\f2cb'}.zmdi-smartphone-iphone:before{content:'\f2cc'}.zmdi-smartphone-landscape-lock:before{content:'\f2cd'}.zmdi-smartphone-landscape:before{content:'\f2ce'}.zmdi-smartphone-lock:before{content:'\f2cf'}.zmdi-smartphone-portrait-lock:before{content:'\f2d0'}.zmdi-smartphone-ring:before{content:'\f2d1'}.zmdi-smartphone-setting:before{content:'\f2d2'}.zmdi-smartphone-setup:before{content:'\f2d3'}.zmdi-smartphone:before{content:'\f2d4'}.zmdi-speaker:before{content:'\f2d5'}.zmdi-tablet-android:before{content:'\f2d6'}.zmdi-tablet-mac:before{content:'\f2d7'}.zmdi-tablet:before{content:'\f2d8'}.zmdi-tv-alt-play:before{content:'\f2d9'}.zmdi-tv-list:before{content:'\f2da'}.zmdi-tv-play:before{content:'\f2db'}.zmdi-tv:before{content:'\f2dc'}.zmdi-usb:before{content:'\f2dd'}.zmdi-videocam-off:before{content:'\f2de'}.zmdi-videocam-switch:before{content:'\f2df'}.zmdi-videocam:before{content:'\f2e0'}.zmdi-watch:before{content:'\f2e1'}.zmdi-wifi-alt-2:before{content:'\f2e2'}.zmdi-wifi-alt:before{content:'\f2e3'}.zmdi-wifi-info:before{content:'\f2e4'}.zmdi-wifi-lock:before{content:'\f2e5'}.zmdi-wifi-off:before{content:'\f2e6'}.zmdi-wifi-outline:before{content:'\f2e7'}.zmdi-wifi:before{content:'\f2e8'}.zmdi-arrow-left-bottom:before{content:'\f2e9'}.zmdi-arrow-left:before{content:'\f2ea'}.zmdi-arrow-merge:before{content:'\f2eb'}.zmdi-arrow-missed:before{content:'\f2ec'}.zmdi-arrow-right-top:before{content:'\f2ed'}.zmdi-arrow-right:before{content:'\f2ee'}.zmdi-arrow-split:before{content:'\f2ef'}.zmdi-arrows:before{content:'\f2f0'}.zmdi-caret-down-circle:before{content:'\f2f1'}.zmdi-caret-down:before{content:'\f2f2'}.zmdi-caret-left-circle:before{content:'\f2f3'}.zmdi-caret-left:before{content:'\f2f4'}.zmdi-caret-right-circle:before{content:'\f2f5'}.zmdi-caret-right:before{content:'\f2f6'}.zmdi-caret-up-circle:before{content:'\f2f7'}.zmdi-caret-up:before{content:'\f2f8'}.zmdi-chevron-down:before{content:'\f2f9'}.zmdi-chevron-left:before{content:'\f2fa'}.zmdi-chevron-right:before{content:'\f2fb'}.zmdi-chevron-up:before{content:'\f2fc'}.zmdi-forward:before{content:'\f2fd'}.zmdi-long-arrow-down:before{content:'\f2fe'}.zmdi-long-arrow-left:before{content:'\f2ff'}.zmdi-long-arrow-return:before{content:'\f300'}.zmdi-long-arrow-right:before{content:'\f301'}.zmdi-long-arrow-tab:before{content:'\f302'}.zmdi-long-arrow-up:before{content:'\f303'}.zmdi-rotate-ccw:before{content:'\f304'}.zmdi-rotate-cw:before{content:'\f305'}.zmdi-rotate-left:before{content:'\f306'}.zmdi-rotate-right:before{content:'\f307'}.zmdi-square-down:before{content:'\f308'}.zmdi-square-right:before{content:'\f309'}.zmdi-swap-alt:before{content:'\f30a'}.zmdi-swap-vertical-circle:before{content:'\f30b'}.zmdi-swap-vertical:before{content:'\f30c'}.zmdi-swap:before{content:'\f30d'}.zmdi-trending-down:before{content:'\f30e'}.zmdi-trending-flat:before{content:'\f30f'}.zmdi-trending-up:before{content:'\f310'}.zmdi-unfold-less:before{content:'\f311'}.zmdi-unfold-more:before{content:'\f312'}.zmdi-apps:before{content:'\f313'}.zmdi-grid-off:before{content:'\f314'}.zmdi-grid:before{content:'\f315'}.zmdi-view-agenda:before{content:'\f316'}.zmdi-view-array:before{content:'\f317'}.zmdi-view-carousel:before{content:'\f318'}.zmdi-view-column:before{content:'\f319'}.zmdi-view-comfy:before{content:'\f31a'}.zmdi-view-compact:before{content:'\f31b'}.zmdi-view-dashboard:before{content:'\f31c'}.zmdi-view-day:before{content:'\f31d'}.zmdi-view-headline:before{content:'\f31e'}.zmdi-view-list-alt:before{content:'\f31f'}.zmdi-view-list:before{content:'\f320'}.zmdi-view-module:before{content:'\f321'}.zmdi-view-quilt:before{content:'\f322'}.zmdi-view-stream:before{content:'\f323'}.zmdi-view-subtitles:before{content:'\f324'}.zmdi-view-toc:before{content:'\f325'}.zmdi-view-web:before{content:'\f326'}.zmdi-view-week:before{content:'\f327'}.zmdi-widgets:before{content:'\f328'}.zmdi-alarm-check:before{content:'\f329'}.zmdi-alarm-off:before{content:'\f32a'}.zmdi-alarm-plus:before{content:'\f32b'}.zmdi-alarm-snooze:before{content:'\f32c'}.zmdi-alarm:before{content:'\f32d'}.zmdi-calendar-alt:before{content:'\f32e'}.zmdi-calendar-check:before{content:'\f32f'}.zmdi-calendar-close:before{content:'\f330'}.zmdi-calendar-note:before{content:'\f331'}.zmdi-calendar:before{content:'\f332'}.zmdi-time-countdown:before{content:'\f333'}.zmdi-time-interval:before{content:'\f334'}.zmdi-time-restore-setting:before{content:'\f335'}.zmdi-time-restore:before{content:'\f336'}.zmdi-time:before{content:'\f337'}.zmdi-timer-off:before{content:'\f338'}.zmdi-timer:before{content:'\f339'}.zmdi-android-alt:before{content:'\f33a'}.zmdi-android:before{content:'\f33b'}.zmdi-apple:before{content:'\f33c'}.zmdi-behance:before{content:'\f33d'}.zmdi-codepen:before{content:'\f33e'}.zmdi-dribbble:before{content:'\f33f'}.zmdi-dropbox:before{content:'\f340'}.zmdi-evernote:before{content:'\f341'}.zmdi-facebook-box:before{content:'\f342'}.zmdi-facebook:before{content:'\f343'}.zmdi-github-box:before{content:'\f344'}.zmdi-github:before{content:'\f345'}.zmdi-google-drive:before{content:'\f346'}.zmdi-google-earth:before{content:'\f347'}.zmdi-google-glass:before{content:'\f348'}.zmdi-google-maps:before{content:'\f349'}.zmdi-google-pages:before{content:'\f34a'}.zmdi-google-play:before{content:'\f34b'}.zmdi-google-plus-box:before{content:'\f34c'}.zmdi-google-plus:before{content:'\f34d'}.zmdi-google:before{content:'\f34e'}.zmdi-instagram:before{content:'\f34f'}.zmdi-language-css3:before{content:'\f350'}.zmdi-language-html5:before{content:'\f351'}.zmdi-language-javascript:before{content:'\f352'}.zmdi-language-python-alt:before{content:'\f353'}.zmdi-language-python:before{content:'\f354'}.zmdi-lastfm:before{content:'\f355'}.zmdi-linkedin-box:before{content:'\f356'}.zmdi-paypal:before{content:'\f357'}.zmdi-pinterest-box:before{content:'\f358'}.zmdi-pocket:before{content:'\f359'}.zmdi-polymer:before{content:'\f35a'}.zmdi-share:before{content:'\f35b'}.zmdi-stackoverflow:before{content:'\f35c'}.zmdi-steam-square:before{content:'\f35d'}.zmdi-steam:before{content:'\f35e'}.zmdi-twitter-box:before{content:'\f35f'}.zmdi-twitter:before{content:'\f360'}.zmdi-vk:before{content:'\f361'}.zmdi-wikipedia:before{content:'\f362'}.zmdi-windows:before{content:'\f363'}.zmdi-aspect-ratio-alt:before{content:'\f364'}.zmdi-aspect-ratio:before{content:'\f365'}.zmdi-blur-circular:before{content:'\f366'}.zmdi-blur-linear:before{content:'\f367'}.zmdi-blur-off:before{content:'\f368'}.zmdi-blur:before{content:'\f369'}.zmdi-brightness-2:before{content:'\f36a'}.zmdi-brightness-3:before{content:'\f36b'}.zmdi-brightness-4:before{content:'\f36c'}.zmdi-brightness-5:before{content:'\f36d'}.zmdi-brightness-6:before{content:'\f36e'}.zmdi-brightness-7:before{content:'\f36f'}.zmdi-brightness-auto:before{content:'\f370'}.zmdi-brightness-setting:before{content:'\f371'}.zmdi-broken-image:before{content:'\f372'}.zmdi-center-focus-strong:before{content:'\f373'}.zmdi-center-focus-weak:before{content:'\f374'}.zmdi-compare:before{content:'\f375'}.zmdi-crop-16-9:before{content:'\f376'}.zmdi-crop-3-2:before{content:'\f377'}.zmdi-crop-5-4:before{content:'\f378'}.zmdi-crop-7-5:before{content:'\f379'}.zmdi-crop-din:before{content:'\f37a'}.zmdi-crop-free:before{content:'\f37b'}.zmdi-crop-landscape:before{content:'\f37c'}.zmdi-crop-portrait:before{content:'\f37d'}.zmdi-crop-square:before{content:'\f37e'}.zmdi-exposure-alt:before{content:'\f37f'}.zmdi-exposure:before{content:'\f380'}.zmdi-filter-b-and-w:before{content:'\f381'}.zmdi-filter-center-focus:before{content:'\f382'}.zmdi-filter-frames:before{content:'\f383'}.zmdi-filter-tilt-shift:before{content:'\f384'}.zmdi-gradient:before{content:'\f385'}.zmdi-grain:before{content:'\f386'}.zmdi-graphic-eq:before{content:'\f387'}.zmdi-hdr-off:before{content:'\f388'}.zmdi-hdr-strong:before{content:'\f389'}.zmdi-hdr-weak:before{content:'\f38a'}.zmdi-hdr:before{content:'\f38b'}.zmdi-iridescent:before{content:'\f38c'}.zmdi-leak-off:before{content:'\f38d'}.zmdi-leak:before{content:'\f38e'}.zmdi-looks:before{content:'\f38f'}.zmdi-loupe:before{content:'\f390'}.zmdi-panorama-horizontal:before{content:'\f391'}.zmdi-panorama-vertical:before{content:'\f392'}.zmdi-panorama-wide-angle:before{content:'\f393'}.zmdi-photo-size-select-large:before{content:'\f394'}.zmdi-photo-size-select-small:before{content:'\f395'}.zmdi-picture-in-picture:before{content:'\f396'}.zmdi-slideshow:before{content:'\f397'}.zmdi-texture:before{content:'\f398'}.zmdi-tonality:before{content:'\f399'}.zmdi-vignette:before{content:'\f39a'}.zmdi-wb-auto:before{content:'\f39b'}.zmdi-eject-alt:before{content:'\f39c'}.zmdi-eject:before{content:'\f39d'}.zmdi-equalizer:before{content:'\f39e'}.zmdi-fast-forward:before{content:'\f39f'}.zmdi-fast-rewind:before{content:'\f3a0'}.zmdi-forward-10:before{content:'\f3a1'}.zmdi-forward-30:before{content:'\f3a2'}.zmdi-forward-5:before{content:'\f3a3'}.zmdi-hearing:before{content:'\f3a4'}.zmdi-pause-circle-outline:before{content:'\f3a5'}.zmdi-pause-circle:before{content:'\f3a6'}.zmdi-pause:before{content:'\f3a7'}.zmdi-play-circle-outline:before{content:'\f3a8'}.zmdi-play-circle:before{content:'\f3a9'}.zmdi-play:before{content:'\f3aa'}.zmdi-playlist-audio:before{content:'\f3ab'}.zmdi-playlist-plus:before{content:'\f3ac'}.zmdi-repeat-one:before{content:'\f3ad'}.zmdi-repeat:before{content:'\f3ae'}.zmdi-replay-10:before{content:'\f3af'}.zmdi-replay-30:before{content:'\f3b0'}.zmdi-replay-5:before{content:'\f3b1'}.zmdi-replay:before{content:'\f3b2'}.zmdi-shuffle:before{content:'\f3b3'}.zmdi-skip-next:before{content:'\f3b4'}.zmdi-skip-previous:before{content:'\f3b5'}.zmdi-stop:before{content:'\f3b6'}.zmdi-surround-sound:before{content:'\f3b7'}.zmdi-tune:before{content:'\f3b8'}.zmdi-volume-down:before{content:'\f3b9'}.zmdi-volume-mute:before{content:'\f3ba'}.zmdi-volume-off:before{content:'\f3bb'}.zmdi-volume-up:before{content:'\f3bc'}.zmdi-n-1-square:before{content:'\f3bd'}.zmdi-n-2-square:before{content:'\f3be'}.zmdi-n-3-square:before{content:'\f3bf'}.zmdi-n-4-square:before{content:'\f3c0'}.zmdi-n-5-square:before{content:'\f3c1'}.zmdi-n-6-square:before{content:'\f3c2'}.zmdi-neg-1:before{content:'\f3c3'}.zmdi-neg-2:before{content:'\f3c4'}.zmdi-plus-1:before{content:'\f3c5'}.zmdi-plus-2:before{content:'\f3c6'}.zmdi-sec-10:before{content:'\f3c7'}.zmdi-sec-3:before{content:'\f3c8'}.zmdi-zero:before{content:'\f3c9'}.zmdi-airline-seat-flat-angled:before{content:'\f3ca'}.zmdi-airline-seat-flat:before{content:'\f3cb'}.zmdi-airline-seat-individual-suite:before{content:'\f3cc'}.zmdi-airline-seat-legroom-extra:before{content:'\f3cd'}.zmdi-airline-seat-legroom-normal:before{content:'\f3ce'}.zmdi-airline-seat-legroom-reduced:before{content:'\f3cf'}.zmdi-airline-seat-recline-extra:before{content:'\f3d0'}.zmdi-airline-seat-recline-normal:before{content:'\f3d1'}.zmdi-airplay:before{content:'\f3d2'}.zmdi-closed-caption:before{content:'\f3d3'}.zmdi-confirmation-number:before{content:'\f3d4'}.zmdi-developer-board:before{content:'\f3d5'}.zmdi-disc-full:before{content:'\f3d6'}.zmdi-explicit:before{content:'\f3d7'}.zmdi-flight-land:before{content:'\f3d8'}.zmdi-flight-takeoff:before{content:'\f3d9'}.zmdi-flip-to-back:before{content:'\f3da'}.zmdi-flip-to-front:before{content:'\f3db'}.zmdi-group-work:before{content:'\f3dc'}.zmdi-hd:before{content:'\f3dd'}.zmdi-hq:before{content:'\f3de'}.zmdi-markunread-mailbox:before{content:'\f3df'}.zmdi-memory:before{content:'\f3e0'}.zmdi-nfc:before{content:'\f3e1'}.zmdi-play-for-work:before{content:'\f3e2'}.zmdi-power-input:before{content:'\f3e3'}.zmdi-present-to-all:before{content:'\f3e4'}.zmdi-satellite:before{content:'\f3e5'}.zmdi-tap-and-play:before{content:'\f3e6'}.zmdi-vibration:before{content:'\f3e7'}.zmdi-voicemail:before{content:'\f3e8'}.zmdi-group:before{content:'\f3e9'}.zmdi-rss:before{content:'\f3ea'}.zmdi-shape:before{content:'\f3eb'}.zmdi-spinner:before{content:'\f3ec'}.zmdi-ungroup:before{content:'\f3ed'}.zmdi-500px:before{content:'\f3ee'}.zmdi-8tracks:before{content:'\f3ef'}.zmdi-amazon:before{content:'\f3f0'}.zmdi-blogger:before{content:'\f3f1'}.zmdi-delicious:before{content:'\f3f2'}.zmdi-disqus:before{content:'\f3f3'}.zmdi-flattr:before{content:'\f3f4'}.zmdi-flickr:before{content:'\f3f5'}.zmdi-github-alt:before{content:'\f3f6'}.zmdi-google-old:before{content:'\f3f7'}.zmdi-linkedin:before{content:'\f3f8'}.zmdi-odnoklassniki:before{content:'\f3f9'}.zmdi-outlook:before{content:'\f3fa'}.zmdi-paypal-alt:before{content:'\f3fb'}.zmdi-pinterest:before{content:'\f3fc'}.zmdi-playstation:before{content:'\f3fd'}.zmdi-reddit:before{content:'\f3fe'}.zmdi-skype:before{content:'\f3ff'}.zmdi-slideshare:before{content:'\f400'}.zmdi-soundcloud:before{content:'\f401'}.zmdi-tumblr:before{content:'\f402'}.zmdi-twitch:before{content:'\f403'}.zmdi-vimeo:before{content:'\f404'}.zmdi-whatsapp:before{content:'\f405'}.zmdi-xbox:before{content:'\f406'}.zmdi-yahoo:before{content:'\f407'}.zmdi-youtube-play:before{content:'\f408'}.zmdi-youtube:before{content:'\f409'}.zmdi-import-export:before{content:'\f30c'}.zmdi-swap-vertical-:before{content:'\f30c'}.zmdi-airplanemode-inactive:before{content:'\f102'}.zmdi-airplanemode-active:before{content:'\f103'}.zmdi-rate-review:before{content:'\f103'}.zmdi-comment-sign:before{content:'\f25a'}.zmdi-network-warning:before{content:'\f2ad'}.zmdi-shopping-cart-add:before{content:'\f1ca'}.zmdi-file-add:before{content:'\f221'}.zmdi-network-wifi-scan:before{content:'\f2e4'}.zmdi-collection-add:before{content:'\f14e'}.zmdi-format-playlist-add:before{content:'\f3ac'}.zmdi-format-queue-music:before{content:'\f3ab'}.zmdi-plus-box:before{content:'\f277'}.zmdi-tag-backspace:before{content:'\f1d9'}.zmdi-alarm-add:before{content:'\f32b'}.zmdi-battery-charging:before{content:'\f114'}.zmdi-daydream-setting:before{content:'\f217'}.zmdi-more-horiz:before{content:'\f19c'}.zmdi-book-photo:before{content:'\f11b'}.zmdi-incandescent:before{content:'\f189'}.zmdi-wb-iridescent:before{content:'\f38c'}.zmdi-calendar-remove:before{content:'\f330'}.zmdi-refresh-sync-disabled:before{content:'\f1b7'}.zmdi-refresh-sync-problem:before{content:'\f1b6'}.zmdi-crop-original:before{content:'\f17e'}.zmdi-power-off:before{content:'\f1af'}.zmdi-power-off-setting:before{content:'\f1ae'}.zmdi-leak-remove:before{content:'\f38d'}.zmdi-star-border:before{content:'\f27c'}.zmdi-brightness-low:before{content:'\f36d'}.zmdi-brightness-medium:before{content:'\f36e'}.zmdi-brightness-high:before{content:'\f36f'}.zmdi-smartphone-portrait:before{content:'\f2d4'}.zmdi-live-tv:before{content:'\f2d9'}.zmdi-format-textdirection-l-to-r:before{content:'\f249'}.zmdi-format-textdirection-r-to-l:before{content:'\f24a'}.zmdi-arrow-back:before{content:'\f2ea'}.zmdi-arrow-forward:before{content:'\f2ee'}.zmdi-arrow-in:before{content:'\f2e9'}.zmdi-arrow-out:before{content:'\f2ed'}.zmdi-rotate-90-degrees-ccw:before{content:'\f304'}.zmdi-adb:before{content:'\f33a'}.zmdi-network-wifi:before{content:'\f2e8'}.zmdi-network-wifi-alt:before{content:'\f2e3'}.zmdi-network-wifi-lock:before{content:'\f2e5'}.zmdi-network-wifi-off:before{content:'\f2e6'}.zmdi-network-wifi-outline:before{content:'\f2e7'}.zmdi-network-wifi-info:before{content:'\f2e4'}.zmdi-layers-clear:before{content:'\f18b'}.zmdi-colorize:before{content:'\f15d'}.zmdi-format-paint:before{content:'\f1ba'}.zmdi-format-quote:before{content:'\f1b2'}.zmdi-camera-monochrome-photos:before{content:'\f285'}.zmdi-sort-by-alpha:before{content:'\f1cf'}.zmdi-folder-shared:before{content:'\f225'}.zmdi-folder-special:before{content:'\f226'}.zmdi-comment-dots:before{content:'\f260'}.zmdi-reorder:before{content:'\f31e'}.zmdi-dehaze:before{content:'\f197'}.zmdi-sort:before{content:'\f1ce'}.zmdi-pages:before{content:'\f34a'}.zmdi-stack-overflow:before{content:'\f35c'}.zmdi-calendar-account:before{content:'\f204'}.zmdi-paste:before{content:'\f109'}.zmdi-cut:before{content:'\f1bc'}.zmdi-save:before{content:'\f297'}.zmdi-smartphone-code:before{content:'\f139'}.zmdi-directions-bike:before{content:'\f117'}.zmdi-directions-boat:before{content:'\f11a'}.zmdi-directions-bus:before{content:'\f121'}.zmdi-directions-car:before{content:'\f125'}.zmdi-directions-railway:before{content:'\f1b3'}.zmdi-directions-run:before{content:'\f215'}.zmdi-directions-subway:before{content:'\f1d5'}.zmdi-directions-walk:before{content:'\f216'}.zmdi-local-hotel:before{content:'\f178'}.zmdi-local-activity:before{content:'\f1df'}.zmdi-local-play:before{content:'\f1df'}.zmdi-local-airport:before{content:'\f103'}.zmdi-local-atm:before{content:'\f198'}.zmdi-local-bar:before{content:'\f137'}.zmdi-local-cafe:before{content:'\f13b'}.zmdi-local-car-wash:before{content:'\f124'}.zmdi-local-convenience-store:before{content:'\f1d3'}.zmdi-local-dining:before{content:'\f153'}.zmdi-local-drink:before{content:'\f157'}.zmdi-local-florist:before{content:'\f168'}.zmdi-local-gas-station:before{content:'\f16f'}.zmdi-local-grocery-store:before{content:'\f1cb'}.zmdi-local-hospital:before{content:'\f177'}.zmdi-local-laundry-service:before{content:'\f1e9'}.zmdi-local-library:before{content:'\f18d'}.zmdi-local-mall:before{content:'\f195'}.zmdi-local-movies:before{content:'\f19d'}.zmdi-local-offer:before{content:'\f187'}.zmdi-local-parking:before{content:'\f1a5'}.zmdi-local-parking:before{content:'\f1a5'}.zmdi-local-pharmacy:before{content:'\f176'}.zmdi-local-phone:before{content:'\f2be'}.zmdi-local-pizza:before{content:'\f1ac'}.zmdi-local-post-office:before{content:'\f15a'}.zmdi-local-printshop:before{content:'\f1b0'}.zmdi-local-see:before{content:'\f28c'}.zmdi-local-shipping:before{content:'\f1e6'}.zmdi-local-store:before{content:'\f1d4'}.zmdi-local-taxi:before{content:'\f123'}.zmdi-local-wc:before{content:'\f211'}.zmdi-my-location:before{content:'\f299'}.zmdi-directions:before{content:'\f1e7'} \ No newline at end of file diff --git a/packages/website/public/css/roboto.css b/packages/website/public/css/roboto.css new file mode 100644 index 000000000..7af568a74 --- /dev/null +++ b/packages/website/public/css/roboto.css @@ -0,0 +1,83 @@ +@font-face { + font-family: 'Roboto'; + src: url('../fonts/Roboto-Thin.ttf') format('truetype'); + font-weight: 100; + font-style: normal; +} + +/*@font-face { + font-family: 'Roboto'; + src: url('../fonts/Roboto-ThinItalic.ttf') format('truetype'); + font-weight: 100; + font-style: italic; +}*/ + +@font-face { + font-family: 'Roboto'; + src: url('../fonts/Roboto-Light.ttf') format('truetype'); + font-weight: 300; + font-style: normal; +} + +/*@font-face { + font-family: 'Roboto'; + src: url('../fonts/Roboto-LightItalic.ttf') format('truetype'); + font-weight: 300; + font-style: italic; +}*/ + +@font-face { + font-family: 'Roboto'; + src: url('../fonts/Roboto-Regular.ttf') format('truetype'); + font-weight: 400; + font-style: normal; +} + +/*@font-face { + font-family: 'Roboto'; + src: url('../fonts/Roboto-RegularItalic.ttf') format('truetype'); + font-weight: 400; + font-style: italic; +}*/ + +/*@font-face { + font-family: 'Roboto'; + src: url('../fonts/Roboto-Medium.ttf') format('truetype'); + font-weight: 500; + font-style: normal; +} + +@font-face { + font-family: 'Roboto'; + src: url('../fonts/Roboto-MediumItalic.ttf') format('truetype'); + font-weight: 500; + font-style: italic; +} + +@font-face { + font-family: 'Roboto'; + src: url('../fonts/Roboto-Bold.ttf') format('truetype'); + font-weight: 700; + font-style: normal; +} + +@font-face { + font-family: 'Roboto'; + src: url('../fonts/Roboto-BoldItalic.ttf') format('truetype'); + font-weight: 700; + font-style: italic; +} + +@font-face { + font-family: 'Roboto'; + src: url('../fonts/Roboto-Black.ttf') format('truetype'); + font-weight: 900; + font-style: normal; +} + +@font-face { + font-family: 'Roboto'; + src: url('../fonts/Roboto-BlackItalic.ttf') format('truetype'); + font-weight: 900; + font-style: italic; +}*/ diff --git a/packages/website/public/css/roboto_mono.css b/packages/website/public/css/roboto_mono.css new file mode 100644 index 000000000..f8159d35f --- /dev/null +++ b/packages/website/public/css/roboto_mono.css @@ -0,0 +1,69 @@ +@font-face { + font-family: 'Roboto Mono'; + src: url('../fonts/RobotoMono-Thin.ttf') format('truetype'); + font-weight: 100; + font-style: normal; +} + +/*@font-face { + font-family: 'Roboto Mono'; + src: url('../fonts/RobotoMono-ThinItalic.ttf') format('truetype'); + font-weight: 100; + font-style: italic; +}*/ + +@font-face { + font-family: 'Roboto Mono'; + src: url('../fonts/RobotoMono-Light.ttf') format('truetype'); + font-weight: 300; + font-style: normal; +} + +/*@font-face { + font-family: 'Roboto Mono'; + src: url('../fonts/RobotoMono-LightItalic.ttf') format('truetype'); + font-weight: 300; + font-style: italic; +}*/ + +@font-face { + font-family: 'Roboto Mono'; + src: url('../fonts/RobotoMono-Regular.ttf') format('truetype'); + font-weight: 400; + font-style: normal; +} + +/*@font-face { + font-family: 'Roboto Mono'; + src: url('../fonts/RobotoMono-RegularItalic.ttf') format('truetype'); + font-weight: 400; + font-style: italic; +}*/ + +/*@font-face { + font-family: 'Roboto Mono'; + src: url('../fonts/RobotoMono-Medium.ttf') format('truetype'); + font-weight: 500; + font-style: normal; +} + +@font-face { + font-family: 'Roboto Mono'; + src: url('../fonts/RobotoMono-MediumItalic.ttf') format('truetype'); + font-weight: 500; + font-style: italic; +} + +@font-face { + font-family: 'Roboto Mono'; + src: url('../fonts/RobotoMono-Bold.ttf') format('truetype'); + font-weight: 700; + font-style: normal; +} + +@font-face { + font-family: 'Roboto Mono'; + src: url('../fonts/RobotoMono-BoldItalic.ttf') format('truetype'); + font-weight: 700; + font-style: italic; +}*/ diff --git a/packages/website/public/fonts/Material-Design-Iconic-Font.eot b/packages/website/public/fonts/Material-Design-Iconic-Font.eot new file mode 100755 index 000000000..5e2519150 Binary files /dev/null and b/packages/website/public/fonts/Material-Design-Iconic-Font.eot differ diff --git a/packages/website/public/fonts/Material-Design-Iconic-Font.svg b/packages/website/public/fonts/Material-Design-Iconic-Font.svg new file mode 100755 index 000000000..1d3d2eaa2 --- /dev/null +++ b/packages/website/public/fonts/Material-Design-Iconic-Font.svg @@ -0,0 +1,787 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/website/public/fonts/Material-Design-Iconic-Font.ttf b/packages/website/public/fonts/Material-Design-Iconic-Font.ttf new file mode 100755 index 000000000..5d489fdd1 Binary files /dev/null and b/packages/website/public/fonts/Material-Design-Iconic-Font.ttf differ diff --git a/packages/website/public/fonts/Material-Design-Iconic-Font.woff b/packages/website/public/fonts/Material-Design-Iconic-Font.woff new file mode 100755 index 000000000..933b2bf85 Binary files /dev/null and b/packages/website/public/fonts/Material-Design-Iconic-Font.woff differ diff --git a/packages/website/public/fonts/Material-Design-Iconic-Font.woff2 b/packages/website/public/fonts/Material-Design-Iconic-Font.woff2 new file mode 100755 index 000000000..35970e277 Binary files /dev/null and b/packages/website/public/fonts/Material-Design-Iconic-Font.woff2 differ diff --git a/packages/website/public/fonts/Roboto-Black.ttf b/packages/website/public/fonts/Roboto-Black.ttf new file mode 100755 index 000000000..689fe5cb3 Binary files /dev/null and b/packages/website/public/fonts/Roboto-Black.ttf differ diff --git a/packages/website/public/fonts/Roboto-BlackItalic.ttf b/packages/website/public/fonts/Roboto-BlackItalic.ttf new file mode 100755 index 000000000..0b4e0ee10 Binary files /dev/null and b/packages/website/public/fonts/Roboto-BlackItalic.ttf differ diff --git a/packages/website/public/fonts/Roboto-Bold.ttf b/packages/website/public/fonts/Roboto-Bold.ttf new file mode 100755 index 000000000..d3f01ad24 Binary files /dev/null and b/packages/website/public/fonts/Roboto-Bold.ttf differ diff --git a/packages/website/public/fonts/Roboto-BoldItalic.ttf b/packages/website/public/fonts/Roboto-BoldItalic.ttf new file mode 100755 index 000000000..41cc1e753 Binary files /dev/null and b/packages/website/public/fonts/Roboto-BoldItalic.ttf differ diff --git a/packages/website/public/fonts/Roboto-Italic.ttf b/packages/website/public/fonts/Roboto-Italic.ttf new file mode 100755 index 000000000..6a1cee5b2 Binary files /dev/null and b/packages/website/public/fonts/Roboto-Italic.ttf differ diff --git a/packages/website/public/fonts/Roboto-Light.ttf b/packages/website/public/fonts/Roboto-Light.ttf new file mode 100755 index 000000000..219063a57 Binary files /dev/null and b/packages/website/public/fonts/Roboto-Light.ttf differ diff --git a/packages/website/public/fonts/Roboto-LightItalic.ttf b/packages/website/public/fonts/Roboto-LightItalic.ttf new file mode 100755 index 000000000..0e81e876f Binary files /dev/null and b/packages/website/public/fonts/Roboto-LightItalic.ttf differ diff --git a/packages/website/public/fonts/Roboto-Medium.ttf b/packages/website/public/fonts/Roboto-Medium.ttf new file mode 100755 index 000000000..1a7f3b0bb Binary files /dev/null and b/packages/website/public/fonts/Roboto-Medium.ttf differ diff --git a/packages/website/public/fonts/Roboto-MediumItalic.ttf b/packages/website/public/fonts/Roboto-MediumItalic.ttf new file mode 100755 index 000000000..003029527 Binary files /dev/null and b/packages/website/public/fonts/Roboto-MediumItalic.ttf differ diff --git a/packages/website/public/fonts/Roboto-Regular.ttf b/packages/website/public/fonts/Roboto-Regular.ttf new file mode 100755 index 000000000..2c97eeadf Binary files /dev/null and b/packages/website/public/fonts/Roboto-Regular.ttf differ diff --git a/packages/website/public/fonts/Roboto-Thin.ttf b/packages/website/public/fonts/Roboto-Thin.ttf new file mode 100755 index 000000000..b74a4fd1a Binary files /dev/null and b/packages/website/public/fonts/Roboto-Thin.ttf differ diff --git a/packages/website/public/fonts/Roboto-ThinItalic.ttf b/packages/website/public/fonts/Roboto-ThinItalic.ttf new file mode 100755 index 000000000..dd0ddb852 Binary files /dev/null and b/packages/website/public/fonts/Roboto-ThinItalic.ttf differ diff --git a/packages/website/public/fonts/RobotoMono-Bold.ttf b/packages/website/public/fonts/RobotoMono-Bold.ttf new file mode 100755 index 000000000..07ef607d5 Binary files /dev/null and b/packages/website/public/fonts/RobotoMono-Bold.ttf differ diff --git a/packages/website/public/fonts/RobotoMono-BoldItalic.ttf b/packages/website/public/fonts/RobotoMono-BoldItalic.ttf new file mode 100755 index 000000000..1cca0bf45 Binary files /dev/null and b/packages/website/public/fonts/RobotoMono-BoldItalic.ttf differ diff --git a/packages/website/public/fonts/RobotoMono-Italic.ttf b/packages/website/public/fonts/RobotoMono-Italic.ttf new file mode 100755 index 000000000..ef92c372c Binary files /dev/null and b/packages/website/public/fonts/RobotoMono-Italic.ttf differ diff --git a/packages/website/public/fonts/RobotoMono-Light.ttf b/packages/website/public/fonts/RobotoMono-Light.ttf new file mode 100755 index 000000000..63229b280 Binary files /dev/null and b/packages/website/public/fonts/RobotoMono-Light.ttf differ diff --git a/packages/website/public/fonts/RobotoMono-LightItalic.ttf b/packages/website/public/fonts/RobotoMono-LightItalic.ttf new file mode 100755 index 000000000..f25bed56a Binary files /dev/null and b/packages/website/public/fonts/RobotoMono-LightItalic.ttf differ diff --git a/packages/website/public/fonts/RobotoMono-Medium.ttf b/packages/website/public/fonts/RobotoMono-Medium.ttf new file mode 100755 index 000000000..88ff0c15a Binary files /dev/null and b/packages/website/public/fonts/RobotoMono-Medium.ttf differ diff --git a/packages/website/public/fonts/RobotoMono-MediumItalic.ttf b/packages/website/public/fonts/RobotoMono-MediumItalic.ttf new file mode 100755 index 000000000..307efad8f Binary files /dev/null and b/packages/website/public/fonts/RobotoMono-MediumItalic.ttf differ diff --git a/packages/website/public/fonts/RobotoMono-Regular.ttf b/packages/website/public/fonts/RobotoMono-Regular.ttf new file mode 100755 index 000000000..b158a334e Binary files /dev/null and b/packages/website/public/fonts/RobotoMono-Regular.ttf differ diff --git a/packages/website/public/fonts/RobotoMono-Thin.ttf b/packages/website/public/fonts/RobotoMono-Thin.ttf new file mode 100755 index 000000000..309484d32 Binary files /dev/null and b/packages/website/public/fonts/RobotoMono-Thin.ttf differ diff --git a/packages/website/public/fonts/RobotoMono-ThinItalic.ttf b/packages/website/public/fonts/RobotoMono-ThinItalic.ttf new file mode 100755 index 000000000..e1bb9121e Binary files /dev/null and b/packages/website/public/fonts/RobotoMono-ThinItalic.ttf differ diff --git a/packages/website/public/gifs/0xAnimation.gif b/packages/website/public/gifs/0xAnimation.gif new file mode 100644 index 000000000..b3e32a6ad Binary files /dev/null and b/packages/website/public/gifs/0xAnimation.gif differ diff --git a/packages/website/public/gifs/genesis.gif b/packages/website/public/gifs/genesis.gif new file mode 100644 index 000000000..009ecf2f8 Binary files /dev/null and b/packages/website/public/gifs/genesis.gif differ diff --git a/packages/website/public/images/0x_logo.png b/packages/website/public/images/0x_logo.png new file mode 100644 index 000000000..7b7eafe7d Binary files /dev/null and b/packages/website/public/images/0x_logo.png differ diff --git a/packages/website/public/images/advisors/fred.jpg b/packages/website/public/images/advisors/fred.jpg new file mode 100644 index 000000000..f3b37e2d9 Binary files /dev/null and b/packages/website/public/images/advisors/fred.jpg differ diff --git a/packages/website/public/images/advisors/joey.jpg b/packages/website/public/images/advisors/joey.jpg new file mode 100644 index 000000000..daccc9b55 Binary files /dev/null and b/packages/website/public/images/advisors/joey.jpg differ diff --git a/packages/website/public/images/advisors/linda.jpg b/packages/website/public/images/advisors/linda.jpg new file mode 100644 index 000000000..1ee59d301 Binary files /dev/null and b/packages/website/public/images/advisors/linda.jpg differ diff --git a/packages/website/public/images/advisors/olaf.png b/packages/website/public/images/advisors/olaf.png new file mode 100644 index 000000000..d1715081f Binary files /dev/null and b/packages/website/public/images/advisors/olaf.png differ diff --git a/packages/website/public/images/ether.png b/packages/website/public/images/ether.png new file mode 100644 index 000000000..6a40a976d Binary files /dev/null and b/packages/website/public/images/ether.png differ diff --git a/packages/website/public/images/favicon/favicon-2-16x16.png b/packages/website/public/images/favicon/favicon-2-16x16.png new file mode 100755 index 000000000..68c493c4f Binary files /dev/null and b/packages/website/public/images/favicon/favicon-2-16x16.png differ diff --git a/packages/website/public/images/favicon/favicon-2-32x32.png b/packages/website/public/images/favicon/favicon-2-32x32.png new file mode 100755 index 000000000..a5abb0eb3 Binary files /dev/null and b/packages/website/public/images/favicon/favicon-2-32x32.png differ diff --git a/packages/website/public/images/favicon/favicon.ico b/packages/website/public/images/favicon/favicon.ico new file mode 100755 index 000000000..b7ada2a1c Binary files /dev/null and b/packages/website/public/images/favicon/favicon.ico differ diff --git a/packages/website/public/images/landing/0x_chips.png b/packages/website/public/images/landing/0x_chips.png new file mode 100644 index 000000000..2e79637b0 Binary files /dev/null and b/packages/website/public/images/landing/0x_chips.png differ diff --git a/packages/website/public/images/landing/aragon.png b/packages/website/public/images/landing/aragon.png new file mode 100644 index 000000000..360b03077 Binary files /dev/null and b/packages/website/public/images/landing/aragon.png differ diff --git a/packages/website/public/images/landing/augur.png b/packages/website/public/images/landing/augur.png new file mode 100644 index 000000000..60a2ee86b Binary files /dev/null and b/packages/website/public/images/landing/augur.png differ diff --git a/packages/website/public/images/landing/currency.png b/packages/website/public/images/landing/currency.png new file mode 100644 index 000000000..280995861 Binary files /dev/null and b/packages/website/public/images/landing/currency.png differ diff --git a/packages/website/public/images/landing/dharma.png b/packages/website/public/images/landing/dharma.png new file mode 100644 index 000000000..5aa464c20 Binary files /dev/null and b/packages/website/public/images/landing/dharma.png differ diff --git a/packages/website/public/images/landing/digital_goods.png b/packages/website/public/images/landing/digital_goods.png new file mode 100644 index 000000000..bbe848441 Binary files /dev/null and b/packages/website/public/images/landing/digital_goods.png differ diff --git a/packages/website/public/images/landing/distributed_network.png b/packages/website/public/images/landing/distributed_network.png new file mode 100644 index 000000000..8dbb23083 Binary files /dev/null and b/packages/website/public/images/landing/distributed_network.png differ diff --git a/packages/website/public/images/landing/ethfinex.png b/packages/website/public/images/landing/ethfinex.png new file mode 100644 index 000000000..53e7913d6 Binary files /dev/null and b/packages/website/public/images/landing/ethfinex.png differ diff --git a/packages/website/public/images/landing/fund_management_icon.png b/packages/website/public/images/landing/fund_management_icon.png new file mode 100644 index 000000000..c45061469 Binary files /dev/null and b/packages/website/public/images/landing/fund_management_icon.png differ diff --git a/packages/website/public/images/landing/gnosis.png b/packages/website/public/images/landing/gnosis.png new file mode 100644 index 000000000..b9dd94f52 Binary files /dev/null and b/packages/website/public/images/landing/gnosis.png differ diff --git a/packages/website/public/images/landing/governance_icon.png b/packages/website/public/images/landing/governance_icon.png new file mode 100644 index 000000000..d21dc313d Binary files /dev/null and b/packages/website/public/images/landing/governance_icon.png differ diff --git a/packages/website/public/images/landing/hero_chip_image.png b/packages/website/public/images/landing/hero_chip_image.png new file mode 100644 index 000000000..447489aa7 Binary files /dev/null and b/packages/website/public/images/landing/hero_chip_image.png differ diff --git a/packages/website/public/images/landing/lendroid.png b/packages/website/public/images/landing/lendroid.png new file mode 100644 index 000000000..3b8d0c86c Binary files /dev/null and b/packages/website/public/images/landing/lendroid.png differ diff --git a/packages/website/public/images/landing/liquidity.png b/packages/website/public/images/landing/liquidity.png new file mode 100644 index 000000000..dc005dd13 Binary files /dev/null and b/packages/website/public/images/landing/liquidity.png differ diff --git a/packages/website/public/images/landing/loans_icon.png b/packages/website/public/images/landing/loans_icon.png new file mode 100644 index 000000000..b10bd83e9 Binary files /dev/null and b/packages/website/public/images/landing/loans_icon.png differ diff --git a/packages/website/public/images/landing/maker.png b/packages/website/public/images/landing/maker.png new file mode 100644 index 000000000..f7a04191e Binary files /dev/null and b/packages/website/public/images/landing/maker.png differ diff --git a/packages/website/public/images/landing/melonport.png b/packages/website/public/images/landing/melonport.png new file mode 100644 index 000000000..bdb95707c Binary files /dev/null and b/packages/website/public/images/landing/melonport.png differ diff --git a/packages/website/public/images/landing/open_source.png b/packages/website/public/images/landing/open_source.png new file mode 100644 index 000000000..2c280742a Binary files /dev/null and b/packages/website/public/images/landing/open_source.png differ diff --git a/packages/website/public/images/landing/paradex.png b/packages/website/public/images/landing/paradex.png new file mode 100644 index 000000000..f6a1e479f Binary files /dev/null and b/packages/website/public/images/landing/paradex.png differ diff --git a/packages/website/public/images/landing/prediction_market_icon.png b/packages/website/public/images/landing/prediction_market_icon.png new file mode 100644 index 000000000..063595c49 Binary files /dev/null and b/packages/website/public/images/landing/prediction_market_icon.png differ diff --git a/packages/website/public/images/landing/project_logos/anx.png b/packages/website/public/images/landing/project_logos/anx.png new file mode 100644 index 000000000..fa04ce327 Binary files /dev/null and b/packages/website/public/images/landing/project_logos/anx.png differ diff --git a/packages/website/public/images/landing/project_logos/aragon.png b/packages/website/public/images/landing/project_logos/aragon.png new file mode 100644 index 000000000..b0cf805d3 Binary files /dev/null and b/packages/website/public/images/landing/project_logos/aragon.png differ diff --git a/packages/website/public/images/landing/project_logos/auctus.png b/packages/website/public/images/landing/project_logos/auctus.png new file mode 100644 index 000000000..2bdc9a42c Binary files /dev/null and b/packages/website/public/images/landing/project_logos/auctus.png differ diff --git a/packages/website/public/images/landing/project_logos/augur.png b/packages/website/public/images/landing/project_logos/augur.png new file mode 100644 index 000000000..47d4f8465 Binary files /dev/null and b/packages/website/public/images/landing/project_logos/augur.png differ diff --git a/packages/website/public/images/landing/project_logos/blocknet.png b/packages/website/public/images/landing/project_logos/blocknet.png new file mode 100644 index 000000000..96aa97953 Binary files /dev/null and b/packages/website/public/images/landing/project_logos/blocknet.png differ diff --git a/packages/website/public/images/landing/project_logos/chronobank.png b/packages/website/public/images/landing/project_logos/chronobank.png new file mode 100644 index 000000000..ad5ff0e5b Binary files /dev/null and b/packages/website/public/images/landing/project_logos/chronobank.png differ diff --git a/packages/website/public/images/landing/project_logos/dharma.png b/packages/website/public/images/landing/project_logos/dharma.png new file mode 100644 index 000000000..a74736ca2 Binary files /dev/null and b/packages/website/public/images/landing/project_logos/dharma.png differ diff --git a/packages/website/public/images/landing/project_logos/district0x.png b/packages/website/public/images/landing/project_logos/district0x.png new file mode 100644 index 000000000..12aa310f0 Binary files /dev/null and b/packages/website/public/images/landing/project_logos/district0x.png differ diff --git a/packages/website/public/images/landing/project_logos/dydx.png b/packages/website/public/images/landing/project_logos/dydx.png new file mode 100644 index 000000000..f2525292c Binary files /dev/null and b/packages/website/public/images/landing/project_logos/dydx.png differ diff --git a/packages/website/public/images/landing/project_logos/ethfinex-top.png b/packages/website/public/images/landing/project_logos/ethfinex-top.png new file mode 100644 index 000000000..5eda914ca Binary files /dev/null and b/packages/website/public/images/landing/project_logos/ethfinex-top.png differ diff --git a/packages/website/public/images/landing/project_logos/ethix.png b/packages/website/public/images/landing/project_logos/ethix.png new file mode 100644 index 000000000..9b202502c Binary files /dev/null and b/packages/website/public/images/landing/project_logos/ethix.png differ diff --git a/packages/website/public/images/landing/project_logos/lendroid.png b/packages/website/public/images/landing/project_logos/lendroid.png new file mode 100644 index 000000000..a5e1eb51e Binary files /dev/null and b/packages/website/public/images/landing/project_logos/lendroid.png differ diff --git a/packages/website/public/images/landing/project_logos/maker.png b/packages/website/public/images/landing/project_logos/maker.png new file mode 100644 index 000000000..d3bb927a5 Binary files /dev/null and b/packages/website/public/images/landing/project_logos/maker.png differ diff --git a/packages/website/public/images/landing/project_logos/melonport.png b/packages/website/public/images/landing/project_logos/melonport.png new file mode 100644 index 000000000..9533faee2 Binary files /dev/null and b/packages/website/public/images/landing/project_logos/melonport.png differ diff --git a/packages/website/public/images/landing/project_logos/paradex_top.png b/packages/website/public/images/landing/project_logos/paradex_top.png new file mode 100644 index 000000000..3fe9472b9 Binary files /dev/null and b/packages/website/public/images/landing/project_logos/paradex_top.png differ diff --git a/packages/website/public/images/landing/project_logos/radar_relay_top.png b/packages/website/public/images/landing/project_logos/radar_relay_top.png new file mode 100644 index 000000000..737159959 Binary files /dev/null and b/packages/website/public/images/landing/project_logos/radar_relay_top.png differ diff --git a/packages/website/public/images/landing/project_logos/status.png b/packages/website/public/images/landing/project_logos/status.png new file mode 100644 index 000000000..24c7e177a Binary files /dev/null and b/packages/website/public/images/landing/project_logos/status.png differ diff --git a/packages/website/public/images/landing/project_logos/the_ocean.png b/packages/website/public/images/landing/project_logos/the_ocean.png new file mode 100644 index 000000000..74678b5d4 Binary files /dev/null and b/packages/website/public/images/landing/project_logos/the_ocean.png differ diff --git a/packages/website/public/images/landing/radar_relay.png b/packages/website/public/images/landing/radar_relay.png new file mode 100644 index 000000000..297f55cfb Binary files /dev/null and b/packages/website/public/images/landing/radar_relay.png differ diff --git a/packages/website/public/images/landing/relayer_diagram.png b/packages/website/public/images/landing/relayer_diagram.png new file mode 100644 index 000000000..44c4ef707 Binary files /dev/null and b/packages/website/public/images/landing/relayer_diagram.png differ diff --git a/packages/website/public/images/landing/stable_tokens_icon.png b/packages/website/public/images/landing/stable_tokens_icon.png new file mode 100644 index 000000000..40e372606 Binary files /dev/null and b/packages/website/public/images/landing/stable_tokens_icon.png differ diff --git a/packages/website/public/images/landing/stocks.png b/packages/website/public/images/landing/stocks.png new file mode 100644 index 000000000..e244cd0c5 Binary files /dev/null and b/packages/website/public/images/landing/stocks.png differ diff --git a/packages/website/public/images/landing/tokenized_world.png b/packages/website/public/images/landing/tokenized_world.png new file mode 100644 index 000000000..b284eaa88 Binary files /dev/null and b/packages/website/public/images/landing/tokenized_world.png differ diff --git a/packages/website/public/images/loading_poster.png b/packages/website/public/images/loading_poster.png new file mode 100644 index 000000000..e5618f260 Binary files /dev/null and b/packages/website/public/images/loading_poster.png differ diff --git a/packages/website/public/images/logos/FBG.png b/packages/website/public/images/logos/FBG.png new file mode 100644 index 000000000..0a91bcabc Binary files /dev/null and b/packages/website/public/images/logos/FBG.png differ diff --git a/packages/website/public/images/logos/aragon.png b/packages/website/public/images/logos/aragon.png new file mode 100644 index 000000000..db4d81905 Binary files /dev/null and b/packages/website/public/images/logos/aragon.png differ diff --git a/packages/website/public/images/logos/augur.png b/packages/website/public/images/logos/augur.png new file mode 100644 index 000000000..709da2f1a Binary files /dev/null and b/packages/website/public/images/logos/augur.png differ diff --git a/packages/website/public/images/logos/blockchain_capital.png b/packages/website/public/images/logos/blockchain_capital.png new file mode 100644 index 000000000..42fdcbfa1 Binary files /dev/null and b/packages/website/public/images/logos/blockchain_capital.png differ diff --git a/packages/website/public/images/logos/chronobank.png b/packages/website/public/images/logos/chronobank.png new file mode 100644 index 000000000..f94aa3fee Binary files /dev/null and b/packages/website/public/images/logos/chronobank.png differ diff --git a/packages/website/public/images/logos/dharma.png b/packages/website/public/images/logos/dharma.png new file mode 100644 index 000000000..65d902a1e Binary files /dev/null and b/packages/website/public/images/logos/dharma.png differ diff --git a/packages/website/public/images/logos/district0x.png b/packages/website/public/images/logos/district0x.png new file mode 100644 index 000000000..e2b4c7e4a Binary files /dev/null and b/packages/website/public/images/logos/district0x.png differ diff --git a/packages/website/public/images/logos/jen_advisors.png b/packages/website/public/images/logos/jen_advisors.png new file mode 100644 index 000000000..de0395d3d Binary files /dev/null and b/packages/website/public/images/logos/jen_advisors.png differ diff --git a/packages/website/public/images/logos/maker.png b/packages/website/public/images/logos/maker.png new file mode 100644 index 000000000..48c08d15d Binary files /dev/null and b/packages/website/public/images/logos/maker.png differ diff --git a/packages/website/public/images/logos/melonport.png b/packages/website/public/images/logos/melonport.png new file mode 100644 index 000000000..b973e081f Binary files /dev/null and b/packages/website/public/images/logos/melonport.png differ diff --git a/packages/website/public/images/logos/openANX.png b/packages/website/public/images/logos/openANX.png new file mode 100644 index 000000000..e0167257f Binary files /dev/null and b/packages/website/public/images/logos/openANX.png differ diff --git a/packages/website/public/images/logos/pantera_capital.png b/packages/website/public/images/logos/pantera_capital.png new file mode 100644 index 000000000..9cffdf39f Binary files /dev/null and b/packages/website/public/images/logos/pantera_capital.png differ diff --git a/packages/website/public/images/logos/polychain_capital.png b/packages/website/public/images/logos/polychain_capital.png new file mode 100644 index 000000000..2b7782134 Binary files /dev/null and b/packages/website/public/images/logos/polychain_capital.png differ diff --git a/packages/website/public/images/og_image.png b/packages/website/public/images/og_image.png new file mode 100644 index 000000000..74265bdf8 Binary files /dev/null and b/packages/website/public/images/og_image.png differ diff --git a/packages/website/public/images/protocol_logo_black.png b/packages/website/public/images/protocol_logo_black.png new file mode 100644 index 000000000..36a905d5a Binary files /dev/null and b/packages/website/public/images/protocol_logo_black.png differ diff --git a/packages/website/public/images/protocol_logo_white.png b/packages/website/public/images/protocol_logo_white.png new file mode 100644 index 000000000..a405ad6d5 Binary files /dev/null and b/packages/website/public/images/protocol_logo_white.png differ diff --git a/packages/website/public/images/social/github.png b/packages/website/public/images/social/github.png new file mode 100644 index 000000000..2c2a3e918 Binary files /dev/null and b/packages/website/public/images/social/github.png differ diff --git a/packages/website/public/images/social/medium.png b/packages/website/public/images/social/medium.png new file mode 100644 index 000000000..11e8f2c44 Binary files /dev/null and b/packages/website/public/images/social/medium.png differ diff --git a/packages/website/public/images/social/reddit.png b/packages/website/public/images/social/reddit.png new file mode 100644 index 000000000..3fbe6229a Binary files /dev/null and b/packages/website/public/images/social/reddit.png differ diff --git a/packages/website/public/images/social/rocketchat.png b/packages/website/public/images/social/rocketchat.png new file mode 100644 index 000000000..58ff8d293 Binary files /dev/null and b/packages/website/public/images/social/rocketchat.png differ diff --git a/packages/website/public/images/social/slack.png b/packages/website/public/images/social/slack.png new file mode 100644 index 000000000..c4b2d7b81 Binary files /dev/null and b/packages/website/public/images/social/slack.png differ diff --git a/packages/website/public/images/social/twitter.png b/packages/website/public/images/social/twitter.png new file mode 100644 index 000000000..fe0d691a9 Binary files /dev/null and b/packages/website/public/images/social/twitter.png differ diff --git a/packages/website/public/images/team/alex.jpg b/packages/website/public/images/team/alex.jpg new file mode 100644 index 000000000..ae6888804 Binary files /dev/null and b/packages/website/public/images/team/alex.jpg differ diff --git a/packages/website/public/images/team/amir.jpeg b/packages/website/public/images/team/amir.jpeg new file mode 100644 index 000000000..7ee16263a Binary files /dev/null and b/packages/website/public/images/team/amir.jpeg differ diff --git a/packages/website/public/images/team/anyone.png b/packages/website/public/images/team/anyone.png new file mode 100644 index 000000000..4de26b0ce Binary files /dev/null and b/packages/website/public/images/team/anyone.png differ diff --git a/packages/website/public/images/team/ben.jpg b/packages/website/public/images/team/ben.jpg new file mode 100644 index 000000000..b42d0a42a Binary files /dev/null and b/packages/website/public/images/team/ben.jpg differ diff --git a/packages/website/public/images/team/brandon.png b/packages/website/public/images/team/brandon.png new file mode 100644 index 000000000..ebd3cf101 Binary files /dev/null and b/packages/website/public/images/team/brandon.png differ diff --git a/packages/website/public/images/team/fabio.jpg b/packages/website/public/images/team/fabio.jpg new file mode 100644 index 000000000..da87a9e95 Binary files /dev/null and b/packages/website/public/images/team/fabio.jpg differ diff --git a/packages/website/public/images/team/leonid.png b/packages/website/public/images/team/leonid.png new file mode 100644 index 000000000..4acbf87c8 Binary files /dev/null and b/packages/website/public/images/team/leonid.png differ diff --git a/packages/website/public/images/team/philippe.png b/packages/website/public/images/team/philippe.png new file mode 100644 index 000000000..cd43c2754 Binary files /dev/null and b/packages/website/public/images/team/philippe.png differ diff --git a/packages/website/public/images/team/will.jpg b/packages/website/public/images/team/will.jpg new file mode 100644 index 000000000..7de028032 Binary files /dev/null and b/packages/website/public/images/team/will.jpg differ diff --git a/packages/website/public/images/token_icons/adtoken.png b/packages/website/public/images/token_icons/adtoken.png new file mode 100644 index 000000000..59290af6b Binary files /dev/null and b/packages/website/public/images/token_icons/adtoken.png differ diff --git a/packages/website/public/images/token_icons/aragon.png b/packages/website/public/images/token_icons/aragon.png new file mode 100644 index 000000000..d162aab24 Binary files /dev/null and b/packages/website/public/images/token_icons/aragon.png differ diff --git a/packages/website/public/images/token_icons/augur.png b/packages/website/public/images/token_icons/augur.png new file mode 100644 index 000000000..31c257ba9 Binary files /dev/null and b/packages/website/public/images/token_icons/augur.png differ diff --git a/packages/website/public/images/token_icons/bancor.png b/packages/website/public/images/token_icons/bancor.png new file mode 100644 index 000000000..d2b2fa472 Binary files /dev/null and b/packages/website/public/images/token_icons/bancor.png differ diff --git a/packages/website/public/images/token_icons/basicattentiontoken.png b/packages/website/public/images/token_icons/basicattentiontoken.png new file mode 100644 index 000000000..77e7dfb1f Binary files /dev/null and b/packages/website/public/images/token_icons/basicattentiontoken.png differ diff --git a/packages/website/public/images/token_icons/bitquence.png b/packages/website/public/images/token_icons/bitquence.png new file mode 100644 index 000000000..d8a2c6960 Binary files /dev/null and b/packages/website/public/images/token_icons/bitquence.png differ diff --git a/packages/website/public/images/token_icons/btc.png b/packages/website/public/images/token_icons/btc.png new file mode 100644 index 000000000..1d9fc8347 Binary files /dev/null and b/packages/website/public/images/token_icons/btc.png differ diff --git a/packages/website/public/images/token_icons/civic.png b/packages/website/public/images/token_icons/civic.png new file mode 100644 index 000000000..1daf28d00 Binary files /dev/null and b/packages/website/public/images/token_icons/civic.png differ diff --git a/packages/website/public/images/token_icons/clams.png b/packages/website/public/images/token_icons/clams.png new file mode 100644 index 000000000..04c2ba7d3 Binary files /dev/null and b/packages/website/public/images/token_icons/clams.png differ diff --git a/packages/website/public/images/token_icons/cofound-it.png b/packages/website/public/images/token_icons/cofound-it.png new file mode 100644 index 000000000..7bccd6248 Binary files /dev/null and b/packages/website/public/images/token_icons/cofound-it.png differ diff --git a/packages/website/public/images/token_icons/default.png b/packages/website/public/images/token_icons/default.png new file mode 100644 index 000000000..7ce754030 Binary files /dev/null and b/packages/website/public/images/token_icons/default.png differ diff --git a/packages/website/public/images/token_icons/digixdao.png b/packages/website/public/images/token_icons/digixdao.png new file mode 100644 index 000000000..f292db716 Binary files /dev/null and b/packages/website/public/images/token_icons/digixdao.png differ diff --git a/packages/website/public/images/token_icons/district0x.png b/packages/website/public/images/token_icons/district0x.png new file mode 100644 index 000000000..edb8f2238 Binary files /dev/null and b/packages/website/public/images/token_icons/district0x.png differ diff --git a/packages/website/public/images/token_icons/edgeless.png b/packages/website/public/images/token_icons/edgeless.png new file mode 100644 index 000000000..606784154 Binary files /dev/null and b/packages/website/public/images/token_icons/edgeless.png differ diff --git a/packages/website/public/images/token_icons/eos.png b/packages/website/public/images/token_icons/eos.png new file mode 100644 index 000000000..a08f3c042 Binary files /dev/null and b/packages/website/public/images/token_icons/eos.png differ diff --git a/packages/website/public/images/token_icons/ether_erc20.png b/packages/website/public/images/token_icons/ether_erc20.png new file mode 100644 index 000000000..f4154db7b Binary files /dev/null and b/packages/website/public/images/token_icons/ether_erc20.png differ diff --git a/packages/website/public/images/token_icons/etheroll.png b/packages/website/public/images/token_icons/etheroll.png new file mode 100644 index 000000000..89dd5e04b Binary files /dev/null and b/packages/website/public/images/token_icons/etheroll.png differ diff --git a/packages/website/public/images/token_icons/firstblood.jpg b/packages/website/public/images/token_icons/firstblood.jpg new file mode 100644 index 000000000..64bb65605 Binary files /dev/null and b/packages/website/public/images/token_icons/firstblood.jpg differ diff --git a/packages/website/public/images/token_icons/funfair.png b/packages/website/public/images/token_icons/funfair.png new file mode 100644 index 000000000..1b7c67ec6 Binary files /dev/null and b/packages/website/public/images/token_icons/funfair.png differ diff --git a/packages/website/public/images/token_icons/gnosis.png b/packages/website/public/images/token_icons/gnosis.png new file mode 100644 index 000000000..0111846d0 Binary files /dev/null and b/packages/website/public/images/token_icons/gnosis.png differ diff --git a/packages/website/public/images/token_icons/golem.png b/packages/website/public/images/token_icons/golem.png new file mode 100644 index 000000000..e61a4367d Binary files /dev/null and b/packages/website/public/images/token_icons/golem.png differ diff --git a/packages/website/public/images/token_icons/iconomi.png b/packages/website/public/images/token_icons/iconomi.png new file mode 100644 index 000000000..3499e4765 Binary files /dev/null and b/packages/website/public/images/token_icons/iconomi.png differ diff --git a/packages/website/public/images/token_icons/iexec.png b/packages/website/public/images/token_icons/iexec.png new file mode 100644 index 000000000..ef4860457 Binary files /dev/null and b/packages/website/public/images/token_icons/iexec.png differ diff --git a/packages/website/public/images/token_icons/lunyr.png b/packages/website/public/images/token_icons/lunyr.png new file mode 100644 index 000000000..f77094ba5 Binary files /dev/null and b/packages/website/public/images/token_icons/lunyr.png differ diff --git a/packages/website/public/images/token_icons/makerdao.png b/packages/website/public/images/token_icons/makerdao.png new file mode 100644 index 000000000..adbc9f38c Binary files /dev/null and b/packages/website/public/images/token_icons/makerdao.png differ diff --git a/packages/website/public/images/token_icons/melon.png b/packages/website/public/images/token_icons/melon.png new file mode 100644 index 000000000..29f58e631 Binary files /dev/null and b/packages/website/public/images/token_icons/melon.png differ diff --git a/packages/website/public/images/token_icons/metal.png b/packages/website/public/images/token_icons/metal.png new file mode 100644 index 000000000..d8a8c33ec Binary files /dev/null and b/packages/website/public/images/token_icons/metal.png differ diff --git a/packages/website/public/images/token_icons/monaco.png b/packages/website/public/images/token_icons/monaco.png new file mode 100644 index 000000000..865341fd3 Binary files /dev/null and b/packages/website/public/images/token_icons/monaco.png differ diff --git a/packages/website/public/images/token_icons/numeraire.png b/packages/website/public/images/token_icons/numeraire.png new file mode 100644 index 000000000..698f7cfdd Binary files /dev/null and b/packages/website/public/images/token_icons/numeraire.png differ diff --git a/packages/website/public/images/token_icons/omisego.png b/packages/website/public/images/token_icons/omisego.png new file mode 100644 index 000000000..40a86b9d7 Binary files /dev/null and b/packages/website/public/images/token_icons/omisego.png differ diff --git a/packages/website/public/images/token_icons/qtum.png b/packages/website/public/images/token_icons/qtum.png new file mode 100644 index 000000000..97e227c5c Binary files /dev/null and b/packages/website/public/images/token_icons/qtum.png differ diff --git a/packages/website/public/images/token_icons/santiment.png b/packages/website/public/images/token_icons/santiment.png new file mode 100644 index 000000000..05ce98c1d Binary files /dev/null and b/packages/website/public/images/token_icons/santiment.png differ diff --git a/packages/website/public/images/token_icons/singularity.png b/packages/website/public/images/token_icons/singularity.png new file mode 100644 index 000000000..9db788935 Binary files /dev/null and b/packages/website/public/images/token_icons/singularity.png differ diff --git a/packages/website/public/images/token_icons/status.png b/packages/website/public/images/token_icons/status.png new file mode 100644 index 000000000..a73ba23ba Binary files /dev/null and b/packages/website/public/images/token_icons/status.png differ diff --git a/packages/website/public/images/token_icons/storjcoinx.png b/packages/website/public/images/token_icons/storjcoinx.png new file mode 100644 index 000000000..87c4d4292 Binary files /dev/null and b/packages/website/public/images/token_icons/storjcoinx.png differ diff --git a/packages/website/public/images/token_icons/taas.png b/packages/website/public/images/token_icons/taas.png new file mode 100644 index 000000000..4cca722f7 Binary files /dev/null and b/packages/website/public/images/token_icons/taas.png differ diff --git a/packages/website/public/images/token_icons/tenx.png b/packages/website/public/images/token_icons/tenx.png new file mode 100644 index 000000000..d9ffca043 Binary files /dev/null and b/packages/website/public/images/token_icons/tenx.png differ diff --git a/packages/website/public/images/token_icons/tokencard.png b/packages/website/public/images/token_icons/tokencard.png new file mode 100644 index 000000000..490c1be69 Binary files /dev/null and b/packages/website/public/images/token_icons/tokencard.png differ diff --git a/packages/website/public/images/token_icons/trust.png b/packages/website/public/images/token_icons/trust.png new file mode 100644 index 000000000..62b412b41 Binary files /dev/null and b/packages/website/public/images/token_icons/trust.png differ diff --git a/packages/website/public/images/token_icons/wings.png b/packages/website/public/images/token_icons/wings.png new file mode 100644 index 000000000..cd0eb4213 Binary files /dev/null and b/packages/website/public/images/token_icons/wings.png differ diff --git a/packages/website/public/images/token_icons/zero_ex.png b/packages/website/public/images/token_icons/zero_ex.png new file mode 100644 index 000000000..6c6deef11 Binary files /dev/null and b/packages/website/public/images/token_icons/zero_ex.png differ diff --git a/packages/website/public/images/trade_arrows.png b/packages/website/public/images/trade_arrows.png new file mode 100644 index 000000000..21a8e1881 Binary files /dev/null and b/packages/website/public/images/trade_arrows.png differ diff --git a/packages/website/public/images/zrx_pie_chart.png b/packages/website/public/images/zrx_pie_chart.png new file mode 100644 index 000000000..16f5126b9 Binary files /dev/null and b/packages/website/public/images/zrx_pie_chart.png differ diff --git a/packages/website/public/images/zrx_token.png b/packages/website/public/images/zrx_token.png new file mode 100644 index 000000000..8c71798de Binary files /dev/null and b/packages/website/public/images/zrx_token.png differ diff --git a/packages/website/public/index.html b/packages/website/public/index.html new file mode 100644 index 000000000..c6f2f666c --- /dev/null +++ b/packages/website/public/index.html @@ -0,0 +1,78 @@ + + + + + + + + + + + 0x: The Protocol for Trading Tokens + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + + + + diff --git a/packages/website/public/js/rollbar.umd.nojson.min.js b/packages/website/public/js/rollbar.umd.nojson.min.js new file mode 100644 index 000000000..f2b05d00a --- /dev/null +++ b/packages/website/public/js/rollbar.umd.nojson.min.js @@ -0,0 +1 @@ +!function(e,r){if("object"==typeof exports&&"object"==typeof module)module.exports=r();else if("function"==typeof define&&define.amd)define([],r);else{var t=r();for(var n in t)("object"==typeof exports?exports:e)[n]=t[n]}}(this,function(){return function(e){function r(n){if(t[n])return t[n].exports;var o=t[n]={exports:{},id:n,loaded:!1};return e[n].call(o.exports,o,o.exports,r),o.loaded=!0,o.exports}var t={};return r.m=e,r.c=t,r.p="",r(0)}([function(e,r,t){e.exports=t(1)},function(e,r,t){"use strict";function n(){var e="undefined"==typeof JSON?{}:JSON;o.setupJSON(e)}var o=t(2),i=t(3);n();var a=window._rollbarConfig,s=a&&a.globalAlias||"Rollbar",u=window[s]&&"undefined"!=typeof window[s].shimId;!u&&a?o.wrapper.init(a):(window.Rollbar=o.wrapper,window.RollbarNotifier=i.Notifier),e.exports=o.wrapper},function(e,r,t){"use strict";function n(e,r,t){!t[4]&&window._rollbarWrappedError&&(t[4]=window._rollbarWrappedError,window._rollbarWrappedError=null),e.uncaughtError.apply(e,t),r&&r.apply(window,t)}function o(e,r){if(r.hasOwnProperty&&r.hasOwnProperty("addEventListener")){var t=r.addEventListener;r.addEventListener=function(r,n,o){t.call(this,r,e.wrap(n),o)};var n=r.removeEventListener;r.removeEventListener=function(e,r,t){n.call(this,e,r&&r._wrapped||r,t)}}}var i=t(3),a=t(8),s=i.Notifier;window._rollbarWrappedError=null;var u={};u.init=function(e,r){var t=new s(r);if(t.configure(e),e.captureUncaught){var i;r&&a.isType(r._rollbarOldOnError,"function")?i=r._rollbarOldOnError:window.onerror&&!window.onerror.belongsToShim&&(i=window.onerror),window.onerror=function(){var e=Array.prototype.slice.call(arguments,0);n(t,i,e)};var u,c,l=["EventTarget","Window","Node","ApplicationCache","AudioTrackList","ChannelMergerNode","CryptoOperation","EventSource","FileReader","HTMLUnknownElement","IDBDatabase","IDBRequest","IDBTransaction","KeyOperation","MediaController","MessagePort","ModalWindow","Notification","SVGElementInstance","Screen","TextTrack","TextTrackCue","TextTrackList","WebSocket","WebSocketWorker","Worker","XMLHttpRequest","XMLHttpRequestEventTarget","XMLHttpRequestUpload"];for(u=0;u=6e4&&(L=i,R=0);var a=window._globalRollbarOptions.maxItems,c=window._globalRollbarOptions.itemsPerMinute,l=function(){return!n.ignoreRateLimit&&a>=1&&T>=a},p=function(){return!n.ignoreRateLimit&&c>=1&&R>=c};return l()?void o(new Error(a+" max items reached")):p()?void o(new Error(c+" items per minute reached")):(T++,R++,l()&&_._log(_.options.uncaughtErrorLevel,"maxItems has been hit. Ignoring errors for the remainder of the current page load.",null,{maxItems:a},null,!1,!0),n.ignoreRateLimit&&delete n.ignoreRateLimit,void y.post(r,t,n,function(r,t){return r?(r instanceof b&&(e.callback=function(){},setTimeout(function(){u(e)},s.RETRY_DELAY)),o(r)):o(null,t)}))}var h,g=t(4),m=t(5),v=t(8),w=t(11),y=w.XHR,b=w.ConnectionError,E=null;s.NOTIFIER_VERSION="1.9.4",s.DEFAULT_ENDPOINT="api.rollbar.com/api/1/",s.DEFAULT_SCRUB_FIELDS=["pw","pass","passwd","password","secret","confirm_password","confirmPassword","password_confirmation","passwordConfirmation","access_token","accessToken","secret_key","secretKey","secretToken"],s.DEFAULT_LOG_LEVEL="debug",s.DEFAULT_REPORT_LEVEL="debug",s.DEFAULT_UNCAUGHT_ERROR_LEVEL="error",s.DEFAULT_ITEMS_PER_MIN=60,s.DEFAULT_MAX_ITEMS=0,s.LEVELS={debug:0,info:1,warning:2,error:3,critical:4},s.RETRY_DELAY=1e4,window._rollbarPayloadQueue=window._rollbarPayloadQueue||[],window._globalRollbarOptions={startTime:(new Date).getTime(),maxItems:s.DEFAULT_MAX_ITEMS,itemsPerMinute:s.DEFAULT_ITEMS_PER_MIN};var _,x=s.prototype;x._getLogArgs=function(e){for(var r,t,n,i,a,u,c=this.options.logLevel||s.DEFAULT_LOG_LEVEL,l=[],p=0;p-1&&(r=r.replace(/eval code/g,"eval").replace(/(\(eval at [^\()]*)|(\)\,.*$)/g,""));var t=r.replace(/^\s+/,"").replace(/\(eval code/g,"(").split(/\s+/).slice(1),n=this.extractLocation(t.pop()),o=t.join(" ")||void 0,i="eval"===n[0]?void 0:n[0];return new e(o,void 0,i,n[1],n[2],r)},this)},parseFFOrSafari:function(n){var o=t(n.stack.split("\n"),function(e){return!e.match(i)},this);return r(o,function(r){if(r.indexOf(" > eval")>-1&&(r=r.replace(/ line (\d+)(?: > eval line \d+)* > eval\:\d+\:\d+/g,":$1")),r.indexOf("@")===-1&&r.indexOf(":")===-1)return new e(r);var t=r.split("@"),n=this.extractLocation(t.pop()),o=t.shift()||void 0;return new e(o,void 0,n[0],n[1],n[2],r)},this)},parseOpera:function(e){return!e.stacktrace||e.message.indexOf("\n")>-1&&e.message.split("\n").length>e.stacktrace.split("\n").length?this.parseOpera9(e):e.stack?this.parseOpera11(e):this.parseOpera10(e)},parseOpera9:function(r){for(var t=/Line (\d+).*script (?:in )?(\S+)/i,n=r.message.split("\n"),o=[],i=2,a=n.length;i/,"$2").replace(/\([^\)]*\)/g,"")||void 0;i.match(/\(([^\)]*)\)/)&&(t=i.replace(/^[^\(]+\(([^\)]*)\)$/,"$1"));var s=void 0===t||"[arguments not available]"===t?void 0:t.split(",");return new e(a,s,o[0],o[1],o[2],r)},this)}}})},function(e,r,t){var n,o,i;!function(t,a){"use strict";o=[],n=a,i="function"==typeof n?n.apply(r,o):n,!(void 0!==i&&(e.exports=i))}(this,function(){"use strict";function e(e){return!isNaN(parseFloat(e))&&isFinite(e)}function r(e,r,t,n,o,i){void 0!==e&&this.setFunctionName(e),void 0!==r&&this.setArgs(r),void 0!==t&&this.setFileName(t),void 0!==n&&this.setLineNumber(n),void 0!==o&&this.setColumnNumber(o),void 0!==i&&this.setSource(i)}return r.prototype={getFunctionName:function(){return this.functionName},setFunctionName:function(e){this.functionName=String(e)},getArgs:function(){return this.args},setArgs:function(e){if("[object Array]"!==Object.prototype.toString.call(e))throw new TypeError("Args must be an Array");this.args=e},getFileName:function(){return this.fileName},setFileName:function(e){this.fileName=String(e)},getLineNumber:function(){return this.lineNumber},setLineNumber:function(r){if(!e(r))throw new TypeError("Line Number must be a Number");this.lineNumber=Number(r)},getColumnNumber:function(){return this.columnNumber},setColumnNumber:function(r){if(!e(r))throw new TypeError("Column Number must be a Number");this.columnNumber=Number(r)},getSource:function(){return this.source},setSource:function(e){this.source=String(e)},toString:function(){var r=this.getFunctionName()||"{anonymous}",t="("+(this.getArgs()||[]).join(",")+")",n=this.getFileName()?"@"+this.getFileName():"",o=e(this.getLineNumber())?":"+this.getLineNumber():"",i=e(this.getColumnNumber())?":"+this.getColumnNumber():"";return r+t+n+o+i}},r})},function(e,r,t){"use strict";function n(e){v=e}function o(e){return{}.toString.call(e).match(/\s([a-zA-Z]+)/)[1].toLowerCase()}function i(e,r){return o(e)===r}function a(e){if(!i(e,"string"))throw new Error("received invalid input");for(var r=w,t=r.parser[r.strictMode?"strict":"loose"].exec(e),n={},o=14;o--;)n[r.key[o]]=t[o]||"";return n[r.q.name]={},n[r.key[12]].replace(r.q.parser,function(e,t,o){t&&(n[r.q.name][t]=o)}),n}function s(e){var r=a(e);return""===r.anchor&&(r.source=r.source.replace("#","")),e=r.source.replace("?"+r.query,"")}function u(e,r){var t,n,o,a=i(e,"object"),s=i(e,"array"),c=[];if(a)for(t in e)e.hasOwnProperty(t)&&c.push(t);else if(s)for(o=0;o500&&(t=t.substr(0,500)+"...")):"undefined"==typeof t&&(t="undefined"),e.push(t)}return e.join(" ")}function d(){m.ieVersion()<=8?console.error(f.apply(null,arguments)):console.error.apply(null,arguments)}function h(){m.ieVersion()<=8?console.info(f.apply(null,arguments)):console.info.apply(null,arguments)}function g(){m.ieVersion()<=8?console.log(f.apply(null,arguments)):console.log.apply(null,arguments)}t(9);var m=t(10),v=null,w={strictMode:!1,key:["source","protocol","authority","userInfo","user","password","host","port","relative","path","directory","file","query","anchor"],q:{name:"queryKey",parser:/(?:^|&)([^&=]*)=?([^&]*)/g},parser:{strict:/^(?:([^:\/?#]+):)?(?:\/\/((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?))?((((?:[^?#\/]*\/)*)([^?#]*))(?:\?([^#]*))?(?:#(.*))?)/,loose:/^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/}},y={setupJSON:n,isType:i,parseUri:a,parseUriOptions:w,redact:c,sanitizeUrl:s,traverse:u,typeName:o,uuid4:l,objectCreate:p,consoleError:d,consoleInfo:h,consoleLog:g};e.exports=y},function(e,r){!function(e){"use strict";e.console||(e.console={});for(var r,t,n=e.console,o=function(){},i=["memory"],a="assert,clear,count,debug,dir,dirxml,error,exception,group,groupCollapsed,groupEnd,info,log,markTimeline,profile,profiles,profileEnd,show,table,time,timeEnd,timeline,timelineEnd,timeStamp,trace,warn".split(",");r=i.pop();)n[r]||(n[r]={});for(;t=a.pop();)n[t]||(n[t]=o)}("undefined"==typeof window?this:window)},function(e,r){"use strict";function t(){for(var e,r=3,t=document.createElement("div"),n=t.getElementsByTagName("i");t.innerHTML="",n[0];);return r>4?r:e}var n={ieVersion:t};e.exports=n},function(e,r,t){"use strict";function n(e){a=e}function o(e){this.name="Connection Error",this.message=e,this.stack=(new Error).stack}var i=t(8),a=null;o.prototype=i.objectCreate(Error.prototype),o.prototype.constructor=o;var s={XMLHttpFactories:[function(){return new XMLHttpRequest},function(){return new ActiveXObject("Msxml2.XMLHTTP")},function(){return new ActiveXObject("Msxml3.XMLHTTP")},function(){return new ActiveXObject("Microsoft.XMLHTTP")}],createXMLHTTPObject:function(){var e,r=!1,t=s.XMLHttpFactories,n=t.length;for(e=0;e=400&&u.status<600?(403==u.status&&i.consoleError("[Rollbar]:"+e.message),n(new Error(String(u.status)))):n(new o("XHR response had no status code (likely connection failure)"))}}catch(e){var r;r=e&&e.stack?e:new Error(e),n(r)}};u.open("POST",e,!0),u.setRequestHeader&&(u.setRequestHeader("Content-Type","application/json"),u.setRequestHeader("X-Rollbar-Access-Token",r)),u.onreadystatechange=c,u.send(t)}catch(r){if("undefined"!=typeof XDomainRequest){"http:"===window.location.href.substring(0,5)&&"https"===e.substring(0,5)&&(e="http"+e.substring(5));var l=function(){n(new o("Request timed out"))},p=function(){n(new Error("Error during request"))},f=function(){n(null,a.parse(u.responseText))};u=new XDomainRequest,u.onprogress=function(){},u.ontimeout=l,u.onerror=p,u.onload=f,u.open("POST",e,!0),u.send(t)}}}catch(e){n(e)}}};e.exports={XHR:s,setupJSON:n,ConnectionError:o}}])}); diff --git a/packages/website/public/pdfs/0x_white_paper.pdf b/packages/website/public/pdfs/0x_white_paper.pdf new file mode 100644 index 000000000..340e8a73a Binary files /dev/null and b/packages/website/public/pdfs/0x_white_paper.pdf differ diff --git a/packages/website/public/videos/0xAnimation.mp4 b/packages/website/public/videos/0xAnimation.mp4 new file mode 100644 index 000000000..d78c07d4f Binary files /dev/null and b/packages/website/public/videos/0xAnimation.mp4 differ diff --git a/packages/website/ts/blockchain.ts b/packages/website/ts/blockchain.ts new file mode 100644 index 000000000..d891acdb6 --- /dev/null +++ b/packages/website/ts/blockchain.ts @@ -0,0 +1,770 @@ +import * as _ from 'lodash'; +import * as React from 'react'; +import { + ZeroEx, + ZeroExError, + ExchangeContractErrs, + ExchangeContractEventArgs, + ExchangeEvents, + SubscriptionOpts, + IndexedFilterValues, + DecodedLogEvent, + BlockParam, + LogFillContractEventArgs, + LogCancelContractEventArgs, + Token as ZeroExToken, + LogWithDecodedArgs, + TransactionReceiptWithDecodedLogs, + SignedOrder, + Order, +} from '0x.js'; +import BigNumber from 'bignumber.js'; +import Web3 = require('web3'); +import promisify = require('es6-promisify'); +import findVersions = require('find-versions'); +import compareVersions = require('compare-versions'); +import contract = require('truffle-contract'); +import ethUtil = require('ethereumjs-util'); +import ProviderEngine = require('web3-provider-engine'); +import FilterSubprovider = require('web3-provider-engine/subproviders/filters'); +import {TransactionSubmitted} from 'ts/components/flash_messages/transaction_submitted'; +import {TokenSendCompleted} from 'ts/components/flash_messages/token_send_completed'; +import {RedundantRPCSubprovider} from 'ts/subproviders/redundant_rpc_subprovider'; +import {InjectedWeb3SubProvider} from 'ts/subproviders/injected_web3_subprovider'; +import {ledgerWalletSubproviderFactory} from 'ts/subproviders/ledger_wallet_subprovider_factory'; +import {Dispatcher} from 'ts/redux/dispatcher'; +import {utils} from 'ts/utils/utils'; +import {constants} from 'ts/utils/constants'; +import {configs} from 'ts/utils/configs'; +import { + BlockchainErrs, + Token, + SignatureData, + Side, + ContractResponse, + BlockchainCallErrs, + ContractInstance, + ProviderType, + LedgerWalletSubprovider, + EtherscanLinkSuffixes, + TokenByAddress, + TokenStateByAddress, +} from 'ts/types'; +import {Web3Wrapper} from 'ts/web3_wrapper'; +import {errorReporter} from 'ts/utils/error_reporter'; +import {tradeHistoryStorage} from 'ts/local_storage/trade_history_storage'; +import {trackedTokenStorage} from 'ts/local_storage/tracked_token_storage'; +import * as MintableArtifacts from '../contracts/Mintable.json'; + +const ALLOWANCE_TO_ZERO_GAS_AMOUNT = 45730; +const BLOCK_NUMBER_BACK_TRACK = 50; + +export class Blockchain { + public networkId: number; + public nodeVersion: string; + private zeroEx: ZeroEx; + private dispatcher: Dispatcher; + private web3Wrapper: Web3Wrapper; + private exchangeAddress: string; + private tokenTransferProxy: ContractInstance; + private tokenRegistry: ContractInstance; + private userAddress: string; + private cachedProvider: Web3.Provider; + private ledgerSubProvider: LedgerWalletSubprovider; + private zrxPollIntervalId: number; + constructor(dispatcher: Dispatcher, isSalePage: boolean = false) { + this.dispatcher = dispatcher; + this.userAddress = ''; + this.onPageLoadInitFireAndForgetAsync(); + } + public async networkIdUpdatedFireAndForgetAsync(newNetworkId: number) { + const isConnected = !_.isUndefined(newNetworkId); + if (!isConnected) { + this.networkId = newNetworkId; + this.dispatcher.encounteredBlockchainError(BlockchainErrs.DISCONNECTED_FROM_ETHEREUM_NODE); + this.dispatcher.updateShouldBlockchainErrDialogBeOpen(true); + } else if (this.networkId !== newNetworkId) { + this.networkId = newNetworkId; + this.dispatcher.encounteredBlockchainError(''); + await this.fetchTokenInformationAsync(); + await this.rehydrateStoreWithContractEvents(); + } + } + public async userAddressUpdatedFireAndForgetAsync(newUserAddress: string) { + if (this.userAddress !== newUserAddress) { + this.userAddress = newUserAddress; + await this.fetchTokenInformationAsync(); + await this.rehydrateStoreWithContractEvents(); + } + } + public async nodeVersionUpdatedFireAndForgetAsync(nodeVersion: string) { + if (this.nodeVersion !== nodeVersion) { + this.nodeVersion = nodeVersion; + } + } + public async isAddressInTokenRegistryAsync(tokenAddress: string): Promise { + utils.assert(!_.isUndefined(this.zeroEx), 'ZeroEx must be instantiated.'); + const tokenIfExists = await this.zeroEx.tokenRegistry.getTokenIfExistsAsync(tokenAddress); + return !_.isUndefined(tokenIfExists); + } + public getLedgerDerivationPathIfExists(): string { + if (_.isUndefined(this.ledgerSubProvider)) { + return undefined; + } + const path = this.ledgerSubProvider.getPath(); + return path; + } + public updateLedgerDerivationPathIfExists(path: string) { + if (_.isUndefined(this.ledgerSubProvider)) { + return; // noop + } + this.ledgerSubProvider.setPath(path); + } + public updateLedgerDerivationIndex(pathIndex: number) { + if (_.isUndefined(this.ledgerSubProvider)) { + return; // noop + } + this.ledgerSubProvider.setPathIndex(pathIndex); + } + public async providerTypeUpdatedFireAndForgetAsync(providerType: ProviderType) { + utils.assert(!_.isUndefined(this.zeroEx), 'ZeroEx must be instantiated.'); + // Should actually be Web3.Provider|ProviderEngine union type but it causes issues + // later on in the logic. + let provider; + switch (providerType) { + case ProviderType.LEDGER: { + const isU2FSupported = await utils.isU2FSupportedAsync(); + if (!isU2FSupported) { + throw new Error('Cannot update providerType to LEDGER without U2F support'); + } + + // Cache injected provider so that we can switch the user back to it easily + this.cachedProvider = this.web3Wrapper.getProviderObj(); + + this.dispatcher.updateUserAddress(''); // Clear old userAddress + + provider = new ProviderEngine(); + this.ledgerSubProvider = ledgerWalletSubproviderFactory(this.getBlockchainNetworkId.bind(this)); + provider.addProvider(this.ledgerSubProvider); + provider.addProvider(new FilterSubprovider()); + const networkId = configs.isMainnetEnabled ? + constants.MAINNET_NETWORK_ID : + constants.TESTNET_NETWORK_ID; + provider.addProvider(new RedundantRPCSubprovider( + constants.PUBLIC_NODE_URLS_BY_NETWORK_ID[networkId], + )); + provider.start(); + this.web3Wrapper.destroy(); + const shouldPollUserAddress = false; + this.web3Wrapper = new Web3Wrapper(this.dispatcher, provider, this.networkId, shouldPollUserAddress); + await this.zeroEx.setProviderAsync(provider); + await this.postInstantiationOrUpdatingProviderZeroExAsync(); + break; + } + + case ProviderType.INJECTED: { + if (_.isUndefined(this.cachedProvider)) { + return; // Going from injected to injected, so we noop + } + provider = this.cachedProvider; + const shouldPollUserAddress = true; + this.web3Wrapper = new Web3Wrapper(this.dispatcher, provider, this.networkId, shouldPollUserAddress); + await this.zeroEx.setProviderAsync(provider); + await this.postInstantiationOrUpdatingProviderZeroExAsync(); + delete this.ledgerSubProvider; + delete this.cachedProvider; + break; + } + + default: + throw utils.spawnSwitchErr('providerType', providerType); + } + + await this.fetchTokenInformationAsync(); + } + public async setProxyAllowanceAsync(token: Token, amountInBaseUnits: BigNumber): Promise { + utils.assert(this.isValidAddress(token.address), BlockchainCallErrs.TOKEN_ADDRESS_IS_INVALID); + utils.assert(this.doesUserAddressExist(), BlockchainCallErrs.USER_HAS_NO_ASSOCIATED_ADDRESSES); + utils.assert(!_.isUndefined(this.zeroEx), 'ZeroEx must be instantiated.'); + + const txHash = await this.zeroEx.token.setProxyAllowanceAsync( + token.address, this.userAddress, amountInBaseUnits, + ); + await this.showEtherScanLinkAndAwaitTransactionMinedAsync(txHash); + const allowance = amountInBaseUnits; + this.dispatcher.replaceTokenAllowanceByAddress(token.address, allowance); + } + public async transferAsync(token: Token, toAddress: string, + amountInBaseUnits: BigNumber): Promise { + const txHash = await this.zeroEx.token.transferAsync( + token.address, this.userAddress, toAddress, amountInBaseUnits, + ); + await this.showEtherScanLinkAndAwaitTransactionMinedAsync(txHash); + const etherScanLinkIfExists = utils.getEtherScanLinkIfExists(txHash, this.networkId, EtherscanLinkSuffixes.tx); + this.dispatcher.showFlashMessage(React.createElement(TokenSendCompleted, { + etherScanLinkIfExists, + token, + toAddress, + amountInBaseUnits, + })); + } + public portalOrderToSignedOrder(maker: string, taker: string, makerTokenAddress: string, + takerTokenAddress: string, makerTokenAmount: BigNumber, + takerTokenAmount: BigNumber, makerFee: BigNumber, + takerFee: BigNumber, expirationUnixTimestampSec: BigNumber, + feeRecipient: string, + signatureData: SignatureData, salt: BigNumber): SignedOrder { + const ecSignature = signatureData; + const exchangeContractAddress = this.getExchangeContractAddressIfExists(); + taker = _.isEmpty(taker) ? constants.NULL_ADDRESS : taker; + const signedOrder = { + ecSignature, + exchangeContractAddress, + expirationUnixTimestampSec, + feeRecipient, + maker, + makerFee, + makerTokenAddress, + makerTokenAmount, + salt, + taker, + takerFee, + takerTokenAddress, + takerTokenAmount, + }; + return signedOrder; + } + public async fillOrderAsync(signedOrder: SignedOrder, + fillTakerTokenAmount: BigNumber): Promise { + utils.assert(this.doesUserAddressExist(), BlockchainCallErrs.USER_HAS_NO_ASSOCIATED_ADDRESSES); + + const shouldThrowOnInsufficientBalanceOrAllowance = true; + + const txHash = await this.zeroEx.exchange.fillOrderAsync( + signedOrder, fillTakerTokenAmount, shouldThrowOnInsufficientBalanceOrAllowance, this.userAddress, + ); + const receipt = await this.showEtherScanLinkAndAwaitTransactionMinedAsync(txHash); + const logs: Array> = receipt.logs as any; + this.zeroEx.exchange.throwLogErrorsAsErrors(logs); + const logFill = _.find(logs, {event: 'LogFill'}); + const args = logFill.args as any as LogFillContractEventArgs; + const filledTakerTokenAmount = args.filledTakerTokenAmount; + return filledTakerTokenAmount; + } + public async cancelOrderAsync(signedOrder: SignedOrder, + cancelTakerTokenAmount: BigNumber): Promise { + const txHash = await this.zeroEx.exchange.cancelOrderAsync( + signedOrder, cancelTakerTokenAmount, + ); + const receipt = await this.showEtherScanLinkAndAwaitTransactionMinedAsync(txHash); + const logs: Array> = receipt.logs as any; + this.zeroEx.exchange.throwLogErrorsAsErrors(logs); + const logCancel = _.find(logs, {event: ExchangeEvents.LogCancel}); + const args = logCancel.args as any as LogCancelContractEventArgs; + const cancelledTakerTokenAmount = args.cancelledTakerTokenAmount; + return cancelledTakerTokenAmount; + } + public async getUnavailableTakerAmountAsync(orderHash: string): Promise { + utils.assert(ZeroEx.isValidOrderHash(orderHash), 'Must be valid orderHash'); + utils.assert(!_.isUndefined(this.zeroEx), 'ZeroEx must be instantiated.'); + const unavailableTakerAmount = await this.zeroEx.exchange.getUnavailableTakerAmountAsync(orderHash); + return unavailableTakerAmount; + } + public getExchangeContractAddressIfExists() { + return this.exchangeAddress; + } + public toHumanReadableErrorMsg(error: ZeroExError|ExchangeContractErrs, takerAddress: string): string { + const ZeroExErrorToHumanReadableError: {[error: string]: string} = { + [ZeroExError.ContractDoesNotExist]: 'Contract does not exist', + [ZeroExError.ExchangeContractDoesNotExist]: 'Exchange contract does not exist', + [ZeroExError.UnhandledError]: ' Unhandled error occured', + [ZeroExError.UserHasNoAssociatedAddress]: 'User has no addresses available', + [ZeroExError.InvalidSignature]: 'Order signature is not valid', + [ZeroExError.ContractNotDeployedOnNetwork]: 'Contract is not deployed on the detected network', + [ZeroExError.InvalidJump]: 'Invalid jump occured while executing the transaction', + [ZeroExError.OutOfGas]: 'Transaction ran out of gas', + [ZeroExError.NoNetworkId]: 'No network id detected', + }; + const exchangeContractErrorToHumanReadableError: {[error: string]: string} = { + [ExchangeContractErrs.OrderFillExpired]: 'This order has expired', + [ExchangeContractErrs.OrderCancelExpired]: 'This order has expired', + [ExchangeContractErrs.OrderCancelAmountZero]: 'Order cancel amount can\'t be 0', + [ExchangeContractErrs.OrderAlreadyCancelledOrFilled]: + 'This order has already been completely filled or cancelled', + [ExchangeContractErrs.OrderFillAmountZero]: 'Order fill amount can\'t be 0', + [ExchangeContractErrs.OrderRemainingFillAmountZero]: + 'This order has already been completely filled or cancelled', + [ExchangeContractErrs.OrderFillRoundingError]: 'Rounding error will occur when filling this order', + [ExchangeContractErrs.InsufficientTakerBalance]: + 'Taker no longer has a sufficient balance to complete this order', + [ExchangeContractErrs.InsufficientTakerAllowance]: + 'Taker no longer has a sufficient allowance to complete this order', + [ExchangeContractErrs.InsufficientMakerBalance]: + 'Maker no longer has a sufficient balance to complete this order', + [ExchangeContractErrs.InsufficientMakerAllowance]: + 'Maker no longer has a sufficient allowance to complete this order', + [ExchangeContractErrs.InsufficientTakerFeeBalance]: 'Taker no longer has a sufficient balance to pay fees', + [ExchangeContractErrs.InsufficientTakerFeeAllowance]: + 'Taker no longer has a sufficient allowance to pay fees', + [ExchangeContractErrs.InsufficientMakerFeeBalance]: 'Maker no longer has a sufficient balance to pay fees', + [ExchangeContractErrs.InsufficientMakerFeeAllowance]: + 'Maker no longer has a sufficient allowance to pay fees', + [ExchangeContractErrs.TransactionSenderIsNotFillOrderTaker]: + `This order can only be filled by ${takerAddress}`, + [ExchangeContractErrs.InsufficientRemainingFillAmount]: + 'Insufficient remaining fill amount', + }; + const humanReadableErrorMsg = exchangeContractErrorToHumanReadableError[error] || + ZeroExErrorToHumanReadableError[error]; + return humanReadableErrorMsg; + } + public async validateFillOrderThrowIfInvalidAsync(signedOrder: SignedOrder, + fillTakerTokenAmount: BigNumber, + takerAddress: string): Promise { + await this.zeroEx.exchange.validateFillOrderThrowIfInvalidAsync( + signedOrder, fillTakerTokenAmount, takerAddress); + } + public async validateCancelOrderThrowIfInvalidAsync(order: Order, + cancelTakerTokenAmount: BigNumber): Promise { + await this.zeroEx.exchange.validateCancelOrderThrowIfInvalidAsync(order, cancelTakerTokenAmount); + } + public isValidAddress(address: string): boolean { + const lowercaseAddress = address.toLowerCase(); + return this.web3Wrapper.isAddress(lowercaseAddress); + } + public async pollTokenBalanceAsync(token: Token) { + utils.assert(this.doesUserAddressExist(), BlockchainCallErrs.USER_HAS_NO_ASSOCIATED_ADDRESSES); + + const [currBalance] = await this.getTokenBalanceAndAllowanceAsync(this.userAddress, token.address); + + this.zrxPollIntervalId = window.setInterval(async () => { + const [balance] = await this.getTokenBalanceAndAllowanceAsync(this.userAddress, token.address); + if (!balance.eq(currBalance)) { + this.dispatcher.replaceTokenBalanceByAddress(token.address, balance); + clearInterval(this.zrxPollIntervalId); + delete this.zrxPollIntervalId; + } + }, 5000); + } + public async signOrderHashAsync(orderHash: string): Promise { + utils.assert(!_.isUndefined(this.zeroEx), 'ZeroEx must be instantiated.'); + const makerAddress = this.userAddress; + // If makerAddress is undefined, this means they have a web3 instance injected into their browser + // but no account addresses associated with it. + if (_.isUndefined(makerAddress)) { + throw new Error('Tried to send a sign request but user has no associated addresses'); + } + const ecSignature = await this.zeroEx.signOrderHashAsync(orderHash, makerAddress); + const signatureData = _.extend({}, ecSignature, { + hash: orderHash, + }); + this.dispatcher.updateSignatureData(signatureData); + return signatureData; + } + public async mintTestTokensAsync(token: Token) { + utils.assert(this.doesUserAddressExist(), BlockchainCallErrs.USER_HAS_NO_ASSOCIATED_ADDRESSES); + + const mintableContract = await this.instantiateContractIfExistsAsync(MintableArtifacts, token.address); + await mintableContract.mint(constants.MINT_AMOUNT, { + from: this.userAddress, + }); + const balanceDelta = constants.MINT_AMOUNT; + this.dispatcher.updateTokenBalanceByAddress(token.address, balanceDelta); + } + public async getBalanceInEthAsync(owner: string): Promise { + const balance = await this.web3Wrapper.getBalanceInEthAsync(owner); + return balance; + } + public async convertEthToWrappedEthTokensAsync(amount: BigNumber): Promise { + utils.assert(!_.isUndefined(this.zeroEx), 'ZeroEx must be instantiated.'); + utils.assert(this.doesUserAddressExist(), BlockchainCallErrs.USER_HAS_NO_ASSOCIATED_ADDRESSES); + + const txHash = await this.zeroEx.etherToken.depositAsync(amount, this.userAddress); + await this.showEtherScanLinkAndAwaitTransactionMinedAsync(txHash); + } + public async convertWrappedEthTokensToEthAsync(amount: BigNumber): Promise { + utils.assert(!_.isUndefined(this.zeroEx), 'ZeroEx must be instantiated.'); + utils.assert(this.doesUserAddressExist(), BlockchainCallErrs.USER_HAS_NO_ASSOCIATED_ADDRESSES); + + const txHash = await this.zeroEx.etherToken.withdrawAsync(amount, this.userAddress); + await this.showEtherScanLinkAndAwaitTransactionMinedAsync(txHash); + } + public async doesContractExistAtAddressAsync(address: string) { + const doesContractExist = await this.web3Wrapper.doesContractExistAtAddressAsync(address); + return doesContractExist; + } + public async getCurrentUserTokenBalanceAndAllowanceAsync(tokenAddress: string): Promise { + const tokenBalanceAndAllowance = await this.getTokenBalanceAndAllowanceAsync(this.userAddress, tokenAddress); + return tokenBalanceAndAllowance; + } + public async getTokenBalanceAndAllowanceAsync(ownerAddress: string, tokenAddress: string): + Promise { + utils.assert(!_.isUndefined(this.zeroEx), 'ZeroEx must be instantiated.'); + + if (_.isEmpty(ownerAddress)) { + const zero = new BigNumber(0); + return [zero, zero]; + } + let balance = new BigNumber(0); + let allowance = new BigNumber(0); + if (this.doesUserAddressExist()) { + balance = await this.zeroEx.token.getBalanceAsync(tokenAddress, ownerAddress); + allowance = await this.zeroEx.token.getProxyAllowanceAsync(tokenAddress, ownerAddress); + } + return [balance, allowance]; + } + public async updateTokenBalancesAndAllowancesAsync(tokens: Token[]) { + const tokenStateByAddress: TokenStateByAddress = {}; + for (const token of tokens) { + let balance = new BigNumber(0); + let allowance = new BigNumber(0); + if (this.doesUserAddressExist()) { + [ + balance, + allowance, + ] = await this.getTokenBalanceAndAllowanceAsync(this.userAddress, token.address); + } + const tokenState = { + balance, + allowance, + }; + tokenStateByAddress[token.address] = tokenState; + } + this.dispatcher.updateTokenStateByAddress(tokenStateByAddress); + } + public async getUserAccountsAsync() { + utils.assert(!_.isUndefined(this.zeroEx), 'ZeroEx must be instantiated.'); + const userAccountsIfExists = await this.zeroEx.getAvailableAddressesAsync(); + return userAccountsIfExists; + } + // HACK: When a user is using a Ledger, we simply dispatch the selected userAddress, which + // by-passes the web3Wrapper logic for updating the prevUserAddress. We therefore need to + // manually update it. This should only be called by the LedgerConfigDialog. + public updateWeb3WrapperPrevUserAddress(newUserAddress: string) { + this.web3Wrapper.updatePrevUserAddress(newUserAddress); + } + public destroy() { + clearInterval(this.zrxPollIntervalId); + this.web3Wrapper.destroy(); + this.stopWatchingExchangeLogFillEventsAsync(); // fire and forget + } + private async showEtherScanLinkAndAwaitTransactionMinedAsync( + txHash: string): Promise { + const etherScanLinkIfExists = utils.getEtherScanLinkIfExists(txHash, this.networkId, EtherscanLinkSuffixes.tx); + this.dispatcher.showFlashMessage(React.createElement(TransactionSubmitted, { + etherScanLinkIfExists, + })); + const receipt = await this.zeroEx.awaitTransactionMinedAsync(txHash); + return receipt; + } + private doesUserAddressExist(): boolean { + return this.userAddress !== ''; + } + private async rehydrateStoreWithContractEvents() { + // Ensure we are only ever listening to one set of events + await this.stopWatchingExchangeLogFillEventsAsync(); + + if (!this.doesUserAddressExist()) { + return; // short-circuit + } + + if (!_.isUndefined(this.zeroEx)) { + // Since we do not have an index on the `taker` address and want to show + // transactions where an account is either the `maker` or `taker`, we loop + // through all fill events, and filter/cache them client-side. + const filterIndexObj = {}; + await this.startListeningForExchangeLogFillEventsAsync(filterIndexObj); + } + } + private async startListeningForExchangeLogFillEventsAsync(indexFilterValues: IndexedFilterValues): Promise { + utils.assert(!_.isUndefined(this.zeroEx), 'ZeroEx must be instantiated.'); + utils.assert(this.doesUserAddressExist(), BlockchainCallErrs.USER_HAS_NO_ASSOCIATED_ADDRESSES); + + // Fetch historical logs + await this.fetchHistoricalExchangeLogFillEventsAsync(indexFilterValues); + + // Start a subscription for new logs + const exchangeAddress = this.getExchangeContractAddressIfExists(); + const subscriptionId = await this.zeroEx.exchange.subscribeAsync( + ExchangeEvents.LogFill, indexFilterValues, + async (err: Error, decodedLogEvent: DecodedLogEvent) => { + if (err) { + // Note: it's not entirely clear from the documentation which + // errors will be thrown by `watch`. For now, let's log the error + // to rollbar and stop watching when one occurs + errorReporter.reportAsync(err); // fire and forget + this.stopWatchingExchangeLogFillEventsAsync(); // fire and forget + return; + } else { + await this.addFillEventToTradeHistoryAsync(decodedLogEvent); + } + }); + } + private async fetchHistoricalExchangeLogFillEventsAsync(indexFilterValues: IndexedFilterValues) { + const fromBlock = tradeHistoryStorage.getFillsLatestBlock(this.userAddress, this.networkId); + const subscriptionOpts: SubscriptionOpts = { + fromBlock, + toBlock: 'latest' as BlockParam, + }; + const decodedLogs = await this.zeroEx.exchange.getLogsAsync( + ExchangeEvents.LogFill, subscriptionOpts, indexFilterValues, + ); + for (const decodedLog of decodedLogs) { + console.log('decodedLog', decodedLog); + await this.addFillEventToTradeHistoryAsync(decodedLog); + } + } + private async addFillEventToTradeHistoryAsync(decodedLog: LogWithDecodedArgs) { + const args = decodedLog.args as LogFillContractEventArgs; + const isUserMakerOrTaker = args.maker === this.userAddress || + args.taker === this.userAddress; + if (!isUserMakerOrTaker) { + return; // We aren't interested in the fill event + } + const isBlockPending = _.isNull(decodedLog.blockNumber); + if (!isBlockPending) { + // Hack: I've observed the behavior where a client won't register certain fill events + // and lowering the cache blockNumber fixes the issue. As a quick fix for now, simply + // set the cached blockNumber 50 below the one returned. This way, upon refreshing, a user + // would still attempt to re-fetch events from the previous 50 blocks, but won't need to + // re-fetch all events in all blocks. + // TODO: Debug if this is a race condition, and apply a more precise fix + const blockNumberToSet = decodedLog.blockNumber - BLOCK_NUMBER_BACK_TRACK < 0 ? + 0 : + decodedLog.blockNumber - BLOCK_NUMBER_BACK_TRACK; + tradeHistoryStorage.setFillsLatestBlock(this.userAddress, this.networkId, blockNumberToSet); + } + const blockTimestamp = await this.web3Wrapper.getBlockTimestampAsync(decodedLog.blockHash); + const fill = { + filledTakerTokenAmount: args.filledTakerTokenAmount, + filledMakerTokenAmount: args.filledMakerTokenAmount, + logIndex: decodedLog.logIndex, + maker: args.maker, + orderHash: args.orderHash, + taker: args.taker, + makerToken: args.makerToken, + takerToken: args.takerToken, + paidMakerFee: args.paidMakerFee, + paidTakerFee: args.paidTakerFee, + transactionHash: decodedLog.transactionHash, + blockTimestamp, + }; + tradeHistoryStorage.addFillToUser(this.userAddress, this.networkId, fill); + } + private async stopWatchingExchangeLogFillEventsAsync() { + this.zeroEx.exchange.unsubscribeAll(); + } + private async getTokenRegistryTokensByAddressAsync(): Promise { + utils.assert(!_.isUndefined(this.zeroEx), 'ZeroEx must be instantiated.'); + const tokenRegistryTokens = await this.zeroEx.tokenRegistry.getTokensAsync(); + + const tokenByAddress: TokenByAddress = {}; + _.each(tokenRegistryTokens, (t: ZeroExToken, i: number) => { + // HACK: For now we have a hard-coded list of iconUrls for the dummyTokens + // TODO: Refactor this out and pull the iconUrl directly from the TokenRegistry + const iconUrl = constants.iconUrlBySymbol[t.symbol]; + const token: Token = { + iconUrl, + address: t.address, + name: t.name, + symbol: t.symbol, + decimals: t.decimals, + isTracked: false, + isRegistered: true, + }; + tokenByAddress[token.address] = token; + }); + return tokenByAddress; + } + private async onPageLoadInitFireAndForgetAsync() { + await this.onPageLoadAsync(); // wait for page to load + + // Hack: We need to know the networkId the injectedWeb3 is connected to (if it is defined) in + // order to properly instantiate the web3Wrapper. Since we must use the async call, we cannot + // retrieve it from within the web3Wrapper constructor. This is and should remain the only + // call to a web3 instance outside of web3Wrapper in the entire dapp. + // In addition, if the user has an injectedWeb3 instance that is disconnected from a backing + // Ethereum node, this call will throw. We need to handle this case gracefully + const injectedWeb3 = (window as any).web3; + let networkId: number; + if (!_.isUndefined(injectedWeb3)) { + try { + networkId = _.parseInt(await promisify(injectedWeb3.version.getNetwork)()); + } catch (err) { + // Ignore error and proceed with networkId undefined + } + } + + const provider = await this.getProviderAsync(injectedWeb3, networkId); + this.zeroEx = new ZeroEx(provider); + await this.updateProviderName(injectedWeb3); + const shouldPollUserAddress = true; + this.web3Wrapper = new Web3Wrapper(this.dispatcher, provider, networkId, shouldPollUserAddress); + await this.postInstantiationOrUpdatingProviderZeroExAsync(); + } + // This method should always be run after instantiating or updating the provider + // of the ZeroEx instance. + private async postInstantiationOrUpdatingProviderZeroExAsync() { + utils.assert(!_.isUndefined(this.zeroEx), 'ZeroEx must be instantiated.'); + this.exchangeAddress = await this.zeroEx.exchange.getContractAddressAsync(); + } + private updateProviderName(injectedWeb3: Web3) { + const doesInjectedWeb3Exist = !_.isUndefined(injectedWeb3); + const providerName = doesInjectedWeb3Exist ? + this.getNameGivenProvider(injectedWeb3.currentProvider) : + constants.PUBLIC_PROVIDER_NAME; + this.dispatcher.updateInjectedProviderName(providerName); + } + // This is only ever called by the LedgerWallet subprovider in order to retrieve + // the current networkId without this value going stale. + private getBlockchainNetworkId() { + return this.networkId; + } + private async getProviderAsync(injectedWeb3: Web3, networkIdIfExists: number) { + const doesInjectedWeb3Exist = !_.isUndefined(injectedWeb3); + const publicNodeUrlsIfExistsForNetworkId = constants.PUBLIC_NODE_URLS_BY_NETWORK_ID[networkIdIfExists]; + const isPublicNodeAvailableForNetworkId = !_.isUndefined(publicNodeUrlsIfExistsForNetworkId); + + let provider; + if (doesInjectedWeb3Exist && isPublicNodeAvailableForNetworkId) { + // We catch all requests involving a users account and send it to the injectedWeb3 + // instance. All other requests go to the public hosted node. + provider = new ProviderEngine(); + provider.addProvider(new InjectedWeb3SubProvider(injectedWeb3)); + provider.addProvider(new FilterSubprovider()); + provider.addProvider(new RedundantRPCSubprovider( + publicNodeUrlsIfExistsForNetworkId, + )); + provider.start(); + } else if (doesInjectedWeb3Exist) { + // Since no public node for this network, all requests go to injectedWeb3 instance + provider = injectedWeb3.currentProvider; + } else { + // If no injectedWeb3 instance, all requests fallback to our public hosted mainnet/testnet node + // We do this so that users can still browse the 0x Portal DApp even if they do not have web3 + // injected into their browser. + provider = new ProviderEngine(); + provider.addProvider(new FilterSubprovider()); + const networkId = configs.isMainnetEnabled ? + constants.MAINNET_NETWORK_ID : + constants.TESTNET_NETWORK_ID; + provider.addProvider(new RedundantRPCSubprovider( + constants.PUBLIC_NODE_URLS_BY_NETWORK_ID[networkId], + )); + provider.start(); + } + + return provider; + } + private getNameGivenProvider(provider: Web3.Provider): string { + if (!_.isUndefined((provider as any).isMetaMask)) { + return constants.METAMASK_PROVIDER_NAME; + } + + // HACK: We use the fact that Parity Signer's provider is an instance of their + // internal `Web3FrameProvider` class. + const isParitySigner = _.startsWith(provider.constructor.toString(), 'function Web3FrameProvider'); + if (isParitySigner) { + return constants.PARITY_SIGNER_PROVIDER_NAME; + } + + return constants.GENERIC_PROVIDER_NAME; + } + private async fetchTokenInformationAsync() { + utils.assert(!_.isUndefined(this.networkId), + 'Cannot call fetchTokenInformationAsync if disconnected from Ethereum node'); + + this.dispatcher.updateBlockchainIsLoaded(false); + this.dispatcher.clearTokenByAddress(); + + const tokenRegistryTokensByAddress = await this.getTokenRegistryTokensByAddressAsync(); + + // HACK: We need to fetch the userAddress here because otherwise we cannot save the + // tracked tokens in localStorage under the users address nor fetch the token + // balances and allowances and we need to do this in order not to trigger the blockchain + // loading dialog to show up twice. First to load the contracts, and second to load the + // balances and allowances. + this.userAddress = await this.web3Wrapper.getFirstAccountIfExistsAsync(); + if (!_.isEmpty(this.userAddress)) { + this.dispatcher.updateUserAddress(this.userAddress); + } + + let trackedTokensIfExists = trackedTokenStorage.getTrackedTokensIfExists(this.userAddress, this.networkId); + const tokenRegistryTokens = _.values(tokenRegistryTokensByAddress); + if (_.isUndefined(trackedTokensIfExists)) { + trackedTokensIfExists = _.map(configs.defaultTrackedTokenSymbols, symbol => { + const token = _.find(tokenRegistryTokens, t => t.symbol === symbol); + token.isTracked = true; + return token; + }); + _.each(trackedTokensIfExists, token => { + trackedTokenStorage.addTrackedTokenToUser(this.userAddress, this.networkId, token); + }); + } else { + // Properly set all tokenRegistry tokens `isTracked` to true if they are in the existing trackedTokens array + _.each(trackedTokensIfExists, trackedToken => { + if (!_.isUndefined(tokenRegistryTokensByAddress[trackedToken.address])) { + tokenRegistryTokensByAddress[trackedToken.address].isTracked = true; + } + }); + } + const allTokens = _.uniq([...tokenRegistryTokens, ...trackedTokensIfExists]); + this.dispatcher.updateTokenByAddress(allTokens); + + // Get balance/allowance for tracked tokens + await this.updateTokenBalancesAndAllowancesAsync(trackedTokensIfExists); + + const mostPopularTradingPairTokens: Token[] = [ + _.find(allTokens, {symbol: configs.defaultTrackedTokenSymbols[0]}), + _.find(allTokens, {symbol: configs.defaultTrackedTokenSymbols[1]}), + ]; + this.dispatcher.updateChosenAssetTokenAddress(Side.deposit, mostPopularTradingPairTokens[0].address); + this.dispatcher.updateChosenAssetTokenAddress(Side.receive, mostPopularTradingPairTokens[1].address); + this.dispatcher.updateBlockchainIsLoaded(true); + } + private async instantiateContractIfExistsAsync(artifact: any, address?: string): Promise { + const c = await contract(artifact); + const providerObj = this.web3Wrapper.getProviderObj(); + c.setProvider(providerObj); + + const artifactNetworkConfigs = artifact.networks[this.networkId]; + let contractAddress; + if (!_.isUndefined(address)) { + contractAddress = address; + } else if (!_.isUndefined(artifactNetworkConfigs)) { + contractAddress = artifactNetworkConfigs.address; + } + + if (!_.isUndefined(contractAddress)) { + const doesContractExist = await this.doesContractExistAtAddressAsync(contractAddress); + if (!doesContractExist) { + utils.consoleLog(`Contract does not exist: ${artifact.contract_name} at ${contractAddress}`); + throw new Error(BlockchainCallErrs.CONTRACT_DOES_NOT_EXIST); + } + } + + try { + const contractInstance = _.isUndefined(address) ? + await c.deployed() : + await c.at(address); + return contractInstance; + } catch (err) { + const errMsg = `${err}`; + utils.consoleLog(`Notice: Error encountered: ${err} ${err.stack}`); + if (_.includes(errMsg, 'not been deployed to detected network')) { + throw new Error(BlockchainCallErrs.CONTRACT_DOES_NOT_EXIST); + } else { + await errorReporter.reportAsync(err); + throw new Error(BlockchainCallErrs.UNHANDLED_ERROR); + } + } + } + private async onPageLoadAsync() { + if (document.readyState === 'complete') { + return; // Already loaded + } + return new Promise((resolve, reject) => { + window.onload = resolve; + }); + } +} diff --git a/packages/website/ts/components/dialogs/blockchain_err_dialog.tsx b/packages/website/ts/components/dialogs/blockchain_err_dialog.tsx new file mode 100644 index 000000000..2e12fc889 --- /dev/null +++ b/packages/website/ts/components/dialogs/blockchain_err_dialog.tsx @@ -0,0 +1,158 @@ +import * as _ from 'lodash'; +import * as React from 'react'; +import Dialog from 'material-ui/Dialog'; +import FlatButton from 'material-ui/FlatButton'; +import {colors} from 'material-ui/styles'; +import {constants} from 'ts/utils/constants'; +import {configs} from 'ts/utils/configs'; +import {Blockchain} from 'ts/blockchain'; +import {BlockchainErrs} from 'ts/types'; + +interface BlockchainErrDialogProps { + blockchain: Blockchain; + blockchainErr: BlockchainErrs; + isOpen: boolean; + userAddress: string; + toggleDialogFn: (isOpen: boolean) => void; + networkId: number; +} + +export class BlockchainErrDialog extends React.Component { + public render() { + const dialogActions = [ + , + ]; + + const hasWalletAddress = this.props.userAddress !== ''; + return ( + +
+ {this.renderExplanation(hasWalletAddress)} +
+
+ ); + } + private getTitle(hasWalletAddress: boolean) { + if (this.props.blockchainErr === BlockchainErrs.A_CONTRACT_NOT_DEPLOYED_ON_NETWORK) { + return '0x smart contracts not found'; + } else if (!hasWalletAddress) { + return 'Enable wallet communication'; + } else if (this.props.blockchainErr === BlockchainErrs.DISCONNECTED_FROM_ETHEREUM_NODE) { + return 'Disconnected from Ethereum network'; + } else { + return 'Unexpected error'; + } + } + private renderExplanation(hasWalletAddress: boolean) { + if (this.props.blockchainErr === BlockchainErrs.A_CONTRACT_NOT_DEPLOYED_ON_NETWORK) { + return this.renderContractsNotDeployedExplanation(); + } else if (!hasWalletAddress) { + return this.renderNoWalletFoundExplanation(); + } else if (this.props.blockchainErr === BlockchainErrs.DISCONNECTED_FROM_ETHEREUM_NODE) { + return this.renderDisconnectedFromNode(); + } else { + return this.renderUnexpectedErrorExplanation(); + } + } + private renderDisconnectedFromNode() { + return ( +
+ You were disconnected from the backing Ethereum node. + {' '}If using + Metamask + or Mist try refreshing + {' '}the page. If using a locally hosted Ethereum node, make sure it's still running. +
+ ); + } + private renderUnexpectedErrorExplanation() { + return ( +
+ We encountered an unexpected error. Please try refreshing the page. +
+ ); + } + private renderNoWalletFoundExplanation() { + return ( +
+
+ We were unable to access an Ethereum wallet you control. In order to interact + {' '}with the 0x portal dApp, + we need a way to interact with one of your Ethereum wallets. + {' '}There are two easy ways you can enable us to do that: +
+

1. Metamask chrome extension

+
+ You can install the{' '} + + Metamask + Chrome extension Ethereum wallet. Once installed and set up, refresh this page. +
+ Note: + {' '}If you already have Metamask installed, make sure it is unlocked. +
+
+

Parity Signer

+
+ The Parity Signer + Chrome extension{' '}lets you connect to a locally running Parity node. + Make sure you have started your local Parity node with{' '} + {configs.isMainnetEnabled && '`parity ui` or'} `parity --chain kovan ui`{' '} + in order to connect to {configs.isMainnetEnabled ? 'mainnet or Kovan respectively.' : 'Kovan.'} +
+
+ Note: + {' '}If you have done one of the above steps and are still seeing this message, + {' '}we might still be unable to retrieve an Ethereum address by calling `web3.eth.accounts`. + {' '}Make sure you have created at least one Ethereum address. +
+
+ ); + } + private renderContractsNotDeployedExplanation() { + return ( +
+
+ The 0x smart contracts are not deployed on the Ethereum network you are + {' '}currently connected to (network Id: {this.props.networkId}). + {' '}In order to use the 0x portal dApp, + {' '}please connect to the + {' '}{constants.TESTNET_NAME} testnet (network Id: {constants.TESTNET_NETWORK_ID}) + {configs.isMainnetEnabled ? + ` or ${constants.MAINNET_NAME} (network Id: ${constants.MAINNET_NETWORK_ID}).` : + `.` + } +
+

Metamask

+
+ If you are using{' '} + + Metamask + , you can switch networks in the top left corner of the extension popover. +
+

Parity Signer

+
+ If using the Parity Signer + Chrome extension, make sure to start your local Parity node with{' '} + {configs.isMainnetEnabled ? + '`parity ui` or `parity --chain Kovan ui` in order to connect to mainnet \ + or Kovan respectively.' : + '`parity --chain kovan ui` in order to connect to Kovan.' + } +
+
+ ); + } +} diff --git a/packages/website/ts/components/dialogs/eth_weth_conversion_dialog.tsx b/packages/website/ts/components/dialogs/eth_weth_conversion_dialog.tsx new file mode 100644 index 000000000..1db85e375 --- /dev/null +++ b/packages/website/ts/components/dialogs/eth_weth_conversion_dialog.tsx @@ -0,0 +1,139 @@ +import * as React from 'react'; +import Dialog from 'material-ui/Dialog'; +import FlatButton from 'material-ui/FlatButton'; +import RadioButtonGroup from 'material-ui/RadioButton/RadioButtonGroup'; +import RadioButton from 'material-ui/RadioButton'; +import {Side, Token, TokenState} from 'ts/types'; +import {TokenAmountInput} from 'ts/components/inputs/token_amount_input'; +import {EthAmountInput} from 'ts/components/inputs/eth_amount_input'; +import BigNumber from 'bignumber.js'; + +interface EthWethConversionDialogProps { + onComplete: (direction: Side, value: BigNumber) => void; + onCancelled: () => void; + isOpen: boolean; + token: Token; + tokenState: TokenState; + etherBalance: BigNumber; +} + +interface EthWethConversionDialogState { + value?: BigNumber; + direction: Side; + shouldShowIncompleteErrs: boolean; + hasErrors: boolean; +} + +export class EthWethConversionDialog extends + React.Component { + constructor() { + super(); + this.state = { + direction: Side.deposit, + shouldShowIncompleteErrs: false, + hasErrors: true, + }; + } + public render() { + const convertDialogActions = [ + , + , + ]; + return ( + + {this.renderConversionDialogBody()} + + ); + } + private renderConversionDialogBody() { + return ( +
+ + + + + {this.state.direction === Side.receive ? + : + + } +
+ ); + } + private onConversionDirectionChange(e: any, direction: Side) { + this.setState({ + value: undefined, + shouldShowIncompleteErrs: false, + direction, + hasErrors: true, + }); + } + private onValueChange(isValid: boolean, amount?: BigNumber) { + this.setState({ + value: amount, + hasErrors: !isValid, + }); + } + private onConvertClick() { + if (this.state.hasErrors) { + this.setState({ + shouldShowIncompleteErrs: true, + }); + } else { + const value = this.state.value; + this.setState({ + value: undefined, + }); + this.props.onComplete(this.state.direction, value); + } + } + private onCancel() { + this.setState({ + value: undefined, + }); + this.props.onCancelled(); + } +} diff --git a/packages/website/ts/components/dialogs/ledger_config_dialog.tsx b/packages/website/ts/components/dialogs/ledger_config_dialog.tsx new file mode 100644 index 000000000..f89935500 --- /dev/null +++ b/packages/website/ts/components/dialogs/ledger_config_dialog.tsx @@ -0,0 +1,288 @@ +import * as _ from 'lodash'; +import * as React from 'react'; +import BigNumber from 'bignumber.js'; +import {colors} from 'material-ui/styles'; +import Dialog from 'material-ui/Dialog'; +import FlatButton from 'material-ui/FlatButton'; +import TextField from 'material-ui/TextField'; +import { + Table, + TableBody, + TableHeader, + TableRow, + TableHeaderColumn, + TableRowColumn, +} from 'material-ui/Table'; +import ReactTooltip = require('react-tooltip'); +import {utils} from 'ts/utils/utils'; +import {constants} from 'ts/utils/constants'; +import {Blockchain} from 'ts/blockchain'; +import {Dispatcher} from 'ts/redux/dispatcher'; +import {LifeCycleRaisedButton} from 'ts/components/ui/lifecycle_raised_button'; + +const VALID_ETHEREUM_DERIVATION_PATH_PREFIX = `44'/60'`; + +enum LedgerSteps { + CONNECT, + SELECT_ADDRESS, +} + +interface LedgerConfigDialogProps { + isOpen: boolean; + toggleDialogFn: (isOpen: boolean) => void; + dispatcher: Dispatcher; + blockchain: Blockchain; + networkId: number; +} + +interface LedgerConfigDialogState { + didConnectFail: boolean; + stepIndex: LedgerSteps; + userAddresses: string[]; + addressBalances: BigNumber[]; + derivationPath: string; + derivationErrMsg: string; +} + +export class LedgerConfigDialog extends React.Component { + constructor(props: LedgerConfigDialogProps) { + super(props); + this.state = { + didConnectFail: false, + stepIndex: LedgerSteps.CONNECT, + userAddresses: [], + addressBalances: [], + derivationPath: constants.DEFAULT_DERIVATION_PATH, + derivationErrMsg: '', + }; + } + public render() { + const dialogActions = [ + , + ]; + const dialogTitle = this.state.stepIndex === LedgerSteps.CONNECT ? + 'Connect to your Ledger' : + 'Select desired address'; + return ( + +
+ {this.state.stepIndex === LedgerSteps.CONNECT && + this.renderConnectStep() + } + {this.state.stepIndex === LedgerSteps.SELECT_ADDRESS && + this.renderSelectAddressStep() + } +
+
+ ); + } + private renderConnectStep() { + return ( +
+
+ Follow these instructions before proceeding: +
+
    +
  1. + Connect your Ledger Nano S & Open the Ethereum application +
  2. +
  3. + Verify that Browser Support is enabled in Settings +
  4. +
  5. + If no Browser Support is found in settings, verify that you have{' '} + Firmware >1.2 +
  6. +
+
+ + {this.state.didConnectFail && +
+ Failed to connect. Follow the instructions and try again. +
+ } +
+
+ ); + } + private renderSelectAddressStep() { + return ( +
+
+ + + + Address + Balance + + + + {this.renderAddressTableRows()} + +
+
+
+
+ +
+
+ +
+
+
+ ); + } + private renderAddressTableRows() { + const rows = _.map(this.state.userAddresses, (userAddress: string, i: number) => { + const balance = this.state.addressBalances[i]; + const addressTooltipId = `address-${userAddress}`; + const balanceTooltipId = `balance-${userAddress}`; + const networkName = constants.networkNameById[this.props.networkId]; + // We specifically prefix kovan ETH. + // TODO: We should probably add prefixes for all networks + const isKovanNetwork = networkName === 'Kovan'; + const balanceString = `${balance.toString()} ${isKovanNetwork ? 'Kovan ' : ''}ETH`; + return ( + + +
+ {userAddress} +
+ {userAddress} +
+ +
+ {balanceString} +
+ {balanceString} +
+
+ ); + }); + return rows; + } + private onClose() { + this.setState({ + didConnectFail: false, + }); + const isOpen = false; + this.props.toggleDialogFn(isOpen); + } + private onAddressSelected(selectedRowIndexes: number[]) { + const selectedRowIndex = selectedRowIndexes[0]; + this.props.blockchain.updateLedgerDerivationIndex(selectedRowIndex); + const selectedAddress = this.state.userAddresses[selectedRowIndex]; + const selectAddressBalance = this.state.addressBalances[selectedRowIndex]; + this.props.dispatcher.updateUserAddress(selectedAddress); + this.props.blockchain.updateWeb3WrapperPrevUserAddress(selectedAddress); + this.props.dispatcher.updateUserEtherBalance(selectAddressBalance); + this.setState({ + stepIndex: LedgerSteps.CONNECT, + }); + const isOpen = false; + this.props.toggleDialogFn(isOpen); + } + private async onFetchAddressesForDerivationPathAsync() { + const currentlySetPath = this.props.blockchain.getLedgerDerivationPathIfExists(); + if (currentlySetPath === this.state.derivationPath) { + return; + } + this.props.blockchain.updateLedgerDerivationPathIfExists(this.state.derivationPath); + const didSucceed = await this.fetchAddressesAndBalancesAsync(); + if (!didSucceed) { + this.setState({ + derivationErrMsg: 'Failed to connect to Ledger.', + }); + } + return didSucceed; + } + private async fetchAddressesAndBalancesAsync() { + let userAddresses: string[]; + const addressBalances: BigNumber[] = []; + try { + userAddresses = await this.getUserAddressesAsync(); + for (const address of userAddresses) { + const balance = await this.props.blockchain.getBalanceInEthAsync(address); + addressBalances.push(balance); + } + } catch (err) { + utils.consoleLog(`Ledger error: ${JSON.stringify(err)}`); + this.setState({ + didConnectFail: true, + }); + return false; + } + this.setState({ + userAddresses, + addressBalances, + }); + return true; + } + private onDerivationPathChanged(e: any, derivationPath: string) { + let derivationErrMsg = ''; + if (!_.startsWith(derivationPath, VALID_ETHEREUM_DERIVATION_PATH_PREFIX)) { + derivationErrMsg = 'Must be valid Ethereum path.'; + } + + this.setState({ + derivationPath, + derivationErrMsg, + }); + } + private async onConnectLedgerClickAsync() { + const didSucceed = await this.fetchAddressesAndBalancesAsync(); + if (didSucceed) { + this.setState({ + stepIndex: LedgerSteps.SELECT_ADDRESS, + }); + } + return didSucceed; + } + private async getUserAddressesAsync(): Promise { + let userAddresses: string[]; + userAddresses = await this.props.blockchain.getUserAccountsAsync(); + + if (_.isEmpty(userAddresses)) { + throw new Error('No addresses retrieved.'); + } + return userAddresses; + } +} diff --git a/packages/website/ts/components/dialogs/portal_disclaimer_dialog.tsx b/packages/website/ts/components/dialogs/portal_disclaimer_dialog.tsx new file mode 100644 index 000000000..8f870b42f --- /dev/null +++ b/packages/website/ts/components/dialogs/portal_disclaimer_dialog.tsx @@ -0,0 +1,44 @@ +import * as React from 'react'; +import {colors} from 'material-ui/styles'; +import FlatButton from 'material-ui/FlatButton'; +import Dialog from 'material-ui/Dialog'; +import {constants} from 'ts/utils/constants'; + +interface PortalDisclaimerDialogProps { + isOpen: boolean; + onToggleDialog: () => void; +} + +export function PortalDisclaimerDialog(props: PortalDisclaimerDialogProps) { + return ( + , + ]} + open={props.isOpen} + onRequestClose={props.onToggleDialog.bind(this)} + autoScrollBodyContent={true} + modal={true} + > +
+
+ 0x Portal is a free software-based tool intended to help users to + buy and sell ERC20-compatible blockchain tokens through the 0x protocol + on a purely peer-to-peer basis. 0x portal is not a regulated marketplace, + exchange or intermediary of any kind, and therefore, you should only use + 0x portal to exchange tokens that are not securities, commodity interests, + or any other form of regulated instrument. 0x has not attempted to screen + or otherwise limit the tokens that you may enter in 0x Portal. By clicking + “I Agree” below, you understand that you are solely responsible for using 0x + Portal and buying and selling tokens using 0x Portal in compliance with all + applicable laws and regulations. +
+
+
+ ); +} diff --git a/packages/website/ts/components/dialogs/send_dialog.tsx b/packages/website/ts/components/dialogs/send_dialog.tsx new file mode 100644 index 000000000..10417a326 --- /dev/null +++ b/packages/website/ts/components/dialogs/send_dialog.tsx @@ -0,0 +1,126 @@ +import * as React from 'react'; +import * as _ from 'lodash'; +import Dialog from 'material-ui/Dialog'; +import FlatButton from 'material-ui/FlatButton'; +import RadioButtonGroup from 'material-ui/RadioButton/RadioButtonGroup'; +import RadioButton from 'material-ui/RadioButton'; +import {Side, Token, TokenState} from 'ts/types'; +import {TokenAmountInput} from 'ts/components/inputs/token_amount_input'; +import {EthAmountInput} from 'ts/components/inputs/eth_amount_input'; +import {AddressInput} from 'ts/components/inputs/address_input'; +import BigNumber from 'bignumber.js'; + +interface SendDialogProps { + onComplete: (recipient: string, value: BigNumber) => void; + onCancelled: () => void; + isOpen: boolean; + token: Token; + tokenState: TokenState; +} + +interface SendDialogState { + value?: BigNumber; + recipient: string; + shouldShowIncompleteErrs: boolean; + isAmountValid: boolean; +} + +export class SendDialog extends React.Component { + constructor() { + super(); + this.state = { + recipient: '', + shouldShowIncompleteErrs: false, + isAmountValid: false, + }; + } + public render() { + const transferDialogActions = [ + , + , + ]; + return ( + + {this.renderSendDialogBody()} + + ); + } + private renderSendDialogBody() { + return ( +
+
+ +
+ +
+ ); + } + private onRecipientChange(recipient?: string) { + this.setState({ + shouldShowIncompleteErrs: false, + recipient, + }); + } + private onValueChange(isValid: boolean, amount?: BigNumber) { + this.setState({ + isAmountValid: isValid, + value: amount, + }); + } + private onSendClick() { + if (this.hasErrors()) { + this.setState({ + shouldShowIncompleteErrs: true, + }); + } else { + const value = this.state.value; + this.setState({ + recipient: undefined, + value: undefined, + }); + this.props.onComplete(this.state.recipient, value); + } + } + private onCancel() { + this.setState({ + value: undefined, + }); + this.props.onCancelled(); + } + private hasErrors() { + return _.isUndefined(this.state.recipient) || + _.isUndefined(this.state.value) || + !this.state.isAmountValid; + } +} diff --git a/packages/website/ts/components/dialogs/track_token_confirmation_dialog.tsx b/packages/website/ts/components/dialogs/track_token_confirmation_dialog.tsx new file mode 100644 index 000000000..97c654656 --- /dev/null +++ b/packages/website/ts/components/dialogs/track_token_confirmation_dialog.tsx @@ -0,0 +1,99 @@ +import * as _ from 'lodash'; +import * as React from 'react'; +import {colors} from 'material-ui/styles'; +import FlatButton from 'material-ui/FlatButton'; +import Dialog from 'material-ui/Dialog'; +import {constants} from 'ts/utils/constants'; +import {Blockchain} from 'ts/blockchain'; +import {Dispatcher} from 'ts/redux/dispatcher'; +import {TrackTokenConfirmation} from 'ts/components/track_token_confirmation'; +import {trackedTokenStorage} from 'ts/local_storage/tracked_token_storage'; +import {Token, TokenByAddress} from 'ts/types'; + +interface TrackTokenConfirmationDialogProps { + tokens: Token[]; + tokenByAddress: TokenByAddress; + isOpen: boolean; + onToggleDialog: (didConfirmTokenTracking: boolean) => void; + dispatcher: Dispatcher; + networkId: number; + blockchain: Blockchain; + userAddress: string; +} + +interface TrackTokenConfirmationDialogState { + isAddingTokenToTracked: boolean; +} + +export class TrackTokenConfirmationDialog extends + React.Component { + constructor(props: TrackTokenConfirmationDialogProps) { + super(props); + this.state = { + isAddingTokenToTracked: false, + }; + } + public render() { + const tokens = this.props.tokens; + return ( + , + , + ]} + open={this.props.isOpen} + onRequestClose={this.props.onToggleDialog.bind(this, false)} + autoScrollBodyContent={true} + > +
+ +
+
+ ); + } + private async onTrackConfirmationRespondedAsync(didUserAcceptTracking: boolean) { + if (!didUserAcceptTracking) { + this.props.onToggleDialog(didUserAcceptTracking); + return; + } + this.setState({ + isAddingTokenToTracked: true, + }); + for (const token of this.props.tokens) { + const newTokenEntry = _.assign({}, token); + + newTokenEntry.isTracked = true; + trackedTokenStorage.addTrackedTokenToUser(this.props.userAddress, this.props.networkId, newTokenEntry); + this.props.dispatcher.updateTokenByAddress([newTokenEntry]); + + const [ + balance, + allowance, + ] = await this.props.blockchain.getCurrentUserTokenBalanceAndAllowanceAsync(token.address); + this.props.dispatcher.updateTokenStateByAddress({ + [token.address]: { + balance, + allowance, + }, + }); + } + + this.setState({ + isAddingTokenToTracked: false, + }); + this.props.onToggleDialog(didUserAcceptTracking); + } +} diff --git a/packages/website/ts/components/dialogs/u2f_not_supported_dialog.tsx b/packages/website/ts/components/dialogs/u2f_not_supported_dialog.tsx new file mode 100644 index 000000000..28c24cdbe --- /dev/null +++ b/packages/website/ts/components/dialogs/u2f_not_supported_dialog.tsx @@ -0,0 +1,53 @@ +import * as React from 'react'; +import {colors} from 'material-ui/styles'; +import FlatButton from 'material-ui/FlatButton'; +import Dialog from 'material-ui/Dialog'; +import {constants} from 'ts/utils/constants'; + +interface U2fNotSupportedDialogProps { + isOpen: boolean; + onToggleDialog: () => void; +} + +export function U2fNotSupportedDialog(props: U2fNotSupportedDialogProps) { + return ( + , + ]} + open={props.isOpen} + onRequestClose={props.onToggleDialog.bind(this)} + autoScrollBodyContent={true} + > +
+
+ It looks like your browser does not support U2F connections + required for us to communicate with your hardware wallet. + Please use a browser that supports U2F connections and try + again. +
+
+
    +
  • Chrome version 38 or later
  • +
  • Opera version 40 of later
  • +
  • + Firefox with{' '} + + this extension + . +
  • +
+
+
+
+ ); +} diff --git a/packages/website/ts/components/eth_weth_conversion_button.tsx b/packages/website/ts/components/eth_weth_conversion_button.tsx new file mode 100644 index 000000000..fd8b713f4 --- /dev/null +++ b/packages/website/ts/components/eth_weth_conversion_button.tsx @@ -0,0 +1,101 @@ +import * as _ from 'lodash'; +import {ZeroEx} from '0x.js'; +import * as React from 'react'; +import BigNumber from 'bignumber.js'; +import RaisedButton from 'material-ui/RaisedButton'; +import {BlockchainCallErrs, TokenState} from 'ts/types'; +import {EthWethConversionDialog} from 'ts/components/dialogs/eth_weth_conversion_dialog'; +import {Side, Token} from 'ts/types'; +import {constants} from 'ts/utils/constants'; +import {utils} from 'ts/utils/utils'; +import {Dispatcher} from 'ts/redux/dispatcher'; +import {errorReporter} from 'ts/utils/error_reporter'; +import {Blockchain} from 'ts/blockchain'; + +interface EthWethConversionButtonProps { + ethToken: Token; + ethTokenState: TokenState; + dispatcher: Dispatcher; + blockchain: Blockchain; + userEtherBalance: BigNumber; + onError: () => void; +} + +interface EthWethConversionButtonState { + isEthConversionDialogVisible: boolean; + isEthConversionHappening: boolean; +} + +export class EthWethConversionButton extends + React.Component { + public constructor(props: EthWethConversionButtonProps) { + super(props); + this.state = { + isEthConversionDialogVisible: false, + isEthConversionHappening: false, + }; + } + public render() { + const labelStyle = this.state.isEthConversionHappening ? {fontSize: 10} : {}; + return ( +
+ + +
+ ); + } + private toggleConversionDialog() { + this.setState({ + isEthConversionDialogVisible: !this.state.isEthConversionDialogVisible, + }); + } + private async onConversionAmountSelectedAsync(direction: Side, value: BigNumber) { + this.setState({ + isEthConversionHappening: true, + }); + this.toggleConversionDialog(); + const token = this.props.ethToken; + const tokenState = this.props.ethTokenState; + let balance = tokenState.balance; + try { + if (direction === Side.deposit) { + await this.props.blockchain.convertEthToWrappedEthTokensAsync(value); + const ethAmount = ZeroEx.toUnitAmount(value, constants.ETH_DECIMAL_PLACES); + this.props.dispatcher.showFlashMessage(`Successfully converted ${ethAmount.toString()} ETH to WETH`); + balance = balance.plus(value); + } else { + await this.props.blockchain.convertWrappedEthTokensToEthAsync(value); + const tokenAmount = ZeroEx.toUnitAmount(value, token.decimals); + this.props.dispatcher.showFlashMessage(`Successfully converted ${tokenAmount.toString()} WETH to ETH`); + balance = balance.minus(value); + } + this.props.dispatcher.replaceTokenBalanceByAddress(token.address, balance); + } catch (err) { + const errMsg = '' + err; + if (_.includes(errMsg, BlockchainCallErrs.USER_HAS_NO_ASSOCIATED_ADDRESSES)) { + this.props.dispatcher.updateShouldBlockchainErrDialogBeOpen(true); + } else if (!_.includes(errMsg, 'User denied transaction')) { + utils.consoleLog(`Unexpected error encountered: ${err}`); + utils.consoleLog(err.stack); + await errorReporter.reportAsync(err); + this.props.onError(); + } + } + this.setState({ + isEthConversionHappening: false, + }); + } +} diff --git a/packages/website/ts/components/fill_order.tsx b/packages/website/ts/components/fill_order.tsx new file mode 100644 index 000000000..dc965283e --- /dev/null +++ b/packages/website/ts/components/fill_order.tsx @@ -0,0 +1,714 @@ +import * as _ from 'lodash'; +import * as React from 'react'; +import * as accounting from 'accounting'; +import {Link} from 'react-router-dom'; +import {ZeroEx, Order as ZeroExOrder} from '0x.js'; +import * as moment from 'moment'; +import BigNumber from 'bignumber.js'; +import Paper from 'material-ui/Paper'; +import {Card, CardText, CardHeader} from 'material-ui/Card'; +import Divider from 'material-ui/Divider'; +import TextField from 'material-ui/TextField'; +import RaisedButton from 'material-ui/RaisedButton'; +import {utils} from 'ts/utils/utils'; +import {constants} from 'ts/utils/constants'; +import { + Side, + TokenByAddress, + TokenStateByAddress, + Order, + BlockchainErrs, + OrderToken, + Token, + ExchangeContractErrs, + AlertTypes, + ContractResponse, + WebsitePaths, +} from 'ts/types'; +import {Alert} from 'ts/components/ui/alert'; +import {Identicon} from 'ts/components/ui/identicon'; +import {EthereumAddress} from 'ts/components/ui/ethereum_address'; +import {TokenAmountInput} from 'ts/components/inputs/token_amount_input'; +import {FillWarningDialog} from 'ts/components/fill_warning_dialog'; +import {FillOrderJSON} from 'ts/components/fill_order_json'; +import {VisualOrder} from 'ts/components/visual_order'; +import {SchemaValidator} from 'ts/schemas/validator'; +import {orderSchema} from 'ts/schemas/order_schema'; +import {Dispatcher} from 'ts/redux/dispatcher'; +import {Blockchain} from 'ts/blockchain'; +import {errorReporter} from 'ts/utils/error_reporter'; +import {trackedTokenStorage} from 'ts/local_storage/tracked_token_storage'; +import {TrackTokenConfirmationDialog} from 'ts/components/dialogs/track_token_confirmation_dialog'; + +const CUSTOM_LIGHT_GRAY = '#BBBBBB'; + +interface FillOrderProps { + blockchain: Blockchain; + blockchainErr: BlockchainErrs; + orderFillAmount: BigNumber; + isOrderInUrl: boolean; + networkId: number; + userAddress: string; + tokenByAddress: TokenByAddress; + tokenStateByAddress: TokenStateByAddress; + initialOrder: Order; + dispatcher: Dispatcher; +} + +interface FillOrderState { + didOrderValidationRun: boolean; + areAllInvolvedTokensTracked: boolean; + globalErrMsg: string; + orderJSON: string; + orderJSONErrMsg: string; + parsedOrder: Order; + didFillOrderSucceed: boolean; + didCancelOrderSucceed: boolean; + unavailableTakerAmount: BigNumber; + isMakerTokenAddressInRegistry: boolean; + isTakerTokenAddressInRegistry: boolean; + isFillWarningDialogOpen: boolean; + isFilling: boolean; + isCancelling: boolean; + isConfirmingTokenTracking: boolean; + tokensToTrack: Token[]; +} + +export class FillOrder extends React.Component { + private validator: SchemaValidator; + constructor(props: FillOrderProps) { + super(props); + this.state = { + globalErrMsg: '', + didOrderValidationRun: false, + areAllInvolvedTokensTracked: false, + didFillOrderSucceed: false, + didCancelOrderSucceed: false, + orderJSON: _.isUndefined(this.props.initialOrder) ? '' : JSON.stringify(this.props.initialOrder), + orderJSONErrMsg: '', + parsedOrder: this.props.initialOrder, + unavailableTakerAmount: new BigNumber(0), + isMakerTokenAddressInRegistry: false, + isTakerTokenAddressInRegistry: false, + isFillWarningDialogOpen: false, + isFilling: false, + isCancelling: false, + isConfirmingTokenTracking: false, + tokensToTrack: [], + }; + this.validator = new SchemaValidator(); + } + public componentWillMount() { + if (!_.isEmpty(this.state.orderJSON)) { + this.validateFillOrderFireAndForgetAsync(this.state.orderJSON); + } + } + public componentDidMount() { + window.scrollTo(0, 0); + } + public render() { + return ( +
+

Fill an order

+ +
+ {!this.props.isOrderInUrl && +
+
+ Paste an order JSON snippet below to begin +
+
Order JSON
+ + {this.renderOrderJsonNotices()} +
+ } +
+ {!_.isUndefined(this.state.parsedOrder) && this.state.didOrderValidationRun + && this.state.areAllInvolvedTokensTracked && + this.renderVisualOrder() + } +
+ {this.props.isOrderInUrl && +
+ + + + + + + {this.renderOrderJsonNotices()} +
+ } +
+ + +
+ ); + } + private renderOrderJsonNotices() { + return ( +
+ {!_.isUndefined(this.props.initialOrder) && !this.state.didOrderValidationRun && +
+ + + + Validating order... +
+ } + {!_.isEmpty(this.state.orderJSONErrMsg) && + + } +
+ ); + } + private renderVisualOrder() { + const takerTokenAddress = this.state.parsedOrder.taker.token.address; + const takerToken = this.props.tokenByAddress[takerTokenAddress]; + const orderTakerAmount = new BigNumber(this.state.parsedOrder.taker.amount); + const orderMakerAmount = new BigNumber(this.state.parsedOrder.maker.amount); + const takerAssetToken = { + amount: orderTakerAmount.minus(this.state.unavailableTakerAmount), + symbol: takerToken.symbol, + }; + const fillToken = this.props.tokenByAddress[takerToken.address]; + const fillTokenState = this.props.tokenStateByAddress[takerToken.address]; + const makerTokenAddress = this.state.parsedOrder.maker.token.address; + const makerToken = this.props.tokenByAddress[makerTokenAddress]; + const makerAssetToken = { + amount: orderMakerAmount.times(takerAssetToken.amount).div(orderTakerAmount), + symbol: makerToken.symbol, + }; + const fillAssetToken = { + amount: this.props.orderFillAmount, + symbol: takerToken.symbol, + }; + const orderTaker = !_.isEmpty(this.state.parsedOrder.taker.address) ? this.state.parsedOrder.taker.address : + this.props.userAddress; + const parsedOrderExpiration = new BigNumber(this.state.parsedOrder.expiration); + const exchangeRate = orderMakerAmount.div(orderTakerAmount); + + let orderReceiveAmount = 0; + if (!_.isUndefined(this.props.orderFillAmount)) { + const orderReceiveAmountBigNumber = exchangeRate.mul(this.props.orderFillAmount); + orderReceiveAmount = this.formatCurrencyAmount(orderReceiveAmountBigNumber, makerToken.decimals); + } + const isUserMaker = !_.isUndefined(this.state.parsedOrder) && + this.state.parsedOrder.maker.address === this.props.userAddress; + const expiryDate = utils.convertToReadableDateTimeFromUnixTimestamp(parsedOrderExpiration); + return ( +
+
+
Order details
+
+
+ Maker: +
+
+ +
+
+ +
+
+
+
+
+ +
+ Expires: {expiryDate} UTC +
+
+
+ {!isUserMaker && +
+
+ +
+
+ = {accounting.formatNumber(orderReceiveAmount, 6)} {makerToken.symbol} +
+
+ } +
+ {isUserMaker ? +
+ + {this.state.didCancelOrderSucceed && + + } +
: +
+ + {!_.isEmpty(this.state.globalErrMsg) && + + } + {this.state.didFillOrderSucceed && + + } +
+ } +
+
+ ); + } + private renderFillSuccessMsg() { + return ( +
+ Order successfully filled. See the trade details in your{' '} + + trade history + +
+ ); + } + private renderCancelSuccessMsg() { + return ( +
+ Order successfully cancelled. +
+ ); + } + private onFillOrderClick() { + if (!this.state.isMakerTokenAddressInRegistry || !this.state.isTakerTokenAddressInRegistry) { + this.setState({ + isFillWarningDialogOpen: true, + }); + } else { + this.onFillOrderClickFireAndForgetAsync(); + } + } + private onFillWarningClosed(didUserCancel: boolean) { + this.setState({ + isFillWarningDialogOpen: false, + }); + if (!didUserCancel) { + this.onFillOrderClickFireAndForgetAsync(); + } + } + private onFillAmountChange(isValid: boolean, amount?: BigNumber) { + this.props.dispatcher.updateOrderFillAmount(amount); + } + private onFillOrderJSONChanged(event: any) { + const orderJSON = event.target.value; + this.setState({ + didOrderValidationRun: _.isEmpty(orderJSON) && _.isEmpty(this.state.orderJSONErrMsg), + didFillOrderSucceed: false, + }); + this.validateFillOrderFireAndForgetAsync(orderJSON); + } + private async checkForUntrackedTokensAndAskToAdd() { + if (!_.isEmpty(this.state.orderJSONErrMsg)) { + return; + } + + const makerTokenIfExists = this.props.tokenByAddress[this.state.parsedOrder.maker.token.address]; + const takerTokenIfExists = this.props.tokenByAddress[this.state.parsedOrder.taker.token.address]; + + const tokensToTrack = []; + const isUnseenMakerToken = _.isUndefined(makerTokenIfExists); + const isMakerTokenTracked = !_.isUndefined(makerTokenIfExists) && makerTokenIfExists.isTracked; + if (isUnseenMakerToken) { + tokensToTrack.push(_.assign({}, this.state.parsedOrder.maker.token, { + iconUrl: undefined, + isTracked: false, + isRegistered: false, + })); + } else if (!isMakerTokenTracked) { + tokensToTrack.push(makerTokenIfExists); + } + const isUnseenTakerToken = _.isUndefined(takerTokenIfExists); + const isTakerTokenTracked = !_.isUndefined(takerTokenIfExists) && takerTokenIfExists.isTracked; + if (isUnseenTakerToken) { + tokensToTrack.push(_.assign({}, this.state.parsedOrder.taker.token, { + iconUrl: undefined, + isTracked: false, + isRegistered: false, + })); + } else if (!isTakerTokenTracked) { + tokensToTrack.push(takerTokenIfExists); + } + if (!_.isEmpty(tokensToTrack)) { + this.setState({ + isConfirmingTokenTracking: true, + tokensToTrack, + }); + } else { + this.setState({ + areAllInvolvedTokensTracked: true, + }); + } + } + private async validateFillOrderFireAndForgetAsync(orderJSON: string) { + let orderJSONErrMsg = ''; + let parsedOrder: Order; + try { + const order = JSON.parse(orderJSON); + const validationResult = this.validator.validate(order, orderSchema); + if (validationResult.errors.length > 0) { + orderJSONErrMsg = 'Submitted order JSON is not a valid order'; + utils.consoleLog(`Unexpected order JSON validation error: ${validationResult.errors.join(', ')}`); + return; + } + parsedOrder = order; + + const exchangeContractAddr = this.props.blockchain.getExchangeContractAddressIfExists(); + const makerAmount = new BigNumber(parsedOrder.maker.amount); + const takerAmount = new BigNumber(parsedOrder.taker.amount); + const expiration = new BigNumber(parsedOrder.expiration); + const salt = new BigNumber(parsedOrder.salt); + const parsedMakerFee = new BigNumber(parsedOrder.maker.feeAmount); + const parsedTakerFee = new BigNumber(parsedOrder.taker.feeAmount); + + const zeroExOrder: ZeroExOrder = { + exchangeContractAddress: parsedOrder.exchangeContract, + expirationUnixTimestampSec: expiration, + feeRecipient: parsedOrder.feeRecipient, + maker: parsedOrder.maker.address, + makerFee: parsedMakerFee, + makerTokenAddress: parsedOrder.maker.token.address, + makerTokenAmount: makerAmount, + salt, + taker: _.isEmpty(parsedOrder.taker.address) ? constants.NULL_ADDRESS : parsedOrder.taker.address, + takerFee: parsedTakerFee, + takerTokenAddress: parsedOrder.taker.token.address, + takerTokenAmount: takerAmount, + }; + const orderHash = ZeroEx.getOrderHashHex(zeroExOrder); + + const signature = parsedOrder.signature; + const isValidSignature = ZeroEx.isValidSignature(signature.hash, signature, parsedOrder.maker.address); + if (this.props.networkId !== parsedOrder.networkId) { + orderJSONErrMsg = `This order was made on another Ethereum network + (id: ${parsedOrder.networkId}). Connect to this network to fill.`; + parsedOrder = undefined; + } else if (exchangeContractAddr !== parsedOrder.exchangeContract) { + orderJSONErrMsg = 'This order was made using a deprecated 0x Exchange contract.'; + parsedOrder = undefined; + } else if (orderHash !== signature.hash) { + orderJSONErrMsg = 'Order hash does not match supplied plaintext values'; + parsedOrder = undefined; + } else if (!isValidSignature) { + orderJSONErrMsg = 'Order signature is invalid'; + parsedOrder = undefined; + } else { + // Update user supplied order cache so that if they navigate away from fill view + // e.g to set a token allowance, when they come back, the fill order persists + this.props.dispatcher.updateUserSuppliedOrderCache(parsedOrder); + } + } catch (err) { + utils.consoleLog(`Validate order err: ${err}`); + if (!_.isEmpty(orderJSON)) { + orderJSONErrMsg = 'Submitted order JSON is not valid JSON'; + } + this.setState({ + didOrderValidationRun: true, + orderJSON, + orderJSONErrMsg, + parsedOrder, + }); + return; + } + + let unavailableTakerAmount = new BigNumber(0); + if (!_.isEmpty(orderJSONErrMsg)) { + // Clear cache entry if user updates orderJSON to invalid entry + this.props.dispatcher.updateUserSuppliedOrderCache(undefined); + } else { + const orderHash = parsedOrder.signature.hash; + unavailableTakerAmount = await this.props.blockchain.getUnavailableTakerAmountAsync(orderHash); + const isMakerTokenAddressInRegistry = await this.props.blockchain.isAddressInTokenRegistryAsync( + parsedOrder.maker.token.address, + ); + const isTakerTokenAddressInRegistry = await this.props.blockchain.isAddressInTokenRegistryAsync( + parsedOrder.taker.token.address, + ); + this.setState({ + isMakerTokenAddressInRegistry, + isTakerTokenAddressInRegistry, + }); + } + + this.setState({ + didOrderValidationRun: true, + orderJSON, + orderJSONErrMsg, + parsedOrder, + unavailableTakerAmount, + }); + + await this.checkForUntrackedTokensAndAskToAdd(); + } + private async onFillOrderClickFireAndForgetAsync(): Promise { + if (!_.isEmpty(this.props.blockchainErr) || _.isEmpty(this.props.userAddress)) { + this.props.dispatcher.updateShouldBlockchainErrDialogBeOpen(true); + return; + } + + this.setState({ + isFilling: true, + didFillOrderSucceed: false, + }); + + const parsedOrder = this.state.parsedOrder; + const orderHash = parsedOrder.signature.hash; + const unavailableTakerAmount = await this.props.blockchain.getUnavailableTakerAmountAsync(orderHash); + const takerFillAmount = this.props.orderFillAmount; + + if (_.isUndefined(this.props.userAddress)) { + this.props.dispatcher.updateShouldBlockchainErrDialogBeOpen(true); + this.setState({ + isFilling: false, + }); + return; + } + let globalErrMsg = ''; + + if (_.isUndefined(takerFillAmount)) { + globalErrMsg = 'You must specify a fill amount'; + } + + const signedOrder = this.props.blockchain.portalOrderToSignedOrder( + parsedOrder.maker.address, + parsedOrder.taker.address, + parsedOrder.maker.token.address, + parsedOrder.taker.token.address, + new BigNumber(parsedOrder.maker.amount), + new BigNumber(parsedOrder.taker.amount), + new BigNumber(parsedOrder.maker.feeAmount), + new BigNumber(parsedOrder.taker.feeAmount), + new BigNumber(this.state.parsedOrder.expiration), + parsedOrder.feeRecipient, + parsedOrder.signature, + new BigNumber(parsedOrder.salt), + ); + if (_.isEmpty(globalErrMsg)) { + try { + await this.props.blockchain.validateFillOrderThrowIfInvalidAsync( + signedOrder, takerFillAmount, this.props.userAddress); + } catch (err) { + globalErrMsg = this.props.blockchain.toHumanReadableErrorMsg(err.message, parsedOrder.taker.address); + } + } + if (!_.isEmpty(globalErrMsg)) { + this.setState({ + isFilling: false, + globalErrMsg, + }); + return; + } + try { + const orderFilledAmount: BigNumber = await this.props.blockchain.fillOrderAsync( + signedOrder, this.props.orderFillAmount, + ); + // After fill completes, let's update the token balances + const makerToken = this.props.tokenByAddress[parsedOrder.maker.token.address]; + const takerToken = this.props.tokenByAddress[parsedOrder.taker.token.address]; + const tokens = [makerToken, takerToken]; + await this.props.blockchain.updateTokenBalancesAndAllowancesAsync(tokens); + this.setState({ + isFilling: false, + didFillOrderSucceed: true, + globalErrMsg: '', + unavailableTakerAmount: this.state.unavailableTakerAmount.plus(orderFilledAmount), + }); + return; + } catch (err) { + this.setState({ + isFilling: false, + }); + const errMsg = `${err}`; + if (_.includes(errMsg, 'User denied transaction signature')) { + return; + } + globalErrMsg = 'Failed to fill order, please refresh and try again'; + utils.consoleLog(`${err}`); + await errorReporter.reportAsync(err); + this.setState({ + globalErrMsg, + }); + return; + } + } + private async onCancelOrderClickFireAndForgetAsync(): Promise { + if (!_.isEmpty(this.props.blockchainErr) || _.isEmpty(this.props.userAddress)) { + this.props.dispatcher.updateShouldBlockchainErrDialogBeOpen(true); + return; + } + + this.setState({ + isCancelling: true, + didCancelOrderSucceed: false, + }); + + const parsedOrder = this.state.parsedOrder; + const orderHash = parsedOrder.signature.hash; + const takerAddress = this.props.userAddress; + + if (_.isUndefined(takerAddress)) { + this.props.dispatcher.updateShouldBlockchainErrDialogBeOpen(true); + this.setState({ + isFilling: false, + }); + return; + } + let globalErrMsg = ''; + + const takerTokenAmount = new BigNumber(parsedOrder.taker.amount); + + const signedOrder = this.props.blockchain.portalOrderToSignedOrder( + parsedOrder.maker.address, + parsedOrder.taker.address, + parsedOrder.maker.token.address, + parsedOrder.taker.token.address, + new BigNumber(parsedOrder.maker.amount), + takerTokenAmount, + new BigNumber(parsedOrder.maker.feeAmount), + new BigNumber(parsedOrder.taker.feeAmount), + new BigNumber(this.state.parsedOrder.expiration), + parsedOrder.feeRecipient, + parsedOrder.signature, + new BigNumber(parsedOrder.salt), + ); + const unavailableTakerAmount = await this.props.blockchain.getUnavailableTakerAmountAsync(orderHash); + const availableTakerTokenAmount = takerTokenAmount.minus(unavailableTakerAmount); + try { + await this.props.blockchain.validateCancelOrderThrowIfInvalidAsync( + signedOrder, availableTakerTokenAmount); + } catch (err) { + globalErrMsg = this.props.blockchain.toHumanReadableErrorMsg(err.message, parsedOrder.taker.address); + } + if (!_.isEmpty(globalErrMsg)) { + this.setState({ + isCancelling: false, + globalErrMsg, + }); + return; + } + try { + await this.props.blockchain.cancelOrderAsync( + signedOrder, availableTakerTokenAmount, + ); + this.setState({ + isCancelling: false, + didCancelOrderSucceed: true, + globalErrMsg: '', + unavailableTakerAmount: takerTokenAmount, + }); + return; + } catch (err) { + this.setState({ + isCancelling: false, + }); + const errMsg = `${err}`; + if (_.includes(errMsg, 'User denied transaction signature')) { + return; + } + globalErrMsg = 'Failed to cancel order, please refresh and try again'; + utils.consoleLog(`${err}`); + await errorReporter.reportAsync(err); + this.setState({ + globalErrMsg, + }); + return; + } + } + private formatCurrencyAmount(amount: BigNumber, decimals: number): number { + const unitAmount = ZeroEx.toUnitAmount(amount, decimals); + const roundedUnitAmount = Math.round(unitAmount.toNumber() * 100000) / 100000; + return roundedUnitAmount; + } + private onToggleTrackConfirmDialog(didConfirmTokenTracking: boolean) { + if (!didConfirmTokenTracking) { + this.setState({ + orderJSON: '', + orderJSONErrMsg: '', + parsedOrder: undefined, + }); + } else { + this.setState({ + areAllInvolvedTokensTracked: true, + }); + } + this.setState({ + isConfirmingTokenTracking: !this.state.isConfirmingTokenTracking, + tokensToTrack: [], + }); + } +} diff --git a/packages/website/ts/components/fill_order_json.tsx b/packages/website/ts/components/fill_order_json.tsx new file mode 100644 index 000000000..b355d910b --- /dev/null +++ b/packages/website/ts/components/fill_order_json.tsx @@ -0,0 +1,69 @@ +import * as _ from 'lodash'; +import * as React from 'react'; +import BigNumber from 'bignumber.js'; +import {ZeroEx} from '0x.js'; +import Paper from 'material-ui/Paper'; +import TextField from 'material-ui/TextField'; +import {Side, TokenByAddress} from 'ts/types'; +import {utils} from 'ts/utils/utils'; +import {Blockchain} from 'ts/blockchain'; +import {constants} from 'ts/utils/constants'; + +interface FillOrderJSONProps { + blockchain: Blockchain; + tokenByAddress: TokenByAddress; + networkId: number; + orderJSON: string; + onFillOrderJSONChanged: (event: any) => void; +} + +interface FillOrderJSONState {} + +export class FillOrderJSON extends React.Component { + public render() { + const tokenAddresses = _.keys(this.props.tokenByAddress); + const exchangeContract = this.props.blockchain.getExchangeContractAddressIfExists(); + const hintSideToAssetToken = { + [Side.deposit]: { + amount: new BigNumber(35), + address: tokenAddresses[0], + }, + [Side.receive]: { + amount: new BigNumber(89), + address: tokenAddresses[1], + }, + }; + const hintOrderExpiryTimestamp = utils.initialOrderExpiryUnixTimestampSec(); + const hintSignatureData = { + hash: '0xf965a9978a0381ab58f5a2408ad967c...', + r: '0xf01103f759e2289a28593eaf22e5820032...', + s: '937862111edcba395f8a9e0cc1b2c5e12320...', + v: 27, + }; + const hintSalt = ZeroEx.generatePseudoRandomSalt(); + const hintOrder = utils.generateOrder(this.props.networkId, exchangeContract, hintSideToAssetToken, + hintOrderExpiryTimestamp, '', '', constants.MAKER_FEE, + constants.TAKER_FEE, constants.FEE_RECIPIENT_ADDRESS, + hintSignatureData, this.props.tokenByAddress, hintSalt); + const hintOrderJSON = `${JSON.stringify(hintOrder, null, '\t').substring(0, 500)}...`; + return ( +
+ + + +
+ ); + } +} diff --git a/packages/website/ts/components/fill_warning_dialog.tsx b/packages/website/ts/components/fill_warning_dialog.tsx new file mode 100644 index 000000000..029fa8b0c --- /dev/null +++ b/packages/website/ts/components/fill_warning_dialog.tsx @@ -0,0 +1,47 @@ +import * as React from 'react'; +import {colors} from 'material-ui/styles'; +import FlatButton from 'material-ui/FlatButton'; +import Dialog from 'material-ui/Dialog'; + +interface FillWarningDialogProps { + isOpen: boolean; + onToggleDialog: () => void; +} + +export function FillWarningDialog(props: FillWarningDialogProps) { + const didCancel = true; + return ( + , + , + ]} + open={props.isOpen} + onRequestClose={props.onToggleDialog.bind(this)} + autoScrollBodyContent={true} + modal={true} + > +
+
+ At least one of the tokens in this order was not found in the + token registry smart contract and may be counterfeit. It is your + responsibility to verify the token addresses on Etherscan ( + + See this how-to guide + ) before filling an order. This action may result in the loss of funds. +
+
+
+ ); +} diff --git a/packages/website/ts/components/flash_messages/token_send_completed.tsx b/packages/website/ts/components/flash_messages/token_send_completed.tsx new file mode 100644 index 000000000..c4977d70b --- /dev/null +++ b/packages/website/ts/components/flash_messages/token_send_completed.tsx @@ -0,0 +1,38 @@ +import * as React from 'react'; +import * as _ from 'lodash'; +import BigNumber from 'bignumber.js'; +import {ZeroEx} from '0x.js'; +import {Token} from 'ts/types'; +import {utils} from 'ts/utils/utils'; + +interface TokenSendCompletedProps { + etherScanLinkIfExists?: string; + token: Token; + toAddress: string; + amountInBaseUnits: BigNumber; +} + +interface TokenSendCompletedState {} + +export class TokenSendCompleted extends React.Component { + public render() { + const etherScanLink = !_.isUndefined(this.props.etherScanLinkIfExists) && + ( + + Verify on Etherscan + + ); + const amountInUnits = ZeroEx.toUnitAmount(this.props.amountInBaseUnits, this.props.token.decimals); + const truncatedAddress = utils.getAddressBeginAndEnd(this.props.toAddress); + return ( +
+ {`Sent ${amountInUnits} ${this.props.token.symbol} to ${truncatedAddress}: `} + {etherScanLink} +
+ ); + } +} diff --git a/packages/website/ts/components/flash_messages/transaction_submitted.tsx b/packages/website/ts/components/flash_messages/transaction_submitted.tsx new file mode 100644 index 000000000..7a3cc6e86 --- /dev/null +++ b/packages/website/ts/components/flash_messages/transaction_submitted.tsx @@ -0,0 +1,29 @@ +import * as React from 'react'; +import * as _ from 'lodash'; + +interface TransactionSubmittedProps { + etherScanLinkIfExists?: string; +} + +interface TransactionSubmittedState {} + +export class TransactionSubmitted extends React.Component { + public render() { + if (_.isUndefined(this.props.etherScanLinkIfExists)) { + return
Transaction submitted to the network
; + } else { + return ( +
+ Transaction submitted to the network:{' '} + + Verify on Etherscan + +
+ ); + } + } +} diff --git a/packages/website/ts/components/footer.tsx b/packages/website/ts/components/footer.tsx new file mode 100644 index 000000000..99f97292e --- /dev/null +++ b/packages/website/ts/components/footer.tsx @@ -0,0 +1,255 @@ +import * as _ from 'lodash'; +import * as React from 'react'; +import {HashLink} from 'react-router-hash-link'; +import {Styles, WebsitePaths} from 'ts/types'; +import { + Link, +} from 'react-router-dom'; +import { + Link as ScrollLink, +} from 'react-scroll'; +import {constants} from 'ts/utils/constants'; + +interface MenuItemsBySection { + [sectionName: string]: FooterMenuItem[]; +} + +interface FooterMenuItem { + title: string; + path?: string; + isExternal?: boolean; + fileName?: string; +} + +enum Sections { + Documentation = 'Documentation', + Community = 'Community', + Organization = 'Organization', +} + +const ICON_DIMENSION = 16; +const CUSTOM_DARK_GRAY = '#393939'; +const CUSTOM_LIGHT_GRAY = '#CACACA'; +const CUSTOM_LIGHTEST_GRAY = '#9E9E9E'; +const menuItemsBySection: MenuItemsBySection = { + Documentation: [ + { + title: '0x.js', + path: WebsitePaths.ZeroExJs, + }, + { + title: '0x Smart Contracts', + path: WebsitePaths.SmartContracts, + }, + { + title: 'Whitepaper', + path: WebsitePaths.Whitepaper, + isExternal: true, + }, + { + title: 'Wiki', + path: WebsitePaths.Wiki, + }, + { + title: 'FAQ', + path: WebsitePaths.FAQ, + }, + ], + Community: [ + { + title: 'Rocket.chat', + isExternal: true, + path: constants.ZEROEX_CHAT_URL, + fileName: 'rocketchat.png', + }, + { + title: 'Blog', + isExternal: true, + path: constants.BLOG_URL, + fileName: 'medium.png', + }, + { + title: 'Twitter', + isExternal: true, + path: constants.TWITTER_URL, + fileName: 'twitter.png', + }, + { + title: 'Reddit', + isExternal: true, + path: constants.REDDIT_URL, + fileName: 'reddit.png', + }, + ], + Organization: [ + { + title: 'About', + isExternal: false, + path: WebsitePaths.About, + }, + { + title: 'Careers', + isExternal: true, + path: constants.ANGELLIST_URL, + }, + { + title: 'Contact', + isExternal: true, + path: 'mailto:team@0xproject.com', + }, + ], +}; +const linkStyle = { + color: 'white', + cursor: 'pointer', +}; + +const titleToIcon: {[title: string]: string} = { + 'Rocket.chat': 'rocketchat.png', + 'Blog': 'medium.png', + 'Twitter': 'twitter.png', + 'Reddit': 'reddit.png', +}; + +export interface FooterProps { + location: Location; +} + +interface FooterState {} + +export class Footer extends React.Component { + public render() { + return ( +
+
+
+
+
+ +
+
+ © ZeroEx, Intl. +
+
+
+
+
+
+ {this.renderHeader(Sections.Documentation)} + {_.map(menuItemsBySection[Sections.Documentation], this.renderMenuItem.bind(this))} +
+
+
+
+ {this.renderHeader(Sections.Community)} + {_.map(menuItemsBySection[Sections.Community], this.renderMenuItem.bind(this))} +
+
+
+
+ {this.renderHeader(Sections.Organization)} + {_.map(menuItemsBySection[Sections.Organization], this.renderMenuItem.bind(this))} +
+
+
+
+
+ ); + } + private renderIcon(fileName: string) { + return ( +
+ +
+ ); + } + private renderMenuItem(item: FooterMenuItem) { + const iconIfExists = titleToIcon[item.title]; + return ( +
+ {item.isExternal ? + + {!_.isUndefined(iconIfExists) ? +
+
+
+ {this.renderIcon(iconIfExists)} +
+
{item.title}
+
+
: + item.title + } +
: + +
+ {!_.isUndefined(iconIfExists) && +
+ {this.renderIcon(iconIfExists)} +
+ } + {item.title} +
+ + } +
+ ); + } + private renderHeader(title: string) { + const headerStyle = { + textTransform: 'uppercase', + color: CUSTOM_LIGHT_GRAY, + letterSpacing: 2, + fontFamily: 'Roboto Mono', + fontSize: 13, + }; + return ( +
+ {title} +
+ ); + } + private renderHomepageLink(title: string) { + const hash = title.toLowerCase(); + if (this.props.location.pathname === WebsitePaths.Home) { + return ( + + {title} + + ); + } else { + return ( + + {title} + + ); + } + } +} diff --git a/packages/website/ts/components/generate_order/asset_picker.tsx b/packages/website/ts/components/generate_order/asset_picker.tsx new file mode 100644 index 000000000..59826d06e --- /dev/null +++ b/packages/website/ts/components/generate_order/asset_picker.tsx @@ -0,0 +1,291 @@ +import * as _ from 'lodash'; +import * as React from 'react'; +import {colors} from 'material-ui/styles'; +import Dialog from 'material-ui/Dialog'; +import GridList from 'material-ui/GridList/GridList'; +import GridTile from 'material-ui/GridList/GridTile'; +import FlatButton from 'material-ui/FlatButton'; +import {utils} from 'ts/utils/utils'; +import {Blockchain} from 'ts/blockchain'; +import {Dispatcher} from 'ts/redux/dispatcher'; +import { + Token, + AssetToken, + TokenByAddress, + Styles, + TokenState, + DialogConfigs, + TokenVisibility, +} from 'ts/types'; +import {NewTokenForm} from 'ts/components/generate_order/new_token_form'; +import {trackedTokenStorage} from 'ts/local_storage/tracked_token_storage'; +import {TrackTokenConfirmation} from 'ts/components/track_token_confirmation'; +import {TokenIcon} from 'ts/components/ui/token_icon'; + +const TOKEN_ICON_DIMENSION = 100; +const TILE_DIMENSION = 146; +enum AssetViews { + ASSET_PICKER = 'ASSET_PICKER', + NEW_TOKEN_FORM = 'NEW_TOKEN_FORM', + CONFIRM_TRACK_TOKEN = 'CONFIRM_TRACK_TOKEN', +} + +interface AssetPickerProps { + userAddress: string; + blockchain: Blockchain; + dispatcher: Dispatcher; + networkId: number; + isOpen: boolean; + currentTokenAddress: string; + onTokenChosen: (tokenAddress: string) => void; + tokenByAddress: TokenByAddress; + tokenVisibility?: TokenVisibility; +} + +interface AssetPickerState { + assetView: AssetViews; + hoveredAddress: string | undefined; + chosenTrackTokenAddress: string; + isAddingTokenToTracked: boolean; +} + +export class AssetPicker extends React.Component { + public static defaultProps: Partial = { + tokenVisibility: TokenVisibility.ALL, + }; + private dialogConfigsByAssetView: {[assetView: string]: DialogConfigs}; + constructor(props: AssetPickerProps) { + super(props); + this.state = { + assetView: AssetViews.ASSET_PICKER, + hoveredAddress: undefined, + chosenTrackTokenAddress: undefined, + isAddingTokenToTracked: false, + }; + this.dialogConfigsByAssetView = { + [AssetViews.ASSET_PICKER]: { + title: 'Select token', + isModal: false, + actions: [], + }, + [AssetViews.NEW_TOKEN_FORM]: { + title: 'Add an ERC20 token', + isModal: false, + actions: [], + }, + [AssetViews.CONFIRM_TRACK_TOKEN]: { + title: 'Tracking confirmation', + isModal: true, + actions: [ + , + , + ], + }, + }; + } + public render() { + const dialogConfigs: DialogConfigs = this.dialogConfigsByAssetView[this.state.assetView]; + return ( + + {this.state.assetView === AssetViews.ASSET_PICKER && + this.renderAssetPicker() + } + {this.state.assetView === AssetViews.NEW_TOKEN_FORM && + + } + {this.state.assetView === AssetViews.CONFIRM_TRACK_TOKEN && + this.renderConfirmTrackToken() + } + + ); + } + private renderConfirmTrackToken() { + const token = this.props.tokenByAddress[this.state.chosenTrackTokenAddress]; + return ( + + ); + } + private renderAssetPicker() { + return ( +
+ {this.renderGridTiles()} +
+ ); + } + private renderGridTiles() { + let isHovered; + let tileStyles; + const gridTiles = _.map(this.props.tokenByAddress, (token: Token, address: string) => { + if ((this.props.tokenVisibility === TokenVisibility.TRACKED && !token.isTracked) || + (this.props.tokenVisibility === TokenVisibility.UNTRACKED && token.isTracked)) { + return null; // Skip + } + isHovered = this.state.hoveredAddress === address; + tileStyles = { + cursor: 'pointer', + opacity: isHovered ? 0.6 : 1, + }; + return ( +
+
+ +
+
{token.name}
+
+ ); + }); + const otherTokenKey = 'otherToken'; + isHovered = this.state.hoveredAddress === otherTokenKey; + tileStyles = { + cursor: 'pointer', + opacity: isHovered ? 0.6 : 1, + }; + if (this.props.tokenVisibility !== TokenVisibility.TRACKED) { + gridTiles.push(( +
+
+ +
+
Other ERC20 Token
+
+ )); + } + return gridTiles; + } + private onToggleHover(address: string, isHovered: boolean) { + const hoveredAddress = isHovered ? address : undefined; + this.setState({ + hoveredAddress, + }); + } + private onCloseDialog() { + this.setState({ + assetView: AssetViews.ASSET_PICKER, + }); + this.props.onTokenChosen(this.props.currentTokenAddress); + } + private onChooseToken(tokenAddress: string) { + const token = this.props.tokenByAddress[tokenAddress]; + if (token.isTracked) { + this.props.onTokenChosen(tokenAddress); + } else { + this.setState({ + assetView: AssetViews.CONFIRM_TRACK_TOKEN, + chosenTrackTokenAddress: tokenAddress, + }); + } + } + private getTitle() { + switch (this.state.assetView) { + case AssetViews.ASSET_PICKER: + return 'Select token'; + + case AssetViews.NEW_TOKEN_FORM: + return 'Add an ERC20 token'; + + case AssetViews.CONFIRM_TRACK_TOKEN: + return 'Tracking confirmation'; + + default: + throw utils.spawnSwitchErr('assetView', this.state.assetView); + } + } + private onCustomAssetChosen() { + this.setState({ + assetView: AssetViews.NEW_TOKEN_FORM, + }); + } + private onNewTokenSubmitted(newToken: Token, newTokenState: TokenState) { + this.props.dispatcher.updateTokenStateByAddress({ + [newToken.address]: newTokenState, + }); + trackedTokenStorage.addTrackedTokenToUser(this.props.userAddress, this.props.networkId, newToken); + this.props.dispatcher.addTokenToTokenByAddress(newToken); + this.setState({ + assetView: AssetViews.ASSET_PICKER, + }); + this.props.onTokenChosen(newToken.address); + } + private async onTrackConfirmationRespondedAsync(didUserAcceptTracking: boolean) { + if (!didUserAcceptTracking) { + this.setState({ + isAddingTokenToTracked: false, + assetView: AssetViews.ASSET_PICKER, + chosenTrackTokenAddress: undefined, + }); + this.onCloseDialog(); + return; + } + this.setState({ + isAddingTokenToTracked: true, + }); + const tokenAddress = this.state.chosenTrackTokenAddress; + const token = this.props.tokenByAddress[tokenAddress]; + const newTokenEntry = _.assign({}, token); + + newTokenEntry.isTracked = true; + trackedTokenStorage.addTrackedTokenToUser(this.props.userAddress, this.props.networkId, newTokenEntry); + this.props.dispatcher.updateTokenByAddress([newTokenEntry]); + + const [ + balance, + allowance, + ] = await this.props.blockchain.getCurrentUserTokenBalanceAndAllowanceAsync(token.address); + this.props.dispatcher.updateTokenStateByAddress({ + [token.address]: { + balance, + allowance, + }, + }); + this.setState({ + isAddingTokenToTracked: false, + assetView: AssetViews.ASSET_PICKER, + chosenTrackTokenAddress: undefined, + }); + this.props.onTokenChosen(tokenAddress); + } +} diff --git a/packages/website/ts/components/generate_order/generate_order_form.tsx b/packages/website/ts/components/generate_order/generate_order_form.tsx new file mode 100644 index 000000000..e9026d9bc --- /dev/null +++ b/packages/website/ts/components/generate_order/generate_order_form.tsx @@ -0,0 +1,348 @@ +import * as _ from 'lodash'; +import * as React from 'react'; +import {ZeroEx, Order} from '0x.js'; +import BigNumber from 'bignumber.js'; +import {Blockchain} from 'ts/blockchain'; +import Divider from 'material-ui/Divider'; +import Dialog from 'material-ui/Dialog'; +import {colors} from 'material-ui/styles'; +import {Dispatcher} from 'ts/redux/dispatcher'; +import {utils} from 'ts/utils/utils'; +import {SchemaValidator} from 'ts/schemas/validator'; +import {orderSchema} from 'ts/schemas/order_schema'; +import {Alert} from 'ts/components/ui/alert'; +import {OrderJSON} from 'ts/components/order_json'; +import {IdenticonAddressInput} from 'ts/components/inputs/identicon_address_input'; +import {TokenInput} from 'ts/components/inputs/token_input'; +import {TokenAmountInput} from 'ts/components/inputs/token_amount_input'; +import {HashInput} from 'ts/components/inputs/hash_input'; +import {ExpirationInput} from 'ts/components/inputs/expiration_input'; +import {LifeCycleRaisedButton} from 'ts/components/ui/lifecycle_raised_button'; +import {errorReporter} from 'ts/utils/error_reporter'; +import {HelpTooltip} from 'ts/components/ui/help_tooltip'; +import {SwapIcon} from 'ts/components/ui/swap_icon'; +import { + Side, + SideToAssetToken, + SignatureData, + HashData, + TokenByAddress, + TokenStateByAddress, + BlockchainErrs, + Token, + AlertTypes, +} from 'ts/types'; + +enum SigningState { + UNSIGNED, + SIGNING, + SIGNED, +} + +interface GenerateOrderFormProps { + blockchain: Blockchain; + blockchainErr: BlockchainErrs; + blockchainIsLoaded: boolean; + dispatcher: Dispatcher; + hashData: HashData; + orderExpiryTimestamp: BigNumber; + networkId: number; + userAddress: string; + orderSignatureData: SignatureData; + orderTakerAddress: string; + orderSalt: BigNumber; + sideToAssetToken: SideToAssetToken; + tokenByAddress: TokenByAddress; + tokenStateByAddress: TokenStateByAddress; +} + +interface GenerateOrderFormState { + globalErrMsg: string; + shouldShowIncompleteErrs: boolean; + signingState: SigningState; +} + +const style = { + paper: { + display: 'inline-block', + position: 'relative', + textAlign: 'center', + width: '100%', + }, +}; + +export class GenerateOrderForm extends React.Component { + private validator: SchemaValidator; + constructor(props: GenerateOrderFormProps) { + super(props); + this.state = { + globalErrMsg: '', + shouldShowIncompleteErrs: false, + signingState: SigningState.UNSIGNED, + }; + this.validator = new SchemaValidator(); + } + public componentDidMount() { + window.scrollTo(0, 0); + } + public render() { + const dispatcher = this.props.dispatcher; + const depositTokenAddress = this.props.sideToAssetToken[Side.deposit].address; + const depositToken = this.props.tokenByAddress[depositTokenAddress]; + const depositTokenState = this.props.tokenStateByAddress[depositTokenAddress]; + const receiveTokenAddress = this.props.sideToAssetToken[Side.receive].address; + const receiveToken = this.props.tokenByAddress[receiveTokenAddress]; + const receiveTokenState = this.props.tokenStateByAddress[receiveTokenAddress]; + const takerExplanation = 'If a taker is specified, only they are
\ + allowed to fill this order. If no taker is
\ + specified, anyone is able to fill it.'; + const exchangeContractIfExists = this.props.blockchain.getExchangeContractAddressIfExists(); + return ( +
+

Generate an order

+ +
+
+
+
+ + +
+
+
+ +
+
+
+ + +
+
+
+
+
+
Expiration
+ +
+
+
+ +
+
+ +
+
+
+
+ +
+
+
+ +
+ {this.state.globalErrMsg !== '' && + + } +
+
+ + + +
+ ); + } + private onTokenAmountChange(token: Token, side: Side, isValid: boolean, amount?: BigNumber) { + this.props.dispatcher.updateChosenAssetToken(side, {address: token.address, amount}); + } + private onCloseOrderJSONDialog() { + // Upon closing the order JSON dialog, we update the orderSalt stored in the Redux store + // with a new value so that if a user signs the identical order again, the newly signed + // orderHash will not collide with the previously generated orderHash. + this.props.dispatcher.updateOrderSalt(ZeroEx.generatePseudoRandomSalt()); + this.setState({ + signingState: SigningState.UNSIGNED, + }); + } + private async onSignClickedAsync(): Promise { + if (this.props.blockchainErr !== '') { + this.props.dispatcher.updateShouldBlockchainErrDialogBeOpen(true); + return false; + } + + // Check if all required inputs were supplied + const debitToken = this.props.sideToAssetToken[Side.deposit]; + const debitBalance = this.props.tokenStateByAddress[debitToken.address].balance; + const debitAllowance = this.props.tokenStateByAddress[debitToken.address].allowance; + const receiveAmount = this.props.sideToAssetToken[Side.receive].amount; + if (!_.isUndefined(debitToken.amount) && !_.isUndefined(receiveAmount) && + debitToken.amount.gt(0) && receiveAmount.gt(0) && + this.props.userAddress !== '' && + debitBalance.gte(debitToken.amount) && debitAllowance.gte(debitToken.amount)) { + const didSignSuccessfully = await this.signTransactionAsync(); + if (didSignSuccessfully) { + this.setState({ + globalErrMsg: '', + shouldShowIncompleteErrs: false, + }); + } + return didSignSuccessfully; + } else { + let globalErrMsg = 'You must fix the above errors in order to generate a valid order'; + if (this.props.userAddress === '') { + globalErrMsg = 'You must enable wallet communication'; + this.props.dispatcher.updateShouldBlockchainErrDialogBeOpen(true); + } + this.setState({ + globalErrMsg, + shouldShowIncompleteErrs: true, + }); + return false; + } + } + private async signTransactionAsync(): Promise { + this.setState({ + signingState: SigningState.SIGNING, + }); + const exchangeContractAddr = this.props.blockchain.getExchangeContractAddressIfExists(); + if (_.isUndefined(exchangeContractAddr)) { + this.props.dispatcher.updateShouldBlockchainErrDialogBeOpen(true); + this.setState({ + isSigning: false, + }); + return false; + } + const hashData = this.props.hashData; + + const zeroExOrder: Order = { + exchangeContractAddress: exchangeContractAddr, + expirationUnixTimestampSec: hashData.orderExpiryTimestamp, + feeRecipient: hashData.feeRecipientAddress, + maker: hashData.orderMakerAddress, + makerFee: hashData.makerFee, + makerTokenAddress: hashData.depositTokenContractAddr, + makerTokenAmount: hashData.depositAmount, + salt: hashData.orderSalt, + taker: hashData.orderTakerAddress, + takerFee: hashData.takerFee, + takerTokenAddress: hashData.receiveTokenContractAddr, + takerTokenAmount: hashData.receiveAmount, + }; + const orderHash = ZeroEx.getOrderHashHex(zeroExOrder); + + let globalErrMsg = ''; + try { + const signatureData = await this.props.blockchain.signOrderHashAsync(orderHash); + const order = utils.generateOrder(this.props.networkId, exchangeContractAddr, this.props.sideToAssetToken, + hashData.orderExpiryTimestamp, this.props.orderTakerAddress, + this.props.userAddress, hashData.makerFee, hashData.takerFee, + hashData.feeRecipientAddress, signatureData, this.props.tokenByAddress, + hashData.orderSalt); + const validationResult = this.validator.validate(order, orderSchema); + if (validationResult.errors.length > 0) { + globalErrMsg = 'Order signing failed. Please refresh and try again'; + utils.consoleLog(`Unexpected error occured: Order validation failed: + ${validationResult.errors}`); + } + } catch (err) { + const errMsg = '' + err; + if (utils.didUserDenyWeb3Request(errMsg)) { + globalErrMsg = 'User denied sign request'; + } else { + globalErrMsg = 'An unexpected error occured. Please try refreshing the page'; + utils.consoleLog(`Unexpected error occured: ${err}`); + utils.consoleLog(err.stack); + await errorReporter.reportAsync(err); + } + } + this.setState({ + signingState: globalErrMsg === '' ? SigningState.SIGNED : SigningState.UNSIGNED, + globalErrMsg, + }); + return globalErrMsg === ''; + } + private updateOrderAddress(address?: string): void { + if (!_.isUndefined(address)) { + this.props.dispatcher.updateOrderTakerAddress(address); + } + } +} diff --git a/packages/website/ts/components/generate_order/new_token_form.tsx b/packages/website/ts/components/generate_order/new_token_form.tsx new file mode 100644 index 000000000..95c05f5bb --- /dev/null +++ b/packages/website/ts/components/generate_order/new_token_form.tsx @@ -0,0 +1,237 @@ +import * as _ from 'lodash'; +import * as React from 'react'; +import {colors} from 'material-ui/styles'; +import TextField from 'material-ui/TextField'; +import {constants} from 'ts/utils/constants'; +import {Blockchain} from 'ts/blockchain'; +import {Token, TokenState, TokenByAddress, AlertTypes} from 'ts/types'; +import {AddressInput} from 'ts/components/inputs/address_input'; +import {Alert} from 'ts/components/ui/alert'; +import {LifeCycleRaisedButton} from 'ts/components/ui/lifecycle_raised_button'; +import {RequiredLabel} from 'ts/components/ui/required_label'; +import BigNumber from 'bignumber.js'; + +interface NewTokenFormProps { + blockchain: Blockchain; + tokenByAddress: TokenByAddress; + onNewTokenSubmitted: (token: Token, tokenState: TokenState) => void; +} + +interface NewTokenFormState { + globalErrMsg: string; + name: string; + nameErrText: string; + symbol: string; + symbolErrText: string; + address: string; + shouldShowAddressIncompleteErr: boolean; + decimals: string; + decimalsErrText: string; +} + +export class NewTokenForm extends React.Component { + constructor(props: NewTokenFormProps) { + super(props); + this.state = { + address: '', + globalErrMsg: '', + name: '', + nameErrText: '', + shouldShowAddressIncompleteErr: false, + symbol: '', + symbolErrText: '', + decimals: '18', + decimalsErrText: '', + }; + } + public render() { + return ( +
+
+ } + value={this.state.name} + errorText={this.state.nameErrText} + onChange={this.onTokenNameChanged.bind(this)} + /> +
+
+ } + value={this.state.symbol} + errorText={this.state.symbolErrText} + onChange={this.onTokenSymbolChanged.bind(this)} + /> +
+
+ +
+
+ } + value={this.state.decimals} + errorText={this.state.decimalsErrText} + onChange={this.onTokenDecimalsChanged.bind(this)} + /> +
+
+ +
+ {this.state.globalErrMsg !== '' && + + } +
+ ); + } + private async onAddNewTokenClickAsync() { + // Trigger validation of name and symbol + this.onTokenNameChanged(undefined, this.state.name); + this.onTokenSymbolChanged(undefined, this.state.symbol); + this.onTokenDecimalsChanged(undefined, this.state.decimals); + + const isAddressIncomplete = this.state.address === ''; + let doesContractExist = false; + if (!isAddressIncomplete) { + doesContractExist = await this.props.blockchain.doesContractExistAtAddressAsync(this.state.address); + } + + let hasBalanceAllowanceErr = false; + let balance = new BigNumber(0); + let allowance = new BigNumber(0); + if (doesContractExist) { + try { + [ + balance, + allowance, + ] = await this.props.blockchain.getCurrentUserTokenBalanceAndAllowanceAsync(this.state.address); + } catch (err) { + hasBalanceAllowanceErr = true; + } + } + + let globalErrMsg = ''; + if (this.state.nameErrText !== '' || this.state.symbolErrText !== '' || + this.state.decimalsErrText !== '' || isAddressIncomplete) { + globalErrMsg = 'Please fix the above issues'; + } else if (!doesContractExist) { + globalErrMsg = 'No contract found at supplied address'; + } else if (hasBalanceAllowanceErr) { + globalErrMsg = 'Unsuccessful call to `balanceOf` and/or `allowance` on supplied contract address'; + } else if (!isAddressIncomplete && !_.isUndefined(this.props.tokenByAddress[this.state.address])) { + globalErrMsg = 'A token already exists with this address'; + } + + if (globalErrMsg !== '') { + this.setState({ + globalErrMsg, + shouldShowAddressIncompleteErr: isAddressIncomplete, + }); + return; + } + + const newToken: Token = { + address: this.state.address, + decimals: _.parseInt(this.state.decimals), + iconUrl: undefined, + name: this.state.name, + symbol: this.state.symbol.toUpperCase(), + isTracked: true, + isRegistered: false, + }; + const newTokenState: TokenState = { + balance, + allowance, + }; + this.props.onNewTokenSubmitted(newToken, newTokenState); + } + private onTokenNameChanged(e: any, name: string) { + let nameErrText = ''; + const maxLength = 30; + const tokens = _.values(this.props.tokenByAddress); + const tokenWithNameIfExists = _.find(tokens, {name}); + const tokenWithNameExists = !_.isUndefined(tokenWithNameIfExists); + if (name === '') { + nameErrText = 'Name is required'; + } else if (!this.isValidName(name)) { + nameErrText = 'Name should only contain letters, digits and spaces'; + } else if (name.length > maxLength) { + nameErrText = `Max length is ${maxLength}`; + } else if (tokenWithNameExists) { + nameErrText = 'Token with this name already exists'; + } + + this.setState({ + name, + nameErrText, + }); + } + private onTokenSymbolChanged(e: any, symbol: string) { + let symbolErrText = ''; + const maxLength = 5; + const tokens = _.values(this.props.tokenByAddress); + const tokenWithSymbolExists = !_.isUndefined(_.find(tokens, {symbol})); + if (symbol === '') { + symbolErrText = 'Symbol is required'; + } else if (!this.isLetters(symbol)) { + symbolErrText = 'Can only include letters'; + } else if (symbol.length > maxLength) { + symbolErrText = `Max length is ${maxLength}`; + } else if (tokenWithSymbolExists) { + symbolErrText = 'Token with symbol already exists'; + } + + this.setState({ + symbol, + symbolErrText, + }); + } + private onTokenDecimalsChanged(e: any, decimals: string) { + let decimalsErrText = ''; + const maxLength = 2; + if (decimals === '') { + decimalsErrText = 'Decimals is required'; + } else if (!this.isInteger(decimals)) { + decimalsErrText = 'Must be an integer'; + } else if (decimals.length > maxLength) { + decimalsErrText = `Max length is ${maxLength}`; + } + + this.setState({ + decimals, + decimalsErrText, + }); + } + private onTokenAddressChanged(address?: string) { + if (!_.isUndefined(address)) { + this.setState({ + address, + }); + } + } + private isValidName(input: string) { + return /^[a-z0-9 ]+$/i.test(input); + } + private isInteger(input: string) { + return /^[0-9]+$/i.test(input); + } + private isLetters(input: string) { + return /^[a-zA-Z]+$/i.test(input); + } +} diff --git a/packages/website/ts/components/inputs/address_input.tsx b/packages/website/ts/components/inputs/address_input.tsx new file mode 100644 index 000000000..57ad7a5e2 --- /dev/null +++ b/packages/website/ts/components/inputs/address_input.tsx @@ -0,0 +1,74 @@ +import * as _ from 'lodash'; +import * as React from 'react'; +import {isAddress} from 'ethereum-address'; +import TextField from 'material-ui/TextField'; +import {colors} from 'material-ui/styles'; +import {Blockchain} from 'ts/blockchain'; +import {RequiredLabel} from 'ts/components/ui/required_label'; + +interface AddressInputProps { + disabled?: boolean; + initialAddress: string; + isRequired?: boolean; + hintText?: string; + shouldHideLabel?: boolean; + label?: string; + shouldShowIncompleteErrs?: boolean; + updateAddress: (address?: string) => void; +} + +interface AddressInputState { + address: string; + errMsg: string; +} + +export class AddressInput extends React.Component { + constructor(props: AddressInputProps) { + super(props); + this.state = { + address: this.props.initialAddress, + errMsg: '', + }; + } + public componentWillReceiveProps(nextProps: AddressInputProps) { + if (nextProps.shouldShowIncompleteErrs && this.props.isRequired && + this.state.address === '') { + this.setState({ + errMsg: 'Address is required', + }); + } + } + public render() { + const label = this.props.isRequired ? : + this.props.label; + const labelDisplay = this.props.shouldHideLabel ? 'hidden' : 'block'; + const hintText = this.props.hintText ? this.props.hintText : ''; + return ( +
+ +
+ ); + } + private onOrderTakerAddressUpdated(e: any) { + const address = e.target.value.toLowerCase(); + const isValidAddress = isAddress(address) || address === ''; + const errMsg = isValidAddress ? '' : 'Invalid ethereum address'; + this.setState({ + address, + errMsg, + }); + const addressIfValid = isValidAddress ? address : undefined; + this.props.updateAddress(addressIfValid); + } +} diff --git a/packages/website/ts/components/inputs/allowance_toggle.tsx b/packages/website/ts/components/inputs/allowance_toggle.tsx new file mode 100644 index 000000000..f02112253 --- /dev/null +++ b/packages/website/ts/components/inputs/allowance_toggle.tsx @@ -0,0 +1,94 @@ +import * as _ from 'lodash'; +import * as React from 'react'; +import BigNumber from 'bignumber.js'; +import Toggle from 'material-ui/Toggle'; +import {Blockchain} from 'ts/blockchain'; +import {Dispatcher} from 'ts/redux/dispatcher'; +import {Token, TokenState, BalanceErrs} from 'ts/types'; +import {utils} from 'ts/utils/utils'; +import {errorReporter} from 'ts/utils/error_reporter'; + +const DEFAULT_ALLOWANCE_AMOUNT_IN_BASE_UNITS = new BigNumber(2).pow(256).minus(1); + +interface AllowanceToggleProps { + blockchain: Blockchain; + dispatcher: Dispatcher; + onErrorOccurred: (errType: BalanceErrs) => void; + token: Token; + tokenState: TokenState; + userAddress: string; +} + +interface AllowanceToggleState { + isSpinnerVisible: boolean; + prevAllowance: BigNumber; +} + +export class AllowanceToggle extends React.Component { + constructor(props: AllowanceToggleProps) { + super(props); + this.state = { + isSpinnerVisible: false, + prevAllowance: props.tokenState.allowance, + }; + } + public componentWillReceiveProps(nextProps: AllowanceToggleProps) { + if (!nextProps.tokenState.allowance.eq(this.state.prevAllowance)) { + this.setState({ + isSpinnerVisible: false, + prevAllowance: nextProps.tokenState.allowance, + }); + } + } + public render() { + return ( +
+
+ +
+ {this.state.isSpinnerVisible && +
+ +
+ } +
+ ); + } + private async onToggleAllowanceAsync() { + if (this.props.userAddress === '') { + this.props.dispatcher.updateShouldBlockchainErrDialogBeOpen(true); + return false; + } + + this.setState({ + isSpinnerVisible: true, + }); + + let newAllowanceAmountInBaseUnits = new BigNumber(0); + if (!this.isAllowanceSet()) { + newAllowanceAmountInBaseUnits = DEFAULT_ALLOWANCE_AMOUNT_IN_BASE_UNITS; + } + try { + await this.props.blockchain.setProxyAllowanceAsync(this.props.token, newAllowanceAmountInBaseUnits); + } catch (err) { + this.setState({ + isSpinnerVisible: false, + }); + const errMsg = '' + err; + if (_.includes(errMsg, 'User denied transaction')) { + return false; + } + utils.consoleLog(`Unexpected error encountered: ${err}`); + utils.consoleLog(err.stack); + await errorReporter.reportAsync(err); + this.props.onErrorOccurred(BalanceErrs.allowanceSettingFailed); + } + } + private isAllowanceSet() { + return !this.props.tokenState.allowance.eq(0); + } +} diff --git a/packages/website/ts/components/inputs/balance_bounded_input.tsx b/packages/website/ts/components/inputs/balance_bounded_input.tsx new file mode 100644 index 000000000..1c8b410a4 --- /dev/null +++ b/packages/website/ts/components/inputs/balance_bounded_input.tsx @@ -0,0 +1,160 @@ +import * as _ from 'lodash'; +import * as React from 'react'; +import BigNumber from 'bignumber.js'; +import {ValidatedBigNumberCallback, InputErrMsg, WebsitePaths} from 'ts/types'; +import TextField from 'material-ui/TextField'; +import {RequiredLabel} from 'ts/components/ui/required_label'; +import {colors} from 'material-ui/styles'; +import {utils} from 'ts/utils/utils'; +import {Link} from 'react-router-dom'; + +interface BalanceBoundedInputProps { + label?: string; + balance: BigNumber; + amount?: BigNumber; + onChange: ValidatedBigNumberCallback; + shouldShowIncompleteErrs?: boolean; + shouldCheckBalance: boolean; + validate?: (amount: BigNumber) => InputErrMsg; + onVisitBalancesPageClick?: () => void; + shouldHideVisitBalancesLink?: boolean; +} + +interface BalanceBoundedInputState { + errMsg: InputErrMsg; + amountString: string; +} + +export class BalanceBoundedInput extends + React.Component { + public static defaultProps: Partial = { + shouldShowIncompleteErrs: false, + shouldHideVisitBalancesLink: false, + }; + constructor(props: BalanceBoundedInputProps) { + super(props); + const amountString = this.props.amount ? this.props.amount.toString() : ''; + this.state = { + errMsg: this.validate(amountString, props.balance), + amountString, + }; + } + public componentWillReceiveProps(nextProps: BalanceBoundedInputProps) { + if (nextProps === this.props) { + return; + } + const isCurrentAmountNumeric = utils.isNumeric(this.state.amountString); + if (!_.isUndefined(nextProps.amount)) { + let shouldResetState = false; + if (!isCurrentAmountNumeric) { + shouldResetState = true; + } else { + const currentAmount = new BigNumber(this.state.amountString); + if (!currentAmount.eq(nextProps.amount) || !nextProps.balance.eq(this.props.balance)) { + shouldResetState = true; + } + } + if (shouldResetState) { + const amountString = nextProps.amount.toString(); + this.setState({ + errMsg: this.validate(amountString, nextProps.balance), + amountString, + }); + } + } else if (isCurrentAmountNumeric) { + const amountString = ''; + this.setState({ + errMsg: this.validate(amountString, nextProps.balance), + amountString, + }); + } + } + public render() { + let errorText = this.state.errMsg; + if (this.props.shouldShowIncompleteErrs && this.state.amountString === '') { + errorText = 'This field is required'; + } + let label: React.ReactNode|string = ''; + if (!_.isUndefined(this.props.label)) { + label = ; + } + return ( + amount} + onChange={this.onValueChange.bind(this)} + underlineStyle={{width: 'calc(100% + 50px)'}} + /> + ); + } + private onValueChange(e: any, amountString: string) { + const errMsg = this.validate(amountString, this.props.balance); + this.setState({ + amountString, + errMsg, + }, () => { + const isValid = _.isUndefined(errMsg); + if (utils.isNumeric(amountString)) { + this.props.onChange(isValid, new BigNumber(amountString)); + } else { + this.props.onChange(isValid); + } + }); + } + private validate(amountString: string, balance: BigNumber): InputErrMsg { + if (!utils.isNumeric(amountString)) { + return amountString !== '' ? 'Must be a number' : ''; + } + const amount = new BigNumber(amountString); + if (amount.eq(0)) { + return 'Cannot be zero'; + } + if (this.props.shouldCheckBalance && amount.gt(balance)) { + return ( + + Insufficient balance.{' '} + {this.renderIncreaseBalanceLink()} + + ); + } + const errMsg = _.isUndefined(this.props.validate) ? undefined : this.props.validate(amount); + return errMsg; + } + private renderIncreaseBalanceLink() { + if (this.props.shouldHideVisitBalancesLink) { + return null; + } + + const increaseBalanceText = 'Increase balance'; + const linkStyle = { + cursor: 'pointer', + color: colors.grey900, + textDecoration: 'underline', + display: 'inline', + }; + if (_.isUndefined(this.props.onVisitBalancesPageClick)) { + return ( + + {increaseBalanceText} + + ); + } else { + return ( +
+ {increaseBalanceText} +
+ ); + } + } +} diff --git a/packages/website/ts/components/inputs/eth_amount_input.tsx b/packages/website/ts/components/inputs/eth_amount_input.tsx new file mode 100644 index 000000000..ad551e125 --- /dev/null +++ b/packages/website/ts/components/inputs/eth_amount_input.tsx @@ -0,0 +1,51 @@ +import BigNumber from 'bignumber.js'; +import * as _ from 'lodash'; +import * as React from 'react'; +import {ZeroEx} from '0x.js'; +import {ValidatedBigNumberCallback} from 'ts/types'; +import {BalanceBoundedInput} from 'ts/components/inputs/balance_bounded_input'; +import {constants} from 'ts/utils/constants'; + +interface EthAmountInputProps { + label?: string; + balance: BigNumber; + amount?: BigNumber; + onChange: ValidatedBigNumberCallback; + shouldShowIncompleteErrs: boolean; + onVisitBalancesPageClick?: () => void; + shouldCheckBalance: boolean; + shouldHideVisitBalancesLink?: boolean; +} + +interface EthAmountInputState {} + +export class EthAmountInput extends React.Component { + public render() { + const amount = this.props.amount ? + ZeroEx.toUnitAmount(this.props.amount, constants.ETH_DECIMAL_PLACES) : + undefined; + return ( +
+ +
+ ETH +
+
+ ); + } + private onChange(isValid: boolean, amount?: BigNumber) { + const baseUnitAmountIfExists = _.isUndefined(amount) ? + undefined : + ZeroEx.toBaseUnitAmount(amount, constants.ETH_DECIMAL_PLACES); + this.props.onChange(isValid, baseUnitAmountIfExists); + } +} diff --git a/packages/website/ts/components/inputs/expiration_input.tsx b/packages/website/ts/components/inputs/expiration_input.tsx new file mode 100644 index 000000000..32dcad189 --- /dev/null +++ b/packages/website/ts/components/inputs/expiration_input.tsx @@ -0,0 +1,108 @@ +import * as React from 'react'; +import * as _ from 'lodash'; +import DatePicker from 'material-ui/DatePicker'; +import TimePicker from 'material-ui/TimePicker'; +import {utils} from 'ts/utils/utils'; +import BigNumber from 'bignumber.js'; +import * as moment from 'moment'; + +interface ExpirationInputProps { + orderExpiryTimestamp: BigNumber; + updateOrderExpiry: (unixTimestampSec: BigNumber) => void; +} + +interface ExpirationInputState { + dateMoment: moment.Moment; + timeMoment: moment.Moment; +} + +export class ExpirationInput extends React.Component { + private earliestPickableMoment: moment.Moment; + constructor(props: ExpirationInputProps) { + super(props); + // Set the earliest pickable date to today at 00:00, so users can only pick the current or later dates + this.earliestPickableMoment = moment().startOf('day'); + const expirationMoment = utils.convertToMomentFromUnixTimestamp(props.orderExpiryTimestamp); + const initialOrderExpiryTimestamp = utils.initialOrderExpiryUnixTimestampSec(); + const didUserSetExpiry = !initialOrderExpiryTimestamp.eq(props.orderExpiryTimestamp); + this.state = { + dateMoment: didUserSetExpiry ? expirationMoment : undefined, + timeMoment: didUserSetExpiry ? expirationMoment : undefined, + }; + } + public render() { + const date = this.state.dateMoment ? this.state.dateMoment.toDate() : undefined; + const time = this.state.timeMoment ? this.state.timeMoment.toDate() : undefined; + return ( +
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+
+ ); + } + private shouldDisableDate(date: Date): boolean { + return moment(date).startOf('day').isBefore(this.earliestPickableMoment); + } + private clearDates() { + this.setState({ + dateMoment: undefined, + timeMoment: undefined, + }); + const defaultDateTime = utils.initialOrderExpiryUnixTimestampSec(); + this.props.updateOrderExpiry(defaultDateTime); + } + private onDateChanged(e: any, date: Date) { + const dateMoment = moment(date); + this.setState({ + dateMoment, + }); + const timestamp = utils.convertToUnixTimestampSeconds(dateMoment, this.state.timeMoment); + this.props.updateOrderExpiry(timestamp); + } + private onTimeChanged(e: any, time: Date) { + const timeMoment = moment(time); + this.setState({ + timeMoment, + }); + const dateMoment = _.isUndefined(this.state.dateMoment) ? moment() : this.state.dateMoment; + const timestamp = utils.convertToUnixTimestampSeconds(dateMoment, timeMoment); + this.props.updateOrderExpiry(timestamp); + } +} diff --git a/packages/website/ts/components/inputs/hash_input.tsx b/packages/website/ts/components/inputs/hash_input.tsx new file mode 100644 index 000000000..3e42f1d5f --- /dev/null +++ b/packages/website/ts/components/inputs/hash_input.tsx @@ -0,0 +1,65 @@ +import * as React from 'react'; +import {Blockchain} from 'ts/blockchain'; +import {ZeroEx, Order} from '0x.js'; +import {FakeTextField} from 'ts/components/ui/fake_text_field'; +import ReactTooltip = require('react-tooltip'); +import {HashData, Styles} from 'ts/types'; +import {constants} from 'ts/utils/constants'; + +const styles: Styles = { + textField: { + overflow: 'hidden', + paddingTop: 8, + textOverflow: 'ellipsis', + whiteSpace: 'nowrap', + }, +}; + +interface HashInputProps { + blockchain: Blockchain; + blockchainIsLoaded: boolean; + hashData: HashData; + label: string; +} + +interface HashInputState {} + +export class HashInput extends React.Component { + public render() { + const msgHashHex = this.props.blockchainIsLoaded ? this.generateMessageHashHex() : ''; + return ( +
+ +
+ {msgHashHex} +
+
+ {msgHashHex} +
+ ); + } + private generateMessageHashHex() { + const exchangeContractAddress = this.props.blockchain.getExchangeContractAddressIfExists(); + const hashData = this.props.hashData; + const order: Order = { + exchangeContractAddress, + expirationUnixTimestampSec: hashData.orderExpiryTimestamp, + feeRecipient: hashData.feeRecipientAddress, + maker: hashData.orderMakerAddress, + makerFee: hashData.makerFee, + makerTokenAddress: hashData.depositTokenContractAddr, + makerTokenAmount: hashData.depositAmount, + salt: hashData.orderSalt, + taker: hashData.orderTakerAddress, + takerFee: hashData.takerFee, + takerTokenAddress: hashData.receiveTokenContractAddr, + takerTokenAmount: hashData.receiveAmount, + }; + const orderHash = ZeroEx.getOrderHashHex(order); + return orderHash; + } +} diff --git a/packages/website/ts/components/inputs/identicon_address_input.tsx b/packages/website/ts/components/inputs/identicon_address_input.tsx new file mode 100644 index 000000000..6452f5fe9 --- /dev/null +++ b/packages/website/ts/components/inputs/identicon_address_input.tsx @@ -0,0 +1,56 @@ +import * as _ from 'lodash'; +import * as React from 'react'; +import {colors} from 'material-ui/styles'; +import {Blockchain} from 'ts/blockchain'; +import {Identicon} from 'ts/components/ui/identicon'; +import {RequiredLabel} from 'ts/components/ui/required_label'; +import {AddressInput} from 'ts/components/inputs/address_input'; +import {InputLabel} from 'ts/components/ui/input_label'; + +interface IdenticonAddressInputProps { + initialAddress: string; + isRequired?: boolean; + label: string; + updateOrderAddress: (address?: string) => void; +} + +interface IdenticonAddressInputState { + address: string; +} + +export class IdenticonAddressInput extends React.Component { + constructor(props: IdenticonAddressInputProps) { + super(props); + this.state = { + address: props.initialAddress, + }; + } + public render() { + const label = this.props.isRequired ? : + this.props.label; + return ( +
+ +
+
+ +
+
+ +
+
+
+ ); + } + private updateAddress(address?: string): void { + this.setState({ + address, + }); + this.props.updateOrderAddress(address); + } +} diff --git a/packages/website/ts/components/inputs/token_amount_input.tsx b/packages/website/ts/components/inputs/token_amount_input.tsx new file mode 100644 index 000000000..e19af8984 --- /dev/null +++ b/packages/website/ts/components/inputs/token_amount_input.tsx @@ -0,0 +1,69 @@ +import * as React from 'react'; +import * as _ from 'lodash'; +import BigNumber from 'bignumber.js'; +import {ZeroEx} from '0x.js'; +import {Link} from 'react-router-dom'; +import {colors} from 'material-ui/styles'; +import {Token, TokenState, InputErrMsg, ValidatedBigNumberCallback, WebsitePaths} from 'ts/types'; +import {BalanceBoundedInput} from 'ts/components/inputs/balance_bounded_input'; + +interface TokenAmountInputProps { + label: string; + token: Token; + tokenState: TokenState; + amount?: BigNumber; + shouldShowIncompleteErrs: boolean; + shouldCheckBalance: boolean; + shouldCheckAllowance: boolean; + onChange: ValidatedBigNumberCallback; + onVisitBalancesPageClick?: () => void; +} + +interface TokenAmountInputState {} + +export class TokenAmountInput extends React.Component { + public render() { + const amount = this.props.amount ? + ZeroEx.toUnitAmount(this.props.amount, this.props.token.decimals) : + undefined; + return ( +
+ +
+ {this.props.token.symbol} +
+
+ ); + } + private onChange(isValid: boolean, amount?: BigNumber) { + let baseUnitAmount; + if (!_.isUndefined(amount)) { + baseUnitAmount = ZeroEx.toBaseUnitAmount(amount, this.props.token.decimals); + } + this.props.onChange(isValid, baseUnitAmount); + } + private validate(amount: BigNumber): InputErrMsg { + if (this.props.shouldCheckAllowance && amount.gt(this.props.tokenState.allowance)) { + return ( + + Insufficient allowance.{' '} + + Set allowance + + + ); + } + } +} diff --git a/packages/website/ts/components/inputs/token_input.tsx b/packages/website/ts/components/inputs/token_input.tsx new file mode 100644 index 000000000..2be74d4fd --- /dev/null +++ b/packages/website/ts/components/inputs/token_input.tsx @@ -0,0 +1,107 @@ +import * as _ from 'lodash'; +import * as React from 'react'; +import Paper from 'material-ui/Paper'; +import {colors} from 'material-ui/styles'; +import {Blockchain} from 'ts/blockchain'; +import {Dispatcher} from 'ts/redux/dispatcher'; +import {AssetToken, Side, TokenByAddress, BlockchainErrs, Token, TokenState} from 'ts/types'; +import {AssetPicker} from 'ts/components/generate_order/asset_picker'; +import {InputLabel} from 'ts/components/ui/input_label'; +import {TokenIcon} from 'ts/components/ui/token_icon'; + +const TOKEN_ICON_DIMENSION = 80; + +interface TokenInputProps { + blockchain: Blockchain; + blockchainErr: BlockchainErrs; + dispatcher: Dispatcher; + label: string; + side: Side; + networkId: number; + assetToken: AssetToken; + updateChosenAssetToken: (side: Side, token: AssetToken) => void; + tokenByAddress: TokenByAddress; + userAddress: string; +} + +interface TokenInputState { + isHoveringIcon: boolean; + isPickerOpen: boolean; + trackCandidateTokenIfExists?: Token; +} + +export class TokenInput extends React.Component { + constructor(props: TokenInputProps) { + super(props); + this.state = { + isHoveringIcon: false, + isPickerOpen: false, + }; + } + public render() { + const token = this.props.tokenByAddress[this.props.assetToken.address]; + const iconStyles = { + cursor: 'pointer', + opacity: this.state.isHoveringIcon ? 0.5 : 1, + }; + return ( +
+
+ +
+ +
+ +
+
+ {token.name} +
+
+ +
+ ); + } + private onTokenChosen(tokenAddress: string) { + const assetToken: AssetToken = { + address: tokenAddress, + amount: this.props.assetToken.amount, + }; + this.props.updateChosenAssetToken(this.props.side, assetToken); + this.setState({ + isPickerOpen: false, + }); + } + private onToggleHover(isHoveringIcon: boolean) { + this.setState({ + isHoveringIcon, + }); + } + private onAssetClicked() { + if (this.props.blockchainErr !== '') { + this.props.dispatcher.updateShouldBlockchainErrDialogBeOpen(true); + return; + } + + this.setState({ + isPickerOpen: true, + }); + } +} diff --git a/packages/website/ts/components/order_json.tsx b/packages/website/ts/components/order_json.tsx new file mode 100644 index 000000000..90e3543dd --- /dev/null +++ b/packages/website/ts/components/order_json.tsx @@ -0,0 +1,164 @@ +import * as _ from 'lodash'; +import * as React from 'react'; +import BigNumber from 'bignumber.js'; +import {utils} from 'ts/utils/utils'; +import {colors} from 'material-ui/styles'; +import {constants} from 'ts/utils/constants'; +import {configs} from 'ts/utils/configs'; +import TextField from 'material-ui/TextField'; +import Paper from 'material-ui/Paper'; +import {CopyIcon} from 'ts/components/ui/copy_icon'; +import {SideToAssetToken, SignatureData, Order, TokenByAddress, WebsitePaths} from 'ts/types'; +import {errorReporter} from 'ts/utils/error_reporter'; + +interface OrderJSONProps { + exchangeContractIfExists: string; + orderExpiryTimestamp: BigNumber; + orderSignatureData: SignatureData; + orderTakerAddress: string; + orderMakerAddress: string; + orderSalt: BigNumber; + orderMakerFee: BigNumber; + orderTakerFee: BigNumber; + orderFeeRecipient: string; + networkId: number; + sideToAssetToken: SideToAssetToken; + tokenByAddress: TokenByAddress; +} + +interface OrderJSONState { + shareLink: string; +} + +export class OrderJSON extends React.Component { + constructor(props: OrderJSONProps) { + super(props); + this.state = { + shareLink: '', + }; + this.setShareLinkAsync(); + } + public render() { + const order = utils.generateOrder(this.props.networkId, this.props.exchangeContractIfExists, + this.props.sideToAssetToken, this.props.orderExpiryTimestamp, + this.props.orderTakerAddress, this.props.orderMakerAddress, + this.props.orderMakerFee, this.props.orderTakerFee, + this.props.orderFeeRecipient, this.props.orderSignatureData, + this.props.tokenByAddress, this.props.orderSalt); + const orderJSON = JSON.stringify(order); + return ( +
+
+ You have successfully generated and cryptographically signed an order! The{' '} + following JSON contains the order parameters and cryptographic signature that{' '} + your counterparty will need to execute a trade with you. +
+
+
+ +
+
+ + + +
+
+ Share your signed order! +
+
+
+ +
+
+
+
+ +
+
+ +
+
+ +
+
+
+
+ ); + } + private async shareViaTwitterAsync() { + const tweetText = encodeURIComponent(`Fill my order using the 0x protocol: ${this.state.shareLink}`); + window.open(`https://twitter.com/intent/tweet?text=${tweetText}`, 'Share your order', 'width=500,height=400'); + } + private async shareViaFacebook() { + (window as any).FB.ui({ + display: 'popup', + href: this.state.shareLink, + method: 'share', + }, _.noop); + } + private async shareViaEmailAsync() { + const encodedSubject = encodeURIComponent('Let\'s trade using the 0x protocol'); + const encodedBody = encodeURIComponent(`I generated an order with the 0x protocol. +You can see and fill it here: ${this.state.shareLink}`); + const mailToLink = `mailto:mail@example.org?subject=${encodedSubject}&body=${encodedBody}`; + window.open(mailToLink, '_blank'); + } + private async setShareLinkAsync() { + const shareLink = await this.generateShareLinkAsync(); + this.setState({ + shareLink, + }); + } + private async generateShareLinkAsync(): Promise { + const longUrl = encodeURIComponent(this.getOrderUrl()); + const bitlyRequestUrl = constants.BITLY_ENDPOINT + '/v3/shorten?' + + 'access_token=' + constants.BITLY_ACCESS_TOKEN + + '&longUrl=' + longUrl; + const response = await fetch(bitlyRequestUrl); + const responseBody = await response.text(); + const bodyObj = JSON.parse(responseBody); + if (response.status !== 200 || bodyObj.status_code !== 200) { + // TODO: Show error message in UI + utils.consoleLog(`Unexpected status code: ${response.status} -> ${responseBody}`); + await errorReporter.reportAsync(new Error(`Bitly returned non-200: ${JSON.stringify(response)}`)); + return ''; + } + return (bodyObj as any).data.url; + } + private getOrderUrl() { + const order = utils.generateOrder(this.props.networkId, this.props.exchangeContractIfExists, + this.props.sideToAssetToken, this.props.orderExpiryTimestamp, this.props.orderTakerAddress, + this.props.orderMakerAddress, this.props.orderMakerFee, this.props.orderTakerFee, + this.props.orderFeeRecipient, this.props.orderSignatureData, this.props.tokenByAddress, + this.props.orderSalt); + const orderJSONString = JSON.stringify(order); + const orderUrl = `${configs.BASE_URL}${WebsitePaths.Portal}/fill?order=${orderJSONString}`; + return orderUrl; + } +} diff --git a/packages/website/ts/components/portal.tsx b/packages/website/ts/components/portal.tsx new file mode 100644 index 000000000..3591a347b --- /dev/null +++ b/packages/website/ts/components/portal.tsx @@ -0,0 +1,344 @@ +import * as _ from 'lodash'; +import * as React from 'react'; +import * as DocumentTitle from 'react-document-title'; +import {Switch, Route} from 'react-router-dom'; +import {Dispatcher} from 'ts/redux/dispatcher'; +import {State} from 'ts/redux/reducer'; +import {utils} from 'ts/utils/utils'; +import {configs} from 'ts/utils/configs'; +import {constants} from 'ts/utils/constants'; +import Paper from 'material-ui/Paper'; +import RaisedButton from 'material-ui/RaisedButton'; +import {colors} from 'material-ui/styles'; +import {GenerateOrderForm} from 'ts/containers/generate_order_form'; +import {TokenBalances} from 'ts/components/token_balances'; +import {PortalDisclaimerDialog} from 'ts/components/dialogs/portal_disclaimer_dialog'; +import {FillOrder} from 'ts/components/fill_order'; +import {Blockchain} from 'ts/blockchain'; +import {SchemaValidator} from 'ts/schemas/validator'; +import {orderSchema} from 'ts/schemas/order_schema'; +import {localStorage} from 'ts/local_storage/local_storage'; +import {TradeHistory} from 'ts/components/trade_history/trade_history'; +import { + HashData, + TokenByAddress, + BlockchainErrs, + Order, + Fill, + Side, + Styles, + ScreenWidths, + Token, + TokenStateByAddress, + WebsitePaths, +} from 'ts/types'; +import {TopBar} from 'ts/components/top_bar'; +import {Footer} from 'ts/components/footer'; +import {Loading} from 'ts/components/ui/loading'; +import {PortalMenu} from 'ts/components/portal_menu'; +import {BlockchainErrDialog} from 'ts/components/dialogs/blockchain_err_dialog'; +import BigNumber from 'bignumber.js'; +import {FlashMessage} from 'ts/components/ui/flash_message'; + +const THROTTLE_TIMEOUT = 100; + +export interface PortalPassedProps {} + +export interface PortalAllProps { + blockchainErr: BlockchainErrs; + blockchainIsLoaded: boolean; + dispatcher: Dispatcher; + hashData: HashData; + networkId: number; + nodeVersion: string; + orderFillAmount: BigNumber; + screenWidth: ScreenWidths; + tokenByAddress: TokenByAddress; + tokenStateByAddress: TokenStateByAddress; + userEtherBalance: BigNumber; + userAddress: string; + shouldBlockchainErrDialogBeOpen: boolean; + userSuppliedOrderCache: Order; + location: Location; + flashMessage?: string|React.ReactNode; +} + +interface PortalAllState { + prevNetworkId: number; + prevNodeVersion: string; + prevUserAddress: string; + hasAcceptedDisclaimer: boolean; +} + +const styles: Styles = { + button: { + color: 'white', + }, + headline: { + fontSize: 20, + fontWeight: 400, + marginBottom: 12, + paddingTop: 16, + }, + inkBar: { + background: colors.amber600, + }, + menuItem: { + padding: '0px 16px 0px 48px', + }, + tabItemContainer: { + background: colors.blueGrey500, + borderRadius: '4px 4px 0 0', + }, +}; + +export class Portal extends React.Component { + private blockchain: Blockchain; + private sharedOrderIfExists: Order; + private throttledScreenWidthUpdate: () => void; + constructor(props: PortalAllProps) { + super(props); + this.sharedOrderIfExists = this.getSharedOrderIfExists(); + this.throttledScreenWidthUpdate = _.throttle(this.updateScreenWidth.bind(this), THROTTLE_TIMEOUT); + this.state = { + prevNetworkId: this.props.networkId, + prevNodeVersion: this.props.nodeVersion, + prevUserAddress: this.props.userAddress, + hasAcceptedDisclaimer: false, + }; + } + public componentDidMount() { + window.addEventListener('resize', this.throttledScreenWidthUpdate); + window.scrollTo(0, 0); + } + public componentWillMount() { + this.blockchain = new Blockchain(this.props.dispatcher); + const didAcceptPortalDisclaimer = localStorage.getItemIfExists(constants.ACCEPT_DISCLAIMER_LOCAL_STORAGE_KEY); + const hasAcceptedDisclaimer = !_.isUndefined(didAcceptPortalDisclaimer) && + !_.isEmpty(didAcceptPortalDisclaimer); + this.setState({ + hasAcceptedDisclaimer, + }); + } + public componentWillUnmount() { + this.blockchain.destroy(); + window.removeEventListener('resize', this.throttledScreenWidthUpdate); + // We re-set the entire redux state when the portal is unmounted so that when it is re-rendered + // the initialization process always occurs from the same base state. This helps avoid + // initialization inconsistencies (i.e While the portal was unrendered, the user might have + // become disconnected from their backing Ethereum node, changes user accounts, etc...) + this.props.dispatcher.resetState(); + } + public componentWillReceiveProps(nextProps: PortalAllProps) { + if (nextProps.networkId !== this.state.prevNetworkId) { + this.blockchain.networkIdUpdatedFireAndForgetAsync(nextProps.networkId); + this.setState({ + prevNetworkId: nextProps.networkId, + }); + } + if (nextProps.userAddress !== this.state.prevUserAddress) { + this.blockchain.userAddressUpdatedFireAndForgetAsync(nextProps.userAddress); + if (!_.isEmpty(nextProps.userAddress) && + nextProps.blockchainIsLoaded) { + const tokens = _.values(nextProps.tokenByAddress); + this.updateBalanceAndAllowanceWithLoadingScreenAsync(tokens); + } + this.setState({ + prevUserAddress: nextProps.userAddress, + }); + } + if (nextProps.nodeVersion !== this.state.prevNodeVersion) { + this.blockchain.nodeVersionUpdatedFireAndForgetAsync(nextProps.nodeVersion); + } + } + public render() { + const updateShouldBlockchainErrDialogBeOpen = this.props.dispatcher + .updateShouldBlockchainErrDialogBeOpen.bind(this.props.dispatcher); + const portalStyle: React.CSSProperties = { + minHeight: '100vh', + display: 'flex', + flexDirection: 'column', + justifyContent: 'space-between', + }; + return ( +
+ + +
+ + {!configs.isMainnetEnabled && this.props.networkId === constants.MAINNET_NETWORK_ID ? +
+
Mainnet unavailable
+
+ +
+
+ 0x portal is currently unavailable on the Ethereum mainnet. +
+ To try it out, switch to the Kovan test network + (networkId: 42). +
+
+ Check back soon! +
+
+
: +
+
+ +
+
+
+ {this.props.blockchainIsLoaded ? + + + + + + : + + } +
+
+
+ } +
+ + + +
+
+
+ ); + } + private renderTradeHistory() { + return ( + + ); + } + private renderTokenBalances() { + return ( + + ); + } + private renderFillOrder(match: any, location: Location, history: History) { + const initialFillOrder = !_.isUndefined(this.props.userSuppliedOrderCache) ? + this.props.userSuppliedOrderCache : + this.sharedOrderIfExists; + return ( + + ); + } + private renderGenerateOrderForm(match: any, location: Location, history: History) { + return ( + + ); + } + private onPortalDisclaimerAccepted() { + localStorage.setItem(constants.ACCEPT_DISCLAIMER_LOCAL_STORAGE_KEY, 'set'); + this.setState({ + hasAcceptedDisclaimer: true, + }); + } + private getSharedOrderIfExists(): Order { + const queryString = window.location.search; + if (queryString.length === 0) { + return; + } + const queryParams = queryString.substring(1).split('&'); + const orderQueryParam = _.find(queryParams, queryParam => { + const queryPair = queryParam.split('='); + return queryPair[0] === 'order'; + }); + if (_.isUndefined(orderQueryParam)) { + return; + } + const orderPair = orderQueryParam.split('='); + if (orderPair.length !== 2) { + return; + } + + const validator = new SchemaValidator(); + const order = JSON.parse(decodeURIComponent(orderPair[1])); + const validationResult = validator.validate(order, orderSchema); + if (validationResult.errors.length > 0) { + utils.consoleLog(`Invalid shared order: ${validationResult.errors}`); + return; + } + return order; + } + private updateScreenWidth() { + const newScreenWidth = utils.getScreenWidth(); + this.props.dispatcher.updateScreenWidth(newScreenWidth); + } + private async updateBalanceAndAllowanceWithLoadingScreenAsync(tokens: Token[]) { + this.props.dispatcher.updateBlockchainIsLoaded(false); + await this.blockchain.updateTokenBalancesAndAllowancesAsync(tokens); + this.props.dispatcher.updateBlockchainIsLoaded(true); + } +} diff --git a/packages/website/ts/components/portal_menu.tsx b/packages/website/ts/components/portal_menu.tsx new file mode 100644 index 000000000..3b3641729 --- /dev/null +++ b/packages/website/ts/components/portal_menu.tsx @@ -0,0 +1,68 @@ +import * as _ from 'lodash'; +import * as React from 'react'; +import {MenuItem} from 'ts/components/ui/menu_item'; +import {Link} from 'react-router-dom'; +import {WebsitePaths} from 'ts/types'; + +export interface PortalMenuProps { + menuItemStyle: React.CSSProperties; + onClick?: () => void; +} + +interface PortalMenuState {} + +export class PortalMenu extends React.Component { + public static defaultProps: Partial = { + onClick: _.noop, + }; + public render() { + return ( +
+ + {this.renderMenuItemWithIcon('Generate order', 'zmdi-arrow-right-top')} + + + {this.renderMenuItemWithIcon('Fill order', 'zmdi-arrow-left-bottom')} + + + {this.renderMenuItemWithIcon('Balances', 'zmdi-balance-wallet')} + + + {this.renderMenuItemWithIcon('Trade history', 'zmdi-format-list-bulleted')} + +
+ ); + } + private renderMenuItemWithIcon(title: string, iconName: string) { + return ( +
+
+ +
+
+ {title} +
+
+ ); + } +} diff --git a/packages/website/ts/components/send_button.tsx b/packages/website/ts/components/send_button.tsx new file mode 100644 index 000000000..274ba96a7 --- /dev/null +++ b/packages/website/ts/components/send_button.tsx @@ -0,0 +1,89 @@ +import * as _ from 'lodash'; +import {ZeroEx} from '0x.js'; +import * as React from 'react'; +import BigNumber from 'bignumber.js'; +import RaisedButton from 'material-ui/RaisedButton'; +import {BlockchainCallErrs, Token, TokenState} from 'ts/types'; +import {SendDialog} from 'ts/components/dialogs/send_dialog'; +import {constants} from 'ts/utils/constants'; +import {utils} from 'ts/utils/utils'; +import {Dispatcher} from 'ts/redux/dispatcher'; +import {errorReporter} from 'ts/utils/error_reporter'; +import {Blockchain} from 'ts/blockchain'; + +interface SendButtonProps { + token: Token; + tokenState: TokenState; + dispatcher: Dispatcher; + blockchain: Blockchain; + onError: () => void; +} + +interface SendButtonState { + isSendDialogVisible: boolean; + isSending: boolean; +} + +export class SendButton extends React.Component { + public constructor(props: SendButtonProps) { + super(props); + this.state = { + isSendDialogVisible: false, + isSending: false, + }; + } + public render() { + const labelStyle = this.state.isSending ? {fontSize: 10} : {}; + return ( +
+ + +
+ ); + } + private toggleSendDialog() { + this.setState({ + isSendDialogVisible: !this.state.isSendDialogVisible, + }); + } + private async onSendAmountSelectedAsync(recipient: string, value: BigNumber) { + this.setState({ + isSending: true, + }); + this.toggleSendDialog(); + const token = this.props.token; + const tokenState = this.props.tokenState; + let balance = tokenState.balance; + try { + await this.props.blockchain.transferAsync(token, recipient, value); + balance = balance.minus(value); + this.props.dispatcher.replaceTokenBalanceByAddress(token.address, balance); + } catch (err) { + const errMsg = `${err}`; + if (_.includes(errMsg, BlockchainCallErrs.USER_HAS_NO_ASSOCIATED_ADDRESSES)) { + this.props.dispatcher.updateShouldBlockchainErrDialogBeOpen(true); + return; + } else if (!_.includes(errMsg, 'User denied transaction')) { + utils.consoleLog(`Unexpected error encountered: ${err}`); + utils.consoleLog(err.stack); + await errorReporter.reportAsync(err); + this.props.onError(); + } + } + this.setState({ + isSending: false, + }); + } +} diff --git a/packages/website/ts/components/token_balances.tsx b/packages/website/ts/components/token_balances.tsx new file mode 100644 index 000000000..c552d19dc --- /dev/null +++ b/packages/website/ts/components/token_balances.tsx @@ -0,0 +1,697 @@ +import * as _ from 'lodash'; +import * as React from 'react'; +import {ZeroEx} from '0x.js'; +import DharmaLoanFrame from 'dharma-loan-frame'; +import {colors} from 'material-ui/styles'; +import Dialog from 'material-ui/Dialog'; +import Divider from 'material-ui/Divider'; +import FlatButton from 'material-ui/FlatButton'; +import RaisedButton from 'material-ui/RaisedButton'; +import FloatingActionButton from 'material-ui/FloatingActionButton'; +import ContentAdd from 'material-ui/svg-icons/content/add'; +import ContentRemove from 'material-ui/svg-icons/content/remove'; +import { + Table, + TableBody, + TableHeader, + TableRow, + TableHeaderColumn, + TableRowColumn, +} from 'material-ui/Table'; +import ReactTooltip = require('react-tooltip'); +import BigNumber from 'bignumber.js'; +import firstBy = require('thenby'); +import QueryString = require('query-string'); +import {Dispatcher} from 'ts/redux/dispatcher'; +import { + TokenByAddress, + TokenStateByAddress, + Token, + BlockchainErrs, + BalanceErrs, + Styles, + ScreenWidths, + EtherscanLinkSuffixes, + BlockchainCallErrs, + TokenVisibility, +} from 'ts/types'; +import {Blockchain} from 'ts/blockchain'; +import {utils} from 'ts/utils/utils'; +import {constants} from 'ts/utils/constants'; +import {configs} from 'ts/utils/configs'; +import {LifeCycleRaisedButton} from 'ts/components/ui/lifecycle_raised_button'; +import {HelpTooltip} from 'ts/components/ui/help_tooltip'; +import {errorReporter} from 'ts/utils/error_reporter'; +import {AllowanceToggle} from 'ts/components/inputs/allowance_toggle'; +import {EthWethConversionButton} from 'ts/components/eth_weth_conversion_button'; +import {SendButton} from 'ts/components/send_button'; +import {AssetPicker} from 'ts/components/generate_order/asset_picker'; +import {TokenIcon} from 'ts/components/ui/token_icon'; +import {trackedTokenStorage} from 'ts/local_storage/tracked_token_storage'; + +const ETHER_ICON_PATH = '/images/ether.png'; +const ETHER_TOKEN_SYMBOL = 'WETH'; +const ZRX_TOKEN_SYMBOL = 'ZRX'; + +const PRECISION = 5; +const ICON_DIMENSION = 40; +const ARTIFICIAL_FAUCET_REQUEST_DELAY = 1000; +const TOKEN_TABLE_ROW_HEIGHT = 60; +const MAX_TOKEN_TABLE_HEIGHT = 420; +const TOKEN_COL_SPAN_LG = 2; +const TOKEN_COL_SPAN_SM = 1; + +const styles: Styles = { + bgColor: { + backgroundColor: colors.grey50, + }, +}; + +interface TokenBalancesProps { + blockchain: Blockchain; + blockchainErr: BlockchainErrs; + blockchainIsLoaded: boolean; + dispatcher: Dispatcher; + screenWidth: ScreenWidths; + tokenByAddress: TokenByAddress; + tokenStateByAddress: TokenStateByAddress; + userAddress: string; + userEtherBalance: BigNumber; + networkId: number; +} + +interface TokenBalancesState { + errorType: BalanceErrs; + isBalanceSpinnerVisible: boolean; + isDharmaDialogVisible: boolean; + isZRXSpinnerVisible: boolean; + currentZrxBalance?: BigNumber; + isTokenPickerOpen: boolean; + isAddingToken: boolean; +} + +export class TokenBalances extends React.Component { + public constructor(props: TokenBalancesProps) { + super(props); + this.state = { + errorType: undefined, + isBalanceSpinnerVisible: false, + isZRXSpinnerVisible: false, + isDharmaDialogVisible: DharmaLoanFrame.isAuthTokenPresent(), + isTokenPickerOpen: false, + isAddingToken: false, + }; + } + public componentWillReceiveProps(nextProps: TokenBalancesProps) { + if (nextProps.userEtherBalance !== this.props.userEtherBalance) { + if (this.state.isBalanceSpinnerVisible) { + const receivedAmount = nextProps.userEtherBalance.minus(this.props.userEtherBalance); + this.props.dispatcher.showFlashMessage(`Received ${receivedAmount.toString(10)} Kovan Ether`); + } + this.setState({ + isBalanceSpinnerVisible: false, + }); + } + const nextZrxToken = _.find(_.values(nextProps.tokenByAddress), t => t.symbol === ZRX_TOKEN_SYMBOL); + const nextZrxTokenBalance = nextProps.tokenStateByAddress[nextZrxToken.address].balance; + if (!_.isUndefined(this.state.currentZrxBalance) && !nextZrxTokenBalance.eq(this.state.currentZrxBalance)) { + if (this.state.isZRXSpinnerVisible) { + const receivedAmount = nextZrxTokenBalance.minus(this.state.currentZrxBalance); + const receiveAmountInUnits = ZeroEx.toUnitAmount(receivedAmount, 18); + this.props.dispatcher.showFlashMessage(`Received ${receiveAmountInUnits.toString(10)} Kovan ZRX`); + } + this.setState({ + isZRXSpinnerVisible: false, + currentZrxBalance: undefined, + }); + } + } + public componentDidMount() { + window.scrollTo(0, 0); + } + public render() { + const errorDialogActions = [ + , + ]; + const dharmaDialogActions = [ + , + ]; + const isTestNetwork = this.props.networkId === constants.TESTNET_NETWORK_ID; + const dharmaButtonColumnStyle = { + paddingLeft: 3, + display: isTestNetwork ? 'table-cell' : 'none', + }; + const stubColumnStyle = { + display: isTestNetwork ? 'none' : 'table-cell', + }; + const allTokenRowHeight = _.size(this.props.tokenByAddress) * TOKEN_TABLE_ROW_HEIGHT; + const tokenTableHeight = allTokenRowHeight < MAX_TOKEN_TABLE_HEIGHT ? + allTokenRowHeight : + MAX_TOKEN_TABLE_HEIGHT; + const isSmallScreen = this.props.screenWidth === ScreenWidths.SM; + const tokenColSpan = isSmallScreen ? TOKEN_COL_SPAN_SM : TOKEN_COL_SPAN_LG; + const dharmaLoanExplanation = 'If you need access to larger amounts of ether,
\ + you can request a loan from the Dharma Loan
\ + network. Your loan should be funded in 5
\ + minutes or less.'; + const allowanceExplanation = '0x smart contracts require access to your
\ + token balances in order to execute trades.
\ + Toggling permissions sets an allowance for the
\ + smart contract so you can start trading that token.'; + return ( +
+

{isTestNetwork ? 'Test ether' : 'Ether'}

+ +
+ {isTestNetwork ? + 'In order to try out the 0x Portal Dapp, request some test ether to pay for \ + gas costs. It might take a bit of time for the test ether to show up.' : + 'Ether must be converted to Ether Tokens in order to be tradable via 0x. \ + You can convert between Ether and Ether Tokens by clicking the "convert" button below.' + } +
+ + + + Currency + Balance + + { + isTestNetwork && + + {isSmallScreen ? 'Faucet' : 'Request from faucet'} + + } + { + isTestNetwork && + + {isSmallScreen ? 'Loan' : 'Request Dharma loan'} + + + } + + + + + + + + + {this.props.userEtherBalance.toFixed(PRECISION)} ETH + {this.state.isBalanceSpinnerVisible && + + + + } + + + { + isTestNetwork && + + + + } + { + isTestNetwork && + + + + } + + +
+
+
+

+ {isTestNetwork ? 'Test tokens' : 'Tokens'} +

+
+
+ + + +
+
+ + + +
+
+ +
+ {isTestNetwork ? + 'Mint some test tokens you\'d like to use to generate or fill an order using 0x.' : + 'Set trading permissions for a token you\'d like to start trading.' + } +
+ + + + + Token + + Balance + +
{!isSmallScreen && 'Trade '}Permissions
+ +
+ + Action + + {this.props.screenWidth !== ScreenWidths.SM && + + Send + + } +
+
+ + {this.renderTokenTableRows()} + +
+ + {this.renderErrorDialogBody()} + + + {this.renderDharmaLoanFrame()} + + +
+ ); + } + private renderTokenTableRows() { + if (!this.props.blockchainIsLoaded || this.props.blockchainErr !== '') { + return ''; + } + const isSmallScreen = this.props.screenWidth === ScreenWidths.SM; + const tokenColSpan = isSmallScreen ? TOKEN_COL_SPAN_SM : TOKEN_COL_SPAN_LG; + const actionPaddingX = isSmallScreen ? 2 : 24; + const allTokens = _.values(this.props.tokenByAddress); + const trackedTokens = _.filter(allTokens, t => t.isTracked); + const trackedTokensStartingWithEtherToken = trackedTokens.sort( + firstBy((t: Token) => (t.symbol !== ETHER_TOKEN_SYMBOL)) + .thenBy((t: Token) => (t.symbol !== ZRX_TOKEN_SYMBOL)) + .thenBy('address'), + ); + const tableRows = _.map( + trackedTokensStartingWithEtherToken, + this.renderTokenRow.bind(this, tokenColSpan, actionPaddingX), + ); + return tableRows; + } + private renderTokenRow(tokenColSpan: number, actionPaddingX: number, token: Token) { + const tokenState = this.props.tokenStateByAddress[token.address]; + const tokenLink = utils.getEtherScanLinkIfExists(token.address, this.props.networkId, + EtherscanLinkSuffixes.address); + const isMintable = _.includes(configs.symbolsOfMintableTokens, token.symbol) && + this.props.networkId !== constants.MAINNET_NETWORK_ID; + return ( + + + {_.isUndefined(tokenLink) ? + this.renderTokenName(token) : + + {this.renderTokenName(token)} + + } + + + {this.renderAmount(tokenState.balance, token.decimals)} {token.symbol} + {this.state.isZRXSpinnerVisible && token.symbol === ZRX_TOKEN_SYMBOL && + + + + } + + + + + + {isMintable && + Minting...} + labelComplete="Minted!" + onClickAsyncFn={this.onMintTestTokensAsync.bind(this, token)} + /> + } + {token.symbol === ETHER_TOKEN_SYMBOL && + + } + {token.symbol === ZRX_TOKEN_SYMBOL && this.props.networkId === constants.TESTNET_NETWORK_ID && + + } + + {this.props.screenWidth !== ScreenWidths.SM && + + + + } + + ); + } + private onAssetTokenPicked(tokenAddress: string) { + if (_.isEmpty(tokenAddress)) { + this.setState({ + isTokenPickerOpen: false, + }); + return; + } + const token = this.props.tokenByAddress[tokenAddress]; + const isDefaultTrackedToken = _.includes(configs.defaultTrackedTokenSymbols, token.symbol); + if (!this.state.isAddingToken && !isDefaultTrackedToken) { + if (token.isRegistered) { + // Remove the token from tracked tokens + const newToken = _.assign({}, token, { + isTracked: false, + }); + this.props.dispatcher.updateTokenByAddress([newToken]); + } else { + this.props.dispatcher.removeTokenToTokenByAddress(token); + } + this.props.dispatcher.removeFromTokenStateByAddress(tokenAddress); + trackedTokenStorage.removeTrackedToken(this.props.userAddress, this.props.networkId, tokenAddress); + } else if (isDefaultTrackedToken) { + this.props.dispatcher.showFlashMessage(`Cannot remove ${token.name} because it's a default token`); + } + this.setState({ + isTokenPickerOpen: false, + }); + } + private onEthWethConversionFailed() { + this.setState({ + errorType: BalanceErrs.wethConversionFailed, + }); + } + private onSendFailed() { + this.setState({ + errorType: BalanceErrs.sendFailed, + }); + } + private renderAmount(amount: BigNumber, decimals: number) { + const unitAmount = ZeroEx.toUnitAmount(amount, decimals); + return unitAmount.toNumber().toFixed(PRECISION); + } + private renderTokenName(token: Token) { + const tooltipId = `tooltip-${token.address}`; + return ( +
+ +
+ {token.name} +
+ {token.address} +
+ ); + } + private renderErrorDialogBody() { + switch (this.state.errorType) { + case BalanceErrs.incorrectNetworkForFaucet: + return ( +
+ Our faucet can only send test Ether to addresses on the {constants.TESTNET_NAME} + {' '}testnet (networkId {constants.TESTNET_NETWORK_ID}). Please make sure you are + {' '}connected to the {constants.TESTNET_NAME} testnet and try requesting ether again. +
+ ); + + case BalanceErrs.faucetRequestFailed: + return ( +
+ An unexpected error occurred while trying to request test Ether from our faucet. + {' '}Please refresh the page and try again. +
+ ); + + case BalanceErrs.faucetQueueIsFull: + return ( +
+ Our test Ether faucet queue is full. Please try requesting test Ether again later. +
+ ); + + case BalanceErrs.mintingFailed: + return ( +
+ Minting your test tokens failed unexpectedly. Please refresh the page and try again. +
+ ); + + case BalanceErrs.wethConversionFailed: + return ( +
+ Converting between Ether and Ether Tokens failed unexpectedly. + Please refresh the page and try again. +
+ ); + + case BalanceErrs.allowanceSettingFailed: + return ( +
+ An unexpected error occurred while trying to set your test token allowance. + {' '}Please refresh the page and try again. +
+ ); + + case undefined: + return null; // No error to show + + default: + throw utils.spawnSwitchErr('errorType', this.state.errorType); + } + } + private renderDharmaLoanFrame() { + if (utils.isUserOnMobile()) { + return ( +

+ We apologize -- Dharma loan requests are not available on + mobile yet. Please try again through your desktop browser. +

+ ); + } else { + return ( + + ); + } + } + private onErrorOccurred(errorType: BalanceErrs) { + this.setState({ + errorType, + }); + } + private async onMintTestTokensAsync(token: Token): Promise { + try { + await this.props.blockchain.mintTestTokensAsync(token); + const amount = ZeroEx.toUnitAmount(constants.MINT_AMOUNT, token.decimals); + this.props.dispatcher.showFlashMessage(`Successfully minted ${amount.toString(10)} ${token.symbol}`); + return true; + } catch (err) { + const errMsg = '' + err; + if (_.includes(errMsg, BlockchainCallErrs.USER_HAS_NO_ASSOCIATED_ADDRESSES)) { + this.props.dispatcher.updateShouldBlockchainErrDialogBeOpen(true); + return false; + } + if (_.includes(errMsg, 'User denied transaction')) { + return false; + } + utils.consoleLog(`Unexpected error encountered: ${err}`); + utils.consoleLog(err.stack); + await errorReporter.reportAsync(err); + this.setState({ + errorType: BalanceErrs.mintingFailed, + }); + return false; + } + } + private async faucetRequestAsync(isEtherRequest: boolean): Promise { + if (this.props.userAddress === '') { + this.props.dispatcher.updateShouldBlockchainErrDialogBeOpen(true); + return false; + } + + // If on another network other then the testnet our faucet serves test ether + // from, we must show user an error message + if (this.props.blockchain.networkId !== constants.TESTNET_NETWORK_ID) { + this.setState({ + errorType: BalanceErrs.incorrectNetworkForFaucet, + }); + return false; + } + + await utils.sleepAsync(ARTIFICIAL_FAUCET_REQUEST_DELAY); + + const segment = isEtherRequest ? 'ether' : 'zrx'; + const response = await fetch(`${constants.ETHER_FAUCET_ENDPOINT}/${segment}/${this.props.userAddress}`); + const responseBody = await response.text(); + if (response.status !== constants.SUCCESS_STATUS) { + utils.consoleLog(`Unexpected status code: ${response.status} -> ${responseBody}`); + await errorReporter.reportAsync(new Error(`Faucet returned non-200: ${JSON.stringify(response)}`)); + const errorType = response.status === constants.UNAVAILABLE_STATUS ? + BalanceErrs.faucetQueueIsFull : + BalanceErrs.faucetRequestFailed; + this.setState({ + errorType, + }); + return false; + } + + if (isEtherRequest) { + this.setState({ + isBalanceSpinnerVisible: true, + }); + } else { + const tokens = _.values(this.props.tokenByAddress); + const zrxToken = _.find(tokens, t => t.symbol === ZRX_TOKEN_SYMBOL); + const zrxTokenState = this.props.tokenStateByAddress[zrxToken.address]; + this.setState({ + isZRXSpinnerVisible: true, + currentZrxBalance: zrxTokenState.balance, + }); + this.props.blockchain.pollTokenBalanceAsync(zrxToken); + } + return true; + } + private onErrorDialogToggle(isOpen: boolean) { + this.setState({ + errorType: undefined, + }); + } + private onDharmaDialogToggle() { + this.setState({ + isDharmaDialogVisible: !this.state.isDharmaDialogVisible, + }); + } + private getWrappedEthToken() { + const tokens = _.values(this.props.tokenByAddress); + const wrappedEthToken = _.find(tokens, {symbol: ETHER_TOKEN_SYMBOL}); + return wrappedEthToken; + } + private onAddTokenClicked() { + this.setState({ + isTokenPickerOpen: true, + isAddingToken: true, + }); + } + private onRemoveTokenClicked() { + this.setState({ + isTokenPickerOpen: true, + isAddingToken: false, + }); + } +} diff --git a/packages/website/ts/components/top_bar.tsx b/packages/website/ts/components/top_bar.tsx new file mode 100644 index 000000000..6248095b3 --- /dev/null +++ b/packages/website/ts/components/top_bar.tsx @@ -0,0 +1,370 @@ +import * as _ from 'lodash'; +import * as React from 'react'; +import { + Link as ScrollLink, + animateScroll, +} from 'react-scroll'; +import {Link} from 'react-router-dom'; +import {HashLink} from 'react-router-hash-link'; +import AppBar from 'material-ui/AppBar'; +import Drawer from 'material-ui/Drawer'; +import MenuItem from 'material-ui/MenuItem'; +import {colors} from 'material-ui/styles'; +import ReactTooltip = require('react-tooltip'); +import {configs} from 'ts/utils/configs'; +import {constants} from 'ts/utils/constants'; +import {Identicon} from 'ts/components/ui/identicon'; +import {NestedSidebarMenu} from 'ts/pages/shared/nested_sidebar_menu'; +import {typeDocUtils} from 'ts/utils/typedoc_utils'; +import {PortalMenu} from 'ts/components/portal_menu'; +import {Styles, TypeDocNode, MenuSubsectionsBySection, WebsitePaths, Docs} from 'ts/types'; +import {TopBarMenuItem} from 'ts/components/top_bar_menu_item'; +import {DropDownMenuItem} from 'ts/components/ui/drop_down_menu_item'; + +const CUSTOM_DARK_GRAY = '#231F20'; +const SECTION_HEADER_COLOR = 'rgb(234, 234, 234)'; + +interface TopBarProps { + userAddress?: string; + blockchainIsLoaded: boolean; + location: Location; + docsVersion?: string; + availableDocVersions?: string[]; + menuSubsectionsBySection?: MenuSubsectionsBySection; + shouldFullWidth?: boolean; + doc?: Docs; + style?: React.CSSProperties; + isNightVersion?: boolean; +} + +interface TopBarState { + isDrawerOpen: boolean; +} + +const styles: Styles = { + address: { + marginRight: 12, + overflow: 'hidden', + paddingTop: 4, + textOverflow: 'ellipsis', + whiteSpace: 'nowrap', + width: 70, + }, + addressPopover: { + backgroundColor: colors.blueGrey500, + color: 'white', + padding: 3, + }, + topBar: { + backgroundColor: 'white', + height: 59, + width: '100%', + position: 'fixed', + top: 0, + zIndex: 1100, + paddingBottom: 1, + }, + bottomBar: { + boxShadow: 'rgba(0, 0, 0, 0.187647) 0px 1px 3px', + }, + menuItem: { + fontSize: 14, + color: CUSTOM_DARK_GRAY, + paddingTop: 6, + paddingBottom: 6, + marginTop: 17, + cursor: 'pointer', + fontWeight: 400, + }, +}; + +export class TopBar extends React.Component { + public static defaultProps: Partial = { + shouldFullWidth: false, + style: {}, + isNightVersion: false, + }; + constructor(props: TopBarProps) { + super(props); + this.state = { + isDrawerOpen: false, + }; + } + public render() { + const isNightVersion = this.props.isNightVersion; + const isFullWidthPage = this.props.shouldFullWidth; + const parentClassNames = `flex mx-auto ${isFullWidthPage ? 'pl2' : 'max-width-4'}`; + const developerSectionMenuItems = [ + + + , + + + , + + + , + + + , + + + , + ]; + const bottomBorderStyle = this.shouldDisplayBottomBar() ? styles.bottomBar : {}; + const fullWithClassNames = isFullWidthPage ? 'pr4' : ''; + const logoUrl = isNightVersion ? '/images/protocol_logo_white.png' : '/images/protocol_logo_black.png'; + return ( +
+
+
+ + + +
+
+
+ {!this.isViewingPortal() && +
+
+ + + + +
+
+ } + {this.props.blockchainIsLoaded && !_.isEmpty(this.props.userAddress) && +
+ {this.renderUser()} +
+ } + {!this.isViewingPortal() && +
+
+ +
+
+ } +
+ {this.renderDrawer()} +
+ ); + } + private renderDrawer() { + return ( + + {this.renderPortalMenu()} + {this.renderDocsMenu()} + {this.renderWiki()} +
Website
+ + Home + + + Wiki + + {!this.isViewing0xjsDocs() && + + 0x.js Docs + + } + {!this.isViewingSmartContractsDocs() && + + Smart Contract Docs + + } + {!this.isViewingPortal() && + + Portal DApp + + } + + Whitepaper + + + About + + + Blog + + + + FAQ + + +
+ ); + } + private renderDocsMenu() { + if (!this.isViewing0xjsDocs() && !this.isViewingSmartContractsDocs()) { + return; + } + + const topLevelMenu = this.isViewing0xjsDocs() ? + typeDocUtils.getFinal0xjsMenu(this.props.docsVersion) : + constants.menuSmartContracts; + + const sectionTitle = this.isViewing0xjsDocs() ? '0x.js Docs' : 'Smart contract Docs'; + return ( +
+
{sectionTitle}
+ +
+ ); + } + private renderWiki() { + if (!this.isViewingWiki()) { + return; + } + + return ( +
+
0x Protocol Wiki
+ +
+ ); + } + private renderPortalMenu() { + if (!this.isViewingPortal()) { + return; + } + + return ( +
+
Portal DApp
+ +
+ ); + } + private renderUser() { + const userAddress = this.props.userAddress; + const identiconDiameter = 26; + return ( +
+
+ {!_.isEmpty(userAddress) ? userAddress : ''} +
+ {userAddress} +
+ +
+
+ ); + } + private onMenuButtonClick() { + this.setState({ + isDrawerOpen: !this.state.isDrawerOpen, + }); + } + private isViewingPortal() { + return _.includes(this.props.location.pathname, WebsitePaths.Portal); + } + private isViewingFAQ() { + return _.includes(this.props.location.pathname, WebsitePaths.FAQ); + } + private isViewing0xjsDocs() { + return _.includes(this.props.location.pathname, WebsitePaths.ZeroExJs); + } + private isViewingSmartContractsDocs() { + return _.includes(this.props.location.pathname, WebsitePaths.SmartContracts); + } + private isViewingWiki() { + return _.includes(this.props.location.pathname, WebsitePaths.Wiki); + } + private shouldDisplayBottomBar() { + return this.isViewingWiki() || this.isViewing0xjsDocs() || this.isViewingFAQ() || + this.isViewingSmartContractsDocs(); + } +} diff --git a/packages/website/ts/components/top_bar_menu_item.tsx b/packages/website/ts/components/top_bar_menu_item.tsx new file mode 100644 index 000000000..de429fba6 --- /dev/null +++ b/packages/website/ts/components/top_bar_menu_item.tsx @@ -0,0 +1,53 @@ +import * as _ from 'lodash'; +import * as React from 'react'; +import {Link} from 'react-router-dom'; +import {Styles} from 'ts/types'; + +const CUSTOM_DARK_GRAY = '#231F20'; +const DEFAULT_STYLE = { + color: CUSTOM_DARK_GRAY, +}; + +interface TopBarMenuItemProps { + title: string; + path?: string; + isPrimary?: boolean; + style?: React.CSSProperties; + className?: string; + isNightVersion?: boolean; +} + +interface TopBarMenuItemState {} + +export class TopBarMenuItem extends React.Component { + public static defaultProps: Partial = { + isPrimary: false, + style: DEFAULT_STYLE, + className: '', + isNightVersion: false, + }; + public render() { + const primaryStyles = this.props.isPrimary ? { + borderRadius: 4, + border: `1px solid ${this.props.isNightVersion ? '#979797' : 'rgb(230, 229, 229)'}`, + marginTop: 15, + paddingLeft: 9, + paddingRight: 9, + width: 77, + } : {}; + const menuItemColor = this.props.isNightVersion ? 'white' : this.props.style.color; + const linkColor = _.isUndefined(menuItemColor) ? + CUSTOM_DARK_GRAY : + menuItemColor; + return ( +
+ + {this.props.title} + +
+ ); + } +} diff --git a/packages/website/ts/components/track_token_confirmation.tsx b/packages/website/ts/components/track_token_confirmation.tsx new file mode 100644 index 000000000..bc036eae3 --- /dev/null +++ b/packages/website/ts/components/track_token_confirmation.tsx @@ -0,0 +1,65 @@ +import * as _ from 'lodash'; +import * as React from 'react'; +import {colors} from 'material-ui/styles'; +import FlatButton from 'material-ui/FlatButton'; +import Dialog from 'material-ui/Dialog'; +import {utils} from 'ts/utils/utils'; +import {Party} from 'ts/components/ui/party'; +import {Token, TokenByAddress} from 'ts/types'; + +interface TrackTokenConfirmationProps { + tokens: Token[]; + tokenByAddress: TokenByAddress; + networkId: number; + isAddingTokenToTracked: boolean; +} + +interface TrackTokenConfirmationState {} + +export class TrackTokenConfirmation extends + React.Component { + public render() { + const isMultipleTokens = this.props.tokens.length > 1; + const allTokens = _.values(this.props.tokenByAddress); + return ( +
+ {this.props.isAddingTokenToTracked ? +
+ + + + Adding token{isMultipleTokens && 's'}... +
: +
+
+ You do not currently track the following token{isMultipleTokens && 's'}: +
+
+ {_.map(this.props.tokens, (token: Token) => ( +
+ +
+ ))} +
+
+ Tracking a token adds it to the balances section of 0x Portal and + allows you to generate/fill orders involving the token + {isMultipleTokens && 's'}. Would you like to start tracking{' '} + {isMultipleTokens ? 'these' : 'this'}{' '}token? +
+
+ } +
+ ); + } +} diff --git a/packages/website/ts/components/trade_history/trade_history.tsx b/packages/website/ts/components/trade_history/trade_history.tsx new file mode 100644 index 000000000..9deaf8fd8 --- /dev/null +++ b/packages/website/ts/components/trade_history/trade_history.tsx @@ -0,0 +1,115 @@ +import * as _ from 'lodash'; +import * as React from 'react'; +import Paper from 'material-ui/Paper'; +import Divider from 'material-ui/Divider'; +import {utils} from 'ts/utils/utils'; +import {Fill, TokenByAddress} from 'ts/types'; +import {TradeHistoryItem} from 'ts/components/trade_history/trade_history_item'; +import {tradeHistoryStorage} from 'ts/local_storage/trade_history_storage'; + +const FILL_POLLING_INTERVAL = 1000; + +interface TradeHistoryProps { + tokenByAddress: TokenByAddress; + userAddress: string; + networkId: number; +} + +interface TradeHistoryState { + sortedFills: Fill[]; +} + +export class TradeHistory extends React.Component { + private fillPollingIntervalId: number; + public constructor(props: TradeHistoryProps) { + super(props); + const sortedFills = this.getSortedFills(); + this.state = { + sortedFills, + }; + } + public componentWillMount() { + this.startPollingForFills(); + } + public componentWillUnmount() { + this.stopPollingForFills(); + } + public componentDidMount() { + window.scrollTo(0, 0); + } + public render() { + return ( +
+

Trade history

+ +
+ {this.renderTrades()} +
+
+ ); + } + private renderTrades() { + const numNonCustomFills = this.numFillsWithoutCustomERC20Tokens(); + if (numNonCustomFills === 0) { + return this.renderEmptyNotice(); + } + + return _.map(this.state.sortedFills, (fill, index) => { + return ( + + ); + }); + } + private renderEmptyNotice() { + return ( + + No filled orders yet. + + ); + } + private numFillsWithoutCustomERC20Tokens() { + let numNonCustomFills = 0; + const tokens = _.values(this.props.tokenByAddress); + _.each(this.state.sortedFills, fill => { + const takerToken = _.find(tokens, token => { + return token.address === fill.takerToken; + }); + const makerToken = _.find(tokens, token => { + return token.address === fill.makerToken; + }); + // For now we don't show history items for orders using custom ERC20 + // tokens the client does not know how to display. + // TODO: Try to retrieve the name/symbol of an unknown token in order to display it + // Be sure to remove similar logic in trade_history_item.tsx + if (!_.isUndefined(takerToken) && !_.isUndefined(makerToken)) { + numNonCustomFills += 1; + } + }); + return numNonCustomFills; + } + private startPollingForFills() { + this.fillPollingIntervalId = window.setInterval(() => { + const sortedFills = this.getSortedFills(); + if (!utils.deepEqual(sortedFills, this.state.sortedFills)) { + this.setState({ + sortedFills, + }); + } + }, FILL_POLLING_INTERVAL); + } + private stopPollingForFills() { + clearInterval(this.fillPollingIntervalId); + } + private getSortedFills() { + const fillsByHash = tradeHistoryStorage.getUserFillsByHash(this.props.userAddress, this.props.networkId); + const fills = _.values(fillsByHash); + const sortedFills = _.sortBy(fills, [(fill: Fill) => fill.blockTimestamp * -1]); + return sortedFills; + } +} diff --git a/packages/website/ts/components/trade_history/trade_history_item.tsx b/packages/website/ts/components/trade_history/trade_history_item.tsx new file mode 100644 index 000000000..96b755e3c --- /dev/null +++ b/packages/website/ts/components/trade_history/trade_history_item.tsx @@ -0,0 +1,178 @@ +import * as _ from 'lodash'; +import * as React from 'react'; +import BigNumber from 'bignumber.js'; +import * as ReactTooltip from 'react-tooltip'; +import * as moment from 'moment'; +import Paper from 'material-ui/Paper'; +import {colors} from 'material-ui/styles'; +import {ZeroEx} from '0x.js'; +import {TokenByAddress, Fill, Token, EtherscanLinkSuffixes} from 'ts/types'; +import {Party} from 'ts/components/ui/party'; +import {EtherScanIcon} from 'ts/components/ui/etherscan_icon'; + +const PRECISION = 5; +const IDENTICON_DIAMETER = 40; + +interface TradeHistoryItemProps { + fill: Fill; + tokenByAddress: TokenByAddress; + userAddress: string; + networkId: number; +} + +interface TradeHistoryItemState {} + +export class TradeHistoryItem extends React.Component { + public render() { + const fill = this.props.fill; + const tokens = _.values(this.props.tokenByAddress); + const takerToken = _.find(tokens, token => { + return token.address === fill.takerToken; + }); + const makerToken = _.find(tokens, token => { + return token.address === fill.makerToken; + }); + // For now we don't show history items for orders using custom ERC20 + // tokens the client does not know how to display. + // TODO: Try to retrieve the name/symbol of an unknown token in order to display it + // Be sure to remove similar logic in trade_history.tsx + if (_.isUndefined(takerToken) || _.isUndefined(makerToken)) { + return null; + } + + const amountColStyle: React.CSSProperties = { + fontWeight: 100, + display: 'inline-block', + }; + const amountColClassNames = 'col col-12 lg-col-4 md-col-4 lg-py2 md-py2 sm-py1 lg-pr2 md-pr2 \ + lg-right-align md-right-align sm-center'; + + return ( + +
+
+ {this.renderDate()} +
+
+
+ + + +
+
+
+ {this.renderAmounts(makerToken, takerToken)} +
+
+
+ +
+
+
+
+ ); + } + private renderAmounts(makerToken: Token, takerToken: Token) { + const fill = this.props.fill; + const filledTakerTokenAmountInUnits = ZeroEx.toUnitAmount(fill.filledTakerTokenAmount, takerToken.decimals); + const filledMakerTokenAmountInUnits = ZeroEx.toUnitAmount(fill.filledMakerTokenAmount, takerToken.decimals); + let exchangeRate = filledTakerTokenAmountInUnits.div(filledMakerTokenAmountInUnits); + const fillMakerTokenAmount = ZeroEx.toBaseUnitAmount(filledMakerTokenAmountInUnits, makerToken.decimals); + + let receiveAmount; + let receiveToken; + let givenAmount; + let givenToken; + if (this.props.userAddress === fill.maker && this.props.userAddress === fill.taker) { + receiveAmount = new BigNumber(0); + givenAmount = new BigNumber(0); + receiveToken = makerToken; + givenToken = takerToken; + } else if (this.props.userAddress === fill.maker) { + receiveAmount = fill.filledTakerTokenAmount; + givenAmount = fillMakerTokenAmount; + receiveToken = takerToken; + givenToken = makerToken; + exchangeRate = new BigNumber(1).div(exchangeRate); + } else if (this.props.userAddress === fill.taker) { + receiveAmount = fillMakerTokenAmount; + givenAmount = fill.filledTakerTokenAmount; + receiveToken = makerToken; + givenToken = takerToken; + } + + return ( +
+
+ +{' '} + {this.renderAmount(receiveAmount, receiveToken.symbol, receiveToken.decimals)} +
+
+ -{' '} + {this.renderAmount(givenAmount, givenToken.symbol, givenToken.decimals)} +
+
+ {exchangeRate.toFixed(PRECISION)} {givenToken.symbol}/{receiveToken.symbol} +
+
+ ); + } + private renderDate() { + const blockMoment = moment.unix(this.props.fill.blockTimestamp); + if (!blockMoment.isValid()) { + return null; + } + + const dayOfMonth = blockMoment.format('D'); + const monthAbreviation = blockMoment.format('MMM'); + const formattedBlockDate = blockMoment.format('H:mmA - MMMM D, YYYY'); + const dateTooltipId = `${this.props.fill.transactionHash}-date`; + + return ( +
+
{monthAbreviation}
+
{dayOfMonth}
+ {formattedBlockDate} +
+ ); + } + private renderAmount(amount: BigNumber, symbol: string, decimals: number) { + const unitAmount = ZeroEx.toUnitAmount(amount, decimals); + return ( + + {unitAmount.toFixed(PRECISION)} {symbol} + + ); + } +} diff --git a/packages/website/ts/components/ui/alert.tsx b/packages/website/ts/components/ui/alert.tsx new file mode 100644 index 000000000..bf2f0baf5 --- /dev/null +++ b/packages/website/ts/components/ui/alert.tsx @@ -0,0 +1,27 @@ +import * as React from 'react'; +import {colors} from 'material-ui/styles'; +import {AlertTypes} from 'ts/types'; + +const CUSTOM_GREEN = 'rgb(137, 199, 116)'; + +interface AlertProps { + type: AlertTypes; + message: string|React.ReactNode; +} + +export function Alert(props: AlertProps) { + const isAlert = props.type === AlertTypes.ERROR; + const errMsgStyles = { + background: isAlert ? colors.red200 : CUSTOM_GREEN, + color: 'white', + marginTop: 10, + padding: 4, + paddingLeft: 8, + }; + + return ( +
+ {props.message} +
+ ); +} diff --git a/packages/website/ts/components/ui/badge.tsx b/packages/website/ts/components/ui/badge.tsx new file mode 100644 index 000000000..1e3bbdb99 --- /dev/null +++ b/packages/website/ts/components/ui/badge.tsx @@ -0,0 +1,58 @@ +import * as _ from 'lodash'; +import * as React from 'react'; +import {colors} from 'material-ui/styles'; +import {Styles} from 'ts/types'; + +const styles: Styles = { + badge: { + width: 50, + fontSize: 11, + height: 10, + borderRadius: 5, + marginTop: 25, + lineHeight: 0.9, + fontFamily: 'Roboto Mono', + marginLeft: 3, + marginRight: 3, + }, +}; + +interface BadgeProps { + title: string; + backgroundColor: string; +} + +interface BadgeState { + isHovering: boolean; +} + +export class Badge extends React.Component { + constructor(props: BadgeProps) { + super(props); + this.state = { + isHovering: false, + }; + } + public render() { + const badgeStyle = { + ...styles.badge, + backgroundColor: this.props.backgroundColor, + opacity: this.state.isHovering ? 0.7 : 1, + }; + return ( +
+ {this.props.title} +
+ ); + } + private setHoverState(isHovering: boolean) { + this.setState({ + isHovering, + }); + } +} diff --git a/packages/website/ts/components/ui/copy_icon.tsx b/packages/website/ts/components/ui/copy_icon.tsx new file mode 100644 index 000000000..f8abaa59e --- /dev/null +++ b/packages/website/ts/components/ui/copy_icon.tsx @@ -0,0 +1,81 @@ +import * as _ from 'lodash'; +import * as React from 'react'; +import * as ReactDOM from 'react-dom'; +import * as CopyToClipboard from 'react-copy-to-clipboard'; +import {colors} from 'material-ui/styles'; +import ReactTooltip = require('react-tooltip'); + +interface CopyIconProps { + data: string; + callToAction?: string; +} + +interface CopyIconState { + isHovering: boolean; +} + +export class CopyIcon extends React.Component { + private copyTooltipTimeoutId: number; + private copyable: HTMLInputElement; + constructor(props: CopyIconProps) { + super(props); + this.state = { + isHovering: false, + }; + } + public componentDidUpdate() { + // Remove tooltip if hover away + if (!this.state.isHovering && this.copyTooltipTimeoutId) { + clearInterval(this.copyTooltipTimeoutId); + this.hideTooltip(); + } + } + public render() { + return ( +
+ +
+
+ +
+ {this.props.callToAction && +
{this.props.callToAction}
+ } +
+
+ Copied! +
+ ); + } + private setRefToProperty(el: HTMLInputElement) { + this.copyable = el; + } + private setHoverState(isHovering: boolean) { + this.setState({ + isHovering, + }); + } + private onCopy() { + if (this.copyTooltipTimeoutId) { + clearInterval(this.copyTooltipTimeoutId); + } + + const tooltipLifespanMs = 1000; + this.copyTooltipTimeoutId = window.setTimeout(() => { + this.hideTooltip(); + }, tooltipLifespanMs); + } + private hideTooltip() { + ReactTooltip.hide(ReactDOM.findDOMNode(this.copyable)); + } +} diff --git a/packages/website/ts/components/ui/drop_down_menu_item.tsx b/packages/website/ts/components/ui/drop_down_menu_item.tsx new file mode 100644 index 000000000..b8b7eb167 --- /dev/null +++ b/packages/website/ts/components/ui/drop_down_menu_item.tsx @@ -0,0 +1,117 @@ +import * as _ from 'lodash'; +import * as React from 'react'; +import { + Link as ScrollLink, +} from 'react-scroll'; +import {Link} from 'react-router-dom'; +import Popover from 'material-ui/Popover'; +import Menu from 'material-ui/Menu'; +import MenuItem from 'material-ui/MenuItem'; +import {Styles, WebsitePaths} from 'ts/types'; + +const CHECK_CLOSE_POPOVER_INTERVAL_MS = 300; +const CUSTOM_LIGHT_GRAY = '#848484'; +const DEFAULT_STYLE = { + fontSize: 14, +}; + +interface DropDownMenuItemProps { + title: string; + subMenuItems: React.ReactNode[]; + style?: React.CSSProperties; + menuItemStyle?: React.CSSProperties; + isNightVersion?: boolean; +} + +interface DropDownMenuItemState { + isDropDownOpen: boolean; + anchorEl?: HTMLInputElement; +} + +export class DropDownMenuItem extends React.Component { + public static defaultProps: Partial = { + style: DEFAULT_STYLE, + menuItemStyle: DEFAULT_STYLE, + isNightVersion: false, + }; + private isHovering: boolean; + private popoverCloseCheckIntervalId: number; + constructor(props: DropDownMenuItemProps) { + super(props); + this.state = { + isDropDownOpen: false, + }; + } + public componentDidMount() { + this.popoverCloseCheckIntervalId = window.setInterval(() => { + this.checkIfShouldClosePopover(); + }, CHECK_CLOSE_POPOVER_INTERVAL_MS); + } + public componentWillUnmount() { + window.clearInterval(this.popoverCloseCheckIntervalId); + } + public render() { + const colorStyle = this.props.isNightVersion ? 'white' : this.props.style.color; + return ( +
+
+
+ {this.props.title} +
+
+ +
+
+ +
+ + {this.props.subMenuItems} + +
+
+
+ ); + } + private onHover(event: React.FormEvent) { + this.isHovering = true; + this.checkIfShouldOpenPopover(event); + } + private checkIfShouldOpenPopover(event: React.FormEvent) { + if (this.state.isDropDownOpen) { + return; // noop + } + + this.setState({ + isDropDownOpen: true, + anchorEl: event.currentTarget, + }); + } + private onHoverOff(event: React.FormEvent) { + this.isHovering = false; + } + private checkIfShouldClosePopover() { + if (!this.state.isDropDownOpen || this.isHovering) { + return; // noop + } + this.closePopover(); + } + private closePopover() { + this.setState({ + isDropDownOpen: false, + }); + } +} diff --git a/packages/website/ts/components/ui/ethereum_address.tsx b/packages/website/ts/components/ui/ethereum_address.tsx new file mode 100644 index 000000000..c3d03b78c --- /dev/null +++ b/packages/website/ts/components/ui/ethereum_address.tsx @@ -0,0 +1,35 @@ +import * as React from 'react'; +import {EtherScanIcon} from 'ts/components/ui/etherscan_icon'; +import ReactTooltip = require('react-tooltip'); +import {EtherscanLinkSuffixes} from 'ts/types'; +import {utils} from 'ts/utils/utils'; + +interface EthereumAddressProps { + address: string; + networkId: number; +} + +export const EthereumAddress = (props: EthereumAddressProps) => { + const tooltipId = `${props.address}-ethereum-address`; + const truncatedAddress = utils.getAddressBeginAndEnd(props.address); + return ( +
+
+ {truncatedAddress} +
+
+ +
+ {props.address} +
+ ); +}; diff --git a/packages/website/ts/components/ui/etherscan_icon.tsx b/packages/website/ts/components/ui/etherscan_icon.tsx new file mode 100644 index 000000000..12044f44b --- /dev/null +++ b/packages/website/ts/components/ui/etherscan_icon.tsx @@ -0,0 +1,50 @@ +import * as _ from 'lodash'; +import * as React from 'react'; +import ReactTooltip = require('react-tooltip'); +import {colors} from 'material-ui/styles'; +import {EtherscanLinkSuffixes} from 'ts/types'; +import {utils} from 'ts/utils/utils'; + +interface EtherScanIconProps { + addressOrTxHash: string; + etherscanLinkSuffixes: EtherscanLinkSuffixes; + networkId: number; +} + +export const EtherScanIcon = (props: EtherScanIconProps) => { + const etherscanLinkIfExists = utils.getEtherScanLinkIfExists( + props.addressOrTxHash, props.networkId, EtherscanLinkSuffixes.address, + ); + const transactionTooltipId = `${props.addressOrTxHash}-etherscan-icon-tooltip`; + return ( +
+ {!_.isUndefined(etherscanLinkIfExists) ? + + {renderIcon()} + : +
+ {renderIcon()} + + Your network (id: {props.networkId}) is not supported by Etherscan + +
+ } +
+ ); +}; + +function renderIcon() { + return ( + + ); +} diff --git a/packages/website/ts/components/ui/fake_text_field.tsx b/packages/website/ts/components/ui/fake_text_field.tsx new file mode 100644 index 000000000..372785c2f --- /dev/null +++ b/packages/website/ts/components/ui/fake_text_field.tsx @@ -0,0 +1,35 @@ +import * as React from 'react'; +import {colors} from 'material-ui/styles'; +import {InputLabel} from 'ts/components/ui/input_label'; +import {Styles} from 'ts/types'; + +const styles: Styles = { + hr: { + borderBottom: '1px solid rgb(224, 224, 224)', + borderLeft: 'none rgb(224, 224, 224)', + borderRight: 'none rgb(224, 224, 224)', + borderTop: 'none rgb(224, 224, 224)', + bottom: 6, + boxSizing: 'content-box', + margin: 0, + position: 'absolute', + width: '100%', + }, +}; + +interface FakeTextFieldProps { + label?: React.ReactNode | string; + children?: any; +} + +export function FakeTextField(props: FakeTextFieldProps) { + return ( +
+ {props.label !== '' && } +
+ {props.children} +
+
+
+ ); +} diff --git a/packages/website/ts/components/ui/flash_message.tsx b/packages/website/ts/components/ui/flash_message.tsx new file mode 100644 index 000000000..684aeef68 --- /dev/null +++ b/packages/website/ts/components/ui/flash_message.tsx @@ -0,0 +1,40 @@ +import * as _ from 'lodash'; +import * as React from 'react'; +import Snackbar from 'material-ui/Snackbar'; +import {Dispatcher} from 'ts/redux/dispatcher'; + +const SHOW_DURATION_MS = 4000; + +interface FlashMessageProps { + dispatcher: Dispatcher; + flashMessage?: string|React.ReactNode; + showDurationMs?: number; + bodyStyle?: React.CSSProperties; +} + +interface FlashMessageState {} + +export class FlashMessage extends React.Component { + public static defaultProps: Partial = { + showDurationMs: SHOW_DURATION_MS, + bodyStyle: {}, + }; + public render() { + if (!_.isUndefined(this.props.flashMessage)) { + return ( + + ); + } else { + return null; + } + } + private onClose() { + this.props.dispatcher.hideFlashMessage(); + } +} diff --git a/packages/website/ts/components/ui/help_tooltip.tsx b/packages/website/ts/components/ui/help_tooltip.tsx new file mode 100644 index 000000000..003b795ef --- /dev/null +++ b/packages/website/ts/components/ui/help_tooltip.tsx @@ -0,0 +1,22 @@ +import * as React from 'react'; +import ReactTooltip = require('react-tooltip'); + +interface HelpTooltipProps { + style?: React.CSSProperties; + explanation: React.ReactNode; +} + +export const HelpTooltip = (props: HelpTooltipProps) => { + return ( +
+ + +
+ ); +}; diff --git a/packages/website/ts/components/ui/identicon.tsx b/packages/website/ts/components/ui/identicon.tsx new file mode 100644 index 000000000..814548fb4 --- /dev/null +++ b/packages/website/ts/components/ui/identicon.tsx @@ -0,0 +1,36 @@ +import * as _ from 'lodash'; +import * as React from 'react'; +import {constants} from 'ts/utils/constants'; +import blockies = require('blockies'); + +interface IdenticonProps { + address: string; + diameter: number; + style?: React.CSSProperties; +} + +interface IdenticonState {} + +export class Identicon extends React.Component { + public static defaultProps: Partial = { + style: {}, + }; + public render() { + let address = this.props.address; + if (_.isEmpty(address)) { + address = constants.NULL_ADDRESS; + } + const diameter = this.props.diameter; + const icon = blockies({ + seed: address.toLowerCase(), + }); + return ( +
+ +
+ ); + } +} diff --git a/packages/website/ts/components/ui/input_label.tsx b/packages/website/ts/components/ui/input_label.tsx new file mode 100644 index 000000000..5866c70b6 --- /dev/null +++ b/packages/website/ts/components/ui/input_label.tsx @@ -0,0 +1,27 @@ +import * as React from 'react'; +import {colors} from 'material-ui/styles'; + +export interface InputLabelProps { + text: string | Element | React.ReactNode; +} + +const styles = { + label: { + color: colors.grey500, + fontSize: 12, + pointerEvents: 'none', + textAlign: 'left', + transform: 'scale(0.75) translate(0px, -28px)', + transformOrigin: 'left top 0px', + transition: 'all 450ms cubic-bezier(0.23, 1, 0.32, 1) 0ms', + userSelect: 'none', + width: 240, + zIndex: 1, + }, +}; + +export const InputLabel = (props: InputLabelProps) => { + return ( + + ); +}; diff --git a/packages/website/ts/components/ui/labeled_switcher.tsx b/packages/website/ts/components/ui/labeled_switcher.tsx new file mode 100644 index 000000000..3ed8ba0a4 --- /dev/null +++ b/packages/website/ts/components/ui/labeled_switcher.tsx @@ -0,0 +1,76 @@ +import * as _ from 'lodash'; +import * as React from 'react'; +import {colors} from 'material-ui/styles'; + +const CUSTOM_BLUE = '#63A6F1'; + +interface LabeledSwitcherProps { + labelLeft: string; + labelRight: string; + isLeftInitiallySelected: boolean; + onLeftLabelClickAsync: () => Promise; + onRightLabelClickAsync: () => Promise; +} + +interface LabeledSwitcherState { + isLeftSelected: boolean; +} + +export class LabeledSwitcher extends React.Component { + constructor(props: LabeledSwitcherProps) { + super(props); + this.state = { + isLeftSelected: props.isLeftInitiallySelected, + }; + } + public render() { + const isLeft = true; + return ( +
+ {this.renderLabel(this.props.labelLeft, isLeft, this.state.isLeftSelected)} + {this.renderLabel(this.props.labelRight, !isLeft, !this.state.isLeftSelected)} +
+ ); + } + private renderLabel(title: string, isLeft: boolean, isSelected: boolean) { + const borderStyle = `2px solid ${isSelected ? '#4F8BCF' : '#DADADA'}`; + const style = { + cursor: 'pointer', + backgroundColor: isSelected ? CUSTOM_BLUE : colors.grey200, + color: isSelected ? 'white' : '#A5A5A5', + boxShadow: isSelected ? `inset 0px 0px 4px #4083CE` : 'inset 0px 0px 4px #F7F6F6', + borderTop: borderStyle, + borderBottom: borderStyle, + [isLeft ? 'borderLeft' : 'borderRight']: borderStyle, + paddingTop: 12, + paddingBottom: 12, + }; + return ( +
+ {title} +
+ ); + } + private async onLabelClickAsync(isLeft: boolean): Promise { + this.setState({ + isLeftSelected: isLeft, + }); + let didSucceed; + if (isLeft) { + didSucceed = await this.props.onLeftLabelClickAsync(); + } else { + didSucceed = await this.props.onRightLabelClickAsync(); + } + if (!didSucceed) { + this.setState({ + isLeftSelected: !isLeft, + }); + } + } +} diff --git a/packages/website/ts/components/ui/lifecycle_raised_button.tsx b/packages/website/ts/components/ui/lifecycle_raised_button.tsx new file mode 100644 index 000000000..e93c80ba4 --- /dev/null +++ b/packages/website/ts/components/ui/lifecycle_raised_button.tsx @@ -0,0 +1,105 @@ +import * as _ from 'lodash'; +import * as React from 'react'; +import {utils} from 'ts/utils/utils'; +import {Token} from 'ts/types'; +import {Blockchain} from 'ts/blockchain'; +import RaisedButton from 'material-ui/RaisedButton'; + +const COMPLETE_STATE_SHOW_LENGTH_MS = 2000; + +enum ButtonState { + READY, + LOADING, + COMPLETE, +}; + +interface LifeCycleRaisedButtonProps { + isHidden?: boolean; + isDisabled?: boolean; + isPrimary?: boolean; + labelReady: React.ReactNode|string; + labelLoading: React.ReactNode|string; + labelComplete: React.ReactNode|string; + onClickAsyncFn: () => boolean; + backgroundColor?: string; + labelColor?: string; +} + +interface LifeCycleRaisedButtonState { + buttonState: ButtonState; +} + +export class LifeCycleRaisedButton extends + React.Component { + public static defaultProps: Partial = { + isDisabled: false, + backgroundColor: 'white', + labelColor: 'rgb(97, 97, 97)', + }; + private buttonTimeoutId: number; + private didUnmount: boolean; + constructor(props: LifeCycleRaisedButtonProps) { + super(props); + this.state = { + buttonState: ButtonState.READY, + }; + } + public componentWillUnmount() { + clearTimeout(this.buttonTimeoutId); + this.didUnmount = true; + } + public render() { + if (this.props.isHidden === true) { + return ; + } + + let label; + switch (this.state.buttonState) { + case ButtonState.READY: + label = this.props.labelReady; + break; + case ButtonState.LOADING: + label = this.props.labelLoading; + break; + case ButtonState.COMPLETE: + label = this.props.labelComplete; + break; + default: + throw utils.spawnSwitchErr('ButtonState', this.state.buttonState); + } + return ( + + ); + } + public async onClickAsync() { + this.setState({ + buttonState: ButtonState.LOADING, + }); + const didSucceed = await this.props.onClickAsyncFn(); + if (this.didUnmount) { + return; // noop since unmount called before async callback returned. + } + if (didSucceed) { + this.setState({ + buttonState: ButtonState.COMPLETE, + }); + this.buttonTimeoutId = window.setTimeout(() => { + this.setState({ + buttonState: ButtonState.READY, + }); + }, COMPLETE_STATE_SHOW_LENGTH_MS); + } else { + this.setState({ + buttonState: ButtonState.READY, + }); + } + } +} diff --git a/packages/website/ts/components/ui/loading.tsx b/packages/website/ts/components/ui/loading.tsx new file mode 100644 index 000000000..39c119d8f --- /dev/null +++ b/packages/website/ts/components/ui/loading.tsx @@ -0,0 +1,36 @@ +import * as _ from 'lodash'; +import * as React from 'react'; +import Paper from 'material-ui/Paper'; +import {utils} from 'ts/utils/utils'; +import {DefaultPlayer as Video} from 'react-html5video'; +import 'react-html5video/dist/styles.css'; + +interface LoadingProps {} + +interface LoadingState {} + +export class Loading extends React.Component { + public render() { + return ( +
+ + {utils.isUserOnMobile() ? + : +
+ +
+ } +
Connecting to the blockchain...
+
+
+ ); + } +} diff --git a/packages/website/ts/components/ui/menu_item.tsx b/packages/website/ts/components/ui/menu_item.tsx new file mode 100644 index 000000000..b9caa91fb --- /dev/null +++ b/packages/website/ts/components/ui/menu_item.tsx @@ -0,0 +1,54 @@ +import * as _ from 'lodash'; +import * as React from 'react'; +import {Link} from 'react-router-dom'; +import {Styles} from 'ts/types'; +import {constants} from 'ts/utils/constants'; +import {colors} from 'material-ui/styles'; + +interface MenuItemProps { + to: string; + style?: React.CSSProperties; + onClick?: () => void; + className?: string; +} + +interface MenuItemState { + isHovering: boolean; +} + +export class MenuItem extends React.Component { + public static defaultProps: Partial = { + onClick: _.noop, + className: '', + }; + public constructor(props: MenuItemProps) { + super(props); + this.state = { + isHovering: false, + }; + } + public render() { + const menuItemStyles = { + cursor: 'pointer', + opacity: this.state.isHovering ? 0.5 : 1, + }; + return ( + +
+ {this.props.children} +
+ + ); + } + private onToggleHover(isHovering: boolean) { + this.setState({ + isHovering, + }); + } +} diff --git a/packages/website/ts/components/ui/party.tsx b/packages/website/ts/components/ui/party.tsx new file mode 100644 index 000000000..b72e75181 --- /dev/null +++ b/packages/website/ts/components/ui/party.tsx @@ -0,0 +1,150 @@ +import * as _ from 'lodash'; +import * as React from 'react'; +import ReactTooltip = require('react-tooltip'); +import {colors} from 'material-ui/styles'; +import {Identicon} from 'ts/components/ui/identicon'; +import {EtherscanLinkSuffixes} from 'ts/types'; +import {utils} from 'ts/utils/utils'; +import {EthereumAddress} from 'ts/components/ui/ethereum_address'; + +const MIN_ADDRESS_WIDTH = 60; +const IMAGE_DIMENSION = 100; +const IDENTICON_DIAMETER = 95; +const CHECK_MARK_GREEN = 'rgb(0, 195, 62)'; + +interface PartyProps { + label: string; + address: string; + networkId: number; + alternativeImage?: string; + identiconDiameter?: number; + identiconStyle?: React.CSSProperties; + isInTokenRegistry?: boolean; + hasUniqueNameAndSymbol?: boolean; +} + +interface PartyState {} + +export class Party extends React.Component { + public static defaultProps: Partial = { + identiconStyle: {}, + identiconDiameter: IDENTICON_DIAMETER, + }; + public render() { + const label = this.props.label; + const address = this.props.address; + const tooltipId = `${label}-${address}-tooltip`; + const identiconDiameter = this.props.identiconDiameter; + const addressWidth = identiconDiameter > MIN_ADDRESS_WIDTH ? + identiconDiameter : MIN_ADDRESS_WIDTH; + const emptyIdenticonStyles = { + width: identiconDiameter, + height: identiconDiameter, + backgroundColor: 'lightgray', + marginTop: 13, + marginBottom: 10, + }; + const tokenImageStyle = { + width: IMAGE_DIMENSION, + height: IMAGE_DIMENSION, + }; + const etherscanLinkIfExists = utils.getEtherScanLinkIfExists( + this.props.address, this.props.networkId, EtherscanLinkSuffixes.address, + ); + const isRegistered = this.props.isInTokenRegistry; + const registeredTooltipId = `${this.props.address}-${isRegistered}-registeredTooltip`; + const uniqueNameAndSymbolTooltipId = `${this.props.address}-${isRegistered}-uniqueTooltip`; + return ( +
+
{label}
+ {_.isEmpty(address) ? +
: + + {isRegistered && !_.isUndefined(this.props.alternativeImage) ? + : +
+ +
+ } +
+ } +
+
+ +
+ {!_.isUndefined(this.props.isInTokenRegistry) && +
+
+ + + {' '} + {isRegistered ? 'Registered' : 'Unregistered'} token + + {isRegistered ? +
+ This token address was found in the token registry
+ smart contract and is therefore believed to be a
+ legitimate token. +
: +
+ This token is not included in the token registry
+ smart contract. We cannot guarantee the legitimacy
+ of this token. Make sure to verify its address on Etherscan. +
+ } +
+
+
+ } + {!_.isUndefined(this.props.hasUniqueNameAndSymbol) && !this.props.hasUniqueNameAndSymbol && +
+
+ + + {' '} + Suspicious token + + This token shares it's name, symbol or both with
+ a token in the 0x Token Registry but it has a different
+ smart contract address. This is most likely a scam token! +
+
+
+ } +
+
+ ); + } +} diff --git a/packages/website/ts/components/ui/required_label.tsx b/packages/website/ts/components/ui/required_label.tsx new file mode 100644 index 000000000..f9c73157a --- /dev/null +++ b/packages/website/ts/components/ui/required_label.tsx @@ -0,0 +1,15 @@ +import * as React from 'react'; +import {colors} from 'material-ui/styles'; + +export interface RequiredLabelProps { + label: string|React.ReactNode; +} + +export const RequiredLabel = (props: RequiredLabelProps) => { + return ( + + {props.label} + * + + ); +}; diff --git a/packages/website/ts/components/ui/simple_loading.tsx b/packages/website/ts/components/ui/simple_loading.tsx new file mode 100644 index 000000000..12d09ecc4 --- /dev/null +++ b/packages/website/ts/components/ui/simple_loading.tsx @@ -0,0 +1,23 @@ +import * as React from 'react'; +import {colors} from 'material-ui/styles'; +import CircularProgress from 'material-ui/CircularProgress'; + +export interface SimpleLoadingProps { + message: string; +} + +export const SimpleLoading = (props: SimpleLoadingProps) => { + return ( +
+
+ +
+ {props.message} +
+
+
+ ); +}; diff --git a/packages/website/ts/components/ui/swap_icon.tsx b/packages/website/ts/components/ui/swap_icon.tsx new file mode 100644 index 000000000..89bb33d55 --- /dev/null +++ b/packages/website/ts/components/ui/swap_icon.tsx @@ -0,0 +1,46 @@ +import * as _ from 'lodash'; +import * as React from 'react'; +import {constants} from 'ts/utils/constants'; +import {colors} from 'material-ui/styles'; + +interface SwapIconProps { + swapTokensFn: () => void; +} + +interface SwapIconState { + isHovering: boolean; +} + +export class SwapIcon extends React.Component { + public constructor(props: SwapIconProps) { + super(props); + this.state = { + isHovering: false, + }; + } + public render() { + const swapStyles = { + color: this.state.isHovering ? colors.amber600 : colors.amber800, + fontSize: 50, + }; + return ( +
+ +
+ ); + } + private onToggleHover(isHovering: boolean) { + this.setState({ + isHovering, + }); + } +} diff --git a/packages/website/ts/components/ui/token_icon.tsx b/packages/website/ts/components/ui/token_icon.tsx new file mode 100644 index 000000000..168c09bd4 --- /dev/null +++ b/packages/website/ts/components/ui/token_icon.tsx @@ -0,0 +1,29 @@ +import * as _ from 'lodash'; +import * as React from 'react'; +import {Token} from 'ts/types'; +import {Identicon} from 'ts/components/ui/identicon'; + +interface TokenIconProps { + token: Token; + diameter: number; +} + +interface TokenIconState {} + +export class TokenIcon extends React.Component { + public render() { + const token = this.props.token; + const diameter = this.props.diameter; + return ( +
+ {(token.isRegistered && !_.isUndefined(token.iconUrl)) ? + : + + } +
+ ); + } +} diff --git a/packages/website/ts/components/visual_order.tsx b/packages/website/ts/components/visual_order.tsx new file mode 100644 index 000000000..a7d6d1a9e --- /dev/null +++ b/packages/website/ts/components/visual_order.tsx @@ -0,0 +1,77 @@ +import * as _ from 'lodash'; +import * as React from 'react'; +import {ZeroEx} from '0x.js'; +import {AssetToken, Token, TokenByAddress} from 'ts/types'; +import {utils} from 'ts/utils/utils'; +import {Party} from 'ts/components/ui/party'; +import {constants} from 'ts/utils/constants'; + +const PRECISION = 5; + +interface VisualOrderProps { + orderTakerAddress: string; + orderMakerAddress: string; + makerAssetToken: AssetToken; + takerAssetToken: AssetToken; + makerToken: Token; + takerToken: Token; + networkId: number; + tokenByAddress: TokenByAddress; + isMakerTokenAddressInRegistry: boolean; + isTakerTokenAddressInRegistry: boolean; +} + +interface VisualOrderState {} + +export class VisualOrder extends React.Component { + public render() { + const allTokens = _.values(this.props.tokenByAddress); + const makerImage = this.props.makerToken.iconUrl; + const takerImage = this.props.takerToken.iconUrl; + return ( +
+
+
+ +
+
+
+ {this.renderAmount(this.props.takerAssetToken, this.props.takerToken)} +
+
+ +
+
+ {this.renderAmount(this.props.makerAssetToken, this.props.makerToken)} +
+
+
+ +
+
+
+ ); + } + private renderAmount(assetToken: AssetToken, token: Token) { + const unitAmount = ZeroEx.toUnitAmount(assetToken.amount, token.decimals); + return ( +
+ {unitAmount.toNumber().toFixed(PRECISION)} {token.symbol} +
+ ); + } +} diff --git a/packages/website/ts/containers/generate_order_form.tsx b/packages/website/ts/containers/generate_order_form.tsx new file mode 100644 index 000000000..97b5172e7 --- /dev/null +++ b/packages/website/ts/containers/generate_order_form.tsx @@ -0,0 +1,54 @@ +import * as _ from 'lodash'; +import * as React from 'react'; +import {connect} from 'react-redux'; +import {Store as ReduxStore, Dispatch} from 'redux'; +import {Dispatcher} from 'ts/redux/dispatcher'; +import {State} from 'ts/redux/reducer'; +import {Blockchain} from 'ts/blockchain'; +import {GenerateOrderForm as GenerateOrderFormComponent} from 'ts/components/generate_order/generate_order_form'; +import { + SideToAssetToken, + SignatureData, + HashData, + TokenByAddress, + TokenStateByAddress, + BlockchainErrs, +} from 'ts/types'; +import BigNumber from 'bignumber.js'; + +interface GenerateOrderFormProps { + blockchain: Blockchain; + hashData: HashData; + dispatcher: Dispatcher; +} + +interface ConnectedState { + blockchainErr: BlockchainErrs; + blockchainIsLoaded: boolean; + orderExpiryTimestamp: BigNumber; + orderSignatureData: SignatureData; + userAddress: string; + orderTakerAddress: string; + orderSalt: BigNumber; + networkId: number; + sideToAssetToken: SideToAssetToken; + tokenByAddress: TokenByAddress; + tokenStateByAddress: TokenStateByAddress; +} + +const mapStateToProps = (state: State, ownProps: GenerateOrderFormProps): ConnectedState => ({ + blockchainErr: state.blockchainErr, + blockchainIsLoaded: state.blockchainIsLoaded, + orderExpiryTimestamp: state.orderExpiryTimestamp, + orderSignatureData: state.orderSignatureData, + orderTakerAddress: state.orderTakerAddress, + orderSalt: state.orderSalt, + networkId: state.networkId, + sideToAssetToken: state.sideToAssetToken, + tokenByAddress: state.tokenByAddress, + tokenStateByAddress: state.tokenStateByAddress, + userAddress: state.userAddress, +}); + +export const GenerateOrderForm: React.ComponentClass = + connect(mapStateToProps)(GenerateOrderFormComponent); diff --git a/packages/website/ts/containers/portal.tsx b/packages/website/ts/containers/portal.tsx new file mode 100644 index 000000000..805058aa3 --- /dev/null +++ b/packages/website/ts/containers/portal.tsx @@ -0,0 +1,94 @@ +import * as _ from 'lodash'; +import * as React from 'react'; +import {connect} from 'react-redux'; +import {Store as ReduxStore, Dispatch} from 'redux'; +import {State} from 'ts/redux/reducer'; +import {constants} from 'ts/utils/constants'; +import {Dispatcher} from 'ts/redux/dispatcher'; +import { + Side, + HashData, + TokenByAddress, + BlockchainErrs, + Fill, + Order, + ScreenWidths, + TokenStateByAddress, +} from 'ts/types'; +import { + Portal as PortalComponent, + PortalAllProps as PortalComponentAllProps, + PortalPassedProps as PortalComponentPassedProps, +} from 'ts/components/portal'; +import BigNumber from 'bignumber.js'; + +interface MapStateToProps { + blockchainErr: BlockchainErrs; + blockchainIsLoaded: boolean; + hashData: HashData; + networkId: number; + nodeVersion: string; + orderFillAmount: number; + tokenByAddress: TokenByAddress; + tokenStateByAddress: TokenStateByAddress; + userEtherBalance: number; + screenWidth: ScreenWidths; + shouldBlockchainErrDialogBeOpen: boolean; + userAddress: string; + userSuppliedOrderCache: Order; +} + +interface ConnectedState {} + +interface ConnectedDispatch { + dispatcher: Dispatcher; +} + +const mapStateToProps = (state: State, ownProps: PortalComponentAllProps): ConnectedState => { + const receiveAssetToken = state.sideToAssetToken[Side.receive]; + const depositAssetToken = state.sideToAssetToken[Side.deposit]; + const receiveAddress = !_.isUndefined(receiveAssetToken.address) ? + receiveAssetToken.address : constants.NULL_ADDRESS; + const depositAddress = !_.isUndefined(depositAssetToken.address) ? + depositAssetToken.address : constants.NULL_ADDRESS; + const receiveAmount = !_.isUndefined(receiveAssetToken.amount) ? + receiveAssetToken.amount : new BigNumber(0); + const depositAmount = !_.isUndefined(depositAssetToken.amount) ? + depositAssetToken.amount : new BigNumber(0); + const hashData = { + depositAmount, + depositTokenContractAddr: depositAddress, + feeRecipientAddress: constants.FEE_RECIPIENT_ADDRESS, + makerFee: constants.MAKER_FEE, + orderExpiryTimestamp: state.orderExpiryTimestamp, + orderMakerAddress: state.userAddress, + orderTakerAddress: state.orderTakerAddress !== '' ? state.orderTakerAddress : constants.NULL_ADDRESS, + receiveAmount, + receiveTokenContractAddr: receiveAddress, + takerFee: constants.TAKER_FEE, + orderSalt: state.orderSalt, + }; + return { + blockchainErr: state.blockchainErr, + blockchainIsLoaded: state.blockchainIsLoaded, + networkId: state.networkId, + nodeVersion: state.nodeVersion, + orderFillAmount: state.orderFillAmount, + hashData, + screenWidth: state.screenWidth, + shouldBlockchainErrDialogBeOpen: state.shouldBlockchainErrDialogBeOpen, + tokenByAddress: state.tokenByAddress, + tokenStateByAddress: state.tokenStateByAddress, + userAddress: state.userAddress, + userEtherBalance: state.userEtherBalance, + userSuppliedOrderCache: state.userSuppliedOrderCache, + flashMessage: state.flashMessage, + }; +}; + +const mapDispatchToProps = (dispatch: Dispatch): ConnectedDispatch => ({ + dispatcher: new Dispatcher(dispatch), +}); + +export const Portal: React.ComponentClass = + connect(mapStateToProps, mapDispatchToProps)(PortalComponent); diff --git a/packages/website/ts/containers/smart_contracts_documentation.tsx b/packages/website/ts/containers/smart_contracts_documentation.tsx new file mode 100644 index 000000000..5d05bdd2f --- /dev/null +++ b/packages/website/ts/containers/smart_contracts_documentation.tsx @@ -0,0 +1,31 @@ +import * as _ from 'lodash'; +import * as React from 'react'; +import {connect} from 'react-redux'; +import {Store as ReduxStore, Dispatch} from 'redux'; +import {Dispatcher} from 'ts/redux/dispatcher'; +import {State} from 'ts/redux/reducer'; +import { + SmartContractsDocumentation as SmartContractsDocumentationComponent, + SmartContractsDocumentationAllProps, +} from 'ts/pages/documentation/smart_contracts_documentation'; + +interface ConnectedState { + docsVersion: string; + availableDocVersions: string[]; +} + +interface ConnectedDispatch { + dispatcher: Dispatcher; +} + +const mapStateToProps = (state: State, ownProps: SmartContractsDocumentationAllProps): ConnectedState => ({ + docsVersion: state.docsVersion, + availableDocVersions: state.availableDocVersions, +}); + +const mapDispatchToProps = (dispatch: Dispatch): ConnectedDispatch => ({ + dispatcher: new Dispatcher(dispatch), +}); + +export const SmartContractsDocumentation: React.ComponentClass = + connect(mapStateToProps, mapDispatchToProps)(SmartContractsDocumentationComponent); diff --git a/packages/website/ts/containers/zero_ex_js_documentation.tsx b/packages/website/ts/containers/zero_ex_js_documentation.tsx new file mode 100644 index 000000000..a5b8298b5 --- /dev/null +++ b/packages/website/ts/containers/zero_ex_js_documentation.tsx @@ -0,0 +1,33 @@ +import * as _ from 'lodash'; +import * as React from 'react'; +import {connect} from 'react-redux'; +import {Store as ReduxStore, Dispatch} from 'redux'; +import {Dispatcher} from 'ts/redux/dispatcher'; +import {State} from 'ts/redux/reducer'; +import {Blockchain} from 'ts/blockchain'; +import { + ZeroExJSDocumentation as ZeroExJSDocumentationComponent, + ZeroExJSDocumentationAllProps, +} from 'ts/pages/documentation/zero_ex_js_documentation'; +import BigNumber from 'bignumber.js'; + +interface ConnectedState { + docsVersion: string; + availableDocVersions: string[]; +} + +interface ConnectedDispatch { + dispatcher: Dispatcher; +} + +const mapStateToProps = (state: State, ownProps: ZeroExJSDocumentationAllProps): ConnectedState => ({ + docsVersion: state.docsVersion, + availableDocVersions: state.availableDocVersions, +}); + +const mapDispatchToProps = (dispatch: Dispatch): ConnectedDispatch => ({ + dispatcher: new Dispatcher(dispatch), +}); + +export const ZeroExJSDocumentation: React.ComponentClass = + connect(mapStateToProps, mapDispatchToProps)(ZeroExJSDocumentationComponent); diff --git a/packages/website/ts/globals.d.ts b/packages/website/ts/globals.d.ts new file mode 100644 index 000000000..ee449ecfd --- /dev/null +++ b/packages/website/ts/globals.d.ts @@ -0,0 +1,154 @@ +declare module 'react-tooltip'; +declare module 'react-router-hash-link'; +declare module 'es6-promisify'; +declare module 'truffle-contract'; +declare module 'ethereumjs-util'; +declare module 'keccak'; +declare module 'web3-provider-engine'; +declare module 'whatwg-fetch'; +declare module 'react-html5video'; +declare module 'web3-provider-engine/subproviders/filters'; +declare module 'thenby'; +declare module 'react-highlight'; +declare module 'react-recaptcha'; +declare module 'react-document-title'; +declare module 'ledgerco'; +declare module 'ethereumjs-tx'; + +declare module '*.json' { + const json: any; + /* tslint:disable */ + export default json; + /* tslint:enable */ +} + +// find-version declarations +declare function findVersions(version: string): string[]; +declare module 'find-versions' { + export = findVersions; +} + +// compare-version declarations +declare function compareVersions(firstVersion: string, secondVersion: string): number; +declare module 'compare-versions' { + export = compareVersions; +} + +// semver-sort declarations +declare module 'semver-sort' { + const desc: (versions: string[]) => string[]; +} + +// xml-js declarations +declare interface XML2JSONOpts { + compact?: boolean; + spaces?: number; +} +declare module 'xml-js' { + const xml2json: (xml: string, opts: XML2JSONOpts) => string; +} + +// This will be defined by default in TS 2.4 +// Source: https://github.com/Microsoft/TypeScript/issues/12364 +interface System { + import(module: string): Promise; +} +declare var System: System; + +// ethereum-address declarations +declare module 'ethereum-address' { + export const isAddress: (address: string) => boolean; +} + +// jsonschema declarations +// Source: https://github.com/tdegrunt/jsonschema/blob/master/lib/index.d.ts +declare interface Schema { + id?: string; + $schema?: string; + title?: string; + description?: string; + multipleOf?: number; + maximum?: number; + exclusiveMaximum?: boolean; + minimum?: number; + exclusiveMinimum?: boolean; + maxLength?: number; + minLength?: number; + pattern?: string; + additionalItems?: boolean | Schema; + items?: Schema | Schema[]; + maxItems?: number; + minItems?: number; + uniqueItems?: boolean; + maxProperties?: number; + minProperties?: number; + required?: string[]; + additionalProperties?: boolean | Schema; + definitions?: { + [name: string]: Schema; + }; + properties?: { + [name: string]: Schema; + }; + patternProperties?: { + [name: string]: Schema; + }; + dependencies?: { + [name: string]: Schema | string[]; + }; + 'enum'?: any[]; + type?: string | string[]; + allOf?: Schema[]; + anyOf?: Schema[]; + oneOf?: Schema[]; + not?: Schema; + // This is the only property that's not defined in https://github.com/tdegrunt/jsonschema/blob/master/lib/index.d.ts + // There is an open issue for that: https://github.com/tdegrunt/jsonschema/issues/194 + // There is also an opened PR: https://github.com/tdegrunt/jsonschema/pull/218/files + // As soon as it gets merged we should be good to use types from 'jsonschema' package + $ref?: string; +} + +// blockies declarations +declare interface BlockiesIcon { + toDataURL(): string; +} +declare interface BlockiesConfig { + seed: string; +} +declare function blockies(config: BlockiesConfig): BlockiesIcon; +declare module 'blockies' { + export = blockies; +} + +// is-mobile declarations +declare function isMobile(): boolean; +declare module 'is-mobile' { + export = isMobile; +} + +// web3-provider-engine declarations +declare class Subprovider {} +declare module 'web3-provider-engine/subproviders/subprovider' { + export = Subprovider; +} +declare class RpcSubprovider { + constructor(options: {rpcUrl: string}); + public handleRequest(payload: any, next: any, end: (err?: Error, data?: any) => void): void; +} +declare module 'web3-provider-engine/subproviders/rpc' { + export = RpcSubprovider; +} +declare class HookedWalletSubprovider { + constructor(wallet: any); +} +declare module 'web3-provider-engine/subproviders/hooked-wallet' { + export = HookedWalletSubprovider; +} + +declare interface Artifact { + abi: any; + networks: {[networkId: number]: { + address: string; + }}; +} diff --git a/packages/website/ts/index.tsx b/packages/website/ts/index.tsx new file mode 100644 index 000000000..ed4d09956 --- /dev/null +++ b/packages/website/ts/index.tsx @@ -0,0 +1,116 @@ +// Polyfills +import 'whatwg-fetch'; + +import * as React from 'react'; +import {render} from 'react-dom'; +import {Provider} from 'react-redux'; +import {createStore, Store as ReduxStore} from 'redux'; +import BigNumber from 'bignumber.js'; +import {constants} from 'ts/utils/constants'; +import {Landing} from 'ts/pages/landing/landing'; +import {FAQ} from 'ts/pages/faq/faq'; +import {About} from 'ts/pages/about/about'; +import {Wiki} from 'ts/pages/wiki/wiki'; +import {NotFound} from 'ts/pages/not_found'; +import {createLazyComponent} from 'ts/lazy_component'; +import {State, reducer} from 'ts/redux/reducer'; +import {colors, getMuiTheme, MuiThemeProvider} from 'material-ui/styles'; +import {Switch, BrowserRouter as Router, Route, Link, Redirect} from 'react-router-dom'; +import {tradeHistoryStorage} from 'ts/local_storage/trade_history_storage'; +import * as injectTapEventPlugin from 'react-tap-event-plugin'; +import {WebsitePaths} from 'ts/types'; +injectTapEventPlugin(); + +// By default BigNumber's `toString` method converts to exponential notation if the value has +// more then 20 digits. We want to avoid this behavior, so we set EXPONENTIAL_AT to a high number +BigNumber.config({ + EXPONENTIAL_AT: 1000, +}); + +// Check if we've introduced an update that requires us to clear the tradeHistory local storage entries +tradeHistoryStorage.clearIfRequired(); + +const CUSTOM_GREY = 'rgb(39, 39, 39)'; +const CUSTOM_GREEN = 'rgb(102, 222, 117)'; +const CUSTOM_DARKER_GREEN = 'rgb(77, 197, 92)'; + +import 'basscss/css/basscss.css'; +import 'less/all.less'; + +const muiTheme = getMuiTheme({ + appBar: { + height: 45, + color: 'white', + textColor: 'black', + }, + palette: { + pickerHeaderColor: constants.CUSTOM_BLUE, + primary1Color: constants.CUSTOM_BLUE, + primary2Color: constants.CUSTOM_BLUE, + textColor: colors.grey700, + }, + datePicker: { + color: colors.grey700, + textColor: 'white', + calendarTextColor: 'white', + selectColor: CUSTOM_GREY, + selectTextColor: 'white', + }, + timePicker: { + color: colors.grey700, + textColor: 'white', + accentColor: 'white', + headerColor: CUSTOM_GREY, + selectColor: CUSTOM_GREY, + selectTextColor: CUSTOM_GREY, + }, + toggle: { + thumbOnColor: CUSTOM_GREEN, + trackOnColor: CUSTOM_DARKER_GREEN, + }, +}); + +// We pass modulePromise returning lambda instead of module promise, +// cause we only want to import the module when the user navigates to the page. +// At the same time webpack statically parses for System.import() to determine bundle chunk split points +// so each lazy import needs it's own `System.import()` declaration. +const LazyPortal = createLazyComponent( + 'Portal', () => System.import(/* webpackChunkName: "portal" */'ts/containers/portal'), +); +const LazyZeroExJSDocumentation = createLazyComponent( + 'ZeroExJSDocumentation', + () => System.import(/* webpackChunkName: "zeroExDocs" */'ts/containers/zero_ex_js_documentation'), +); +const LazySmartContractsDocumentation = createLazyComponent( + 'SmartContractsDocumentation', + () => System.import(/* webpackChunkName: "smartContractDocs" */'ts/containers/smart_contracts_documentation'), +); + +const store: ReduxStore = createStore(reducer); +render( + +
+ + +
+ + + + + + + + + + + +
+
+
+
+
, + document.getElementById('app'), +); diff --git a/packages/website/ts/lazy_component.tsx b/packages/website/ts/lazy_component.tsx new file mode 100644 index 000000000..7052b7be6 --- /dev/null +++ b/packages/website/ts/lazy_component.tsx @@ -0,0 +1,66 @@ +import * as React from 'react'; +import * as _ from 'lodash'; + +interface LazyComponentProps { + reactComponentPromise: Promise>; + reactComponentProps: any; +} + +interface LazyComponentState { + component?: React.ComponentClass; +} + +/** + * This component is used for rendering components that are lazily loaded from other chunks. + * Source: https://reacttraining.com/react-router/web/guides/code-splitting + */ +export class LazyComponent extends React.Component { + constructor(props: LazyComponentProps) { + super(props); + this.state = { + component: undefined, + }; + } + public componentWillMount() { + this.loadComponentFireAndForgetAsync(this.props); + } + public componentWillReceiveProps(nextProps: LazyComponentProps) { + if (nextProps.reactComponentPromise !== this.props.reactComponentPromise) { + this.loadComponentFireAndForgetAsync(nextProps); + } + } + public render() { + return _.isUndefined(this.state.component) ? + null : + React.createElement(this.state.component, this.props.reactComponentProps); + } + private async loadComponentFireAndForgetAsync(props: LazyComponentProps) { + const component = await props.reactComponentPromise; + this.setState({ + component, + }); + } +} + +/** + * [createLazyComponent description] + * @param componentName name of exported component + * @param lazyImport lambda returning module promise + * we pass a lambda because we only want to require a module if it's used + * @example `const LazyPortal = createLazyComponent('Portal', () => System.import('ts/containers/portal'));`` + */ +export const createLazyComponent = (componentName: string, lazyImport: () => Promise) => { + return (props: any) => { + const reactComponentPromise = (async (): Promise> => { + const mod = await lazyImport(); + const component = mod[componentName]; + return component; + })(); + return ( + + ); + }; +}; diff --git a/packages/website/ts/local_storage/local_storage.ts b/packages/website/ts/local_storage/local_storage.ts new file mode 100644 index 000000000..d94e6877b --- /dev/null +++ b/packages/website/ts/local_storage/local_storage.ts @@ -0,0 +1,35 @@ +import * as _ from 'lodash'; + +export const localStorage = { + doesExist() { + return !!window.localStorage; + }, + getItemIfExists(key: string): string { + if (!this.doesExist) { + return undefined; + } + const item = window.localStorage.getItem(key); + if (_.isNull(item) || item === 'undefined') { + return ''; + } + return item; + }, + setItem(key: string, value: string) { + if (!this.doesExist || _.isUndefined(value)) { + return; + } + window.localStorage.setItem(key, value); + }, + removeItem(key: string) { + if (!this.doesExist) { + return; + } + window.localStorage.removeItem(key); + }, + getAllKeys(): string[] { + if (!this.doesExist) { + return []; + } + return _.keys(window.localStorage); + }, +}; diff --git a/packages/website/ts/local_storage/tracked_token_storage.ts b/packages/website/ts/local_storage/tracked_token_storage.ts new file mode 100644 index 000000000..0b54a66e0 --- /dev/null +++ b/packages/website/ts/local_storage/tracked_token_storage.ts @@ -0,0 +1,56 @@ +import * as _ from 'lodash'; +import {Token, TrackedTokensByNetworkId} from 'ts/types'; +import {localStorage} from 'ts/local_storage/local_storage'; + +const TRACKED_TOKENS_KEY = 'trackedTokens'; + +export const trackedTokenStorage = { + addTrackedTokenToUser(userAddress: string, networkId: number, token: Token) { + const trackedTokensByUserAddress = this.getTrackedTokensByUserAddress(); + let trackedTokensByNetworkId = trackedTokensByUserAddress[userAddress]; + if (_.isUndefined(trackedTokensByNetworkId)) { + trackedTokensByNetworkId = {}; + } + const trackedTokens = !_.isUndefined(trackedTokensByNetworkId[networkId]) ? + trackedTokensByNetworkId[networkId] : + []; + trackedTokens.push(token); + trackedTokensByNetworkId[networkId] = trackedTokens; + trackedTokensByUserAddress[userAddress] = trackedTokensByNetworkId; + const trackedTokensByUserAddressJSONString = JSON.stringify(trackedTokensByUserAddress); + localStorage.setItem(TRACKED_TOKENS_KEY, trackedTokensByUserAddressJSONString); + }, + getTrackedTokensByUserAddress(): TrackedTokensByNetworkId { + const trackedTokensJSONString = localStorage.getItemIfExists(TRACKED_TOKENS_KEY); + if (_.isEmpty(trackedTokensJSONString)) { + return {}; + } + const trackedTokensByUserAddress = JSON.parse(trackedTokensJSONString); + return trackedTokensByUserAddress; + }, + getTrackedTokensIfExists(userAddress: string, networkId: number): Token[] { + const trackedTokensJSONString = localStorage.getItemIfExists(TRACKED_TOKENS_KEY); + if (_.isEmpty(trackedTokensJSONString)) { + return undefined; + } + const trackedTokensByUserAddress = JSON.parse(trackedTokensJSONString); + const trackedTokensByNetworkId = trackedTokensByUserAddress[userAddress]; + if (_.isUndefined(trackedTokensByNetworkId)) { + return undefined; + } + const trackedTokens = trackedTokensByNetworkId[networkId]; + return trackedTokens; + }, + removeTrackedToken(userAddress: string, networkId: number, tokenAddress: string) { + const trackedTokensByUserAddress = this.getTrackedTokensByUserAddress(); + const trackedTokensByNetworkId = trackedTokensByUserAddress[userAddress]; + const trackedTokens = trackedTokensByNetworkId[networkId]; + const remainingTrackedTokens = _.filter(trackedTokens, (token: Token) => { + return token.address !== tokenAddress; + }); + trackedTokensByNetworkId[networkId] = remainingTrackedTokens; + trackedTokensByUserAddress[userAddress] = trackedTokensByNetworkId; + const trackedTokensByUserAddressJSONString = JSON.stringify(trackedTokensByUserAddress); + localStorage.setItem(TRACKED_TOKENS_KEY, trackedTokensByUserAddressJSONString); + }, +}; diff --git a/packages/website/ts/local_storage/trade_history_storage.tsx b/packages/website/ts/local_storage/trade_history_storage.tsx new file mode 100644 index 000000000..415b380b7 --- /dev/null +++ b/packages/website/ts/local_storage/trade_history_storage.tsx @@ -0,0 +1,82 @@ +import * as _ from 'lodash'; +import {Fill} from 'ts/types'; +import {configs} from 'ts/utils/configs'; +import {constants} from 'ts/utils/constants'; +import {localStorage} from 'ts/local_storage/local_storage'; +import ethUtil = require('ethereumjs-util'); +import BigNumber from 'bignumber.js'; + +const FILLS_KEY = 'fills'; +const FILLS_LATEST_BLOCK = 'fillsLatestBlock'; +const FILL_CLEAR_KEY = 'lastClearFillDate'; + +export const tradeHistoryStorage = { + // Clear all fill related localStorage if we've updated the config variable in an update + // that introduced a backward incompatible change requiring the user to re-fetch the fills from + // the blockchain + clearIfRequired() { + const lastClearFillDate = localStorage.getItemIfExists(FILL_CLEAR_KEY); + if (lastClearFillDate !== configs.lastLocalStorageFillClearanceDate) { + const localStorageKeys = localStorage.getAllKeys(); + _.each(localStorageKeys, key => { + if (_.startsWith(key, `${FILLS_KEY}-`) || _.startsWith(key, `${FILLS_LATEST_BLOCK}-`)) { + localStorage.removeItem(key); + } + }); + } + localStorage.setItem(FILL_CLEAR_KEY, configs.lastLocalStorageFillClearanceDate); + }, + addFillToUser(userAddress: string, networkId: number, fill: Fill) { + const fillsByHash = this.getUserFillsByHash(userAddress, networkId); + const fillHash = this._getFillHash(fill); + const doesFillExist = !_.isUndefined(fillsByHash[fillHash]); + if (doesFillExist) { + return; + } + fillsByHash[fillHash] = fill; + const userFillsJSONString = JSON.stringify(fillsByHash); + const userFillsKey = this._getUserFillsKey(userAddress, networkId); + localStorage.setItem(userFillsKey, userFillsJSONString); + }, + getUserFillsByHash(userAddress: string, networkId: number): {[fillHash: string]: Fill} { + const userFillsKey = this._getUserFillsKey(userAddress, networkId); + const userFillsJSONString = localStorage.getItemIfExists(userFillsKey); + if (_.isEmpty(userFillsJSONString)) { + return {}; + } + const userFillsByHash = JSON.parse(userFillsJSONString); + _.each(userFillsByHash, (fill, hash) => { + fill.paidMakerFee = new BigNumber(fill.paidMakerFee); + fill.paidTakerFee = new BigNumber(fill.paidTakerFee); + fill.filledTakerTokenAmount = new BigNumber(fill.filledTakerTokenAmount); + fill.filledMakerTokenAmount = new BigNumber(fill.filledMakerTokenAmount); + }); + return userFillsByHash; + }, + getFillsLatestBlock(userAddress: string, networkId: number): number { + const userFillsLatestBlockKey = this._getFillsLatestBlockKey(userAddress, networkId); + const blockNumberStr = localStorage.getItemIfExists(userFillsLatestBlockKey); + if (_.isEmpty(blockNumberStr)) { + return constants.GENESIS_ORDER_BLOCK_BY_NETWORK_ID[networkId]; + } + const blockNumber = _.parseInt(blockNumberStr); + return blockNumber; + }, + setFillsLatestBlock(userAddress: string, networkId: number, blockNumber: number) { + const userFillsLatestBlockKey = this._getFillsLatestBlockKey(userAddress, networkId); + localStorage.setItem(userFillsLatestBlockKey, `${blockNumber}`); + }, + _getUserFillsKey(userAddress: string, networkId: number) { + const userFillsKey = `${FILLS_KEY}-${userAddress}-${networkId}`; + return userFillsKey; + }, + _getFillsLatestBlockKey(userAddress: string, networkId: number) { + const userFillsLatestBlockKey = `${FILLS_LATEST_BLOCK}-${userAddress}-${networkId}`; + return userFillsLatestBlockKey; + }, + _getFillHash(fill: Fill): string { + const fillJSON = JSON.stringify(fill); + const fillHash = ethUtil.sha256(fillJSON); + return fillHash.toString('hex'); + }, +}; diff --git a/packages/website/ts/pages/about/about.tsx b/packages/website/ts/pages/about/about.tsx new file mode 100644 index 000000000..8859fb00a --- /dev/null +++ b/packages/website/ts/pages/about/about.tsx @@ -0,0 +1,253 @@ +import * as _ from 'lodash'; +import * as React from 'react'; +import * as DocumentTitle from 'react-document-title'; +import RaisedButton from 'material-ui/RaisedButton'; +import {colors} from 'material-ui/styles'; +import {Styles, ProfileInfo} from 'ts/types'; +import {utils} from 'ts/utils/utils'; +import {Link} from 'react-router-dom'; +import {Footer} from 'ts/components/footer'; +import {TopBar} from 'ts/components/top_bar'; +import {Question} from 'ts/pages/faq/question'; +import {configs} from 'ts/utils/configs'; +import {constants} from 'ts/utils/constants'; +import {Profile} from 'ts/pages/about/profile'; + +const CUSTOM_BACKGROUND_COLOR = '#F0F0F0'; +const CUSTOM_GRAY = '#4C4C4C'; +const CUSTOM_LIGHT_GRAY = '#A2A2A2'; + +const teamRow1: ProfileInfo[] = [ + { + name: 'Will Warren', + title: 'Co-founder & CEO', + description: `Smart contract R&D. Previously applied physics at Los Alamos \ + Nat Lab. Mechanical engineering at UC San Diego. PhD dropout.`, + image: '/images/team/will.jpg', + linkedIn: 'https://www.linkedin.com/in/will-warren-92aab62b/', + github: 'https://github.com/willwarren89', + medium: 'https://medium.com/@willwarren89', + }, + { + name: 'Amir Bandeali', + title: 'Co-founder & CTO', + description: `Smart contract R&D. Previously fixed income trader at DRW. \ + Finance at University of Illinois, Urbana-Champaign.`, + image: '/images/team/amir.jpeg', + linkedIn: 'https://www.linkedin.com/in/abandeali1/', + github: 'https://github.com/abandeali1', + medium: 'https://medium.com/@abandeali1', + }, + { + name: 'Fabio Berger', + title: 'Senior Engineer', + description: `Full-stack blockchain engineer. Previously software engineer \ + at Airtable and founder of WealthLift. Computer science at Duke.`, + image: '/images/team/fabio.jpg', + linkedIn: 'https://www.linkedin.com/in/fabio-berger-03ab261a/', + github: 'https://github.com/fabioberger', + medium: 'https://medium.com/@fabioberger', + }, + { + name: 'Alex Xu', + title: 'Director of Operations', + description: `Strategy and operations. Previously digital marketing at Google \ + and vendor management at Amazon. Economics at UC San Diego.`, + image: '/images/team/alex.jpg', + linkedIn: 'https://www.linkedin.com/in/alex-xu/', + github: '', + medium: '', + }, +]; + +const teamRow2: ProfileInfo[] = [ + { + name: 'Leonid Logvinov', + title: 'Engineer', + description: `Full-stack blockchain engineer. Previously blockchain engineer \ + at Neufund. Computer science at University of Warsaw.`, + image: '/images/team/leonid.png', + linkedIn: 'https://www.linkedin.com/in/leonidlogvinov/', + github: 'https://github.com/LogvinovLeon', + medium: '', + }, + { + name: 'Ben Burns', + title: 'Designer', + description: `Product, motion, and graphic designer. Previously designer \ + at Airtable and Apple. Digital Design at University of Cincinnati.`, + image: '/images/team/ben.jpg', + linkedIn: 'https://www.linkedin.com/in/ben-burns-30170478/', + github: '', + medium: '', + }, + { + name: 'Philippe Castonguay', + title: 'Dev Relations Manager', + description: `Developer relations. Previously computational neuroscience \ + research at Janelia. Statistics at Western University. MA Dropout.`, + image: '/images/team/philippe.png', + linkedIn: '', + github: 'https://github.com/PhABC', + medium: '', + }, + { + name: 'Brandon Millman', + title: 'Senior Engineer', + description: `Full-stack engineer. Previously senior software engineer at \ + Twitter. Electrical and Computer Engineering at Duke.`, + image: '/images/team/brandon.png', + linkedIn: 'https://www.linkedin.com/company-beta/17942619/', + }, +]; + +const advisors: ProfileInfo[] = [ + { + name: 'Fred Ehrsam', + description: 'Co-founder of Coinbase. Previously FX trader at Goldman Sachs.', + image: '/images/advisors/fred.jpg', + linkedIn: 'https://www.linkedin.com/in/fredehrsam/', + medium: 'https://medium.com/@FEhrsam', + twitter: 'https://twitter.com/FEhrsam', + }, + { + name: 'Olaf Carlson-Wee', + image: '/images/advisors/olaf.png', + description: 'Founder of Polychain Capital. First hire at Coinbase. Angel investor.', + linkedIn: 'https://www.linkedin.com/in/olafcw/', + angellist: 'https://angel.co/olafcw', + }, + { + name: 'Joey Krug', + description: `Co-CIO at Pantera Capital. Founder of Augur. Thiel 20 Under 20 Fellow.`, + image: '/images/advisors/joey.jpg', + linkedIn: 'https://www.linkedin.com/in/joeykrug/', + github: 'https://github.com/joeykrug', + angellist: 'https://angel.co/joeykrug', + }, + { + name: 'Linda Xie', + description: 'Co-founder of Scalar Capital. Previously PM at Coinbase.', + image: '/images/advisors/linda.jpg', + linkedIn: 'https://www.linkedin.com/in/lindaxie/', + medium: 'https://medium.com/@linda.xie', + twitter: 'https://twitter.com/ljxie', + }, +]; + +export interface AboutProps { + source: string; + location: Location; +} + +interface AboutState {} + +const styles: Styles = { + header: { + fontFamily: 'Roboto Mono', + fontSize: 36, + color: 'black', + paddingTop: 110, + }, +}; + +export class About extends React.Component { + public componentDidMount() { + window.scrollTo(0, 0); + } + public render() { + return ( +
+ + +
+
+
+ About us: +
+
+ Our team is a diverse and globally distributed group with backgrounds + in engineering, research, business and design. We are passionate about + decentralized technology and its potential to act as an equalizing force + in the world. +
+
+
+
+ {this.renderProfiles(teamRow1)} +
+
+ {this.renderProfiles(teamRow2)} +
+
+
+
+ Advisors: +
+
+ {this.renderProfiles(advisors)} +
+
+
+
+ WE'RE HIRING +
+
+ We are seeking outstanding candidates to{' '} + + join our team + + . We value passion, diversity and unique perspectives. +
+
+
+
+
+ ); + } + private renderProfiles(profiles: ProfileInfo[]) { + const numIndiv = profiles.length; + const colSize = utils.getColSize(profiles.length); + return _.map(profiles, profile => { + return ( +
+ +
+ ); + }); + } +} diff --git a/packages/website/ts/pages/about/profile.tsx b/packages/website/ts/pages/about/profile.tsx new file mode 100644 index 000000000..6c48a8553 --- /dev/null +++ b/packages/website/ts/pages/about/profile.tsx @@ -0,0 +1,99 @@ +import * as _ from 'lodash'; +import * as React from 'react'; +import {colors} from 'material-ui/styles'; +import {utils} from 'ts/utils/utils'; +import {Element as ScrollElement} from 'react-scroll'; +import {Styles, ProfileInfo} from 'ts/types'; + +const IMAGE_DIMENSION = 149; +const styles: Styles = { + subheader: { + textTransform: 'uppercase', + fontSize: 32, + margin: 0, + }, + imageContainer: { + width: IMAGE_DIMENSION, + height: IMAGE_DIMENSION, + boxShadow: 'rgba(0, 0, 0, 0.19) 2px 5px 10px', + }, +}; + +interface ProfileProps { + colSize: number; + profileInfo: ProfileInfo; +} + +export function Profile(props: ProfileProps) { + return ( +
+
+
+ +
+
+ {props.profileInfo.name} +
+ {!_.isUndefined(props.profileInfo.title) && +
+ {props.profileInfo.title.toUpperCase()} +
+ } +
+ {props.profileInfo.description} +
+
+ {renderSocialMediaIcons(props.profileInfo)} +
+
+
+ ); +} + +function renderSocialMediaIcons(profileInfo: ProfileInfo) { + const icons = [ + renderSocialMediaIcon('zmdi-github-box', profileInfo.github), + renderSocialMediaIcon('zmdi-linkedin-box', profileInfo.linkedIn), + renderSocialMediaIcon('zmdi-twitter-box', profileInfo.twitter), + ]; + return icons; +} + +function renderSocialMediaIcon(iconName: string, url: string) { + if (_.isEmpty(url)) { + return null; + } + + return ( +
+ + + +
+ ); +} diff --git a/packages/website/ts/pages/documentation/comment.tsx b/packages/website/ts/pages/documentation/comment.tsx new file mode 100644 index 000000000..78bbdc069 --- /dev/null +++ b/packages/website/ts/pages/documentation/comment.tsx @@ -0,0 +1,24 @@ +import * as _ from 'lodash'; +import * as React from 'react'; +import * as ReactMarkdown from 'react-markdown'; +import {MarkdownCodeBlock} from 'ts/pages/shared/markdown_code_block'; + +interface CommentProps { + comment: string; + className?: string; +} + +const defaultProps = { + className: '', +}; + +export const Comment: React.SFC = (props: CommentProps) => { + return ( +
+ +
+ ); +}; diff --git a/packages/website/ts/pages/documentation/custom_enum.tsx b/packages/website/ts/pages/documentation/custom_enum.tsx new file mode 100644 index 000000000..aca8af832 --- /dev/null +++ b/packages/website/ts/pages/documentation/custom_enum.tsx @@ -0,0 +1,31 @@ +import * as _ from 'lodash'; +import * as React from 'react'; +import {utils} from 'ts/utils/utils'; +import {CustomType} from 'ts/types'; + +const STRING_ENUM_CODE_PREFIX = ' strEnum('; + +interface CustomEnumProps { + type: CustomType; +} + +// This component renders custom string enums that was a work-around for versions of +// TypeScript <2.4.0 that did not support them natively. We keep it around to support +// older versions of 0x.js <0.9.0 +export function CustomEnum(props: CustomEnumProps) { + const type = props.type; + if (!_.startsWith(type.defaultValue, STRING_ENUM_CODE_PREFIX)) { + utils.consoleLog('We do not yet support `Variable` types that are not strEnums'); + return null; + } + // Remove the prefix and postfix, leaving only the strEnum values without quotes. + const enumValues = type.defaultValue.slice(10, -3).replace(/'/g, ''); + return ( + + {`{`} + {'\t'}{enumValues} +
+ {`}`} +
+ ); +} diff --git a/packages/website/ts/pages/documentation/enum.tsx b/packages/website/ts/pages/documentation/enum.tsx new file mode 100644 index 000000000..9364a5d31 --- /dev/null +++ b/packages/website/ts/pages/documentation/enum.tsx @@ -0,0 +1,26 @@ +import * as _ from 'lodash'; +import * as React from 'react'; +import {utils} from 'ts/utils/utils'; +import {TypeDocNode, EnumValue} from 'ts/types'; + +const STRING_ENUM_CODE_PREFIX = ' strEnum('; + +interface EnumProps { + values: EnumValue[]; +} + +export function Enum(props: EnumProps) { + const values = _.map(props.values, (value, i) => { + const isLast = i === props.values.length - 1; + const defaultValueIfAny = !_.isUndefined(value.defaultValue) ? ` = ${value.defaultValue}` : ''; + return `\n\t${value.name}${defaultValueIfAny},`; + }); + return ( + + {`{`} + {values} +
+ {`}`} +
+ ); +} diff --git a/packages/website/ts/pages/documentation/event_definition.tsx b/packages/website/ts/pages/documentation/event_definition.tsx new file mode 100644 index 000000000..58271e98f --- /dev/null +++ b/packages/website/ts/pages/documentation/event_definition.tsx @@ -0,0 +1,80 @@ +import * as _ from 'lodash'; +import * as React from 'react'; +import {constants} from 'ts/utils/constants'; +import {utils} from 'ts/utils/utils'; +import {Event, EventArg, HeaderSizes} from 'ts/types'; +import {Type} from 'ts/pages/documentation/type'; +import {AnchorTitle} from 'ts/pages/shared/anchor_title'; + +const KEYWORD_COLOR = '#a81ca6'; +const CUSTOM_GREEN = 'rgb(77, 162, 75)'; + +interface EventDefinitionProps { + event: Event; +} + +interface EventDefinitionState { + shouldShowAnchor: boolean; +} + +export class EventDefinition extends React.Component { + constructor(props: EventDefinitionProps) { + super(props); + this.state = { + shouldShowAnchor: false, + }; + } + public render() { + const event = this.props.event; + return ( +
+ +
+
+                        
+                            {this.renderEventCode()}
+                        
+                    
+
+
+ ); + } + private renderEventCode() { + const indexed = indexed; + const eventArgs = _.map(this.props.event.eventArgs, (eventArg: EventArg) => { + return ( + + {eventArg.name}{eventArg.isIndexed ? indexed : ''}: , + + ); + }); + const argList = _.reduce(eventArgs, (prev: React.ReactNode, curr: React.ReactNode) => { + return [prev, '\n\t', curr]; + }); + return ( + + {`{`} +
+ {'\t'}{argList} +
+ {`}`} +
+ ); + } + private setAnchorVisibility(shouldShowAnchor: boolean) { + this.setState({ + shouldShowAnchor, + }); + } +} diff --git a/packages/website/ts/pages/documentation/interface.tsx b/packages/website/ts/pages/documentation/interface.tsx new file mode 100644 index 000000000..9e40b8901 --- /dev/null +++ b/packages/website/ts/pages/documentation/interface.tsx @@ -0,0 +1,54 @@ +import * as _ from 'lodash'; +import * as React from 'react'; +import {CustomType, TypeDocTypes} from 'ts/types'; +import {Type} from 'ts/pages/documentation/type'; +import {MethodSignature} from 'ts/pages/documentation/method_signature'; + +interface InterfaceProps { + type: CustomType; +} + +export function Interface(props: InterfaceProps) { + const type = props.type; + const properties = _.map(type.children, property => { + return ( + + {property.name}:{' '} + {property.type.typeDocType !== TypeDocTypes.Reflection ? + : + + }, + + ); + }); + const hasIndexSignature = !_.isUndefined(type.indexSignature); + if (hasIndexSignature) { + const is = type.indexSignature; + const param = ( + + {is.keyName}: + + ); + properties.push(( + + [{param}]: {is.valueName}, + + )); + } + const propertyList = _.reduce(properties, (prev: React.ReactNode, curr: React.ReactNode) => { + return [prev, '\n\t', curr]; + }); + return ( + + {`{`} +
+ {'\t'}{propertyList} +
+ {`}`} +
+ ); +} diff --git a/packages/website/ts/pages/documentation/method_block.tsx b/packages/website/ts/pages/documentation/method_block.tsx new file mode 100644 index 000000000..6fead2f47 --- /dev/null +++ b/packages/website/ts/pages/documentation/method_block.tsx @@ -0,0 +1,174 @@ +import * as _ from 'lodash'; +import * as React from 'react'; +import * as ReactMarkdown from 'react-markdown'; +import {Chip} from 'material-ui/Chip'; +import {colors} from 'material-ui/styles'; +import { + TypeDocNode, + Styles, + TypeDefinitionByName, + TypescriptMethod, + SolidityMethod, + Parameter, + HeaderSizes, +} from 'ts/types'; +import {utils} from 'ts/utils/utils'; +import {SourceLink} from 'ts/pages/documentation/source_link'; +import {MethodSignature} from 'ts/pages/documentation/method_signature'; +import {AnchorTitle} from 'ts/pages/shared/anchor_title'; +import {Comment} from 'ts/pages/documentation/comment'; +import {typeDocUtils} from 'ts/utils/typedoc_utils'; + +interface MethodBlockProps { + method: SolidityMethod|TypescriptMethod; + libraryVersion: string; + typeDefinitionByName: TypeDefinitionByName; +} + +interface MethodBlockState { + shouldShowAnchor: boolean; +} + +const styles: Styles = { + chip: { + fontSize: 13, + backgroundColor: colors.lightBlueA700, + color: 'white', + height: 11, + borderRadius: 14, + marginTop: 19, + lineHeight: 0.8, + }, +}; + +export class MethodBlock extends React.Component { + constructor(props: MethodBlockProps) { + super(props); + this.state = { + shouldShowAnchor: false, + }; + } + public render() { + const method = this.props.method; + if (typeDocUtils.isPrivateOrProtectedProperty(method.name)) { + return null; + } + + return ( +
+ {!method.isConstructor && +
+ {(method as TypescriptMethod).isStatic && + this.renderChip('Static') + } + {(method as SolidityMethod).isConstant && + this.renderChip('Constant') + } + {(method as SolidityMethod).isPayable && + this.renderChip('Payable') + } + +
+ } + + + + {(method as TypescriptMethod).source && + + } + {method.comment && + + } + {method.parameters && !_.isEmpty(method.parameters) && +
+

+ ARGUMENTS +

+ {this.renderParameterDescriptions(method.parameters)} +
+ } + {method.returnComment && +
+

+ RETURNS +

+ +
+ } +
+ ); + } + private renderChip(text: string) { + return ( +
+ {text} +
+ ); + } + private renderParameterDescriptions(parameters: Parameter[]) { + const descriptions = _.map(parameters, parameter => { + const isOptional = parameter.isOptional; + return ( +
+
+
+
+ {parameter.name} +
+
+ {isOptional && 'optional'} +
+
+
+ {parameter.comment && + + } +
+
+ ); + }); + return descriptions; + } + private setAnchorVisibility(shouldShowAnchor: boolean) { + this.setState({ + shouldShowAnchor, + }); + } +} diff --git a/packages/website/ts/pages/documentation/method_signature.tsx b/packages/website/ts/pages/documentation/method_signature.tsx new file mode 100644 index 000000000..3b5d2ce78 --- /dev/null +++ b/packages/website/ts/pages/documentation/method_signature.tsx @@ -0,0 +1,62 @@ +import * as _ from 'lodash'; +import * as React from 'react'; +import {TypescriptMethod, SolidityMethod, TypeDefinitionByName, Parameter} from 'ts/types'; +import {Type} from 'ts/pages/documentation/type'; + +interface MethodSignatureProps { + method: TypescriptMethod|SolidityMethod; + shouldHideMethodName?: boolean; + shouldUseArrowSyntax?: boolean; + typeDefinitionByName?: TypeDefinitionByName; +} + +const defaultProps = { + shouldHideMethodName: false, + shouldUseArrowSyntax: false, +}; + +export const MethodSignature: React.SFC = (props: MethodSignatureProps) => { + const parameters = renderParameters(props.method, props.typeDefinitionByName); + const paramString = _.reduce(parameters, (prev: React.ReactNode, curr: React.ReactNode) => { + return [prev, ', ', curr]; + }); + const methodName = props.shouldHideMethodName ? '' : props.method.name; + const typeParameterIfExists = _.isUndefined((props.method as TypescriptMethod).typeParameter) ? + undefined : + renderTypeParameter(props.method, props.typeDefinitionByName); + return ( + + {props.method.callPath}{methodName}{typeParameterIfExists}({paramString}) + {props.shouldUseArrowSyntax ? ' => ' : ': '} + {' '} + {props.method.returnType && + + } + + ); +}; + +function renderParameters(method: TypescriptMethod|SolidityMethod, typeDefinitionByName?: TypeDefinitionByName) { + const parameters = method.parameters; + const params = _.map(parameters, (p: Parameter) => { + const isOptional = p.isOptional; + return ( + + {p.name}{isOptional && '?'}: + + ); + }); + return params; +} + +function renderTypeParameter(method: TypescriptMethod, typeDefinitionByName?: TypeDefinitionByName) { + const typeParameter = method.typeParameter; + const typeParam = ( + + {`<${typeParameter.name} extends `} + + {`>`} + + ); + return typeParam; +} diff --git a/packages/website/ts/pages/documentation/smart_contracts_documentation.tsx b/packages/website/ts/pages/documentation/smart_contracts_documentation.tsx new file mode 100644 index 000000000..3e97829c4 --- /dev/null +++ b/packages/website/ts/pages/documentation/smart_contracts_documentation.tsx @@ -0,0 +1,401 @@ +import * as _ from 'lodash'; +import * as React from 'react'; +import DocumentTitle = require('react-document-title'); +import findVersions = require('find-versions'); +import semverSort = require('semver-sort'); +import {colors} from 'material-ui/styles'; +import CircularProgress from 'material-ui/CircularProgress'; +import { + scroller, +} from 'react-scroll'; +import {Dispatcher} from 'ts/redux/dispatcher'; +import { + SmartContractsDocSections, + Styles, + DoxityDocObj, + TypeDefinitionByName, + DocAgnosticFormat, + SolidityMethod, + Property, + CustomType, + MenuSubsectionsBySection, + Event, + Docs, + AddressByContractName, + Networks, + EtherscanLinkSuffixes, +} from 'ts/types'; +import {TopBar} from 'ts/components/top_bar'; +import {utils} from 'ts/utils/utils'; +import {docUtils} from 'ts/utils/doc_utils'; +import {constants} from 'ts/utils/constants'; +import {MethodBlock} from 'ts/pages/documentation/method_block'; +import {SourceLink} from 'ts/pages/documentation/source_link'; +import {Type} from 'ts/pages/documentation/type'; +import {TypeDefinition} from 'ts/pages/documentation/type_definition'; +import {MarkdownSection} from 'ts/pages/shared/markdown_section'; +import {Comment} from 'ts/pages/documentation/comment'; +import {Badge} from 'ts/components/ui/badge'; +import {EventDefinition} from 'ts/pages/documentation/event_definition'; +import {AnchorTitle} from 'ts/pages/shared/anchor_title'; +import {SectionHeader} from 'ts/pages/shared/section_header'; +import {NestedSidebarMenu} from 'ts/pages/shared/nested_sidebar_menu'; +import {doxityUtils} from 'ts/utils/doxity_utils'; +/* tslint:disable:no-var-requires */ +const IntroMarkdown = require('md/docs/smart_contracts/introduction'); +/* tslint:enable:no-var-requires */ + +const SCROLL_TO_TIMEOUT = 500; +const CUSTOM_PURPLE = '#690596'; +const CUSTOM_RED = '#e91751'; +const CUSTOM_TURQUOIS = '#058789'; +const DOC_JSON_ROOT = constants.S3_SMART_CONTRACTS_DOCUMENTATION_JSON_ROOT; + +const sectionNameToMarkdown = { + [SmartContractsDocSections.Introduction]: IntroMarkdown, +}; +const networkNameToColor: {[network: string]: string} = { + [Networks.kovan]: CUSTOM_PURPLE, + [Networks.ropsten]: CUSTOM_RED, + [Networks.mainnet]: CUSTOM_TURQUOIS, +}; + +export interface SmartContractsDocumentationAllProps { + source: string; + location: Location; + dispatcher: Dispatcher; + docsVersion: string; + availableDocVersions: string[]; +} + +interface SmartContractsDocumentationState { + docAgnosticFormat?: DocAgnosticFormat; +} + +const styles: Styles = { + mainContainers: { + position: 'absolute', + top: 60, + left: 0, + bottom: 0, + right: 0, + overflowZ: 'hidden', + overflowY: 'scroll', + minHeight: 'calc(100vh - 60px)', + WebkitOverflowScrolling: 'touch', + }, + menuContainer: { + borderColor: colors.grey300, + maxWidth: 330, + marginLeft: 20, + }, +}; + +export class SmartContractsDocumentation extends + React.Component { + constructor(props: SmartContractsDocumentationAllProps) { + super(props); + this.state = { + docAgnosticFormat: undefined, + }; + } + public componentWillMount() { + const pathName = this.props.location.pathname; + const lastSegment = pathName.substr(pathName.lastIndexOf('/') + 1); + const versions = findVersions(lastSegment); + const preferredVersionIfExists = versions.length > 0 ? versions[0] : undefined; + this.fetchJSONDocsFireAndForgetAsync(preferredVersionIfExists); + } + public render() { + const menuSubsectionsBySection = _.isUndefined(this.state.docAgnosticFormat) + ? {} + : this.getMenuSubsectionsBySection(this.state.docAgnosticFormat); + return ( +
+ + + {_.isUndefined(this.state.docAgnosticFormat) ? +
+
+
+ +
+
Loading documentation...
+
+
: +
+
+
+ +
+
+
+
+
+

+ + 0x Smart Contracts + +

+ {this.renderDocumentation()} +
+
+
+ } +
+ ); + } + private renderDocumentation(): React.ReactNode { + const subMenus = _.values(constants.menuSmartContracts); + const orderedSectionNames = _.flatten(subMenus); + // Since smart contract method params are all base types, no need to pass + // down the typeDefinitionByName + const typeDefinitionByName = {}; + const sections = _.map(orderedSectionNames, this.renderSection.bind(this, typeDefinitionByName)); + + return sections; + } + private renderSection(typeDefinitionByName: TypeDefinitionByName, sectionName: string): React.ReactNode { + const docSection = this.state.docAgnosticFormat[sectionName]; + + const markdownFileIfExists = sectionNameToMarkdown[sectionName]; + if (!_.isUndefined(markdownFileIfExists)) { + return ( + + ); + } + + if (_.isUndefined(docSection)) { + return null; + } + + const sortedProperties = _.sortBy(docSection.properties, 'name'); + const propertyDefs = _.map(sortedProperties, this.renderProperty.bind(this)); + + const sortedMethods = _.sortBy(docSection.methods, 'name'); + const methodDefs = _.map(sortedMethods, method => { + const isConstructor = false; + return this.renderMethodBlocks(method, sectionName, isConstructor, typeDefinitionByName); + }); + + const sortedEvents = _.sortBy(docSection.events, 'name'); + const eventDefs = _.map(sortedEvents, (event: Event, i: number) => { + return ( + + ); + }); + return ( +
+
+
+ +
+ {this.renderNetworkBadges(sectionName)} +
+ {docSection.comment && + + } + {docSection.constructors.length > 0 && +
+

Constructor

+ {this.renderConstructors(docSection.constructors, typeDefinitionByName)} +
+ } + {docSection.properties.length > 0 && +
+

Properties

+
{propertyDefs}
+
+ } + {docSection.methods.length > 0 && +
+

Methods

+
{methodDefs}
+
+ } + {docSection.events.length > 0 && +
+

Events

+
{eventDefs}
+
+ } +
+ ); + } + private renderNetworkBadges(sectionName: string) { + const networkToAddressByContractName = constants.contractAddresses[this.props.docsVersion]; + const badges = _.map(networkToAddressByContractName, + (addressByContractName: AddressByContractName, networkName: string) => { + const contractAddress = addressByContractName[sectionName]; + const linkIfExists = utils.getEtherScanLinkIfExists( + contractAddress, constants.networkIdByName[networkName], EtherscanLinkSuffixes.address, + ); + return ( + + + + ); + }); + return badges; + } + private renderConstructors(constructors: SolidityMethod[], + typeDefinitionByName: TypeDefinitionByName): React.ReactNode { + const constructorDefs = _.map(constructors, constructor => { + return this.renderMethodBlocks( + constructor, SmartContractsDocSections.zeroEx, constructor.isConstructor, typeDefinitionByName, + ); + }); + return ( +
+ {constructorDefs} +
+ ); + } + private renderProperty(property: Property): React.ReactNode { + return ( +
+ + {property.name}: + + {property.source && + + } + {property.comment && + + } +
+ ); + } + private renderMethodBlocks(method: SolidityMethod, sectionName: string, isConstructor: boolean, + typeDefinitionByName: TypeDefinitionByName): React.ReactNode { + return ( + + ); + } + private scrollToHash(): void { + const hashWithPrefix = this.props.location.hash; + let hash = hashWithPrefix.slice(1); + if (_.isEmpty(hash)) { + hash = 'smartContractsDocs'; // scroll to the top + } + + scroller.scrollTo(hash, {duration: 0, offset: 0, containerId: 'documentation'}); + } + private getMenuSubsectionsBySection(docAgnosticFormat?: DocAgnosticFormat): MenuSubsectionsBySection { + const menuSubsectionsBySection = {} as MenuSubsectionsBySection; + if (_.isUndefined(docAgnosticFormat)) { + return menuSubsectionsBySection; + } + + const docSections = _.keys(SmartContractsDocSections); + _.each(docSections, sectionName => { + const docSection = docAgnosticFormat[sectionName]; + if (_.isUndefined(docSection)) { + return; // no-op + } + + if (sectionName === SmartContractsDocSections.types) { + const sortedTypesNames = _.sortBy(docSection.types, 'name'); + const typeNames = _.map(sortedTypesNames, t => t.name); + menuSubsectionsBySection[sectionName] = typeNames; + } else { + const sortedEventNames = _.sortBy(docSection.events, 'name'); + const eventNames = _.map(sortedEventNames, m => m.name); + const sortedMethodNames = _.sortBy(docSection.methods, 'name'); + const methodNames = _.map(sortedMethodNames, m => m.name); + menuSubsectionsBySection[sectionName] = [...methodNames, ...eventNames]; + } + }); + return menuSubsectionsBySection; + } + private async fetchJSONDocsFireAndForgetAsync(preferredVersionIfExists?: string): Promise { + const versionToFileName = await docUtils.getVersionToFileNameAsync(DOC_JSON_ROOT); + const versions = _.keys(versionToFileName); + this.props.dispatcher.updateAvailableDocVersions(versions); + const sortedVersions = semverSort.desc(versions); + const latestVersion = sortedVersions[0]; + + let versionToFetch = latestVersion; + if (!_.isUndefined(preferredVersionIfExists)) { + const preferredVersionFileNameIfExists = versionToFileName[preferredVersionIfExists]; + if (!_.isUndefined(preferredVersionFileNameIfExists)) { + versionToFetch = preferredVersionIfExists; + } + } + this.props.dispatcher.updateCurrentDocsVersion(versionToFetch); + + const versionFileNameToFetch = versionToFileName[versionToFetch]; + const versionDocObj = await docUtils.getJSONDocFileAsync(versionFileNameToFetch, DOC_JSON_ROOT); + const docAgnosticFormat = doxityUtils.convertToDocAgnosticFormat(versionDocObj as DoxityDocObj); + + this.setState({ + docAgnosticFormat, + }, () => { + this.scrollToHash(); + }); + } +} diff --git a/packages/website/ts/pages/documentation/source_link.tsx b/packages/website/ts/pages/documentation/source_link.tsx new file mode 100644 index 000000000..2fb69e2f0 --- /dev/null +++ b/packages/website/ts/pages/documentation/source_link.tsx @@ -0,0 +1,27 @@ +import * as React from 'react'; +import {colors} from 'material-ui/styles'; +import {Source} from 'ts/types'; +import {constants} from 'ts/utils/constants'; + +interface SourceLinkProps { + source: Source; + version: string; +} + +export function SourceLink(props: SourceLinkProps) { + const source = props.source; + const githubUrl = constants.GITHUB_0X_JS_URL; + const sourceCodeUrl = `${githubUrl}/blob/v${props.version}/${source.fileName}#L${source.line}`; + return ( + + ); +} diff --git a/packages/website/ts/pages/documentation/type.tsx b/packages/website/ts/pages/documentation/type.tsx new file mode 100644 index 000000000..af18f97c2 --- /dev/null +++ b/packages/website/ts/pages/documentation/type.tsx @@ -0,0 +1,187 @@ +import * as _ from 'lodash'; +import * as React from 'react'; +import {Link as ScrollLink} from 'react-scroll'; +import * as ReactTooltip from 'react-tooltip'; +import {colors} from 'material-ui/styles'; +import {typeDocUtils} from 'ts/utils/typedoc_utils'; +import {constants} from 'ts/utils/constants'; +import {Type as TypeDef, TypeDocTypes, TypeDefinitionByName} from 'ts/types'; +import {utils} from 'ts/utils/utils'; +import {TypeDefinition} from 'ts/pages/documentation/type_definition'; + +const BUILT_IN_TYPE_COLOR = '#e69d00'; +const STRING_LITERAL_COLOR = '#4da24b'; + +// Some types reference other libraries. For these types, we want to link the user to the relevant documentation. +const typeToUrl: {[typeName: string]: string} = { + Web3: constants.WEB3_DOCS_URL, + Provider: constants.WEB3_PROVIDER_DOCS_URL, + BigNumber: constants.BIGNUMBERJS_GITHUB_URL, +}; + +const typeToSection: {[typeName: string]: string} = { + ExchangeWrapper: 'exchange', + TokenWrapper: 'token', + TokenRegistryWrapper: 'tokenRegistry', + EtherTokenWrapper: 'etherToken', + ProxyWrapper: 'proxy', + TokenTransferProxyWrapper: 'proxy', +}; + +interface TypeProps { + type: TypeDef; + typeDefinitionByName?: TypeDefinitionByName; +} + +// The return type needs to be `any` here so that we can recursively define components within +// components (e.g when rendering the union type). +export function Type(props: TypeProps): any { + const type = props.type; + const isIntrinsic = type.typeDocType === TypeDocTypes.Intrinsic; + const isReference = type.typeDocType === TypeDocTypes.Reference; + const isArray = type.typeDocType === TypeDocTypes.Array; + const isStringLiteral = type.typeDocType === TypeDocTypes.StringLiteral; + let typeNameColor = 'inherit'; + let typeName: string|React.ReactNode; + let typeArgs: React.ReactNode[] = []; + switch (type.typeDocType) { + case TypeDocTypes.Intrinsic: + case TypeDocTypes.Unknown: + typeName = type.name; + typeNameColor = BUILT_IN_TYPE_COLOR; + break; + + case TypeDocTypes.Reference: + typeName = type.name; + typeArgs = _.map(type.typeArguments, (arg: TypeDef) => { + if (arg.typeDocType === TypeDocTypes.Array) { + const key = `type-${arg.elementType.name}-${arg.elementType.typeDocType}`; + return ( + + [] + + ); + } else { + const subType = ( + + ); + return subType; + } + }); + break; + + case TypeDocTypes.StringLiteral: + typeName = `'${type.value}'`; + typeNameColor = STRING_LITERAL_COLOR; + break; + + case TypeDocTypes.Array: + typeName = type.elementType.name; + break; + + case TypeDocTypes.Union: + const unionTypes = _.map(type.types, t => { + return ( + + ); + }); + typeName = _.reduce(unionTypes, (prev: React.ReactNode, curr: React.ReactNode) => { + return [prev, '|', curr]; + }); + break; + + case TypeDocTypes.TypeParameter: + typeName = type.name; + break; + + default: + throw utils.spawnSwitchErr('type.typeDocType', type.typeDocType); + } + // HACK: Normalize BigNumber to simply BigNumber. For some reason the type + // name is unpredictably one or the other. + if (typeName === 'BigNumber') { + typeName = 'BigNumber'; + } + const commaSeparatedTypeArgs = _.reduce(typeArgs, (prev: React.ReactNode, curr: React.ReactNode) => { + return [prev, ', ', curr]; + }); + + const typeNameUrlIfExists = typeToUrl[(typeName as string)]; + const sectionNameIfExists = typeToSection[(typeName as string)]; + if (!_.isUndefined(typeNameUrlIfExists)) { + typeName = ( + + {typeName} + + ); + } else if ((isReference || isArray) && + (typeDocUtils.isPublicType(typeName as string) || + !_.isUndefined(sectionNameIfExists))) { + const id = Math.random().toString(); + const typeDefinitionAnchorId = _.isUndefined(sectionNameIfExists) ? typeName : sectionNameIfExists; + let typeDefinition; + if (props.typeDefinitionByName) { + typeDefinition = props.typeDefinitionByName[typeName as string]; + } + typeName = ( + + {_.isUndefined(typeDefinition) || utils.isUserOnMobile() ? + + {typeName} + : + + {typeName} + + + + + } + + ); + } + return ( + + {typeName} + {isArray && '[]'}{!_.isEmpty(typeArgs) && + + {'<'}{commaSeparatedTypeArgs}{'>'} + + } + + ); +} diff --git a/packages/website/ts/pages/documentation/type_definition.tsx b/packages/website/ts/pages/documentation/type_definition.tsx new file mode 100644 index 000000000..bcb07be8e --- /dev/null +++ b/packages/website/ts/pages/documentation/type_definition.tsx @@ -0,0 +1,135 @@ +import * as _ from 'lodash'; +import * as React from 'react'; +import {constants} from 'ts/utils/constants'; +import {utils} from 'ts/utils/utils'; +import {KindString, CustomType, TypeDocTypes, CustomTypeChild, HeaderSizes} from 'ts/types'; +import {Type} from 'ts/pages/documentation/type'; +import {Interface} from 'ts/pages/documentation/interface'; +import {CustomEnum} from 'ts/pages/documentation/custom_enum'; +import {Enum} from 'ts/pages/documentation/enum'; +import {MethodSignature} from 'ts/pages/documentation/method_signature'; +import {AnchorTitle} from 'ts/pages/shared/anchor_title'; +import {Comment} from 'ts/pages/documentation/comment'; +import {typeDocUtils} from 'ts/utils/typedoc_utils'; + +const KEYWORD_COLOR = '#a81ca6'; + +interface TypeDefinitionProps { + customType: CustomType; + shouldAddId?: boolean; +} + +interface TypeDefinitionState { + shouldShowAnchor: boolean; +} + +export class TypeDefinition extends React.Component { + public static defaultProps: Partial = { + shouldAddId: true, + }; + constructor(props: TypeDefinitionProps) { + super(props); + this.state = { + shouldShowAnchor: false, + }; + } + public render() { + const customType = this.props.customType; + if (!typeDocUtils.isPublicType(customType.name)) { + return null; // no-op + } + + let typePrefix: string; + let codeSnippet: React.ReactNode; + switch (customType.kindString) { + case KindString.Interface: + typePrefix = 'Interface'; + codeSnippet = ( + + ); + break; + + case KindString.Variable: + typePrefix = 'Enum'; + codeSnippet = ( + + ); + break; + + case KindString.Enumeration: + typePrefix = 'Enum'; + const enumValues = _.map(customType.children, (c: CustomTypeChild) => { + return { + name: c.name, + defaultValue: c.defaultValue, + }; + }); + codeSnippet = ( + + ); + break; + + case KindString['Type alias']: + typePrefix = 'Type Alias'; + codeSnippet = ( + + type {customType.name} ={' '} + {customType.type.typeDocType !== TypeDocTypes.Reflection ? + : + + } + + ); + break; + + default: + throw utils.spawnSwitchErr('type.kindString', customType.kindString); + } + + const typeDefinitionAnchorId = customType.name; + return ( +
+ +
+
+                        
+                            {codeSnippet}
+                        
+                    
+
+ {customType.comment && + + } +
+ ); + } + private setAnchorVisibility(shouldShowAnchor: boolean) { + this.setState({ + shouldShowAnchor, + }); + } +} diff --git a/packages/website/ts/pages/documentation/zero_ex_js_documentation.tsx b/packages/website/ts/pages/documentation/zero_ex_js_documentation.tsx new file mode 100644 index 000000000..c26fb7d0a --- /dev/null +++ b/packages/website/ts/pages/documentation/zero_ex_js_documentation.tsx @@ -0,0 +1,340 @@ +import * as _ from 'lodash'; +import * as React from 'react'; +import * as ReactMarkdown from 'react-markdown'; +import DocumentTitle = require('react-document-title'); +import findVersions = require('find-versions'); +import semverSort = require('semver-sort'); +import {colors} from 'material-ui/styles'; +import MenuItem from 'material-ui/MenuItem'; +import CircularProgress from 'material-ui/CircularProgress'; +import Paper from 'material-ui/Paper'; +import { + Link as ScrollLink, + Element as ScrollElement, + scroller, +} from 'react-scroll'; +import {Dispatcher} from 'ts/redux/dispatcher'; +import { + KindString, + TypeDocNode, + ZeroExJsDocSections, + Styles, + ScreenWidths, + TypeDefinitionByName, + DocAgnosticFormat, + TypescriptMethod, + Property, + CustomType, + Docs, +} from 'ts/types'; +import {TopBar} from 'ts/components/top_bar'; +import {utils} from 'ts/utils/utils'; +import {docUtils} from 'ts/utils/doc_utils'; +import {constants} from 'ts/utils/constants'; +import {Loading} from 'ts/components/ui/loading'; +import {MethodBlock} from 'ts/pages/documentation/method_block'; +import {SourceLink} from 'ts/pages/documentation/source_link'; +import {Type} from 'ts/pages/documentation/type'; +import {TypeDefinition} from 'ts/pages/documentation/type_definition'; +import {MarkdownSection} from 'ts/pages/shared/markdown_section'; +import {Comment} from 'ts/pages/documentation/comment'; +import {AnchorTitle} from 'ts/pages/shared/anchor_title'; +import {SectionHeader} from 'ts/pages/shared/section_header'; +import {NestedSidebarMenu} from 'ts/pages/shared/nested_sidebar_menu'; +import {typeDocUtils} from 'ts/utils/typedoc_utils'; +/* tslint:disable:no-var-requires */ +const IntroMarkdown = require('md/docs/0xjs/introduction'); +const InstallationMarkdown = require('md/docs/0xjs/installation'); +const AsyncMarkdown = require('md/docs/0xjs/async'); +const ErrorsMarkdown = require('md/docs/0xjs/errors'); +const versioningMarkdown = require('md/docs/0xjs/versioning'); +/* tslint:enable:no-var-requires */ + +const SCROLL_TO_TIMEOUT = 500; +const DOC_JSON_ROOT = constants.S3_0XJS_DOCUMENTATION_JSON_ROOT; + +const sectionNameToMarkdown = { + [ZeroExJsDocSections.introduction]: IntroMarkdown, + [ZeroExJsDocSections.installation]: InstallationMarkdown, + [ZeroExJsDocSections.async]: AsyncMarkdown, + [ZeroExJsDocSections.errors]: ErrorsMarkdown, + [ZeroExJsDocSections.versioning]: versioningMarkdown, +}; + +export interface ZeroExJSDocumentationPassedProps { + source: string; + location: Location; +} + +export interface ZeroExJSDocumentationAllProps { + source: string; + location: Location; + dispatcher: Dispatcher; + docsVersion: string; + availableDocVersions: string[]; +} + +interface ZeroExJSDocumentationState { + docAgnosticFormat?: DocAgnosticFormat; +} + +const styles: Styles = { + mainContainers: { + position: 'absolute', + top: 60, + left: 0, + bottom: 0, + right: 0, + overflowZ: 'hidden', + overflowY: 'scroll', + minHeight: 'calc(100vh - 60px)', + WebkitOverflowScrolling: 'touch', + }, + menuContainer: { + borderColor: colors.grey300, + maxWidth: 330, + marginLeft: 20, + }, +}; + +export class ZeroExJSDocumentation extends React.Component { + constructor(props: ZeroExJSDocumentationAllProps) { + super(props); + this.state = { + docAgnosticFormat: undefined, + }; + } + public componentWillMount() { + const pathName = this.props.location.pathname; + const lastSegment = pathName.substr(pathName.lastIndexOf('/') + 1); + const versions = findVersions(lastSegment); + const preferredVersionIfExists = versions.length > 0 ? versions[0] : undefined; + this.fetchJSONDocsFireAndForgetAsync(preferredVersionIfExists); + } + public render() { + const menuSubsectionsBySection = _.isUndefined(this.state.docAgnosticFormat) + ? {} + : typeDocUtils.getMenuSubsectionsBySection(this.state.docAgnosticFormat); + return ( +
+ + + {_.isUndefined(this.state.docAgnosticFormat) ? +
+
+
+ +
+
Loading documentation...
+
+
: +
+
+
+ +
+
+
+
+
+

+ + 0x.js + +

+ {this.renderDocumentation()} +
+
+
+ } +
+ ); + } + private renderDocumentation(): React.ReactNode { + const typeDocSection = this.state.docAgnosticFormat[ZeroExJsDocSections.types]; + const typeDefinitionByName = _.keyBy(typeDocSection.types, 'name'); + + const subMenus = _.values(constants.menu0xjs); + const orderedSectionNames = _.flatten(subMenus); + const sections = _.map(orderedSectionNames, this.renderSection.bind(this, typeDefinitionByName)); + + return sections; + } + private renderSection(typeDefinitionByName: TypeDefinitionByName, sectionName: string): React.ReactNode { + const docSection = this.state.docAgnosticFormat[sectionName]; + + const markdownFileIfExists = sectionNameToMarkdown[sectionName]; + if (!_.isUndefined(markdownFileIfExists)) { + return ( + + ); + } + + if (_.isUndefined(docSection)) { + return null; + } + + const typeDefs = _.map(docSection.types, customType => { + return ( + + ); + }); + const propertyDefs = _.map(docSection.properties, this.renderProperty.bind(this)); + const methodDefs = _.map(docSection.methods, method => { + const isConstructor = false; + return this.renderMethodBlocks(method, sectionName, isConstructor, typeDefinitionByName); + }); + return ( +
+ + + {sectionName === ZeroExJsDocSections.zeroEx && docSection.constructors.length > 0 && +
+

Constructor

+ {this.renderZeroExConstructors(docSection.constructors, typeDefinitionByName)} +
+ } + {docSection.properties.length > 0 && +
+

Properties

+
{propertyDefs}
+
+ } + {docSection.methods.length > 0 && +
+

Methods

+
{methodDefs}
+
+ } + {typeDefs.length > 0 && +
+
{typeDefs}
+
+ } +
+ ); + } + private renderZeroExConstructors(constructors: TypescriptMethod[], + typeDefinitionByName: TypeDefinitionByName): React.ReactNode { + const constructorDefs = _.map(constructors, constructor => { + return this.renderMethodBlocks( + constructor, ZeroExJsDocSections.zeroEx, constructor.isConstructor, typeDefinitionByName, + ); + }); + return ( +
+ {constructorDefs} +
+ ); + } + private renderProperty(property: Property): React.ReactNode { + return ( +
+ + {property.name}: + + + {property.comment && + + } +
+ ); + } + private renderMethodBlocks(method: TypescriptMethod, sectionName: string, isConstructor: boolean, + typeDefinitionByName: TypeDefinitionByName): React.ReactNode { + return ( + + ); + } + private scrollToHash(): void { + const hashWithPrefix = this.props.location.hash; + let hash = hashWithPrefix.slice(1); + if (_.isEmpty(hash)) { + hash = 'zeroExJSDocs'; // scroll to the top + } + + scroller.scrollTo(hash, {duration: 0, offset: 0, containerId: 'documentation'}); + } + private async fetchJSONDocsFireAndForgetAsync(preferredVersionIfExists?: string): Promise { + const versionToFileName = await docUtils.getVersionToFileNameAsync(DOC_JSON_ROOT); + const versions = _.keys(versionToFileName); + this.props.dispatcher.updateAvailableDocVersions(versions); + const sortedVersions = semverSort.desc(versions); + const latestVersion = sortedVersions[0]; + + let versionToFetch = latestVersion; + if (!_.isUndefined(preferredVersionIfExists)) { + const preferredVersionFileNameIfExists = versionToFileName[preferredVersionIfExists]; + if (!_.isUndefined(preferredVersionFileNameIfExists)) { + versionToFetch = preferredVersionIfExists; + } + } + this.props.dispatcher.updateCurrentDocsVersion(versionToFetch); + + const versionFileNameToFetch = versionToFileName[versionToFetch]; + const versionDocObj = await docUtils.getJSONDocFileAsync(versionFileNameToFetch, DOC_JSON_ROOT); + const docAgnosticFormat = typeDocUtils.convertToDocAgnosticFormat((versionDocObj as TypeDocNode)); + + this.setState({ + docAgnosticFormat, + }, () => { + this.scrollToHash(); + }); + } +} diff --git a/packages/website/ts/pages/faq/faq.tsx b/packages/website/ts/pages/faq/faq.tsx new file mode 100644 index 000000000..3c65d1042 --- /dev/null +++ b/packages/website/ts/pages/faq/faq.tsx @@ -0,0 +1,497 @@ +import * as _ from 'lodash'; +import * as React from 'react'; +import * as DocumentTitle from 'react-document-title'; +import RaisedButton from 'material-ui/RaisedButton'; +import {colors} from 'material-ui/styles'; +import {Styles, FAQSection, FAQQuestion, WebsitePaths} from 'ts/types'; +import {Link} from 'react-router-dom'; +import {Footer} from 'ts/components/footer'; +import {TopBar} from 'ts/components/top_bar'; +import {Question} from 'ts/pages/faq/question'; +import {configs} from 'ts/utils/configs'; +import {constants} from 'ts/utils/constants'; + +export interface FAQProps { + source: string; + location: Location; +} + +interface FAQState {} + +const styles: Styles = { + thin: { + fontWeight: 100, + }, +}; + +const sections: FAQSection[] = [ + { + name: '0x Protocol', + questions: [ + { + prompt: 'What is 0x?', + answer: ( +
+ At its core, 0x is an open and non-rent seeking protocol that facilitates trustless, + low friction exchange of Ethereum-based assets. Developers can use 0x as a platform + to build exchange applications on top of{' '} + (0x.js + {' '}is a Javascript library for interacting with the 0x protocol). For end users, 0x will be + the infrastructure of a wide variety of user-facing applications i.e.{' '} + 0x Portal, + a decentralized application that facilitates trustless trading of Ethereum-based tokens + between known counterparties. +
+ ), + }, + { + prompt: 'What problem does 0x solve?', + answer: ( +
+ In the two years since the Ethereum blockchain’s genesis block, numerous decentralized + applications (dApps) have created Ethereum smart contracts for peer-to-peer exchange. + Rapid iteration and a lack of best practices have left the blockchain scattered with + proprietary and application-specific implementations. As a result, end users are + exposed to numerous smart contracts of varying quality and security, with unique + configuration processes and learning curves, all of which implement the same + functionality. This approach imposes unnecessary costs on the network by fragmenting + end users according to the particular dApp each user happens to be using, eliminating + valuable network effects around liquidity. 0x is the solution to this problem by + acting as modular, unopinionated building blocks that may be assembled and reconfigured. +
+ ), + }, + { + prompt: 'How is 0x different from a centralized exchange like Poloniex or ShapeShift?', + answer: ( +
+
    +
  • + 0x is a protocol for exchange, not a user-facing exchange application. +
  • +
  • + 0x is decentralized and trustless; there is no central party which can be + hacked, run away with customer funds or be subjected to government regulations. + Hacks of Mt. Gox, Shapeshift and Bitfinex have demonstrated that these types of + systemic risks are palpable. +
  • +
  • + Rather than a proprietary system that exists to extract rent for its owners, + 0x is public infrastructure that is funded by a globally distributed community + of stakeholders. While the protocol is free to use, it enables for-profit + user-facing exchange applications to be built on top of the protocol. +
  • +
+
+ ), + }, + { + prompt: 'If 0x protocol is free to use, where do transaction fees come in?', + answer: ( +
+ 0x protocol uses off-chain order books to massively reduce friction costs for + market makers and ensure that the blockchain is only used for trade settlement. + Hosting and maintaining an off-chain order book is a service; to incent “Relayers” + to provide this service they must be able to charge transaction fees on trading + activity. Relayers are free to set their transaction fees to any value they desire. + We expect Relayers to be highly competitive and transaction fees to approach an + efficient economic equilibrium over time. +
+ ), + }, + { + prompt: 'What are the differences between 0x protocol and state channels?', + answer: ( +
+
+ Participants in a state channel pass cryptographically signed messages back and + forth, accumulating intermediate state changes without publishing them to the + canonical chain until the channel is closed. State channels are ideal for “bar tab” + applications where numerous intermediate state changes may be accumulated off-chain + before being settled by a final on-chain transaction (i.e. day trading, poker, + turn-based games). +
+
    +
  • + While state channels drastically reduce the number of on-chain transactions + for specific use cases, numerous on-chain transactions and a security deposit + are required to open and safely close a state channel making them less efficient + than 0x for executing one-time trades. +
  • +
  • + State channels are isolated from the Ethereum blockchain meaning that + they cannot interact with smart contracts. 0x is designed to be integrated + directly into smart contracts so trades can be executed programmatically + in a single line of Solidity code. +
  • +
+
+ ), + }, + { + prompt: 'What types of digital assets are supported by 0x?', + answer: ( +
+ 0x supports all Ethereum-based assets that adhere to the ERC20 token standard. + There are many ERC20 tokens, worth a combined $2.2B, and more tokens are created + each month. We believe that, by 2020, thousands of assets will be tokenized and + moved onto the Ethereum blockchain including traditional securities such as equities, + bonds and derivatives, fiat currencies and scarce digital goods such as video game + items. In the future, cross-blockchain solutions such as{' '} + Cosmos and{' '} + Polkadot will allow cryptocurrencies + to freely move between blockchains and, naturally, currencies such as Bitcoin will + end up being represented as ERC20 tokens on the Ethereum blockchain. +
+ ), + }, + { + prompt: '0x is open source: what prevents someone from forking the protocol?', + answer: ( +
+ Ethereum and Bitcoin are both open source protocols. Each protocol has been forked, + but the resulting clone networks have seen little adoption (as measured by transaction + count or market cap). This is because users have little to no incentive to switch + over to a clone network if the original has initial network effects and a talented + developer team behind it. + An exception is in the case that a protocol includes a controversial feature such as + a method of rent extraction or a monetary policy that favors one group of users over + another (Zcash developer subsidy - for better or worse - resulted in Zclassic). + Perceived inequality can provide a strong enough incentive that users will fork the + original protocol’s codebase and spin up a new network that eliminates the controversial + feature. In the case of 0x, there is no rent extraction and no users are given + special permissions. + + 0x protocol is upgradable. Cutting-edge technical capabilities can be integrated + into 0x via decentralized governance (see section below), eliminating incentives + to fork off of the original protocol and sacrifice the network effects surrounding + liquidity that result from the shared protocol and settlement layer. +
+ ), + }, + ], + }, + { + name: '0x Token (ZRX)', + questions: [ + { + prompt: 'Explain how the 0x protocol token (zrx) works.', + answer: ( +
+
+ 0x protocol token (ZRX) is utilized in two ways: 1) to solve the{' '} + + coordination problem + and drive network effects around liquidity, creating a feedback loop + where early adopters of the protocol benefit from wider adoption and 2) to + be used for decentralized governance over 0x protocol's update mechanism. + In more detail: +
+
    +
  • + ZRX tokens are used by Makers and Takers (market participants that generate + and consume orders, respectively) to pay transaction fees to Relayers + (entities that host and maintain public order books). +
  • +
  • + ZRX tokens are used for decentralized governance over 0x protocol’s update + mechanism which allows its underlying smart contracts to be replaced and + improved over time. An update mechanism is needed because 0x is built upon + Ethereum’s rapidly evolving technology stack, decentralized governance is + needed because 0x protocol’s smart contracts will have access to user funds + and numerous dApps will need to plug into 0x smart contracts. Decentralized + governance ensures that this update process is secure and minimizes disruption + to the network. +
  • +
+
+ ), + }, + { + prompt: 'Why must transaction fees be denominated in 0x token (ZRX) rather than ETH?', + answer: ( +
+ 0x protocol’s decentralized update mechanism is analogous to proof-of-stake. + To maximize the alignment of stakeholder and end user incentives, the staked + asset must provide utility within the protocol. +
+ ), + }, + { + prompt: 'How will decentralized governance work?', + answer: ( +
+ Decentralized governance is an ongoing focus of research; it will involve token + voting with ZRX. Ultimately the solution will maximize security while also maximizing + the protocol’s ability to absorb new innovations. Until the governance structure is + formalized and encoded within 0x DAO, a multi-sig will be used as a placeholder. +
+ ), + }, + ], + }, + { + name: 'ZRX Token Launch and Fund Use', + questions: [ + { + prompt: 'What is the total supply of ZRX tokens?', + answer: ( +
+ 1,000,000,000 ZRX. Fixed supply. +
+ ), + }, + { + prompt: 'When is the Token Launch? will there be a pre-sale?', + answer: ( +
+ The token launch will be on August 15th, 2017. There will not be a pre-sale. +
+ ), + }, + { + prompt: 'What will the token launch proceeds be used for?', + answer: ( +
+ 100% of the proceeds raised in the token launch will be used to fund the development + of free and open source software, tools and infrastructure that support the protocol + and surrounding ecosystem. Check out our{' '} + + development roadmap + . +
+ ), + }, + { + prompt: 'What will be the initial distribution of ZRX tokens?', + answer: ( +
+
+ +
+
+
+ Token Launch (50%) +
+
+ ZRX is inherently a governance token that plays a critical role in the + process of upgrading 0x protocol. We are fully committed to formulating + a functional and theoretically sound governance model and we plan to dedicate + significant resources to R&D. +
+
+
+
+ Retained by 0x (15%) +
+
+ The 0x core development team will be able to sustain itself for approximately + five years using funds raised through the token launch. If 0x protocol + proves to be as foundational a technology as we believe it to be, the + retained ZRX tokens will allow the 0x core development team to sustain + operations beyond the first 5 years. +
+
+
+
+ Developer Fund (15%) +
+
+ The Developer Fund will be used to make targeted capital injections + into high potential projects and teams that are attempting to grow + the 0x ecosystem, strategic partnerships, hackathon prizes and community + development activities. +
+
+
+
+ Founding Team (10%) +
+
+ The founding team’s allocation of ZRX will vest over a traditional 4 + year vesting schedule with a one year cliff. We believe this should + be standard practice for any team that is committed to making their + project a long term success. +
+
+
+
+ Early Backers & Advisors (10%) +
+
+ Our backers and advisors have provided capital, resources and guidance + that have allowed us to fill out our team, setup a robust legal entity + and build a fully functional product before launching a token. As a result, + we have a proven track record and can offer a token that holds genuine utility. +
+
+
+ ), + }, + { + prompt: 'Can I mine ZRX tokens?', + answer: ( +
+ No, the total supply of ZRX tokens is fixed and there is no continuous issuance + model. Users that facilitate trading over 0x protocol by operating a Relayer + earn transaction fees denominated in ZRX; as more trading activity is generated, + more transaction fees are earned. +
+ ), + }, + { + prompt: 'Will there be a lockup period for ZRX tokens sold in the token launch?', + answer: ( +
+ No, ZRX tokens sold in the token launch will immediately be liquid. +
+ ), + }, + { + prompt: 'Will there be a lockup period for tokens allocated to the founding team?', + answer: ( +
+ Yes. ZRX tokens allocated to founders, advisors and staff members will be released + over a 4 year vesting schedule with a 25% cliff upon completion of the initial + token launch and 25% released each subsequent year in monthly installments. Staff + members hired after the token launch will have a 4 year vesting schedule with a + one year cliff. +
+ ), + }, + { + prompt: 'Which cryptocurrencies will be accepted in the token launch?', + answer: ( +
ETH.
+ ), + }, + { + prompt: 'When will 0x be live?', + answer: ( +
+ An alpha version of 0x has been live on our private test network since January + 2017. Version 1.0 of 0x protocol will be deployed to the canonical Ethereum + blockchain after a round of security audits and prior to the public token launch. + 0x will be using the 0x protocol during our token launch. +
+ ), + }, + { + prompt: 'Where can I find a development roadmap?', + answer: ( +
+ Check it out{' '} + + here + . +
+ ), + }, + ], + }, + { + name: 'Team', + questions: [ + { + prompt: 'Where is 0x based?', + answer: ( +
+ 0x was founded in SF and is driven by a diverse group of contributors. +
+ ), + }, + { + prompt: 'How can I get involved?', + answer: ( +
+ Join our Rocket.chat! + As an open source project, 0x will rely on a worldwide community of passionate + developers to contribute proposals, ideas and code. +
+ ), + }, + { + prompt: 'Why the name 0x?', + answer: ( +
+ 0x is the prefix for hexadecimal numeric constants including Ethereum addresses. + In a more abstract context, as the first open protocol for exchange 0x represents + the beginning of the end for the exchange industry’s rent seeking oligopoly: + zero exchange. +
+ ), + }, + { + prompt: 'How do you pronounce 0x?', + answer: ( +
+ We pronounce 0x as “zero-ex,” but you are free to pronounce it however you please. +
+ ), + }, + ], + }, +]; + +export class FAQ extends React.Component { + public componentDidMount() { + window.scrollTo(0, 0); + } + public render() { + return ( +
+ + +
+

0x FAQ

+
+ {this.renderSections()} +
+
+
+
+ ); + } + private renderSections() { + const renderedSections = _.map(sections, (section: FAQSection, i: number) => { + const isFirstSection = i === 0; + return ( +
+

{section.name}

+ {this.renderQuestions(section.questions, isFirstSection)} +
+ ); + }); + return renderedSections; + } + private renderQuestions(questions: FAQQuestion[], isFirstSection: boolean) { + const renderedQuestions = _.map(questions, (question: FAQQuestion, i: number) => { + const isFirstQuestion = i === 0; + return ( + + ); + }); + return renderedQuestions; + } +} diff --git a/packages/website/ts/pages/faq/question.tsx b/packages/website/ts/pages/faq/question.tsx new file mode 100644 index 000000000..4ed198b91 --- /dev/null +++ b/packages/website/ts/pages/faq/question.tsx @@ -0,0 +1,52 @@ +import * as _ from 'lodash'; +import * as React from 'react'; +import {Card, CardHeader, CardText} from 'material-ui/Card'; + +export interface QuestionProps { + prompt: string; + answer: React.ReactNode; + shouldDisplayExpanded: boolean; +} + +interface QuestionState { + isExpanded: boolean; +} + +export class Question extends React.Component { + constructor(props: QuestionProps) { + super(props); + this.state = { + isExpanded: props.shouldDisplayExpanded, + }; + } + public render() { + return ( +
+ + + +
+ {this.props.answer} +
+
+
+
+ ); + } + private onExchangeChange() { + this.setState({ + isExpanded: !this.state.isExpanded, + }); + } +} diff --git a/packages/website/ts/pages/landing/landing.tsx b/packages/website/ts/pages/landing/landing.tsx new file mode 100644 index 000000000..32ea86736 --- /dev/null +++ b/packages/website/ts/pages/landing/landing.tsx @@ -0,0 +1,843 @@ +import * as _ from 'lodash'; +import * as React from 'react'; +import DocumentTitle = require('react-document-title'); +import {Link} from 'react-router-dom'; +import RaisedButton from 'material-ui/RaisedButton'; +import {colors} from 'material-ui/styles'; +import {configs} from 'ts/utils/configs'; +import {constants} from 'ts/utils/constants'; +import {Styles, WebsitePaths, ScreenWidths} from 'ts/types'; +import {utils} from 'ts/utils/utils'; +import {TopBar} from 'ts/components/top_bar'; +import {Footer} from 'ts/components/footer'; + +interface BoxContent { + title: string; + description: string; + imageUrl: string; + classNames: string; +} +interface AssetType { + title: string; + imageUrl: string; + style?: React.CSSProperties; +} +interface UseCase { + imageUrl: string; + type: string; + description: string; + classNames: string; + style?: React.CSSProperties; + projectIconUrls: string[]; +} +interface Project { + logoFileName: string; + projectUrl: string; +} + +const THROTTLE_TIMEOUT = 100; +const CUSTOM_HERO_BACKGROUND_COLOR = '#404040'; +const CUSTOM_PROJECTS_BACKGROUND_COLOR = '#343333'; +const CUSTOM_WHITE_BACKGROUND = 'rgb(245, 245, 245)'; +const CUSTOM_WHITE_TEXT = '#E4E4E4'; +const CUSTOM_GRAY_TEXT = '#919191'; + +const boxContents: BoxContent[] = [ + { + title: 'Trustless exchange', + description: 'Built on Ethereum\'s distributed network with no centralized \ + point of failure and no down time, each trade is settled atomically \ + and without counterparty risk.', + imageUrl: '/images/landing/distributed_network.png', + classNames: '', + }, + { + title: 'Shared liquidity', + description: 'By sharing a standard API, relayers can easily aggregate liquidity pools, \ + creating network effects around liquidity that compound as more relayers come online.', + imageUrl: '/images/landing/liquidity.png', + classNames: 'mx-auto', + }, + { + title: 'Open source', + description: '0x is open source, permissionless and free to use. Trade directly with a known \ + counterparty for free or pay a relayer some ZRX tokens to access their liquidity \ + pool.', + imageUrl: '/images/landing/open_source.png', + classNames: 'right', + }, +]; + +const projects: Project[] = [ + { + logoFileName: 'ethfinex-top.png', + projectUrl: constants.ETHFINEX_URL, + }, + { + logoFileName: 'radar_relay_top.png', + projectUrl: constants.RADAR_RELAY_URL, + }, + { + logoFileName: 'paradex_top.png', + projectUrl: constants.PARADEX_URL, + }, + { + logoFileName: 'the_ocean.png', + projectUrl: constants.OCEAN_URL, + }, + { + logoFileName: 'dydx.png', + projectUrl: constants.DYDX_URL, + }, + { + logoFileName: 'melonport.png', + projectUrl: constants.MELONPORT_URL, + }, + { + logoFileName: 'maker.png', + projectUrl: constants.MAKER_URL, + }, + { + logoFileName: 'dharma.png', + projectUrl: constants.DHARMA_URL, + }, + { + logoFileName: 'lendroid.png', + projectUrl: constants.LENDROID_URL, + }, + { + logoFileName: 'district0x.png', + projectUrl: constants.DISTRICT_0X_URL, + }, + { + logoFileName: 'aragon.png', + projectUrl: constants.ARAGON_URL, + }, + { + logoFileName: 'blocknet.png', + projectUrl: constants.BLOCKNET_URL, + }, + { + logoFileName: 'status.png', + projectUrl: constants.STATUS_URL, + }, + { + logoFileName: 'augur.png', + projectUrl: constants.AUGUR_URL, + }, + { + logoFileName: 'anx.png', + projectUrl: constants.OPEN_ANX_URL, + }, + { + logoFileName: 'auctus.png', + projectUrl: constants.AUCTUS_URL, + }, +]; + +export interface LandingProps { + location: Location; +} + +interface LandingState { + screenWidth: ScreenWidths; +} + +export class Landing extends React.Component { + private throttledScreenWidthUpdate: () => void; + constructor(props: LandingProps) { + super(props); + this.state = { + screenWidth: utils.getScreenWidth(), + }; + this.throttledScreenWidthUpdate = _.throttle(this.updateScreenWidth.bind(this), THROTTLE_TIMEOUT); + } + public componentDidMount() { + window.addEventListener('resize', this.throttledScreenWidthUpdate); + window.scrollTo(0, 0); + } + public componentWillUnmount() { + window.removeEventListener('resize', this.throttledScreenWidthUpdate); + } + public render() { + return ( +
+ + + {this.renderHero()} + {this.renderProjects()} + {this.renderTokenizationSection()} + {this.renderProtocolSection()} + {this.renderInfoBoxes()} + {this.renderBuildingBlocksSection()} + {this.renderUseCases()} + {this.renderCallToAction()} +
+
+ ); + } + private renderHero() { + const isSmallScreen = this.state.screenWidth === ScreenWidths.SM; + const buttonLabelStyle: React.CSSProperties = { + textTransform: 'none', + fontSize: isSmallScreen ? 12 : 14, + fontWeight: 400, + }; + const lightButtonStyle: React.CSSProperties = { + borderRadius: 6, + border: '1px solid #D8D8D8', + lineHeight: '33px', + height: 38, + }; + const left = 'col lg-col-7 md-col-7 col-12 lg-pt4 md-pt4 sm-pt0 mt1 lg-pl4 md-pl4 sm-pl0 sm-px3 sm-center'; + return ( +
+
+
+
+ +
+
+
+
+ Powering decentralized exchange +
+
+ 0x is an open, permissionless protocol allowing for ERC20 tokens to + be traded on the Ethereum blockchain. +
+
+
+ + + +
+
+ + + +
+
+
+
+
+
+
+ ); + } + private renderProjects() { + const isSmallScreen = this.state.screenWidth === ScreenWidths.SM; + const isMediumScreen = this.state.screenWidth === ScreenWidths.MD; + const projectList = _.map(projects, (project: Project, i: number) => { + const colWidth = isSmallScreen ? 3 : isMediumScreen ? 4 : 2 - (i % 2); + return ( +
+
+ + + +
+
+ ); + }); + const titleStyle: React.CSSProperties = { + fontFamily: 'Roboto Mono', + color: '#A4A4A4', + textTransform: 'uppercase', + fontWeight: 300, + letterSpacing: 3, + }; + return ( +
+
+
+ Projects building on 0x +
+
+ {projectList} +
+
+ view the{' '} + + full list + +
+
+
+ ); + } + private renderTokenizationSection() { + const isSmallScreen = this.state.screenWidth === ScreenWidths.SM; + return ( +
+
+ {isSmallScreen && + this.renderTokenCloud() + } +
+
+
+ The world's value is becoming tokenized +
+
+ {isSmallScreen ? + + The Ethereum blockchain is an open, borderless financial system that represents + a wide variety of assets as cryptographic tokens. In the future, most digital + assets and goods will be tokenized. + : +
+
+ The Ethereum blockchain is an open, borderless + financial system that represents +
+
+ a wide variety of assets as cryptographic tokens. + In the future, most digital assets and goods will be tokenized. +
+
+ } +
+
+ {this.renderAssetTypes()} +
+
+
+ {!isSmallScreen && + this.renderTokenCloud() + } +
+
+ ); + } + private renderProtocolSection() { + const isSmallScreen = this.state.screenWidth === ScreenWidths.SM; + return ( +
+
+
+ +
+
+
+
+ Off-chain order relay +
+
+ On-chain settlement +
+
+
+ In 0x protocol, orders are transported off-chain, massively reducing gas + costs and eliminating blockchain bloat. Relayers help broadcast orders and + collect a fee each time they facilitate a trade. Anyone can build a relayer. +
+
+
+
+ RELAYERS BUILDING ON 0X +
+
+ + view all + +
+
+
+
+ +
+
+ +
+
+ +
+
+
+
+
+
+ ); + } + private renderBuildingBlocksSection() { + const isSmallScreen = this.state.screenWidth === ScreenWidths.SM; + const underlineStyle: React.CSSProperties = { + height: isSmallScreen ? 18 : 23, + lineHeight: 'none', + borderBottom: '2px solid #979797', + }; + const descriptionStyle: React.CSSProperties = { + fontFamily: 'Roboto Mono', + lineHeight: isSmallScreen ? 1.5 : 2, + fontWeight: 300, + fontSize: 15, + maxWidth: isSmallScreen ? 375 : 'none', + }; + const callToActionStyle: React.CSSProperties = { + fontFamily: 'Roboto Mono', + fontSize: 15, + fontWeight: 300, + maxWidth: isSmallScreen ? 375 : 441, + }; + return ( +
+
+ {isSmallScreen && + this.renderBlockChipImage() + } +
+
+ A building block for dApps +
+
+ 0x protocol is a pluggable building block for dApps that require exchange functionality. + Join the many developers that are already using 0x in their web applications and smart + contracts. +
+
+ Learn how in our{' '} + + 0x.js + + {' '}and{' '} + + smart contract + + {' '}docs +
+
+ {!isSmallScreen && + this.renderBlockChipImage() + } +
+
+ ); + } + private renderBlockChipImage() { + const isSmallScreen = this.state.screenWidth === ScreenWidths.SM; + return ( +
+ +
+ ); + } + private renderTokenCloud() { + const isSmallScreen = this.state.screenWidth === ScreenWidths.SM; + return ( +
+ +
+ ); + } + private renderAssetTypes() { + const isSmallScreen = this.state.screenWidth === ScreenWidths.SM; + const assetTypes: AssetType[] = [ + { + title: 'Currency', + imageUrl: '/images/landing/currency.png', + }, + { + title: 'Traditional assets', + imageUrl: '/images/landing/stocks.png', + style: {paddingLeft: isSmallScreen ? 41 : 56, paddingRight: isSmallScreen ? 41 : 56}, + }, + { + title: 'Digital goods', + imageUrl: '/images/landing/digital_goods.png', + }, + ]; + const assets = _.map(assetTypes, (assetType: AssetType) => { + const style = _.isUndefined(assetType.style) ? {} : assetType.style; + return ( +
+
+ +
+
+ {assetType.title} +
+
+ ); + }); + return assets; + } + private renderLink(label: string, path: string, color: string, style?: React.CSSProperties) { + return ( +
+ + {label} + +
+ ); + } + private renderInfoBoxes() { + const isSmallScreen = this.state.screenWidth === ScreenWidths.SM; + const boxStyle: React.CSSProperties = { + maxWidth: 252, + height: 386, + backgroundColor: '#F9F9F9', + borderRadius: 5, + padding: '10px 24px 24px', + }; + const boxes = _.map(boxContents, (boxContent: BoxContent) => { + return ( +
+
+
+ +
+
+ {boxContent.title} +
+
+ {boxContent.description} +
+
+
+ ); + + }); + return ( +
+
+ {boxes} +
+
+ ); + } + private renderUseCases() { + const isSmallScreen = this.state.screenWidth === ScreenWidths.SM; + const isMediumScreen = this.state.screenWidth === ScreenWidths.MD; + + const useCases: UseCase[] = [ + { + imageUrl: '/images/landing/governance_icon.png', + type: 'Decentralized governance', + description: 'Decentralized organizations use tokens to represent ownership and \ + guide their governance logic. 0x allows decentralized organizations \ + to seamlessly and safely trade ownership for startup capital.', + projectIconUrls: ['/images/landing/aragon.png'], + classNames: 'lg-px2 md-px2', + }, + { + imageUrl: '/images/landing/prediction_market_icon.png', + type: 'Prediction markets', + description: 'Decentralized prediction market platforms generate sets of tokens that \ + represent a financial stake in the outcomes of real-world events. 0x allows \ + these tokens to be instantly tradable.', + projectIconUrls: ['/images/landing/augur.png'], + classNames: 'lg-px2 md-px2', + }, + { + imageUrl: '/images/landing/stable_tokens_icon.png', + type: 'Stable tokens', + description: 'Novel economic constructs such as stable coins require efficient, liquid \ + markets to succeed. 0x will facilitate the underlying economic mechanisms \ + that allow these tokens to remain stable.', + projectIconUrls: ['/images/landing/maker.png'], + classNames: 'lg-px2 md-px2', + }, + { + imageUrl: '/images/landing/loans_icon.png', + type: 'Decentralized loans', + description: 'Efficient lending requires liquid markets where investors can buy and re-sell loans. \ + 0x enables an ecosystem of lenders to self-organize and efficiently determine \ + market prices for all outstanding loans.', + projectIconUrls: ['/images/landing/dharma.png', '/images/landing/lendroid.png'], + classNames: 'lg-pr2 md-pr2 lg-col-6 md-col-6', + style: {width: 291, float: 'right', marginTop: !isSmallScreen ? 38 : 0}, + }, + { + imageUrl: '/images/landing/fund_management_icon.png', + type: 'Fund management', + description: 'Decentralized fund management limits fund managers to investing in pre-agreed \ + upon asset classes. Embedding 0x into fund management smart contracts enables \ + them to enforce these security constraints.', + projectIconUrls: ['/images/landing/melonport.png'], + classNames: 'lg-pl2 md-pl2 lg-col-6 md-col-6', + style: {width: 291, marginTop: !isSmallScreen ? 38 : 0}, + }, + ]; + + const cases = _.map(useCases, (useCase: UseCase) => { + const style = _.isUndefined(useCase.style) || isSmallScreen ? {} : useCase.style; + const useCaseBoxStyle = { + color: '#A2A2A2', + border: '1px solid #565656', + borderRadius: 4, + maxWidth: isSmallScreen ? 375 : 'none', + ...style, + }; + const typeStyle: React.CSSProperties = { + color: '#EBEBEB', + fontSize: 13, + textTransform: 'uppercase', + fontFamily: 'Roboto Mono', + fontWeight: 300, + }; + return ( +
+
+
+ +
+
+ {useCase.type} +
+
+ {useCase.description} +
+
+
+ ); + }); + return ( +
+
+ {cases} +
+
+ ); + } + private renderCallToAction() { + const isSmallScreen = this.state.screenWidth === ScreenWidths.SM; + const buttonLabelStyle: React.CSSProperties = { + textTransform: 'none', + fontSize: 15, + fontWeight: 400, + }; + const lightButtonStyle: React.CSSProperties = { + borderRadius: 6, + border: '1px solid #a0a0a0', + lineHeight: '33px', + height: 49, + }; + const callToActionClassNames = 'col lg-col-8 md-col-8 col-12 lg-pr3 md-pr3 \ + lg-right-align md-right-align sm-center sm-px3 h4'; + return ( +
+
+
+ Get started on building the decentralized future +
+
+ + + +
+
+
+ ); + } + private updateScreenWidth() { + const newScreenWidth = utils.getScreenWidth(); + if (newScreenWidth !== this.state.screenWidth) { + this.setState({ + screenWidth: newScreenWidth, + }); + } + } +} diff --git a/packages/website/ts/pages/not_found.tsx b/packages/website/ts/pages/not_found.tsx new file mode 100644 index 000000000..ddd720c97 --- /dev/null +++ b/packages/website/ts/pages/not_found.tsx @@ -0,0 +1,46 @@ +import * as _ from 'lodash'; +import * as React from 'react'; +import {Styles} from 'ts/types'; +import {Link} from 'react-router-dom'; +import {Footer} from 'ts/components/footer'; +import {TopBar} from 'ts/components/top_bar'; + +export interface NotFoundProps { + location: Location; +} + +interface NotFoundState {} + +const styles: Styles = { + thin: { + fontWeight: 100, + }, +}; + +export class NotFound extends React.Component { + public render() { + return ( +
+ +
+
+
+
+

404 Not Found

+
+
+ Hm... looks like we couldn't find what you are looking for. +
+
+
+
+
+
+
+
+ ); + } +} diff --git a/packages/website/ts/pages/shared/anchor_title.tsx b/packages/website/ts/pages/shared/anchor_title.tsx new file mode 100644 index 000000000..dfa9401ae --- /dev/null +++ b/packages/website/ts/pages/shared/anchor_title.tsx @@ -0,0 +1,98 @@ +import * as React from 'react'; +import {Styles, HeaderSizes} from 'ts/types'; +import {utils} from 'ts/utils/utils'; +import {constants} from 'ts/utils/constants'; +import {Link as ScrollLink} from 'react-scroll'; + +const headerSizeToScrollOffset: {[headerSize: string]: number} = { + h2: -20, + h3: 0, +}; + +interface AnchorTitleProps { + title: string|React.ReactNode; + id: string; + headerSize: HeaderSizes; + shouldShowAnchor: boolean; +} + +interface AnchorTitleState { + isHovering: boolean; +} + +const styles: Styles = { + anchor: { + fontSize: 20, + transform: 'rotate(45deg)', + cursor: 'pointer', + }, + headers: { + WebkitMarginStart: 0, + WebkitMarginEnd: 0, + fontWeight: 'bold', + display: 'block', + }, + h1: { + fontSize: '1.8em', + WebkitMarginBefore: '0.83em', + WebkitMarginAfter: '0.83em', + }, + h2: { + fontSize: '1.5em', + WebkitMarginBefore: '0.83em', + WebkitMarginAfter: '0.83em', + }, + h3: { + fontSize: '1.17em', + WebkitMarginBefore: '1em', + WebkitMarginAfter: '1em', + }, +}; + +export class AnchorTitle extends React.Component { + constructor(props: AnchorTitleProps) { + super(props); + this.state = { + isHovering: false, + }; + } + public render() { + let opacity = 0; + if (this.props.shouldShowAnchor) { + if (this.state.isHovering) { + opacity = 0.6; + } else { + opacity = 1; + } + } + return ( +
+
+ {this.props.title} +
+ + + +
+ ); + } + private setHoverState(isHovering: boolean) { + this.setState({ + isHovering, + }); + } +} diff --git a/packages/website/ts/pages/shared/markdown_code_block.tsx b/packages/website/ts/pages/shared/markdown_code_block.tsx new file mode 100644 index 000000000..621e5b606 --- /dev/null +++ b/packages/website/ts/pages/shared/markdown_code_block.tsx @@ -0,0 +1,20 @@ +import * as _ from 'lodash'; +import * as React from 'react'; +import * as HighLight from 'react-highlight'; + +interface MarkdownCodeBlockProps { + literal: string; + language: string; +} + +export function MarkdownCodeBlock(props: MarkdownCodeBlockProps) { + return ( + + + {props.literal} + + + ); +} diff --git a/packages/website/ts/pages/shared/markdown_section.tsx b/packages/website/ts/pages/shared/markdown_section.tsx new file mode 100644 index 000000000..32b55abc8 --- /dev/null +++ b/packages/website/ts/pages/shared/markdown_section.tsx @@ -0,0 +1,77 @@ +import * as _ from 'lodash'; +import * as React from 'react'; +import * as ReactMarkdown from 'react-markdown'; +import {Element as ScrollElement} from 'react-scroll'; +import {AnchorTitle} from 'ts/pages/shared/anchor_title'; +import {utils} from 'ts/utils/utils'; +import {MarkdownCodeBlock} from 'ts/pages/shared/markdown_code_block'; +import RaisedButton from 'material-ui/RaisedButton'; +import {HeaderSizes} from 'ts/types'; + +interface MarkdownSectionProps { + sectionName: string; + markdownContent: string; + headerSize?: HeaderSizes; + githubLink?: string; +} + +interface MarkdownSectionState { + shouldShowAnchor: boolean; +} + +export class MarkdownSection extends React.Component { + public static defaultProps: Partial = { + headerSize: HeaderSizes.H3, + }; + constructor(props: MarkdownSectionProps) { + super(props); + this.state = { + shouldShowAnchor: false, + }; + } + public render() { + const sectionName = this.props.sectionName; + const id = utils.getIdFromName(sectionName); + return ( +
+ +
+
+ + + +
+
+ {!_.isUndefined(this.props.githubLink) && + } + /> + } +
+
+ +
+
+ ); + } + private setAnchorVisibility(shouldShowAnchor: boolean) { + this.setState({ + shouldShowAnchor, + }); + } +} diff --git a/packages/website/ts/pages/shared/nested_sidebar_menu.tsx b/packages/website/ts/pages/shared/nested_sidebar_menu.tsx new file mode 100644 index 000000000..e69506bb8 --- /dev/null +++ b/packages/website/ts/pages/shared/nested_sidebar_menu.tsx @@ -0,0 +1,163 @@ +import * as _ from 'lodash'; +import * as React from 'react'; +import MenuItem from 'material-ui/MenuItem'; +import {colors} from 'material-ui/styles'; +import {utils} from 'ts/utils/utils'; +import {constants} from 'ts/utils/constants'; +import {VersionDropDown} from 'ts/pages/shared/version_drop_down'; +import {ZeroExJsDocSections, Styles, MenuSubsectionsBySection, Docs} from 'ts/types'; +import {typeDocUtils} from 'ts/utils/typedoc_utils'; +import {Link as ScrollLink} from 'react-scroll'; + +interface NestedSidebarMenuProps { + topLevelMenu: {[topLevel: string]: string[]}; + menuSubsectionsBySection: MenuSubsectionsBySection; + shouldDisplaySectionHeaders?: boolean; + onMenuItemClick?: () => void; + selectedVersion?: string; + versions?: string[]; + doc?: Docs; + isSectionHeaderClickable?: boolean; +} + +interface NestedSidebarMenuState {} + +const styles: Styles = { + menuItemWithHeaders: { + minHeight: 0, + }, + menuItemWithoutHeaders: { + minHeight: 48, + }, + menuItemInnerDivWithHeaders: { + lineHeight: 2, + }, +}; + +export class NestedSidebarMenu extends React.Component { + public static defaultProps: Partial = { + shouldDisplaySectionHeaders: true, + onMenuItemClick: _.noop, + }; + public render() { + const navigation = _.map(this.props.topLevelMenu, (menuItems: string[], sectionName: string) => { + const finalSectionName = sectionName.replace(/-/g, ' '); + if (this.props.shouldDisplaySectionHeaders) { + const id = utils.getIdFromName(sectionName); + return ( +
+ +
+ {finalSectionName.toUpperCase()} +
+
+ {this.renderMenuItems(menuItems)} +
+ ); + } else { + return ( +
+ {this.renderMenuItems(menuItems)} +
+ ); + } + }); + return ( +
+ {!_.isUndefined(this.props.versions) && + !_.isUndefined(this.props.selectedVersion) && + !_.isUndefined(this.props.doc) && + + } + {navigation} +
+ ); + } + private renderMenuItems(menuItemNames: string[]): React.ReactNode[] { + const menuItemStyles = this.props.shouldDisplaySectionHeaders ? + styles.menuItemWithHeaders : + styles.menuItemWithoutHeaders; + const menuItemInnerDivStyles = this.props.shouldDisplaySectionHeaders ? + styles.menuItemInnerDivWithHeaders : {}; + const menuItems = _.map(menuItemNames, menuItemName => { + const id = utils.getIdFromName(menuItemName); + return ( +
+ + + + {menuItemName} + + + + {this.renderMenuItemSubsections(menuItemName)} +
+ ); + }); + return menuItems; + } + private renderMenuItemSubsections(menuItemName: string): React.ReactNode { + if (_.isUndefined(this.props.menuSubsectionsBySection[menuItemName])) { + return null; + } + return this.renderMenuSubsectionsBySection(menuItemName, this.props.menuSubsectionsBySection[menuItemName]); + } + private renderMenuSubsectionsBySection(menuItemName: string, entityNames: string[]): React.ReactNode { + return ( +
    + {_.map(entityNames, entityName => { + const id = utils.getIdFromName(entityName); + return ( +
  • + + + {entityName} + + +
  • + ); + })} +
+ ); + } + private onMenuItemClick(menuItemName: string): void { + const id = utils.getIdFromName(menuItemName); + utils.setUrlHash(id); + this.props.onMenuItemClick(); + } +} diff --git a/packages/website/ts/pages/shared/section_header.tsx b/packages/website/ts/pages/shared/section_header.tsx new file mode 100644 index 000000000..5937be13b --- /dev/null +++ b/packages/website/ts/pages/shared/section_header.tsx @@ -0,0 +1,50 @@ +import * as React from 'react'; +import {Element as ScrollElement} from 'react-scroll'; +import {AnchorTitle} from 'ts/pages/shared/anchor_title'; +import {utils} from 'ts/utils/utils'; +import {HeaderSizes} from 'ts/types'; + +interface SectionHeaderProps { + sectionName: string; + headerSize?: HeaderSizes; +} + +interface SectionHeaderState { + shouldShowAnchor: boolean; +} + +export class SectionHeader extends React.Component { + public static defaultProps: Partial = { + headerSize: HeaderSizes.H2, + }; + constructor(props: SectionHeaderProps) { + super(props); + this.state = { + shouldShowAnchor: false, + }; + } + public render() { + const sectionName = this.props.sectionName.replace(/-/g, ' '); + const id = utils.getIdFromName(sectionName); + return ( +
+ + {sectionName}} + id={id} + shouldShowAnchor={this.state.shouldShowAnchor} + /> + +
+ ); + } + private setAnchorVisibility(shouldShowAnchor: boolean) { + this.setState({ + shouldShowAnchor, + }); + } +} diff --git a/packages/website/ts/pages/shared/version_drop_down.tsx b/packages/website/ts/pages/shared/version_drop_down.tsx new file mode 100644 index 000000000..f29547c9c --- /dev/null +++ b/packages/website/ts/pages/shared/version_drop_down.tsx @@ -0,0 +1,46 @@ +import * as _ from 'lodash'; +import * as React from 'react'; +import MenuItem from 'material-ui/MenuItem'; +import DropDownMenu from 'material-ui/DropDownMenu'; +import {constants} from 'ts/utils/constants'; +import {Docs} from 'ts/types'; + +interface VersionDropDownProps { + selectedVersion: string; + versions: string[]; + doc: Docs; +} + +interface VersionDropDownState {} + +export class VersionDropDown extends React.Component { + public render() { + return ( +
+ + {this.renderDropDownItems()} + +
+ ); + } + private renderDropDownItems() { + const items = _.map(this.props.versions, version => { + return ( + + ); + }); + return items; + } + private updateSelectedVersion(e: any, index: number, value: string) { + const docPath = constants.docToPath[this.props.doc]; + window.location.href = `${docPath}/${value}${window.location.hash}`; + } +} diff --git a/packages/website/ts/pages/wiki/wiki.tsx b/packages/website/ts/pages/wiki/wiki.tsx new file mode 100644 index 000000000..0e6fc98ab --- /dev/null +++ b/packages/website/ts/pages/wiki/wiki.tsx @@ -0,0 +1,210 @@ +import * as _ from 'lodash'; +import * as React from 'react'; +import DocumentTitle = require('react-document-title'); +import {colors} from 'material-ui/styles'; +import CircularProgress from 'material-ui/CircularProgress'; +import { + scroller, +} from 'react-scroll'; +import {Styles, Article, ArticlesBySection} from 'ts/types'; +import {TopBar} from 'ts/components/top_bar'; +import {HeaderSizes, WebsitePaths} from 'ts/types'; +import {utils} from 'ts/utils/utils'; +import {constants} from 'ts/utils/constants'; +import {configs} from 'ts/utils/configs'; +import {NestedSidebarMenu} from 'ts/pages/shared/nested_sidebar_menu'; +import {SectionHeader} from 'ts/pages/shared/section_header'; +import {MarkdownSection} from 'ts/pages/shared/markdown_section'; + +const WIKI_NOT_READY_BACKOUT_TIMEOUT_MS = 5000; + +export interface WikiProps { + source: string; + location: Location; +} + +interface WikiState { + articlesBySection: ArticlesBySection; +} + +const styles: Styles = { + mainContainers: { + position: 'absolute', + top: 60, + left: 0, + bottom: 0, + right: 0, + overflowZ: 'hidden', + overflowY: 'scroll', + minHeight: 'calc(100vh - 60px)', + WebkitOverflowScrolling: 'touch', + }, + menuContainer: { + borderColor: colors.grey300, + maxWidth: 330, + marginLeft: 20, + }, +}; + +export class Wiki extends React.Component { + private wikiBackoffTimeoutId: number; + constructor(props: WikiProps) { + super(props); + this.state = { + articlesBySection: undefined, + }; + } + public componentWillMount() { + this.fetchArticlesBySectionAsync(); + } + public componentWillUnmount() { + clearTimeout(this.wikiBackoffTimeoutId); + } + public render() { + const menuSubsectionsBySection = _.isUndefined(this.state.articlesBySection) + ? {} + : this.getMenuSubsectionsBySection(this.state.articlesBySection); + return ( +
+ + + {_.isUndefined(this.state.articlesBySection) ? +
+
+
+ +
+
Loading wiki...
+
+
: +
+
+
+ +
+
+
+
+
+

+ + 0x Protocol Wiki + +

+
+ {this.renderWikiArticles()} +
+
+
+
+ } +
+ ); + } + private renderWikiArticles(): React.ReactNode { + const sectionNames = _.keys(this.state.articlesBySection); + const sections = _.map(sectionNames, sectionName => this.renderSection(sectionName)); + return sections; + } + private renderSection(sectionName: string) { + const articles = this.state.articlesBySection[sectionName]; + const renderedArticles = _.map(articles, (article: Article) => { + const githubLink = `${constants.GITHUB_WIKI_URL}/edit/master/${sectionName}/${article.fileName}`; + return ( +
+ +
+ See a way to make this article better?{' '} + + Edit here → + +
+
+ ); + }); + return ( +
+ + {renderedArticles} +
+ ); + } + private scrollToHash(): void { + const hashWithPrefix = this.props.location.hash; + let hash = hashWithPrefix.slice(1); + if (_.isEmpty(hash)) { + hash = '0xProtocolWiki'; // scroll to the top + } + + scroller.scrollTo(hash, {duration: 0, offset: 0, containerId: 'documentation'}); + } + private async fetchArticlesBySectionAsync(): Promise { + const endpoint = `${configs.BACKEND_BASE_URL}${WebsitePaths.Wiki}`; + const response = await fetch(endpoint); + if (response.status === constants.HTTP_NO_CONTENT_STATUS_CODE) { + // We need to backoff and try fetching again later + this.wikiBackoffTimeoutId = window.setTimeout(() => { + this.fetchArticlesBySectionAsync(); + }, WIKI_NOT_READY_BACKOUT_TIMEOUT_MS); + return; + } + if (response.status !== 200) { + // TODO: Show the user an error message when the wiki fail to load + const errMsg = await response.text(); + utils.consoleLog(`Failed to load wiki: ${response.status} ${errMsg}`); + return; + } + const articlesBySection = await response.json(); + this.setState({ + articlesBySection, + }, () => { + this.scrollToHash(); + }); + } + private getMenuSubsectionsBySection(articlesBySection: ArticlesBySection) { + const sectionNames = _.keys(articlesBySection); + const menuSubsectionsBySection: {[section: string]: string[]} = {}; + for (const sectionName of sectionNames) { + const articles = articlesBySection[sectionName]; + const articleNames = _.map(articles, article => article.title); + menuSubsectionsBySection[sectionName] = articleNames; + } + return menuSubsectionsBySection; + } +} diff --git a/packages/website/ts/redux/dispatcher.ts b/packages/website/ts/redux/dispatcher.ts new file mode 100644 index 000000000..6badf95bd --- /dev/null +++ b/packages/website/ts/redux/dispatcher.ts @@ -0,0 +1,244 @@ +import {Dispatch} from 'redux'; +import {State} from 'ts/redux/reducer'; +import { + Direction, + Side, + AssetToken, + BlockchainErrs, + Token, + SignatureData, + Fill, + Order, + ActionTypes, + ScreenWidths, + ProviderType, + TokenStateByAddress, +} from 'ts/types'; +import BigNumber from 'bignumber.js'; + +export class Dispatcher { + private dispatch: Dispatch; + constructor(dispatch: Dispatch) { + this.dispatch = dispatch; + } + // Portal + public resetState() { + this.dispatch({ + type: ActionTypes.RESET_STATE, + }); + } + public updateNodeVersion(nodeVersion: string) { + this.dispatch({ + data: nodeVersion, + type: ActionTypes.UPDATE_NODE_VERSION, + }); + } + public updateScreenWidth(screenWidth: ScreenWidths) { + this.dispatch({ + data: screenWidth, + type: ActionTypes.UPDATE_SCREEN_WIDTH, + }); + } + public swapAssetTokenSymbols() { + this.dispatch({ + type: ActionTypes.SWAP_ASSET_TOKENS, + }); + } + public updateGenerateOrderStep(direction: Direction) { + this.dispatch({ + data: direction, + type: ActionTypes.UPDATE_GENERATE_ORDER_STEP, + }); + } + public updateOrderSalt(salt: BigNumber) { + this.dispatch({ + data: salt, + type: ActionTypes.UPDATE_ORDER_SALT, + }); + } + public updateUserSuppliedOrderCache(order: Order) { + this.dispatch({ + data: order, + type: ActionTypes.UPDATE_USER_SUPPLIED_ORDER_CACHE, + }); + } + public updateShouldBlockchainErrDialogBeOpen(shouldBeOpen: boolean) { + this.dispatch({ + data: shouldBeOpen, + type: ActionTypes.UPDATE_SHOULD_BLOCKCHAIN_ERR_DIALOG_BE_OPEN, + }); + } + public updateChosenAssetToken(side: Side, token: AssetToken) { + this.dispatch({ + data: { + side, + token, + }, + type: ActionTypes.UPDATE_CHOSEN_ASSET_TOKEN, + }); + } + public updateChosenAssetTokenAddress(side: Side, address: string) { + this.dispatch({ + data: { + address, + side, + }, + type: ActionTypes.UPDATE_CHOSEN_ASSET_TOKEN_ADDRESS, + }); + } + public updateOrderTakerAddress(address: string) { + this.dispatch({ + data: address, + type: ActionTypes.UPDATE_ORDER_TAKER_ADDRESS, + }); + } + public updateUserAddress(address: string) { + this.dispatch({ + data: address, + type: ActionTypes.UPDATE_USER_ADDRESS, + }); + } + public updateOrderExpiry(unixTimestampSec: BigNumber) { + this.dispatch({ + data: unixTimestampSec, + type: ActionTypes.UPDATE_ORDER_EXPIRY, + }); + } + public encounteredBlockchainError(err: BlockchainErrs) { + this.dispatch({ + data: err, + type: ActionTypes.BLOCKCHAIN_ERR_ENCOUNTERED, + }); + } + public updateBlockchainIsLoaded(isLoaded: boolean) { + this.dispatch({ + data: isLoaded, + type: ActionTypes.UPDATE_BLOCKCHAIN_IS_LOADED, + }); + } + public addTokenToTokenByAddress(token: Token) { + this.dispatch({ + data: token, + type: ActionTypes.ADD_TOKEN_TO_TOKEN_BY_ADDRESS, + }); + } + public removeTokenToTokenByAddress(token: Token) { + this.dispatch({ + data: token, + type: ActionTypes.REMOVE_TOKEN_TO_TOKEN_BY_ADDRESS, + }); + } + public clearTokenByAddress() { + this.dispatch({ + type: ActionTypes.CLEAR_TOKEN_BY_ADDRESS, + }); + } + public updateTokenByAddress(tokens: Token[]) { + this.dispatch({ + data: tokens, + type: ActionTypes.UPDATE_TOKEN_BY_ADDRESS, + }); + } + public updateTokenStateByAddress(tokenStateByAddress: TokenStateByAddress) { + this.dispatch({ + data: tokenStateByAddress, + type: ActionTypes.UPDATE_TOKEN_STATE_BY_ADDRESS, + }); + } + public removeFromTokenStateByAddress(tokenAddress: string) { + this.dispatch({ + data: tokenAddress, + type: ActionTypes.REMOVE_FROM_TOKEN_STATE_BY_ADDRESS, + }); + } + public replaceTokenAllowanceByAddress(address: string, allowance: BigNumber) { + this.dispatch({ + data: { + address, + allowance, + }, + type: ActionTypes.REPLACE_TOKEN_ALLOWANCE_BY_ADDRESS, + }); + } + public replaceTokenBalanceByAddress(address: string, balance: BigNumber) { + this.dispatch({ + data: { + address, + balance, + }, + type: ActionTypes.REPLACE_TOKEN_BALANCE_BY_ADDRESS, + }); + } + public updateTokenBalanceByAddress(address: string, balanceDelta: BigNumber) { + this.dispatch({ + data: { + address, + balanceDelta, + }, + type: ActionTypes.UPDATE_TOKEN_BALANCE_BY_ADDRESS, + }); + } + public updateSignatureData(signatureData: SignatureData) { + this.dispatch({ + data: signatureData, + type: ActionTypes.UPDATE_ORDER_SIGNATURE_DATA, + }); + } + public updateUserEtherBalance(balance: BigNumber) { + this.dispatch({ + data: balance, + type: ActionTypes.UPDATE_USER_ETHER_BALANCE, + }); + } + public updateNetworkId(networkId: number) { + this.dispatch({ + data: networkId, + type: ActionTypes.UPDATE_NETWORK_ID, + }); + } + public updateOrderFillAmount(amount: BigNumber) { + this.dispatch({ + data: amount, + type: ActionTypes.UPDATE_ORDER_FILL_AMOUNT, + }); + } + + // Docs + public updateCurrentDocsVersion(version: string) { + this.dispatch({ + data: version, + type: ActionTypes.UPDATE_LIBRARY_VERSION, + }); + } + public updateAvailableDocVersions(versions: string[]) { + this.dispatch({ + data: versions, + type: ActionTypes.UPDATE_AVAILABLE_LIBRARY_VERSIONS, + }); + } + + // Shared + public showFlashMessage(msg: string|React.ReactNode) { + this.dispatch({ + data: msg, + type: ActionTypes.SHOW_FLASH_MESSAGE, + }); + } + public hideFlashMessage() { + this.dispatch({ + type: ActionTypes.HIDE_FLASH_MESSAGE, + }); + } + public updateProviderType(providerType: ProviderType) { + this.dispatch({ + type: ActionTypes.UPDATE_PROVIDER_TYPE, + data: providerType, + }); + } + public updateInjectedProviderName(injectedProviderName: string) { + this.dispatch({ + type: ActionTypes.UPDATE_INJECTED_PROVIDER_NAME, + data: injectedProviderName, + }); + } +} diff --git a/packages/website/ts/redux/reducer.ts b/packages/website/ts/redux/reducer.ts new file mode 100644 index 000000000..7723597cd --- /dev/null +++ b/packages/website/ts/redux/reducer.ts @@ -0,0 +1,363 @@ +import * as _ from 'lodash'; +import {ZeroEx} from '0x.js'; +import BigNumber from 'bignumber.js'; +import {utils} from 'ts/utils/utils'; +import { + GenerateOrderSteps, + Side, + SideToAssetToken, + Direction, + BlockchainErrs, + SignatureData, + TokenByAddress, + TokenStateByAddress, + Order, + Action, + ActionTypes, + ScreenWidths, + ProviderType, + TokenState, +} from 'ts/types'; + +// Instead of defaulting the docs version to an empty string, we pre-populate it with +// a valid version value. This does not need to be updated however, since onLoad, it +// is always replaced with a value retrieved from our S3 bucket. +const DEFAULT_DOCS_VERSION = '0.0.0'; + +export interface State { + // Portal + blockchainErr: BlockchainErrs; + blockchainIsLoaded: boolean; + generateOrderStep: GenerateOrderSteps; + networkId: number; + orderExpiryTimestamp: BigNumber; + orderFillAmount: BigNumber; + orderTakerAddress: string; + orderSignatureData: SignatureData; + orderSalt: BigNumber; + nodeVersion: string; + screenWidth: ScreenWidths; + shouldBlockchainErrDialogBeOpen: boolean; + sideToAssetToken: SideToAssetToken; + tokenByAddress: TokenByAddress; + tokenStateByAddress: TokenStateByAddress; + userAddress: string; + userEtherBalance: BigNumber; + // Note: cache of supplied orderJSON in fill order step. Do not use for anything else. + userSuppliedOrderCache: Order; + + // Docs + docsVersion: string; + availableDocVersions: string[]; + + // Shared + flashMessage: string|React.ReactNode; + providerType: ProviderType; + injectedProviderName: string; +}; + +const INITIAL_STATE: State = { + // Portal + blockchainErr: '', + blockchainIsLoaded: false, + generateOrderStep: GenerateOrderSteps.ChooseAssets, + networkId: undefined, + orderExpiryTimestamp: utils.initialOrderExpiryUnixTimestampSec(), + orderFillAmount: undefined, + orderSignatureData: { + hash: '', + r: '', + s: '', + v: 27, + }, + orderTakerAddress: '', + orderSalt: ZeroEx.generatePseudoRandomSalt(), + nodeVersion: undefined, + screenWidth: utils.getScreenWidth(), + shouldBlockchainErrDialogBeOpen: false, + sideToAssetToken: { + [Side.deposit]: {}, + [Side.receive]: {}, + }, + tokenByAddress: {}, + tokenStateByAddress: {}, + userAddress: '', + userEtherBalance: new BigNumber(0), + userSuppliedOrderCache: undefined, + + // Docs + docsVersion: DEFAULT_DOCS_VERSION, + availableDocVersions: [DEFAULT_DOCS_VERSION], + + // Shared + flashMessage: undefined, + providerType: ProviderType.INJECTED, + injectedProviderName: '', +}; + +export function reducer(state: State = INITIAL_STATE, action: Action) { + switch (action.type) { + // Portal + case ActionTypes.RESET_STATE: + return INITIAL_STATE; + + case ActionTypes.UPDATE_ORDER_SALT: { + return _.assign({}, state, { + orderSalt: action.data, + }); + } + + case ActionTypes.UPDATE_NODE_VERSION: { + return _.assign({}, state, { + nodeVersion: action.data, + }); + } + + case ActionTypes.UPDATE_ORDER_FILL_AMOUNT: { + return _.assign({}, state, { + orderFillAmount: action.data, + }); + } + + case ActionTypes.UPDATE_SHOULD_BLOCKCHAIN_ERR_DIALOG_BE_OPEN: { + return _.assign({}, state, { + shouldBlockchainErrDialogBeOpen: action.data, + }); + } + + case ActionTypes.UPDATE_USER_ETHER_BALANCE: { + return _.assign({}, state, { + userEtherBalance: action.data, + }); + } + + case ActionTypes.UPDATE_USER_SUPPLIED_ORDER_CACHE: { + return _.assign({}, state, { + userSuppliedOrderCache: action.data, + }); + } + + case ActionTypes.CLEAR_TOKEN_BY_ADDRESS: { + return _.assign({}, state, { + tokenByAddress: {}, + }); + } + + case ActionTypes.ADD_TOKEN_TO_TOKEN_BY_ADDRESS: { + const newTokenByAddress = state.tokenByAddress; + newTokenByAddress[action.data.address] = action.data; + return _.assign({}, state, { + tokenByAddress: newTokenByAddress, + }); + } + + case ActionTypes.REMOVE_TOKEN_TO_TOKEN_BY_ADDRESS: { + const newTokenByAddress = state.tokenByAddress; + delete newTokenByAddress[action.data.address]; + return _.assign({}, state, { + tokenByAddress: newTokenByAddress, + }); + } + + case ActionTypes.UPDATE_TOKEN_BY_ADDRESS: { + const tokenByAddress = state.tokenByAddress; + const tokens = action.data; + _.each(tokens, token => { + const updatedToken = _.assign({}, tokenByAddress[token.address], token); + tokenByAddress[token.address] = updatedToken; + }); + return _.assign({}, state, { + tokenByAddress, + }); + } + + case ActionTypes.UPDATE_TOKEN_STATE_BY_ADDRESS: { + const tokenStateByAddress = state.tokenStateByAddress; + const updatedTokenStateByAddress = action.data; + _.each(updatedTokenStateByAddress, (tokenState: TokenState, address: string) => { + const updatedTokenState = _.assign({}, tokenStateByAddress[address], tokenState); + tokenStateByAddress[address] = updatedTokenState; + }); + return _.assign({}, state, { + tokenStateByAddress, + }); + } + + case ActionTypes.REMOVE_FROM_TOKEN_STATE_BY_ADDRESS: { + const tokenStateByAddress = state.tokenStateByAddress; + const tokenAddress = action.data; + delete tokenStateByAddress[tokenAddress]; + return _.assign({}, state, { + tokenStateByAddress, + }); + } + + case ActionTypes.REPLACE_TOKEN_ALLOWANCE_BY_ADDRESS: { + const tokenStateByAddress = state.tokenStateByAddress; + const allowance = action.data.allowance; + const tokenAddress = action.data.address; + tokenStateByAddress[tokenAddress] = _.assign({}, tokenStateByAddress[tokenAddress], { + allowance, + }); + return _.assign({}, state, { + tokenStateByAddress, + }); + } + + case ActionTypes.REPLACE_TOKEN_BALANCE_BY_ADDRESS: { + const tokenStateByAddress = state.tokenStateByAddress; + const balance = action.data.balance; + const tokenAddress = action.data.address; + tokenStateByAddress[tokenAddress] = _.assign({}, tokenStateByAddress[tokenAddress], { + balance, + }); + return _.assign({}, state, { + tokenStateByAddress, + }); + } + + case ActionTypes.UPDATE_TOKEN_BALANCE_BY_ADDRESS: { + const tokenStateByAddress = state.tokenStateByAddress; + const balanceDelta = action.data.balanceDelta; + const tokenAddress = action.data.address; + const currBalance = tokenStateByAddress[tokenAddress].balance; + tokenStateByAddress[tokenAddress] = _.assign({}, tokenStateByAddress[tokenAddress], { + balance: currBalance.plus(balanceDelta), + }); + return _.assign({}, state, { + tokenStateByAddress, + }); + } + + case ActionTypes.UPDATE_ORDER_SIGNATURE_DATA: { + return _.assign({}, state, { + orderSignatureData: action.data, + }); + } + + case ActionTypes.UPDATE_SCREEN_WIDTH: { + return _.assign({}, state, { + screenWidth: action.data, + }); + } + + case ActionTypes.UPDATE_BLOCKCHAIN_IS_LOADED: { + return _.assign({}, state, { + blockchainIsLoaded: action.data, + }); + } + + case ActionTypes.BLOCKCHAIN_ERR_ENCOUNTERED: { + return _.assign({}, state, { + blockchainErr: action.data, + }); + } + + case ActionTypes.UPDATE_NETWORK_ID: { + return _.assign({}, state, { + networkId: action.data, + }); + } + + case ActionTypes.UPDATE_GENERATE_ORDER_STEP: { + const direction = action.data; + let nextGenerateOrderStep = state.generateOrderStep; + if (direction === Direction.forward) { + nextGenerateOrderStep += 1; + } else if (state.generateOrderStep !== 0) { + nextGenerateOrderStep -= 1; + } + return _.assign({}, state, { + generateOrderStep: nextGenerateOrderStep, + }); + } + + case ActionTypes.UPDATE_CHOSEN_ASSET_TOKEN: { + const newSideToAssetToken = _.assign({}, state.sideToAssetToken, { + [action.data.side]: action.data.token, + }); + return _.assign({}, state, { + sideToAssetToken: newSideToAssetToken, + }); + } + + case ActionTypes.UPDATE_CHOSEN_ASSET_TOKEN_ADDRESS: { + const newAssetToken = state.sideToAssetToken[action.data.side]; + newAssetToken.address = action.data.address; + const newSideToAssetToken = _.assign({}, state.sideToAssetToken, { + [action.data.side]: newAssetToken, + }); + return _.assign({}, state, { + sideToAssetToken: newSideToAssetToken, + }); + } + + case ActionTypes.SWAP_ASSET_TOKENS: { + const newSideToAssetToken = _.assign({}, state.sideToAssetToken, { + [Side.deposit]: state.sideToAssetToken[Side.receive], + [Side.receive]: state.sideToAssetToken[Side.deposit], + }); + return _.assign({}, state, { + sideToAssetToken: newSideToAssetToken, + }); + } + + case ActionTypes.UPDATE_ORDER_EXPIRY: { + return _.assign({}, state, { + orderExpiryTimestamp: action.data, + }); + } + + case ActionTypes.UPDATE_ORDER_TAKER_ADDRESS: { + return _.assign({}, state, { + orderTakerAddress: action.data, + }); + } + + case ActionTypes.UPDATE_USER_ADDRESS: { + return _.assign({}, state, { + userAddress: action.data, + }); + } + + // Docs + case ActionTypes.UPDATE_LIBRARY_VERSION: { + return _.assign({}, state, { + docsVersion: action.data, + }); + } + case ActionTypes.UPDATE_AVAILABLE_LIBRARY_VERSIONS: { + return _.assign({}, state, { + availableDocVersions: action.data, + }); + } + + // Shared + case ActionTypes.SHOW_FLASH_MESSAGE: { + return _.assign({}, state, { + flashMessage: action.data, + }); + } + + case ActionTypes.HIDE_FLASH_MESSAGE: { + return _.assign({}, state, { + flashMessage: undefined, + }); + } + + case ActionTypes.UPDATE_PROVIDER_TYPE: { + return _.assign({}, state, { + providerType: action.data, + }); + } + + case ActionTypes.UPDATE_INJECTED_PROVIDER_NAME: { + return _.assign({}, state, { + injectedProviderName: action.data, + }); + } + + default: + return state; + } +} diff --git a/packages/website/ts/schemas/order_schema.ts b/packages/website/ts/schemas/order_schema.ts new file mode 100644 index 000000000..61b93d273 --- /dev/null +++ b/packages/website/ts/schemas/order_schema.ts @@ -0,0 +1,24 @@ +export const orderSchema = { + id: '/Order', + properties: { + maker: {$ref: '/OrderTaker'}, + taker: {$ref: '/OrderTaker'}, + salt: {type: 'string'}, + signature: {$ref: '/SignatureData'}, + expiration: {type: 'string'}, + feeRecipient: {type: 'string'}, + exchangeContract: {type: 'string'}, + networkId: {type: 'number'}, + }, + required: [ + 'maker', + 'taker', + 'salt', + 'signature', + 'expiration', + 'feeRecipient', + 'exchangeContract', + 'networkId', + ], + type: 'object', +}; diff --git a/packages/website/ts/schemas/order_taker_schema.ts b/packages/website/ts/schemas/order_taker_schema.ts new file mode 100644 index 000000000..6b484a60d --- /dev/null +++ b/packages/website/ts/schemas/order_taker_schema.ts @@ -0,0 +1,11 @@ +export const orderTakerSchema = { + id: '/OrderTaker', + properties: { + address: {type: 'string'}, + token: {$ref: '/Token'}, + amount: {type: 'string'}, + feeAmount: {type: 'string'}, + }, + required: ['address', 'token', 'amount', 'feeAmount'], + type: 'object', +}; diff --git a/packages/website/ts/schemas/signature_data_schema.ts b/packages/website/ts/schemas/signature_data_schema.ts new file mode 100644 index 000000000..d208cc438 --- /dev/null +++ b/packages/website/ts/schemas/signature_data_schema.ts @@ -0,0 +1,11 @@ +export const signatureDataSchema = { + id: '/SignatureData', + properties: { + hash: {type: 'string'}, + r: {type: 'string'}, + s: {type: 'string'}, + v: {type: 'number'}, + }, + required: ['hash', 'r', 's', 'v'], + type: 'object', +}; diff --git a/packages/website/ts/schemas/token_schema.ts b/packages/website/ts/schemas/token_schema.ts new file mode 100644 index 000000000..c15f57429 --- /dev/null +++ b/packages/website/ts/schemas/token_schema.ts @@ -0,0 +1,11 @@ +export const tokenSchema = { + id: '/Token', + properties: { + name: {type: 'string'}, + symbol: {type: 'string'}, + decimals: {type: 'number'}, + address: {type: 'string'}, + }, + required: ['name', 'symbol', 'decimals', 'address'], + type: 'object', +}; diff --git a/packages/website/ts/schemas/validator.ts b/packages/website/ts/schemas/validator.ts new file mode 100644 index 000000000..bf6ba4044 --- /dev/null +++ b/packages/website/ts/schemas/validator.ts @@ -0,0 +1,19 @@ +import {Validator, Schema as JSONSchema} from 'jsonschema'; +import {signatureDataSchema} from 'ts/schemas/signature_data_schema'; +import {orderSchema} from 'ts/schemas/order_schema'; +import {tokenSchema} from 'ts/schemas/token_schema'; +import {orderTakerSchema} from 'ts/schemas/order_taker_schema'; + +export class SchemaValidator { + private validator: Validator; + constructor() { + this.validator = new Validator(); + this.validator.addSchema(signatureDataSchema as JSONSchema, signatureDataSchema.id); + this.validator.addSchema(tokenSchema as JSONSchema, tokenSchema.id); + this.validator.addSchema(orderTakerSchema as JSONSchema, orderTakerSchema.id); + this.validator.addSchema(orderSchema as JSONSchema, orderSchema.id); + } + public validate(instance: object, schema: Schema) { + return this.validator.validate(instance, schema); + } +} diff --git a/packages/website/ts/subproviders/injected_web3_subprovider.ts b/packages/website/ts/subproviders/injected_web3_subprovider.ts new file mode 100644 index 000000000..b9e5af3ef --- /dev/null +++ b/packages/website/ts/subproviders/injected_web3_subprovider.ts @@ -0,0 +1,44 @@ +import * as _ from 'lodash'; +import Web3 = require('web3'); +import {constants} from 'ts/utils/constants'; + +/* + * This class implements the web3-provider-engine subprovider interface and forwards + * requests involving user accounts (getAccounts, sendTransaction, etc...) to the injected + * web3 instance in their browser. + * Source: https://github.com/MetaMask/provider-engine/blob/master/subproviders/subprovider.js + */ +export class InjectedWeb3SubProvider { + private injectedWeb3: Web3; + constructor(injectedWeb3: Web3) { + this.injectedWeb3 = injectedWeb3; + } + public handleRequest(payload: any, next: () => void, end: (err: Error, result: any) => void) { + switch (payload.method) { + case 'web3_clientVersion': + this.injectedWeb3.version.getNode(end); + return; + case 'eth_accounts': + this.injectedWeb3.eth.getAccounts(end); + return; + + case 'eth_sendTransaction': + const [txParams] = payload.params; + this.injectedWeb3.eth.sendTransaction(txParams, end); + return; + + case 'eth_sign': + const [address, message] = payload.params; + this.injectedWeb3.eth.sign(address, message, end); + return; + + default: + next(); + return; + } + } + // Required to implement this method despite not needing it for this subprovider + public setEngine(engine: any) { + // noop + } +} diff --git a/packages/website/ts/subproviders/ledger_wallet_subprovider_factory.ts b/packages/website/ts/subproviders/ledger_wallet_subprovider_factory.ts new file mode 100644 index 000000000..df0c5a4db --- /dev/null +++ b/packages/website/ts/subproviders/ledger_wallet_subprovider_factory.ts @@ -0,0 +1,172 @@ +import * as _ from 'lodash'; +import Web3 = require('web3'); +import * as EthereumTx from 'ethereumjs-tx'; +import ethUtil = require('ethereumjs-util'); +import * as ledger from 'ledgerco'; +import HookedWalletSubprovider = require('web3-provider-engine/subproviders/hooked-wallet'); +import {constants} from 'ts/utils/constants'; +import {LedgerEthConnection, SignPersonalMessageParams, TxParams} from 'ts/types'; + +const NUM_ADDRESSES_TO_FETCH = 10; +const ASK_FOR_ON_DEVICE_CONFIRMATION = false; +const SHOULD_GET_CHAIN_CODE = false; + +export class LedgerWallet { + public isU2FSupported: boolean; + public getAccounts: (callback: (err: Error, accounts: string[]) => void) => void; + public signMessage: (msgParams: SignPersonalMessageParams, + callback: (err: Error, result?: string) => void) => void; + public signTransaction: (txParams: TxParams, + callback: (err: Error, result?: string) => void) => void; + private getNetworkId: () => number; + private path: string; + private pathIndex: number; + private ledgerEthConnection: LedgerEthConnection; + private accounts: string[]; + constructor(getNetworkIdFn: () => number) { + this.path = constants.DEFAULT_DERIVATION_PATH; + this.pathIndex = 0; + this.isU2FSupported = false; + this.getNetworkId = getNetworkIdFn; + this.getAccounts = this.getAccountsAsync.bind(this); + this.signMessage = this.signPersonalMessageAsync.bind(this); + this.signTransaction = this.signTransactionAsync.bind(this); + } + public getPath(): string { + return this.path; + } + public setPath(derivationPath: string) { + this.path = derivationPath; + // HACK: Must re-assign getAccounts, signMessage and signTransaction since they were + // previously bound to old values of this.path + this.getAccounts = this.getAccountsAsync.bind(this); + this.signMessage = this.signPersonalMessageAsync.bind(this); + this.signTransaction = this.signTransactionAsync.bind(this); + } + public setPathIndex(pathIndex: number) { + this.pathIndex = pathIndex; + // HACK: Must re-assign signMessage & signTransaction since they it was previously bound to + // old values of this.path + this.signMessage = this.signPersonalMessageAsync.bind(this); + this.signTransaction = this.signTransactionAsync.bind(this); + } + public async getAccountsAsync(callback: (err: Error, accounts: string[]) => void) { + if (!_.isUndefined(this.ledgerEthConnection)) { + callback(null, []); + return; + } + this.ledgerEthConnection = await this.createLedgerConnectionAsync(); + + const accounts = []; + for (let i = 0; i < NUM_ADDRESSES_TO_FETCH; i++) { + try { + const derivationPath = `${this.path}/${i}`; + const result = await this.ledgerEthConnection.getAddress_async( + derivationPath, ASK_FOR_ON_DEVICE_CONFIRMATION, SHOULD_GET_CHAIN_CODE, + ); + accounts.push(result.address.toLowerCase()); + } catch (err) { + await this.closeLedgerConnectionAsync(); + callback(err, null); + return; + } + } + + await this.closeLedgerConnectionAsync(); + callback(null, accounts); + } + public async signTransactionAsync(txParams: TxParams, callback: (err: Error, result?: string) => void) { + const tx = new EthereumTx(txParams); + + const networkId = this.getNetworkId(); + const chainId = networkId; // Same thing + + // Set the EIP155 bits + tx.raw[6] = Buffer.from([chainId]); // v + tx.raw[7] = Buffer.from([]); // r + tx.raw[8] = Buffer.from([]); // s + + const txHex = tx.serialize().toString('hex'); + + this.ledgerEthConnection = await this.createLedgerConnectionAsync(); + + try { + const derivationPath = this.getDerivationPath(); + const result = await this.ledgerEthConnection.signTransaction_async(derivationPath, txHex); + + // Store signature in transaction + tx.v = new Buffer(result.v, 'hex'); + tx.r = new Buffer(result.r, 'hex'); + tx.s = new Buffer(result.s, 'hex'); + + // EIP155: v should be chain_id * 2 + {35, 36} + const signedChainId = Math.floor((tx.v[0] - 35) / 2); + if (signedChainId !== chainId) { + const err = new Error('TOO_OLD_LEDGER_FIRMWARE'); + callback(err, null); + return; + } + + const signedTxHex = `0x${tx.serialize().toString('hex')}`; + await this.closeLedgerConnectionAsync(); + callback(null, signedTxHex); + } catch (err) { + await this.closeLedgerConnectionAsync(); + callback(err, null); + } + } + public async signPersonalMessageAsync(msgParams: SignPersonalMessageParams, + callback: (err: Error, result?: string) => void) { + if (!_.isUndefined(this.ledgerEthConnection)) { + callback(new Error('Another request is in progress.')); + return; + } + this.ledgerEthConnection = await this.createLedgerConnectionAsync(); + + try { + const derivationPath = this.getDerivationPath(); + const result = await this.ledgerEthConnection.signPersonalMessage_async( + derivationPath, ethUtil.stripHexPrefix(msgParams.data), + ); + const v = _.parseInt(result.v) - 27; + let vHex = v.toString(16); + if (vHex.length < 2) { + vHex = `0${v}`; + } + const signature = `0x${result.r}${result.s}${vHex}`; + await this.closeLedgerConnectionAsync(); + callback(null, signature); + } catch (err) { + await this.closeLedgerConnectionAsync(); + callback(err, null); + } + } + private async createLedgerConnectionAsync() { + if (!_.isUndefined(this.ledgerEthConnection)) { + throw new Error('Multiple open connections to the Ledger disallowed.'); + } + const ledgerConnection = await ledger.comm_u2f.create_async(); + const ledgerEthConnection = new ledger.eth(ledgerConnection); + return ledgerEthConnection; + } + private async closeLedgerConnectionAsync() { + if (_.isUndefined(this.ledgerEthConnection)) { + return; + } + await this.ledgerEthConnection.comm.close_async(); + this.ledgerEthConnection = undefined; + } + private getDerivationPath() { + const derivationPath = `${this.path}/${this.pathIndex}`; + return derivationPath; + } +} + +export const ledgerWalletSubproviderFactory = (getNetworkIdFn: () => number): LedgerWallet => { + const ledgerWallet = new LedgerWallet(getNetworkIdFn); + const ledgerWalletSubprovider = new HookedWalletSubprovider(ledgerWallet) as LedgerWallet; + ledgerWalletSubprovider.getPath = ledgerWallet.getPath.bind(ledgerWallet); + ledgerWalletSubprovider.setPath = ledgerWallet.setPath.bind(ledgerWallet); + ledgerWalletSubprovider.setPathIndex = ledgerWallet.setPathIndex.bind(ledgerWallet); + return ledgerWalletSubprovider; +}; diff --git a/packages/website/ts/subproviders/redundant_rpc_subprovider.ts b/packages/website/ts/subproviders/redundant_rpc_subprovider.ts new file mode 100644 index 000000000..a6c53ebd1 --- /dev/null +++ b/packages/website/ts/subproviders/redundant_rpc_subprovider.ts @@ -0,0 +1,41 @@ +import * as _ from 'lodash'; +import {JSONRPCPayload} from 'ts/types'; +import promisify = require('es6-promisify'); +import Subprovider = require('web3-provider-engine/subproviders/subprovider'); +import RpcSubprovider = require('web3-provider-engine/subproviders/rpc'); + +export class RedundantRPCSubprovider extends Subprovider { + private rpcs: RpcSubprovider[]; + constructor(endpoints: string[]) { + super(); + this.rpcs = _.map(endpoints, endpoint => { + return new RpcSubprovider({ + rpcUrl: endpoint, + }); + }); + } + public async handleRequest(payload: JSONRPCPayload, next: () => void, + end: (err?: Error, data?: any) => void): Promise { + const rpcsCopy = this.rpcs.slice(); + try { + const data = await this.firstSuccessAsync(rpcsCopy, payload, next); + end(null, data); + } catch (err) { + end(err); + } + + } + private async firstSuccessAsync(rpcs: RpcSubprovider[], payload: JSONRPCPayload, next: () => void): Promise { + let lastErr; + for (const rpc of rpcs) { + try { + const data = await promisify(rpc.handleRequest.bind(rpc))(payload, next); + return data; + } catch (err) { + lastErr = err; + continue; + } + } + throw Error(lastErr); + } +} diff --git a/packages/website/ts/types.ts b/packages/website/ts/types.ts new file mode 100644 index 000000000..2d0103499 --- /dev/null +++ b/packages/website/ts/types.ts @@ -0,0 +1,692 @@ +import * as _ from 'lodash'; +import BigNumber from 'bignumber.js'; + +// Utility function to create a K:V from a list of strings +// Adapted from: https://basarat.gitbooks.io/typescript/content/docs/types/literal-types.html +function strEnum(values: string[]): {[key: string]: string} { + return _.reduce(values, (result, key) => { + result[key] = key; + return result; + }, Object.create(null)); +} + +export enum GenerateOrderSteps { + ChooseAssets, + GrantAllowance, + RemainingConfigs, + SignTransaction, + CopyAndShare, +}; + +export const Side = strEnum([ + 'receive', + 'deposit', +]); +export type Side = keyof typeof Side; + +export const BlockchainErrs = strEnum([ + 'A_CONTRACT_NOT_DEPLOYED_ON_NETWORK', + 'DISCONNECTED_FROM_ETHEREUM_NODE', + 'UNHANDLED_ERROR', +]); +export type BlockchainErrs = keyof typeof BlockchainErrs; + +export const Direction = strEnum([ + 'forward', + 'backward', +]); +export type Direction = keyof typeof Direction; + +export interface Token { + iconUrl?: string; + name: string; + address: string; + symbol: string; + decimals: number; + isTracked: boolean; + isRegistered: boolean; +}; + +export interface TokenByAddress { + [address: string]: Token; +}; + +export interface TokenState { + allowance: BigNumber; + balance: BigNumber; +} + +export interface TokenStateByAddress { + [address: string]: TokenState; +}; + +export interface AssetToken { + address?: string; + amount?: BigNumber; +} + +export interface SideToAssetToken { + [side: string]: AssetToken; +}; + +export interface SignatureData { + hash: string; + r: string; + s: string; + v: number; +}; + +export interface HashData { + depositAmount: BigNumber; + depositTokenContractAddr: string; + feeRecipientAddress: string; + makerFee: BigNumber; + orderExpiryTimestamp: BigNumber; + orderMakerAddress: string; + orderTakerAddress: string; + receiveAmount: BigNumber; + receiveTokenContractAddr: string; + takerFee: BigNumber; + orderSalt: BigNumber; +} + +export interface OrderToken { + name: string; + symbol: string; + decimals: number; + address: string; +} + +export interface OrderParty { + address: string; + token: OrderToken; + amount: string; + feeAmount: string; +} + +export interface Order { + maker: OrderParty; + taker: OrderParty; + expiration: string; + feeRecipient: string; + salt: string; + signature: SignatureData; + exchangeContract: string; + networkId: number; +} + +export interface Fill { + logIndex: number; + maker: string; + taker: string; + makerToken: string; + takerToken: string; + filledMakerTokenAmount: BigNumber; + filledTakerTokenAmount: BigNumber; + paidMakerFee: BigNumber; + paidTakerFee: BigNumber; + orderHash: string; + transactionHash: string; + blockTimestamp: number; +} + +export enum BalanceErrs { + incorrectNetworkForFaucet, + faucetRequestFailed, + faucetQueueIsFull, + mintingFailed, + wethConversionFailed, + sendFailed, + allowanceSettingFailed, +}; + +export const ActionTypes = strEnum([ + // Portal + 'UPDATE_SCREEN_WIDTH', + 'UPDATE_NODE_VERSION', + 'RESET_STATE', + 'ADD_TOKEN_TO_TOKEN_BY_ADDRESS', + 'BLOCKCHAIN_ERR_ENCOUNTERED', + 'CLEAR_TOKEN_BY_ADDRESS', + 'UPDATE_BLOCKCHAIN_IS_LOADED', + 'UPDATE_NETWORK_ID', + 'UPDATE_GENERATE_ORDER_STEP', + 'UPDATE_CHOSEN_ASSET_TOKEN', + 'UPDATE_CHOSEN_ASSET_TOKEN_ADDRESS', + 'UPDATE_ORDER_TAKER_ADDRESS', + 'UPDATE_ORDER_SALT', + 'UPDATE_ORDER_SIGNATURE_DATA', + 'UPDATE_TOKEN_BY_ADDRESS', + 'REMOVE_TOKEN_TO_TOKEN_BY_ADDRESS', + 'UPDATE_TOKEN_STATE_BY_ADDRESS', + 'REMOVE_FROM_TOKEN_STATE_BY_ADDRESS', + 'REPLACE_TOKEN_ALLOWANCE_BY_ADDRESS', + 'REPLACE_TOKEN_BALANCE_BY_ADDRESS', + 'UPDATE_TOKEN_BALANCE_BY_ADDRESS', + 'UPDATE_ORDER_EXPIRY', + 'SWAP_ASSET_TOKENS', + 'UPDATE_USER_ADDRESS', + 'UPDATE_USER_ETHER_BALANCE', + 'UPDATE_USER_SUPPLIED_ORDER_CACHE', + 'UPDATE_ORDER_FILL_AMOUNT', + 'UPDATE_SHOULD_BLOCKCHAIN_ERR_DIALOG_BE_OPEN', + + // Docs + 'UPDATE_LIBRARY_VERSION', + 'UPDATE_AVAILABLE_LIBRARY_VERSIONS', + + // Shared + 'SHOW_FLASH_MESSAGE', + 'HIDE_FLASH_MESSAGE', + 'UPDATE_PROVIDER_TYPE', + 'UPDATE_INJECTED_PROVIDER_NAME', +]); +export type ActionTypes = keyof typeof ActionTypes; + +export interface Action { + type: ActionTypes; + data?: any; +} + +export interface TrackedTokensByNetworkId { + [networkId: number]: Token; +} + +export interface Styles { + [name: string]: React.CSSProperties; +} + +export interface ProfileInfo { + name: string; + title?: string; + description: string; + image: string; + linkedIn?: string; + github?: string; + angellist?: string; + medium?: string; + twitter?: string; +} + +export interface Partner { + name: string; + logo: string; + url: string; +} + +export interface Statistic { + title: string; + figure: string; +} + +export interface StatisticByKey { + [key: string]: Statistic; +} + +export interface ERC20MarketInfo { + etherMarketCapUsd: number; + numLiquidERC20Tokens: number; + marketCapERC20TokensUsd: number; +} + +export enum ExchangeContractErrs { + OrderFillExpired = 'ORDER_FILL_EXPIRED', + OrderAlreadyCancelledOrFilled = 'ORDER_ALREADY_CANCELLED_OR_FILLED', + OrderRemainingFillAmountZero = 'ORDER_REMAINING_FILL_AMOUNT_ZERO', + OrderFillRoundingError = 'ORDER_FILL_ROUNDING_ERROR', + FillBalanceAllowanceError = 'FILL_BALANCE_ALLOWANCE_ERROR', + InsufficientTakerBalance = 'INSUFFICIENT_TAKER_BALANCE', + InsufficientTakerAllowance = 'INSUFFICIENT_TAKER_ALLOWANCE', + InsufficientMakerBalance = 'INSUFFICIENT_MAKER_BALANCE', + InsufficientMakerAllowance = 'INSUFFICIENT_MAKER_ALLOWANCE', + TransactionSenderIsNotFillOrderTaker = 'TRANSACTION_SENDER_IS_NOT_FILL_ORDER_TAKER', + InsufficientRemainingFillAmount = 'INSUFFICIENT_REMAINING_FILL_AMOUNT', +} + +export interface ContractResponse { + logs: ContractEvent[]; +} + +export interface ContractEvent { + event: string; + args: any; +} + +export type InputErrMsg = React.ReactNode | string | undefined; +export type ValidatedBigNumberCallback = (isValid: boolean, amount?: BigNumber) => void; +export const ScreenWidths = strEnum([ + 'SM', + 'MD', + 'LG', +]); +export type ScreenWidths = keyof typeof ScreenWidths; + +export enum AlertTypes { + ERROR, + SUCCESS, +} + +export const EtherscanLinkSuffixes = strEnum([ + 'address', + 'tx', +]); +export type EtherscanLinkSuffixes = keyof typeof EtherscanLinkSuffixes; + +export const BlockchainCallErrs = strEnum([ + 'CONTRACT_DOES_NOT_EXIST', + 'USER_HAS_NO_ASSOCIATED_ADDRESSES', + 'UNHANDLED_ERROR', + 'TOKEN_ADDRESS_IS_INVALID', + 'INVALID_SIGNATURE', +]); +export type BlockchainCallErrs = keyof typeof BlockchainCallErrs; + +export const KindString = strEnum([ + 'Constructor', + 'Property', + 'Method', + 'Interface', + 'Type alias', + 'Variable', + 'Function', + 'Enumeration', +]); +export type KindString = keyof typeof KindString; + +export interface EnumValue { + name: string; + defaultValue?: string; +} + +export enum Environments { + DEVELOPMENT, + PRODUCTION, +} + +export type ContractInstance = any; // TODO: add type definition for Contract + +export interface TypeDocType { + type: TypeDocTypes; + value: string; + name: string; + types: TypeDocType[]; + typeArguments?: TypeDocType[]; + declaration: TypeDocNode; + elementType?: TypeDocType; +} + +export interface TypeDocFlags { + isStatic?: boolean; + isOptional?: boolean; + isPublic?: boolean; +} + +export interface TypeDocGroup { + title: string; + children: number[]; +} + +export interface TypeDocNode { + id?: number; + name?: string; + kind?: string; + defaultValue?: string; + kindString?: string; + type?: TypeDocType; + fileName?: string; + line?: number; + comment?: TypeDocNode; + text?: string; + shortText?: string; + returns?: string; + declaration: TypeDocNode; + flags?: TypeDocFlags; + indexSignature?: TypeDocNode[]; + signatures?: TypeDocNode[]; + parameters?: TypeDocNode[]; + typeParameter?: TypeDocNode[]; + sources?: TypeDocNode[]; + children?: TypeDocNode[]; + groups?: TypeDocGroup[]; +} + +export enum TypeDocTypes { + Intrinsic = 'intrinsic', + Reference = 'reference', + Array = 'array', + StringLiteral = 'stringLiteral', + Reflection = 'reflection', + Union = 'union', + TypeParameter = 'typeParameter', + Unknown = 'unknown', +} + +export interface DocAgnosticFormat { + [sectionName: string]: DocSection; +} + +export interface DocSection { + comment: string; + constructors: Array; + methods: Array; + properties: Property[]; + types: CustomType[]; + events?: Event[]; +} + +export interface Event { + name: string; + eventArgs: EventArg[]; +} + +export interface EventArg { + isIndexed: boolean; + name: string; + type: Type; +} + +export interface Property { + name: string; + type: Type; + source?: Source; + comment?: string; +} + +export interface BaseMethod { + isConstructor: boolean; + name: string; + returnComment?: string|undefined; + callPath: string; + parameters: Parameter[]; + returnType: Type; + comment?: string; +} + +export interface TypescriptMethod extends BaseMethod { + source?: Source; + isStatic?: boolean; + typeParameter?: TypeParameter; +} + +export interface SolidityMethod extends BaseMethod { + isConstant?: boolean; + isPayable?: boolean; +} + +export interface Source { + fileName: string; + line: number; +} + +export interface Parameter { + name: string; + comment: string; + isOptional: boolean; + type: Type; +} + +export interface TypeParameter { + name: string; + type: Type; +} + +export interface Type { + name: string; + typeDocType: TypeDocTypes; + value?: string; + typeArguments?: Type[]; + elementType?: ElementType; + types?: Type[]; + method?: TypescriptMethod; +} + +export interface ElementType { + name: string; + typeDocType: TypeDocTypes; +} + +export interface IndexSignature { + keyName: string; + keyType: Type; + valueName: string; +} + +export interface CustomType { + name: string; + kindString: string; + type?: Type; + method?: TypescriptMethod; + indexSignature?: IndexSignature; + defaultValue?: string; + comment?: string; + children?: CustomTypeChild[]; +} + +export interface CustomTypeChild { + name: string; + type?: Type; + defaultValue?: string; +} + +export const ZeroExJsDocSections = strEnum([ + 'introduction', + 'installation', + 'testrpc', + 'async', + 'errors', + 'versioning', + 'zeroEx', + 'exchange', + 'token', + 'tokenRegistry', + 'etherToken', + 'proxy', + 'types', +]); +export type ZeroExJsDocSections = keyof typeof ZeroExJsDocSections; + +export const SmartContractsDocSections = strEnum([ + 'Introduction', + 'Exchange', + 'TokenTransferProxy', + 'TokenRegistry', + 'ZRXToken', + 'EtherToken', +]); +export type SmartContractsDocSections = keyof typeof SmartContractsDocSections; + +export interface FAQQuestion { + prompt: string; + answer: React.ReactNode; +} +export interface FAQSection { + name: string; + questions: FAQQuestion[]; +} + +export interface S3FileObject { + Key: { + _text: string; + }; +} + +export interface MenuSubsectionsBySection { + [section: string]: string[]; +} + +export const ProviderType = strEnum([ + 'INJECTED', + 'LEDGER', +]); +export type ProviderType = keyof typeof ProviderType; + +export interface Fact { + title: string; + explanation: string; + image: string; +} + +interface LedgerGetAddressResult { + address: string; +} +interface LedgerSignResult { + v: string; + r: string; + s: string; +} +interface LedgerCommunication { + close_async: () => void; +} +export interface LedgerEthConnection { + getAddress_async: (derivationPath: string, askForDeviceConfirmation: boolean, + shouldGetChainCode: boolean) => Promise; + signPersonalMessage_async: (derivationPath: string, messageHex: string) => Promise; + signTransaction_async: (derivationPath: string, txHex: string) => Promise; + comm: LedgerCommunication; +} +export interface SignPersonalMessageParams { + data: string; +} + +export interface LedgerWalletSubprovider { + getPath: () => string; + setPath: (path: string) => void; + setPathIndex: (pathIndex: number) => void; +} + +export interface TxParams { + nonce: string; + gasPrice?: number; + gasLimit: string; + to: string; + value?: string; + data?: string; + chainId: number; // EIP 155 chainId - mainnet: 1, ropsten: 3 +} + +export interface PublicNodeUrlsByNetworkId { + [networkId: number]: string[]; +}; + +export interface JSONRPCPayload { + params: any[]; + method: string; +} + +export interface BlogPost { + image: string; + date: string; + title: string; + description: string; + url: string; +} + +export interface TypeDefinitionByName { + [typeName: string]: CustomType; +} + +export interface Article { + section: string; + title: string; + content: string; + fileName: string; +} + +export interface ArticlesBySection { + [section: string]: Article[]; +} + +export interface DialogConfigs { + title: string; + isModal: boolean; + actions: any[]; +} + +export enum TokenVisibility { + ALL = 'ALL', + UNTRACKED = 'UNTRACKED', + TRACKED = 'TRACKED', +} + +export enum HeaderSizes { + H1 = 'h1', + H2 = 'h2', + H3 = 'h3', +} + +export interface DoxityDocObj { + [contractName: string]: DoxityContractObj; +} + +export interface DoxityContractObj { + title: string; + fileName: string; + name: string; + abiDocs: DoxityAbiDoc[]; +} + +export interface DoxityAbiDoc { + constant: boolean; + inputs: DoxityInput[]; + name: string; + outputs: DoxityOutput[]; + payable: boolean; + type: string; + details?: string; + return?: string; +} + +export interface DoxityOutput { + name: string; + type: string; +} + +export interface DoxityInput { + name: string; + type: string; + description: string; + indexed?: boolean; +} + +export interface VersionToFileName { + [version: string]: string; +} + +export enum Docs { + ZeroExJs, + SmartContracts, +} + +export interface ContractAddresses { + [version: string]: { + [network: string]: AddressByContractName; + }; +} + +export interface AddressByContractName { + [contractName: string]: string; +} + +export enum Networks { + mainnet = 'Mainnet', + kovan = 'Kovan', + ropsten = 'Ropsten', + rinkeby = 'Rinkeby', +} + +export enum AbiTypes { + Constructor = 'constructor', + Function = 'function', + Event = 'event', +} + +export enum WebsitePaths { + Portal = '/portal', + Wiki = '/wiki', + ZeroExJs = '/docs/0xjs', + Home = '/', + FAQ = '/faq', + About = '/about', + Whitepaper = '/pdfs/0x_white_paper.pdf', + SmartContracts = '/docs/contracts', +} diff --git a/packages/website/ts/utils/configs.ts b/packages/website/ts/utils/configs.ts new file mode 100644 index 000000000..49ac4b5e0 --- /dev/null +++ b/packages/website/ts/utils/configs.ts @@ -0,0 +1,18 @@ +import * as _ from 'lodash'; +import {Environments} from 'ts/types'; + +const BASE_URL = window.location.origin; +const isDevelopment = _.includes(BASE_URL, 'https://0xproject.dev:3572') || + _.includes(BASE_URL, 'https://localhost:3572') || + _.includes(BASE_URL, 'https://127.0.0.1'); + +export const configs = { + BASE_URL, + ENVIRONMENT: isDevelopment ? Environments.DEVELOPMENT : Environments.PRODUCTION, + BACKEND_BASE_URL: isDevelopment ? 'https://localhost:3001' : 'https://website-api.0xproject.com', + symbolsOfMintableTokens: ['MKR', 'MLN', 'GNT', 'DGD', 'REP'], + // WARNING: ZRX & WETH MUST always be default trackedTokens + defaultTrackedTokenSymbols: ['WETH', 'ZRX'], + lastLocalStorageFillClearanceDate: '2017-09-09', + isMainnetEnabled: true, +}; diff --git a/packages/website/ts/utils/constants.ts b/packages/website/ts/utils/constants.ts new file mode 100644 index 000000000..42b80795e --- /dev/null +++ b/packages/website/ts/utils/constants.ts @@ -0,0 +1,270 @@ +import { + ExchangeContractErrs, + PublicNodeUrlsByNetworkId, + ZeroExJsDocSections, + SmartContractsDocSections, + Docs, + ContractAddresses, + Networks, + WebsitePaths, +} from 'ts/types'; +import BigNumber from 'bignumber.js'; + +const INFURA_API_KEY = 'T5WSC8cautR4KXyYgsRs'; + +export const constants = { + ANGELLIST_URL: 'https://angel.co/0xproject/jobs', + STAGING_DOMAIN: 'staging-0xproject.s3-website-us-east-1.amazonaws.com', + PRODUCTION_DOMAIN: '0xproject.com', + DEVELOPMENT_DOMAIN: '0xproject.dev:3572', + BIGNUMBERJS_GITHUB_URL: 'http://mikemcl.github.io/bignumber.js', + BITLY_ACCESS_TOKEN: 'ffc4c1a31e5143848fb7c523b39f91b9b213d208', + BITLY_ENDPOINT: 'https://api-ssl.bitly.com', + BLOG_URL: 'https://blog.0xproject.com/latest', + CUSTOM_BLUE: '#60a4f4', + DEFAULT_DERIVATION_PATH: `44'/60'/0'`, + ETHER_FAUCET_ENDPOINT: 'https://faucet.0xproject.com', + FEE_RECIPIENT_ADDRESS: '0x0000000000000000000000000000000000000000', + FIREFOX_U2F_ADDON: 'https://addons.mozilla.org/en-US/firefox/addon/u2f-support-add-on/', + GITHUB_URL: 'https://github.com/0xProject', + GITHUB_0X_JS_URL: 'https://github.com/0xProject/0x.js', + GITHUB_CONTRACTS_URL: 'https://github.com/0xProject/contracts', + GITHUB_WIKI_URL: 'https://github.com/0xProject/wiki', + HTTP_NO_CONTENT_STATUS_CODE: 204, + ACCEPT_DISCLAIMER_LOCAL_STORAGE_KEY: 'didAcceptPortalDisclaimer', + LINKEDIN_0X_URL: 'https://www.linkedin.com/company/0x', + LEDGER_PROVIDER_NAME: 'Ledger', + METAMASK_PROVIDER_NAME: 'Metamask', + GENESIS_ORDER_BLOCK_BY_NETWORK_ID: { + 1: 4145578, + 42: 3117574, + 50: 0, + } as {[networkId: number]: number}, + PUBLIC_PROVIDER_NAME: '0x Public', + // The order matters. We first try first node and only then fall back to others. + PUBLIC_NODE_URLS_BY_NETWORK_ID: { + [1]: [ + `https://mainnet.infura.io/${INFURA_API_KEY}`, + ], + [42]: [ + `https://kovan.infura.io/${INFURA_API_KEY}`, + ], + } as PublicNodeUrlsByNetworkId, + PARITY_SIGNER_PROVIDER_NAME: 'Parity Signer', + GENERIC_PROVIDER_NAME: 'Injected Web3', + MAKER_FEE: new BigNumber(0), + MAINNET_NAME: 'Main network', + MAINNET_NETWORK_ID: 1, + METAMASK_CHROME_STORE_URL: 'https://chrome.google.com/webstore/detail/metamask/nkbihfbeogaeaoehlefnkodbefgpgknn', + // tslint:disable-next-line:max-line-length + PARITY_CHROME_STORE_URL: 'https://chrome.google.com/webstore/detail/parity-ethereum-integrati/himekenlppkgeaoeddcliojfddemadig', + MIST_DOWNLOAD_URL: 'https://github.com/ethereum/mist/releases', + NULL_ADDRESS: '0x0000000000000000000000000000000000000000', + ROLLBAR_ACCESS_TOKEN: 'a6619002b51c4464928201e6ea94de65', + DOCS_SCROLL_DURATION_MS: 0, + DOCS_CONTAINER_ID: 'documentation', + HOME_SCROLL_DURATION_MS: 500, + REDDIT_URL: 'https://reddit.com/r/0xproject', + STANDARD_RELAYER_API_GITHUB: 'https://github.com/0xProject/standard-relayer-api/blob/master/README.md', + SUCCESS_STATUS: 200, + S3_0XJS_DOCUMENTATION_JSON_ROOT: 'https://s3.amazonaws.com/0xjs-docs-jsons', + S3_SMART_CONTRACTS_DOCUMENTATION_JSON_ROOT: 'https://s3.amazonaws.com/smart-contracts-docs-json', + UNAVAILABLE_STATUS: 503, + TAKER_FEE: new BigNumber(0), + TESTNET_NAME: 'Kovan', + TESTNET_NETWORK_ID: 42, + TESTRPC_NETWORK_ID: 50, + TWITTER_URL: 'https://twitter.com/0xproject', + ETH_DECIMAL_PLACES: 18, + MINT_AMOUNT: new BigNumber('100000000000000000000'), + WEB3_DOCS_URL: 'https://github.com/ethereum/wiki/wiki/JavaScript-API', + WEB3_PROVIDER_DOCS_URL: 'https://github.com/ethereum/wiki/wiki/JavaScript-API#example-7', + ZEROEX_CHAT_URL: 'https://chat.0xproject.com', + // Projects + ETHFINEX_URL: 'https://www.bitfinex.com/ethfinex', + RADAR_RELAY_URL: 'https://radarrelay.com', + PARADEX_URL: 'https://paradex.io', + DYDX_URL: 'https://dydx.exchange', + MELONPORT_URL: 'https://melonport.com', + DISTRICT_0X_URL: 'https://district0x.io', + DHARMA_URL: 'https://dharma.io', + LENDROID_URL: 'https://lendroid.com', + MAKER_URL: 'https://makerdao.com', + ARAGON_URL: 'https://aragon.one', + BLOCKNET_URL: 'https://blocknet.co', + OCEAN_URL: 'http://the0cean.com', + STATUS_URL: 'https://status.im', + AUGUR_URL: 'https://augur.net', + AUCTUS_URL: 'https://auctus.org', + OPEN_ANX_URL: 'https://www.openanx.org', + + iconUrlBySymbol: { + 'REP': '/images/token_icons/augur.png', + 'DGD': '/images/token_icons/digixdao.png', + 'WETH': '/images/token_icons/ether_erc20.png', + 'MLN': '/images/token_icons/melon.png', + 'GNT': '/images/token_icons/golem.png', + 'MKR': '/images/token_icons/makerdao.png', + 'ZRX': '/images/token_icons/zero_ex.png', + 'ANT': '/images/token_icons/aragon.png', + 'BNT': '/images/token_icons/bancor.png', + 'BAT': '/images/token_icons/basicattentiontoken.png', + 'CVC': '/images/token_icons/civic.png', + 'EOS': '/images/token_icons/eos.png', + 'FUN': '/images/token_icons/funfair.png', + 'GNO': '/images/token_icons/gnosis.png', + 'ICN': '/images/token_icons/iconomi.png', + 'OMG': '/images/token_icons/omisego.png', + 'SNT': '/images/token_icons/status.png', + 'STORJ': '/images/token_icons/storjcoinx.png', + 'PAY': '/images/token_icons/tenx.png', + 'QTUM': '/images/token_icons/qtum.png', + 'DNT': '/images/token_icons/district0x.png', + 'SNGLS': '/images/token_icons/singularity.png', + 'EDG': '/images/token_icons/edgeless.png', + '1ST': '/images/token_icons/firstblood.jpg', + 'WINGS': '/images/token_icons/wings.png', + 'BQX': '/images/token_icons/bitquence.png', + 'LUN': '/images/token_icons/lunyr.png', + 'RLC': '/images/token_icons/iexec.png', + 'MCO': '/images/token_icons/monaco.png', + 'ADT': '/images/token_icons/adtoken.png', + 'CFI': '/images/token_icons/cofound-it.png', + 'ROL': '/images/token_icons/etheroll.png', + 'WGNT': '/images/token_icons/golem.png', + 'MTL': '/images/token_icons/metal.png', + 'NMR': '/images/token_icons/numeraire.png', + 'SAN': '/images/token_icons/santiment.png', + 'TAAS': '/images/token_icons/taas.png', + 'TKN': '/images/token_icons/tokencard.png', + 'TRST': '/images/token_icons/trust.png', + } as {[symbol: string]: string}, + networkNameById: { + 1: Networks.mainnet, + 3: Networks.ropsten, + 4: Networks.rinkeby, + 42: Networks.kovan, + } as {[symbol: number]: string}, + networkIdByName: { + [Networks.mainnet]: 1, + [Networks.ropsten]: 3, + [Networks.rinkeby]: 4, + [Networks.kovan]: 42, + } as {[networkName: string]: number}, + // Note: This needs to be kept in sync with the types exported in index.ts. Unfortunately there is + // currently no way to extract the re-exported types from index.ts via TypeDoc :( + public0xjsTypes: [ + 'Order', + 'SignedOrder', + 'ECSignature', + 'ZeroExError', + 'EventCallback', + 'EventCallbackAsync', + 'EventCallbackSync', + 'ExchangeContractErrs', + 'ContractEvent', + 'Token', + 'ExchangeEvents', + 'IndexedFilterValues', + 'SubscriptionOpts', + 'BlockParam', + 'OrderFillOrKillRequest', + 'OrderCancellationRequest', + 'OrderFillRequest', + 'ContractEventEmitter', + 'Web3Provider', + 'ContractEventArgs', + 'LogCancelArgs', + 'LogFillArgs', + 'LogErrorContractEventArgs', + 'LogFillContractEventArgs', + 'LogCancelContractEventArgs', + 'TokenEvents', + 'ExchangeContractEventArgs', + 'TransferContractEventArgs', + 'ApprovalContractEventArgs', + 'TokenContractEventArgs', + 'ZeroExConfig', + 'TransactionReceiptWithDecodedLogs', + 'LogWithDecodedArgs', + 'DecodedLogArgs', + 'MethodOpts', + 'ValidateOrderFillableOpts', + 'OrderTransactionOpts', + 'ContractEventArg', + 'LogEvent', + 'LogEntry', + 'DecodedLogEvent', + ], + menuSmartContracts: { + introduction: [ + SmartContractsDocSections.Introduction, + ], + contracts: [ + SmartContractsDocSections.Exchange, + SmartContractsDocSections.TokenRegistry, + SmartContractsDocSections.ZRXToken, + SmartContractsDocSections.EtherToken, + SmartContractsDocSections.TokenTransferProxy, + ], + }, + menu0xjs: { + introduction: [ + ZeroExJsDocSections.introduction, + ], + install: [ + ZeroExJsDocSections.installation, + ], + topics: [ + ZeroExJsDocSections.async, + ZeroExJsDocSections.errors, + ZeroExJsDocSections.versioning, + ], + zeroEx: [ + ZeroExJsDocSections.zeroEx, + ], + contracts: [ + ZeroExJsDocSections.exchange, + ZeroExJsDocSections.token, + ZeroExJsDocSections.tokenRegistry, + ZeroExJsDocSections.etherToken, + ZeroExJsDocSections.proxy, + ], + types: [ + ZeroExJsDocSections.types, + ], + }, + menuSubsectionToVersionWhenIntroduced: { + [ZeroExJsDocSections.etherToken]: '0.7.1', + [ZeroExJsDocSections.proxy]: '0.8.0', + }, + docToPath: { + [Docs.ZeroExJs]: WebsitePaths.ZeroExJs, + [Docs.SmartContracts]: WebsitePaths.SmartContracts, + }, + contractAddresses: { + '1.0.0': { + [Networks.mainnet]: { + [SmartContractsDocSections.Exchange]: '0x12459c951127e0c374ff9105dda097662a027093', + [SmartContractsDocSections.TokenTransferProxy]: '0x8da0d80f5007ef1e431dd2127178d224e32c2ef4', + [SmartContractsDocSections.ZRXToken]: '0xe41d2489571d322189246dafa5ebde1f4699f498', + [SmartContractsDocSections.EtherToken]: '0x2956356cd2a2bf3202f771f50d3d14a367b48070', + [SmartContractsDocSections.TokenRegistry]: '0x926a74c5c36adf004c87399e65f75628b0f98d2c', + }, + [Networks.ropsten]: { + [SmartContractsDocSections.Exchange]: '0x479cc461fecd078f766ecc58533d6f69580cf3ac', + [SmartContractsDocSections.TokenTransferProxy]: '0x4e9aad8184de8833365fea970cd9149372fdf1e6', + [SmartContractsDocSections.ZRXToken]: '0xa8e9fa8f91e5ae138c74648c9c304f1c75003a8d', + [SmartContractsDocSections.EtherToken]: '0xc00fd9820cd2898cc4c054b7bf142de637ad129a', + [SmartContractsDocSections.TokenRegistry]: '0x6b1a50f0bb5a7995444bd3877b22dc89c62843ed', + }, + [Networks.kovan]: { + [SmartContractsDocSections.Exchange]: '0x90fe2af704b34e0224bf2299c838e04d4dcf1364', + [SmartContractsDocSections.TokenTransferProxy]: '0x087Eed4Bc1ee3DE49BeFbd66C662B434B15d49d4', + [SmartContractsDocSections.ZRXToken]: '0x6ff6c0ff1d68b964901f986d4c9fa3ac68346570', + [SmartContractsDocSections.EtherToken]: '0x05d090b51c40b020eab3bfcb6a2dff130df22e9c', + [SmartContractsDocSections.TokenRegistry]: '0xf18e504561f4347bea557f3d4558f559dddbae7f', + }, + }, + } as ContractAddresses, +}; diff --git a/packages/website/ts/utils/doc_utils.ts b/packages/website/ts/utils/doc_utils.ts new file mode 100644 index 000000000..ca6e0dc52 --- /dev/null +++ b/packages/website/ts/utils/doc_utils.ts @@ -0,0 +1,55 @@ +import * as _ from 'lodash'; +import findVersions = require('find-versions'); +import convert = require('xml-js'); +import {constants} from 'ts/utils/constants'; +import {utils} from 'ts/utils/utils'; +import {VersionToFileName, S3FileObject, TypeDocNode, DoxityDocObj} from 'ts/types'; + +export const docUtils = { + async getVersionToFileNameAsync(s3DocJsonRoot: string): + Promise { + const versionFileNames = await this.getVersionFileNamesAsync(s3DocJsonRoot); + const versionToFileName: VersionToFileName = {}; + _.each(versionFileNames, fileName => { + const [version] = findVersions(fileName); + versionToFileName[version] = fileName; + }); + return versionToFileName; + }, + async getVersionFileNamesAsync(s3DocJsonRoot: string): Promise { + const response = await fetch(s3DocJsonRoot); + if (response.status !== 200) { + // TODO: Show the user an error message when the docs fail to load + const errMsg = await response.text(); + utils.consoleLog(`Failed to load JSON file list: ${response.status} ${errMsg}`); + return; + } + const responseXML = await response.text(); + const responseJSONString = convert.xml2json(responseXML, { + compact: true, + }); + const responseObj = JSON.parse(responseJSONString); + let fileObjs: S3FileObject[]; + if (_.isArray(responseObj.ListBucketResult.Contents)) { + fileObjs = responseObj.ListBucketResult.Contents as S3FileObject[]; + } else { + fileObjs = [responseObj.ListBucketResult.Contents]; + } + const versionFileNames = _.map(fileObjs, fileObj => { + return fileObj.Key._text; + }); + return versionFileNames; + }, + async getJSONDocFileAsync(fileName: string, s3DocJsonRoot: string): Promise { + const endpoint = `${s3DocJsonRoot}/${fileName}`; + const response = await fetch(endpoint); + if (response.status !== 200) { + // TODO: Show the user an error message when the docs fail to load + const errMsg = await response.text(); + utils.consoleLog(`Failed to load Doc JSON: ${response.status} ${errMsg}`); + return; + } + const jsonDocObj = await response.json(); + return jsonDocObj; + }, +}; diff --git a/packages/website/ts/utils/doxity_utils.ts b/packages/website/ts/utils/doxity_utils.ts new file mode 100644 index 000000000..3bab0a69d --- /dev/null +++ b/packages/website/ts/utils/doxity_utils.ts @@ -0,0 +1,162 @@ +import * as _ from 'lodash'; +import { + DoxityDocObj, + DoxityContractObj, + DoxityAbiDoc, + DoxityInput, + DocAgnosticFormat, + DocSection, + Parameter, + Property, + Type, + TypeDocTypes, + EventArg, + AbiTypes, + SolidityMethod, +} from 'ts/types'; + +export const doxityUtils = { + convertToDocAgnosticFormat(doxityDocObj: DoxityDocObj): DocAgnosticFormat { + const docAgnosticFormat: DocAgnosticFormat = {}; + _.each(doxityDocObj, (doxityContractObj: DoxityContractObj, contractName: string) => { + const doxityConstructor = _.find(doxityContractObj.abiDocs, (abiDoc: DoxityAbiDoc) => { + return abiDoc.type === AbiTypes.Constructor; + }); + const constructors = []; + if (!_.isUndefined(doxityConstructor)) { + const constructor = { + isConstructor: true, + name: doxityContractObj.name, + comment: doxityConstructor.details, + returnComment: doxityConstructor.return, + callPath: '', + parameters: this._convertParameters(doxityConstructor.inputs), + returnType: this._convertType(doxityContractObj.name), + }; + constructors.push(constructor); + } + + const doxityMethods: DoxityAbiDoc[] = _.filter + (doxityContractObj.abiDocs, (abiDoc: DoxityAbiDoc) => { + return this._isMethod(abiDoc); + }); + const methods: SolidityMethod[] = _.map(doxityMethods, + (doxityMethod: DoxityAbiDoc) => { + // We assume that none of our functions returns more then a single value + const outputIfExists = !_.isUndefined(doxityMethod.outputs) ? + doxityMethod.outputs[0] : + undefined; + const returnTypeIfExists = !_.isUndefined(outputIfExists) ? + this._convertType(outputIfExists.type) : + undefined; + // For ZRXToken, we want to convert it to zrxToken, rather then simply zRXToken + const callPath = contractName !== 'ZRXToken' ? + `${contractName[0].toLowerCase()}${contractName.slice(1)}.` : + `${contractName.slice(0, 3).toLowerCase()}${contractName.slice(3)}.`; + const method = { + isConstructor: false, + isConstant: doxityMethod.constant, + isPayable: doxityMethod.payable, + name: doxityMethod.name, + comment: doxityMethod.details, + returnComment: doxityMethod.return, + callPath, + parameters: this._convertParameters(doxityMethod.inputs), + returnType: returnTypeIfExists, + }; + return method; + }); + + const doxityProperties: DoxityAbiDoc[] = _.filter + (doxityContractObj.abiDocs, (abiDoc: DoxityAbiDoc) => { + return this._isProperty(abiDoc); + }); + const properties = _.map(doxityProperties, (doxityProperty: DoxityAbiDoc) => { + // We assume that none of our functions return more then a single return value + let typeName = doxityProperty.outputs[0].type; + if (!_.isEmpty(doxityProperty.inputs)) { + // Properties never have more then a single input + typeName = `(${doxityProperty.inputs[0].type} => ${typeName})`; + } + const property = { + name: doxityProperty.name, + type: this._convertType(typeName), + comment: doxityProperty.details, + }; + return property; + }); + + const doxityEvents = _.filter( + doxityContractObj.abiDocs, (abiDoc: DoxityAbiDoc) => abiDoc.type === AbiTypes.Event, + ); + const events = _.map(doxityEvents, doxityEvent => { + const event = { + name: doxityEvent.name, + eventArgs: this._convertEventArgs(doxityEvent.inputs), + }; + return event; + }); + + const docSection: DocSection = { + comment: doxityContractObj.title, + constructors, + methods, + properties, + types: [], + events, + }; + docAgnosticFormat[contractName] = docSection; + }); + return docAgnosticFormat; + }, + _convertParameters(inputs: DoxityInput[]): Parameter[] { + const parameters = _.map(inputs, input => { + const parameter = { + name: input.name, + comment: input.description, + isOptional: false, + type: this._convertType(input.type), + }; + return parameter; + }); + return parameters; + }, + _convertType(typeName: string): Type { + const type = { + name: typeName, + typeDocType: TypeDocTypes.Intrinsic, + }; + return type; + }, + _isMethod(abiDoc: DoxityAbiDoc) { + if (abiDoc.type !== AbiTypes.Function) { + return false; + } + const hasInputs = !_.isEmpty(abiDoc.inputs); + const hasNamedOutputIfExists = !hasInputs || !_.isEmpty(abiDoc.inputs[0].name); + const isNameAllCaps = abiDoc.name === abiDoc.name.toUpperCase(); + const isMethod = hasNamedOutputIfExists && !isNameAllCaps; + return isMethod; + }, + _isProperty(abiDoc: DoxityAbiDoc) { + if (abiDoc.type !== AbiTypes.Function) { + return false; + } + const hasInputs = !_.isEmpty(abiDoc.inputs); + const hasNamedOutputIfExists = !hasInputs || !_.isEmpty(abiDoc.inputs[0].name); + const isNameAllCaps = abiDoc.name === abiDoc.name.toUpperCase(); + const isProperty = !hasNamedOutputIfExists || isNameAllCaps; + return isProperty; + }, + _convertEventArgs(inputs: DoxityInput[]): EventArg[] { + const eventArgs = _.map(inputs, input => { + const eventArg = { + isIndexed: input.indexed, + name: input.name, + type: this._convertType(input.type), + }; + return eventArg; + }); + return eventArgs; + }, +}; diff --git a/packages/website/ts/utils/error_reporter.ts b/packages/website/ts/utils/error_reporter.ts new file mode 100644 index 000000000..a9731c4d4 --- /dev/null +++ b/packages/website/ts/utils/error_reporter.ts @@ -0,0 +1,52 @@ +import {utils} from 'ts/utils/utils'; +import {constants} from 'ts/utils/constants'; +import {configs} from 'ts/utils/configs'; +import {Environments} from 'ts/types'; + +// Suggested way to include Rollbar with Webpack +// https://github.com/rollbar/rollbar.js/tree/master/examples/webpack +const rollbarConfig = { + accessToken: constants.ROLLBAR_ACCESS_TOKEN, + captureUncaught: true, + captureUnhandledRejections: true, + itemsPerMinute: 10, + maxItems: 500, + payload: { + environment: configs.ENVIRONMENT, + }, + uncaughtErrorLevel: 'error', + hostWhiteList: [constants.PRODUCTION_DOMAIN, constants.STAGING_DOMAIN], + ignoredMessages: [ + // Errors from the third-party scripts + 'Script error', + // Network errors or ad-blockers + 'TypeError: Failed to fetch', + 'Exchange has not been deployed to detected network (network/artifact mismatch)', + // Source: https://groups.google.com/a/chromium.org/forum/#!topic/chromium-discuss/7VU0_VvC7mE + 'undefined is not an object (evaluating \'__gCrWeb.autofill.extractForms\')', + // Source: http://stackoverflow.com/questions/43399818/securityerror-from-facebook-and-cross-domain-messaging + 'SecurityError (DOM Exception 18)', + ], +}; +import Rollbar = require('../../public/js/rollbar.umd.nojson.min.js'); +const rollbar = Rollbar.init(rollbarConfig); + +export const errorReporter = { + reportAsync(err: Error): Promise { + if (configs.ENVIRONMENT === Environments.DEVELOPMENT) { + return; // Let's not log development errors to rollbar + } + + return new Promise((resolve, reject) => { + rollbar.error(err, (rollbarErr: Error) => { + if (rollbarErr) { + utils.consoleLog(`Error reporting to rollbar, ignoring: ${rollbarErr}`); + // We never want to reject and cause the app to throw because of rollbar + resolve(); + } else { + resolve(); + } + }); + }); + }, +}; diff --git a/packages/website/ts/utils/typedoc_utils.ts b/packages/website/ts/utils/typedoc_utils.ts new file mode 100644 index 000000000..b3d0f7d90 --- /dev/null +++ b/packages/website/ts/utils/typedoc_utils.ts @@ -0,0 +1,356 @@ +import * as _ from 'lodash'; +import compareVersions = require('compare-versions'); +import {constants} from 'ts/utils/constants'; +import {utils} from 'ts/utils/utils'; +import { + TypeDocNode, + KindString, + ZeroExJsDocSections, + MenuSubsectionsBySection, + TypeDocType, + Type, + DocAgnosticFormat, + DocSection, + TypescriptMethod, + Parameter, + Property, + CustomType, + IndexSignature, + CustomTypeChild, + TypeParameter, + TypeDocTypes, +} from 'ts/types'; + +const TYPES_MODULE_PATH = '"src/types"'; + +export const sectionNameToPossibleModulePaths: {[name: string]: string[]} = { + [ZeroExJsDocSections.zeroEx]: ['"src/0x"'], + [ZeroExJsDocSections.exchange]: ['"src/contract_wrappers/exchange_wrapper"'], + [ZeroExJsDocSections.tokenRegistry]: ['"src/contract_wrappers/token_registry_wrapper"'], + [ZeroExJsDocSections.token]: ['"src/contract_wrappers/token_wrapper"'], + [ZeroExJsDocSections.etherToken]: ['"src/contract_wrappers/ether_token_wrapper"'], + [ZeroExJsDocSections.proxy]: [ + '"src/contract_wrappers/proxy_wrapper"', + '"src/contract_wrappers/token_transfer_proxy_wrapper"', + ], + [ZeroExJsDocSections.types]: [TYPES_MODULE_PATH], +}; + +export const typeDocUtils = { + isType(entity: TypeDocNode): boolean { + return entity.kindString === KindString.Interface || + entity.kindString === KindString.Function || + entity.kindString === KindString['Type alias'] || + entity.kindString === KindString.Variable || + entity.kindString === KindString.Enumeration; + }, + isMethod(entity: TypeDocNode): boolean { + return entity.kindString === KindString.Method; + }, + isConstructor(entity: TypeDocNode): boolean { + return entity.kindString === KindString.Constructor; + }, + isProperty(entity: TypeDocNode): boolean { + return entity.kindString === KindString.Property; + }, + isPrivateOrProtectedProperty(propertyName: string): boolean { + return _.startsWith(propertyName, '_'); + }, + isPublicType(typeName: string): boolean { + return _.includes(constants.public0xjsTypes, typeName); + }, + getModuleDefinitionBySectionNameIfExists(versionDocObj: TypeDocNode, sectionName: string): + TypeDocNode|undefined { + const possibleModulePathNames = sectionNameToPossibleModulePaths[sectionName]; + const modules = versionDocObj.children; + for (const mod of modules) { + if (_.includes(possibleModulePathNames, mod.name)) { + const moduleWithName = mod; + return moduleWithName; + } + } + return undefined; + }, + getMenuSubsectionsBySection(docAgnosticFormat?: DocAgnosticFormat): MenuSubsectionsBySection { + const menuSubsectionsBySection = {} as MenuSubsectionsBySection; + if (_.isUndefined(docAgnosticFormat)) { + return menuSubsectionsBySection; + } + + const docSections = _.keys(ZeroExJsDocSections); + _.each(docSections, sectionName => { + const docSection = docAgnosticFormat[sectionName]; + if (_.isUndefined(docSection)) { + return; // no-op + } + + if (sectionName === ZeroExJsDocSections.types) { + const typeNames = _.map(docSection.types, t => t.name); + menuSubsectionsBySection[sectionName] = typeNames; + } else { + const methodNames = _.map(docSection.methods, m => m.name); + menuSubsectionsBySection[sectionName] = methodNames; + } + }); + return menuSubsectionsBySection; + }, + getFinal0xjsMenu(selectedVersion: string): {[section: string]: string[]} { + const finalMenu = _.cloneDeep(constants.menu0xjs); + finalMenu.contracts = _.filter(finalMenu.contracts, (contractName: string) => { + const versionIntroducedIfExists = constants.menuSubsectionToVersionWhenIntroduced[contractName]; + if (!_.isUndefined(versionIntroducedIfExists)) { + const existsInSelectedVersion = compareVersions(selectedVersion, + versionIntroducedIfExists) >= 0; + return existsInSelectedVersion; + } else { + return true; + } + }); + return finalMenu; + }, + convertToDocAgnosticFormat(typeDocJson: TypeDocNode): DocAgnosticFormat { + const subMenus = _.values(constants.menu0xjs); + const orderedSectionNames = _.flatten(subMenus); + const docAgnosticFormat: DocAgnosticFormat = {}; + _.each(orderedSectionNames, sectionName => { + const packageDefinitionIfExists = typeDocUtils.getModuleDefinitionBySectionNameIfExists( + typeDocJson, sectionName, + ); + if (_.isUndefined(packageDefinitionIfExists)) { + return; // no-op + } + + // Since the `types.ts` file is the only file that does not export a module/class but + // instead has each type export itself, we do not need to go down two levels of nesting + // for it. + let entities; + let packageComment = ''; + if (sectionName === ZeroExJsDocSections.types) { + entities = packageDefinitionIfExists.children; + } else { + entities = packageDefinitionIfExists.children[0].children; + const commentObj = packageDefinitionIfExists.children[0].comment; + packageComment = !_.isUndefined(commentObj) ? commentObj.shortText : packageComment; + } + + const docSection = typeDocUtils._convertEntitiesToDocSection(entities, sectionName); + docSection.comment = packageComment; + docAgnosticFormat[sectionName] = docSection; + }); + return docAgnosticFormat; + }, + _convertEntitiesToDocSection(entities: TypeDocNode[], sectionName: string) { + const docSection: DocSection = { + comment: '', + constructors: [], + methods: [], + properties: [], + types: [], + }; + + let isConstructor; + _.each(entities, entity => { + switch (entity.kindString) { + case KindString.Constructor: + isConstructor = true; + const constructor = typeDocUtils._convertMethod(entity, isConstructor, sectionName); + docSection.constructors.push(constructor); + break; + + case KindString.Method: + if (entity.flags.isPublic) { + isConstructor = false; + const method = typeDocUtils._convertMethod(entity, isConstructor, sectionName); + docSection.methods.push(method); + } + break; + + case KindString.Property: + if (!typeDocUtils.isPrivateOrProtectedProperty(entity.name)) { + const property = typeDocUtils._convertProperty(entity, sectionName); + docSection.properties.push(property); + } + break; + + case KindString.Interface: + case KindString.Function: + case KindString.Variable: + case KindString.Enumeration: + case KindString['Type alias']: + if (typeDocUtils.isPublicType(entity.name)) { + const customType = typeDocUtils._convertCustomType(entity, sectionName); + docSection.types.push(customType); + } + break; + + default: + throw utils.spawnSwitchErr('kindString', entity.kindString); + } + }); + return docSection; + }, + _convertCustomType(entity: TypeDocNode, sectionName: string): CustomType { + const typeIfExists = !_.isUndefined(entity.type) ? + typeDocUtils._convertType(entity.type, sectionName) : + undefined; + const isConstructor = false; + const methodIfExists = !_.isUndefined(entity.declaration) ? + typeDocUtils._convertMethod(entity.declaration, isConstructor, sectionName) : + undefined; + const indexSignatureIfExists = !_.isUndefined(entity.indexSignature) ? + typeDocUtils._convertIndexSignature(entity.indexSignature[0], sectionName) : + undefined; + const commentIfExists = !_.isUndefined(entity.comment) && !_.isUndefined(entity.comment.shortText) ? + entity.comment.shortText : + undefined; + + const childrenIfExist = !_.isUndefined(entity.children) ? + _.map(entity.children, (child: TypeDocNode) => { + const childTypeIfExists = !_.isUndefined(child.type) ? + typeDocUtils._convertType(child.type, sectionName) : + undefined; + const c: CustomTypeChild = { + name: child.name, + type: childTypeIfExists, + defaultValue: child.defaultValue, + }; + return c; + }) : + undefined; + + const customType = { + name: entity.name, + kindString: entity.kindString, + type: typeIfExists, + method: methodIfExists, + indexSignature: indexSignatureIfExists, + defaultValue: entity.defaultValue, + comment: commentIfExists, + children: childrenIfExist, + }; + return customType; + }, + _convertIndexSignature(entity: TypeDocNode, sectionName: string): IndexSignature { + const key = entity.parameters[0]; + const indexSignature = { + keyName: key.name, + keyType: typeDocUtils._convertType(key.type, sectionName), + valueName: entity.type.name, + }; + return indexSignature; + }, + _convertProperty(entity: TypeDocNode, sectionName: string): Property { + const source = entity.sources[0]; + const commentIfExists = !_.isUndefined(entity.comment) ? entity.comment.shortText : undefined; + const property = { + name: entity.name, + type: typeDocUtils._convertType(entity.type, sectionName), + source: { + fileName: source.fileName, + line: source.line, + }, + comment: commentIfExists, + }; + return property; + }, + _convertMethod(entity: TypeDocNode, isConstructor: boolean, sectionName: string): TypescriptMethod { + const signature = entity.signatures[0]; + const source = entity.sources[0]; + const hasComment = !_.isUndefined(signature.comment); + const isStatic = _.isUndefined(entity.flags.isStatic) ? false : entity.flags.isStatic; + + const topLevelInterface = isStatic ? 'ZeroEx.' : 'zeroEx.'; + // HACK: we use the fact that the sectionName is the same as the property name at the top-level + // of the public interface. In the future, we shouldn't use this hack but rather get it from the JSON. + let callPath = (sectionName !== ZeroExJsDocSections.zeroEx) ? + `${topLevelInterface}${sectionName}.` : + topLevelInterface; + callPath = isConstructor ? '' : callPath; + + const parameters = _.map(signature.parameters, param => { + return typeDocUtils._convertParameter(param, sectionName); + }); + const returnType = typeDocUtils._convertType(signature.type, sectionName); + const typeParameter = _.isUndefined(signature.typeParameter) ? + undefined : + typeDocUtils._convertTypeParameter(signature.typeParameter[0], sectionName); + + const method = { + isConstructor, + isStatic, + name: signature.name, + comment: hasComment ? signature.comment.shortText : undefined, + returnComment: hasComment && signature.comment.returns ? signature.comment.returns : undefined, + source: { + fileName: source.fileName, + line: source.line, + }, + callPath, + parameters, + returnType, + typeParameter, + }; + return method; + }, + _convertTypeParameter(entity: TypeDocNode, sectionName: string): TypeParameter { + const type = typeDocUtils._convertType(entity.type, sectionName); + const parameter = { + name: entity.name, + type, + }; + return parameter; + }, + _convertParameter(entity: TypeDocNode, sectionName: string): Parameter { + let comment = ''; + if (entity.comment && entity.comment.shortText) { + comment = entity.comment.shortText; + } else if (entity.comment && entity.comment.text) { + comment = entity.comment.text; + } + + const isOptional = !_.isUndefined(entity.flags.isOptional) ? + entity.flags.isOptional : + false; + + const type = typeDocUtils._convertType(entity.type, sectionName); + + const parameter = { + name: entity.name, + comment, + isOptional, + type, + }; + return parameter; + }, + _convertType(entity: TypeDocType, sectionName: string): Type { + const typeArguments = _.map(entity.typeArguments, typeArgument => { + return typeDocUtils._convertType(typeArgument, sectionName); + }); + const types = _.map(entity.types, t => { + return typeDocUtils._convertType(t, sectionName); + }); + + const isConstructor = false; + const methodIfExists = !_.isUndefined(entity.declaration) ? + typeDocUtils._convertMethod(entity.declaration, isConstructor, sectionName) : + undefined; + + const elementTypeIfExists = !_.isUndefined(entity.elementType) ? + { + name: entity.elementType.name, + typeDocType: entity.elementType.type, + } : + undefined; + + const type = { + name: entity.name, + value: entity.value, + typeDocType: entity.type, + typeArguments, + elementType: elementTypeIfExists, + types, + method: methodIfExists, + }; + return type; + }, +}; diff --git a/packages/website/ts/utils/utils.ts b/packages/website/ts/utils/utils.ts new file mode 100644 index 000000000..eb4c5be3a --- /dev/null +++ b/packages/website/ts/utils/utils.ts @@ -0,0 +1,215 @@ +import * as _ from 'lodash'; +import { + SideToAssetToken, + SignatureData, + Order, + Side, + TokenByAddress, + OrderParty, + ScreenWidths, + EtherscanLinkSuffixes, + Token, + Networks, +} from 'ts/types'; +import * as moment from 'moment'; +import isMobile = require('is-mobile'); +import * as u2f from 'ts/vendor/u2f_api'; +import deepEqual = require('deep-equal'); +import ethUtil = require('ethereumjs-util'); +import BigNumber from 'bignumber.js'; +import {constants} from 'ts/utils/constants'; + +const LG_MIN_EM = 64; +const MD_MIN_EM = 52; + +export const utils = { + assert(condition: boolean, message: string) { + if (!condition) { + throw new Error(message); + } + }, + spawnSwitchErr(name: string, value: any) { + return new Error(`Unexpected switch value: ${value} encountered for ${name}`); + }, + isNumeric(n: string) { + return !isNaN(parseFloat(n)) && isFinite(Number(n)); + }, + // This default unix timestamp is used for orders where the user does not specify an expiry date. + // It is a fixed constant so that both the redux store's INITIAL_STATE and components can check for + // whether a user has set an expiry date or not. It is set unrealistically high so as not to collide + // with actual values a user would select. + initialOrderExpiryUnixTimestampSec(): BigNumber { + const m = moment('2050-01-01'); + return new BigNumber(m.unix()); + }, + convertToUnixTimestampSeconds(date: moment.Moment, time?: moment.Moment): BigNumber { + const finalMoment = date; + if (!_.isUndefined(time)) { + finalMoment.hours(time.hours()); + finalMoment.minutes(time.minutes()); + } + return new BigNumber(finalMoment.unix()); + }, + convertToMomentFromUnixTimestamp(unixTimestampSec: BigNumber): moment.Moment { + return moment.unix(unixTimestampSec.toNumber()); + }, + convertToReadableDateTimeFromUnixTimestamp(unixTimestampSec: BigNumber): string { + const m = this.convertToMomentFromUnixTimestamp(unixTimestampSec); + const formattedDate: string = m.format('h:MMa MMMM D YYYY'); + return formattedDate; + }, + generateOrder(networkId: number, exchangeContract: string, sideToAssetToken: SideToAssetToken, + orderExpiryTimestamp: BigNumber, orderTakerAddress: string, orderMakerAddress: string, + makerFee: BigNumber, takerFee: BigNumber, feeRecipient: string, + signatureData: SignatureData, tokenByAddress: TokenByAddress, orderSalt: BigNumber): Order { + const makerToken = tokenByAddress[sideToAssetToken[Side.deposit].address]; + const takerToken = tokenByAddress[sideToAssetToken[Side.receive].address]; + const order = { + maker: { + address: orderMakerAddress, + token: { + name: makerToken.name, + symbol: makerToken.symbol, + decimals: makerToken.decimals, + address: makerToken.address, + }, + amount: sideToAssetToken[Side.deposit].amount.toString(), + feeAmount: makerFee.toString(), + }, + taker: { + address: orderTakerAddress, + token: { + name: takerToken.name, + symbol: takerToken.symbol, + decimals: takerToken.decimals, + address: takerToken.address, + }, + amount: sideToAssetToken[Side.receive].amount.toString(), + feeAmount: takerFee.toString(), + }, + expiration: orderExpiryTimestamp.toString(), + feeRecipient, + salt: orderSalt.toString(), + signature: signatureData, + exchangeContract, + networkId, + }; + return order; + }, + consoleLog(message: string) { + /* tslint:disable */ + console.log(message); + /* tslint:enable */ + }, + sleepAsync(ms: number) { + return new Promise(resolve => setTimeout(resolve, ms)); + }, + deepEqual(actual: any, expected: any, opts?: {strict: boolean}) { + return deepEqual(actual, expected, opts); + }, + getColSize(items: number) { + const bassCssGridSize = 12; // Source: http://basscss.com/#basscss-grid + const colSize = 12 / items; + if (!_.isInteger(colSize)) { + throw new Error('Number of cols must be divisible by 12'); + } + return colSize; + }, + getScreenWidth() { + const documentEl = document.documentElement; + const body = document.getElementsByTagName('body')[0]; + const widthInPx = window.innerWidth || documentEl.clientWidth || body.clientWidth; + const bodyStyles: any = window.getComputedStyle(document.querySelector('body')); + const widthInEm = widthInPx / parseFloat(bodyStyles['font-size']); + + // This logic mirrors the CSS media queries in BassCSS for the `lg-`, `md-` and `sm-` CSS + // class prefixes. Do not edit these. + if (widthInEm > LG_MIN_EM) { + return ScreenWidths.LG; + } else if (widthInEm > MD_MIN_EM) { + return ScreenWidths.MD; + } else { + return ScreenWidths.SM; + } + }, + isUserOnMobile(): boolean { + const isUserOnMobile = isMobile(); + return isUserOnMobile; + }, + getEtherScanLinkIfExists(addressOrTxHash: string, networkId: number, suffix: EtherscanLinkSuffixes): string { + const networkName = constants.networkNameById[networkId]; + if (_.isUndefined(networkName)) { + return undefined; + } + const etherScanPrefix = networkName === Networks.mainnet ? '' : `${networkName.toLowerCase()}.`; + return `https://${etherScanPrefix}etherscan.io/${suffix}/${addressOrTxHash}`; + }, + setUrlHash(anchorId: string) { + window.location.hash = anchorId; + }, + async isU2FSupportedAsync(): Promise { + const w = (window as any); + return new Promise((resolve: (isSupported: boolean) => void) => { + if (w.u2f && !w.u2f.getApiVersion) { + // u2f object was found (Firefox with extension) + resolve(true); + } else { + // u2f object was not found. Using Google polyfill + // HACK: u2f.getApiVersion will simply not return a version if the + // U2F call fails for any reason. Because of this, we set a hard 3sec + // timeout to the request on our end. + const getApiVersionTimeoutMs = 3000; + const intervalId = setTimeout(() => { + resolve(false); + }, getApiVersionTimeoutMs); + u2f.getApiVersion((version: number) => { + clearTimeout(intervalId); + resolve(true); + }); + } + }); + }, + // This checks the error message returned from an injected Web3 instance on the page + // after a user was prompted to sign a message or send a transaction and decided to + // reject the request. + didUserDenyWeb3Request(errMsg: string) { + const metamaskDenialErrMsg = 'User denied message'; + const paritySignerDenialErrMsg = 'Request has been rejected'; + const ledgerDenialErrMsg = 'Invalid status 6985'; + const isUserDeniedErrMsg = _.includes(errMsg, metamaskDenialErrMsg) || + _.includes(errMsg, paritySignerDenialErrMsg) || + _.includes(errMsg, ledgerDenialErrMsg); + return isUserDeniedErrMsg; + }, + getCurrentEnvironment() { + switch (location.host) { + case constants.DEVELOPMENT_DOMAIN: + return 'development'; + case constants.STAGING_DOMAIN: + return 'staging'; + case constants.PRODUCTION_DOMAIN: + return 'production'; + default: + return 'production'; + } + }, + getIdFromName(name: string) { + const id = name.replace(/ /g, '-'); + return id; + }, + getAddressBeginAndEnd(address: string): string { + const truncatedAddress = `${address.substring(0, 6)}...${address.substr(-4)}`; // 0x3d5a...b287 + return truncatedAddress; + }, + hasUniqueNameAndSymbol(tokens: Token[], token: Token) { + if (token.isRegistered) { + return true; // Since it's registered, it is the canonical token + } + const registeredTokens = _.filter(tokens, t => t.isRegistered); + const tokenWithSameNameIfExists = _.find(registeredTokens, {name: token.name}); + const isUniqueName = _.isUndefined(tokenWithSameNameIfExists); + const tokenWithSameSymbolIfExists = _.find(registeredTokens, {name: token.symbol}); + const isUniqueSymbol = _.isUndefined(tokenWithSameSymbolIfExists); + return isUniqueName && isUniqueSymbol; + }, +}; diff --git a/packages/website/ts/vendor/u2f_api.js b/packages/website/ts/vendor/u2f_api.js new file mode 100644 index 000000000..3b538d817 --- /dev/null +++ b/packages/website/ts/vendor/u2f_api.js @@ -0,0 +1,760 @@ +//Copyright 2014-2015 Google Inc. All rights reserved. + +//Use of this source code is governed by a BSD-style +//license that can be found in the LICENSE file or at +//https://developers.google.com/open-source/licenses/bsd + +/** + * @fileoverview The U2F api. + */ +'use strict'; + + +/** + * Namespace for the U2F api. + * @type {Object} + */ +var u2f = u2f || {}; + +/** + * Require integration + */ +if (typeof module != "undefined") { + module.exports = u2f; +} + +/** + * FIDO U2F Javascript API Version + * @number + */ +var js_api_version; + +/** + * The U2F extension id + * @const {string} + */ +// The Chrome packaged app extension ID. +// Uncomment this if you want to deploy a server instance that uses +// the package Chrome app and does not require installing the U2F Chrome extension. + u2f.EXTENSION_ID = 'kmendfapggjehodndflmmgagdbamhnfd'; +// The U2F Chrome extension ID. +// Uncomment this if you want to deploy a server instance that uses +// the U2F Chrome extension to authenticate. +// u2f.EXTENSION_ID = 'pfboblefjcgdjicmnffhdgionmgcdmne'; + + +/** + * Message types for messsages to/from the extension + * @const + * @enum {string} + */ +u2f.MessageTypes = { + 'U2F_REGISTER_REQUEST': 'u2f_register_request', + 'U2F_REGISTER_RESPONSE': 'u2f_register_response', + 'U2F_SIGN_REQUEST': 'u2f_sign_request', + 'U2F_SIGN_RESPONSE': 'u2f_sign_response', + 'U2F_GET_API_VERSION_REQUEST': 'u2f_get_api_version_request', + 'U2F_GET_API_VERSION_RESPONSE': 'u2f_get_api_version_response' +}; + + +/** + * Response status codes + * @const + * @enum {number} + */ +u2f.ErrorCodes = { + 'OK': 0, + 'OTHER_ERROR': 1, + 'BAD_REQUEST': 2, + 'CONFIGURATION_UNSUPPORTED': 3, + 'DEVICE_INELIGIBLE': 4, + 'TIMEOUT': 5 +}; + + +/** + * A message for registration requests + * @typedef {{ + * type: u2f.MessageTypes, + * appId: ?string, + * timeoutSeconds: ?number, + * requestId: ?number + * }} + */ +u2f.U2fRequest; + + +/** + * A message for registration responses + * @typedef {{ + * type: u2f.MessageTypes, + * responseData: (u2f.Error | u2f.RegisterResponse | u2f.SignResponse), + * requestId: ?number + * }} + */ +u2f.U2fResponse; + + +/** + * An error object for responses + * @typedef {{ + * errorCode: u2f.ErrorCodes, + * errorMessage: ?string + * }} + */ +u2f.Error; + +/** + * Data object for a single sign request. + * @typedef {enum {BLUETOOTH_RADIO, BLUETOOTH_LOW_ENERGY, USB, NFC}} + */ +u2f.Transport; + + +/** + * Data object for a single sign request. + * @typedef {Array} + */ +u2f.Transports; + +/** + * Data object for a single sign request. + * @typedef {{ + * version: string, + * challenge: string, + * keyHandle: string, + * appId: string + * }} + */ +u2f.SignRequest; + + +/** + * Data object for a sign response. + * @typedef {{ + * keyHandle: string, + * signatureData: string, + * clientData: string + * }} + */ +u2f.SignResponse; + + +/** + * Data object for a registration request. + * @typedef {{ + * version: string, + * challenge: string + * }} + */ +u2f.RegisterRequest; + + +/** + * Data object for a registration response. + * @typedef {{ + * version: string, + * keyHandle: string, + * transports: Transports, + * appId: string + * }} + */ +u2f.RegisterResponse; + + +/** + * Data object for a registered key. + * @typedef {{ + * version: string, + * keyHandle: string, + * transports: ?Transports, + * appId: ?string + * }} + */ +u2f.RegisteredKey; + + +/** + * Data object for a get API register response. + * @typedef {{ + * js_api_version: number + * }} + */ +u2f.GetJsApiVersionResponse; + + +//Low level MessagePort API support + +/** + * Sets up a MessagePort to the U2F extension using the + * available mechanisms. + * @param {function((MessagePort|u2f.WrappedChromeRuntimePort_))} callback + */ +u2f.getMessagePort = function(callback) { + if (typeof chrome != 'undefined' && chrome.runtime) { + // The actual message here does not matter, but we need to get a reply + // for the callback to run. Thus, send an empty signature request + // in order to get a failure response. + var msg = { + type: u2f.MessageTypes.U2F_SIGN_REQUEST, + signRequests: [] + }; + chrome.runtime.sendMessage(u2f.EXTENSION_ID, msg, function() { + if (!chrome.runtime.lastError) { + // We are on a whitelisted origin and can talk directly + // with the extension. + u2f.getChromeRuntimePort_(callback); + } else { + // chrome.runtime was available, but we couldn't message + // the extension directly, use iframe + u2f.getIframePort_(callback); + } + }); + } else if (u2f.isAndroidChrome_()) { + u2f.getAuthenticatorPort_(callback); + } else if (u2f.isIosChrome_()) { + u2f.getIosPort_(callback); + } else { + // chrome.runtime was not available at all, which is normal + // when this origin doesn't have access to any extensions. + u2f.getIframePort_(callback); + } +}; + +/** + * Detect chrome running on android based on the browser's useragent. + * @private + */ +u2f.isAndroidChrome_ = function() { + var userAgent = navigator.userAgent; + return userAgent.indexOf('Chrome') != -1 && + userAgent.indexOf('Android') != -1; +}; + +/** + * Detect chrome running on iOS based on the browser's platform. + * @private + */ +u2f.isIosChrome_ = function() { + return ["iPhone", "iPad", "iPod"].indexOf(navigator.platform) > -1; +}; + +/** + * Connects directly to the extension via chrome.runtime.connect. + * @param {function(u2f.WrappedChromeRuntimePort_)} callback + * @private + */ +u2f.getChromeRuntimePort_ = function(callback) { + var port = chrome.runtime.connect(u2f.EXTENSION_ID, + {'includeTlsChannelId': true}); + setTimeout(function() { + callback(new u2f.WrappedChromeRuntimePort_(port)); + }, 0); +}; + +/** + * Return a 'port' abstraction to the Authenticator app. + * @param {function(u2f.WrappedAuthenticatorPort_)} callback + * @private + */ +u2f.getAuthenticatorPort_ = function(callback) { + setTimeout(function() { + callback(new u2f.WrappedAuthenticatorPort_()); + }, 0); +}; + +/** + * Return a 'port' abstraction to the iOS client app. + * @param {function(u2f.WrappedIosPort_)} callback + * @private + */ +u2f.getIosPort_ = function(callback) { + setTimeout(function() { + callback(new u2f.WrappedIosPort_()); + }, 0); +}; + +/** + * A wrapper for chrome.runtime.Port that is compatible with MessagePort. + * @param {Port} port + * @constructor + * @private + */ +u2f.WrappedChromeRuntimePort_ = function(port) { + this.port_ = port; +}; + +/** + * Format and return a sign request compliant with the JS API version supported by the extension. + * @param {Array} signRequests + * @param {number} timeoutSeconds + * @param {number} reqId + * @return {Object} + */ +u2f.formatSignRequest_ = + function(appId, challenge, registeredKeys, timeoutSeconds, reqId) { + if (js_api_version === undefined || js_api_version < 1.1) { + // Adapt request to the 1.0 JS API + var signRequests = []; + for (var i = 0; i < registeredKeys.length; i++) { + signRequests[i] = { + version: registeredKeys[i].version, + challenge: challenge, + keyHandle: registeredKeys[i].keyHandle, + appId: appId + }; + } + return { + type: u2f.MessageTypes.U2F_SIGN_REQUEST, + signRequests: signRequests, + timeoutSeconds: timeoutSeconds, + requestId: reqId + }; + } + // JS 1.1 API + return { + type: u2f.MessageTypes.U2F_SIGN_REQUEST, + appId: appId, + challenge: challenge, + registeredKeys: registeredKeys, + timeoutSeconds: timeoutSeconds, + requestId: reqId + }; +}; + +/** + * Format and return a register request compliant with the JS API version supported by the extension.. + * @param {Array} signRequests + * @param {Array} signRequests + * @param {number} timeoutSeconds + * @param {number} reqId + * @return {Object} + */ +u2f.formatRegisterRequest_ = + function(appId, registeredKeys, registerRequests, timeoutSeconds, reqId) { + if (js_api_version === undefined || js_api_version < 1.1) { + // Adapt request to the 1.0 JS API + for (var i = 0; i < registerRequests.length; i++) { + registerRequests[i].appId = appId; + } + var signRequests = []; + for (var i = 0; i < registeredKeys.length; i++) { + signRequests[i] = { + version: registeredKeys[i].version, + challenge: registerRequests[0], + keyHandle: registeredKeys[i].keyHandle, + appId: appId + }; + } + return { + type: u2f.MessageTypes.U2F_REGISTER_REQUEST, + signRequests: signRequests, + registerRequests: registerRequests, + timeoutSeconds: timeoutSeconds, + requestId: reqId + }; + } + // JS 1.1 API + return { + type: u2f.MessageTypes.U2F_REGISTER_REQUEST, + appId: appId, + registerRequests: registerRequests, + registeredKeys: registeredKeys, + timeoutSeconds: timeoutSeconds, + requestId: reqId + }; +}; + + +/** + * Posts a message on the underlying channel. + * @param {Object} message + */ +u2f.WrappedChromeRuntimePort_.prototype.postMessage = function(message) { + this.port_.postMessage(message); +}; + + +/** + * Emulates the HTML 5 addEventListener interface. Works only for the + * onmessage event, which is hooked up to the chrome.runtime.Port.onMessage. + * @param {string} eventName + * @param {function({data: Object})} handler + */ +u2f.WrappedChromeRuntimePort_.prototype.addEventListener = + function(eventName, handler) { + var name = eventName.toLowerCase(); + if (name == 'message' || name == 'onmessage') { + this.port_.onMessage.addListener(function(message) { + // Emulate a minimal MessageEvent object + handler({'data': message}); + }); + } else { + console.error('WrappedChromeRuntimePort only supports onMessage'); + } +}; + +/** + * Wrap the Authenticator app with a MessagePort interface. + * @constructor + * @private + */ +u2f.WrappedAuthenticatorPort_ = function() { + this.requestId_ = -1; + this.requestObject_ = null; +} + +/** + * Launch the Authenticator intent. + * @param {Object} message + */ +u2f.WrappedAuthenticatorPort_.prototype.postMessage = function(message) { + var intentUrl = + u2f.WrappedAuthenticatorPort_.INTENT_URL_BASE_ + + ';S.request=' + encodeURIComponent(JSON.stringify(message)) + + ';end'; + document.location = intentUrl; +}; + +/** + * Tells what type of port this is. + * @return {String} port type + */ +u2f.WrappedAuthenticatorPort_.prototype.getPortType = function() { + return "WrappedAuthenticatorPort_"; +}; + + +/** + * Emulates the HTML 5 addEventListener interface. + * @param {string} eventName + * @param {function({data: Object})} handler + */ +u2f.WrappedAuthenticatorPort_.prototype.addEventListener = function(eventName, handler) { + var name = eventName.toLowerCase(); + if (name == 'message') { + var self = this; + /* Register a callback to that executes when + * chrome injects the response. */ + window.addEventListener( + 'message', self.onRequestUpdate_.bind(self, handler), false); + } else { + console.error('WrappedAuthenticatorPort only supports message'); + } +}; + +/** + * Callback invoked when a response is received from the Authenticator. + * @param function({data: Object}) callback + * @param {Object} message message Object + */ +u2f.WrappedAuthenticatorPort_.prototype.onRequestUpdate_ = + function(callback, message) { + var messageObject = JSON.parse(message.data); + var intentUrl = messageObject['intentURL']; + + var errorCode = messageObject['errorCode']; + var responseObject = null; + if (messageObject.hasOwnProperty('data')) { + responseObject = /** @type {Object} */ ( + JSON.parse(messageObject['data'])); + } + + callback({'data': responseObject}); +}; + +/** + * Base URL for intents to Authenticator. + * @const + * @private + */ +/* +u2f.WrappedAuthenticatorPort_.INTENT_URL_BASE_ = + 'intent:#Intent;action=com.google.android.apps.authenticator.AUTHENTICATE'; +*/ +u2f.WrappedAuthenticatorPort_.INTENT_URL_BASE_ = + 'intent:#Intent;action=com.ledger.android.u2f.bridge.AUTHENTICATE'; + + +/** + * Wrap the iOS client app with a MessagePort interface. + * @constructor + * @private + */ +u2f.WrappedIosPort_ = function() {}; + +/** + * Launch the iOS client app request + * @param {Object} message + */ +u2f.WrappedIosPort_.prototype.postMessage = function(message) { + var str = JSON.stringify(message); + var url = "u2f://auth?" + encodeURI(str); + location.replace(url); +}; + +/** + * Tells what type of port this is. + * @return {String} port type + */ +u2f.WrappedIosPort_.prototype.getPortType = function() { + return "WrappedIosPort_"; +}; + +/** + * Emulates the HTML 5 addEventListener interface. + * @param {string} eventName + * @param {function({data: Object})} handler + */ +u2f.WrappedIosPort_.prototype.addEventListener = function(eventName, handler) { + var name = eventName.toLowerCase(); + if (name !== 'message') { + console.error('WrappedIosPort only supports message'); + } +}; + +/** + * Sets up an embedded trampoline iframe, sourced from the extension. + * @param {function(MessagePort)} callback + * @private + */ +u2f.getIframePort_ = function(callback) { + // Create the iframe + var iframeOrigin = 'chrome-extension://' + u2f.EXTENSION_ID; + var iframe = document.createElement('iframe'); + iframe.src = iframeOrigin + '/u2f-comms.html'; + iframe.setAttribute('style', 'display:none'); + document.body.appendChild(iframe); + + var channel = new MessageChannel(); + var ready = function(message) { + if (message.data == 'ready') { + channel.port1.removeEventListener('message', ready); + callback(channel.port1); + } else { + console.error('First event on iframe port was not "ready"'); + } + }; + channel.port1.addEventListener('message', ready); + channel.port1.start(); + + iframe.addEventListener('load', function() { + // Deliver the port to the iframe and initialize + iframe.contentWindow.postMessage('init', iframeOrigin, [channel.port2]); + }); +}; + + +//High-level JS API + +/** + * Default extension response timeout in seconds. + * @const + */ +u2f.EXTENSION_TIMEOUT_SEC = 30; + +/** + * A singleton instance for a MessagePort to the extension. + * @type {MessagePort|u2f.WrappedChromeRuntimePort_} + * @private + */ +u2f.port_ = null; + +/** + * Callbacks waiting for a port + * @type {Array} + * @private + */ +u2f.waitingForPort_ = []; + +/** + * A counter for requestIds. + * @type {number} + * @private + */ +u2f.reqCounter_ = 0; + +/** + * A map from requestIds to client callbacks + * @type {Object.} + * @private + */ +u2f.callbackMap_ = {}; + +/** + * Creates or retrieves the MessagePort singleton to use. + * @param {function((MessagePort|u2f.WrappedChromeRuntimePort_))} callback + * @private + */ +u2f.getPortSingleton_ = function(callback) { + if (u2f.port_) { + callback(u2f.port_); + } else { + if (u2f.waitingForPort_.length == 0) { + u2f.getMessagePort(function(port) { + u2f.port_ = port; + u2f.port_.addEventListener('message', + /** @type {function(Event)} */ (u2f.responseHandler_)); + + // Careful, here be async callbacks. Maybe. + while (u2f.waitingForPort_.length) + u2f.waitingForPort_.shift()(u2f.port_); + }); + } + u2f.waitingForPort_.push(callback); + } +}; + +/** + * Handles response messages from the extension. + * @param {MessageEvent.} message + * @private + */ +u2f.responseHandler_ = function(message) { + var response = message.data; + var reqId = response['requestId']; + if (!reqId || !u2f.callbackMap_[reqId]) { + console.error('Unknown or missing requestId in response.'); + return; + } + var cb = u2f.callbackMap_[reqId]; + delete u2f.callbackMap_[reqId]; + cb(response['responseData']); +}; + +/** + * Dispatches an array of sign requests to available U2F tokens. + * If the JS API version supported by the extension is unknown, it first sends a + * message to the extension to find out the supported API version and then it sends + * the sign request. + * @param {string=} appId + * @param {string=} challenge + * @param {Array} registeredKeys + * @param {function((u2f.Error|u2f.SignResponse))} callback + * @param {number=} opt_timeoutSeconds + */ +u2f.sign = function(appId, challenge, registeredKeys, callback, opt_timeoutSeconds) { + if (js_api_version === undefined) { + // Send a message to get the extension to JS API version, then send the actual sign request. + u2f.getApiVersion( + function (response) { + js_api_version = response['js_api_version'] === undefined ? 0 : response['js_api_version']; + //console.log("Extension JS API Version: ", js_api_version); + u2f.sendSignRequest(appId, challenge, registeredKeys, callback, opt_timeoutSeconds); + }); + } else { + // We know the JS API version. Send the actual sign request in the supported API version. + u2f.sendSignRequest(appId, challenge, registeredKeys, callback, opt_timeoutSeconds); + } +}; + +/** + * Dispatches an array of sign requests to available U2F tokens. + * @param {string=} appId + * @param {string=} challenge + * @param {Array} registeredKeys + * @param {function((u2f.Error|u2f.SignResponse))} callback + * @param {number=} opt_timeoutSeconds + */ +u2f.sendSignRequest = function(appId, challenge, registeredKeys, callback, opt_timeoutSeconds) { + u2f.getPortSingleton_(function(port) { + var reqId = ++u2f.reqCounter_; + u2f.callbackMap_[reqId] = callback; + var timeoutSeconds = (typeof opt_timeoutSeconds !== 'undefined' ? + opt_timeoutSeconds : u2f.EXTENSION_TIMEOUT_SEC); + var req = u2f.formatSignRequest_(appId, challenge, registeredKeys, timeoutSeconds, reqId); + port.postMessage(req); + }); +}; + +/** + * Dispatches register requests to available U2F tokens. An array of sign + * requests identifies already registered tokens. + * If the JS API version supported by the extension is unknown, it first sends a + * message to the extension to find out the supported API version and then it sends + * the register request. + * @param {string=} appId + * @param {Array} registerRequests + * @param {Array} registeredKeys + * @param {function((u2f.Error|u2f.RegisterResponse))} callback + * @param {number=} opt_timeoutSeconds + */ +u2f.register = function(appId, registerRequests, registeredKeys, callback, opt_timeoutSeconds) { + if (js_api_version === undefined) { + // Send a message to get the extension to JS API version, then send the actual register request. + u2f.getApiVersion( + function (response) { + js_api_version = response['js_api_version'] === undefined ? 0: response['js_api_version']; + //console.log("Extension JS API Version: ", js_api_version); + u2f.sendRegisterRequest(appId, registerRequests, registeredKeys, + callback, opt_timeoutSeconds); + }); + } else { + // We know the JS API version. Send the actual register request in the supported API version. + u2f.sendRegisterRequest(appId, registerRequests, registeredKeys, + callback, opt_timeoutSeconds); + } +}; + +/** + * Dispatches register requests to available U2F tokens. An array of sign + * requests identifies already registered tokens. + * @param {string=} appId + * @param {Array} registerRequests + * @param {Array} registeredKeys + * @param {function((u2f.Error|u2f.RegisterResponse))} callback + * @param {number=} opt_timeoutSeconds + */ +u2f.sendRegisterRequest = function(appId, registerRequests, registeredKeys, callback, opt_timeoutSeconds) { + u2f.getPortSingleton_(function(port) { + var reqId = ++u2f.reqCounter_; + u2f.callbackMap_[reqId] = callback; + var timeoutSeconds = (typeof opt_timeoutSeconds !== 'undefined' ? + opt_timeoutSeconds : u2f.EXTENSION_TIMEOUT_SEC); + var req = u2f.formatRegisterRequest_( + appId, registeredKeys, registerRequests, timeoutSeconds, reqId); + port.postMessage(req); + }); +}; + + +/** + * Dispatches a message to the extension to find out the supported + * JS API version. + * If the user is on a mobile phone and is thus using Google Authenticator instead + * of the Chrome extension, don't send the request and simply return 0. + * @param {function((u2f.Error|u2f.GetJsApiVersionResponse))} callback + * @param {number=} opt_timeoutSeconds + */ +u2f.getApiVersion = function(callback, opt_timeoutSeconds) { + u2f.getPortSingleton_(function(port) { + // If we are using Android Google Authenticator or iOS client app, + // do not fire an intent to ask which JS API version to use. + if (port.getPortType) { + var apiVersion; + switch (port.getPortType()) { + case 'WrappedIosPort_': + case 'WrappedAuthenticatorPort_': + apiVersion = 1.1; + break; + + default: + apiVersion = 0; + break; + } + callback({ 'js_api_version': apiVersion }); + return; + } + var reqId = ++u2f.reqCounter_; + u2f.callbackMap_[reqId] = callback; + var req = { + type: u2f.MessageTypes.U2F_GET_API_VERSION_REQUEST, + timeoutSeconds: (typeof opt_timeoutSeconds !== 'undefined' ? + opt_timeoutSeconds : u2f.EXTENSION_TIMEOUT_SEC), + requestId: reqId + }; + port.postMessage(req); + }); +}; diff --git a/packages/website/ts/web3_wrapper.ts b/packages/website/ts/web3_wrapper.ts new file mode 100644 index 000000000..24279f5d2 --- /dev/null +++ b/packages/website/ts/web3_wrapper.ts @@ -0,0 +1,146 @@ +import * as _ from 'lodash'; +import Web3 = require('web3'); +import BigNumber from 'bignumber.js'; +import promisify = require('es6-promisify'); +import {Dispatcher} from 'ts/redux/dispatcher'; + +export class Web3Wrapper { + private dispatcher: Dispatcher; + private web3: Web3; + private prevNetworkId: number; + private shouldPollUserAddress: boolean; + private watchNetworkAndBalanceIntervalId: number; + private prevUserEtherBalanceInEth: BigNumber; + private prevUserAddress: string; + constructor(dispatcher: Dispatcher, provider: Web3.Provider, networkIdIfExists: number, + shouldPollUserAddress: boolean) { + this.dispatcher = dispatcher; + this.prevNetworkId = networkIdIfExists; + this.shouldPollUserAddress = shouldPollUserAddress; + + this.web3 = new Web3(); + this.web3.setProvider(provider); + + this.startEmittingNetworkConnectionAndUserBalanceStateAsync(); + } + public isAddress(address: string) { + return this.web3.isAddress(address); + } + public async getAccountsAsync(): Promise { + const addresses = await promisify(this.web3.eth.getAccounts)(); + return addresses; + } + public async getFirstAccountIfExistsAsync() { + const addresses = await this.getAccountsAsync(); + if (_.isEmpty(addresses)) { + return ''; + } + return (addresses as string[])[0]; + } + public async getNodeVersionAsync() { + const nodeVersion = await promisify(this.web3.version.getNode)(); + return nodeVersion; + } + public getProviderObj() { + return this.web3.currentProvider; + } + public async getNetworkIdIfExists() { + try { + const networkId = await this.getNetworkAsync(); + return Number(networkId); + } catch (err) { + return undefined; + } + } + public async getBalanceInEthAsync(owner: string): Promise { + const balanceInWei: BigNumber = await promisify(this.web3.eth.getBalance)(owner); + const balanceEthOldBigNumber = this.web3.fromWei(balanceInWei, 'ether'); + const balanceEth = new BigNumber(balanceEthOldBigNumber); + return balanceEth; + } + public async doesContractExistAtAddressAsync(address: string): Promise { + const code = await promisify(this.web3.eth.getCode)(address); + // Regex matches 0x0, 0x00, 0x in order to accomodate poorly implemented clients + const zeroHexAddressRegex = /^0[xX][0]*$/; + const didFindCode = _.isNull(code.match(zeroHexAddressRegex)); + return didFindCode; + } + public async signTransactionAsync(address: string, message: string): Promise { + const signData = await promisify(this.web3.eth.sign)(address, message); + return signData; + } + public async getBlockTimestampAsync(blockHash: string): Promise { + const {timestamp} = await promisify(this.web3.eth.getBlock)(blockHash); + return timestamp; + } + public destroy() { + this.stopEmittingNetworkConnectionAndUserBalanceStateAsync(); + // HACK: stop() is only available on providerEngine instances + const provider = this.web3.currentProvider; + if (!_.isUndefined((provider as any).stop)) { + (provider as any).stop(); + } + } + // This should only be called from the LedgerConfigDialog + public updatePrevUserAddress(userAddress: string) { + this.prevUserAddress = userAddress; + } + private async getNetworkAsync() { + const networkId = await promisify(this.web3.version.getNetwork)(); + return networkId; + } + private async startEmittingNetworkConnectionAndUserBalanceStateAsync() { + if (!_.isUndefined(this.watchNetworkAndBalanceIntervalId)) { + return; // we are already emitting the state + } + + let prevNodeVersion: string; + this.prevUserEtherBalanceInEth = new BigNumber(0); + this.dispatcher.updateNetworkId(this.prevNetworkId); + this.watchNetworkAndBalanceIntervalId = window.setInterval(async () => { + // Check for network state changes + const currentNetworkId = await this.getNetworkIdIfExists(); + if (currentNetworkId !== this.prevNetworkId) { + this.prevNetworkId = currentNetworkId; + this.dispatcher.updateNetworkId(currentNetworkId); + } + + // Check for node version changes + const currentNodeVersion = await this.getNodeVersionAsync(); + if (currentNodeVersion !== prevNodeVersion) { + prevNodeVersion = currentNodeVersion; + this.dispatcher.updateNodeVersion(currentNodeVersion); + } + + if (this.shouldPollUserAddress) { + const userAddressIfExists = await this.getFirstAccountIfExistsAsync(); + // Update makerAddress on network change + if (this.prevUserAddress !== userAddressIfExists) { + this.prevUserAddress = userAddressIfExists; + this.dispatcher.updateUserAddress(userAddressIfExists); + } + + // Check for user ether balance changes + if (userAddressIfExists !== '') { + await this.updateUserEtherBalanceAsync(userAddressIfExists); + } + } else { + // This logic is primarily for the Ledger, since we don't regularly poll for the address + // we simply update the balance for the last fetched address. + if (!_.isEmpty(this.prevUserAddress)) { + await this.updateUserEtherBalanceAsync(this.prevUserAddress); + } + } + }, 5000); + } + private async updateUserEtherBalanceAsync(userAddress: string) { + const balance = await this.getBalanceInEthAsync(userAddress); + if (!balance.eq(this.prevUserEtherBalanceInEth)) { + this.prevUserEtherBalanceInEth = balance; + this.dispatcher.updateUserEtherBalance(balance); + } + } + private stopEmittingNetworkConnectionAndUserBalanceStateAsync() { + clearInterval(this.watchNetworkAndBalanceIntervalId); + } +} diff --git a/packages/website/tsconfig.json b/packages/website/tsconfig.json new file mode 100644 index 000000000..15e764c02 --- /dev/null +++ b/packages/website/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "allowSyntheticDefaultImports": true, + "outDir": "./transpiled/", + "sourceMap": true, + "noImplicitAny": true, + "module": "commonjs", + "target": "es5", + "jsx": "react", + "baseUrl": "./", + "allowJs": true, + "paths": { + "*": [ "node_modules/@types/*", "*"] + } + }, + "include": [ + "./ts/**/*", + "../../node_modules/web3-typescript-typings/index.d.ts" + ] +} diff --git a/packages/website/tslint.json b/packages/website/tslint.json new file mode 100644 index 000000000..6ac3ed760 --- /dev/null +++ b/packages/website/tslint.json @@ -0,0 +1,9 @@ +{ + "extends": [ + "@0xproject/tslint-config" + ], + "rules": { + "no-implicit-dependencies": false, + "no-object-literal-type-assertion": false + } +} diff --git a/packages/website/webpack.config.js b/packages/website/webpack.config.js new file mode 100644 index 000000000..c436888bd --- /dev/null +++ b/packages/website/webpack.config.js @@ -0,0 +1,86 @@ +const path = require('path'); +const webpack = require('webpack'); + +module.exports = { + entry: ['./ts/index.tsx'], + output: { + path: path.join(__dirname, '/public'), + filename: 'bundle.js', + chunkFilename: 'bundle-[name].js', + publicPath: '/', + }, + devtool: 'source-map', + resolve: { + modules: [ + path.join(__dirname, '/ts'), + 'node_modules', + ], + extensions: ['.ts', '.tsx', '.js', '.jsx', '.json', '.md'], + alias: { + ts: path.join(__dirname, '/ts'), + less: path.join(__dirname, '/less'), + md: path.join(__dirname, '/md'), + }, + }, + module: { + rules: [ + { + test: /\.js$/, + loader: 'source-map-loader', + }, + { + test: /\.tsx?$/, + loader: 'awesome-typescript-loader', + }, + { + test: /\.md$/, + use: 'raw-loader', + }, + { + test: /\.less$/, + loader: 'style-loader!css-loader!less-loader', + exclude: /node_modules/, + }, + { + test: /\.css$/, + loaders: ['style-loader', 'css-loader'], + }, + { + test: /\.json$/, + loader: 'json-loader', + }, + ], + }, + devServer: { + port: 3572, + historyApiFallback: { + // Fixes issue where having dots in URL path that aren't part of fileNames causes webpack-dev-server + // to fail. Doc versions have dots in them, therefore we special case these urls to also load index.html. + // Source: https://github.com/cvut/fittable/issues/171 + rewrites: [ + { + from: /^\/docs\/.*$/, + to: function() { + return 'index.html'; + } + } + ] + }, + disableHostCheck: true, + }, + plugins: process.env.NODE_ENV === 'production' ? [ + // Since we do not use moment's locale feature, we exclude them from the bundle. + // This reduces the bundle size by 0.4MB. + new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/), + new webpack.DefinePlugin({ + 'process.env': { + 'NODE_ENV': JSON.stringify(process.env.NODE_ENV) + } + }), + new webpack.optimize.UglifyJsPlugin({ + mangle: { + except: ['BigNumber'] + } + }) + ] : [], +}; diff --git a/yarn.lock b/yarn.lock index 6bc4ccf1b..71fa75f7a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,6 +2,22 @@ # yarn lockfile v1 +"@types/accounting@^0.4.1": + version "0.4.1" + resolved "https://registry.yarnpkg.com/@types/accounting/-/accounting-0.4.1.tgz#865d9f5694fd7c438fba34eb4bc82eec6f34cdd5" + +"@types/dateformat@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@types/dateformat/-/dateformat-1.0.1.tgz#2e5b235c8c55652c4fec284506d2a36fe65fe87e" + +"@types/deep-equal@^1.0.0": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@types/deep-equal/-/deep-equal-1.0.1.tgz#71cfabb247c22bcc16d536111f50c0ed12476b03" + +"@types/es6-promise@0.0.32": + version "0.0.32" + resolved "https://registry.yarnpkg.com/@types/es6-promise/-/es6-promise-0.0.32.tgz#3bcf44fb1e429f3df76188c8c6d874463ba371fd" + "@types/fetch-mock@^5.12.1": version "5.12.2" resolved "https://registry.yarnpkg.com/@types/fetch-mock/-/fetch-mock-5.12.2.tgz#8c96517ff74303031c65c5da2d99858e34c844d2" @@ -27,6 +43,10 @@ version "9.12.1" resolved "https://registry.yarnpkg.com/@types/highlight.js/-/highlight.js-9.12.1.tgz#990f5dc63774852beab55e6173e5851d35da52f2" +"@types/history@*": + version "4.6.2" + resolved "https://registry.yarnpkg.com/@types/history/-/history-4.6.2.tgz#12cfaba693ba20f114ed5765467ff25fdf67ddb0" + "@types/jsonschema@^1.1.1": version "1.1.1" resolved "https://registry.yarnpkg.com/@types/jsonschema/-/jsonschema-1.1.1.tgz#08703dfe074010e8e829123111594af731f57b1a" @@ -45,7 +65,7 @@ dependencies: "@types/lodash" "*" -"@types/lodash@*", "@types/lodash@^4.14.37", "@types/lodash@^4.14.64", "@types/lodash@^4.14.77", "@types/lodash@^4.14.78": +"@types/lodash@*", "@types/lodash@^4.14.37", "@types/lodash@^4.14.55", "@types/lodash@^4.14.64", "@types/lodash@^4.14.77", "@types/lodash@^4.14.78": version "4.14.85" resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.85.tgz#a16fbf942422f6eca5622b6910492c496c35069b" @@ -53,6 +73,13 @@ version "0.0.28" resolved "https://registry.yarnpkg.com/@types/marked/-/marked-0.0.28.tgz#44ba754e9fa51432583e8eb30a7c4dd249b52faa" +"@types/material-ui@0.18.0": + version "0.18.0" + resolved "https://registry.yarnpkg.com/@types/material-ui/-/material-ui-0.18.0.tgz#f3abc5431df8faa4592233c6c5377f2843eb807f" + dependencies: + "@types/react" "*" + "@types/react-addons-linked-state-mixin" "*" + "@types/minimatch@*": version "3.0.1" resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.1.tgz#b683eb60be358304ef146f5775db4c0e3696a550" @@ -65,14 +92,88 @@ version "2.2.44" resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-2.2.44.tgz#1d4a798e53f35212fd5ad4d04050620171cd5b5e" +"@types/moment@^2.13.0": + version "2.13.0" + resolved "https://registry.yarnpkg.com/@types/moment/-/moment-2.13.0.tgz#604ebd189bc3bc34a1548689404e61a2a4aac896" + dependencies: + moment "*" + "@types/node@*", "@types/node@^8.0.1": version "8.0.51" resolved "https://registry.yarnpkg.com/@types/node/-/node-8.0.51.tgz#b31d716fb8d58eeb95c068a039b9b6292817d5fb" -"@types/query-string@^5.0.1": +"@types/node@^7.0.8": + version "7.0.48" + resolved "https://registry.yarnpkg.com/@types/node/-/node-7.0.48.tgz#24bfdc0aa82e8f6dbd017159c58094a2e06d0abb" + +"@types/query-string@^5.0.0", "@types/query-string@^5.0.1": version "5.0.1" resolved "https://registry.yarnpkg.com/@types/query-string/-/query-string-5.0.1.tgz#6cb41c724cb1644d56c2d1dae7c7b204e706b39e" +"@types/react-addons-linked-state-mixin@*": + version "0.14.18" + resolved "https://registry.yarnpkg.com/@types/react-addons-linked-state-mixin/-/react-addons-linked-state-mixin-0.14.18.tgz#6977ae59e19aa0ce3a5d7a9e057961aaf1afe959" + dependencies: + "@types/react" "*" + +"@types/react-copy-to-clipboard@^4.2.0": + version "4.2.3" + resolved "https://registry.yarnpkg.com/@types/react-copy-to-clipboard/-/react-copy-to-clipboard-4.2.3.tgz#9da09521fc0f7768f9ad9781d9768f1cde81e5bd" + dependencies: + "@types/react" "*" + +"@types/react-dom@^0.14.23": + version "0.14.23" + resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-0.14.23.tgz#cecfcfad754b4c2765fe5d29b81b301889ad6c2e" + dependencies: + "@types/react" "*" + +"@types/react-redux@^4.4.37": + version "4.4.47" + resolved "https://registry.yarnpkg.com/@types/react-redux/-/react-redux-4.4.47.tgz#12af1677116e08d413fe2620d0a85560c8a0536e" + dependencies: + "@types/react" "*" + redux "^3.6.0" + +"@types/react-router-dom@^4.0.4": + version "4.2.1" + resolved "https://registry.yarnpkg.com/@types/react-router-dom/-/react-router-dom-4.2.1.tgz#c5583550d7246dea60612cc55a523cf980d56df1" + dependencies: + "@types/history" "*" + "@types/react" "*" + "@types/react-router" "*" + +"@types/react-router@*": + version "4.0.17" + resolved "https://registry.yarnpkg.com/@types/react-router/-/react-router-4.0.17.tgz#1032c800ed0da188d8a3bb38fe6afa8c917865d5" + dependencies: + "@types/history" "*" + "@types/react" "*" + +"@types/react-scroll@0.0.31": + version "0.0.31" + resolved "https://registry.yarnpkg.com/@types/react-scroll/-/react-scroll-0.0.31.tgz#1bb26bfd9f595da6403c2f13c2f9a3ed4d2929fa" + dependencies: + "@types/react" "*" + +"@types/react-tap-event-plugin@0.0.30": + version "0.0.30" + resolved "https://registry.yarnpkg.com/@types/react-tap-event-plugin/-/react-tap-event-plugin-0.0.30.tgz#123f35080412f489b6770c5a65c159ff96654cb5" + +"@types/react@*": + version "16.0.25" + resolved "https://registry.yarnpkg.com/@types/react/-/react-16.0.25.tgz#bf696b83fe480c5e0eff4335ee39ebc95884a1ed" + +"@types/react@^15.0.15": + version "15.6.7" + resolved "https://registry.yarnpkg.com/@types/react/-/react-15.6.7.tgz#e910b6aace59d8d0b48dd679c2c03cffedafeec6" + +"@types/redux@^3.6.0": + version "3.6.0" + resolved "https://registry.yarnpkg.com/@types/redux/-/redux-3.6.0.tgz#f1ebe1e5411518072e4fdfca5c76e16e74c1399a" + dependencies: + redux "*" + "@types/shelljs@^0.7.0": version "0.7.6" resolved "https://registry.yarnpkg.com/@types/shelljs/-/shelljs-0.7.6.tgz#4ac7ca01c191ba65b8e2bf50543c5560084d8d27" @@ -123,6 +224,17 @@ abstract-leveldown@~2.7.1: dependencies: xtend "~4.0.0" +accepts@~1.3.4: + version "1.3.4" + resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.4.tgz#86246758c7dd6d21a6474ff084a4740ec05eb21f" + dependencies: + mime-types "~2.1.16" + negotiator "0.6.1" + +accounting@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/accounting/-/accounting-0.4.1.tgz#87dd4103eff7f4460f1e186f5c677ed6cf566883" + acorn-dynamic-import@^2.0.0: version "2.0.2" resolved "https://registry.yarnpkg.com/acorn-dynamic-import/-/acorn-dynamic-import-2.0.2.tgz#c752bd210bef679501b6c6cb7fc84f8f47158cc4" @@ -173,6 +285,10 @@ align-text@^0.1.1, align-text@^0.1.3: longest "^1.0.1" repeat-string "^1.5.2" +alphanum-sort@^1.0.1, alphanum-sort@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/alphanum-sort/-/alphanum-sort-1.0.2.tgz#97a1119649b211ad33691d9f9f486a8ec9fbe0a3" + amdefine@>=0.0.4: version "1.0.1" resolved "https://registry.yarnpkg.com/amdefine/-/amdefine-1.0.1.tgz#4a5282ac164729e93619bcfd3ad151f817ce91f5" @@ -181,6 +297,10 @@ ansi-escapes@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-3.0.0.tgz#ec3e8b4e9f8064fc02c3ac9b65f1c275bda8ef92" +ansi-html@0.0.7: + version "0.0.7" + resolved "https://registry.yarnpkg.com/ansi-html/-/ansi-html-0.0.7.tgz#813584021962a9e9e6fd039f940d12f56ca7859e" + ansi-regex@^1.0.0, ansi-regex@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-1.1.1.tgz#41c847194646375e6a1a5d10c3ca054ef9fc980d" @@ -263,10 +383,25 @@ array-find-index@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/array-find-index/-/array-find-index-1.0.2.tgz#df010aa1287e164bbda6f9723b0a96a1ec4187a1" +array-flatten@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" + +array-flatten@^2.1.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-2.1.1.tgz#426bb9da84090c1838d812c8150af20a8331e296" + array-ify@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/array-ify/-/array-ify-1.0.0.tgz#9e528762b4a9066ad163a6962a364418e9626ece" +array-includes@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.0.3.tgz#184b48f62d92d7452bb31b323165c7f8bd02266d" + dependencies: + define-properties "^1.1.2" + es-abstract "^1.7.0" + array-map@~0.0.0: version "0.0.0" resolved "https://registry.yarnpkg.com/array-map/-/array-map-0.0.0.tgz#88a2bab73d1cf7bcd5c1b118a003f66f665fa662" @@ -297,6 +432,10 @@ arrify@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d" +asap@~2.0.3: + version "2.0.6" + resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" + asn1.js@^4.0.0: version "4.9.2" resolved "https://registry.yarnpkg.com/asn1.js/-/asn1.js-4.9.2.tgz#8117ef4f7ed87cd8f89044b5bff97ac243a16c9a" @@ -349,11 +488,17 @@ async-eventemitter@ahultgren/async-eventemitter#fa06e39e56786ba541c180061dbf2c0a dependencies: async "^2.4.0" +async@2.1.4: + version "2.1.4" + resolved "https://registry.yarnpkg.com/async/-/async-2.1.4.tgz#2d2160c7788032e4dd6cbe2502f1f9a2c8f6cde4" + dependencies: + lodash "^4.14.0" + async@^0.9.0: version "0.9.2" resolved "https://registry.yarnpkg.com/async/-/async-0.9.2.tgz#aea74d5e61c1f899613bf64bda66d4c78f2fd17d" -async@^1.4.0, async@^1.4.2, async@^1.5.0: +async@^1.4.0, async@^1.4.2, async@^1.5.0, async@^1.5.2: version "1.5.2" resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a" @@ -371,6 +516,17 @@ atob@^2.0.0: version "2.0.3" resolved "https://registry.yarnpkg.com/atob/-/atob-2.0.3.tgz#19c7a760473774468f20b2d2d03372ad7d4cbf5d" +autoprefixer@^6.3.1: + version "6.7.7" + resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-6.7.7.tgz#1dbd1c835658e35ce3f9984099db00585c782014" + dependencies: + browserslist "^1.7.6" + caniuse-db "^1.0.30000634" + normalize-range "^0.1.2" + num2fraction "^1.2.2" + postcss "^5.2.16" + postcss-value-parser "^3.2.3" + awesome-typescript-loader@^3.1.3: version "3.3.0" resolved "https://registry.yarnpkg.com/awesome-typescript-loader/-/awesome-typescript-loader-3.3.0.tgz#24bed5650ca0d6e95457904d9969127ba4ff3575" @@ -396,7 +552,7 @@ aws4@^1.2.1, aws4@^1.6.0: version "1.6.0" resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.6.0.tgz#83ef5ca860b2b32e4a0deedee8c771b9db57471e" -babel-code-frame@^6.22.0, babel-code-frame@^6.26.0: +babel-code-frame@^6.22.0, babel-code-frame@^6.26.0, babel-code-frame@^6.8.0: version "6.26.0" resolved "https://registry.yarnpkg.com/babel-code-frame/-/babel-code-frame-6.26.0.tgz#63fd43f7dc1e3bb7ce35947db8fe369a3f58c74b" dependencies: @@ -428,7 +584,33 @@ babel-core@^6.0.14, babel-core@^6.26.0: slash "^1.0.0" source-map "^0.5.6" -babel-generator@^6.18.0, babel-generator@^6.26.0: +babel-core@~6.13.2: + version "6.13.2" + resolved "https://registry.yarnpkg.com/babel-core/-/babel-core-6.13.2.tgz#f761e1199361d5a6ed16f93ce801ad50acadb338" + dependencies: + babel-code-frame "^6.8.0" + babel-generator "^6.11.4" + babel-helpers "^6.8.0" + babel-messages "^6.8.0" + babel-register "^6.9.0" + babel-runtime "^6.9.1" + babel-template "^6.9.0" + babel-traverse "^6.13.0" + babel-types "^6.13.0" + babylon "^6.7.0" + convert-source-map "^1.1.0" + debug "^2.1.1" + json5 "^0.4.0" + lodash "^4.2.0" + minimatch "^3.0.2" + path-exists "^1.0.0" + path-is-absolute "^1.0.0" + private "^0.1.6" + shebang-regex "^1.0.0" + slash "^1.0.0" + source-map "^0.5.0" + +babel-generator@^6.11.4, babel-generator@^6.18.0, babel-generator@^6.26.0: version "6.26.0" resolved "https://registry.yarnpkg.com/babel-generator/-/babel-generator-6.26.0.tgz#ac1ae20070b79f6e3ca1d3269613053774f20dc5" dependencies: @@ -441,6 +623,14 @@ babel-generator@^6.18.0, babel-generator@^6.26.0: source-map "^0.5.6" trim-right "^1.0.1" +babel-helper-builder-react-jsx@^6.24.1: + version "6.26.0" + resolved "https://registry.yarnpkg.com/babel-helper-builder-react-jsx/-/babel-helper-builder-react-jsx-6.26.0.tgz#39ff8313b75c8b65dceff1f31d383e0ff2a408a0" + dependencies: + babel-runtime "^6.26.0" + babel-types "^6.26.0" + esutils "^2.0.2" + babel-helper-call-delegate@^6.24.1: version "6.24.1" resolved "https://registry.yarnpkg.com/babel-helper-call-delegate/-/babel-helper-call-delegate-6.24.1.tgz#ece6aacddc76e41c3461f88bfc575bd0daa2df8d" @@ -509,38 +699,50 @@ babel-helper-replace-supers@^6.24.1: babel-traverse "^6.24.1" babel-types "^6.24.1" -babel-helpers@^6.24.1: +babel-helpers@^6.24.1, babel-helpers@^6.8.0: version "6.24.1" resolved "https://registry.yarnpkg.com/babel-helpers/-/babel-helpers-6.24.1.tgz#3471de9caec388e5c850e597e58a26ddf37602b2" dependencies: babel-runtime "^6.22.0" babel-template "^6.24.1" -babel-messages@^6.23.0: +babel-messages@^6.23.0, babel-messages@^6.8.0: version "6.23.0" resolved "https://registry.yarnpkg.com/babel-messages/-/babel-messages-6.23.0.tgz#f3cdf4703858035b2a2951c6ec5edf6c62f2630e" dependencies: babel-runtime "^6.22.0" -babel-plugin-check-es2015-constants@^6.22.0: +babel-plugin-check-es2015-constants@^6.22.0, babel-plugin-check-es2015-constants@^6.3.13: version "6.22.0" resolved "https://registry.yarnpkg.com/babel-plugin-check-es2015-constants/-/babel-plugin-check-es2015-constants-6.22.0.tgz#35157b101426fd2ffd3da3f75c7d1e91835bbf8a" dependencies: babel-runtime "^6.22.0" -babel-plugin-transform-es2015-arrow-functions@^6.22.0: +babel-plugin-syntax-flow@^6.18.0, babel-plugin-syntax-flow@^6.3.13: + version "6.18.0" + resolved "https://registry.yarnpkg.com/babel-plugin-syntax-flow/-/babel-plugin-syntax-flow-6.18.0.tgz#4c3ab20a2af26aa20cd25995c398c4eb70310c8d" + +babel-plugin-syntax-jsx@^6.3.13, babel-plugin-syntax-jsx@^6.8.0: + version "6.18.0" + resolved "https://registry.yarnpkg.com/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz#0af32a9a6e13ca7a3fd5069e62d7b0f58d0d8946" + +babel-plugin-syntax-jsx@~6.13.0: + version "6.13.0" + resolved "https://registry.yarnpkg.com/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.13.0.tgz#e741ff3992c578310be45c571bcd90a2f9c5586e" + +babel-plugin-transform-es2015-arrow-functions@^6.22.0, babel-plugin-transform-es2015-arrow-functions@^6.3.13: version "6.22.0" resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-arrow-functions/-/babel-plugin-transform-es2015-arrow-functions-6.22.0.tgz#452692cb711d5f79dc7f85e440ce41b9f244d221" dependencies: babel-runtime "^6.22.0" -babel-plugin-transform-es2015-block-scoped-functions@^6.22.0: +babel-plugin-transform-es2015-block-scoped-functions@^6.22.0, babel-plugin-transform-es2015-block-scoped-functions@^6.3.13: version "6.22.0" resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-block-scoped-functions/-/babel-plugin-transform-es2015-block-scoped-functions-6.22.0.tgz#bbc51b49f964d70cb8d8e0b94e820246ce3a6141" dependencies: babel-runtime "^6.22.0" -babel-plugin-transform-es2015-block-scoping@^6.24.1: +babel-plugin-transform-es2015-block-scoping@^6.24.1, babel-plugin-transform-es2015-block-scoping@^6.9.0: version "6.26.0" resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-block-scoping/-/babel-plugin-transform-es2015-block-scoping-6.26.0.tgz#d70f5299c1308d05c12f463813b0a09e73b1895f" dependencies: @@ -550,7 +752,7 @@ babel-plugin-transform-es2015-block-scoping@^6.24.1: babel-types "^6.26.0" lodash "^4.17.4" -babel-plugin-transform-es2015-classes@^6.24.1: +babel-plugin-transform-es2015-classes@^6.24.1, babel-plugin-transform-es2015-classes@^6.9.0: version "6.24.1" resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-classes/-/babel-plugin-transform-es2015-classes-6.24.1.tgz#5a4c58a50c9c9461e564b4b2a3bfabc97a2584db" dependencies: @@ -564,33 +766,33 @@ babel-plugin-transform-es2015-classes@^6.24.1: babel-traverse "^6.24.1" babel-types "^6.24.1" -babel-plugin-transform-es2015-computed-properties@^6.24.1: +babel-plugin-transform-es2015-computed-properties@^6.24.1, babel-plugin-transform-es2015-computed-properties@^6.3.13: version "6.24.1" resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-computed-properties/-/babel-plugin-transform-es2015-computed-properties-6.24.1.tgz#6fe2a8d16895d5634f4cd999b6d3480a308159b3" dependencies: babel-runtime "^6.22.0" babel-template "^6.24.1" -babel-plugin-transform-es2015-destructuring@^6.22.0: +babel-plugin-transform-es2015-destructuring@^6.22.0, babel-plugin-transform-es2015-destructuring@^6.9.0: version "6.23.0" resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-destructuring/-/babel-plugin-transform-es2015-destructuring-6.23.0.tgz#997bb1f1ab967f682d2b0876fe358d60e765c56d" dependencies: babel-runtime "^6.22.0" -babel-plugin-transform-es2015-duplicate-keys@^6.24.1: +babel-plugin-transform-es2015-duplicate-keys@^6.24.1, babel-plugin-transform-es2015-duplicate-keys@^6.6.0: version "6.24.1" resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-duplicate-keys/-/babel-plugin-transform-es2015-duplicate-keys-6.24.1.tgz#73eb3d310ca969e3ef9ec91c53741a6f1576423e" dependencies: babel-runtime "^6.22.0" babel-types "^6.24.1" -babel-plugin-transform-es2015-for-of@^6.22.0: +babel-plugin-transform-es2015-for-of@^6.22.0, babel-plugin-transform-es2015-for-of@^6.6.0: version "6.23.0" resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-for-of/-/babel-plugin-transform-es2015-for-of-6.23.0.tgz#f47c95b2b613df1d3ecc2fdb7573623c75248691" dependencies: babel-runtime "^6.22.0" -babel-plugin-transform-es2015-function-name@^6.24.1: +babel-plugin-transform-es2015-function-name@^6.24.1, babel-plugin-transform-es2015-function-name@^6.9.0: version "6.24.1" resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-function-name/-/babel-plugin-transform-es2015-function-name-6.24.1.tgz#834c89853bc36b1af0f3a4c5dbaa94fd8eacaa8b" dependencies: @@ -598,13 +800,13 @@ babel-plugin-transform-es2015-function-name@^6.24.1: babel-runtime "^6.22.0" babel-types "^6.24.1" -babel-plugin-transform-es2015-literals@^6.22.0: +babel-plugin-transform-es2015-literals@^6.22.0, babel-plugin-transform-es2015-literals@^6.3.13: version "6.22.0" resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-literals/-/babel-plugin-transform-es2015-literals-6.22.0.tgz#4f54a02d6cd66cf915280019a31d31925377ca2e" dependencies: babel-runtime "^6.22.0" -babel-plugin-transform-es2015-modules-amd@^6.24.1: +babel-plugin-transform-es2015-modules-amd@^6.24.1, babel-plugin-transform-es2015-modules-amd@^6.8.0: version "6.24.1" resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-amd/-/babel-plugin-transform-es2015-modules-amd-6.24.1.tgz#3b3e54017239842d6d19c3011c4bd2f00a00d154" dependencies: @@ -612,7 +814,7 @@ babel-plugin-transform-es2015-modules-amd@^6.24.1: babel-runtime "^6.22.0" babel-template "^6.24.1" -babel-plugin-transform-es2015-modules-commonjs@^6.24.1: +babel-plugin-transform-es2015-modules-commonjs@^6.24.1, babel-plugin-transform-es2015-modules-commonjs@^6.6.0: version "6.26.0" resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-commonjs/-/babel-plugin-transform-es2015-modules-commonjs-6.26.0.tgz#0d8394029b7dc6abe1a97ef181e00758dd2e5d8a" dependencies: @@ -621,7 +823,7 @@ babel-plugin-transform-es2015-modules-commonjs@^6.24.1: babel-template "^6.26.0" babel-types "^6.26.0" -babel-plugin-transform-es2015-modules-systemjs@^6.24.1: +babel-plugin-transform-es2015-modules-systemjs@^6.12.0, babel-plugin-transform-es2015-modules-systemjs@^6.24.1: version "6.24.1" resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-systemjs/-/babel-plugin-transform-es2015-modules-systemjs-6.24.1.tgz#ff89a142b9119a906195f5f106ecf305d9407d23" dependencies: @@ -629,7 +831,7 @@ babel-plugin-transform-es2015-modules-systemjs@^6.24.1: babel-runtime "^6.22.0" babel-template "^6.24.1" -babel-plugin-transform-es2015-modules-umd@^6.24.1: +babel-plugin-transform-es2015-modules-umd@^6.12.0, babel-plugin-transform-es2015-modules-umd@^6.24.1: version "6.24.1" resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-umd/-/babel-plugin-transform-es2015-modules-umd-6.24.1.tgz#ac997e6285cd18ed6176adb607d602344ad38468" dependencies: @@ -637,14 +839,14 @@ babel-plugin-transform-es2015-modules-umd@^6.24.1: babel-runtime "^6.22.0" babel-template "^6.24.1" -babel-plugin-transform-es2015-object-super@^6.24.1: +babel-plugin-transform-es2015-object-super@^6.24.1, babel-plugin-transform-es2015-object-super@^6.3.13: version "6.24.1" resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-object-super/-/babel-plugin-transform-es2015-object-super-6.24.1.tgz#24cef69ae21cb83a7f8603dad021f572eb278f8d" dependencies: babel-helper-replace-supers "^6.24.1" babel-runtime "^6.22.0" -babel-plugin-transform-es2015-parameters@^6.24.1: +babel-plugin-transform-es2015-parameters@^6.24.1, babel-plugin-transform-es2015-parameters@^6.9.0: version "6.24.1" resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-parameters/-/babel-plugin-transform-es2015-parameters-6.24.1.tgz#57ac351ab49caf14a97cd13b09f66fdf0a625f2b" dependencies: @@ -655,20 +857,20 @@ babel-plugin-transform-es2015-parameters@^6.24.1: babel-traverse "^6.24.1" babel-types "^6.24.1" -babel-plugin-transform-es2015-shorthand-properties@^6.24.1: +babel-plugin-transform-es2015-shorthand-properties@^6.24.1, babel-plugin-transform-es2015-shorthand-properties@^6.3.13: version "6.24.1" resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-shorthand-properties/-/babel-plugin-transform-es2015-shorthand-properties-6.24.1.tgz#24f875d6721c87661bbd99a4622e51f14de38aa0" dependencies: babel-runtime "^6.22.0" babel-types "^6.24.1" -babel-plugin-transform-es2015-spread@^6.22.0: +babel-plugin-transform-es2015-spread@^6.22.0, babel-plugin-transform-es2015-spread@^6.3.13: version "6.22.0" resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-spread/-/babel-plugin-transform-es2015-spread-6.22.0.tgz#d6d68a99f89aedc4536c81a542e8dd9f1746f8d1" dependencies: babel-runtime "^6.22.0" -babel-plugin-transform-es2015-sticky-regex@^6.24.1: +babel-plugin-transform-es2015-sticky-regex@^6.24.1, babel-plugin-transform-es2015-sticky-regex@^6.3.13: version "6.24.1" resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-sticky-regex/-/babel-plugin-transform-es2015-sticky-regex-6.24.1.tgz#00c1cdb1aca71112cdf0cf6126c2ed6b457ccdbc" dependencies: @@ -676,19 +878,19 @@ babel-plugin-transform-es2015-sticky-regex@^6.24.1: babel-runtime "^6.22.0" babel-types "^6.24.1" -babel-plugin-transform-es2015-template-literals@^6.22.0: +babel-plugin-transform-es2015-template-literals@^6.22.0, babel-plugin-transform-es2015-template-literals@^6.6.0: version "6.22.0" resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-template-literals/-/babel-plugin-transform-es2015-template-literals-6.22.0.tgz#a84b3450f7e9f8f1f6839d6d687da84bb1236d8d" dependencies: babel-runtime "^6.22.0" -babel-plugin-transform-es2015-typeof-symbol@^6.22.0: +babel-plugin-transform-es2015-typeof-symbol@^6.22.0, babel-plugin-transform-es2015-typeof-symbol@^6.6.0: version "6.23.0" resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-typeof-symbol/-/babel-plugin-transform-es2015-typeof-symbol-6.23.0.tgz#dec09f1cddff94b52ac73d505c84df59dcceb372" dependencies: babel-runtime "^6.22.0" -babel-plugin-transform-es2015-unicode-regex@^6.24.1: +babel-plugin-transform-es2015-unicode-regex@^6.24.1, babel-plugin-transform-es2015-unicode-regex@^6.3.13: version "6.24.1" resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-unicode-regex/-/babel-plugin-transform-es2015-unicode-regex-6.24.1.tgz#d38b12f42ea7323f729387f18a7c5ae1faeb35e9" dependencies: @@ -696,7 +898,42 @@ babel-plugin-transform-es2015-unicode-regex@^6.24.1: babel-runtime "^6.22.0" regexpu-core "^2.0.0" -babel-plugin-transform-regenerator@^6.24.1: +babel-plugin-transform-flow-strip-types@^6.3.13: + version "6.22.0" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-flow-strip-types/-/babel-plugin-transform-flow-strip-types-6.22.0.tgz#84cb672935d43714fdc32bce84568d87441cf7cf" + dependencies: + babel-plugin-syntax-flow "^6.18.0" + babel-runtime "^6.22.0" + +babel-plugin-transform-react-display-name@^6.3.13: + version "6.25.0" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-react-display-name/-/babel-plugin-transform-react-display-name-6.25.0.tgz#67e2bf1f1e9c93ab08db96792e05392bf2cc28d1" + dependencies: + babel-runtime "^6.22.0" + +babel-plugin-transform-react-jsx-self@^6.11.0: + version "6.22.0" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-react-jsx-self/-/babel-plugin-transform-react-jsx-self-6.22.0.tgz#df6d80a9da2612a121e6ddd7558bcbecf06e636e" + dependencies: + babel-plugin-syntax-jsx "^6.8.0" + babel-runtime "^6.22.0" + +babel-plugin-transform-react-jsx-source@^6.3.13: + version "6.22.0" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-react-jsx-source/-/babel-plugin-transform-react-jsx-source-6.22.0.tgz#66ac12153f5cd2d17b3c19268f4bf0197f44ecd6" + dependencies: + babel-plugin-syntax-jsx "^6.8.0" + babel-runtime "^6.22.0" + +babel-plugin-transform-react-jsx@^6.3.13: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-react-jsx/-/babel-plugin-transform-react-jsx-6.24.1.tgz#840a028e7df460dfc3a2d29f0c0d91f6376e66a3" + dependencies: + babel-helper-builder-react-jsx "^6.24.1" + babel-plugin-syntax-jsx "^6.8.0" + babel-runtime "^6.22.0" + +babel-plugin-transform-regenerator@^6.24.1, babel-plugin-transform-regenerator@^6.9.0: version "6.26.0" resolved "https://registry.yarnpkg.com/babel-plugin-transform-regenerator/-/babel-plugin-transform-regenerator-6.26.0.tgz#e0703696fbde27f0a3efcacf8b4dca2f7b3a8f2f" dependencies: @@ -709,6 +946,12 @@ babel-plugin-transform-strict-mode@^6.24.1: babel-runtime "^6.22.0" babel-types "^6.24.1" +babel-preset-es2015-loose@~7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/babel-preset-es2015-loose/-/babel-preset-es2015-loose-7.0.0.tgz#fd80c85d3b20cbf309bd0ce30a36380c5784bf06" + dependencies: + modify-babel-preset "^1.0.0" + babel-preset-es2015@^6.24.0: version "6.24.1" resolved "https://registry.yarnpkg.com/babel-preset-es2015/-/babel-preset-es2015-6.24.1.tgz#d44050d6bc2c9feea702aaf38d727a0210538939" @@ -738,7 +981,48 @@ babel-preset-es2015@^6.24.0: babel-plugin-transform-es2015-unicode-regex "^6.24.1" babel-plugin-transform-regenerator "^6.24.1" -babel-register@^6.26.0: +babel-preset-es2015@~6.13.2: + version "6.13.2" + resolved "https://registry.yarnpkg.com/babel-preset-es2015/-/babel-preset-es2015-6.13.2.tgz#006c469a7528bd066f2917c8b4955309dcd53cfb" + dependencies: + babel-plugin-check-es2015-constants "^6.3.13" + babel-plugin-transform-es2015-arrow-functions "^6.3.13" + babel-plugin-transform-es2015-block-scoped-functions "^6.3.13" + babel-plugin-transform-es2015-block-scoping "^6.9.0" + babel-plugin-transform-es2015-classes "^6.9.0" + babel-plugin-transform-es2015-computed-properties "^6.3.13" + babel-plugin-transform-es2015-destructuring "^6.9.0" + babel-plugin-transform-es2015-duplicate-keys "^6.6.0" + babel-plugin-transform-es2015-for-of "^6.6.0" + babel-plugin-transform-es2015-function-name "^6.9.0" + babel-plugin-transform-es2015-literals "^6.3.13" + babel-plugin-transform-es2015-modules-amd "^6.8.0" + babel-plugin-transform-es2015-modules-commonjs "^6.6.0" + babel-plugin-transform-es2015-modules-systemjs "^6.12.0" + babel-plugin-transform-es2015-modules-umd "^6.12.0" + babel-plugin-transform-es2015-object-super "^6.3.13" + babel-plugin-transform-es2015-parameters "^6.9.0" + babel-plugin-transform-es2015-shorthand-properties "^6.3.13" + babel-plugin-transform-es2015-spread "^6.3.13" + babel-plugin-transform-es2015-sticky-regex "^6.3.13" + babel-plugin-transform-es2015-template-literals "^6.6.0" + babel-plugin-transform-es2015-typeof-symbol "^6.6.0" + babel-plugin-transform-es2015-unicode-regex "^6.3.13" + babel-plugin-transform-regenerator "^6.9.0" + +babel-preset-react@~6.11.0: + version "6.11.1" + resolved "https://registry.yarnpkg.com/babel-preset-react/-/babel-preset-react-6.11.1.tgz#98ac2bd3c1b76f3062ae082580eade154a19b590" + dependencies: + babel-plugin-syntax-flow "^6.3.13" + babel-plugin-syntax-jsx "^6.3.13" + babel-plugin-transform-flow-strip-types "^6.3.13" + babel-plugin-transform-react-display-name "^6.3.13" + babel-plugin-transform-react-jsx "^6.3.13" + babel-plugin-transform-react-jsx-self "^6.11.0" + babel-plugin-transform-react-jsx-source "^6.3.13" + +babel-register@^6.26.0, babel-register@^6.9.0: version "6.26.0" resolved "https://registry.yarnpkg.com/babel-register/-/babel-register-6.26.0.tgz#6ed021173e2fcb486d7acb45c6009a856f647071" dependencies: @@ -750,14 +1034,14 @@ babel-register@^6.26.0: mkdirp "^0.5.1" source-map-support "^0.4.15" -babel-runtime@^6.11.6, babel-runtime@^6.18.0, babel-runtime@^6.22.0, babel-runtime@^6.26.0: +babel-runtime@^6.11.6, babel-runtime@^6.18.0, babel-runtime@^6.20.0, babel-runtime@^6.22.0, babel-runtime@^6.23.0, babel-runtime@^6.26.0, babel-runtime@^6.9.1: version "6.26.0" resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.26.0.tgz#965c7058668e82b55d7bfe04ff2337bc8b5647fe" dependencies: core-js "^2.4.0" regenerator-runtime "^0.11.0" -babel-template@^6.16.0, babel-template@^6.24.1, babel-template@^6.26.0: +babel-template@^6.16.0, babel-template@^6.24.1, babel-template@^6.26.0, babel-template@^6.9.0: version "6.26.0" resolved "https://registry.yarnpkg.com/babel-template/-/babel-template-6.26.0.tgz#de03e2d16396b069f46dd9fff8521fb1a0e35e02" dependencies: @@ -767,7 +1051,7 @@ babel-template@^6.16.0, babel-template@^6.24.1, babel-template@^6.26.0: babylon "^6.18.0" lodash "^4.17.4" -babel-traverse@^6.18.0, babel-traverse@^6.24.1, babel-traverse@^6.26.0: +babel-traverse@^6.13.0, babel-traverse@^6.18.0, babel-traverse@^6.24.1, babel-traverse@^6.26.0: version "6.26.0" resolved "https://registry.yarnpkg.com/babel-traverse/-/babel-traverse-6.26.0.tgz#46a9cbd7edcc62c8e5c064e2d2d8d0f4035766ee" dependencies: @@ -781,7 +1065,7 @@ babel-traverse@^6.18.0, babel-traverse@^6.24.1, babel-traverse@^6.26.0: invariant "^2.2.2" lodash "^4.17.4" -babel-types@^6.18.0, babel-types@^6.19.0, babel-types@^6.24.1, babel-types@^6.26.0: +babel-types@^6.13.0, babel-types@^6.18.0, babel-types@^6.19.0, babel-types@^6.24.1, babel-types@^6.26.0: version "6.26.0" resolved "https://registry.yarnpkg.com/babel-types/-/babel-types-6.26.0.tgz#a3b073f94ab49eb6fa55cd65227a334380632497" dependencies: @@ -797,10 +1081,14 @@ babelify@^7.3.0: babel-core "^6.0.14" object-assign "^4.0.0" -babylon@^6.18.0: +babylon@^6.18.0, babylon@^6.7.0: version "6.18.0" resolved "https://registry.yarnpkg.com/babylon/-/babylon-6.18.0.tgz#af2f3b88fa6f5c1e4c634d1a0f8eac4f55b395e3" +balanced-match@^0.4.2: + version "0.4.2" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-0.4.2.tgz#cb3f3e3c732dc0f01ee70b403f302e61d7709838" + balanced-match@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" @@ -825,6 +1113,70 @@ base@^0.11.1: mixin-deep "^1.2.0" pascalcase "^0.1.1" +basscss-align@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/basscss-align/-/basscss-align-1.0.2.tgz#294aa689d6f99da86e4af4c5c2892870855c1c37" + +basscss-border@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/basscss-border/-/basscss-border-4.0.2.tgz#14b4506329b90cb14abe5f4d3473e9fe9202df2e" + +basscss-flexbox@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/basscss-flexbox/-/basscss-flexbox-1.0.1.tgz#f64bc7607f97a7dfab74c1e011e1a77ad734f83a" + +basscss-grid@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/basscss-grid/-/basscss-grid-2.0.0.tgz#6f4c3198e786a38529f8362bc3b3bce5254c1369" + +basscss-hide@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/basscss-hide/-/basscss-hide-1.0.1.tgz#34bc138bba867c6c49ab8682a610ef495e47d750" + +basscss-layout@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/basscss-layout/-/basscss-layout-3.1.0.tgz#f9f392e480da66657d9fe5de9ca4c07c579c3a4e" + +basscss-margin@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/basscss-margin/-/basscss-margin-1.0.7.tgz#5a92d8cda98ef391c73a15ede97b34b48886417c" + +basscss-padding@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/basscss-padding/-/basscss-padding-1.1.3.tgz#69db799414e6dd58bed83776952cc299e2e6874e" + +basscss-position@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/basscss-position/-/basscss-position-2.0.3.tgz#467180a1f8f386e9072ed8d08294d2a6e0ba4305" + +basscss-type-scale@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/basscss-type-scale/-/basscss-type-scale-1.0.5.tgz#23bf5e41c9d142c8061cf9829ccf23e9b3258ec7" + +basscss-typography@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/basscss-typography/-/basscss-typography-3.0.3.tgz#182cf43df7c4ebed02750dc748041cbadff60d43" + +basscss@^8.0.3: + version "8.0.3" + resolved "https://registry.yarnpkg.com/basscss/-/basscss-8.0.3.tgz#720607254e89d363cd8d46b3aefa01d3c2b91363" + dependencies: + basscss-align "^1.0.2" + basscss-border "^4.0.2" + basscss-flexbox "^1.0.1" + basscss-grid "^2.0.0" + basscss-hide "^1.0.1" + basscss-layout "^3.1.0" + basscss-margin "^1.0.7" + basscss-padding "^1.1.3" + basscss-position "^2.0.3" + basscss-type-scale "^1.0.5" + basscss-typography "^3.0.3" + +batch@0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/batch/-/batch-0.6.1.tgz#dc34314f4e679318093fc760272525f94bf25c16" + bcrypt-pbkdf@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz#63bc5dcb61331b92bc05fd528953c33462a06f8d" @@ -893,6 +1245,18 @@ block-stream@*: dependencies: inherits "~2.0.0" +blockies@^0.0.2: + version "0.0.2" + resolved "https://registry.yarnpkg.com/blockies/-/blockies-0.0.2.tgz#22ad58da4f6b382bc79bf4386c5820c70047e4ed" + +bluebird@^3.5.1: + version "3.5.1" + resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.1.tgz#d9551f9de98f1fcda1e683d17ee91a0602ee2eb9" + +bn.js@4.11.6: + version "4.11.6" + resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.6.tgz#53344adb14617a13f6e8dd2ce28905d1c0ba3215" + bn.js@4.11.7: version "4.11.7" resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.7.tgz#ddb048e50d9482790094c13eb3fcfc833ce7ab46" @@ -901,6 +1265,32 @@ bn.js@4.11.8, bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.1.1, bn.js@^4.10.0, bn.js@^4. version "4.11.8" resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.8.tgz#2cde09eb5ee341f484746bb0309b3253b1b1442f" +body-parser@1.18.2: + version "1.18.2" + resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.18.2.tgz#87678a19d84b47d859b83199bd59bce222b10454" + dependencies: + bytes "3.0.0" + content-type "~1.0.4" + debug "2.6.9" + depd "~1.1.1" + http-errors "~1.6.2" + iconv-lite "0.4.19" + on-finished "~2.3.0" + qs "6.5.1" + raw-body "2.3.2" + type-is "~1.6.15" + +bonjour@^3.5.0: + version "3.5.0" + resolved "https://registry.yarnpkg.com/bonjour/-/bonjour-3.5.0.tgz#8e890a183d8ee9a2393b3844c691a42bcf7bc9f5" + dependencies: + array-flatten "^2.1.0" + deep-equal "^1.0.1" + dns-equal "^1.0.0" + dns-txt "^2.0.2" + multicast-dns "^6.0.1" + multicast-dns-service-types "^1.1.0" + boom@2.x.x: version "2.10.1" resolved "https://registry.yarnpkg.com/boom/-/boom-2.10.1.tgz#39c8918ceff5799f83f9492a848f625add0c766f" @@ -919,6 +1309,10 @@ boom@5.x.x: dependencies: hoek "4.x.x" +bowser@^1.7.3: + version "1.8.1" + resolved "https://registry.yarnpkg.com/bowser/-/bowser-1.8.1.tgz#49785777e7302febadb1a5b71d9a646520ed310d" + brace-expansion@^1.1.7: version "1.1.8" resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.8.tgz#c07b211c7c952ec1f8efd51a77ef0d1d3990a292" @@ -1016,6 +1410,13 @@ browserify-zlib@^0.1.4: dependencies: pako "~0.2.0" +browserslist@^1.3.6, browserslist@^1.5.2, browserslist@^1.7.6: + version "1.7.7" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-1.7.7.tgz#0bd76704258be829b2398bb50e4b62d1a166b0b9" + dependencies: + caniuse-db "^1.0.30000639" + electron-to-chromium "^1.2.7" + bs58@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/bs58/-/bs58-2.0.1.tgz#55908d58f1982aba2008fa1bed8f91998a29bf8d" @@ -1033,6 +1434,14 @@ bs58check@^1.0.8: bs58 "^3.1.0" create-hash "^1.1.0" +buffer-indexof@^1.0.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/buffer-indexof/-/buffer-indexof-1.1.1.tgz#52fabcc6a606d1a00302802648ef68f639da268c" + +buffer-shims@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/buffer-shims/-/buffer-shims-1.0.0.tgz#9978ce317388c649ad8793028c3477ef044a8b51" + buffer-xor@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/buffer-xor/-/buffer-xor-1.0.3.tgz#26e61ed1422fb70dd42e6e36729ed51d855fe8d9" @@ -1064,6 +1473,10 @@ byline@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/byline/-/byline-5.0.0.tgz#741c5216468eadc457b03410118ad77de8c1ddb1" +bytes@3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048" + cache-base@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/cache-base/-/cache-base-1.0.1.tgz#0a7f46416831c8b662ee36fe4e7c59d76f666ab2" @@ -1109,6 +1522,19 @@ camelcase@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-4.1.0.tgz#d545635be1e33c542649c69173e5de6acfae34dd" +caniuse-api@^1.5.2: + version "1.6.1" + resolved "https://registry.yarnpkg.com/caniuse-api/-/caniuse-api-1.6.1.tgz#b534e7c734c4f81ec5fbe8aca2ad24354b962c6c" + dependencies: + browserslist "^1.3.6" + caniuse-db "^1.0.30000529" + lodash.memoize "^4.1.2" + lodash.uniq "^4.5.0" + +caniuse-db@^1.0.30000529, caniuse-db@^1.0.30000634, caniuse-db@^1.0.30000639: + version "1.0.30000769" + resolved "https://registry.yarnpkg.com/caniuse-db/-/caniuse-db-1.0.30000769.tgz#c230b9c1b9e8db3e1c0d858c96e685741b96cc10" + capture-stack-trace@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/capture-stack-trace/-/capture-stack-trace-1.0.0.tgz#4a6fa07399c26bba47f0b2496b4d0fb408c5550d" @@ -1159,6 +1585,10 @@ chai@^4.0.1, chai@^4.1.1: pathval "^1.0.0" type-detect "^4.0.0" +chain-function@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/chain-function/-/chain-function-1.0.0.tgz#0d4ab37e7e18ead0bdc47b920764118ce58733dc" + chalk@^1.0.0, chalk@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" @@ -1169,7 +1599,7 @@ chalk@^1.0.0, chalk@^1.1.3: strip-ansi "^3.0.0" supports-color "^2.0.0" -chalk@^2.0.0, chalk@^2.1.0: +chalk@^2.0.0, chalk@^2.1.0, chalk@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.3.0.tgz#b5ea48efc9c1793dccc9b4767c93914d3f2d52ba" dependencies: @@ -1177,6 +1607,10 @@ chalk@^2.0.0, chalk@^2.1.0: escape-string-regexp "^1.0.5" supports-color "^4.0.0" +change-emitter@^0.1.2: + version "0.1.6" + resolved "https://registry.yarnpkg.com/change-emitter/-/change-emitter-0.1.6.tgz#e8b2fe3d7f1ab7d69a32199aff91ea6931409515" + check-error@^1.0.1, check-error@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/check-error/-/check-error-1.0.2.tgz#574d312edd88bb5dd8912e9286dd6c0aed4aac82" @@ -1187,7 +1621,7 @@ checkpoint-store@^1.1.0: dependencies: functional-red-black-tree "^1.0.1" -chokidar@^1.7.0: +chokidar@^1.6.0, chokidar@^1.7.0: version "1.7.0" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-1.7.0.tgz#798e689778151c8076b4b360e5edd28cda2bb468" dependencies: @@ -1217,6 +1651,12 @@ cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3: inherits "^2.0.1" safe-buffer "^5.0.1" +clap@^1.0.9: + version "1.2.3" + resolved "https://registry.yarnpkg.com/clap/-/clap-1.2.3.tgz#4f36745b32008492557f46412d66d50cb99bce51" + dependencies: + chalk "^1.1.3" + class-utils@^0.3.5: version "0.3.5" resolved "https://registry.yarnpkg.com/class-utils/-/class-utils-0.3.5.tgz#17e793103750f9627b2176ea34cfd1b565903c80" @@ -1227,6 +1667,10 @@ class-utils@^0.3.5: lazy-cache "^2.0.2" static-extend "^0.1.1" +classnames@^2.2.5: + version "2.2.5" + resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.2.5.tgz#fb3801d453467649ef3603c7d61a02bd129bde6d" + cli-cursor@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-2.1.0.tgz#b35dac376479facc3e94747d41d0d0f5238ffcb5" @@ -1276,6 +1720,12 @@ co@^4.6.0: version "4.6.0" resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" +coa@~1.0.1: + version "1.0.4" + resolved "https://registry.yarnpkg.com/coa/-/coa-1.0.4.tgz#a9ef153660d6a86a8bdec0289a5c684d217432fd" + dependencies: + q "^1.1.2" + code-point-at@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" @@ -1294,17 +1744,39 @@ collection-visit@^1.0.0: map-visit "^1.0.0" object-visit "^1.0.0" -color-convert@^1.9.0: +color-convert@^1.3.0, color-convert@^1.9.0: version "1.9.1" resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.1.tgz#c1261107aeb2f294ebffec9ed9ecad529a6097ed" dependencies: color-name "^1.1.1" -color-name@^1.1.1: +color-name@^1.0.0, color-name@^1.1.1: version "1.1.3" resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" -colors@^1.1.2: +color-string@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/color-string/-/color-string-0.3.0.tgz#27d46fb67025c5c2fa25993bfbf579e47841b991" + dependencies: + color-name "^1.0.0" + +color@^0.11.0: + version "0.11.4" + resolved "https://registry.yarnpkg.com/color/-/color-0.11.4.tgz#6d7b5c74fb65e841cd48792ad1ed5e07b904d764" + dependencies: + clone "^1.0.2" + color-convert "^1.3.0" + color-string "^0.3.0" + +colormin@^1.0.5: + version "1.1.2" + resolved "https://registry.yarnpkg.com/colormin/-/colormin-1.1.2.tgz#ea2f7420a72b96881a38aae59ec124a6f7298133" + dependencies: + color "^0.11.0" + css-color-names "0.0.4" + has "^1.0.1" + +colors@^1.1.2, colors@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/colors/-/colors-1.1.2.tgz#168a4701756b6a7f51a12ce0c97bfa28c084ed63" @@ -1333,6 +1805,23 @@ commondir@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b" +commonmark-react-renderer@^4.3.4: + version "4.3.4" + resolved "https://registry.yarnpkg.com/commonmark-react-renderer/-/commonmark-react-renderer-4.3.4.tgz#29f345357951ab36eb386d45ea6bc08006f3ff9b" + dependencies: + lodash.assign "^4.2.0" + lodash.isplainobject "^4.0.6" + pascalcase "^0.1.1" + xss-filters "^1.2.6" + +commonmark@^0.24.0: + version "0.24.0" + resolved "https://registry.yarnpkg.com/commonmark/-/commonmark-0.24.0.tgz#b80de0182c546355643aa15db12bfb282368278f" + dependencies: + entities "~ 1.1.1" + mdurl "~ 1.0.1" + string.prototype.repeat "^0.2.0" + compare-func@^1.3.1: version "1.3.2" resolved "https://registry.yarnpkg.com/compare-func/-/compare-func-1.3.2.tgz#99dd0ba457e1f9bc722b12c08ec33eeab31fa648" @@ -1344,10 +1833,59 @@ compare-versions@^3.0.1: version "3.1.0" resolved "https://registry.yarnpkg.com/compare-versions/-/compare-versions-3.1.0.tgz#43310256a5c555aaed4193c04d8f154cf9c6efd5" +component-clone@0.2.2: + version "0.2.2" + resolved "https://registry.yarnpkg.com/component-clone/-/component-clone-0.2.2.tgz#c7f5979822880fad8cfb0962ba29186d061ee04f" + dependencies: + component-type "*" + +component-emitter@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.2.0.tgz#ccd113a86388d06482d03de3fc7df98526ba8efe" + component-emitter@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.2.1.tgz#137918d6d78283f7df7a6b7c5a63e140e69425e6" +component-raf@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/component-raf/-/component-raf-1.2.0.tgz#b2bc72d43f1b014fde7a4b3c447c764bc73ccbaa" + +component-tween@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/component-tween/-/component-tween-1.2.0.tgz#cc39ce5dbab05b52825f41d1947638a0b01b2b8a" + dependencies: + component-clone "0.2.2" + component-emitter "1.2.0" + component-type "1.1.0" + ease-component "1.0.0" + +component-type@*: + version "1.2.1" + resolved "https://registry.yarnpkg.com/component-type/-/component-type-1.2.1.tgz#8a47901700238e4fc32269771230226f24b415a9" + +component-type@1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/component-type/-/component-type-1.1.0.tgz#95b666aad53e5c8d1f2be135c45b5d499197c0c5" + +compressible@~2.0.11: + version "2.0.12" + resolved "https://registry.yarnpkg.com/compressible/-/compressible-2.0.12.tgz#c59a5c99db76767e9876500e271ef63b3493bd66" + dependencies: + mime-db ">= 1.30.0 < 2" + +compression@^1.5.2: + version "1.7.1" + resolved "https://registry.yarnpkg.com/compression/-/compression-1.7.1.tgz#eff2603efc2e22cf86f35d2eb93589f9875373db" + dependencies: + accepts "~1.3.4" + bytes "3.0.0" + compressible "~2.0.11" + debug "2.6.9" + on-headers "~1.0.1" + safe-buffer "5.1.1" + vary "~1.1.2" + concat-map@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" @@ -1360,6 +1898,10 @@ concat-stream@^1.4.10: readable-stream "^2.2.2" typedarray "^0.0.6" +connect-history-api-fallback@^1.3.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/connect-history-api-fallback/-/connect-history-api-fallback-1.5.0.tgz#b06873934bc5e344fef611a196a6faae0aee015a" + console-browserify@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/console-browserify/-/console-browserify-1.1.0.tgz#f0241c45730a9fc6323b206dbf38edc741d0bb10" @@ -1370,10 +1912,22 @@ console-control-strings@^1.0.0, console-control-strings@~1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" +consolidated-events@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/consolidated-events/-/consolidated-events-1.1.1.tgz#25395465b35e531395418b7bbecb5ecaf198d179" + constants-browserify@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/constants-browserify/-/constants-browserify-1.0.0.tgz#c20b96d8c617748aaf1c16021760cd27fcb8cb75" +content-disposition@0.5.2: + version "0.5.2" + resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.2.tgz#0cf68bb9ddf5f2be7961c3a85178cb85dba78cb4" + +content-type@~1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b" + conventional-changelog-angular@^1.5.2: version "1.5.2" resolved "https://registry.yarnpkg.com/conventional-changelog-angular/-/conventional-changelog-angular-1.5.2.tgz#2b38f665fe9c5920af1a2f82f547f4babe6de57c" @@ -1519,14 +2073,41 @@ conventional-recommended-bump@^1.0.1: meow "^3.3.0" object-assign "^4.0.1" -convert-source-map@^1.3.0, convert-source-map@^1.5.0: +convert-source-map@^1.1.0, convert-source-map@^1.3.0, convert-source-map@^1.5.0: version "1.5.0" resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.5.0.tgz#9acd70851c6d5dfdd93d9282e5edf94a03ff46b5" +cookie-signature@1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" + +cookie@0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.3.1.tgz#e7e0a1f9ef43b4c8ba925c5c5a96e806d16873bb" + copy-descriptor@^0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d" +copy-to-clipboard@^3: + version "3.0.8" + resolved "https://registry.yarnpkg.com/copy-to-clipboard/-/copy-to-clipboard-3.0.8.tgz#f4e82f4a8830dce4666b7eb8ded0c9bcc313aba9" + dependencies: + toggle-selection "^1.0.3" + +copy-webpack-plugin@^4.0.1: + version "4.2.1" + resolved "https://registry.yarnpkg.com/copy-webpack-plugin/-/copy-webpack-plugin-4.2.1.tgz#4017366b2d815bd2997e7d6b2b2446f719f486a1" + dependencies: + bluebird "^3.5.1" + fs-extra "^4.0.2" + glob "^7.1.2" + is-glob "^4.0.0" + loader-utils "^0.2.15" + lodash "^4.3.0" + minimatch "^3.0.4" + node-dir "^0.1.10" + copyfiles@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/copyfiles/-/copyfiles-1.2.0.tgz#a8da3ac41aa2220ae29bd3c58b6984294f2c593c" @@ -1538,6 +2119,10 @@ copyfiles@^1.2.0: noms "0.0.0" through2 "^2.0.1" +core-js@^1.0.0: + version "1.2.7" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-1.2.7.tgz#652294c14651db28fa93bd2d5ff2983a4f08c636" + core-js@^2.4.0, core-js@^2.5.0: version "2.5.1" resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.5.1.tgz#ae6874dc66937789b80754ff5428df66819ca50b" @@ -1589,6 +2174,14 @@ create-hmac@^1.1.0, create-hmac@^1.1.2, create-hmac@^1.1.4: safe-buffer "^5.0.1" sha.js "^2.4.8" +create-react-class@^15.5.2, create-react-class@^15.6.0: + version "15.6.2" + resolved "https://registry.yarnpkg.com/create-react-class/-/create-react-class-15.6.2.tgz#cf1ed15f12aad7f14ef5f2dfe05e6c42f91ef02a" + dependencies: + fbjs "^0.8.9" + loose-envify "^1.3.1" + object-assign "^4.1.1" + cross-spawn@^4: version "4.0.2" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-4.0.2.tgz#7b9247621c23adfdd3856004a823cbe397424d41" @@ -1636,6 +2229,106 @@ crypto-js@^3.1.4, crypto-js@^3.1.6: version "3.1.8" resolved "https://registry.yarnpkg.com/crypto-js/-/crypto-js-3.1.8.tgz#715f070bf6014f2ae992a98b3929258b713f08d5" +crypto-js@^3.1.9-1: + version "3.1.9-1" + resolved "https://registry.yarnpkg.com/crypto-js/-/crypto-js-3.1.9-1.tgz#fda19e761fc077e01ffbfdc6e9fdfc59e8806cd8" + +css-color-names@0.0.4: + version "0.0.4" + resolved "https://registry.yarnpkg.com/css-color-names/-/css-color-names-0.0.4.tgz#808adc2e79cf84738069b646cb20ec27beb629e0" + +css-in-js-utils@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/css-in-js-utils/-/css-in-js-utils-2.0.0.tgz#5af1dd70f4b06b331f48d22a3d86e0786c0b9435" + dependencies: + hyphenate-style-name "^1.0.2" + +css-loader@0.23.x: + version "0.23.1" + resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-0.23.1.tgz#9fa23f2b5c0965235910ad5ecef3b8a36390fe50" + dependencies: + css-selector-tokenizer "^0.5.1" + cssnano ">=2.6.1 <4" + loader-utils "~0.2.2" + lodash.camelcase "^3.0.1" + object-assign "^4.0.1" + postcss "^5.0.6" + postcss-modules-extract-imports "^1.0.0" + postcss-modules-local-by-default "^1.0.1" + postcss-modules-scope "^1.0.0" + postcss-modules-values "^1.1.0" + source-list-map "^0.1.4" + +css-selector-tokenizer@^0.5.1: + version "0.5.4" + resolved "https://registry.yarnpkg.com/css-selector-tokenizer/-/css-selector-tokenizer-0.5.4.tgz#139bafd34a35fd0c1428487049e0699e6f6a2c21" + dependencies: + cssesc "^0.1.0" + fastparse "^1.1.1" + +css-selector-tokenizer@^0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/css-selector-tokenizer/-/css-selector-tokenizer-0.7.0.tgz#e6988474ae8c953477bf5e7efecfceccd9cf4c86" + dependencies: + cssesc "^0.1.0" + fastparse "^1.1.1" + regexpu-core "^1.0.0" + +cssesc@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-0.1.0.tgz#c814903e45623371a0477b40109aaafbeeaddbb4" + +"cssnano@>=2.6.1 <4": + version "3.10.0" + resolved "https://registry.yarnpkg.com/cssnano/-/cssnano-3.10.0.tgz#4f38f6cea2b9b17fa01490f23f1dc68ea65c1c38" + dependencies: + autoprefixer "^6.3.1" + decamelize "^1.1.2" + defined "^1.0.0" + has "^1.0.1" + object-assign "^4.0.1" + postcss "^5.0.14" + postcss-calc "^5.2.0" + postcss-colormin "^2.1.8" + postcss-convert-values "^2.3.4" + postcss-discard-comments "^2.0.4" + postcss-discard-duplicates "^2.0.1" + postcss-discard-empty "^2.0.1" + postcss-discard-overridden "^0.1.1" + postcss-discard-unused "^2.2.1" + postcss-filter-plugins "^2.0.0" + postcss-merge-idents "^2.1.5" + postcss-merge-longhand "^2.0.1" + postcss-merge-rules "^2.0.3" + postcss-minify-font-values "^1.0.2" + postcss-minify-gradients "^1.0.1" + postcss-minify-params "^1.0.4" + postcss-minify-selectors "^2.0.4" + postcss-normalize-charset "^1.1.0" + postcss-normalize-url "^3.0.7" + postcss-ordered-values "^2.1.0" + postcss-reduce-idents "^2.2.2" + postcss-reduce-initial "^1.0.0" + postcss-reduce-transforms "^1.0.3" + postcss-svgo "^2.1.1" + postcss-unique-selectors "^2.0.2" + postcss-value-parser "^3.2.3" + postcss-zindex "^2.0.1" + +csso@~2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/csso/-/csso-2.0.0.tgz#178b43a44621221c27756086f531e02f42900ee8" + dependencies: + clap "^1.0.9" + source-map "^0.5.3" + +csso@~2.3.1: + version "2.3.2" + resolved "https://registry.yarnpkg.com/csso/-/csso-2.3.2.tgz#ddd52c587033f49e94b71fc55569f252e8ff5f85" + dependencies: + clap "^1.0.9" + source-map "^0.5.3" + currently-unhandled@^0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/currently-unhandled/-/currently-unhandled-0.4.1.tgz#988df33feab191ef799a61369dd76c17adf957ea" @@ -1671,21 +2364,31 @@ dateformat@^1.0.11, dateformat@^1.0.12: get-stdin "^4.0.1" meow "^3.3.0" +dateformat@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/dateformat/-/dateformat-2.2.0.tgz#4065e2013cf9fb916ddfd82efb506ad4c6769062" + debug-log@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/debug-log/-/debug-log-1.0.1.tgz#2307632d4c04382b8df8a32f70b895046d52745f" +debug@2.6.9, debug@^2.1.1, debug@^2.2.0, debug@^2.3.3, debug@^2.6.6, debug@^2.6.8: + version "2.6.9" + resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" + dependencies: + ms "2.0.0" + debug@3.1.0, debug@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261" dependencies: ms "2.0.0" -debug@^2.2.0, debug@^2.3.3, debug@^2.6.8: - version "2.6.9" - resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" +debug@~2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/debug/-/debug-2.2.0.tgz#f87057e995b1a1f6ae6a4960664137bc56f039da" dependencies: - ms "2.0.0" + ms "0.7.1" decamelize@^1.0.0, decamelize@^1.1.1, decamelize@^1.1.2: version "1.2.0" @@ -1705,7 +2408,7 @@ deep-eql@^3.0.0: dependencies: type-detect "^4.0.0" -deep-equal@~1.0.1: +deep-equal@^1.0.1, deep-equal@~1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.0.1.tgz#f5d260292b660e084eff4cdbc9f08ad3247448b5" @@ -1750,10 +2453,21 @@ define-property@^1.0.0: dependencies: is-descriptor "^1.0.0" -defined@~1.0.0: +defined@^1.0.0, defined@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/defined/-/defined-1.0.0.tgz#c98d9bcef75674188e110969151199e39b1fa693" +del@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/del/-/del-3.0.0.tgz#53ecf699ffcbcb39637691ab13baf160819766e5" + dependencies: + globby "^6.1.0" + is-path-cwd "^1.0.0" + is-path-in-cwd "^1.0.0" + p-map "^1.1.1" + pify "^3.0.0" + rimraf "^2.2.8" + delayed-stream@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" @@ -1762,6 +2476,10 @@ delegates@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" +depd@1.1.1, depd@~1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.1.tgz#5783b4e1c459f06fa5ca27f991f3d06e7a310359" + des.js@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/des.js/-/des.js-1.0.0.tgz#c074d2e2aa6a8a9a07dbd61f9a15c2cd83ec8ecc" @@ -1769,7 +2487,11 @@ des.js@^1.0.0: inherits "^2.0.1" minimalistic-assert "^1.0.0" -detect-indent@^4.0.0: +destroy@~1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80" + +detect-indent@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-4.0.0.tgz#f76d064352cdf43a1cb6ce619c4ee3a9475de208" dependencies: @@ -1783,6 +2505,18 @@ detect-libc@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.2.tgz#71ad5d204bf17a6a6ca8f450c61454066ef461e1" +detect-node@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.0.3.tgz#a2033c09cc8e158d37748fbde7507832bd6ce127" + +dharma-loan-frame@^0.0.12: + version "0.0.12" + resolved "https://registry.yarnpkg.com/dharma-loan-frame/-/dharma-loan-frame-0.0.12.tgz#5765883feb92aec0481471f663f5dcb71e88b5e7" + dependencies: + progress-checkmark "^0.0.1" + react-svg-loader "^1.1.1" + urlencode "^1.1.0" + diff@3.3.1, diff@^3.1.0, diff@^3.2.0: version "3.3.1" resolved "https://registry.yarnpkg.com/diff/-/diff-3.3.1.tgz#aa8567a6eed03c531fc89d3f711cd0e5259dec75" @@ -1799,6 +2533,27 @@ dirty-chai@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/dirty-chai/-/dirty-chai-2.0.1.tgz#6b2162ef17f7943589da840abc96e75bda01aff3" +dns-equal@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/dns-equal/-/dns-equal-1.0.0.tgz#b39e7f1da6eb0a75ba9c17324b34753c47e0654d" + +dns-packet@^1.0.1: + version "1.2.2" + resolved "https://registry.yarnpkg.com/dns-packet/-/dns-packet-1.2.2.tgz#a8a26bec7646438963fc86e06f8f8b16d6c8bf7a" + dependencies: + ip "^1.1.0" + safe-buffer "^5.0.1" + +dns-txt@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/dns-txt/-/dns-txt-2.0.2.tgz#b91d806f5d27188e4ab3e7d107d881a1cc4642b6" + dependencies: + buffer-indexof "^1.0.0" + +dom-helpers@^3.2.0: + version "3.2.1" + resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-3.2.1.tgz#3203e07fed217bd1f424b019735582fc37b2825a" + dom-walk@^0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/dom-walk/-/dom-walk-0.1.1.tgz#672226dc74c8f799ad35307df936aba11acd6018" @@ -1835,6 +2590,10 @@ duplexer@^0.1.1, duplexer@~0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.1.tgz#ace6ff808c1ce66b57d1ebf97977acb02334cfc1" +ease-component@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/ease-component/-/ease-component-1.0.0.tgz#b375726db0b5b04595b77440396fec7daa5d77c9" + ecc-jsbn@~0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz#0fc73a9ed5f0d53c38193398523ef7e543777505" @@ -1845,6 +2604,14 @@ editor@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/editor/-/editor-1.0.0.tgz#60c7f87bd62bcc6a894fa8ccd6afb7823a24f742" +ee-first@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" + +electron-to-chromium@^1.2.7: + version "1.3.27" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.27.tgz#78ecb8a399066187bb374eede35d9c70565a803d" + elliptic@^6.0.0, elliptic@^6.2.3: version "6.4.0" resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.4.0.tgz#cac9af8762c85836187003c8dfe193e5e2eae5df" @@ -1861,6 +2628,10 @@ emojis-list@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-2.1.0.tgz#4daa4d9db00f9819880c79fa457ae5b09a1fd389" +encodeurl@~1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.1.tgz#79e3d58655346909fe6f0f45a5de68103b294d20" + encoding@^0.1.11: version "0.1.12" resolved "https://registry.yarnpkg.com/encoding/-/encoding-0.1.12.tgz#538b66f3ee62cd1ab51ec323829d1f9480c74beb" @@ -1882,7 +2653,7 @@ enhanced-resolve@3.3.0: object-assign "^4.0.1" tapable "^0.2.5" -enhanced-resolve@^3.4.0: +enhanced-resolve@^3.3.0, enhanced-resolve@^3.4.0: version "3.4.1" resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-3.4.1.tgz#0421e339fd71419b3da13d129b3979040230476e" dependencies: @@ -1891,7 +2662,11 @@ enhanced-resolve@^3.4.0: object-assign "^4.0.1" tapable "^0.2.7" -errno@^0.1.3, errno@~0.1.1: +"entities@~ 1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.1.tgz#6e5c2d0a5621b5dadaecef80b90edfb5cd7772f0" + +errno@^0.1.1, errno@^0.1.3, errno@~0.1.1: version "0.1.4" resolved "https://registry.yarnpkg.com/errno/-/errno-0.1.4.tgz#b896e23a9e5e8ba33871fc996abd3635fc9a1c7d" dependencies: @@ -1903,7 +2678,7 @@ error-ex@^1.2.0, error-ex@^1.3.1: dependencies: is-arrayish "^0.2.1" -es-abstract@^1.4.3, es-abstract@^1.5.0: +es-abstract@^1.4.3, es-abstract@^1.5.0, es-abstract@^1.7.0: version "1.9.0" resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.9.0.tgz#690829a07cae36b222e7fd9b75c0d0573eb25227" dependencies: @@ -1987,6 +2762,10 @@ es6-weak-map@^2.0.1: es6-iterator "^2.0.1" es6-symbol "^3.1.1" +escape-html@~1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" + escape-string-regexp@1.0.5, escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" @@ -2000,6 +2779,10 @@ escope@^3.6.0: esrecurse "^4.1.0" estraverse "^4.1.1" +esprima@^2.6.0: + version "2.7.3" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-2.7.3.tgz#96e3b70d5779f6ad49cd032673d1c312767ba581" + esprima@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.0.tgz#4499eddcd1110e0b218bacf2fa7f7f59f55ca804" @@ -2019,6 +2802,10 @@ esutils@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.2.tgz#0abf4f1caa5bcb1f7a9d8acc6dea4faaa04bac9b" +etag@~1.8.1: + version "1.8.1" + resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" + eth-block-tracker@^2.2.2: version "2.2.2" resolved "https://registry.yarnpkg.com/eth-block-tracker/-/eth-block-tracker-2.2.2.tgz#b3d72cd82ba5ee37471d22bac4f56387ee4137cf" @@ -2037,7 +2824,7 @@ eth-query@^2.1.0: json-rpc-random-id "^1.0.0" xtend "^4.0.1" -eth-sig-util@^1.3.0: +eth-sig-util@^1.2.1, eth-sig-util@^1.3.0: version "1.4.0" resolved "https://registry.yarnpkg.com/eth-sig-util/-/eth-sig-util-1.4.0.tgz#ad42fd1d9c60fff19bdef7377b42fb38e92ee7e1" dependencies: @@ -2103,7 +2890,7 @@ ethereumjs-testrpc@4.0.1: dependencies: webpack "^3.0.0" -ethereumjs-tx@^1.2.0, ethereumjs-tx@^1.2.2: +ethereumjs-tx@^1.2.0, ethereumjs-tx@^1.2.2, ethereumjs-tx@^1.3.3: version "1.3.3" resolved "https://registry.yarnpkg.com/ethereumjs-tx/-/ethereumjs-tx-1.3.3.tgz#ece051d3efdbe771ad2a518d61632ca2ab75ecbb" dependencies: @@ -2161,6 +2948,14 @@ ethereumjs-wallet@^0.6.0: utf8 "^2.1.1" uuid "^2.0.1" +ethjs-abi@0.1.8: + version "0.1.8" + resolved "https://registry.yarnpkg.com/ethjs-abi/-/ethjs-abi-0.1.8.tgz#cd288583ed628cdfadaf8adefa3ba1dbcbca6c18" + dependencies: + bn.js "4.11.6" + js-sha3 "0.5.5" + number-to-bn "1.7.0" + ethjs-util@^0.1.3: version "0.1.4" resolved "https://registry.yarnpkg.com/ethjs-util/-/ethjs-util-0.1.4.tgz#1c8b6879257444ef4d3f3fbbac2ded12cd997d93" @@ -2187,10 +2982,20 @@ event-stream@~3.3.0: stream-combiner "~0.0.4" through "~2.3.1" +eventemitter3@1.x.x: + version "1.2.0" + resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-1.2.0.tgz#1c86991d816ad1e504750e73874224ecf3bec508" + events@^1.0.0: version "1.1.1" resolved "https://registry.yarnpkg.com/events/-/events-1.1.1.tgz#9ebdb7635ad099c70dcc4c2a1f5004288e8bd924" +eventsource@0.1.6: + version "0.1.6" + resolved "https://registry.yarnpkg.com/eventsource/-/eventsource-0.1.6.tgz#0acede849ed7dd1ccc32c811bb11b944d4f29232" + dependencies: + original ">=0.0.5" + evp_bytestokey@^1.0.0, evp_bytestokey@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz#7fcbdb198dc71959432efe13842684e0525acb02" @@ -2222,6 +3027,10 @@ execa@^0.8.0: signal-exit "^3.0.0" strip-eof "^1.0.0" +exenv@^1.2.1: + version "1.2.2" + resolved "https://registry.yarnpkg.com/exenv/-/exenv-1.2.2.tgz#2ae78e85d9894158670b03d47bec1f03bd91bb9d" + expand-brackets@^0.1.4: version "0.1.5" resolved "https://registry.yarnpkg.com/expand-brackets/-/expand-brackets-0.1.5.tgz#df07284e342a807cd733ac5af72411e581d1177b" @@ -2250,6 +3059,48 @@ expand-template@^1.0.2: version "1.1.0" resolved "https://registry.yarnpkg.com/expand-template/-/expand-template-1.1.0.tgz#e09efba977bf98f9ee0ed25abd0c692e02aec3fc" +exports-loader@0.6.x: + version "0.6.4" + resolved "https://registry.yarnpkg.com/exports-loader/-/exports-loader-0.6.4.tgz#d70fc6121975b35fc12830cf52754be2740fc886" + dependencies: + loader-utils "^1.0.2" + source-map "0.5.x" + +express@^4.13.3: + version "4.16.2" + resolved "https://registry.yarnpkg.com/express/-/express-4.16.2.tgz#e35c6dfe2d64b7dca0a5cd4f21781be3299e076c" + dependencies: + accepts "~1.3.4" + array-flatten "1.1.1" + body-parser "1.18.2" + content-disposition "0.5.2" + content-type "~1.0.4" + cookie "0.3.1" + cookie-signature "1.0.6" + debug "2.6.9" + depd "~1.1.1" + encodeurl "~1.0.1" + escape-html "~1.0.3" + etag "~1.8.1" + finalhandler "1.1.0" + fresh "0.5.2" + merge-descriptors "1.0.1" + methods "~1.1.2" + on-finished "~2.3.0" + parseurl "~1.3.2" + path-to-regexp "0.1.7" + proxy-addr "~2.0.2" + qs "6.5.1" + range-parser "~1.2.0" + safe-buffer "5.1.1" + send "0.16.1" + serve-static "1.13.1" + setprototypeof "1.1.0" + statuses "~1.3.1" + type-is "~1.6.15" + utils-merge "1.0.1" + vary "~1.1.2" + extend-shallow@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-2.0.1.tgz#51af7d614ad9a9f610ea1bafbb989d6b1c56890f" @@ -2305,6 +3156,34 @@ fast-json-stable-stringify@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz#d5142c0caee6b1189f87d3a76111064f86c8bbf2" +fastparse@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/fastparse/-/fastparse-1.1.1.tgz#d1e2643b38a94d7583b479060e6c4affc94071f8" + +faye-websocket@^0.10.0: + version "0.10.0" + resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.10.0.tgz#4e492f8d04dfb6f89003507f6edbf2d501e7c6f4" + dependencies: + websocket-driver ">=0.5.1" + +faye-websocket@~0.11.0: + version "0.11.1" + resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.11.1.tgz#f0efe18c4f56e4f40afc7e06c719fd5ee6188f38" + dependencies: + websocket-driver ">=0.5.1" + +fbjs@^0.8, fbjs@^0.8.1, fbjs@^0.8.16, fbjs@^0.8.4, fbjs@^0.8.6, fbjs@^0.8.9: + version "0.8.16" + resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-0.8.16.tgz#5e67432f550dc41b572bf55847b8aca64e5337db" + dependencies: + core-js "^1.0.0" + isomorphic-fetch "^2.1.1" + loose-envify "^1.0.0" + object-assign "^4.1.0" + promise "^7.1.1" + setimmediate "^1.0.5" + ua-parser-js "^0.7.9" + fetch-mock@^5.13.1: version "5.13.1" resolved "https://registry.yarnpkg.com/fetch-mock/-/fetch-mock-5.13.1.tgz#955794a77f3d972f1644b9ace65a0fdfd60f1df7" @@ -2359,6 +3238,18 @@ fill-range@^4.0.0: repeat-string "^1.6.1" to-regex-range "^2.1.0" +finalhandler@1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.0.tgz#ce0b6855b45853e791b2fcc680046d88253dd7f5" + dependencies: + debug "2.6.9" + encodeurl "~1.0.1" + escape-html "~1.0.3" + on-finished "~2.3.0" + parseurl "~1.3.2" + statuses "~1.3.1" + unpipe "~1.0.0" + find-cache-dir@^0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-0.1.1.tgz#c8defae57c8a52a8a784f9e31c57c742e993a0b9" @@ -2387,6 +3278,10 @@ find-versions@^2.0.0: array-uniq "^1.0.0" semver-regex "^1.0.0" +flatten@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/flatten/-/flatten-1.0.2.tgz#dae46a9d78fbe25292258cc1e780a41d95c03782" + for-each@^0.3.2, for-each@~0.3.2: version "0.3.2" resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.2.tgz#2c40450b9348e97f281322593ba96704b9abd4d4" @@ -2440,12 +3335,20 @@ formatio@1.2.0, formatio@^1.2.0: dependencies: samsam "1.x" +forwarded@~0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.2.tgz#98c23dab1175657b8c0573e8ceccd91b0ff18c84" + fragment-cache@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/fragment-cache/-/fragment-cache-0.2.1.tgz#4290fad27f13e89be7f33799c6bc5a0abfff0d19" dependencies: map-cache "^0.2.2" +fresh@0.5.2: + version "0.5.2" + resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" + from@~0: version "0.1.7" resolved "https://registry.yarnpkg.com/from/-/from-0.1.7.tgz#83c60afc58b9c56997007ed1a768b3ab303a44fe" @@ -2460,7 +3363,7 @@ fs-extra@^0.30.0: path-is-absolute "^1.0.0" rimraf "^2.2.8" -fs-extra@^4.0.0, fs-extra@^4.0.1: +fs-extra@^4.0.0, fs-extra@^4.0.1, fs-extra@^4.0.2: version "4.0.2" resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-4.0.2.tgz#f91704c53d1b461f893452b0c307d9997647ab6b" dependencies: @@ -2479,7 +3382,7 @@ fsevents@^1.0.0: nan "^2.3.0" node-pre-gyp "^0.6.39" -fstream-ignore@^1.0.5: +fstream-ignore@^1.0.5, fstream-ignore@~1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/fstream-ignore/-/fstream-ignore-1.0.5.tgz#9c31dae34767018fe1d249b24dada67d092da105" dependencies: @@ -2487,7 +3390,7 @@ fstream-ignore@^1.0.5: inherits "2" minimatch "^3.0.0" -fstream@^1.0.0, fstream@^1.0.10, fstream@^1.0.2: +fstream@^1.0.0, fstream@^1.0.10, fstream@^1.0.2, fstream@~1.0.10: version "1.0.11" resolved "https://registry.yarnpkg.com/fstream/-/fstream-1.0.11.tgz#5c1fb1f117477114f0632a0eb4b71b3cb0fd3171" dependencies: @@ -2691,6 +3594,10 @@ growl@1.10.3: version "1.10.3" resolved "https://registry.yarnpkg.com/growl/-/growl-1.10.3.tgz#1926ba90cf3edfe2adb4927f5880bc22c66c790f" +handle-thing@^1.2.5: + version "1.2.5" + resolved "https://registry.yarnpkg.com/handle-thing/-/handle-thing-1.2.5.tgz#fd7aad726bf1a5fd16dfc29b2f7a6601d27139c4" + handlebars@^4.0.2, handlebars@^4.0.3, handlebars@^4.0.6: version "4.0.11" resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.0.11.tgz#630a35dfe0294bc281edae6ffc5d329fc7982dcc" @@ -2823,10 +3730,20 @@ he@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/he/-/he-1.1.1.tgz#93410fd21b009735151f8868c2f271f3427e23fd" -highlight.js@^9.0.0: +highlight.js@^9.0.0, highlight.js@^9.11.0: version "9.12.0" resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-9.12.0.tgz#e6d9dbe57cbefe60751f02af336195870c90c01e" +history@^4.7.2: + version "4.7.2" + resolved "https://registry.yarnpkg.com/history/-/history-4.7.2.tgz#22b5c7f31633c5b8021c7f4a8a954ac139ee8d5b" + dependencies: + invariant "^2.2.1" + loose-envify "^1.2.0" + resolve-pathname "^2.2.0" + value-equal "^0.4.0" + warning "^3.0.0" + hmac-drbg@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1" @@ -2843,6 +3760,14 @@ hoek@4.x.x: version "4.2.0" resolved "https://registry.yarnpkg.com/hoek/-/hoek-4.2.0.tgz#72d9d0754f7fe25ca2d01ad8f8f9a9449a89526d" +hoist-non-react-statics@^1.0.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-1.2.0.tgz#aa448cf0986d55cc40773b17174b7dd066cb7cfb" + +hoist-non-react-statics@^2.2.1, hoist-non-react-statics@^2.3.0: + version "2.3.1" + resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-2.3.1.tgz#343db84c6018c650778898240135a1420ee22ce0" + home-or-tmp@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/home-or-tmp/-/home-or-tmp-2.0.0.tgz#e36c3f2d2cae7d746a857e38d18d5f32a7882db8" @@ -2854,6 +3779,56 @@ hosted-git-info@^2.1.4, hosted-git-info@^2.5.0: version "2.5.0" resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.5.0.tgz#6d60e34b3abbc8313062c3b798ef8d901a07af3c" +hpack.js@^2.1.6: + version "2.1.6" + resolved "https://registry.yarnpkg.com/hpack.js/-/hpack.js-2.1.6.tgz#87774c0949e513f42e84575b3c45681fade2a0b2" + dependencies: + inherits "^2.0.1" + obuf "^1.0.0" + readable-stream "^2.0.1" + wbuf "^1.1.0" + +html-comment-regex@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/html-comment-regex/-/html-comment-regex-1.1.1.tgz#668b93776eaae55ebde8f3ad464b307a4963625e" + +html-entities@^1.2.0: + version "1.2.1" + resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-1.2.1.tgz#0df29351f0721163515dfb9e5543e5f6eed5162f" + +http-deceiver@^1.2.7: + version "1.2.7" + resolved "https://registry.yarnpkg.com/http-deceiver/-/http-deceiver-1.2.7.tgz#fa7168944ab9a519d337cb0bec7284dc3e723d87" + +http-errors@1.6.2, http-errors@~1.6.2: + version "1.6.2" + resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.2.tgz#0a002cc85707192a7e7946ceedc11155f60ec736" + dependencies: + depd "1.1.1" + inherits "2.0.3" + setprototypeof "1.0.3" + statuses ">= 1.3.1 < 2" + +http-parser-js@>=0.4.0: + version "0.4.9" + resolved "https://registry.yarnpkg.com/http-parser-js/-/http-parser-js-0.4.9.tgz#ea1a04fb64adff0242e9974f297dd4c3cad271e1" + +http-proxy-middleware@~0.17.4: + version "0.17.4" + resolved "https://registry.yarnpkg.com/http-proxy-middleware/-/http-proxy-middleware-0.17.4.tgz#642e8848851d66f09d4f124912846dbaeb41b833" + dependencies: + http-proxy "^1.16.2" + is-glob "^3.1.0" + lodash "^4.17.2" + micromatch "^2.3.11" + +http-proxy@^1.16.2: + version "1.16.2" + resolved "https://registry.yarnpkg.com/http-proxy/-/http-proxy-1.16.2.tgz#06dff292952bf64dbe8471fa9df73066d4f37742" + dependencies: + eventemitter3 "1.x.x" + requires-port "1.x.x" + http-signature@~1.1.0: version "1.1.1" resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.1.1.tgz#df72e267066cd0ac67fb76adf8e134a8fbcf91bf" @@ -2870,6 +3845,14 @@ http-signature@~1.2.0: jsprim "^1.2.2" sshpk "^1.7.0" +httpplease@^0.16: + version "0.16.4" + resolved "https://registry.yarnpkg.com/httpplease/-/httpplease-0.16.4.tgz#d382ebe230ef5079080b4e9ffebf316a9e75c0da" + dependencies: + urllite "~0.5.0" + xmlhttprequest "*" + xtend "~3.0.0" + https-browserify@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-0.0.1.tgz#3f91365cabe60b77ed0ebba24b454e3e09d95a82" @@ -2881,14 +3864,26 @@ hyperquest@~1.2.0: duplexer2 "~0.0.2" through2 "~0.6.3" -iconv-lite@^0.4.17, iconv-lite@~0.4.13: +hyphenate-style-name@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/hyphenate-style-name/-/hyphenate-style-name-1.0.2.tgz#31160a36930adaf1fc04c6074f7eb41465d4ec4b" + +iconv-lite@0.4.19, iconv-lite@^0.4.17, iconv-lite@~0.4.11, iconv-lite@~0.4.13: version "0.4.19" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.19.tgz#f7468f60135f5e5dad3399c0a81be9a1603a082b" +icss-replace-symbols@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/icss-replace-symbols/-/icss-replace-symbols-1.1.0.tgz#06ea6f83679a7749e386cfe1fe812ae5db223ded" + ieee754@^1.1.4: version "1.1.8" resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.8.tgz#be33d40ac10ef1926701f6f08a2d86fbfd1ad3e4" +image-size@~0.5.0: + version "0.5.5" + resolved "https://registry.yarnpkg.com/image-size/-/image-size-0.5.5.tgz#09dfd4ab9d20e29eb1c3e80b8990378df9e3cb9c" + immediate@^3.2.3: version "3.2.3" resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.2.3.tgz#d140fa8f614659bd6541233097ddaac25cdd991c" @@ -2897,6 +3892,20 @@ immutable@3.8.1: version "3.8.1" resolved "https://registry.yarnpkg.com/immutable/-/immutable-3.8.1.tgz#200807f11ab0f72710ea485542de088075f68cd2" +import-local@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/import-local/-/import-local-0.1.1.tgz#b1179572aacdc11c6a91009fb430dbcab5f668a8" + dependencies: + pkg-dir "^2.0.0" + resolve-cwd "^2.0.0" + +imports-loader@0.6.x: + version "0.6.5" + resolved "https://registry.yarnpkg.com/imports-loader/-/imports-loader-0.6.5.tgz#ae74653031d59e37b3c2fb2544ac61aeae3530a6" + dependencies: + loader-utils "0.2.x" + source-map "0.1.x" + imurmurhash@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" @@ -2907,6 +3916,10 @@ indent-string@^2.1.0: dependencies: repeating "^2.0.0" +indexes-of@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/indexes-of/-/indexes-of-1.0.1.tgz#f30f716c8e2bd346c7b67d3df3915566a7c05607" + indexof@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/indexof/-/indexof-0.0.1.tgz#82dc336d232b9062179d05ab3293a66059fd435d" @@ -2918,7 +3931,7 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@2, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.0, inherits@~2.0.1, inherits@~2.0.3: +inherits@2, inherits@2.0.3, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.0, inherits@~2.0.1, inherits@~2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" @@ -2930,6 +3943,13 @@ ini@^1.3.2, ini@~1.3.0: version "1.3.4" resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.4.tgz#0537cb79daf59b59a1a517dff706c86ec039162e" +inline-style-prefixer@^3.0.2: + version "3.0.8" + resolved "https://registry.yarnpkg.com/inline-style-prefixer/-/inline-style-prefixer-3.0.8.tgz#8551b8e5b4d573244e66a34b04f7d32076a2b534" + dependencies: + bowser "^1.7.3" + css-in-js-utils "^2.0.0" + inquirer@^0.8.2: version "0.8.5" resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-0.8.5.tgz#dbd740cf6ca3b731296a63ce6f6d961851f336df" @@ -2962,11 +3982,17 @@ inquirer@^3.2.2: strip-ansi "^4.0.0" through "^2.3.6" +internal-ip@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/internal-ip/-/internal-ip-1.2.0.tgz#ae9fbf93b984878785d50a8de1b356956058cf5c" + dependencies: + meow "^3.3.0" + interpret@^1.0.0: version "1.0.4" resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.0.4.tgz#820cdd588b868ffb191a809506d6c9c8f212b1b0" -invariant@^2.2.2: +invariant@^2.0.0, invariant@^2.2.1, invariant@^2.2.2: version "2.2.2" resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.2.tgz#9e1f56ac0acdb6bf303306f338be3b204ae60360" dependencies: @@ -2976,6 +4002,18 @@ invert-kv@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-1.0.0.tgz#104a8e4aaca6d3d8cd157a8ef8bfab2d7a3ffdb6" +ip@^1.1.0, ip@^1.1.5: + version "1.1.5" + resolved "https://registry.yarnpkg.com/ip/-/ip-1.1.5.tgz#bdded70114290828c0a039e72ef25f5aaec4354a" + +ipaddr.js@1.5.2: + version "1.5.2" + resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.5.2.tgz#d4b505bde9946987ccf0fc58d9010ff9607e3fa0" + +is-absolute-url@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-absolute-url/-/is-absolute-url-2.1.0.tgz#50530dfb84fcc9aa7dbe7852e83a37b93b9f2aa6" + is-accessor-descriptor@^0.1.6: version "0.1.6" resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz#a9e12cb3ae8d876727eeef3843f8a0897b5c98d6" @@ -3056,7 +4094,7 @@ is-extglob@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-1.0.0.tgz#ac468177c4943405a092fc8f29760c6ffc6206c0" -is-extglob@^2.1.0: +is-extglob@^2.1.0, is-extglob@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" @@ -3096,10 +4134,20 @@ is-glob@^3.1.0: dependencies: is-extglob "^2.1.0" +is-glob@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.0.tgz#9521c76845cc2610a85203ddf080a958c2ffabc0" + dependencies: + is-extglob "^2.1.1" + is-hex-prefixed@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-hex-prefixed/-/is-hex-prefixed-1.0.0.tgz#7d8d37e6ad77e5d127148913c573e082d777f554" +is-mobile@^0.2.2: + version "0.2.2" + resolved "https://registry.yarnpkg.com/is-mobile/-/is-mobile-0.2.2.tgz#0e2e006d99ed2c2155b761df80f2a3619ae2ad9f" + is-number@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/is-number/-/is-number-2.1.0.tgz#01fcbbb393463a548f2f466cce16dece49db908f" @@ -3122,6 +4170,22 @@ is-odd@^1.0.0: dependencies: is-number "^3.0.0" +is-path-cwd@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-path-cwd/-/is-path-cwd-1.0.0.tgz#d225ec23132e89edd38fda767472e62e65f1106d" + +is-path-in-cwd@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-path-in-cwd/-/is-path-in-cwd-1.0.0.tgz#6477582b8214d602346094567003be8a9eac04dc" + dependencies: + is-path-inside "^1.0.0" + +is-path-inside@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-1.0.0.tgz#fc06e5a1683fbda13de667aff717bbc10a48f37f" + dependencies: + path-is-inside "^1.0.1" + is-plain-obj@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-1.1.0.tgz#71a50c8429dfca773c92a390a4a03b39fcd51d3e" @@ -3166,6 +4230,12 @@ is-subset@^0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/is-subset/-/is-subset-0.1.1.tgz#8a59117d932de1de00f245fcdd39ce43f1e939a6" +is-svg@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-svg/-/is-svg-2.1.0.tgz#cf61090da0d9efbcab8722deba6f032208dbb0e9" + dependencies: + html-comment-regex "^1.1.0" + is-symbol@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.1.tgz#3cc59f00025194b6ab2e38dbae6689256b660572" @@ -3188,6 +4258,10 @@ is-utf8@^0.2.0: version "0.2.1" resolved "https://registry.yarnpkg.com/is-utf8/-/is-utf8-0.2.1.tgz#4b0da1442104d1b336340e80797e865cf39f7d72" +is-wsl@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-1.1.0.tgz#1f16e4aa22b04d1336b66188a66af3c600c3a66d" + isarray@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" @@ -3210,7 +4284,7 @@ isobject@^3.0.0, isobject@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" -isomorphic-fetch@^2.2.0, isomorphic-fetch@^2.2.1: +isomorphic-fetch@^2.1.1, isomorphic-fetch@^2.2.0, isomorphic-fetch@^2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz#611ae1acf14f5e81f729507472819fe9733558a9" dependencies: @@ -3268,6 +4342,14 @@ istanbul-reports@^1.1.3: dependencies: handlebars "^4.0.3" +js-base64@^2.1.9: + version "2.3.2" + resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-2.3.2.tgz#a79a923666372b580f8e27f51845c6f7e8fbfbaf" + +js-sha3@0.5.5: + version "0.5.5" + resolved "https://registry.yarnpkg.com/js-sha3/-/js-sha3-0.5.5.tgz#baf0c0e8c54ad5903447df96ade7a4a1bca79a4a" + js-sha3@^0.3.1: version "0.3.1" resolved "https://registry.yarnpkg.com/js-sha3/-/js-sha3-0.3.1.tgz#86122802142f0828502a0d1dee1d95e253bb0243" @@ -3287,6 +4369,20 @@ js-yaml@^3.6.1: argparse "^1.0.7" esprima "^4.0.0" +js-yaml@~3.6.0: + version "3.6.1" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.6.1.tgz#6e5fe67d8b205ce4d22fad05b7781e8dadcc4b30" + dependencies: + argparse "^1.0.7" + esprima "^2.6.0" + +js-yaml@~3.7.0: + version "3.7.0" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.7.0.tgz#5c967ddd837a9bfdca5f2de84253abe8a1c03b80" + dependencies: + argparse "^1.0.7" + esprima "^2.6.0" + jsbn@~0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" @@ -3339,6 +4435,14 @@ json-stringify-safe@^5.0.1, json-stringify-safe@~5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" +json3@^3.3.2: + version "3.3.2" + resolved "https://registry.yarnpkg.com/json3/-/json3-3.3.2.tgz#3c0434743df93e2f5c42aee7b19bcb483575f4e1" + +json5@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/json5/-/json5-0.4.0.tgz#054352e4c4c80c86c0923877d449de176a732c8d" + json5@^0.5.0, json5@^0.5.1: version "0.5.1" resolved "https://registry.yarnpkg.com/json5/-/json5-0.5.1.tgz#1eade7acc012034ad84e2396767ead9fa5495821" @@ -3363,7 +4467,7 @@ jsonparse@^1.2.0: version "1.3.1" resolved "https://registry.yarnpkg.com/jsonparse/-/jsonparse-1.3.1.tgz#3f4dae4a91fac315f71062f8521cc239f1366280" -jsonschema@*, jsonschema@^1.2.0: +jsonschema@*, jsonschema@^1.1.1, jsonschema@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/jsonschema/-/jsonschema-1.2.0.tgz#d6ebaf70798db7b3a20c544f6c9ef9319b077de2" @@ -3397,6 +4501,14 @@ keccakjs@^0.2.0: browserify-sha3 "^0.0.1" sha3 "^1.1.0" +keycode@^2.1.8: + version "2.1.9" + resolved "https://registry.yarnpkg.com/keycode/-/keycode-2.1.9.tgz#964a23c54e4889405b4861a5c9f0480d45141dfa" + +killable@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/killable/-/killable-1.0.0.tgz#da8b84bd47de5395878f95d64d02f2449fe05e6b" + kind-of@^3.0.2, kind-of@^3.0.3, kind-of@^3.2.0: version "3.2.2" resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64" @@ -3443,6 +4555,14 @@ lcov-parse@^0.0.10: version "0.0.10" resolved "https://registry.yarnpkg.com/lcov-parse/-/lcov-parse-0.0.10.tgz#1b0b8ff9ac9c7889250582b70b71315d9da6d9a3" +ledgerco@0xProject/ledger-node-js-api: + version "1.1.3" + resolved "https://codeload.github.com/0xProject/ledger-node-js-api/tar.gz/dc2024bac997bf023f12203f118d10ba84d15ded" + dependencies: + async "2.1.4" + node-hid "0.5.4" + q "1.4.1" + lerna@^2.5.1: version "2.5.1" resolved "https://registry.yarnpkg.com/lerna/-/lerna-2.5.1.tgz#d07099bd3051ee799f98c753328bd69e96c6fab8" @@ -3486,6 +4606,25 @@ lerna@^2.5.1: write-pkg "^3.1.0" yargs "^8.0.2" +less-loader@^2.2.3: + version "2.2.3" + resolved "https://registry.yarnpkg.com/less-loader/-/less-loader-2.2.3.tgz#b6d8f8139c8493df09d992a93a00734b08f84528" + dependencies: + loader-utils "^0.2.5" + +less@^2.7.2: + version "2.7.3" + resolved "https://registry.yarnpkg.com/less/-/less-2.7.3.tgz#cc1260f51c900a9ec0d91fb6998139e02507b63b" + optionalDependencies: + errno "^0.1.1" + graceful-fs "^4.1.2" + image-size "~0.5.0" + mime "^1.2.11" + mkdirp "^0.5.0" + promise "^7.1.1" + request "2.81.0" + source-map "^0.5.3" + level-codec@~7.0.0: version "7.0.1" resolved "https://registry.yarnpkg.com/level-codec/-/level-codec-7.0.1.tgz#341f22f907ce0f16763f24bddd681e395a0fb8a7" @@ -3565,7 +4704,16 @@ loader-runner@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-2.3.0.tgz#f482aea82d543e07921700d5a46ef26fdac6b8a2" -loader-utils@^1.1.0: +loader-utils@0.2.x, loader-utils@^0.2.15, loader-utils@^0.2.5, loader-utils@~0.2.13, loader-utils@~0.2.2: + version "0.2.17" + resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-0.2.17.tgz#f86e6374d43205a6e6c60e9196f17c0299bfb348" + dependencies: + big.js "^3.1.3" + emojis-list "^2.0.0" + json5 "^0.5.0" + object-assign "^4.0.1" + +loader-utils@^1.0.2, loader-utils@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.1.0.tgz#c98aef488bcceda2ffb5e2de646d6a754429f5cd" dependencies: @@ -3580,14 +4728,41 @@ locate-path@^2.0.0: p-locate "^2.0.0" path-exists "^3.0.0" +lodash-es@^4.2.0, lodash-es@^4.2.1: + version "4.17.4" + resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.4.tgz#dcc1d7552e150a0640073ba9cb31d70f032950e7" + +lodash._createcompounder@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/lodash._createcompounder/-/lodash._createcompounder-3.0.0.tgz#5dd2cb55372d6e70e0e2392fb2304d6631091075" + dependencies: + lodash.deburr "^3.0.0" + lodash.words "^3.0.0" + lodash._reinterpolate@~3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz#0ccf2d89166af03b3663c796538b75ac6e114d9d" -lodash.assign@^4.0.3, lodash.assign@^4.0.6: +lodash._root@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/lodash._root/-/lodash._root-3.0.1.tgz#fba1c4524c19ee9a5f8136b4609f017cf4ded692" + +lodash.assign@^4.0.3, lodash.assign@^4.0.6, lodash.assign@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/lodash.assign/-/lodash.assign-4.2.0.tgz#0d99f3ccd7a6d261d19bdaeb9245005d285808e7" +lodash.camelcase@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-3.0.1.tgz#932c8b87f8a4377897c67197533282f97aeac298" + dependencies: + lodash._createcompounder "^3.0.0" + +lodash.deburr@^3.0.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/lodash.deburr/-/lodash.deburr-3.2.0.tgz#6da8f54334a366a7cf4c4c76ef8d80aa1b365ed5" + dependencies: + lodash._root "^3.0.0" + lodash.foreach@^4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/lodash.foreach/-/lodash.foreach-4.5.0.tgz#1a6a35eace401280c7f06dddec35165ab27e3e53" @@ -3596,6 +4771,18 @@ lodash.get@^4.4.2: version "4.4.2" resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99" +lodash.isplainobject@^4.0.6, lodash.isplainobject@~4.0.4: + version "4.0.6" + resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb" + +lodash.memoize@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" + +lodash.merge@^4.6.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.0.tgz#69884ba144ac33fe699737a6086deffadd0f89c5" + lodash.template@^4.0.2: version "4.4.0" resolved "https://registry.yarnpkg.com/lodash.template/-/lodash.template-4.4.0.tgz#e73a0385c8355591746e020b99679c690e68fba0" @@ -3609,15 +4796,29 @@ lodash.templatesettings@^4.0.0: dependencies: lodash._reinterpolate "~3.0.0" +lodash.throttle@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/lodash.throttle/-/lodash.throttle-4.1.1.tgz#c23e91b710242ac70c37f1e1cda9274cc39bf2f4" + +lodash.uniq@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" + lodash.values@^4.3.0: version "4.3.0" resolved "https://registry.yarnpkg.com/lodash.values/-/lodash.values-4.3.0.tgz#a3a6c2b0ebecc5c2cba1c17e6e620fe81b53d347" +lodash.words@^3.0.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/lodash.words/-/lodash.words-3.2.0.tgz#4e2a8649bc08745b17c695b1a3ce8fee596623b3" + dependencies: + lodash._root "^3.0.0" + lodash@^3.3.1, lodash@^3.6.0: version "3.10.1" resolved "https://registry.yarnpkg.com/lodash/-/lodash-3.10.1.tgz#5bf45e8e49ba4189e17d482789dfd15bd140b7b6" -lodash@^4.0.0, lodash@^4.1.0, lodash@^4.13.1, lodash@^4.14.0, lodash@^4.17.4, lodash@^4.2.1, lodash@^4.3.0: +lodash@^4.0.0, lodash@^4.1.0, lodash@^4.13.1, lodash@^4.14.0, lodash@^4.17.2, lodash@^4.17.4, lodash@^4.2.0, lodash@^4.2.1, lodash@^4.3.0: version "4.17.4" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae" @@ -3625,6 +4826,10 @@ log-driver@^1.2.5: version "1.2.5" resolved "https://registry.yarnpkg.com/log-driver/-/log-driver-1.2.5.tgz#7ae4ec257302fd790d557cb10c97100d857b0056" +loglevel@^1.4.1: + version "1.6.0" + resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.6.0.tgz#ae0caa561111498c5ba13723d6fb631d24003934" + lolex@^1.6.0: version "1.6.0" resolved "https://registry.yarnpkg.com/lolex/-/lolex-1.6.0.tgz#3a9a0283452a47d7439e72731b9e07d7386e49f6" @@ -3637,7 +4842,7 @@ longest@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/longest/-/longest-1.0.1.tgz#30a0b2da38f73770e8294a0d22e6625ed77d0097" -loose-envify@^1.0.0: +loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.2.0, loose-envify@^1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.3.1.tgz#d1a8ad33fa9ce0e713d65fdd0ac8b748d478c848" dependencies: @@ -3669,6 +4874,10 @@ ltgt@~2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/ltgt/-/ltgt-2.2.0.tgz#b65ba5fcb349a29924c8e333f7c6a5562f2e4842" +macaddress@^0.2.8: + version "0.2.8" + resolved "https://registry.yarnpkg.com/macaddress/-/macaddress-0.2.8.tgz#5904dc537c39ec6dbefeae902327135fa8511f12" + make-dir@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-1.1.0.tgz#19b4369fe48c116f53c2af95ad102c0e39e85d51" @@ -3697,6 +4906,27 @@ marked@^0.3.5: version "0.3.6" resolved "https://registry.yarnpkg.com/marked/-/marked-0.3.6.tgz#b2c6c618fccece4ef86c4fc6cb8a7cbf5aeda8d7" +material-ui@^0.17.1: + version "0.17.4" + resolved "https://registry.yarnpkg.com/material-ui/-/material-ui-0.17.4.tgz#193999ecb49c3ec15ae0abb4e90fdf9a7bd343e0" + dependencies: + babel-runtime "^6.23.0" + inline-style-prefixer "^3.0.2" + keycode "^2.1.8" + lodash.merge "^4.6.0" + lodash.throttle "^4.1.1" + prop-types "^15.5.7" + react-addons-create-fragment "^15.4.0" + react-addons-transition-group "^15.4.0" + react-event-listener "^0.4.5" + recompose "^0.23.0" + simple-assign "^0.1.0" + warning "^3.0.0" + +math-expression-evaluator@^1.2.14: + version "1.2.17" + resolved "https://registry.yarnpkg.com/math-expression-evaluator/-/math-expression-evaluator-1.2.17.tgz#de819fdbcd84dccd8fae59c6aeb79615b9d266ac" + md5-hex@^1.2.0: version "1.3.0" resolved "https://registry.yarnpkg.com/md5-hex/-/md5-hex-1.3.0.tgz#d2c4afe983c4370662179b8cad145219135046c4" @@ -3714,6 +4944,14 @@ md5.js@^1.3.4: hash-base "^3.0.0" inherits "^2.0.1" +"mdurl@~ 1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/mdurl/-/mdurl-1.0.1.tgz#fe85b2ec75a59037f2adfec100fd6c601761152e" + +media-typer@0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" + mem@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/mem/-/mem-1.1.0.tgz#5edd52b485ca1d900fe64895505399a0dfa45f76" @@ -3757,6 +4995,10 @@ meow@^3.1.0, meow@^3.3.0, meow@^3.7.0: redent "^1.0.0" trim-newlines "^1.0.0" +merge-descriptors@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" + merge-source-map@^1.0.2: version "1.0.4" resolved "https://registry.yarnpkg.com/merge-source-map/-/merge-source-map-1.0.4.tgz#a5de46538dae84d4114cc5ea02b4772a6346701f" @@ -3776,6 +5018,10 @@ merkle-patricia-tree@^2.1.2: rlp "^2.0.0" semaphore ">=1.0.1" +methods@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" + micromatch@^2.1.5, micromatch@^2.3.11: version "2.3.11" resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-2.3.11.tgz#86677c97d1720b363431d04d0d15293bd38c1565" @@ -3819,17 +5065,21 @@ miller-rabin@^4.0.0: bn.js "^4.0.0" brorand "^1.0.1" +"mime-db@>= 1.30.0 < 2": + version "1.31.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.31.0.tgz#a49cd8f3ebf3ed1a482b60561d9105ad40ca74cb" + mime-db@~1.30.0: version "1.30.0" resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.30.0.tgz#74c643da2dd9d6a45399963465b26d5ca7d71f01" -mime-types@^2.1.12, mime-types@~2.1.17, mime-types@~2.1.7: +mime-types@^2.1.12, mime-types@~2.1.15, mime-types@~2.1.16, mime-types@~2.1.17, mime-types@~2.1.7: version "2.1.17" resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.17.tgz#09d7a393f03e995a79f8af857b70a9e0ab16557a" dependencies: mime-db "~1.30.0" -mime@^1.3.4: +mime@1.4.1, mime@^1.2.11, mime@^1.3.4: version "1.4.1" resolved "https://registry.yarnpkg.com/mime/-/mime-1.4.1.tgz#121f9ebc49e3766f311a76e1fa1c8003c4b03aa6" @@ -3876,7 +5126,7 @@ mixin-deep@^1.2.0: for-in "^1.0.2" is-extendable "^0.1.1" -mkdirp@0.5.1, "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0: +mkdirp@0.5.1, mkdirp@0.5.x, "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0, mkdirp@~0.5.1: version "0.5.1" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" dependencies: @@ -3897,18 +5147,39 @@ mocha@^4.0.0, mocha@^4.0.1: mkdirp "0.5.1" supports-color "4.4.0" +modify-babel-preset@^1.0.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/modify-babel-preset/-/modify-babel-preset-1.2.0.tgz#d1b7c8c24896e19dbc4847347213e6b7144d1bc7" + dependencies: + require-relative "^0.8.7" + modify-values@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/modify-values/-/modify-values-1.0.0.tgz#e2b6cdeb9ce19f99317a53722f3dbf5df5eaaab2" -moment@^2.6.0: +moment@*, moment@^2.18.1, moment@^2.6.0: version "2.19.2" resolved "https://registry.yarnpkg.com/moment/-/moment-2.19.2.tgz#8a7f774c95a64550b4c7ebd496683908f9419dbe" +ms@0.7.1: + version "0.7.1" + resolved "https://registry.yarnpkg.com/ms/-/ms-0.7.1.tgz#9cd13c03adbff25b65effde7ce864ee952017098" + ms@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" +multicast-dns-service-types@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/multicast-dns-service-types/-/multicast-dns-service-types-1.1.0.tgz#899f11d9686e5e05cb91b35d5f0e63b773cfc901" + +multicast-dns@^6.0.1: + version "6.2.0" + resolved "https://registry.yarnpkg.com/multicast-dns/-/multicast-dns-6.2.0.tgz#13f22d0c32dc5ee82a32878e3c380d875b3eab22" + dependencies: + dns-packet "^1.0.1" + thunky "^0.1.0" + mute-stream@0.0.4: version "0.0.4" resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.4.tgz#a9219960a6d5d5d046597aee51252c6655f7177e" @@ -3921,6 +5192,10 @@ nan@^2.0.5, nan@^2.0.8, nan@^2.2.1, nan@^2.3.0, nan@^2.3.3: version "2.7.0" resolved "https://registry.yarnpkg.com/nan/-/nan-2.7.0.tgz#d95bf721ec877e08db276ed3fc6eb78f9083ad46" +nan@^2.4.0: + version "2.8.0" + resolved "https://registry.yarnpkg.com/nan/-/nan-2.8.0.tgz#ed715f3fe9de02b57a5e6252d90a96675e1f085a" + nanomatch@^1.2.5: version "1.2.5" resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.5.tgz#5c9ab02475c76676275731b0bf0a7395c624a9c4" @@ -3937,6 +5212,10 @@ nanomatch@^1.2.5: snapdragon "^0.8.1" to-regex "^3.0.1" +negotiator@0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.1.tgz#2b327184e8992101177b28563fb5e7102acd0ca9" + nise@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/nise/-/nise-1.2.0.tgz#079d6cadbbcb12ba30e38f1c999f36ad4d6baa53" @@ -3953,6 +5232,12 @@ node-abi@^2.1.1: dependencies: semver "^5.4.1" +node-dir@^0.1.10: + version "0.1.17" + resolved "https://registry.yarnpkg.com/node-dir/-/node-dir-0.1.17.tgz#5f5665d93351335caabef8f1c554516cf5f1e4e5" + dependencies: + minimatch "^3.0.2" + node-fetch@^1.0.1, node-fetch@^1.3.3, node-fetch@~1.7.1: version "1.7.3" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-1.7.3.tgz#980f6f72d85211a5347c6b2bc18c5b84c3eb47ef" @@ -3960,6 +5245,17 @@ node-fetch@^1.0.1, node-fetch@^1.3.3, node-fetch@~1.7.1: encoding "^0.1.11" is-stream "^1.0.1" +node-forge@0.6.33: + version "0.6.33" + resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.6.33.tgz#463811879f573d45155ad6a9f43dc296e8e85ebc" + +node-hid@0.5.4: + version "0.5.4" + resolved "https://registry.yarnpkg.com/node-hid/-/node-hid-0.5.4.tgz#a7246dfc08d52774147fa264354d5da6eab40253" + dependencies: + nan "^2.4.0" + node-pre-gyp "0.6.31" + node-libs-browser@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/node-libs-browser/-/node-libs-browser-2.0.0.tgz#a3a59ec97024985b46e958379646f96c4b616646" @@ -3988,6 +5284,20 @@ node-libs-browser@^2.0.0: util "^0.10.3" vm-browserify "0.0.4" +node-pre-gyp@0.6.31: + version "0.6.31" + resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.6.31.tgz#d8a00ddaa301a940615dbcc8caad4024d58f6017" + dependencies: + mkdirp "~0.5.1" + nopt "~3.0.6" + npmlog "^4.0.0" + rc "~1.1.6" + request "^2.75.0" + rimraf "~2.5.4" + semver "~5.3.0" + tar "~2.2.1" + tar-pack "~3.3.0" + node-pre-gyp@^0.6.39: version "0.6.39" resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.6.39.tgz#c00e96860b23c0e1420ac7befc5044e1d78d8649" @@ -4022,6 +5332,12 @@ nopt@^4.0.1: abbrev "1" osenv "^0.1.4" +nopt@~3.0.6: + version "3.0.6" + resolved "https://registry.yarnpkg.com/nopt/-/nopt-3.0.6.tgz#c6465dbf08abcd4db359317f79ac68a646b28ff9" + dependencies: + abbrev "1" + normalize-package-data@^2.3.0, normalize-package-data@^2.3.2, normalize-package-data@^2.3.4, normalize-package-data@^2.3.5: version "2.4.0" resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.4.0.tgz#12f95a307d58352075a04907b84ac8be98ac012f" @@ -4037,6 +5353,19 @@ normalize-path@^2.0.0, normalize-path@^2.0.1: dependencies: remove-trailing-separator "^1.0.1" +normalize-range@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/normalize-range/-/normalize-range-0.1.2.tgz#2d10c06bdfd312ea9777695a4d28439456b75942" + +normalize-url@^1.4.0: + version "1.9.1" + resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-1.9.1.tgz#2cc0d66b31ea23036458436e3620d85954c66c3c" + dependencies: + object-assign "^4.0.1" + prepend-http "^1.0.0" + query-string "^4.1.0" + sort-keys "^1.0.0" + npm-run-all@^4.0.2, npm-run-all@^4.1.1: version "4.1.2" resolved "https://registry.yarnpkg.com/npm-run-all/-/npm-run-all-4.1.2.tgz#90d62d078792d20669139e718621186656cea056" @@ -4057,7 +5386,7 @@ npm-run-path@^2.0.0: dependencies: path-key "^2.0.0" -npmlog@^4.0.1, npmlog@^4.0.2, npmlog@^4.1.2: +npmlog@^4.0.0, npmlog@^4.0.1, npmlog@^4.0.2, npmlog@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b" dependencies: @@ -4066,10 +5395,21 @@ npmlog@^4.0.1, npmlog@^4.0.2, npmlog@^4.1.2: gauge "~2.7.3" set-blocking "~2.0.0" +num2fraction@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/num2fraction/-/num2fraction-1.2.2.tgz#6f682b6a027a4e9ddfa4564cd2589d1d4e669ede" + number-is-nan@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d" +number-to-bn@1.7.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/number-to-bn/-/number-to-bn-1.7.0.tgz#bb3623592f7e5f9e0030b1977bd41a0c53fe1ea0" + dependencies: + bn.js "4.11.6" + strip-hex-prefix "1.0.0" + nyc@^11.0.1: version "11.3.0" resolved "https://registry.yarnpkg.com/nyc/-/nyc-11.3.0.tgz#a42bc17b3cfa41f7b15eb602bc98b2633ddd76f0" @@ -4149,12 +5489,32 @@ object.pick@^1.3.0: dependencies: isobject "^3.0.1" -once@^1.3.0, once@^1.3.1, once@^1.3.3, once@^1.4.0: +obuf@^1.0.0, obuf@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/obuf/-/obuf-1.1.1.tgz#104124b6c602c6796881a042541d36db43a5264e" + +on-finished@~2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947" + dependencies: + ee-first "1.1.1" + +on-headers@~1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/on-headers/-/on-headers-1.0.1.tgz#928f5d0f470d49342651ea6794b0857c100693f7" + +once@^1.3.0, once@^1.3.1, once@^1.3.3, once@^1.4, once@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" dependencies: wrappy "1" +once@~1.3.3: + version "1.3.3" + resolved "https://registry.yarnpkg.com/once/-/once-1.3.3.tgz#b2e261557ce4c314ec8304f3fa82663e4297ca20" + dependencies: + wrappy "1" + onetime@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/onetime/-/onetime-2.0.1.tgz#067428230fd67443b2794b22bba528b6867962d4" @@ -4178,6 +5538,12 @@ opn@^4.0.0: object-assign "^4.0.1" pinkie-promise "^2.0.0" +opn@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/opn/-/opn-5.1.0.tgz#72ce2306a17dbea58ff1041853352b4a8fc77519" + dependencies: + is-wsl "^1.1.0" + optimist@^0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/optimist/-/optimist-0.6.1.tgz#da3ea74686fa21a19a111c326e90eb15a0196686" @@ -4185,6 +5551,12 @@ optimist@^0.6.1: minimist "~0.0.1" wordwrap "~0.0.2" +original@>=0.0.5: + version "1.0.0" + resolved "https://registry.yarnpkg.com/original/-/original-1.0.0.tgz#9147f93fa1696d04be61e01bd50baeaca656bd3b" + dependencies: + url-parse "1.0.x" + os-browserify@^0.2.0: version "0.2.1" resolved "https://registry.yarnpkg.com/os-browserify/-/os-browserify-0.2.1.tgz#63fc4ccee5d2d7763d26bbf8601078e6c2e0044f" @@ -4232,6 +5604,10 @@ p-locate@^2.0.0: dependencies: p-limit "^1.1.0" +p-map@^1.1.1: + version "1.2.0" + resolved "https://registry.yarnpkg.com/p-map/-/p-map-1.2.0.tgz#e4e94f311eabbc8633a1e79908165fca26241b6b" + package-json@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/package-json/-/package-json-4.0.1.tgz#8869a0401253661c4c4ca3da6c2121ed555f5eed" @@ -4294,6 +5670,10 @@ parse-json@^4.0.0: error-ex "^1.3.1" json-parse-better-errors "^1.0.1" +parseurl@~1.3.2: + version "1.3.2" + resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.2.tgz#fc289d4ed8993119460c156253262cdc8de65bf3" + pascalcase@^0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/pascalcase/-/pascalcase-0.1.1.tgz#b363e55e8006ca6fe21784d2db22bd15d7917f14" @@ -4306,6 +5686,10 @@ path-dirname@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/path-dirname/-/path-dirname-1.0.2.tgz#cc33d24d525e099a5388c0336c6e32b9160609e0" +path-exists@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-1.0.0.tgz#d5a8998eb71ef37a74c34eb0d9eba6e878eea081" + path-exists@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-2.1.0.tgz#0feb6c64f0fc518d9a754dd5efb62c7022761f4b" @@ -4320,6 +5704,10 @@ path-is-absolute@^1.0.0, path-is-absolute@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" +path-is-inside@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/path-is-inside/-/path-is-inside-1.0.2.tgz#365417dede44430d1c11af61027facf074bdfc53" + path-key@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" @@ -4328,6 +5716,10 @@ path-parse@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.5.tgz#3c1adf871ea9cd6c9431b6ea2bd74a0ff055c4c1" +path-to-regexp@0.1.7: + version "0.1.7" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" + path-to-regexp@^1.7.0: version "1.7.0" resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-1.7.0.tgz#59fde0f435badacba103a84e9d3bc64e96b9937d" @@ -4406,14 +5798,274 @@ pkg-dir@^1.0.0: dependencies: find-up "^1.0.0" +pkg-dir@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-2.0.0.tgz#f6d5d1109e19d63edf428e0bd57e12777615334b" + dependencies: + find-up "^2.1.0" + pkginfo@^0.3.0: version "0.3.1" resolved "https://registry.yarnpkg.com/pkginfo/-/pkginfo-0.3.1.tgz#5b29f6a81f70717142e09e765bbeab97b4f81e21" +portfinder@^1.0.9: + version "1.0.13" + resolved "https://registry.yarnpkg.com/portfinder/-/portfinder-1.0.13.tgz#bb32ecd87c27104ae6ee44b5a3ccbf0ebb1aede9" + dependencies: + async "^1.5.2" + debug "^2.2.0" + mkdirp "0.5.x" + posix-character-classes@^0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab" +postcss-calc@^5.2.0: + version "5.3.1" + resolved "https://registry.yarnpkg.com/postcss-calc/-/postcss-calc-5.3.1.tgz#77bae7ca928ad85716e2fda42f261bf7c1d65b5e" + dependencies: + postcss "^5.0.2" + postcss-message-helpers "^2.0.0" + reduce-css-calc "^1.2.6" + +postcss-colormin@^2.1.8: + version "2.2.2" + resolved "https://registry.yarnpkg.com/postcss-colormin/-/postcss-colormin-2.2.2.tgz#6631417d5f0e909a3d7ec26b24c8a8d1e4f96e4b" + dependencies: + colormin "^1.0.5" + postcss "^5.0.13" + postcss-value-parser "^3.2.3" + +postcss-convert-values@^2.3.4: + version "2.6.1" + resolved "https://registry.yarnpkg.com/postcss-convert-values/-/postcss-convert-values-2.6.1.tgz#bbd8593c5c1fd2e3d1c322bb925dcae8dae4d62d" + dependencies: + postcss "^5.0.11" + postcss-value-parser "^3.1.2" + +postcss-discard-comments@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/postcss-discard-comments/-/postcss-discard-comments-2.0.4.tgz#befe89fafd5b3dace5ccce51b76b81514be00e3d" + dependencies: + postcss "^5.0.14" + +postcss-discard-duplicates@^2.0.1: + version "2.1.0" + resolved "https://registry.yarnpkg.com/postcss-discard-duplicates/-/postcss-discard-duplicates-2.1.0.tgz#b9abf27b88ac188158a5eb12abcae20263b91932" + dependencies: + postcss "^5.0.4" + +postcss-discard-empty@^2.0.1: + version "2.1.0" + resolved "https://registry.yarnpkg.com/postcss-discard-empty/-/postcss-discard-empty-2.1.0.tgz#d2b4bd9d5ced5ebd8dcade7640c7d7cd7f4f92b5" + dependencies: + postcss "^5.0.14" + +postcss-discard-overridden@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/postcss-discard-overridden/-/postcss-discard-overridden-0.1.1.tgz#8b1eaf554f686fb288cd874c55667b0aa3668d58" + dependencies: + postcss "^5.0.16" + +postcss-discard-unused@^2.2.1: + version "2.2.3" + resolved "https://registry.yarnpkg.com/postcss-discard-unused/-/postcss-discard-unused-2.2.3.tgz#bce30b2cc591ffc634322b5fb3464b6d934f4433" + dependencies: + postcss "^5.0.14" + uniqs "^2.0.0" + +postcss-filter-plugins@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/postcss-filter-plugins/-/postcss-filter-plugins-2.0.2.tgz#6d85862534d735ac420e4a85806e1f5d4286d84c" + dependencies: + postcss "^5.0.4" + uniqid "^4.0.0" + +postcss-merge-idents@^2.1.5: + version "2.1.7" + resolved "https://registry.yarnpkg.com/postcss-merge-idents/-/postcss-merge-idents-2.1.7.tgz#4c5530313c08e1d5b3bbf3d2bbc747e278eea270" + dependencies: + has "^1.0.1" + postcss "^5.0.10" + postcss-value-parser "^3.1.1" + +postcss-merge-longhand@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/postcss-merge-longhand/-/postcss-merge-longhand-2.0.2.tgz#23d90cd127b0a77994915332739034a1a4f3d658" + dependencies: + postcss "^5.0.4" + +postcss-merge-rules@^2.0.3: + version "2.1.2" + resolved "https://registry.yarnpkg.com/postcss-merge-rules/-/postcss-merge-rules-2.1.2.tgz#d1df5dfaa7b1acc3be553f0e9e10e87c61b5f721" + dependencies: + browserslist "^1.5.2" + caniuse-api "^1.5.2" + postcss "^5.0.4" + postcss-selector-parser "^2.2.2" + vendors "^1.0.0" + +postcss-message-helpers@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/postcss-message-helpers/-/postcss-message-helpers-2.0.0.tgz#a4f2f4fab6e4fe002f0aed000478cdf52f9ba60e" + +postcss-minify-font-values@^1.0.2: + version "1.0.5" + resolved "https://registry.yarnpkg.com/postcss-minify-font-values/-/postcss-minify-font-values-1.0.5.tgz#4b58edb56641eba7c8474ab3526cafd7bbdecb69" + dependencies: + object-assign "^4.0.1" + postcss "^5.0.4" + postcss-value-parser "^3.0.2" + +postcss-minify-gradients@^1.0.1: + version "1.0.5" + resolved "https://registry.yarnpkg.com/postcss-minify-gradients/-/postcss-minify-gradients-1.0.5.tgz#5dbda11373703f83cfb4a3ea3881d8d75ff5e6e1" + dependencies: + postcss "^5.0.12" + postcss-value-parser "^3.3.0" + +postcss-minify-params@^1.0.4: + version "1.2.2" + resolved "https://registry.yarnpkg.com/postcss-minify-params/-/postcss-minify-params-1.2.2.tgz#ad2ce071373b943b3d930a3fa59a358c28d6f1f3" + dependencies: + alphanum-sort "^1.0.1" + postcss "^5.0.2" + postcss-value-parser "^3.0.2" + uniqs "^2.0.0" + +postcss-minify-selectors@^2.0.4: + version "2.1.1" + resolved "https://registry.yarnpkg.com/postcss-minify-selectors/-/postcss-minify-selectors-2.1.1.tgz#b2c6a98c0072cf91b932d1a496508114311735bf" + dependencies: + alphanum-sort "^1.0.2" + has "^1.0.1" + postcss "^5.0.14" + postcss-selector-parser "^2.0.0" + +postcss-modules-extract-imports@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/postcss-modules-extract-imports/-/postcss-modules-extract-imports-1.1.0.tgz#b614c9720be6816eaee35fb3a5faa1dba6a05ddb" + dependencies: + postcss "^6.0.1" + +postcss-modules-local-by-default@^1.0.1: + version "1.2.0" + resolved "https://registry.yarnpkg.com/postcss-modules-local-by-default/-/postcss-modules-local-by-default-1.2.0.tgz#f7d80c398c5a393fa7964466bd19500a7d61c069" + dependencies: + css-selector-tokenizer "^0.7.0" + postcss "^6.0.1" + +postcss-modules-scope@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/postcss-modules-scope/-/postcss-modules-scope-1.1.0.tgz#d6ea64994c79f97b62a72b426fbe6056a194bb90" + dependencies: + css-selector-tokenizer "^0.7.0" + postcss "^6.0.1" + +postcss-modules-values@^1.1.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/postcss-modules-values/-/postcss-modules-values-1.3.0.tgz#ecffa9d7e192518389f42ad0e83f72aec456ea20" + dependencies: + icss-replace-symbols "^1.1.0" + postcss "^6.0.1" + +postcss-normalize-charset@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/postcss-normalize-charset/-/postcss-normalize-charset-1.1.1.tgz#ef9ee71212d7fe759c78ed162f61ed62b5cb93f1" + dependencies: + postcss "^5.0.5" + +postcss-normalize-url@^3.0.7: + version "3.0.8" + resolved "https://registry.yarnpkg.com/postcss-normalize-url/-/postcss-normalize-url-3.0.8.tgz#108f74b3f2fcdaf891a2ffa3ea4592279fc78222" + dependencies: + is-absolute-url "^2.0.0" + normalize-url "^1.4.0" + postcss "^5.0.14" + postcss-value-parser "^3.2.3" + +postcss-ordered-values@^2.1.0: + version "2.2.3" + resolved "https://registry.yarnpkg.com/postcss-ordered-values/-/postcss-ordered-values-2.2.3.tgz#eec6c2a67b6c412a8db2042e77fe8da43f95c11d" + dependencies: + postcss "^5.0.4" + postcss-value-parser "^3.0.1" + +postcss-reduce-idents@^2.2.2: + version "2.4.0" + resolved "https://registry.yarnpkg.com/postcss-reduce-idents/-/postcss-reduce-idents-2.4.0.tgz#c2c6d20cc958284f6abfbe63f7609bf409059ad3" + dependencies: + postcss "^5.0.4" + postcss-value-parser "^3.0.2" + +postcss-reduce-initial@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/postcss-reduce-initial/-/postcss-reduce-initial-1.0.1.tgz#68f80695f045d08263a879ad240df8dd64f644ea" + dependencies: + postcss "^5.0.4" + +postcss-reduce-transforms@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/postcss-reduce-transforms/-/postcss-reduce-transforms-1.0.4.tgz#ff76f4d8212437b31c298a42d2e1444025771ae1" + dependencies: + has "^1.0.1" + postcss "^5.0.8" + postcss-value-parser "^3.0.1" + +postcss-selector-parser@^2.0.0, postcss-selector-parser@^2.2.2: + version "2.2.3" + resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-2.2.3.tgz#f9437788606c3c9acee16ffe8d8b16297f27bb90" + dependencies: + flatten "^1.0.2" + indexes-of "^1.0.1" + uniq "^1.0.1" + +postcss-svgo@^2.1.1: + version "2.1.6" + resolved "https://registry.yarnpkg.com/postcss-svgo/-/postcss-svgo-2.1.6.tgz#b6df18aa613b666e133f08adb5219c2684ac108d" + dependencies: + is-svg "^2.0.0" + postcss "^5.0.14" + postcss-value-parser "^3.2.3" + svgo "^0.7.0" + +postcss-unique-selectors@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/postcss-unique-selectors/-/postcss-unique-selectors-2.0.2.tgz#981d57d29ddcb33e7b1dfe1fd43b8649f933ca1d" + dependencies: + alphanum-sort "^1.0.1" + postcss "^5.0.4" + uniqs "^2.0.0" + +postcss-value-parser@^3.0.1, postcss-value-parser@^3.0.2, postcss-value-parser@^3.1.1, postcss-value-parser@^3.1.2, postcss-value-parser@^3.2.3, postcss-value-parser@^3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-3.3.0.tgz#87f38f9f18f774a4ab4c8a232f5c5ce8872a9d15" + +postcss-zindex@^2.0.1: + version "2.2.0" + resolved "https://registry.yarnpkg.com/postcss-zindex/-/postcss-zindex-2.2.0.tgz#d2109ddc055b91af67fc4cb3b025946639d2af22" + dependencies: + has "^1.0.1" + postcss "^5.0.4" + uniqs "^2.0.0" + +postcss@^5.0.10, postcss@^5.0.11, postcss@^5.0.12, postcss@^5.0.13, postcss@^5.0.14, postcss@^5.0.16, postcss@^5.0.2, postcss@^5.0.4, postcss@^5.0.5, postcss@^5.0.6, postcss@^5.0.8, postcss@^5.2.16: + version "5.2.18" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-5.2.18.tgz#badfa1497d46244f6390f58b319830d9107853c5" + dependencies: + chalk "^1.1.3" + js-base64 "^2.1.9" + source-map "^0.5.6" + supports-color "^3.2.3" + +postcss@^6.0.1: + version "6.0.14" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-6.0.14.tgz#5534c72114739e75d0afcf017db853099f562885" + dependencies: + chalk "^2.3.0" + source-map "^0.6.1" + supports-color "^4.4.0" + prebuild-install@^2.0.0: version "2.3.0" resolved "https://registry.yarnpkg.com/prebuild-install/-/prebuild-install-2.3.0.tgz#19481247df728b854ab57b187ce234211311b485" @@ -4433,7 +6085,7 @@ prebuild-install@^2.0.0: tunnel-agent "^0.6.0" xtend "4.0.1" -prepend-http@^1.0.1: +prepend-http@^1.0.0, prepend-http@^1.0.1: version "1.0.4" resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-1.0.4.tgz#d4f4562b0ce3696e41ac52d0e002e57a635dc6dc" @@ -4464,6 +6116,10 @@ process@~0.5.1: version "0.5.2" resolved "https://registry.yarnpkg.com/process/-/process-0.5.2.tgz#1638d8a8e34c2f440a91db95ab9aeb677fc185cf" +progress-checkmark@^0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/progress-checkmark/-/progress-checkmark-0.0.1.tgz#a43248dfc1de42d6f235a06a00c168cdbe99efc8" + progress-stream@^1.0.1: version "1.2.0" resolved "https://registry.yarnpkg.com/progress-stream/-/progress-stream-1.2.0.tgz#2cd3cfea33ba3a89c9c121ec3347abe9ab125f77" @@ -4482,6 +6138,27 @@ promise-to-callback@^1.0.0: is-fn "^1.0.0" set-immediate-shim "^1.0.1" +promise@^7.1.1: + version "7.3.1" + resolved "https://registry.yarnpkg.com/promise/-/promise-7.3.1.tgz#064b72602b18f90f29192b8b1bc418ffd1ebd3bf" + dependencies: + asap "~2.0.3" + +prop-types@^15.0.0, prop-types@^15.5.1, prop-types@^15.5.10, prop-types@^15.5.4, prop-types@^15.5.6, prop-types@^15.5.7, prop-types@^15.5.8, prop-types@^15.6.0: + version "15.6.0" + resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.6.0.tgz#ceaf083022fc46b4a35f69e13ef75aed0d639856" + dependencies: + fbjs "^0.8.16" + loose-envify "^1.3.1" + object-assign "^4.1.1" + +proxy-addr@~2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.2.tgz#6571504f47bb988ec8180253f85dd7e14952bdec" + dependencies: + forwarded "~0.1.2" + ipaddr.js "1.5.2" + prr@~0.0.0: version "0.0.0" resolved "https://registry.yarnpkg.com/prr/-/prr-0.0.0.tgz#1a84b85908325501411853d0081ee3fa86e2926a" @@ -4543,19 +6220,30 @@ punycode@^1.2.4, punycode@^1.4.1: version "1.4.1" resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" -q@^1.4.1: +q@1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/q/-/q-1.4.1.tgz#55705bcd93c5f3673530c2c2cbc0c2b3addc286e" + +q@^1.1.2, q@^1.4.1: version "1.5.1" resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7" +qs@6.5.1, qs@~6.5.1: + version "6.5.1" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.1.tgz#349cdf6eef89ec45c12d7d5eb3fc0c870343a6d8" + qs@~6.4.0: version "6.4.0" resolved "https://registry.yarnpkg.com/qs/-/qs-6.4.0.tgz#13e26d28ad6b0ffaa91312cd3bf708ed351e7233" -qs@~6.5.1: - version "6.5.1" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.1.tgz#349cdf6eef89ec45c12d7d5eb3fc0c870343a6d8" +query-string@^4.1.0: + version "4.3.4" + resolved "https://registry.yarnpkg.com/query-string/-/query-string-4.3.4.tgz#bbb693b9ca915c232515b228b1a02b609043dbeb" + dependencies: + object-assign "^4.1.0" + strict-uri-encode "^1.0.0" -query-string@^5.0.1: +query-string@^5.0.0, query-string@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/query-string/-/query-string-5.0.1.tgz#6e2b86fe0e08aef682ecbe86e85834765402bd88" dependencies: @@ -4571,6 +6259,14 @@ querystring@0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620" +querystringify@0.0.x: + version "0.0.4" + resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-0.0.4.tgz#0cf7f84f9463ff0ae51c4c4b142d95be37724d9c" + +querystringify@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-1.0.0.tgz#6286242112c5b712fa654e526652bf6a13ff05cb" + randomatic@^1.1.3: version "1.1.7" resolved "https://registry.yarnpkg.com/randomatic/-/randomatic-1.1.7.tgz#c7abe9cc8b87c0baa876b19fde83fd464797e38c" @@ -4591,6 +6287,23 @@ randomfill@^1.0.3: randombytes "^2.0.5" safe-buffer "^5.1.0" +range-parser@^1.0.3, range-parser@~1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.0.tgz#f49be6b487894ddc40dcc94a322f611092e00d5e" + +raw-body@2.3.2: + version "2.3.2" + resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.3.2.tgz#bcd60c77d3eb93cde0050295c3f379389bc88f89" + dependencies: + bytes "3.0.0" + http-errors "1.6.2" + iconv-lite "0.4.19" + unpipe "1.0.0" + +raw-loader@^0.5.1: + version "0.5.1" + resolved "https://registry.yarnpkg.com/raw-loader/-/raw-loader-0.5.1.tgz#0c3d0beaed8a01c966d9787bf778281252a979aa" + rc@^1.0.1, rc@^1.1.6, rc@^1.1.7: version "1.2.2" resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.2.tgz#d8ce9cb57e8d64d9c7badd9876c7c34cbe3c7077" @@ -4600,6 +6313,222 @@ rc@^1.0.1, rc@^1.1.6, rc@^1.1.7: minimist "^1.2.0" strip-json-comments "~2.0.1" +rc@~1.1.6: + version "1.1.7" + resolved "https://registry.yarnpkg.com/rc/-/rc-1.1.7.tgz#c5ea564bb07aff9fd3a5b32e906c1d3a65940fea" + dependencies: + deep-extend "~0.4.0" + ini "~1.3.0" + minimist "^1.2.0" + strip-json-comments "~2.0.1" + +react-addons-create-fragment@^15.4.0: + version "15.6.2" + resolved "https://registry.yarnpkg.com/react-addons-create-fragment/-/react-addons-create-fragment-15.6.2.tgz#a394de7c2c7becd6b5475ba1b97ac472ce7c74f8" + dependencies: + fbjs "^0.8.4" + loose-envify "^1.3.1" + object-assign "^4.1.0" + +react-addons-transition-group@^15.4.0: + version "15.6.2" + resolved "https://registry.yarnpkg.com/react-addons-transition-group/-/react-addons-transition-group-15.6.2.tgz#8baebc2ae91ccdbf245fe29c9fd3d36f8b471923" + dependencies: + react-transition-group "^1.2.0" + +react-copy-to-clipboard@^4.2.3: + version "4.3.1" + resolved "https://registry.yarnpkg.com/react-copy-to-clipboard/-/react-copy-to-clipboard-4.3.1.tgz#aa429ce6029077c987e2bc4af7eec9a09ba5075b" + dependencies: + copy-to-clipboard "^3" + create-react-class "^15.5.2" + prop-types "^15.5.8" + +react-document-title@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/react-document-title/-/react-document-title-2.0.3.tgz#bbf922a0d71412fc948245e4283b2412df70f2b9" + dependencies: + prop-types "^15.5.6" + react-side-effect "^1.0.2" + +react-dom@15.6.1: + version "15.6.1" + resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-15.6.1.tgz#2cb0ed4191038e53c209eb3a79a23e2a4cf99470" + dependencies: + fbjs "^0.8.9" + loose-envify "^1.1.0" + object-assign "^4.1.0" + prop-types "^15.5.10" + +react-dom@^15.5.4: + version "15.6.2" + resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-15.6.2.tgz#41cfadf693b757faf2708443a1d1fd5a02bef730" + dependencies: + fbjs "^0.8.9" + loose-envify "^1.1.0" + object-assign "^4.1.0" + prop-types "^15.5.10" + +react-event-listener@^0.4.5: + version "0.4.5" + resolved "https://registry.yarnpkg.com/react-event-listener/-/react-event-listener-0.4.5.tgz#e3e895a0970cf14ee8f890113af68197abf3d0b1" + dependencies: + babel-runtime "^6.20.0" + fbjs "^0.8.4" + prop-types "^15.5.4" + warning "^3.0.0" + +react-highlight@^0.10.0: + version "0.10.0" + resolved "https://registry.yarnpkg.com/react-highlight/-/react-highlight-0.10.0.tgz#d386f9dceab867dc0dcc2364153fb1cc7645d046" + dependencies: + highlight.js "^9.11.0" + react "^15.5.4" + react-dom "^15.5.4" + +react-html5video@^2.1.0: + version "2.5.1" + resolved "https://registry.yarnpkg.com/react-html5video/-/react-html5video-2.5.1.tgz#bbc4314be1d583cb991a11ca3480e70a3c964caf" + +react-inlinesvg@^0.5.5: + version "0.5.5" + resolved "https://registry.yarnpkg.com/react-inlinesvg/-/react-inlinesvg-0.5.5.tgz#811c75decd392eaef682346751418c2fd911af42" + dependencies: + fbjs "^0.8" + httpplease "^0.16" + once "^1.4" + +react-markdown@^2.5.0: + version "2.5.1" + resolved "https://registry.yarnpkg.com/react-markdown/-/react-markdown-2.5.1.tgz#f7a6c26a3a5faf5d4c2098155d9775e826fd56ee" + dependencies: + commonmark "^0.24.0" + commonmark-react-renderer "^4.3.4" + prop-types "^15.5.1" + +react-recaptcha@^2.3.2: + version "2.3.5" + resolved "https://registry.yarnpkg.com/react-recaptcha/-/react-recaptcha-2.3.5.tgz#a5db337125bb00fb13c2fa2e4ebfbe8b0cd06bb7" + +react-redux@^5.0.3: + version "5.0.6" + resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-5.0.6.tgz#23ed3a4f986359d68b5212eaaa681e60d6574946" + dependencies: + hoist-non-react-statics "^2.2.1" + invariant "^2.0.0" + lodash "^4.2.0" + lodash-es "^4.2.0" + loose-envify "^1.1.0" + prop-types "^15.5.10" + +react-router-dom@^4.1.1: + version "4.2.2" + resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-4.2.2.tgz#c8a81df3adc58bba8a76782e946cbd4eae649b8d" + dependencies: + history "^4.7.2" + invariant "^2.2.2" + loose-envify "^1.3.1" + prop-types "^15.5.4" + react-router "^4.2.0" + warning "^3.0.0" + +react-router-hash-link@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/react-router-hash-link/-/react-router-hash-link-1.1.1.tgz#7380cc7a8809243d94385924af52dc9d2c9fbaa1" + dependencies: + prop-types "^15.6.0" + +react-router@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/react-router/-/react-router-4.2.0.tgz#61f7b3e3770daeb24062dae3eedef1b054155986" + dependencies: + history "^4.7.2" + hoist-non-react-statics "^2.3.0" + invariant "^2.2.2" + loose-envify "^1.3.1" + path-to-regexp "^1.7.0" + prop-types "^15.5.4" + warning "^3.0.0" + +react-scroll@^1.5.2: + version "1.6.4" + resolved "https://registry.yarnpkg.com/react-scroll/-/react-scroll-1.6.4.tgz#c5bca461771bce460a1bfb19550350512b9d076b" + dependencies: + object-assign "^4.1.1" + prop-types "^15.5.8" + +react-side-effect@^1.0.2: + version "1.1.3" + resolved "https://registry.yarnpkg.com/react-side-effect/-/react-side-effect-1.1.3.tgz#512c25abe0dec172834c4001ec5c51e04d41bc5c" + dependencies: + exenv "^1.2.1" + shallowequal "^1.0.1" + +react-svg-loader@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/react-svg-loader/-/react-svg-loader-1.1.1.tgz#ac9ffa098b620f26581a03594505244346c00d76" + dependencies: + babel-core "~6.13.2" + babel-plugin-syntax-jsx "~6.13.0" + babel-preset-es2015 "~6.13.2" + babel-preset-es2015-loose "~7.0.0" + babel-preset-react "~6.11.0" + js-yaml "~3.6.0" + loader-utils "~0.2.13" + lodash.isplainobject "~4.0.4" + svgo "~0.6.3" + yargs "~4.8.0" + +react-tap-event-plugin@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/react-tap-event-plugin/-/react-tap-event-plugin-2.0.1.tgz#316beb3bc6556e29ec869a7293e89c826a9074d2" + dependencies: + fbjs "^0.8.6" + +react-tooltip@^3.2.7: + version "3.4.0" + resolved "https://registry.yarnpkg.com/react-tooltip/-/react-tooltip-3.4.0.tgz#037f38f797c3e6b1b58d2534ccc8c2c76af4f52d" + dependencies: + classnames "^2.2.5" + prop-types "^15.6.0" + +react-transition-group@^1.2.0: + version "1.2.1" + resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-1.2.1.tgz#e11f72b257f921b213229a774df46612346c7ca6" + dependencies: + chain-function "^1.0.0" + dom-helpers "^3.2.0" + loose-envify "^1.3.1" + prop-types "^15.5.6" + warning "^3.0.0" + +react-waypoint@^7.0.4: + version "7.3.1" + resolved "https://registry.yarnpkg.com/react-waypoint/-/react-waypoint-7.3.1.tgz#abb165d9b6c9590f8d82ceafbe61c2c887262a37" + dependencies: + consolidated-events "^1.1.0" + prop-types "^15.0.0" + +react@15.6.1: + version "15.6.1" + resolved "https://registry.yarnpkg.com/react/-/react-15.6.1.tgz#baa8434ec6780bde997cdc380b79cd33b96393df" + dependencies: + create-react-class "^15.6.0" + fbjs "^0.8.9" + loose-envify "^1.1.0" + object-assign "^4.1.0" + prop-types "^15.5.10" + +react@^15.5.4: + version "15.6.2" + resolved "https://registry.yarnpkg.com/react/-/react-15.6.2.tgz#dba0434ab439cfe82f108f0f511663908179aa72" + dependencies: + create-react-class "^15.6.0" + fbjs "^0.8.9" + loose-envify "^1.1.0" + object-assign "^4.1.0" + prop-types "^15.5.10" + read-cmd-shim@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/read-cmd-shim/-/read-cmd-shim-1.0.1.tgz#2d5d157786a37c055d22077c32c53f8329e91c7b" @@ -4680,6 +6609,18 @@ readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable string_decoder "~1.0.3" util-deprecate "~1.0.1" +readable-stream@~2.1.4: + version "2.1.5" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.1.5.tgz#66fa8b720e1438b364681f2ad1a63c618448c9d0" + dependencies: + buffer-shims "^1.0.0" + core-util-is "~1.0.0" + inherits "~2.0.1" + isarray "~1.0.0" + process-nextick-args "~1.0.6" + string_decoder "~0.10.x" + util-deprecate "~1.0.1" + readdirp@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-2.1.0.tgz#4ed0ad060df3073300c48440373f72d1cc642d78" @@ -4702,6 +6643,15 @@ rechoir@^0.6.2: dependencies: resolve "^1.1.6" +recompose@^0.23.0: + version "0.23.5" + resolved "https://registry.yarnpkg.com/recompose/-/recompose-0.23.5.tgz#72ac8261246bec378235d187467d02a721e8b1de" + dependencies: + change-emitter "^0.1.2" + fbjs "^0.8.1" + hoist-non-react-statics "^1.0.0" + symbol-observable "^1.0.4" + redent@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/redent/-/redent-1.0.0.tgz#cf916ab1fd5f1f16dfb20822dd6ec7f730c2afde" @@ -4709,6 +6659,29 @@ redent@^1.0.0: indent-string "^2.1.0" strip-indent "^1.0.1" +reduce-css-calc@^1.2.6: + version "1.3.0" + resolved "https://registry.yarnpkg.com/reduce-css-calc/-/reduce-css-calc-1.3.0.tgz#747c914e049614a4c9cfbba629871ad1d2927716" + dependencies: + balanced-match "^0.4.2" + math-expression-evaluator "^1.2.14" + reduce-function-call "^1.0.1" + +reduce-function-call@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/reduce-function-call/-/reduce-function-call-1.0.2.tgz#5a200bf92e0e37751752fe45b0ab330fd4b6be99" + dependencies: + balanced-match "^0.4.2" + +redux@*, redux@^3.6.0: + version "3.7.2" + resolved "https://registry.yarnpkg.com/redux/-/redux-3.7.2.tgz#06b73123215901d25d065be342eb026bc1c8537b" + dependencies: + lodash "^4.2.1" + lodash-es "^4.2.1" + loose-envify "^1.1.0" + symbol-observable "^1.0.3" + regenerate@^1.2.1: version "1.3.3" resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.3.3.tgz#0c336d3980553d755c39b586ae3b20aa49c82b7f" @@ -4737,6 +6710,14 @@ regex-not@^1.0.0: dependencies: extend-shallow "^2.0.1" +regexpu-core@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-1.0.0.tgz#86a763f58ee4d7c2f6b102e4764050de7ed90c6b" + dependencies: + regenerate "^1.2.1" + regjsgen "^0.2.0" + regjsparser "^0.1.4" + regexpu-core@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-2.0.0.tgz#49d038837b8dcf8bfa5b9a42139938e6ea2ae240" @@ -4827,7 +6808,7 @@ request@2.81.0: tunnel-agent "^0.6.0" uuid "^3.0.0" -request@^2.54.0, request@^2.67.0, request@^2.79.0, request@^2.81.0: +request@^2.54.0, request@^2.67.0, request@^2.75.0, request@^2.79.0, request@^2.81.0: version "2.83.0" resolved "https://registry.yarnpkg.com/request/-/request-2.83.0.tgz#ca0b65da02ed62935887808e6f510381034e3356" dependencies: @@ -4866,10 +6847,32 @@ require-main-filename@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-1.0.1.tgz#97f717b69d48784f5f526a6c5aa8ffdda055a4d1" +require-relative@^0.8.7: + version "0.8.7" + resolved "https://registry.yarnpkg.com/require-relative/-/require-relative-0.8.7.tgz#7999539fc9e047a37928fa196f8e1563dabd36de" + +requires-port@1.0.x, requires-port@1.x.x, requires-port@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" + +resolve-cwd@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-2.0.0.tgz#00a9f7387556e27038eae232caa372a6a59b665a" + dependencies: + resolve-from "^3.0.0" + resolve-from@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-2.0.0.tgz#9480ab20e94ffa1d9e80a804c7ea147611966b57" +resolve-from@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-3.0.0.tgz#b22c7af7d9d6881bc8b6e653335eebcb0a188748" + +resolve-pathname@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/resolve-pathname/-/resolve-pathname-2.2.0.tgz#7e9ae21ed815fd63ab189adeee64dc831eefa879" + resolve-url@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" @@ -4905,6 +6908,12 @@ rimraf@2, rimraf@^2.2.8, rimraf@^2.3.3, rimraf@^2.5.1, rimraf@^2.5.4, rimraf@^2. dependencies: glob "^7.0.5" +rimraf@~2.5.1, rimraf@~2.5.4: + version "2.5.4" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.5.4.tgz#96800093cbf1a0c86bd95b4625467535c29dfa04" + dependencies: + glob "^7.0.5" + ripemd160@^2.0.0, ripemd160@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/ripemd160/-/ripemd160-2.0.1.tgz#0f4584295c53a3628af7e6d79aca21ce57d1c6e7" @@ -4940,7 +6949,7 @@ rx@^2.4.3: version "2.5.3" resolved "https://registry.yarnpkg.com/rx/-/rx-2.5.3.tgz#21adc7d80f02002af50dae97fd9dbf248755f566" -safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@~5.1.0, safe-buffer@~5.1.1: +safe-buffer@5.1.1, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@~5.1.0, safe-buffer@~5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.1.tgz#893312af69b2123def71f57889001671eeb2c853" @@ -4948,6 +6957,23 @@ samsam@1.x: version "1.3.0" resolved "https://registry.yarnpkg.com/samsam/-/samsam-1.3.0.tgz#8d1d9350e25622da30de3e44ba692b5221ab7c50" +sax@^1.2.4, sax@~1.2.1: + version "1.2.4" + resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" + +scroll-to-element@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/scroll-to-element/-/scroll-to-element-2.0.0.tgz#3467330e3384743b7295ac64b30279990c5ac164" + dependencies: + scroll-to "0.0.2" + +scroll-to@0.0.2: + version "0.0.2" + resolved "https://registry.yarnpkg.com/scroll-to/-/scroll-to-0.0.2.tgz#936d398a9133660a2492145c2c0081dfcb0728f3" + dependencies: + component-raf "1.2.0" + component-tween "1.2.0" + scrypt.js@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/scrypt.js/-/scrypt.js-0.2.0.tgz#af8d1465b71e9990110bedfc593b9479e03a8ada" @@ -4981,6 +7007,16 @@ secp256k1@^3.0.1: prebuild-install "^2.0.0" safe-buffer "^5.1.0" +select-hose@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/select-hose/-/select-hose-2.0.0.tgz#625d8658f865af43ec962bfc376a37359a4994ca" + +selfsigned@^1.9.1: + version "1.10.1" + resolved "https://registry.yarnpkg.com/selfsigned/-/selfsigned-1.10.1.tgz#bf8cb7b83256c4551e31347c6311778db99eec52" + dependencies: + node-forge "0.6.33" + semaphore@>=1.0.1, semaphore@^1.0.3: version "1.1.0" resolved "https://registry.yarnpkg.com/semaphore/-/semaphore-1.1.0.tgz#aaad8b86b20fe8e9b32b16dc2ee682a8cd26a8aa" @@ -4989,7 +7025,7 @@ semver-regex@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/semver-regex/-/semver-regex-1.0.0.tgz#92a4969065f9c70c694753d55248fc68f8f652c9" -semver-sort@^0.0.4: +semver-sort@0.0.4, semver-sort@^0.0.4: version "0.0.4" resolved "https://registry.yarnpkg.com/semver-sort/-/semver-sort-0.0.4.tgz#34fdbddc6a6b2b4161398c3c4dba56243bfeaa8b" dependencies: @@ -5000,6 +7036,49 @@ semver-sort@^0.0.4: version "5.4.1" resolved "https://registry.yarnpkg.com/semver/-/semver-5.4.1.tgz#e059c09d8571f0540823733433505d3a2f00b18e" +semver@~5.3.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.3.0.tgz#9b2ce5d3de02d17c6012ad326aa6b4d0cf54f94f" + +send@0.16.1: + version "0.16.1" + resolved "https://registry.yarnpkg.com/send/-/send-0.16.1.tgz#a70e1ca21d1382c11d0d9f6231deb281080d7ab3" + dependencies: + debug "2.6.9" + depd "~1.1.1" + destroy "~1.0.4" + encodeurl "~1.0.1" + escape-html "~1.0.3" + etag "~1.8.1" + fresh "0.5.2" + http-errors "~1.6.2" + mime "1.4.1" + ms "2.0.0" + on-finished "~2.3.0" + range-parser "~1.2.0" + statuses "~1.3.1" + +serve-index@^1.7.2: + version "1.9.1" + resolved "https://registry.yarnpkg.com/serve-index/-/serve-index-1.9.1.tgz#d3768d69b1e7d82e5ce050fff5b453bea12a9239" + dependencies: + accepts "~1.3.4" + batch "0.6.1" + debug "2.6.9" + escape-html "~1.0.3" + http-errors "~1.6.2" + mime-types "~2.1.17" + parseurl "~1.3.2" + +serve-static@1.13.1: + version "1.13.1" + resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.13.1.tgz#4c57d53404a761d8f2e7c1e8a18a47dbf278a719" + dependencies: + encodeurl "~1.0.1" + escape-html "~1.0.3" + parseurl "~1.3.2" + send "0.16.1" + set-blocking@^2.0.0, set-blocking@~2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" @@ -5032,10 +7111,18 @@ set-value@^2.0.0: is-plain-object "^2.0.3" split-string "^3.0.1" -setimmediate@^1.0.4: +setimmediate@^1.0.4, setimmediate@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" +setprototypeof@1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.0.3.tgz#66567e37043eeb4f04d91bd658c0cbefb55b8e04" + +setprototypeof@1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.0.tgz#d0bd85536887b6fe7c0d818cb962d9d91c54e656" + sha.js@^2.4.0, sha.js@^2.4.8: version "2.4.9" resolved "https://registry.yarnpkg.com/sha.js/-/sha.js-2.4.9.tgz#98f64880474b74f4a38b8da9d3c0f2d104633e7d" @@ -5049,6 +7136,10 @@ sha3@^1.1.0: dependencies: nan "^2.0.5" +shallowequal@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/shallowequal/-/shallowequal-1.0.2.tgz#1561dbdefb8c01408100319085764da3fcf83f8f" + shebang-command@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" @@ -5088,6 +7179,10 @@ signal-exit@^3.0.0, signal-exit@^3.0.1, signal-exit@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d" +simple-assign@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/simple-assign/-/simple-assign-0.1.0.tgz#17fd3066a5f3d7738f50321bb0f14ca281cc4baa" + simple-get@^1.4.2: version "1.4.3" resolved "https://registry.yarnpkg.com/simple-get/-/simple-get-1.4.3.tgz#e9755eda407e96da40c5e5158c9ea37b33becbeb" @@ -5159,6 +7254,24 @@ sntp@2.x.x: dependencies: hoek "4.x.x" +sockjs-client@1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/sockjs-client/-/sockjs-client-1.1.4.tgz#5babe386b775e4cf14e7520911452654016c8b12" + dependencies: + debug "^2.6.6" + eventsource "0.1.6" + faye-websocket "~0.11.0" + inherits "^2.0.1" + json3 "^3.3.2" + url-parse "^1.1.8" + +sockjs@0.3.18: + version "0.3.18" + resolved "https://registry.yarnpkg.com/sockjs/-/sockjs-0.3.18.tgz#d9b289316ca7df77595ef299e075f0f937eb4207" + dependencies: + faye-websocket "^0.10.0" + uuid "^2.0.2" + solc@^0.4.2: version "0.4.18" resolved "https://registry.yarnpkg.com/solc/-/solc-0.4.18.tgz#83ac6d871dd16a9710e67dbb76dad7f614100702" @@ -5169,16 +7282,34 @@ solc@^0.4.2: semver "^5.3.0" yargs "^4.7.1" +sort-keys@^1.0.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/sort-keys/-/sort-keys-1.1.2.tgz#441b6d4d346798f1b4e49e8920adfba0e543f9ad" + dependencies: + is-plain-obj "^1.0.0" + sort-keys@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/sort-keys/-/sort-keys-2.0.0.tgz#658535584861ec97d730d6cf41822e1f56684128" dependencies: is-plain-obj "^1.0.0" +source-list-map@^0.1.4: + version "0.1.8" + resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-0.1.8.tgz#c550b2ab5427f6b3f21f5afead88c4f5587b2106" + source-list-map@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-2.0.0.tgz#aaa47403f7b245a92fbc97ea08f250d6087ed085" +source-map-loader@^0.1.6: + version "0.1.6" + resolved "https://registry.yarnpkg.com/source-map-loader/-/source-map-loader-0.1.6.tgz#c09903da6d73b9e53b7ed8ee5245597051e98e91" + dependencies: + async "^0.9.0" + loader-utils "~0.2.2" + source-map "~0.1.33" + source-map-resolve@^0.5.0: version "0.5.1" resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.5.1.tgz#7ad0f593f2281598e854df80f19aae4b92d7a11a" @@ -5211,17 +7342,23 @@ source-map-url@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.0.tgz#3e935d7ddd73631b97659956d55128e87b5084a3" -source-map@^0.4.4: - version "0.4.4" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.4.4.tgz#eba4f5da9c0dc999de68032d8b4f76173652036b" +source-map@0.1.x, source-map@~0.1.33: + version "0.1.43" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.1.43.tgz#c24bc146ca517c1471f5dacbe2571b2b7f9e3346" dependencies: amdefine ">=0.0.4" -source-map@^0.5.3, source-map@^0.5.6, source-map@~0.5.1: +source-map@0.5.x, source-map@^0.5.0, source-map@^0.5.3, source-map@^0.5.6, source-map@~0.5.1: version "0.5.7" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" -source-map@^0.6.0, source-map@~0.6.1: +source-map@^0.4.4: + version "0.4.4" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.4.4.tgz#eba4f5da9c0dc999de68032d8b4f76173652036b" + dependencies: + amdefine ">=0.0.4" + +source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" @@ -5250,6 +7387,29 @@ spdx-license-ids@^1.0.2: version "1.2.2" resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-1.2.2.tgz#c9df7a3424594ade6bd11900d596696dc06bac57" +spdy-transport@^2.0.18: + version "2.0.20" + resolved "https://registry.yarnpkg.com/spdy-transport/-/spdy-transport-2.0.20.tgz#735e72054c486b2354fe89e702256004a39ace4d" + dependencies: + debug "^2.6.8" + detect-node "^2.0.3" + hpack.js "^2.1.6" + obuf "^1.1.1" + readable-stream "^2.2.9" + safe-buffer "^5.0.1" + wbuf "^1.7.2" + +spdy@^3.4.1: + version "3.4.7" + resolved "https://registry.yarnpkg.com/spdy/-/spdy-3.4.7.tgz#42ff41ece5cc0f99a3a6c28aabb73f5c3b03acbc" + dependencies: + debug "^2.6.8" + handle-thing "^1.2.5" + http-deceiver "^1.2.7" + safe-buffer "^5.0.1" + select-hose "^2.0.0" + spdy-transport "^2.0.18" + speedometer@~0.1.2: version "0.1.4" resolved "https://registry.yarnpkg.com/speedometer/-/speedometer-0.1.4.tgz#9876dbd2a169d3115402d48e6ea6329c8816a50d" @@ -5303,6 +7463,14 @@ static-extend@^0.1.1: define-property "^0.2.5" object-copy "^0.1.0" +"statuses@>= 1.3.1 < 2": + version "1.4.0" + resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.4.0.tgz#bb73d446da2796106efcc1b601a253d6c46bd087" + +statuses@~1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.3.1.tgz#faf51b9eb74aaef3b3acf4ad5f61abf24cb7b93e" + stealthy-require@^1.1.0: version "1.1.1" resolved "https://registry.yarnpkg.com/stealthy-require/-/stealthy-require-1.1.1.tgz#35b09875b4ff49f26a777e509b3090a3226bf24b" @@ -5363,6 +7531,10 @@ string.prototype.padend@^3.0.0: es-abstract "^1.4.3" function-bind "^1.0.2" +string.prototype.repeat@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/string.prototype.repeat/-/string.prototype.repeat-0.2.0.tgz#aba36de08dcee6a5a337d49b2ea1da1b28fc0ecf" + string.prototype.trim@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/string.prototype.trim/-/string.prototype.trim-1.1.2.tgz#d04de2c89e137f4d7d206f086b5ed2fae6be8cea" @@ -5443,6 +7615,12 @@ strong-log-transformer@^1.0.6: moment "^2.6.0" through "^2.3.4" +style-loader@0.13.x: + version "0.13.2" + resolved "https://registry.yarnpkg.com/style-loader/-/style-loader-0.13.2.tgz#74533384cf698c7104c7951150b49717adc2f3bb" + dependencies: + loader-utils "^1.0.2" + supports-color@4.4.0, supports-color@^4.0.0, supports-color@^4.2.1, supports-color@^4.4.0: version "4.4.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-4.4.0.tgz#883f7ddabc165142b2a61427f3352ded195d1a3e" @@ -5453,13 +7631,41 @@ supports-color@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" -supports-color@^3.1.2: +supports-color@^3.1.0, supports-color@^3.1.2, supports-color@^3.2.3: version "3.2.3" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-3.2.3.tgz#65ac0504b3954171d8a64946b2ae3cbb8a5f54f6" dependencies: has-flag "^1.0.0" -tapable@^0.2.5, tapable@^0.2.7: +svgo@^0.7.0: + version "0.7.2" + resolved "https://registry.yarnpkg.com/svgo/-/svgo-0.7.2.tgz#9f5772413952135c6fefbf40afe6a4faa88b4bb5" + dependencies: + coa "~1.0.1" + colors "~1.1.2" + csso "~2.3.1" + js-yaml "~3.7.0" + mkdirp "~0.5.1" + sax "~1.2.1" + whet.extend "~0.9.9" + +svgo@~0.6.3: + version "0.6.6" + resolved "https://registry.yarnpkg.com/svgo/-/svgo-0.6.6.tgz#b340889036f20f9b447543077d0f5573ed044c08" + dependencies: + coa "~1.0.1" + colors "~1.1.2" + csso "~2.0.0" + js-yaml "~3.6.0" + mkdirp "~0.5.1" + sax "~1.2.1" + whet.extend "~0.9.9" + +symbol-observable@^1.0.3, symbol-observable@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.0.4.tgz#29bf615d4aa7121bdd898b22d4b3f9bc4e2aa03d" + +tapable@^0.2.5, tapable@^0.2.7, tapable@~0.2.5: version "0.2.8" resolved "https://registry.yarnpkg.com/tapable/-/tapable-0.2.8.tgz#99372a5c999bf2df160afc0d74bed4f47948cd22" @@ -5503,6 +7709,19 @@ tar-pack@^3.4.0: tar "^2.2.1" uid-number "^0.0.6" +tar-pack@~3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/tar-pack/-/tar-pack-3.3.0.tgz#30931816418f55afc4d21775afdd6720cee45dae" + dependencies: + debug "~2.2.0" + fstream "~1.0.10" + fstream-ignore "~1.0.5" + once "~1.3.3" + readable-stream "~2.1.4" + rimraf "~2.5.1" + tar "~2.2.1" + uid-number "~0.0.6" + tar-stream@^1.1.2: version "1.5.4" resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-1.5.4.tgz#36549cf04ed1aee9b2a30c0143252238daf94016" @@ -5512,7 +7731,7 @@ tar-stream@^1.1.2: readable-stream "^2.0.0" xtend "^4.0.0" -tar@^2.2.1: +tar@^2.2.1, tar@~2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/tar/-/tar-2.2.1.tgz#8e4d2a256c0e2185c6b18ad694aec968b83cb1d1" dependencies: @@ -5571,6 +7790,10 @@ text-extensions@^1.0.0: version "1.7.0" resolved "https://registry.yarnpkg.com/text-extensions/-/text-extensions-1.7.0.tgz#faaaba2625ed746d568a23e4d0aacd9bf08a8b39" +thenby@^1.2.3: + version "1.2.3" + resolved "https://registry.yarnpkg.com/thenby/-/thenby-1.2.3.tgz#62465b07e3d8b9466f01026df837f738e0faaa69" + through2@^2.0.0, through2@^2.0.1, through2@^2.0.2: version "2.0.3" resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.3.tgz#0004569b37c7c74ba39c43f3ced78d1ad94140be" @@ -5596,6 +7819,14 @@ through@2, "through@>=2.2.7 <3", through@^2.3.4, through@^2.3.6, through@~2.3, t version "2.3.8" resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" +thunky@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/thunky/-/thunky-0.1.0.tgz#bf30146824e2b6e67b0f2d7a4ac8beb26908684e" + +time-stamp@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/time-stamp/-/time-stamp-2.0.0.tgz#95c6a44530e15ba8d6f4a3ecb8c3a3fac46da357" + timed-out@^4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/timed-out/-/timed-out-4.0.1.tgz#f32eacac5a175bea25d7fab565ab3ed8741ef56f" @@ -5641,6 +7872,10 @@ to-regex@^3.0.1: extend-shallow "^2.0.1" regex-not "^1.0.0" +toggle-selection@^1.0.3: + version "1.0.6" + resolved "https://registry.yarnpkg.com/toggle-selection/-/toggle-selection-1.0.6.tgz#6e45b1263f2017fa0acc7d89d78b15b8bf77da32" + tough-cookie@>=2.3.3, tough-cookie@~2.3.0, tough-cookie@~2.3.3: version "2.3.3" resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.3.3.tgz#0b618a5565b6dea90bf3425d04d55edc475a7561" @@ -5663,6 +7898,27 @@ trim@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/trim/-/trim-0.0.1.tgz#5858547f6b290757ee95cccc666fb50084c460dd" +truffle-blockchain-utils@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/truffle-blockchain-utils/-/truffle-blockchain-utils-0.0.1.tgz#07a58e55bb0555a64208c9119c0b04ffe1464aa4" + dependencies: + web3 "^0.18.0" + +truffle-contract-schema@0.0.5: + version "0.0.5" + resolved "https://registry.yarnpkg.com/truffle-contract-schema/-/truffle-contract-schema-0.0.5.tgz#5e9d20bd0bf2a27fe94310748249d484eee49961" + dependencies: + crypto-js "^3.1.9-1" + +truffle-contract@2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/truffle-contract/-/truffle-contract-2.0.1.tgz#f83e3f18d8044027f2a9ee7c33767ba10fd39dd8" + dependencies: + ethjs-abi "0.1.8" + truffle-blockchain-utils "0.0.1" + truffle-contract-schema "0.0.5" + web3 "^0.18.0" + truffle-hdwallet-provider@^0.0.3: version "0.0.3" resolved "https://registry.yarnpkg.com/truffle-hdwallet-provider/-/truffle-hdwallet-provider-0.0.3.tgz#0e1de02104b73d3875e1cf7093305b4ea8a2d843" @@ -5676,7 +7932,13 @@ tslib@^1.7.1: version "1.8.0" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.8.0.tgz#dc604ebad64bcbf696d613da6c954aa0e7ea1eb6" -tslint-react@^3.2.0: +tslint-config-0xproject@^0.0.2: + version "0.0.2" + resolved "https://registry.yarnpkg.com/tslint-config-0xproject/-/tslint-config-0xproject-0.0.2.tgz#39901e0c0b3e9388f00092a28b90c015395d5bba" + dependencies: + tslint-react "^3.0.0" + +tslint-react@^3.0.0, tslint-react@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/tslint-react/-/tslint-react-3.2.0.tgz#851fb505201c63d0343c51726e6364f7e9ad2e99" dependencies: @@ -5722,6 +7984,13 @@ type-detect@^4.0.0: version "4.0.5" resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.5.tgz#d70e5bc81db6de2a381bcaca0c6e0cbdc7635de2" +type-is@~1.6.15: + version "1.6.15" + resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.15.tgz#cab10fb4909e441c82842eafe1ad646c81804410" + dependencies: + media-typer "0.3.0" + mime-types "~2.1.15" + typedarray-to-buffer@^3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/typedarray-to-buffer/-/typedarray-to-buffer-3.1.2.tgz#1017b32d984ff556eba100f501589aba1ace2e04" @@ -5776,10 +8045,14 @@ typescript@2.4.1: version "2.4.1" resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.4.1.tgz#c3ccb16ddaa0b2314de031e7e6fee89e5ba346bc" -typescript@2.6.1, typescript@^2.4.2, typescript@~2.6.1: +typescript@2.6.1, typescript@^2.4.1, typescript@^2.4.2, typescript@~2.6.1: version "2.6.1" resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.6.1.tgz#ef39cdea27abac0b500242d6726ab90e0c846631" +ua-parser-js@^0.7.9: + version "0.7.17" + resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.17.tgz#e9ec5f9498b9ec910e7ae3ac626a805c4d09ecac" + uglify-js@^2.6, uglify-js@^2.8.29: version "2.8.29" resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-2.8.29.tgz#29c5733148057bb4e1f75df35b7a9cb72e6a59dd" @@ -5801,7 +8074,7 @@ uglifyjs-webpack-plugin@^0.4.6: uglify-js "^2.8.29" webpack-sources "^1.0.1" -uid-number@^0.0.6: +uid-number@^0.0.6, uid-number@~0.0.6: version "0.0.6" resolved "https://registry.yarnpkg.com/uid-number/-/uid-number-0.0.6.tgz#0ea10e8035e8eb5b8e4449f06da1c730663baa81" @@ -5814,6 +8087,20 @@ union-value@^1.0.0: is-extendable "^0.1.1" set-value "^0.4.3" +uniq@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/uniq/-/uniq-1.0.1.tgz#b31c5ae8254844a3a8281541ce2b04b865a734ff" + +uniqid@^4.0.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/uniqid/-/uniqid-4.1.1.tgz#89220ddf6b751ae52b5f72484863528596bb84c1" + dependencies: + macaddress "^0.2.8" + +uniqs@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/uniqs/-/uniqs-2.0.0.tgz#ffede4b36b25290696e6e165d4a59edb998e6b02" + universalify@^0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.1.tgz#fa71badd4437af4c148841e3b3b165f9e9e590b7" @@ -5822,6 +8109,10 @@ unorm@^1.3.3: version "1.4.1" resolved "https://registry.yarnpkg.com/unorm/-/unorm-1.4.1.tgz#364200d5f13646ca8bcd44490271335614792300" +unpipe@1.0.0, unpipe@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" + unset-value@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/unset-value/-/unset-value-1.0.0.tgz#8376873f7d2335179ffb1e6fc3a8ed0dfc8ab559" @@ -5847,6 +8138,20 @@ url-parse-lax@^1.0.0: dependencies: prepend-http "^1.0.1" +url-parse@1.0.x: + version "1.0.5" + resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.0.5.tgz#0854860422afdcfefeb6c965c662d4800169927b" + dependencies: + querystringify "0.0.x" + requires-port "1.0.x" + +url-parse@^1.1.8: + version "1.2.0" + resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.2.0.tgz#3a19e8aaa6d023ddd27dcc44cb4fc8f7fec23986" + dependencies: + querystringify "~1.0.0" + requires-port "~1.0.0" + url@^0.11.0: version "0.11.0" resolved "https://registry.yarnpkg.com/url/-/url-0.11.0.tgz#3838e97cfc60521eb73c525a8e55bfdd9e2e28f1" @@ -5854,6 +8159,18 @@ url@^0.11.0: punycode "1.3.2" querystring "0.2.0" +urlencode@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/urlencode/-/urlencode-1.1.0.tgz#1f2ba26f013c85f0133f7a3ad6ff2730adf7cbb7" + dependencies: + iconv-lite "~0.4.11" + +urllite@~0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/urllite/-/urllite-0.5.0.tgz#1b7bb9ca3fb0db9520de113466bbcf7cc341451a" + dependencies: + xtend "~4.0.0" + use@^2.0.0: version "2.0.2" resolved "https://registry.yarnpkg.com/use/-/use-2.0.2.tgz#ae28a0d72f93bf22422a18a2e379993112dec8e8" @@ -5876,11 +8193,15 @@ util@0.10.3, util@^0.10.3: dependencies: inherits "2.0.1" +utils-merge@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" + uuid@3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.0.1.tgz#6544bba2dfda8c1cf17e629a3a305e2bb1fee6c1" -uuid@^2.0.1: +uuid@^2.0.1, uuid@^2.0.2: version "2.0.3" resolved "https://registry.yarnpkg.com/uuid/-/uuid-2.0.3.tgz#67e2e863797215530dff318e5bf9dcebfd47b21a" @@ -5899,6 +8220,18 @@ validate-npm-package-license@^3.0.1: spdx-correct "~1.0.0" spdx-expression-parse "~1.0.0" +value-equal@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/value-equal/-/value-equal-0.4.0.tgz#c5bdd2f54ee093c04839d71ce2e4758a6890abc7" + +vary@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" + +vendors@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/vendors/-/vendors-1.0.1.tgz#37ad73c8ee417fb3d580e785312307d274847f22" + verror@1.10.0: version "1.10.0" resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400" @@ -5913,7 +8246,13 @@ vm-browserify@0.0.4: dependencies: indexof "0.0.1" -watchpack@^1.4.0: +warning@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/warning/-/warning-3.0.0.tgz#32e5377cb572de4ab04753bdf8821c01ed605b7c" + dependencies: + loose-envify "^1.0.0" + +watchpack@^1.3.1, watchpack@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-1.4.0.tgz#4a1472bcbb952bd0a9bb4036801f954dfb39faac" dependencies: @@ -5921,12 +8260,40 @@ watchpack@^1.4.0: chokidar "^1.7.0" graceful-fs "^4.1.2" +wbuf@^1.1.0, wbuf@^1.7.2: + version "1.7.2" + resolved "https://registry.yarnpkg.com/wbuf/-/wbuf-1.7.2.tgz#d697b99f1f59512df2751be42769c1580b5801fe" + dependencies: + minimalistic-assert "^1.0.0" + wcwidth@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/wcwidth/-/wcwidth-1.0.1.tgz#f0b0dcf915bc5ff1528afadb2c0e17b532da2fe8" dependencies: defaults "^1.0.3" +web3-provider-engine@^11.0.0: + version "11.0.2" + resolved "https://registry.yarnpkg.com/web3-provider-engine/-/web3-provider-engine-11.0.2.tgz#d63dae73ecb37d141b4d63431766c22ff406f32f" + dependencies: + async "^2.1.2" + clone "^2.0.0" + eth-sig-util "^1.2.1" + ethereumjs-block "^1.2.2" + ethereumjs-tx "^1.2.0" + ethereumjs-util "^5.1.1" + ethereumjs-vm "^2.0.2" + fetch-ponyfill "^4.0.0" + json-rpc-error "^2.0.0" + promise-to-callback "^1.0.0" + request "^2.67.0" + semaphore "^1.0.3" + solc "^0.4.2" + tape "^4.4.0" + web3 "^0.16.0" + xhr "^2.2.0" + xtend "^4.0.1" + web3-provider-engine@^13.0.1: version "13.3.3" resolved "https://registry.yarnpkg.com/web3-provider-engine/-/web3-provider-engine-13.3.3.tgz#1f6df4ec540161125f3e5f501bb0e56af83a01df" @@ -5985,7 +8352,7 @@ web3@^0.16.0: utf8 "^2.1.1" xmlhttprequest "*" -web3@^0.18.2: +web3@^0.18.0, web3@^0.18.2: version "0.18.4" resolved "https://registry.yarnpkg.com/web3/-/web3-0.18.4.tgz#81ec1784145491f2eaa8955b31c06049e07c5e7d" dependencies: @@ -6005,6 +8372,48 @@ web3@^0.20.0: xhr2 "*" xmlhttprequest "*" +webpack-dev-middleware@^1.10.0, webpack-dev-middleware@^1.11.0: + version "1.12.0" + resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-1.12.0.tgz#d34efefb2edda7e1d3b5dbe07289513219651709" + dependencies: + memory-fs "~0.4.1" + mime "^1.3.4" + path-is-absolute "^1.0.0" + range-parser "^1.0.3" + time-stamp "^2.0.0" + +webpack-dev-server@^2.5.0: + version "2.9.4" + resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-2.9.4.tgz#7883e61759c6a4b33e9b19ec4037bd4ab61428d1" + dependencies: + ansi-html "0.0.7" + array-includes "^3.0.3" + bonjour "^3.5.0" + chokidar "^1.6.0" + compression "^1.5.2" + connect-history-api-fallback "^1.3.0" + debug "^3.1.0" + del "^3.0.0" + express "^4.13.3" + html-entities "^1.2.0" + http-proxy-middleware "~0.17.4" + import-local "^0.1.1" + internal-ip "1.2.0" + ip "^1.1.5" + killable "^1.0.0" + loglevel "^1.4.1" + opn "^5.1.0" + portfinder "^1.0.9" + selfsigned "^1.9.1" + serve-index "^1.7.2" + sockjs "0.3.18" + sockjs-client "1.1.4" + spdy "^3.4.1" + strip-ansi "^3.0.1" + supports-color "^4.2.1" + webpack-dev-middleware "^1.11.0" + yargs "^6.6.0" + webpack-sources@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-1.0.2.tgz#d0148ec083b3b5ccef1035a6b3ec16442983b27a" @@ -6012,6 +8421,33 @@ webpack-sources@^1.0.1: source-list-map "^2.0.0" source-map "~0.6.1" +webpack@3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/webpack/-/webpack-3.1.0.tgz#ac0675e500db835f9ab2369d29ba096f51ad0731" + dependencies: + acorn "^5.0.0" + acorn-dynamic-import "^2.0.0" + ajv "^5.1.5" + ajv-keywords "^2.0.0" + async "^2.1.2" + enhanced-resolve "^3.3.0" + escope "^3.6.0" + interpret "^1.0.0" + json-loader "^0.5.4" + json5 "^0.5.1" + loader-runner "^2.3.0" + loader-utils "^1.1.0" + memory-fs "~0.4.1" + mkdirp "~0.5.0" + node-libs-browser "^2.0.0" + source-map "^0.5.3" + supports-color "^3.1.0" + tapable "~0.2.5" + uglifyjs-webpack-plugin "^0.4.6" + watchpack "^1.3.1" + webpack-sources "^1.0.1" + yargs "^6.0.0" + webpack@^3.0.0, webpack@^3.1.0: version "3.8.1" resolved "https://registry.yarnpkg.com/webpack/-/webpack-3.8.1.tgz#b16968a81100abe61608b0153c9159ef8bb2bd83" @@ -6039,6 +8475,17 @@ webpack@^3.0.0, webpack@^3.1.0: webpack-sources "^1.0.1" yargs "^8.0.2" +websocket-driver@>=0.5.1: + version "0.7.0" + resolved "https://registry.yarnpkg.com/websocket-driver/-/websocket-driver-0.7.0.tgz#0caf9d2d755d93aee049d4bdd0d3fe2cca2a24eb" + dependencies: + http-parser-js ">=0.4.0" + websocket-extensions ">=0.1.1" + +websocket-extensions@>=0.1.1: + version "0.1.3" + resolved "https://registry.yarnpkg.com/websocket-extensions/-/websocket-extensions-0.1.3.tgz#5d2ff22977003ec687a4b87073dfbbac146ccf29" + websocket@^1.0.25: version "1.0.25" resolved "https://registry.yarnpkg.com/websocket/-/websocket-1.0.25.tgz#998ec790f0a3eacb8b08b50a4350026692a11958" @@ -6048,10 +8495,14 @@ websocket@^1.0.25: typedarray-to-buffer "^3.1.2" yaeti "^0.0.6" -whatwg-fetch@>=0.10.0: +whatwg-fetch@>=0.10.0, whatwg-fetch@^2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-2.0.3.tgz#9c84ec2dcf68187ff00bc64e1274b442176e1c84" +whet.extend@~0.9.9: + version "0.9.9" + resolved "https://registry.yarnpkg.com/whet.extend/-/whet.extend-0.9.9.tgz#f877d5bf648c97e5aa542fadc16d6a259b9c11a1" + which-module@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/which-module/-/which-module-1.0.0.tgz#bba63ca861948994ff307736089e3b96026c2a4f" @@ -6146,10 +8597,20 @@ xhr@^2.2.0: parse-headers "^2.0.0" xtend "^4.0.0" +xml-js@^1.3.2: + version "1.5.2" + resolved "https://registry.yarnpkg.com/xml-js/-/xml-js-1.5.2.tgz#234780d012b49a19093dbd464228d06303fddc23" + dependencies: + sax "^1.2.4" + xmlhttprequest@*: version "1.8.0" resolved "https://registry.yarnpkg.com/xmlhttprequest/-/xmlhttprequest-1.8.0.tgz#67fe075c5c24fef39f9d65f5f7b7fe75171968fc" +xss-filters@^1.2.6: + version "1.2.7" + resolved "https://registry.yarnpkg.com/xss-filters/-/xss-filters-1.2.7.tgz#59fa1de201f36f2f3470dcac5f58ccc2830b0a9a" + xtend@4.0.1, "xtend@>=4.0.0 <4.1.0-0", xtend@^4.0.0, xtend@^4.0.1, xtend@~4.0.0, xtend@~4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af" @@ -6160,6 +8621,10 @@ xtend@~2.1.1: dependencies: object-keys "~0.4.0" +xtend@~3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/xtend/-/xtend-3.0.0.tgz#5cce7407baf642cba7becda568111c493f59665a" + y18n@^3.2.1: version "3.2.1" resolved "https://registry.yarnpkg.com/y18n/-/y18n-3.2.1.tgz#6d15fba884c08679c0d77e88e7759e811e07fa41" @@ -6179,6 +8644,12 @@ yargs-parser@^2.4.1: camelcase "^3.0.0" lodash.assign "^4.0.6" +yargs-parser@^4.2.0: + version "4.2.1" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-4.2.1.tgz#29cceac0dc4f03c6c87b4a9f217dd18c9f74871c" + dependencies: + camelcase "^3.0.0" + yargs-parser@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-7.0.0.tgz#8d0ac42f16ea55debd332caf4c4038b3e3f5dfd9" @@ -6208,7 +8679,7 @@ yargs@^10.0.3: y18n "^3.2.1" yargs-parser "^8.0.0" -yargs@^4.7.1: +yargs@^4.7.1, yargs@~4.8.0: version "4.8.1" resolved "https://registry.yarnpkg.com/yargs/-/yargs-4.8.1.tgz#c0c42924ca4aaa6b0e6da1739dfb216439f9ddc0" dependencies: @@ -6227,6 +8698,24 @@ yargs@^4.7.1: y18n "^3.2.1" yargs-parser "^2.4.1" +yargs@^6.0.0, yargs@^6.6.0: + version "6.6.0" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-6.6.0.tgz#782ec21ef403345f830a808ca3d513af56065208" + dependencies: + camelcase "^3.0.0" + cliui "^3.2.0" + decamelize "^1.1.1" + get-caller-file "^1.0.1" + os-locale "^1.4.0" + read-pkg-up "^1.0.1" + require-directory "^2.1.1" + require-main-filename "^1.0.1" + set-blocking "^2.0.0" + string-width "^1.0.2" + which-module "^1.0.0" + y18n "^3.2.1" + yargs-parser "^4.2.0" + yargs@^8.0.2: version "8.0.2" resolved "https://registry.yarnpkg.com/yargs/-/yargs-8.0.2.tgz#6299a9055b1cefc969ff7e79c1d918dceb22c360" -- cgit v1.2.3 From cb377f29c2dc76a089a97c752888fa5c5b4a001f Mon Sep 17 00:00:00 2001 From: Fabio Berger Date: Wed, 22 Nov 2017 14:20:24 -0600 Subject: Fix build bug --- packages/website/tsconfig.json | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/website/tsconfig.json b/packages/website/tsconfig.json index 15e764c02..5b3510c26 100644 --- a/packages/website/tsconfig.json +++ b/packages/website/tsconfig.json @@ -3,6 +3,7 @@ "allowSyntheticDefaultImports": true, "outDir": "./transpiled/", "sourceMap": true, + "lib": [ "es2015", "dom" ], "noImplicitAny": true, "module": "commonjs", "target": "es5", -- cgit v1.2.3 From 353b6f3d6df1e13ba76f86287195f7c0ce7cecc7 Mon Sep 17 00:00:00 2001 From: Fabio Berger Date: Wed, 22 Nov 2017 14:20:35 -0600 Subject: add bundles to gitignore --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index b461f33de..68773f3c2 100644 --- a/.gitignore +++ b/.gitignore @@ -66,3 +66,5 @@ _bundles docs/ TODO.md + +packages/website/public/bundle* -- cgit v1.2.3 From f5e0fd8de5a3201ac17a8dde68aec3314674f1c4 Mon Sep 17 00:00:00 2001 From: Fabio Berger Date: Wed, 22 Nov 2017 14:21:07 -0600 Subject: Upgrade to latest 0x.js version and refactor subscriptions to use latest interface --- packages/website/package.json | 2 +- packages/website/ts/blockchain.ts | 64 ++++++++++++++-------- .../ts/local_storage/trade_history_storage.tsx | 14 ++++- yarn.lock | 4 -- 4 files changed, 54 insertions(+), 30 deletions(-) diff --git a/packages/website/package.json b/packages/website/package.json index ba71ee0eb..d6d92d0f8 100644 --- a/packages/website/package.json +++ b/packages/website/package.json @@ -16,6 +16,7 @@ "author": "Fabio Berger", "license": "Apache-2.0", "dependencies": { + "0x.js": "^0.26.0", "accounting": "^0.4.1", "basscss": "^8.0.3", "bignumber.js": "~4.1.0", @@ -69,7 +70,6 @@ "@types/accounting": "^0.4.1", "@types/dateformat": "^1.0.1", "@types/deep-equal": "^1.0.0", - "@types/es6-promise": "0.0.32", "@types/jsonschema": "^1.1.1", "@types/lodash": "^4.14.55", "@types/material-ui": "0.18.0", diff --git a/packages/website/ts/blockchain.ts b/packages/website/ts/blockchain.ts index d891acdb6..3a1b62164 100644 --- a/packages/website/ts/blockchain.ts +++ b/packages/website/ts/blockchain.ts @@ -27,7 +27,7 @@ import contract = require('truffle-contract'); import ethUtil = require('ethereumjs-util'); import ProviderEngine = require('web3-provider-engine'); import FilterSubprovider = require('web3-provider-engine/subproviders/filters'); -import {TransactionSubmitted} from 'ts/components/flash_messages/transaction_submitted'; +import { TransactionSubmitted } from 'ts/components/flash_messages/transaction_submitted'; import {TokenSendCompleted} from 'ts/components/flash_messages/token_send_completed'; import {RedundantRPCSubprovider} from 'ts/subproviders/redundant_rpc_subprovider'; import {InjectedWeb3SubProvider} from 'ts/subproviders/injected_web3_subprovider'; @@ -496,7 +496,16 @@ export class Blockchain { this.stopWatchingExchangeLogFillEventsAsync(); // fire and forget return; } else { - await this.addFillEventToTradeHistoryAsync(decodedLogEvent); + if (this.doesLogEventInvolveUser(decodedLogEvent)) { + return; // We aren't interested in the fill event + } + this.updateLatestFillsBlockIfNeeded(decodedLogEvent.blockNumber); + const fill = await this.convertDecodedLogToFillAsync(decodedLogEvent); + if (decodedLogEvent.removed) { + tradeHistoryStorage.removeFillFromUser(this.userAddress, this.networkId, fill); + } else { + tradeHistoryStorage.addFillToUser(this.userAddress, this.networkId, fill); + } } }); } @@ -510,30 +519,16 @@ export class Blockchain { ExchangeEvents.LogFill, subscriptionOpts, indexFilterValues, ); for (const decodedLog of decodedLogs) { - console.log('decodedLog', decodedLog); - await this.addFillEventToTradeHistoryAsync(decodedLog); + if (this.doesLogEventInvolveUser(decodedLog)) { + continue; // We aren't interested in the fill event + } + this.updateLatestFillsBlockIfNeeded(decodedLog.blockNumber); + const fill = await this.convertDecodedLogToFillAsync(decodedLog); + tradeHistoryStorage.addFillToUser(this.userAddress, this.networkId, fill); } } - private async addFillEventToTradeHistoryAsync(decodedLog: LogWithDecodedArgs) { + private async convertDecodedLogToFillAsync(decodedLog: LogWithDecodedArgs) { const args = decodedLog.args as LogFillContractEventArgs; - const isUserMakerOrTaker = args.maker === this.userAddress || - args.taker === this.userAddress; - if (!isUserMakerOrTaker) { - return; // We aren't interested in the fill event - } - const isBlockPending = _.isNull(decodedLog.blockNumber); - if (!isBlockPending) { - // Hack: I've observed the behavior where a client won't register certain fill events - // and lowering the cache blockNumber fixes the issue. As a quick fix for now, simply - // set the cached blockNumber 50 below the one returned. This way, upon refreshing, a user - // would still attempt to re-fetch events from the previous 50 blocks, but won't need to - // re-fetch all events in all blocks. - // TODO: Debug if this is a race condition, and apply a more precise fix - const blockNumberToSet = decodedLog.blockNumber - BLOCK_NUMBER_BACK_TRACK < 0 ? - 0 : - decodedLog.blockNumber - BLOCK_NUMBER_BACK_TRACK; - tradeHistoryStorage.setFillsLatestBlock(this.userAddress, this.networkId, blockNumberToSet); - } const blockTimestamp = await this.web3Wrapper.getBlockTimestampAsync(decodedLog.blockHash); const fill = { filledTakerTokenAmount: args.filledTakerTokenAmount, @@ -549,7 +544,28 @@ export class Blockchain { transactionHash: decodedLog.transactionHash, blockTimestamp, }; - tradeHistoryStorage.addFillToUser(this.userAddress, this.networkId, fill); + return fill; + } + private doesLogEventInvolveUser(decodedLog: LogWithDecodedArgs) { + const args = decodedLog.args as LogFillContractEventArgs; + const isUserMakerOrTaker = args.maker === this.userAddress || + args.taker === this.userAddress; + return isUserMakerOrTaker; + } + private updateLatestFillsBlockIfNeeded(blockNumber: number) { + const isBlockPending = _.isNull(blockNumber); + if (!isBlockPending) { + // Hack: I've observed the behavior where a client won't register certain fill events + // and lowering the cache blockNumber fixes the issue. As a quick fix for now, simply + // set the cached blockNumber 50 below the one returned. This way, upon refreshing, a user + // would still attempt to re-fetch events from the previous 50 blocks, but won't need to + // re-fetch all events in all blocks. + // TODO: Debug if this is a race condition, and apply a more precise fix + const blockNumberToSet = blockNumber - BLOCK_NUMBER_BACK_TRACK < 0 ? + 0 : + blockNumber - BLOCK_NUMBER_BACK_TRACK; + tradeHistoryStorage.setFillsLatestBlock(this.userAddress, this.networkId, blockNumberToSet); + } } private async stopWatchingExchangeLogFillEventsAsync() { this.zeroEx.exchange.unsubscribeAll(); diff --git a/packages/website/ts/local_storage/trade_history_storage.tsx b/packages/website/ts/local_storage/trade_history_storage.tsx index 415b380b7..dd5872609 100644 --- a/packages/website/ts/local_storage/trade_history_storage.tsx +++ b/packages/website/ts/local_storage/trade_history_storage.tsx @@ -31,13 +31,25 @@ export const tradeHistoryStorage = { const fillHash = this._getFillHash(fill); const doesFillExist = !_.isUndefined(fillsByHash[fillHash]); if (doesFillExist) { - return; + return; // noop } fillsByHash[fillHash] = fill; const userFillsJSONString = JSON.stringify(fillsByHash); const userFillsKey = this._getUserFillsKey(userAddress, networkId); localStorage.setItem(userFillsKey, userFillsJSONString); }, + removeFillFromUser(userAddress: string, networkId: number, fill: Fill) { + const fillsByHash = this.getUserFillsByHash(userAddress, networkId); + const fillHash = this._getFillHash(fill); + const doesFillExist = !_.isUndefined(fillsByHash[fillHash]); + if (!doesFillExist) { + return; // noop + } + delete fillsByHash[fillHash]; + const userFillsJSONString = JSON.stringify(fillsByHash); + const userFillsKey = this._getUserFillsKey(userAddress, networkId); + localStorage.setItem(userFillsKey, userFillsJSONString); + }, getUserFillsByHash(userAddress: string, networkId: number): {[fillHash: string]: Fill} { const userFillsKey = this._getUserFillsKey(userAddress, networkId); const userFillsJSONString = localStorage.getItemIfExists(userFillsKey); diff --git a/yarn.lock b/yarn.lock index 71fa75f7a..6b7bb615d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -14,10 +14,6 @@ version "1.0.1" resolved "https://registry.yarnpkg.com/@types/deep-equal/-/deep-equal-1.0.1.tgz#71cfabb247c22bcc16d536111f50c0ed12476b03" -"@types/es6-promise@0.0.32": - version "0.0.32" - resolved "https://registry.yarnpkg.com/@types/es6-promise/-/es6-promise-0.0.32.tgz#3bcf44fb1e429f3df76188c8c6d874463ba371fd" - "@types/fetch-mock@^5.12.1": version "5.12.2" resolved "https://registry.yarnpkg.com/@types/fetch-mock/-/fetch-mock-5.12.2.tgz#8c96517ff74303031c65c5da2d99858e34c844d2" -- cgit v1.2.3 From 805a055946e6b4285e7db9e30263f4f7f41dd4eb Mon Sep 17 00:00:00 2001 From: Fabio Berger Date: Wed, 22 Nov 2017 16:43:17 -0600 Subject: Force the clearance of tradeHistory --- packages/website/ts/utils/configs.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/website/ts/utils/configs.ts b/packages/website/ts/utils/configs.ts index 49ac4b5e0..63fcd27b6 100644 --- a/packages/website/ts/utils/configs.ts +++ b/packages/website/ts/utils/configs.ts @@ -13,6 +13,6 @@ export const configs = { symbolsOfMintableTokens: ['MKR', 'MLN', 'GNT', 'DGD', 'REP'], // WARNING: ZRX & WETH MUST always be default trackedTokens defaultTrackedTokenSymbols: ['WETH', 'ZRX'], - lastLocalStorageFillClearanceDate: '2017-09-09', + lastLocalStorageFillClearanceDate: '2017-11-22', isMainnetEnabled: true, }; -- cgit v1.2.3 From bcc5d635169e3202a5c46cb2995ff51c8482a36c Mon Sep 17 00:00:00 2001 From: Fabio Berger Date: Thu, 23 Nov 2017 13:56:16 -0600 Subject: Fix bug --- packages/website/ts/blockchain.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/website/ts/blockchain.ts b/packages/website/ts/blockchain.ts index 3a1b62164..9be764917 100644 --- a/packages/website/ts/blockchain.ts +++ b/packages/website/ts/blockchain.ts @@ -496,7 +496,7 @@ export class Blockchain { this.stopWatchingExchangeLogFillEventsAsync(); // fire and forget return; } else { - if (this.doesLogEventInvolveUser(decodedLogEvent)) { + if (!this.doesLogEventInvolveUser(decodedLogEvent)) { return; // We aren't interested in the fill event } this.updateLatestFillsBlockIfNeeded(decodedLogEvent.blockNumber); @@ -519,7 +519,7 @@ export class Blockchain { ExchangeEvents.LogFill, subscriptionOpts, indexFilterValues, ); for (const decodedLog of decodedLogs) { - if (this.doesLogEventInvolveUser(decodedLog)) { + if (!this.doesLogEventInvolveUser(decodedLog)) { continue; // We aren't interested in the fill event } this.updateLatestFillsBlockIfNeeded(decodedLog.blockNumber); -- cgit v1.2.3 From a42e8ae873d075c9013ed71d261c1c4f33715a00 Mon Sep 17 00:00:00 2001 From: Fabio Berger Date: Thu, 23 Nov 2017 13:56:34 -0600 Subject: Add missing keys --- packages/website/ts/components/token_balances.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/website/ts/components/token_balances.tsx b/packages/website/ts/components/token_balances.tsx index c552d19dc..2d533a9f2 100644 --- a/packages/website/ts/components/token_balances.tsx +++ b/packages/website/ts/components/token_balances.tsx @@ -132,6 +132,7 @@ export class TokenBalances extends React.Component Date: Thu, 23 Nov 2017 13:56:42 -0600 Subject: Fix alignment --- packages/website/ts/redux/dispatcher.ts | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/packages/website/ts/redux/dispatcher.ts b/packages/website/ts/redux/dispatcher.ts index 6badf95bd..566ab8a01 100644 --- a/packages/website/ts/redux/dispatcher.ts +++ b/packages/website/ts/redux/dispatcher.ts @@ -107,25 +107,25 @@ export class Dispatcher { public encounteredBlockchainError(err: BlockchainErrs) { this.dispatch({ data: err, - type: ActionTypes.BLOCKCHAIN_ERR_ENCOUNTERED, + type: ActionTypes.BLOCKCHAIN_ERR_ENCOUNTERED, }); } public updateBlockchainIsLoaded(isLoaded: boolean) { this.dispatch({ data: isLoaded, - type: ActionTypes.UPDATE_BLOCKCHAIN_IS_LOADED, + type: ActionTypes.UPDATE_BLOCKCHAIN_IS_LOADED, }); } public addTokenToTokenByAddress(token: Token) { this.dispatch({ data: token, - type: ActionTypes.ADD_TOKEN_TO_TOKEN_BY_ADDRESS, + type: ActionTypes.ADD_TOKEN_TO_TOKEN_BY_ADDRESS, }); } public removeTokenToTokenByAddress(token: Token) { this.dispatch({ data: token, - type: ActionTypes.REMOVE_TOKEN_TO_TOKEN_BY_ADDRESS, + type: ActionTypes.REMOVE_TOKEN_TO_TOKEN_BY_ADDRESS, }); } public clearTokenByAddress() { @@ -136,13 +136,13 @@ export class Dispatcher { public updateTokenByAddress(tokens: Token[]) { this.dispatch({ data: tokens, - type: ActionTypes.UPDATE_TOKEN_BY_ADDRESS, + type: ActionTypes.UPDATE_TOKEN_BY_ADDRESS, }); } public updateTokenStateByAddress(tokenStateByAddress: TokenStateByAddress) { this.dispatch({ data: tokenStateByAddress, - type: ActionTypes.UPDATE_TOKEN_STATE_BY_ADDRESS, + type: ActionTypes.UPDATE_TOKEN_STATE_BY_ADDRESS, }); } public removeFromTokenStateByAddress(tokenAddress: string) { @@ -181,19 +181,19 @@ export class Dispatcher { public updateSignatureData(signatureData: SignatureData) { this.dispatch({ data: signatureData, - type: ActionTypes.UPDATE_ORDER_SIGNATURE_DATA, + type: ActionTypes.UPDATE_ORDER_SIGNATURE_DATA, }); } public updateUserEtherBalance(balance: BigNumber) { this.dispatch({ data: balance, - type: ActionTypes.UPDATE_USER_ETHER_BALANCE, + type: ActionTypes.UPDATE_USER_ETHER_BALANCE, }); } public updateNetworkId(networkId: number) { this.dispatch({ data: networkId, - type: ActionTypes.UPDATE_NETWORK_ID, + type: ActionTypes.UPDATE_NETWORK_ID, }); } public updateOrderFillAmount(amount: BigNumber) { -- cgit v1.2.3 From 6a9f10cd36e254bfb43f13e5728b5346823b5cfa Mon Sep 17 00:00:00 2001 From: Fabio Berger Date: Thu, 23 Nov 2017 13:57:01 -0600 Subject: Add error when unexpected condition hit --- packages/website/ts/components/trade_history/trade_history_item.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/website/ts/components/trade_history/trade_history_item.tsx b/packages/website/ts/components/trade_history/trade_history_item.tsx index 96b755e3c..58bdf84ae 100644 --- a/packages/website/ts/components/trade_history/trade_history_item.tsx +++ b/packages/website/ts/components/trade_history/trade_history_item.tsx @@ -122,6 +122,9 @@ export class TradeHistoryItem extends React.Component Date: Thu, 23 Nov 2017 13:58:27 -0600 Subject: update tokenByAddress after tokenStateByAddress to avoid race-condition --- packages/website/ts/components/generate_order/asset_picker.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/website/ts/components/generate_order/asset_picker.tsx b/packages/website/ts/components/generate_order/asset_picker.tsx index 59826d06e..410b0440a 100644 --- a/packages/website/ts/components/generate_order/asset_picker.tsx +++ b/packages/website/ts/components/generate_order/asset_picker.tsx @@ -269,7 +269,6 @@ export class AssetPicker extends React.Component Date: Thu, 23 Nov 2017 14:01:23 -0600 Subject: Add exit code 0 because it is expected that uglifying the code throws an error --- packages/website/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/website/package.json b/packages/website/package.json index d6d92d0f8..f91b579bd 100644 --- a/packages/website/package.json +++ b/packages/website/package.json @@ -3,7 +3,7 @@ "version": "0.0.0", "description": "Website and 0x portal dapp", "scripts": { - "build": "NODE_ENV=production webpack", + "build": "NODE_ENV=production webpack; exit 0;", "clean": "shx rm -f public/bundle*", "dev": "webpack-dev-server --content-base public --https", "update_contracts": "for i in ${npm_package_config_artifacts}; do copyfiles -u 4 ../contracts/build/contracts/$i.json ../website/contracts; done;", -- cgit v1.2.3 From ecfee00feca331ee1efa55165471d79774cb03d2 Mon Sep 17 00:00:00 2001 From: Fabio Berger Date: Thu, 23 Nov 2017 18:21:48 -0600 Subject: Fix subscriptions given latest changes to 0x.js --- packages/website/ts/blockchain.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/packages/website/ts/blockchain.ts b/packages/website/ts/blockchain.ts index 9be764917..b13c48a65 100644 --- a/packages/website/ts/blockchain.ts +++ b/packages/website/ts/blockchain.ts @@ -488,6 +488,7 @@ export class Blockchain { const subscriptionId = await this.zeroEx.exchange.subscribeAsync( ExchangeEvents.LogFill, indexFilterValues, async (err: Error, decodedLogEvent: DecodedLogEvent) => { + const decodedLog = decodedLogEvent.log; if (err) { // Note: it's not entirely clear from the documentation which // errors will be thrown by `watch`. For now, let's log the error @@ -496,12 +497,12 @@ export class Blockchain { this.stopWatchingExchangeLogFillEventsAsync(); // fire and forget return; } else { - if (!this.doesLogEventInvolveUser(decodedLogEvent)) { + if (!this.doesLogEventInvolveUser(decodedLog)) { return; // We aren't interested in the fill event } - this.updateLatestFillsBlockIfNeeded(decodedLogEvent.blockNumber); - const fill = await this.convertDecodedLogToFillAsync(decodedLogEvent); - if (decodedLogEvent.removed) { + this.updateLatestFillsBlockIfNeeded(decodedLog.blockNumber); + const fill = await this.convertDecodedLogToFillAsync(decodedLog); + if (decodedLogEvent.isRemoved) { tradeHistoryStorage.removeFillFromUser(this.userAddress, this.networkId, fill); } else { tradeHistoryStorage.addFillToUser(this.userAddress, this.networkId, fill); -- cgit v1.2.3