Merge branch 'master' into ab-poa-block-emission-rewards

pull/2505/head
Ayrat Badykov 5 years ago committed by GitHub
commit 57374668c0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      .credo.exs
  2. 2
      .dialyzer-ignore
  3. 66
      CHANGELOG.md
  4. 1
      ISSUE_TEMPLATE.md
  5. 3
      PULL_REQUEST_TEMPLATE.md
  6. 2
      apps/block_scout_web/.sobelow-conf
  7. 4
      apps/block_scout_web/assets/__tests__/lib/async_listing_load.js
  8. 15
      apps/block_scout_web/assets/css/_images-preload.scss
  9. 24
      apps/block_scout_web/assets/css/app.scss
  10. 4
      apps/block_scout_web/assets/css/components/_api.scss
  11. 1
      apps/block_scout_web/assets/css/components/_button.scss
  12. 5
      apps/block_scout_web/assets/css/components/_form.scss
  13. 6
      apps/block_scout_web/assets/css/components/_modal.scss
  14. 5
      apps/block_scout_web/assets/css/components/_modal_variables.scss
  15. 34
      apps/block_scout_web/assets/css/components/_network-selector.scss
  16. 2
      apps/block_scout_web/assets/css/components/_stakes_variables.scss
  17. 0
      apps/block_scout_web/assets/css/components/stakes/_copy_icon.scss
  18. 0
      apps/block_scout_web/assets/css/components/stakes/_modal_become_candidate.scss
  19. 0
      apps/block_scout_web/assets/css/components/stakes/_modal_bottom_disclaimer.scss
  20. 0
      apps/block_scout_web/assets/css/components/stakes/_modal_stake.scss
  21. 0
      apps/block_scout_web/assets/css/components/stakes/_modal_validator_info.scss
  22. 0
      apps/block_scout_web/assets/css/components/stakes/_progress_from_to.scss
  23. 2
      apps/block_scout_web/assets/css/components/stakes/_stakes.scss
  24. 0
      apps/block_scout_web/assets/css/components/stakes/_stakes_btn_remove_pool.scss
  25. 0
      apps/block_scout_web/assets/css/components/stakes/_stakes_empty_content.scss
  26. 0
      apps/block_scout_web/assets/css/components/stakes/_stakes_progress.scss
  27. 17
      apps/block_scout_web/assets/css/non-critical.scss
  28. 20
      apps/block_scout_web/assets/css/stakes.scss
  29. 2
      apps/block_scout_web/assets/css/theme/_base_variables.scss
  30. 7
      apps/block_scout_web/assets/css/theme/_dai_variables-non-critical.scss
  31. 1
      apps/block_scout_web/assets/css/theme/_dai_variables.scss
  32. 106
      apps/block_scout_web/assets/css/theme/_dark-theme.scss
  33. 7
      apps/block_scout_web/assets/css/theme/_ether1_variables-non-critical.scss
  34. 7
      apps/block_scout_web/assets/css/theme/_ethereum_classic_variables-non-critical.scss
  35. 1
      apps/block_scout_web/assets/css/theme/_ethereum_classic_variables.scss
  36. 7
      apps/block_scout_web/assets/css/theme/_ethereum_variables-non-critical.scss
  37. 1
      apps/block_scout_web/assets/css/theme/_ethereum_variables.scss
  38. 8
      apps/block_scout_web/assets/css/theme/_goerli_variables-non-critical.scss
  39. 1
      apps/block_scout_web/assets/css/theme/_goerli_variables.scss
  40. 7
      apps/block_scout_web/assets/css/theme/_kovan_variables-non-critical.scss
  41. 1
      apps/block_scout_web/assets/css/theme/_kovan_variables.scss
  42. 7
      apps/block_scout_web/assets/css/theme/_lukso_variables-non-critical.scss
  43. 1
      apps/block_scout_web/assets/css/theme/_lukso_variables.scss
  44. 7
      apps/block_scout_web/assets/css/theme/_neutral_variables-non-critical.scss
  45. 1
      apps/block_scout_web/assets/css/theme/_neutral_variables.scss
  46. 7
      apps/block_scout_web/assets/css/theme/_poa_variables-non-critical.scss
  47. 1
      apps/block_scout_web/assets/css/theme/_poa_variables.scss
  48. 7
      apps/block_scout_web/assets/css/theme/_rinkeby_variables-non-critical.scss
  49. 1
      apps/block_scout_web/assets/css/theme/_rinkeby_variables.scss
  50. 7
      apps/block_scout_web/assets/css/theme/_ropsten_variables-non-critical.scss
  51. 1
      apps/block_scout_web/assets/css/theme/_ropsten_variables.scss
  52. 7
      apps/block_scout_web/assets/css/theme/_rsk_variables-non-critical.scss
  53. 1
      apps/block_scout_web/assets/css/theme/_rsk_variables.scss
  54. 8
      apps/block_scout_web/assets/css/theme/_sokol_variables-non-critical.scss
  55. 1
      apps/block_scout_web/assets/css/theme/_sokol_variables.scss
  56. 22
      apps/block_scout_web/assets/css/theme/_variables-non-critical.scss
  57. 4
      apps/block_scout_web/assets/js/app.js
  58. 30
      apps/block_scout_web/assets/js/lib/async_listing_load.js
  59. 637
      apps/block_scout_web/assets/js/lib/awesomplete-util.js
  60. 2
      apps/block_scout_web/assets/js/lib/awesomplete.js
  61. 10
      apps/block_scout_web/assets/js/lib/coin_balance_history_chart.js
  62. 31
      apps/block_scout_web/assets/js/lib/token_transfers_toggle.js
  63. 2
      apps/block_scout_web/assets/js/lib/tooltip.js
  64. 70
      apps/block_scout_web/assets/js/lib/try_eth_api.js
  65. 2
      apps/block_scout_web/assets/js/pages/address/logs.js
  66. 1
      apps/block_scout_web/assets/js/pages/stakes.js
  67. 3104
      apps/block_scout_web/assets/package-lock.json
  68. 10
      apps/block_scout_web/assets/package.json
  69. 54
      apps/block_scout_web/assets/webpack.config.js
  70. 15
      apps/block_scout_web/config/config.exs
  71. 3
      apps/block_scout_web/config/test.exs
  72. 3
      apps/block_scout_web/lib/block_scout_web.ex
  73. 73
      apps/block_scout_web/lib/block_scout_web/api_router.ex
  74. 1
      apps/block_scout_web/lib/block_scout_web/application.ex
  75. 9
      apps/block_scout_web/lib/block_scout_web/chain.ex
  76. 2
      apps/block_scout_web/lib/block_scout_web/channels/address_channel.ex
  77. 12
      apps/block_scout_web/lib/block_scout_web/cldr.ex
  78. 7
      apps/block_scout_web/lib/block_scout_web/controllers/address_coin_balance_by_day_controller.ex
  79. 15
      apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/rpc_translator.ex
  80. 4
      apps/block_scout_web/lib/block_scout_web/csp_header.ex
  81. 13
      apps/block_scout_web/lib/block_scout_web/etherscan.ex
  82. 2
      apps/block_scout_web/lib/block_scout_web/notifier.ex
  83. 266
      apps/block_scout_web/lib/block_scout_web/router.ex
  84. 2
      apps/block_scout_web/lib/block_scout_web/schema/types.ex
  85. 7
      apps/block_scout_web/lib/block_scout_web/templates/address/overview.html.eex
  86. 10
      apps/block_scout_web/lib/block_scout_web/templates/address_logs/_logs.html.eex
  87. 182
      apps/block_scout_web/lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex
  88. 20
      apps/block_scout_web/lib/block_scout_web/templates/api_docs/eth_rpc.html.eex
  89. 2
      apps/block_scout_web/lib/block_scout_web/templates/api_docs/index.html.eex
  90. 16
      apps/block_scout_web/lib/block_scout_web/templates/block/overview.html.eex
  91. 3
      apps/block_scout_web/lib/block_scout_web/templates/chain/_block.html.eex
  92. 6
      apps/block_scout_web/lib/block_scout_web/templates/chain/show.html.eex
  93. 17
      apps/block_scout_web/lib/block_scout_web/templates/common_components/_pagination_container.html.eex
  94. 6
      apps/block_scout_web/lib/block_scout_web/templates/common_components/_tile-loader.html.eex
  95. 3
      apps/block_scout_web/lib/block_scout_web/templates/internal_transaction/_tile.html.eex
  96. 2
      apps/block_scout_web/lib/block_scout_web/templates/layout/_footer.html.eex
  97. 2
      apps/block_scout_web/lib/block_scout_web/templates/layout/_network_selector_item.html.eex
  98. 223
      apps/block_scout_web/lib/block_scout_web/templates/layout/_topnav.html.eex
  99. 14
      apps/block_scout_web/lib/block_scout_web/templates/layout/app.html.eex
  100. 2
      apps/block_scout_web/lib/block_scout_web/templates/tokens/holder/_token_balances.html.eex
  101. Some files were not shown because too many files have changed in this diff Show More

@ -89,7 +89,7 @@
# or the `schema` macro in Ecto schemas to trigger DuplicatedCode, just # or the `schema` macro in Ecto schemas to trigger DuplicatedCode, just
# set the `excluded_macros` parameter to `[:schema, :setup, :test]`. # set the `excluded_macros` parameter to `[:schema, :setup, :test]`.
# #
{Credo.Check.Design.DuplicatedCode, false}, {Credo.Check.Design.DuplicatedCode, excluded_macros: [], mass_threshold: 80},
# You can also customize the exit_status of each check. # You can also customize the exit_status of each check.
# If you don't want TODO comments to cause `mix credo` to fail, just # If you don't want TODO comments to cause `mix credo` to fail, just

@ -11,3 +11,5 @@ apps/explorer/lib/explorer/repo/prometheus_logger.ex:8: The call 'Elixir.System'
apps/block_scout_web/lib/block_scout_web/views/layout_view.ex:175: The call 'Elixir.Poison.Parser':'parse!'(any(),#{'keys':='atoms!'}) will never return since the success typing is (binary() | maybe_improper_list(binary() | maybe_improper_list(any(),binary() | []) | byte(),binary() | []),[{atom(),_}]) -> 'false' | 'nil' | 'true' | binary() | ['false' | 'nil' | 'true' | binary() | [any()] | number() | map()] | number() | map() and the contract is (iodata(),'Elixir.Keyword':t()) -> t() apps/block_scout_web/lib/block_scout_web/views/layout_view.ex:175: The call 'Elixir.Poison.Parser':'parse!'(any(),#{'keys':='atoms!'}) will never return since the success typing is (binary() | maybe_improper_list(binary() | maybe_improper_list(any(),binary() | []) | byte(),binary() | []),[{atom(),_}]) -> 'false' | 'nil' | 'true' | binary() | ['false' | 'nil' | 'true' | binary() | [any()] | number() | map()] | number() | map() and the contract is (iodata(),'Elixir.Keyword':t()) -> t()
apps/explorer/lib/explorer/smart_contract/publisher_worker.ex:6: The pattern 'false' can never match the type 'true' apps/explorer/lib/explorer/smart_contract/publisher_worker.ex:6: The pattern 'false' can never match the type 'true'
apps/explorer/lib/explorer/smart_contract/publisher_worker.ex:6: The test 5 == 'infinity' can never evaluate to 'true' apps/explorer/lib/explorer/smart_contract/publisher_worker.ex:6: The test 5 == 'infinity' can never evaluate to 'true'
lib/block_scout_web/router.ex:1
lib/phoenix/router.ex:324

@ -1,24 +1,83 @@
## Current ## Current
### Features ### Features
- [#2561](https://github.com/poanetwork/blockscout/pull/2561) - Add token's type to the response of tokenlist method
- [#2499](https://github.com/poanetwork/blockscout/pull/2499) - import emission reward ranges
- [#2497](https://github.com/poanetwork/blockscout/pull/2497) - Add generic Ordered Cache behaviour and implementation
### Fixes
- [#2572](https://github.com/poanetwork/blockscout/pull/2572) - Ease non-critical css
- [#2570](https://github.com/poanetwork/blockscout/pull/2570) - Network icons preload
- [#2569](https://github.com/poanetwork/blockscout/pull/2569) - do not fetch emission rewards for transactions csv exporter
- [#2568](https://github.com/poanetwork/blockscout/pull/2568) - filter pending token transfers
- [#2564](https://github.com/poanetwork/blockscout/pull/2564) - fix first page button for uncles and reorgs
- [#2563](https://github.com/poanetwork/blockscout/pull/2563) - Fix view less transfers button
- [#2538](https://github.com/poanetwork/blockscout/pull/2538) - fetch the last not empty coin balance records
### Chore
- [#2566](https://github.com/poanetwork/blockscout/pull/2566) - upgrade absinthe phoenix
## 2.0.3-beta
### Features
- [#2433](https://github.com/poanetwork/blockscout/pull/2433) - Add a functionality to try Eth RPC methods in the documentation
- [#2529](https://github.com/poanetwork/blockscout/pull/2529) - show both eth value and token transfers on transaction overview page
- [#2376](https://github.com/poanetwork/blockscout/pull/2376) - Split API and WebApp routes
- [#2477](https://github.com/poanetwork/blockscout/pull/2477) - aggregate token transfers on transaction page - [#2477](https://github.com/poanetwork/blockscout/pull/2477) - aggregate token transfers on transaction page
- [#2458](https://github.com/poanetwork/blockscout/pull/2458) - Add LAST_BLOCK var to add ability indexing in the range of blocks - [#2458](https://github.com/poanetwork/blockscout/pull/2458) - Add LAST_BLOCK var to add ability indexing in the range of blocks
- [#2456](https://github.com/poanetwork/blockscout/pull/2456) - fetch pending transactions for geth - [#2456](https://github.com/poanetwork/blockscout/pull/2456) - fetch pending transactions for geth
- [#2403](https://github.com/poanetwork/blockscout/pull/2403) - Return gasPrice field at the result of gettxinfo method
### Fixes ### Fixes
- [#2562](https://github.com/poanetwork/blockscout/pull/2562) - Fix dark theme flickering
- [#2560](https://github.com/poanetwork/blockscout/pull/2560) - fix slash before not empty path in docs
- [#2559](https://github.com/poanetwork/blockscout/pull/2559) - fix rsk total supply for empty exchange rate
- [#2553](https://github.com/poanetwork/blockscout/pull/2553) - Dark theme import to the end of sass
- [#2550](https://github.com/poanetwork/blockscout/pull/2550) - correctly encode decimal values for frontend
- [#2549](https://github.com/poanetwork/blockscout/pull/2549) - Fix wrong colour of tooltip
- [#2548](https://github.com/poanetwork/blockscout/pull/2548) - CSS preload support in Firefox
- [#2547](https://github.com/poanetwork/blockscout/pull/2547) - do not show eth value if it's zero on the transaction overview page
- [#2543](https://github.com/poanetwork/blockscout/pull/2543) - do not hide search input during logs search
- [#2524](https://github.com/poanetwork/blockscout/pull/2524) - fix dark theme validator data styles
- [#2532](https://github.com/poanetwork/blockscout/pull/2532) - don't show empty token transfers on the transaction overview page
- [#2528](https://github.com/poanetwork/blockscout/pull/2528) - fix coin history chart data
- [#2520](https://github.com/poanetwork/blockscout/pull/2520) - Hide loading message when fetching is failed
- [#2523](https://github.com/poanetwork/blockscout/pull/2523) - Avoid importing internal_transactions of pending transactions
- [#2519](https://github.com/poanetwork/blockscout/pull/2519) - enable `First` page button in pagination
- [#2517](https://github.com/poanetwork/blockscout/pull/2517) - remove duplicate indexes
- [#2515](https://github.com/poanetwork/blockscout/pull/2515) - do not aggregate NFT token transfers
- [#2514](https://github.com/poanetwork/blockscout/pull/2514) - Isolating of staking dapp css && extracting of non-critical css
- [#2512](https://github.com/poanetwork/blockscout/pull/2512) - alert link fix
- [#2509](https://github.com/poanetwork/blockscout/pull/2509) - value-ticker gaps fix
- [#2508](https://github.com/poanetwork/blockscout/pull/2508) - logs view columns fix
- [#2506](https://github.com/poanetwork/blockscout/pull/2506) - fix two active tab in the top menu
- [#2503](https://github.com/poanetwork/blockscout/pull/2503) - Mitigate autocompletion library influence to page loading performance
- [#2502](https://github.com/poanetwork/blockscout/pull/2502) - increase reward task timeout
- [#2463](https://github.com/poanetwork/blockscout/pull/2463) - dark theme fixes - [#2463](https://github.com/poanetwork/blockscout/pull/2463) - dark theme fixes
- [#2496](https://github.com/poanetwork/blockscout/pull/2496) - fix docker build - [#2496](https://github.com/poanetwork/blockscout/pull/2496) - fix docker build
- [#2495](https://github.com/poanetwork/blockscout/pull/2495) - fix logs for indexed chain - [#2495](https://github.com/poanetwork/blockscout/pull/2495) - fix logs for indexed chain
- [#2459](https://github.com/poanetwork/blockscout/pull/2459) - fix top addresses query - [#2459](https://github.com/poanetwork/blockscout/pull/2459) - fix top addresses query
- [#2425](https://github.com/poanetwork/blockscout/pull/2425) - Force to show address view for checksummed address even if it is not in DB - [#2425](https://github.com/poanetwork/blockscout/pull/2425) - Force to show address view for checksummed address even if it is not in DB
- [#2551](https://github.com/poanetwork/blockscout/pull/2551) - Correctly handle dynamically created Bootstrap tooltips
### Chore ### Chore
- [#2554](https://github.com/poanetwork/blockscout/pull/2554) - remove extra slash for endpoint url in docs
- [#2552](https://github.com/poanetwork/blockscout/pull/2552) - remove brackets for token holders percentage
- [#2507](https://github.com/poanetwork/blockscout/pull/2507) - update minor version of ecto, ex_machina, phoenix_live_reload
- [#2516](https://github.com/poanetwork/blockscout/pull/2516) - update absinthe plug from fork
- [#2473](https://github.com/poanetwork/blockscout/pull/2473) - get rid of cldr warnings
- [#2402](https://github.com/poanetwork/blockscout/pull/2402) - bump otp version to 22.0 - [#2402](https://github.com/poanetwork/blockscout/pull/2402) - bump otp version to 22.0
- [#2492](https://github.com/poanetwork/blockscout/pull/2492) - hide decoded row if event is not decoded
- [#2490](https://github.com/poanetwork/blockscout/pull/2490) - enable credo duplicated code check
- [#2432](https://github.com/poanetwork/blockscout/pull/2432) - bump credo version - [#2432](https://github.com/poanetwork/blockscout/pull/2432) - bump credo version
- [#2457](https://github.com/poanetwork/blockscout/pull/2457) - update mix.lock - [#2457](https://github.com/poanetwork/blockscout/pull/2457) - update mix.lock
- [#2435](https://github.com/poanetwork/blockscout/pull/2435) - Replace deprecated extract-text-webpack-plugin with mini-css-extract-plugin - [#2435](https://github.com/poanetwork/blockscout/pull/2435) - Replace deprecated extract-text-webpack-plugin with mini-css-extract-plugin
- [#2450](https://github.com/poanetwork/blockscout/pull/2450) - Fix clearance of logs and node_modules folders in clearing script - [#2450](https://github.com/poanetwork/blockscout/pull/2450) - Fix clearance of logs and node_modules folders in clearing script
- [#2434](https://github.com/poanetwork/blockscout/pull/2434) - get rid of timex warnings - [#2434](https://github.com/poanetwork/blockscout/pull/2434) - get rid of timex warnings
- [#2402](https://github.com/poanetwork/blockscout/pull/2402) - bump otp version to 22.0
- [#2373](https://github.com/poanetwork/blockscout/pull/2373) - Add script to validate internal_transactions constraint for large DBs
## 2.0.2-beta ## 2.0.2-beta
@ -164,6 +223,7 @@
- [#2255](https://github.com/poanetwork/blockscout/pull/2255) - upgrade elixir version to 1.9.0 - [#2255](https://github.com/poanetwork/blockscout/pull/2255) - upgrade elixir version to 1.9.0
- [#2256](https://github.com/poanetwork/blockscout/pull/2256) - use the latest version of chromedriver - [#2256](https://github.com/poanetwork/blockscout/pull/2256) - use the latest version of chromedriver
## 2.0.0-beta ## 2.0.0-beta
### Features ### Features
@ -246,6 +306,7 @@
- [#2055](https://github.com/poanetwork/blockscout/pull/2055) - Increase timeout for geth indexers - [#2055](https://github.com/poanetwork/blockscout/pull/2055) - Increase timeout for geth indexers
- [#2069](https://github.com/poanetwork/blockscout/pull/2069) - Docsify integration: static docs page generation - [#2069](https://github.com/poanetwork/blockscout/pull/2069) - Docsify integration: static docs page generation
## 1.3.15-beta ## 1.3.15-beta
### Features ### Features
@ -275,6 +336,7 @@
- [#1892](https://github.com/poanetwork/blockscout/pull/1892) - Remove temporary worker modules - [#1892](https://github.com/poanetwork/blockscout/pull/1892) - Remove temporary worker modules
## 1.3.13-beta ## 1.3.13-beta
### Features ### Features
@ -287,10 +349,12 @@
- [#1881](https://github.com/poanetwork/blockscout/pull/1881) - fix: store solc versions locally for performance - [#1881](https://github.com/poanetwork/blockscout/pull/1881) - fix: store solc versions locally for performance
- [#1898](https://github.com/poanetwork/blockscout/pull/1898) - check if the constructor has arguments before verifying constructor arguments - [#1898](https://github.com/poanetwork/blockscout/pull/1898) - check if the constructor has arguments before verifying constructor arguments
## 1.3.12-beta ## 1.3.12-beta
Reverting of synchronous block counter, implemented in #1848 Reverting of synchronous block counter, implemented in #1848
## 1.3.11-beta ## 1.3.11-beta
### Features ### Features
@ -312,6 +376,7 @@ Reverting of synchronous block counter, implemented in #1848
- [#1814](https://github.com/poanetwork/blockscout/pull/1814) - Clear build artefacts script - [#1814](https://github.com/poanetwork/blockscout/pull/1814) - Clear build artefacts script
- [#1837](https://github.com/poanetwork/blockscout/pull/1837) - Add -f flag to clear_build.sh script delete static folder - [#1837](https://github.com/poanetwork/blockscout/pull/1837) - Add -f flag to clear_build.sh script delete static folder
## 1.3.10-beta ## 1.3.10-beta
### Features ### Features
@ -422,6 +487,7 @@ Reverting of synchronous block counter, implemented in #1848
- [#1610](https://github.com/poanetwork/blockscout/pull/1610) - Add PIRL to Readme - [#1610](https://github.com/poanetwork/blockscout/pull/1610) - Add PIRL to Readme
## 1.3.6-beta ## 1.3.6-beta
### Features ### Features

@ -4,6 +4,7 @@
* Elixir & Erlang/OTP versions (`elixir -version`): * Elixir & Erlang/OTP versions (`elixir -version`):
* Operating System: * Operating System:
* Blockscout Version/branch:
### Steps to reproduce ### Steps to reproduce

@ -35,4 +35,5 @@
- [ ] If I added new functionality, I added tests covering it. - [ ] If I added new functionality, I added tests covering it.
- [ ] If I fixed a bug, I added a regression test to prevent the bug from silently reappearing again. - [ ] If I fixed a bug, I added a regression test to prevent the bug from silently reappearing again.
- [ ] I checked whether I should update the docs and did so if necessary - [ ] I checked whether I should update the docs and did so if necessary
- [ ] If I added/changed/removed ENV var, I should update the list of env vars in https://github.com/poanetwork/blockscout/blob/master/docs/env-variables.md to reflect changes in the table here https://poanetwork.github.io/blockscout/#/env-variables?id=blockscout-env-variables - [ ] If I added/changed/removed ENV var, I should update the list of env vars in https://github.com/poanetwork/blockscout/blob/master/docs/env-variables.md to reflect changes in the table here https://poanetwork.github.io/blockscout/#/env-variables?id=blockscout-env-variables. I've set `master` in the `Version` column.
- [ ] If I add new indices into DB, I checked, that they don't redundant with PGHero or other tools

@ -5,5 +5,5 @@
router: "lib/block_scout_web/router.ex", router: "lib/block_scout_web/router.ex",
exit: "low", exit: "low",
format: "compact", format: "compact",
ignore: ["Config.Headers"] ignore: ["Config.Headers", "Config.CSWH"]
] ]

@ -46,14 +46,12 @@ describe('REQUEST_ERROR', () => {
describe('FINISH_REQUEST', () => { describe('FINISH_REQUEST', () => {
test('sets loading status to false', () => { test('sets loading status to false', () => {
const state = Object.assign({}, asyncInitialState, { const state = Object.assign({}, asyncInitialState, {
loading: true, loading: true
loadingFirstPage: true
}) })
const action = { type: 'FINISH_REQUEST' } const action = { type: 'FINISH_REQUEST' }
const output = asyncReducer(state, action) const output = asyncReducer(state, action)
expect(output.loading).toEqual(false) expect(output.loading).toEqual(false)
expect(output.loadingFirstPage).toEqual(false)
}) })
}) })

@ -0,0 +1,15 @@
body:after {
position:absolute; width:0; height:0; overflow:hidden; z-index:-1;
content:
url(/images/network-selector-icons/callisto-mainnet.png)
url(/images/network-selector-icons/ethereum-mainnet.png)
url(/images/network-selector-icons/ethereum-classic.png)
url(/images/network-selector-icons/goerli-testnet.png)
url(/images/network-selector-icons/kovan-testnet.png)
url(/images/network-selector-icons/poa-core.png)
url(/images/network-selector-icons/poa-sokol.png)
url(/images/network-selector-icons/rinkeby-testnet.png)
url(/images/network-selector-icons/rsk-mainnet.png)
url(/images/network-selector-icons/ropsten-testnet.png)
url(/images/network-selector-icons/xdai-chain.png)
};

@ -24,7 +24,6 @@ $fa-font-path: "~@fortawesome/fontawesome-free/webfonts";
@import "node_modules/bootstrap/scss/reboot"; @import "node_modules/bootstrap/scss/reboot";
@import "node_modules/bootstrap/scss/grid"; @import "node_modules/bootstrap/scss/grid";
@import "node_modules/bootstrap/scss/code"; @import "node_modules/bootstrap/scss/code";
@import "node_modules/bootstrap/scss/modal";
@import "node_modules/bootstrap/scss/close"; @import "node_modules/bootstrap/scss/close";
@import "node_modules/bootstrap/scss/buttons"; @import "node_modules/bootstrap/scss/buttons";
@import "node_modules/bootstrap/scss/forms"; @import "node_modules/bootstrap/scss/forms";
@ -50,7 +49,6 @@ $fa-font-path: "~@fortawesome/fontawesome-free/webfonts";
@import "node_modules/bootstrap/scss/navbar"; @import "node_modules/bootstrap/scss/navbar";
@import "node_modules/bootstrap/scss/pagination"; @import "node_modules/bootstrap/scss/pagination";
@import "node_modules/bootstrap/scss/tables"; @import "node_modules/bootstrap/scss/tables";
@import "node_modules/bootstrap/scss/tooltip";
@import "node_modules/bootstrap/scss/transitions"; @import "node_modules/bootstrap/scss/transitions";
// Code highlight // Code highlight
@ -62,6 +60,7 @@ $fa-font-path: "~@fortawesome/fontawesome-free/webfonts";
// Custom SCSS // Custom SCSS
@import "layout"; @import "layout";
@import "typography"; @import "typography";
@import "images-preload";
@import "code"; @import "code";
@import "helpers"; @import "helpers";
@import "elements"; @import "elements";
@ -75,8 +74,8 @@ $fa-font-path: "~@fortawesome/fontawesome-free/webfonts";
@import "components/filter"; @import "components/filter";
@import "components/button"; @import "components/button";
@import "components/table"; @import "components/table";
@import "components/qr-code";
@import "components/navbar"; @import "components/navbar";
@import "components/alerts";
@import "components/animations"; @import "components/animations";
@import "components/card"; @import "components/card";
@import "components/tile"; @import "components/tile";
@ -93,41 +92,28 @@ $fa-font-path: "~@fortawesome/fontawesome-free/webfonts";
@import "components/transaction-input"; @import "components/transaction-input";
@import "components/coin-balance-tile"; @import "components/coin-balance-tile";
@import "components/highlight"; @import "components/highlight";
@import "components/copy_icon";
@import "components/btn_full"; @import "components/btn_full";
@import "components/btn_line"; @import "components/btn_line";
@import "components/stakes";
@import "components/check"; @import "components/check";
@import "components/stakes_variables";
@import "components/stakes_table"; @import "components/stakes_table";
@import "components/i_tooltip";
@import "components/check_tooltip";
@import "components/tooltip";
@import "components/progress_from_to";
@import "components/stakes_empty_content";
@import "components/stakes_btn_remove_pool";
@import "components/modal";
@import "components/modal_validator_info";
@import "components/form"; @import "components/form";
@import "components/stakes_progress";
@import "components/modal_status";
@import "components/modal_bottom_disclaimer";
@import "components/modal_become_candidate";
@import "components/modal_stake";
@import "components/btn_copy"; @import "components/btn_copy";
@import "components/btn_qr"; @import "components/btn_qr";
@import "components/btn_address_card"; @import "components/btn_address_card";
@import "components/btn_dropdown_line"; @import "components/btn_dropdown_line";
@import "components/transaction"; @import "components/transaction";
@import "components/api"; @import "components/api";
@import "components/alerts";
@import "components/verify_other_explorers"; @import "components/verify_other_explorers";
@import "components/errors"; @import "components/errors";
@import "components/log-search"; @import "components/log-search";
@import "components/radio"; @import "components/radio";
@import "components/modal_variables";
@import "components/network-selector"; @import "components/network-selector";
@import "components/new_smart_contract"; @import "components/new_smart_contract";
@import "components/radio_big"; @import "components/radio_big";
@import "components/btn_no_border"; @import "components/btn_no_border";
@import "theme/dark-theme"; @import "theme/dark-theme";
:export { :export {

@ -116,6 +116,10 @@ $api-doc-list-item-view-more-color: $api-doc-list-item-title-color !default;
margin: 0; margin: 0;
} }
.api-doc-list-item-description {
width: 100%
}
.api-doc-list-item-controls { .api-doc-list-item-controls {
display: flex; display: flex;
flex-direction: column; flex-direction: column;

@ -23,6 +23,7 @@ $button-secondary-color: $secondary !default;
background-color: darken($button-primary-color, 10%); background-color: darken($button-primary-color, 10%);
border-color: darken($button-primary-color, 10%); border-color: darken($button-primary-color, 10%);
color: #fff; color: #fff;
outline: none !important;
text-decoration: none; text-decoration: none;
} }

@ -9,6 +9,11 @@ $form-control-border-color: #e2e5ec !default;
border-radius: 4px; border-radius: 4px;
} }
&:focus {
border-color: $secondary;
box-shadow: none;
}
&.n-b-r { &.n-b-r {
border-right: none; border-right: none;
} }

@ -1,9 +1,3 @@
$modal-overlay-color: rgba($primary, 0.9) !default;
$modal-horizontal-padding: 30px !default;
$modal-vertical-padding: 25px !default;
$modal-border-radius: 10px !default;
$modal-gray-background: #f6f7f9 !default;
.modal-backdrop { .modal-backdrop {
background-color: $modal-overlay-color; background-color: $modal-overlay-color;

@ -0,0 +1,5 @@
$modal-overlay-color: rgba($primary, 0.9) !default;
$modal-horizontal-padding: 30px !default;
$modal-vertical-padding: 25px !default;
$modal-border-radius: 10px !default;
$modal-gray-background: #f6f7f9 !default;

@ -243,6 +243,40 @@ $network-selector-item-icon-dimensions: 30px !default;
height: $network-selector-item-icon-dimensions; height: $network-selector-item-icon-dimensions;
margin: 0 15px 0 0; margin: 0 15px 0 0;
width: $network-selector-item-icon-dimensions; width: $network-selector-item-icon-dimensions;
&-callisto-mainnet {
background-image: url(/images/network-selector-icons/callisto-mainnet.png)
}
&-ethereum-mainnet {
background-image: url(/images/network-selector-icons/ethereum-mainnet.png)
}
&-ethereum-classic {
background-image: url(/images/network-selector-icons/ethereum-classic.png)
}
&-goerli-testnet {
background-image: url(/images/network-selector-icons/goerli-testnet.png)
}
&-kovan-testnet {
background-image: url(/images/network-selector-icons/kovan-testnet.png)
}
&-poa-core {
background-image: url(/images/network-selector-icons/poa-core.png)
}
&-poa-sokol {
background-image: url(/images/network-selector-icons/poa-sokol.png)
}
&-rinkeby-testnet {
background-image: url(/images/network-selector-icons/rinkeby-testnet.png)
}
&-rsk-mainnet {
background-image: url(/images/network-selector-icons/rsk-mainnet.png)
}
&-ropsten-testnet {
background-image: url(/images/network-selector-icons/ropsten-testnet.png)
}
&-xdai-chain {
background-image: url(/images/network-selector-icons/xdai-chain.png)
}
} }
.network-selector-item-title { .network-selector-item-title {

@ -0,0 +1,2 @@
$stakes-banned-background: #fff3f7 !default;
$stakes-banned-color: #ff7986 !default;

@ -1,8 +1,6 @@
$stakes-dashboard-copy-icon-color: $copy-icon-color !default; $stakes-dashboard-copy-icon-color: $copy-icon-color !default;
$stakes-address-color: $primary !default; $stakes-address-color: $primary !default;
$stakes-control-color: $primary !default; $stakes-control-color: $primary !default;
$stakes-banned-color: #ff7986 !default;
$stakes-banned-background: #fff3f7 !default;
$stakes-stats-item-color: #fff !default; $stakes-stats-item-color: #fff !default;
$stakes-stats-item-border-color: #fff !default; $stakes-stats-item-border-color: #fff !default;

@ -0,0 +1,17 @@
// Bootstrap Core CSS
@import "node_modules/bootstrap/scss/functions";
@import "node_modules/bootstrap/scss/mixins";
@import "theme/variables-non-critical";
@import "node_modules/bootstrap/scss/modal";
@import "node_modules/bootstrap/scss/tooltip";
@import "components/i_tooltip";
@import "components/check_tooltip";
@import "components/tooltip";
@import "components/qr-code";
@import "components/modal_variables";
@import "components/modal";
@import "components/modal_status";

@ -0,0 +1,20 @@
@import "./mixins";
// Bootstrap Core CSS
@import "node_modules/bootstrap/scss/functions";
@import "node_modules/bootstrap/scss/mixins";
@import "theme/variables";
@import "components/stakes_variables";
@import "components/stakes/copy_icon";
@import "components/stakes/stakes";
@import "components/stakes/progress_from_to";
@import "components/stakes/stakes_empty_content";
@import "components/stakes/stakes_btn_remove_pool";
@import "components/modal_variables";
@import "components/stakes/stakes_progress";
@import "components/stakes/modal_stake";
@import "components/stakes/modal_become_candidate";
@import "components/stakes/modal_validator_info";
@import "components/stakes/modal_bottom_disclaimer";

@ -359,7 +359,7 @@ $input-btn-padding-y: 0.375rem !default;
$input-btn-padding-x: 0.75rem !default; $input-btn-padding-x: 0.75rem !default;
$input-btn-line-height: $line-height-base !default; $input-btn-line-height: $line-height-base !default;
$input-btn-focus-width: 0.2rem !default; $input-btn-focus-width: 1px !default;
$input-btn-focus-color: rgba($component-active-bg, 0.25) !default; $input-btn-focus-color: rgba($component-active-bg, 0.25) !default;
$input-btn-focus-box-shadow: 0 0 0 $input-btn-focus-width $input-btn-focus-color !default; $input-btn-focus-box-shadow: 0 0 0 $input-btn-focus-width $input-btn-focus-color !default;

@ -0,0 +1,7 @@
// general
$primary: #17314f;
$secondary: #15bba6;
$tertiary: #93d7ff;
$additional-font: #fff;
$btn-line-color: $secondary; // button border and font color && hover bg color

@ -38,6 +38,7 @@ $btn-line-bg: #fff; // button bg
$btn-line-color: $secondary; // button border and font color && hover bg color $btn-line-color: $secondary; // button border and font color && hover bg color
$btn-copy-color: $secondary; // btn copy $btn-copy-color: $secondary; // btn copy
$btn-qr-color: $secondary; // btn qr-code $btn-qr-color: $secondary; // btn qr-code
$btn-address-card-icon-color: $secondary; // btn address color
//links & tile //links & tile
$tile-body-a-color: $secondary; $tile-body-a-color: $secondary;

@ -234,7 +234,7 @@ $labels-dark: #8a8dba; // header nav, labels
} }
} }
.btn-copy-icon, .btn-qr-icon { .btn-copy-icon, .btn-qr-icon, .btn-address-card-icon {
border-color: $dark-primary; border-color: $dark-primary;
path { path {
fill: $dark-primary; fill: $dark-primary;
@ -578,9 +578,41 @@ $labels-dark: #8a8dba; // header nav, labels
} }
} }
} }
.verify-other-explorers-cell {
.exp-logo { #explorersModal {
color: #333 !important; .modal-title {
color: #fff;
}
.text-muted {
color: $labels-dark;
}
.modal-footer {
border-top-color: darken($labels-dark, 30);
}
.modal-content {
background-color: $dark-light-bg;
.btn-primary {
background-color: $dark-primary;
border-color: $dark-primary;
&:hover {
background-color: $dark-primary;
border-color: $dark-primary;
}
}
}
.verify-other-explorers-cell {
.exp-logo {
color: #fff;
}
}
.close {
color: #fff;
} }
} }
@ -680,4 +712,70 @@ $labels-dark: #8a8dba; // header nav, labels
color: #3f436b !important; color: #3f436b !important;
border-right-color: #3f436b !important; border-right-color: #3f436b !important;
} }
// 'text dark' label
.text-dark {
color: #fff;
}
// validator info
#validatorModal {
.modal-title {
color: #fff;
}
.text-muted {
color: $labels-dark;
}
.modal-footer {
border-top-color: darken($labels-dark, 30);
}
.modal-content {
background-color: $dark-light-bg;
.btn-primary {
background-color: $dark-primary;
border-color: $dark-primary;
&:hover {
background-color: $dark-primary;
border-color: $dark-primary;
}
}
}
.close {
color: #fff;
}
}
// alerts
.alert-link {
color: $labels-dark;
}
.alert-danger {
background-color: $dark-light;
border-color: $dark-light;
.alert-link {
color: $alert-danger-color;
}
}
.tile .alert {
background: rgba(#000, .1);
}
// primary buttons
.btn-full-primary, .button-primary {
background: $dark-primary;
border-color: $dark-primary;
color: #fff;
&:hover {
background: darken($dark-primary, 6);
border-color: darken($dark-primary, 6);
color: #fff;
}
}
} }

@ -0,0 +1,7 @@
// general
$primary: #840032;
$secondary: #343434;
$tertiary: #7f7f7f;
$additional-font: #ff95db;
$btn-line-color: #4b021e; // button border and font color && hover bg color

@ -0,0 +1,7 @@
// general
$primary: #1c1c3d;
$secondary: #4ad7a7;
$tertiary: #5959d8;
$additional-font: #bdbdff;
$btn-line-color: $tertiary; // button border and font color && hover bg color

@ -34,6 +34,7 @@ $btn-line-bg: #fff; // button bg
$btn-line-color: $tertiary; // button border and font color && hover bg color $btn-line-color: $tertiary; // button border and font color && hover bg color
$btn-copy-color: $tertiary; // btn copy $btn-copy-color: $tertiary; // btn copy
$btn-qr-color: $tertiary; // btn qr-code $btn-qr-color: $tertiary; // btn qr-code
$btn-address-card-icon-color: $tertiary; // btn address color
//links & tile //links & tile
$tile-body-a-color: $tertiary; $tile-body-a-color: $tertiary;

@ -0,0 +1,7 @@
// general
$primary: #153550;
$secondary: #49a2ee;
$tertiary: #4ad7a7;
$additional-font: #89cae6;
$btn-line-color: $secondary; // button border and font color && hover bg color

@ -37,6 +37,7 @@ $btn-line-bg: #fff; // button bg
$btn-line-color: $secondary; // button border and font color && hover bg color $btn-line-color: $secondary; // button border and font color && hover bg color
$btn-copy-color: $secondary; // btn copy $btn-copy-color: $secondary; // btn copy
$btn-qr-color: $secondary; // btn qr-code $btn-qr-color: $secondary; // btn qr-code
$btn-address-card-icon-color: $secondary; // btn address color
//links & tile //links & tile
$tile-body-a-color: $secondary; $tile-body-a-color: $secondary;

@ -0,0 +1,8 @@
// general
$primary: #2b2b2b;
$secondary: #eac247;
$tertiary: #929292;
$additional-font: #ffffff;
$sub-accent-color: #a46f30;
$btn-line-color: $sub-accent-color; // button border and font color && hover bg color

@ -43,6 +43,7 @@ $btn-line-bg: #fff; // button bg
$btn-line-color: $sub-accent-color; // button border and font color && hover bg color $btn-line-color: $sub-accent-color; // button border and font color && hover bg color
$btn-copy-color: $sub-accent-color; // btn copy $btn-copy-color: $sub-accent-color; // btn copy
$btn-qr-color: $sub-accent-color; // btn qr-code $btn-qr-color: $sub-accent-color; // btn qr-code
$btn-address-card-icon-color: $sub-accent-color; // btn address color
//links & tile //links & tile
$tile-body-a-color: $sub-accent-color; $tile-body-a-color: $sub-accent-color;

@ -0,0 +1,7 @@
// general
$primary: #101f25;
$secondary: #35e3d8;
$tertiary: #1f857f;
$additional-font: #99fff9;
$btn-line-color: $tertiary; // button border and font color && hover bg color

@ -38,6 +38,7 @@ $btn-line-bg: #fff; // button bg
$btn-line-color: $tertiary; // button border and font color && hover bg color $btn-line-color: $tertiary; // button border and font color && hover bg color
$btn-copy-color: $tertiary; // btn copy $btn-copy-color: $tertiary; // btn copy
$btn-qr-color: $tertiary; // btn qr-code $btn-qr-color: $tertiary; // btn qr-code
$btn-address-card-icon-color: $tertiary; // btn address color
//links & tile //links & tile
$tile-body-a-color: $tertiary; $tile-body-a-color: $tertiary;

@ -0,0 +1,7 @@
// general
$primary: #1d3154;
$secondary: #fdcec4;
$tertiary: #a96c55;
$additional-font: #a1ded1;
$btn-line-color: $primary; // button border and font color && hover bg color

@ -44,6 +44,7 @@ $btn-line-bg: #fff; // button bg
$btn-line-color: $primary; // button border and font color && hover bg color $btn-line-color: $primary; // button border and font color && hover bg color
$btn-copy-color: $primary; // btn copy $btn-copy-color: $primary; // btn copy
$btn-qr-color: $primary; // btn qr-code $btn-qr-color: $primary; // btn qr-code
$btn-address-card-icon-color: $primary; // btn address color
// card // card
$card-background-1: $primary; $card-background-1: $primary;

@ -0,0 +1,7 @@
// general
$primary: #5c34a2;
$secondary: #87e1a9;
$tertiary: #bf9cff;
$additional-font: #fff;
$btn-line-color: $primary; // button border and font color && hover bg color

@ -40,6 +40,7 @@ $btn-line-bg: #fff; // button bg
$btn-line-color: $primary; // button border and font color && hover bg color $btn-line-color: $primary; // button border and font color && hover bg color
$btn-copy-color: $primary; // btn copy $btn-copy-color: $primary; // btn copy
$btn-qr-color: $primary; // btn qr-code $btn-qr-color: $primary; // btn qr-code
$btn-address-card-icon-color: $primary; // btn address color
//links & tile //links & tile
$tile-body-a-color: $primary; $tile-body-a-color: $primary;

@ -0,0 +1,7 @@
// general
$primary: #5c34a2;
$secondary: #87e1a9;
$tertiary: #bf9cff;
$additional-font: #fff;
$btn-line-color: $primary; // button border and font color && hover bg color

@ -40,6 +40,7 @@ $btn-line-bg: #fff; // button bg
$btn-line-color: $primary; // button border and font color && hover bg color $btn-line-color: $primary; // button border and font color && hover bg color
$btn-copy-color: $primary; // btn copy $btn-copy-color: $primary; // btn copy
$btn-qr-color: $primary; // btn qr-code $btn-qr-color: $primary; // btn qr-code
$btn-address-card-icon-color: $primary; // btn address color
//links & tile //links & tile
$tile-body-a-color: $primary; $tile-body-a-color: $primary;

@ -0,0 +1,7 @@
// general
$primary: #153550;
$secondary: #38a9f5;
$tertiary: #76f1ff;
$additional-font: #89cae6;
$btn-line-color: $secondary; // button border and font color && hover bg color

@ -37,6 +37,7 @@ $btn-line-bg: #fff; // button bg
$btn-line-color: $secondary; // button border and font color && hover bg color $btn-line-color: $secondary; // button border and font color && hover bg color
$btn-copy-color: $secondary; // btn copy $btn-copy-color: $secondary; // btn copy
$btn-qr-color: $secondary; // btn qr-code $btn-qr-color: $secondary; // btn qr-code
$btn-address-card-icon-color: $secondary; // btn address color
//links & tile //links & tile
$tile-body-a-color: $secondary; $tile-body-a-color: $secondary;

@ -0,0 +1,7 @@
// general
$primary: #153550;
$secondary: #38a9f5;
$tertiary: #76f1ff;
$additional-font: #89cae6;
$btn-line-color: $secondary; // button border and font color && hover bg color

@ -37,6 +37,7 @@ $btn-line-bg: #fff; // button bg
$btn-line-color: $secondary; // button border and font color && hover bg color $btn-line-color: $secondary; // button border and font color && hover bg color
$btn-copy-color: $secondary; // btn copy $btn-copy-color: $secondary; // btn copy
$btn-qr-color: $secondary; // btn qr-code $btn-qr-color: $secondary; // btn qr-code
$btn-address-card-icon-color: $secondary; // btn address color
//links & tile //links & tile
$tile-body-a-color: $secondary; $tile-body-a-color: $secondary;

@ -0,0 +1,7 @@
// general
$primary: #101f25;
$secondary: #27ac8d;
$tertiary: #e39a54;
$additional-font: #a1ded1;
$btn-line-color: $secondary; // button border and font color && hover bg color

@ -38,6 +38,7 @@ $btn-line-bg: #fff; // button bg
$btn-line-color: $secondary; // button border and font color && hover bg color $btn-line-color: $secondary; // button border and font color && hover bg color
$btn-copy-color: $secondary; // btn copy $btn-copy-color: $secondary; // btn copy
$btn-qr-color: $secondary; // btn qr-code $btn-qr-color: $secondary; // btn qr-code
$btn-address-card-icon-color: $secondary; // btn address color
// card // card
$card-background-1: $secondary; $card-background-1: $secondary;

@ -0,0 +1,8 @@
// general
$primary: #093731;
$secondary: #40bfb2;
$tertiary: #25c9ff;
$additional-font: #93e8dd;
$sub-accent-color: #1c9f90;
$btn-line-color: $sub-accent-color; // button border and font color && hover bg color

@ -49,6 +49,7 @@ $btn-line-bg: #fff; // button bg
$btn-line-color: $sub-accent-color; // button border and font color && hover bg color $btn-line-color: $sub-accent-color; // button border and font color && hover bg color
$btn-copy-color: $sub-accent-color; // btn copy $btn-copy-color: $sub-accent-color; // btn copy
$btn-qr-color: $sub-accent-color; // btn qr-code $btn-qr-color: $sub-accent-color; // btn qr-code
$btn-address-card-icon-color: $sub-accent-color; // btn address color
//links & tile //links & tile
$tile-body-a-color: $sub-accent-color; $tile-body-a-color: $sub-accent-color;

@ -0,0 +1,22 @@
@import "theme/base_variables";
@import "neutral_variables-non-critical";
// @import "dai_variables-non-critical";
// @import "ethereum_classic_variables-non-critical";
// @import "ethereum_variables-non-critical";
// @import "ether1_variables-non-critical";
// @import "expanse_variables-non-critical";
// @import "gochain_variables-non-critical";
// @import "goerli_variables-non-critical";
// @import "kovan_variables-non-critical";
// @import "lukso_variables-non-critical";
// @import "musicoin_variables-non-critical";
// @import "pirl_variables-non-critical";
// @import "poa_variables-non-critical";
// @import "posdao_variables-non-critical";
// @import "rinkeby_variables-non-critical";
// @import "ropsten_variables-non-critical";
// @import "social_variables-non-critical";
// @import "sokol_variables-non-critical";
// @import "tobalaba_variables-non-critical";
// @import "tomochain_variables-non-critical";
// @import "rsk_variables-non-critical";

@ -20,6 +20,9 @@ import 'bootstrap'
import './locale' import './locale'
// support of preload in Firefox
import '../node_modules/fg-loadcss/dist/cssrelpreload.min'
import './pages/address' import './pages/address'
import './pages/address/coin_balances' import './pages/address/coin_balances'
import './pages/address/transactions' import './pages/address/transactions'
@ -59,5 +62,6 @@ import './lib/async_listing_load'
import './lib/tooltip' import './lib/tooltip'
import './lib/modals' import './lib/modals'
import './lib/try_api' import './lib/try_api'
import './lib/try_eth_api'
import './lib/card_tabs' import './lib/card_tabs'
import './lib/network_selector' import './lib/network_selector'

@ -51,8 +51,6 @@ export const asyncInitialState = {
requestError: false, requestError: false,
/* if response has no items */ /* if response has no items */
emptyResponse: false, emptyResponse: false,
/* if it is loading the first page */
loadingFirstPage: true,
/* link to the next page */ /* link to the next page */
nextPagePath: null, nextPagePath: null,
/* link to the previous page */ /* link to the previous page */
@ -80,8 +78,7 @@ export function asyncReducer (state = asyncInitialState, action) {
} }
case 'FINISH_REQUEST': { case 'FINISH_REQUEST': {
return Object.assign({}, state, { return Object.assign({}, state, {
loading: false, loading: false
loadingFirstPage: false
}) })
} }
case 'ITEMS_FETCHED': { case 'ITEMS_FETCHED': {
@ -134,7 +131,7 @@ export const elements = {
}, },
'[data-async-listing] [data-loading-message]': { '[data-async-listing] [data-loading-message]': {
render ($el, state) { render ($el, state) {
if (state.loadingFirstPage) return $el.show() if (state.loading) return $el.show()
$el.hide() $el.hide()
} }
@ -143,7 +140,7 @@ export const elements = {
render ($el, state) { render ($el, state) {
if ( if (
!state.requestError && !state.requestError &&
(!state.loading || !state.loadingFirstPage) && (!state.loading) &&
state.items.length === 0 state.items.length === 0
) { ) {
return $el.show() return $el.show()
@ -201,6 +198,25 @@ export const elements = {
$el.attr('href', state.prevPagePath) $el.attr('href', state.prevPagePath)
} }
}, },
'[data-async-listing] [data-first-page-button]': {
render ($el, state) {
if (state.pagesStack.length === 0) {
return $el.hide()
}
$el.show()
$el.attr('disabled', false)
const urlParams = new URLSearchParams(window.location.search)
const blockParam = urlParams.get('block_type')
const firstPageHref = window.location.href.split('?')[0]
if (blockParam !== null) {
$el.attr('href', firstPageHref + '?block_type=' + blockParam)
} else {
$el.attr('href', firstPageHref)
}
}
},
'[data-async-listing] [data-page-number]': { '[data-async-listing] [data-page-number]': {
render ($el, state) { render ($el, state) {
if (state.emptyResponse) { if (state.emptyResponse) {
@ -216,7 +232,7 @@ export const elements = {
}, },
'[data-async-listing] [data-loading-button]': { '[data-async-listing] [data-loading-button]': {
render ($el, state) { render ($el, state) {
if (!state.loadingFirstPage && state.loading) return $el.show() if (state.loading) return $el.show()
$el.hide() $el.hide()
} }

@ -0,0 +1,637 @@
/* eslint-env browser */
/* global Awesomplete */
/* exported AwesompleteUtil */
/*
* Library endorsing Lea Verou's Awesomplete widget, providing:
* - dynamic remote data loading
* - labels with HTML markup
* - events and styling for exact matches
* - events and styling for mismatches
* - select item when TAB key is used
*
* (c) Nico Hoogervorst
* License: MIT
*
*/
window.AwesompleteUtil = (function () {
//
// event names and css classes
//
var _AWE = 'awesomplete-'
var _AWE_LOAD = _AWE + 'loadcomplete'
var _AWE_CLOSE = _AWE + 'close'
var _AWE_MATCH = _AWE + 'match'
var _AWE_PREPOP = _AWE + 'prepop'
var _AWE_SELECT = _AWE + 'select'
var _CLS_FOUND = 'awe-found'
var _CLS_NOT_FOUND = 'awe-not-found'
var $ = Awesomplete.$ /* shortcut for document.querySelector */
//
// private functions
//
// Some parts are shamelessly copied from Awesomplete.js like the logic inside this _suggestion function.
// Returns an object with label and value properties. Data parameter is plain text or Object/Array with label and value.
function _suggestion (data) {
var lv = Array.isArray(data)
? { label: data[0], value: data[1] }
: typeof data === 'object' && 'label' in data && 'value' in data ? data : { label: data, value: data }
return {label: lv.label || lv.value, value: lv.value}
}
// Helper to send events with detail property.
function _fire (target, name, detail) {
// $.fire uses deprecated methods but other methods don't work in IE11.
return $.fire(target, name, {detail: detail})
}
// Look if there is an exact match or a mismatch, set awe-found, awe-not-found css class and send match events.
function _matchValue (awe, prepop) {
var input = awe.input /* the input field */
var classList = input.classList
var utilprops = awe.utilprops /* extra properties piggybacked on Awesomplete object */
var selected = utilprops.selected /* the exact selected Suggestion with label and value */
var val = utilprops.convertInput.call(awe, input.value) /* trimmed lowercased value */
var opened = awe.opened /* is the suggestion list opened? */
var result = [] /* matches with value */
var list = awe._list /* current list of suggestions */
var suggestion, fake, rec, j /* function scoped variables */
utilprops.prepop = false /* after the first call it's not a prepopulation phase anymore */
if (list) { /* if there is a suggestion list */
for (j = 0; j < list.length; j++) { /* loop all suggestions */
rec = list[j]
suggestion = _suggestion(awe.data(rec, val)) /* call data convert function */
// with maxItems = 0 cannot look if suggestion list is opened to determine if there are still matches,
// instead call the filter method to see if there are still some options.
if (awe.maxItems === 0) {
// Awesomplete.FILTER_CONTAINS and Awesomplete.FILTER_STARTSWITH use the toString method.
suggestion.toString = function () { return '' + this.label }
if (awe.filter(suggestion, val)) {
// filter returns true, so there is at least one partial match.
opened = true
}
}
// Don't want to change the real input field, emulate a fake one.
fake = {input: {value: ''}}
// Determine how this suggestion would look like if it is replaced in the input field,
// it is an exact match if somebody types exactly that.
// Use the fake input here. fake.input.value will contain the result of the replace function.
awe.replace.call(fake, suggestion)
// Trim and lowercase also the fake input and compare that with the currently typed-in value.
if (utilprops.convertInput.call(awe, fake.input.value) === val) {
// This is an exact match. However there might more suggestions with the same value.
// If the user selected a suggestion from the list, check if this one matches, assuming that
// value + label is unique (if not it will be difficult for the user to make an informed decision).
if (selected && selected.value === suggestion.value && selected.label === suggestion.label) {
// this surely is the selected one
result = [rec]
break
}
// add the matching record to the result set.
result.push(rec)
} // end if
} // end loop
// if the result differs from the previous result
if (utilprops.prevSelected !== result) {
// if there is an exact match
if (result.length > 0) {
// if prepopulation phase (initial/autofill value); not triggered by user input
if (prepop) {
_fire(input, _AWE_PREPOP, result)
} else if (utilprops.changed) { /* if input is changed */
utilprops.prevSelected = result /* new result */
classList.remove(_CLS_NOT_FOUND) /* remove class */
classList.add(_CLS_FOUND) /* add css class */
_fire(input, _AWE_MATCH, result) /* fire event */
}
} else if (prepop) { /* no exact match, if in prepopulation phase */
_fire(input, _AWE_PREPOP, [])
} else if (utilprops.changed) { /* no exact match, if input is changed */
utilprops.prevSelected = []
classList.remove(_CLS_FOUND)
// Mark as not-found if there are no suggestions anymore or if another field is now active
if (!opened || (input !== document.activeElement)) {
if (val.length > 0) {
classList.add(_CLS_NOT_FOUND)
_fire(input, _AWE_MATCH, [])
}
} else {
classList.remove(_CLS_NOT_FOUND)
}
}
}
}
}
// Listen to certain events of THIS awesomplete object to trigger input validation.
function _match (ev) {
var awe = this
if ((ev.type === _AWE_CLOSE || ev.type === _AWE_LOAD || ev.type === 'blur') && ev.target === awe.input) {
_matchValue(awe, awe.utilprops.prepop && ev.type === _AWE_LOAD)
}
}
// Select currently selected item if tab or shift-tab key is used.
function _onKeydown (ev) {
var awe = this
if (ev.target === awe.input && ev.keyCode === 9) { // TAB key
awe.select() // take current selected item
}
}
// Handle selection event. State changes when an item is selected.
function _select (ev) {
var awe = this
awe.utilprops.changed = true // yes, user made a change
awe.utilprops.selected = ev.text // Suggestion object
}
// check if the object is empty {} object
function _isEmpty (val) {
return Object.keys(val).length === 0 && val.constructor === Object
}
// Need an updated suggestion list if:
// - There is no result yet, or there is a result but not for the characters we entered
// - or there might be more specific results because the limit was reached.
function _ifNeedListUpdate (awe, val, queryVal) {
var utilprops = awe.utilprops
return (!utilprops.listQuery ||
(!utilprops.loadall && /* with loadall, if there is a result, there is no need for new lists */
val.lastIndexOf(queryVal, 0) === 0 &&
(val.lastIndexOf(utilprops.listQuery, 0) !== 0 ||
(typeof utilprops.limit === 'number' && awe._list.length >= utilprops.limit))))
}
// Set a new suggestion list. Trigger loadcomplete event.
function _loadComplete (awe, list, queryVal) {
awe.list = list
awe.utilprops.listQuery = queryVal
_fire(awe.input, _AWE_LOAD, queryVal)
}
// Handle ajax response. Expects HTTP OK (200) response with JSON object with suggestion(s) (array).
function _onLoad () {
var t = this
var awe = t.awe
var xhr = t.xhr
var queryVal = t.queryVal
var val = awe.utilprops.val
var data
var prop
if (xhr.status === 200) {
data = JSON.parse(xhr.responseText)
if (awe.utilprops.convertResponse) data = awe.utilprops.convertResponse(data)
if (!Array.isArray(data)) {
if (awe.utilprops.limit === 0 || awe.utilprops.limit === 1) {
// if there is max 1 result expected, the array is not needed.
// Fur further processing, take the whole result and put it as one element in an array.
data = _isEmpty(data) ? [] : [data]
} else {
// search for the first property that contains an array
for (prop in data) {
if (Array.isArray(data[prop])) {
data = data[prop]
break
}
}
}
}
// can only handle arrays
if (Array.isArray(data)) {
// are we still interested in this response?
if (_ifNeedListUpdate(awe, val, queryVal)) {
// accept the new suggestion list
_loadComplete(awe, data, queryVal || awe.utilprops.loadall)
}
}
}
}
// Perform suggestion list lookup for the current value and validate. Use ajax when there is an url specified.
function _lookup (awe, val) {
var xhr
if (awe.utilprops.url) {
// are we still interested in this response?
if (_ifNeedListUpdate(awe, val, val)) {
xhr = new XMLHttpRequest()
awe.utilprops.ajax.call(awe,
awe.utilprops.url,
awe.utilprops.urlEnd,
awe.utilprops.loadall ? '' : val,
_onLoad.bind({awe: awe, xhr: xhr, queryVal: val}),
xhr
)
} else {
_matchValue(awe, awe.utilprops.prepop)
}
} else {
_matchValue(awe, awe.utilprops.prepop)
}
}
// Restart autocomplete search: clear css classes and send match-event with empty list.
function _restart (awe) {
var elem = awe.input
var classList = elem.classList
// IE11 only handles the first parameter of the remove method.
classList.remove(_CLS_NOT_FOUND)
classList.remove(_CLS_FOUND)
_fire(elem, _AWE_MATCH, [])
}
// handle new input value
function _update (awe, val, prepop) {
// prepop parameter is optional. Default value is false.
awe.utilprops.prepop = prepop || false
// if value changed
if (awe.utilprops.val !== val) {
// new value, clear previous selection
awe.utilprops.selected = null
// yes, user made a change
awe.utilprops.changed = true
awe.utilprops.val = val
// value is empty or smaller than minChars
if (val.length < awe.minChars || val.length === 0) {
// restart autocomplete search
_restart(awe)
}
if (val.length >= awe.minChars) {
// lookup suggestions and validate input
_lookup(awe, val)
}
}
return awe
}
// handle input changed event for THIS awesomplete object
function _onInput (e) {
var awe = this
var val
if (e.target === awe.input) {
// lowercase and trim input value
val = awe.utilprops.convertInput.call(awe, awe.input.value)
_update(awe, val)
}
}
// item function (as specified in Awesomplete) which just creates the 'li' HTML tag.
function _item (html /* , input */) {
return $.create('li', {
innerHTML: html,
'aria-selected': 'false'
})
}
// Escape HTML characters in text.
function _htmlEscape (text) {
return text.replace('&', '&amp;').replace('<', '&lt;').replace('>', '&gt;')
}
// Function to copy a field from the selected autocomplete item to another DOM element.
function _copyFun (e) {
var t = this
var sourceId = t.sourceId
var dataField = t.dataField
var targetId = t.targetId
var elem
var val
if (e.target === $(sourceId)) {
if (typeof targetId === 'function') {
targetId(e, dataField)
} else {
// lookup target element if it isn't resolved yet
elem = $(targetId)
// don't override target inputs if user is currently editing it.
if (elem && elem !== document.activeElement) {
// event must contain 1 item from suggestion list
val = Array.isArray(e.detail) && e.detail.length === 1 ? e.detail[0] : null
// if a datafield is specified, take that value
val = (dataField && val ? val[dataField] : val) || ''
// if it is an input control
if (typeof elem.value !== 'undefined') {
// set new value
elem.value = val
// not really sure if it is an input control, check if it has a classList
if (elem.classList && elem.classList.remove) {
// it might be another awesomplete control, if so the input is not wrong anymore because it's changed now
elem.classList.remove(_CLS_NOT_FOUND)
}
} else if (typeof elem.src !== 'undefined') { /* is it an image tag? */
elem.src = val
} else {
// use innerHTML to set the new value, because value might intentionally contain HTML markup
elem.innerHTML = val
}
}
}
}
}
// click function for the combobox button
function _clickFun (e) {
var t = this
var awe
var minChars
if (e.target === $(t.btnId)) {
e.preventDefault()
awe = t.awe
// toggle open/close
if (awe.ul.childNodes.length === 0 || awe.ul.hasAttribute('hidden')) {
minChars = awe.minChars
// ignore that the input value is empty
awe.minChars = 0
// show the suggestion list
awe.evaluate()
awe.minChars = minChars
} else {
awe.close()
}
}
}
// Return text with mark tags arround matching input. Don't replace inside <HTML> tags.
// When startsWith is true, mark only the matching begin text.
function _mark (text, input, startsWith) {
var searchText = $.regExpEscape(_htmlEscape(input).trim())
var regExp = searchText.length <= 0 ? null : startsWith ? RegExp('^' + searchText, 'i') : RegExp('(?!<[^>]+?>)' + searchText + '(?![^<]*?>)', 'gi')
return text.replace(regExp, '<mark>$&</mark>')
}
// Recursive jsonFlatten function
function _jsonFlatten (result, cur, prop, level, opts) {
var root = opts.root /* filter resulting json tree on root property (optional) */
var value = opts.value /* search for this property and copy it's value to a new 'value' property
(optional, do not specify it if the json array contains plain strings) */
var label = opts.label || opts.value /* search this property and copy it's value to a new 'label' property.
If there is a 'opts.value' field but no 'opts.label', assume label is the same. */
var isEmpty = true
var arrayResult = []
var j
// at top level, look if there is a property which starts with root (if specified)
if (level === 0 && root && prop && (prop + '.').lastIndexOf(root + '.', 0) !== 0 && (root + '.').lastIndexOf(prop + '.', 0) !== 0) {
return result
}
// handle current part of the json tree
if (Object(cur) !== cur) {
if (prop) {
result[prop] = cur
} else {
result = cur
}
} else if (Array.isArray(cur)) {
for (j = 0; j < cur.length; j++) {
arrayResult.push(_jsonFlatten({}, cur[j], '', level + 1, opts))
}
if (prop) {
result[prop] = arrayResult
} else {
result = arrayResult
}
} else {
for (j in cur) {
isEmpty = false
_jsonFlatten(result, cur[j], prop ? prop + '.' + j : j, level, opts)
}
if (isEmpty && prop) result[prop] = {}
}
// for arrays at top and subtop level
if (level < 2 && prop) {
// if a 'value' is specified and found a mathing property, create extra 'value' property.
if (value && (prop + '.').lastIndexOf(value + '.', 0) === 0) { result['value'] = result[prop] }
// if a 'label' is specified and found a mathing property, create extra 'label' property.
if (label && (prop + '.').lastIndexOf(label + '.', 0) === 0) { result['label'] = result[prop] }
}
if (level === 0) {
// Make sure that both value and label properties exist, even if they are nil.
// This is handy with limit 0 or 1 when the result doesn't have to contain an array.
if (value && !('value' in result)) { result['value'] = null }
if (label && !('label' in result)) { result['label'] = null }
}
return result
}
// Stop AwesompleteUtil; detach event handlers from the Awesomplete object.
function _detach () {
var t = this
var elem = t.awe.input
var boundMatch = t.boundMatch
var boundOnInput = t.boundOnInput
var boundOnKeydown = t.boundOnKeydown
var boundSelect = t.boundSelect
elem.removeEventListener(_AWE_SELECT, boundSelect)
elem.removeEventListener(_AWE_LOAD, boundMatch)
elem.removeEventListener(_AWE_CLOSE, boundMatch)
elem.removeEventListener('blur', boundMatch)
elem.removeEventListener('input', boundOnInput)
elem.removeEventListener('keydown', boundOnKeydown)
}
//
// public methods
//
return {
// ajax call for url + val + urlEnd. fn is the callback function. xhr parameter is optional.
ajax: function (url, urlEnd, val, fn, xhr) {
xhr = xhr || new XMLHttpRequest()
xhr.open('GET', url + encodeURIComponent(val) + (urlEnd || ''))
xhr.onload = fn
xhr.send()
return xhr
},
// Convert input before comparing it with suggestion. lowercase and trim the text
convertInput: function (text) {
return typeof text === 'string' ? text.trim().toLowerCase() : ''
},
// item function as defined in Awesomplete.
// item(html, input). input is optional and ignored in this implementation
item: _item,
// Set a new suggestion list. Trigger loadcomplete event.
// load(awesomplete, list, queryVal)
load: _loadComplete,
// Return text with mark tags arround matching input. Don't replace inside <HTML> tags.
// When startsWith is true, mark only the matching begin text.
// mark(text, input, startsWith)
mark: _mark,
// highlight items: Marks input in the first line, not in the optional description
itemContains: function (text, input) {
var arr
if (input.trim().length > 0) {
arr = ('' + text).split(/<p>/)
arr[0] = _mark(arr[0], input)
text = arr.join('<p>')
}
return _item(text, input)
},
// highlight items: mark all occurrences of the input text
itemMarkAll: function (text, input) {
return _item(input.trim() === '' ? '' + text : _mark('' + text, input), input)
},
// highlight items: mark input in the begin text
itemStartsWith: function (text, input) {
return _item(input.trim() === '' ? '' + text : _mark('' + text, input, true), input)
},
// create Awesomplete object for input control elemId. opts are passed unchanged to Awesomplete.
create: function (elemId, utilOpts, opts) {
opts.item = opts.item || this.itemContains /* by default uses itemContains, can be overriden */
var awe = new Awesomplete(elemId, opts)
awe.utilprops = utilOpts || {}
// loadall is true if there is no url (there is a static data-list)
if (!awe.utilprops.url && typeof awe.utilprops.loadall === 'undefined') {
awe.utilprops.loadall = true
}
awe.utilprops.ajax = awe.utilprops.ajax || this.ajax /* default ajax function can be overriden */
awe.utilprops.convertInput = awe.utilprops.convertInput || this.convertInput /* the same applies for convertInput */
return awe
},
// attach Awesomplete object to event listeners
attach: function (awe) {
var elem = awe.input
var boundMatch = _match.bind(awe)
var boundOnKeydown = _onKeydown.bind(awe)
var boundOnInput = _onInput.bind(awe)
var boundSelect = _select.bind(awe)
var boundDetach = _detach.bind({awe: awe,
boundMatch: boundMatch,
boundOnInput: boundOnInput,
boundOnKeydown: boundOnKeydown,
boundSelect: boundSelect
})
var events = {
'keydown': boundOnKeydown,
'input': boundOnInput
}
events['blur'] = events[_AWE_CLOSE] = events[_AWE_LOAD] = boundMatch
events[_AWE_SELECT] = boundSelect
$.bind(elem, events)
awe.utilprops.detach = boundDetach
// Perform ajax call if prepop is true and there is an initial input value, or when all values must be loaded (loadall)
if (awe.utilprops.prepop && (awe.utilprops.loadall || elem.value.length > 0)) {
awe.utilprops.val = awe.utilprops.convertInput.call(awe, elem.value)
_lookup(awe, awe.utilprops.val)
}
return awe
},
// update input value via javascript. Use prepop=true when this is an initial/prepopulation value.
update: function (awe, value, prepop) {
awe.input.value = value
return _update(awe, value, prepop)
},
// create and attach Awesomplete object for input control elemId. opts are passed unchanged to Awesomplete.
start: function (elemId, utilOpts, opts) {
return this.attach(this.create(elemId, utilOpts, opts))
},
// Stop AwesompleteUtil; detach event handlers from the Awesomplete object.
detach: function (awe) {
if (awe.utilprops.detach) {
awe.utilprops.detach()
delete awe.utilprops.detach
}
return awe
},
// Create function to copy a field from the selected autocomplete item to another DOM element.
// dataField can be null.
createCopyFun: function (sourceId, dataField, targetId) {
return _copyFun.bind({sourceId: sourceId, dataField: dataField, targetId: $(targetId) || targetId})
},
// attach copy function to event listeners. prepop is optional and by default true.
// if true the copy function will also listen to awesomplete-prepop events.
// The optional listenEl is the element that listens, defaults to document.body.
attachCopyFun: function (fun, prepop, listenEl) {
// prepop parameter defaults to true
prepop = typeof prepop === 'boolean' ? prepop : true
listenEl = listenEl || document.body
listenEl.addEventListener(_AWE_MATCH, fun)
if (prepop) listenEl.addEventListener(_AWE_PREPOP, fun)
return fun
},
// Create and attach copy function.
startCopy: function (sourceId, dataField, targetId, prepop) {
var sourceEl = $(sourceId)
return this.attachCopyFun(this.createCopyFun(sourceEl || sourceId, dataField, targetId), prepop, sourceEl)
},
// Stop copy function. Detach it from event listeners.
// The optional listenEl must be the same element that was used during startCopy/attachCopyFun;
// in general: Awesomplete.$(sourceId). listenEl defaults to document.body.
detachCopyFun: function (fun, listenEl) {
listenEl = listenEl || document.body
listenEl.removeEventListener(_AWE_PREPOP, fun)
listenEl.removeEventListener(_AWE_MATCH, fun)
return fun
},
// Create function for combobox button (btnId) to toggle dropdown list.
createClickFun: function (btnId, awe) {
return _clickFun.bind({btnId: btnId, awe: awe})
},
// Attach click function for combobox to click event.
// The optional listenEl is the element that listens, defaults to document.body.
attachClickFun: function (fun, listenEl) {
listenEl = listenEl || document.body
listenEl.addEventListener('click', fun)
return fun
},
// Create and attach click function for combobox button. Toggles open/close of suggestion list.
startClick: function (btnId, awe) {
var btnEl = $(btnId)
return this.attachClickFun(this.createClickFun(btnEl || btnId, awe), btnEl)
},
// Stop click function. Detach it from event listeners.
// The optional listenEl must be the same element that was used during startClick/attachClickFun;
// in general: Awesomplete.$(btnId). listenEl defaults to document.body.
detachClickFun: function (fun, listenEl) {
listenEl = listenEl || document.body
listenEl.removeEventListener('click', fun)
return fun
},
// filter function as specified in Awesomplete. Filters suggestion list on items containing input value.
// Awesomplete.FILTER_CONTAINS filters on data.label, however
// this function filters on value and not on the shown label which may contain markup.
filterContains: function (data, input) {
return Awesomplete.FILTER_CONTAINS(data.value, input)
},
// filter function as specified in Awesomplete. Filters suggestion list on matching begin text.
// Awesomplete.FILTER_STARTSWITH filters on data.label, however
// this function filters on value and not on the shown label which may contain markup.
filterStartsWith: function (data, input) {
return Awesomplete.FILTER_STARTSWITH(data.value, input)
},
// Flatten JSON.
// { "a":{"b":{"c":[{"d":{"e":1}}]}}} becomes {"a.b.c":[{"d.e":1}]}.
// This function can be bind to configure it with extra options;
// bind({root: '<root path>', value: '<value property>', label: '<label property>'})
jsonFlatten: function (data) {
// start json tree recursion
return _jsonFlatten({}, data, '', 0, this)
}
}
}())

@ -0,0 +1,2 @@
import 'awesomplete/awesomplete.css'
import 'awesomplete'

@ -18,6 +18,14 @@ export function createCoinBalanceHistoryChart (el) {
y: balance.value y: balance.value
})) }))
var stepSize = 3
if (data.length > 1) {
var diff = Math.abs(new Date(data[data.length - 1].date) - new Date(data[data.length - 2].date))
var periodInDays = diff / (1000 * 60 * 60 * 24)
stepSize = periodInDays
}
return new Chart(el, { return new Chart(el, {
type: 'line', type: 'line',
data: { data: {
@ -36,7 +44,7 @@ export function createCoinBalanceHistoryChart (el) {
type: 'time', type: 'time',
time: { time: {
unit: 'day', unit: 'day',
stepSize: 3 stepSize: stepSize
} }
}], }],
yAxes: [{ yAxes: [{

@ -1,20 +1,17 @@
import $ from 'jquery' import $ from 'jquery'
const tokenTransferToggle = (element) => { $(document.body).on('click', '[data-selector="token-transfer-open"]', event => {
const $element = $(element) const $tokenTransferOpen = event.target
const $tokenTransferClose = $element.find("[data-selector='token-transfer-close']") const $parent = $tokenTransferOpen.parentElement
const $tokenTransferOpen = $element.find("[data-selector='token-transfer-open']") const $tokenTransferClose = $parent.querySelector("[data-selector='token-transfer-close']")
$tokenTransferOpen.classList.add('d-none')
$tokenTransferClose.classList.remove('d-none')
})
$element.on('show.bs.collapse', () => { $(document.body).on('click', '[data-selector="token-transfer-close"]', event => {
$tokenTransferOpen.addClass('d-none') const $tokenTransferClose = event.target
$tokenTransferClose.removeClass('d-none') const $parent = $tokenTransferClose.parentElement
}) const $tokenTransferOpen = $parent.querySelector("[data-selector='token-transfer-open']")
$tokenTransferClose.classList.add('d-none')
$element.on('hide.bs.collapse', () => { $tokenTransferOpen.classList.remove('d-none')
$tokenTransferClose.addClass('d-none') })
$tokenTransferOpen.removeClass('d-none')
})
}
// Initialize the script scoped for each card.
$("[data-selector='token-transfers-toggle']").each((_index, element) => tokenTransferToggle(element))

@ -1,5 +1,5 @@
import $ from 'jquery' import $ from 'jquery'
$(function () { $(function () {
$('[data-toggle="tooltip"]').tooltip() $('body').tooltip({ selector: '[data-toggle="tooltip"]' })
}) })

@ -0,0 +1,70 @@
import $ from 'jquery'
function composeCurlCommand (data) {
return `curl -H "content-type: application/json" -X POST --data '${JSON.stringify(data)}'`
}
function handleResponse (data, xhr, clickedButton) {
const module = clickedButton.attr('data-module')
const action = clickedButton.attr('data-action')
const curl = $(`[data-selector="${module}-${action}-curl"]`)[0]
const code = $(`[data-selector="${module}-${action}-server-response-code"]`)[0]
const body = $(`[data-selector="${module}-${action}-server-response-body"]`)[0]
curl.innerHTML = composeCurlCommand(data)
code.innerHTML = xhr.status
body.innerHTML = JSON.stringify(xhr.responseJSON, undefined, 2)
$(`[data-selector="${module}-${action}-try-api-ui-result"]`).show()
$(`[data-selector="${module}-${action}-btn-try-api-clear"]`).show()
clickedButton.html(clickedButton.data('original-text'))
clickedButton.prop('disabled', false)
}
function wrapJsonRpc (method, params) {
return {
id: 0,
jsonrpc: '2.0',
method: method,
params: params
}
}
function parseInput (input) {
const type = $(input).attr('data-parameter-type')
const value = $(input).val()
switch (type) {
case 'string':
return value
case 'json':
return JSON.parse(value)
default:
return value
}
}
$('button[data-try-eth-api-ui-button-type="execute"]').click(event => {
const clickedButton = $(event.target)
const module = clickedButton.attr('data-module')
const action = clickedButton.attr('data-action')
const inputs = $(`input[data-selector="${module}-${action}-try-api-ui"]`)
const params = $.map(inputs, parseInput)
const formData = wrapJsonRpc(action, params)
console.log(formData)
const loadingText = '<span class="loading-spinner-small mr-2"><span class="loading-spinner-block-1"></span><span class="loading-spinner-block-2"></span></span> Loading...'
clickedButton.prop('disabled', true)
clickedButton.data('original-text', clickedButton.html())
if (clickedButton.html() !== loadingText) {
clickedButton.html(loadingText)
}
$.ajax({
url: '/api/eth_rpc',
type: 'POST',
data: JSON.stringify(formData),
dataType: 'json',
contentType: 'application/json; charset=utf-8'
}).then((_data, _status, xhr) => handleResponse(formData, xhr, clickedButton))
})

@ -45,7 +45,7 @@ const elements = {
}, },
'[data-search]': { '[data-search]': {
render ($el, state) { render ($el, state) {
if (state.emptyResponse) { if (state.emptyResponse && !state.isSearch) {
return $el.hide() return $el.hide()
} }

@ -0,0 +1 @@
import '../../css/stakes.scss'

File diff suppressed because it is too large Load Diff

@ -20,12 +20,14 @@
}, },
"dependencies": { "dependencies": {
"@fortawesome/fontawesome-free": "^5.1.0-4", "@fortawesome/fontawesome-free": "^5.1.0-4",
"highlight.js": "^9.13.1", "awesomplete": "1.1.2",
"highlightjs-solidity": "^1.0.6",
"bignumber.js": "^7.2.1", "bignumber.js": "^7.2.1",
"bootstrap": "^4.1.3", "bootstrap": "^4.1.3",
"chart.js": "^2.7.2", "chart.js": "^2.7.2",
"clipboard": "^2.0.1", "clipboard": "^2.0.1",
"fg-loadcss": "^2.1.0",
"highlight.js": "^9.13.1",
"highlightjs-solidity": "^1.0.6",
"humps": "^2.0.1", "humps": "^2.0.1",
"jquery": "^3.3.1", "jquery": "^3.3.1",
"lodash": "^4.17.11", "lodash": "^4.17.11",
@ -47,7 +49,7 @@
"babel-loader": "^7.1.4", "babel-loader": "^7.1.4",
"babel-preset-env": "^1.6.1", "babel-preset-env": "^1.6.1",
"copy-webpack-plugin": "^4.5.1", "copy-webpack-plugin": "^4.5.1",
"css-loader": "^0.28.11", "css-loader": "^3.1.0",
"eslint": "^4.15.0", "eslint": "^4.15.0",
"eslint-config-standard": "^11.0.0-beta.0", "eslint-config-standard": "^11.0.0-beta.0",
"eslint-plugin-import": "^2.8.0", "eslint-plugin-import": "^2.8.0",
@ -60,7 +62,7 @@
"node-sass": "^4.9.3", "node-sass": "^4.9.3",
"optimize-css-assets-webpack-plugin": "^5.0.3", "optimize-css-assets-webpack-plugin": "^5.0.3",
"postcss-loader": "^2.1.4", "postcss-loader": "^2.1.4",
"sass-loader": "^7.0.1", "sass-loader": "^7.1.0",
"style-loader": "^0.21.0", "style-loader": "^0.21.0",
"terser-webpack-plugin": "^1.3.0", "terser-webpack-plugin": "^1.3.0",
"webpack": "^4.6.0", "webpack": "^4.6.0",

@ -27,15 +27,59 @@ function transpileViewScript(file) {
} }
}; };
const jsOptimizationParams = {
cache: true,
parallel: true,
sourceMap: true,
}
const awesompleteJs = {
entry: {
awesomplete: './js/lib/awesomplete.js',
'awesomplete-util': './js/lib/awesomplete-util.js',
},
output: {
filename: '[name].min.js',
path: path.resolve(__dirname, '../priv/static/js')
},
module: {
rules: [
{
test: /\.css$/,
use: [
MiniCssExtractPlugin.loader,
{
loader: "css-loader",
}
],
},
],
},
optimization: {
minimizer: [
new TerserJSPlugin(jsOptimizationParams),
],
},
plugins: [
new MiniCssExtractPlugin({
filename: '../css/awesomplete.css'
}),
]
}
const appJs = const appJs =
{ {
entry: './js/app.js', entry: {
app: './js/app.js',
stakes: './js/pages/stakes.js',
'non-critical': './css/non-critical.scss',
},
output: { output: {
filename: 'app.js', filename: '[name].js',
path: path.resolve(__dirname, '../priv/static/js') path: path.resolve(__dirname, '../priv/static/js')
}, },
optimization: { optimization: {
minimizer: [new TerserJSPlugin({}), new OptimizeCSSAssetsPlugin({})], minimizer: [new TerserJSPlugin(jsOptimizationParams), new OptimizeCSSAssetsPlugin({})],
}, },
module: { module: {
rules: [ rules: [
@ -80,7 +124,7 @@ const appJs =
}, },
plugins: [ plugins: [
new MiniCssExtractPlugin({ new MiniCssExtractPlugin({
filename: '../css/app.css' filename: '../css/[name].css'
}), }),
new CopyWebpackPlugin([{ from: 'static/', to: '../' }]), new CopyWebpackPlugin([{ from: 'static/', to: '../' }]),
new ContextReplacementPlugin(/moment[\/\\]locale$/, /en/) new ContextReplacementPlugin(/moment[\/\\]locale$/, /en/)
@ -89,4 +133,4 @@ const appJs =
const viewScripts = glob.sync('./js/view_specific/**/*.js').map(transpileViewScript); const viewScripts = glob.sync('./js/view_specific/**/*.js').map(transpileViewScript);
module.exports = viewScripts.concat(appJs); module.exports = viewScripts.concat(appJs, awesompleteJs);

@ -28,7 +28,9 @@ config :block_scout_web,
"EtherChain" => "https://www.etherchain.org/", "EtherChain" => "https://www.etherchain.org/",
"Bloxy" => "https://bloxy.info/" "Bloxy" => "https://bloxy.info/"
}, },
other_networks: System.get_env("SUPPORTED_CHAINS") other_networks: System.get_env("SUPPORTED_CHAINS"),
webapp_url: System.get_env("WEBAPP_URL"),
api_url: System.get_env("API_URL")
config :block_scout_web, BlockScoutWeb.Counters.BlocksIndexedCounter, enabled: true config :block_scout_web, BlockScoutWeb.Counters.BlocksIndexedCounter, enabled: true
@ -40,7 +42,7 @@ config :block_scout_web, BlockScoutWeb.Endpoint,
path: System.get_env("NETWORK_PATH") || "/" path: System.get_env("NETWORK_PATH") || "/"
], ],
render_errors: [view: BlockScoutWeb.ErrorView, accepts: ~w(html json)], render_errors: [view: BlockScoutWeb.ErrorView, accepts: ~w(html json)],
pubsub: [name: BlockScoutWeb.PubSub, adapter: Phoenix.PubSub.PG2] pubsub: [name: BlockScoutWeb.PubSub]
config :block_scout_web, BlockScoutWeb.Tracer, config :block_scout_web, BlockScoutWeb.Tracer,
service: :block_scout_web, service: :block_scout_web,
@ -58,8 +60,7 @@ config :block_scout_web, BlockScoutWeb.SocialMedia,
config :ex_cldr, config :ex_cldr,
default_locale: "en", default_locale: "en",
locales: ["en"], default_backend: BlockScoutWeb.Cldr
gettext: BlockScoutWeb.Gettext
config :logger, :block_scout_web, config :logger, :block_scout_web,
# keep synced with `config/config.exs` # keep synced with `config/config.exs`
@ -86,6 +87,12 @@ config :wobserver,
discovery: :none, discovery: :none,
mode: :plug mode: :plug
config :block_scout_web, BlockScoutWeb.ApiRouter,
writing_enabled: System.get_env("DISABLE_WRITE_API") != "true",
reading_enabled: System.get_env("DISABLE_READ_API") != "true"
config :block_scout_web, BlockScoutWeb.WebRouter, enabled: System.get_env("DISABLE_WEBAPP") != "true"
# Import environment specific config. This must remain at the bottom # Import environment specific config. This must remain at the bottom
# of this file so it overrides the configuration defined above. # of this file so it overrides the configuration defined above.
import_config "#{Mix.env()}.exs" import_config "#{Mix.env()}.exs"

@ -7,7 +7,8 @@ config :block_scout_web, :sql_sandbox, true
config :block_scout_web, BlockScoutWeb.Endpoint, config :block_scout_web, BlockScoutWeb.Endpoint,
http: [port: 4002], http: [port: 4002],
secret_key_base: "27Swe6KtEtmN37WyEYRjKWyxYULNtrxlkCEKur4qoV+Lwtk8lafsR16ifz1XBBYj", secret_key_base: "27Swe6KtEtmN37WyEYRjKWyxYULNtrxlkCEKur4qoV+Lwtk8lafsR16ifz1XBBYj",
server: true server: true,
pubsub: [name: BlockScoutWeb.PubSub]
config :block_scout_web, BlockScoutWeb.Tracer, disabled?: false config :block_scout_web, BlockScoutWeb.Tracer, disabled?: false

@ -24,6 +24,7 @@ defmodule BlockScoutWeb do
import BlockScoutWeb.Controller import BlockScoutWeb.Controller
import BlockScoutWeb.Router.Helpers import BlockScoutWeb.Router.Helpers
import BlockScoutWeb.WebRouter.Helpers, except: [static_path: 2]
import BlockScoutWeb.Gettext import BlockScoutWeb.Gettext
import BlockScoutWeb.ErrorHelpers import BlockScoutWeb.ErrorHelpers
import Plug.Conn import Plug.Conn
@ -55,6 +56,8 @@ defmodule BlockScoutWeb do
WeiHelpers WeiHelpers
} }
import BlockScoutWeb.WebRouter.Helpers, except: [static_path: 2]
import PhoenixFormAwesomplete import PhoenixFormAwesomplete
end end
end end

@ -0,0 +1,73 @@
defmodule RPCTranslatorForwarder do
@moduledoc """
Phoenix router limits forwarding,
so this module is to forward old paths for backward compatibility
"""
alias BlockScoutWeb.API.RPC.RPCTranslator
defdelegate init(opts), to: RPCTranslator
defdelegate call(conn, opts), to: RPCTranslator
end
defmodule BlockScoutWeb.ApiRouter do
@moduledoc """
Router for API
"""
use BlockScoutWeb, :router
pipeline :api do
plug(:accepts, ["json"])
end
scope "/v1", BlockScoutWeb.API.V1, as: :api_v1 do
pipe_through(:api)
get("/health", HealthController, :health)
if Application.get_env(:block_scout_web, __MODULE__)[:writing_enabled] do
post("/decompiled_smart_contract", DecompiledSmartContractController, :create)
post("/verified_smart_contracts", VerifiedSmartContractController, :create)
end
end
if Application.get_env(:block_scout_web, __MODULE__)[:reading_enabled] do
scope "/" do
alias BlockScoutWeb.API.{RPC, V1}
pipe_through(:api)
scope "/v1", as: :api_v1 do
get("/supply", V1.SupplyController, :supply)
post("/eth_rpc", RPC.EthController, :eth_request)
end
# For backward compatibility. Should be removed
post("/eth_rpc", RPC.EthController, :eth_request)
end
end
scope "/" do
pipe_through(:api)
alias BlockScoutWeb.API.RPC
scope "/v1", as: :api_v1 do
forward("/", RPC.RPCTranslator, %{
"block" => {RPC.BlockController, []},
"account" => {RPC.AddressController, []},
"logs" => {RPC.LogsController, []},
"token" => {RPC.TokenController, []},
"stats" => {RPC.StatsController, []},
"contract" => {RPC.ContractController, [:verify]},
"transaction" => {RPC.TransactionController, []}
})
end
# For backward compatibility. Should be removed
forward("/", RPCTranslatorForwarder, %{
"block" => {RPC.BlockController, []},
"account" => {RPC.AddressController, []},
"logs" => {RPC.LogsController, []},
"token" => {RPC.TokenController, []},
"stats" => {RPC.StatsController, []},
"contract" => {RPC.ContractController, [:verify]},
"transaction" => {RPC.TransactionController, []}
})
end
end

@ -18,6 +18,7 @@ defmodule BlockScoutWeb.Application do
# Define workers and child supervisors to be supervised # Define workers and child supervisors to be supervised
children = [ children = [
# Start the endpoint when the application starts # Start the endpoint when the application starts
{Phoenix.PubSub.PG2, name: BlockScoutWeb.PubSub, fastlane: Phoenix.Channel.Server},
supervisor(Endpoint, []), supervisor(Endpoint, []),
supervisor(Absinthe.Subscription, [Endpoint]), supervisor(Absinthe.Subscription, [Endpoint]),
{RealtimeEventHandler, name: RealtimeEventHandler}, {RealtimeEventHandler, name: RealtimeEventHandler},

@ -31,6 +31,15 @@ defmodule BlockScoutWeb.Chain do
alias Explorer.PagingOptions alias Explorer.PagingOptions
defimpl Poison.Encoder, for: Decimal do
def encode(value, _opts) do
# silence the xref warning
decimal = Decimal
[?\", decimal.to_string(value), ?\"]
end
end
@page_size 50 @page_size 50
@default_paging_options %PagingOptions{page_size: @page_size + 1} @default_paging_options %PagingOptions{page_size: @page_size + 1}
@address_hash_len 40 @address_hash_len 40

@ -73,7 +73,7 @@ defmodule BlockScoutWeb.AddressChannel do
def handle_out("count", %{count: count}, socket) do def handle_out("count", %{count: count}, socket) do
Gettext.put_locale(BlockScoutWeb.Gettext, socket.assigns.locale) Gettext.put_locale(BlockScoutWeb.Gettext, socket.assigns.locale)
push(socket, "count", %{count: Cldr.Number.to_string!(count, format: "#,###")}) push(socket, "count", %{count: BlockScoutWeb.Cldr.Number.to_string!(count, format: "#,###")})
{:noreply, socket} {:noreply, socket}
end end

@ -0,0 +1,12 @@
defmodule BlockScoutWeb.Cldr do
@moduledoc """
Cldr global configuration.
"""
use Cldr,
default_locale: "en",
locales: ["en"],
gettext: BlockScoutWeb.Gettext,
generate_docs: false,
providers: [Cldr.Number, Cldr.Unit]
end

@ -9,7 +9,12 @@ defmodule BlockScoutWeb.AddressCoinBalanceByDayController do
def index(conn, %{"address_id" => address_hash_string, "type" => "JSON"}) do def index(conn, %{"address_id" => address_hash_string, "type" => "JSON"}) do
with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string) do with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string) do
balances_by_day = Chain.address_to_balances_by_day(address_hash) balances_by_day =
address_hash
|> Chain.address_to_balances_by_day()
|> Enum.map(fn %{value: value} = map ->
Map.put(map, :value, Decimal.to_float(value))
end)
json(conn, balances_by_day) json(conn, balances_by_day)
end end

@ -25,8 +25,9 @@ defmodule BlockScoutWeb.API.RPC.RPCTranslator do
def init(opts), do: opts def init(opts), do: opts
def call(%Conn{params: %{"module" => module, "action" => action}} = conn, translations) do def call(%Conn{params: %{"module" => module, "action" => action}} = conn, translations) do
with {:ok, controller} <- translate_module(translations, module), with {:ok, {controller, write_actions}} <- translate_module(translations, module),
{:ok, action} <- translate_action(action), {:ok, action} <- translate_action(action),
true <- action_accessed?(action, write_actions),
{:ok, conn} <- call_controller(conn, controller, action) do {:ok, conn} <- call_controller(conn, controller, action) do
conn conn
else else
@ -64,7 +65,7 @@ defmodule BlockScoutWeb.API.RPC.RPCTranslator do
end end
@doc false @doc false
@spec translate_module(map(), String.t()) :: {:ok, module()} | {:error, :no_action} @spec translate_module(map(), String.t()) :: {:ok, {module(), list(atom())}} | {:error, :no_action}
defp translate_module(translations, module) do defp translate_module(translations, module) do
module_lowercase = String.downcase(module) module_lowercase = String.downcase(module)
@ -83,6 +84,16 @@ defmodule BlockScoutWeb.API.RPC.RPCTranslator do
ArgumentError -> {:error, :no_action} ArgumentError -> {:error, :no_action}
end end
defp action_accessed?(action, write_actions) do
conf = Application.get_env(:block_scout_web, BlockScoutWeb.ApiRouter)
if action in write_actions do
conf[:writing_enabled] || {:error, :no_action}
else
conf[:reading_enabled] || {:error, :no_action}
end
end
@doc false @doc false
@spec call_controller(Conn.t(), module(), atom()) :: {:ok, Conn.t()} | {:error, :no_action} | {:error, Exception.t()} @spec call_controller(Conn.t(), module(), atom()) :: {:ok, Conn.t()} | {:error, :no_action} | {:error, Exception.t()}
defp call_controller(conn, controller, action) do defp call_controller(conn, controller, action) do

@ -13,8 +13,8 @@ defmodule BlockScoutWeb.CSPHeader do
"content-security-policy" => "\ "content-security-policy" => "\
connect-src 'self' #{websocket_endpoints(conn)}; \ connect-src 'self' #{websocket_endpoints(conn)}; \
default-src 'self';\ default-src 'self';\
script-src 'self' 'unsafe-inline' 'unsafe-eval' https://nico-amsterdam.github.io;\ script-src 'self' 'unsafe-inline' 'unsafe-eval';\
style-src 'self' 'unsafe-inline' 'unsafe-eval' https://fonts.googleapis.com https://nico-amsterdam.github.io;\ style-src 'self' 'unsafe-inline' 'unsafe-eval' https://fonts.googleapis.com;\
img-src 'self' 'unsafe-inline' 'unsafe-eval' data:;\ img-src 'self' 'unsafe-inline' 'unsafe-eval' data:;\
font-src 'self' 'unsafe-inline' 'unsafe-eval' https://fonts.gstatic.com data:;\ font-src 'self' 'unsafe-inline' 'unsafe-eval' https://fonts.gstatic.com data:;\
" "

@ -163,7 +163,16 @@ defmodule BlockScoutWeb.Etherscan do
"contractAddress" => "0x0000000000000000000000000000000000000000", "contractAddress" => "0x0000000000000000000000000000000000000000",
"name" => "Example Token", "name" => "Example Token",
"decimals" => "18", "decimals" => "18",
"symbol" => "ET" "symbol" => "ET",
"type" => "ERC-20"
},
%{
"balance" => "1",
"contractAddress" => "0x0000000000000000000000000000000000000001",
"name" => "Example ERC-721 Token",
"decimals" => "18",
"symbol" => "ET7",
"type" => "ERC-721"
} }
] ]
} }
@ -440,6 +449,7 @@ defmodule BlockScoutWeb.Etherscan do
"from" => "0x000000000000000000000000000000000000000c", "from" => "0x000000000000000000000000000000000000000c",
"gasLimit" => "91966", "gasLimit" => "91966",
"gasUsed" => "95123", "gasUsed" => "95123",
"gasPrice" => "100000",
"hash" => "0x0000000000000000000000000000000000000000000000000000000000000004", "hash" => "0x0000000000000000000000000000000000000000000000000000000000000004",
"input" => "0x04", "input" => "0x04",
"logs" => [ "logs" => [
@ -986,6 +996,7 @@ defmodule BlockScoutWeb.Etherscan do
input: @input_type, input: @input_type,
gasLimit: @wei_type, gasLimit: @wei_type,
gasUsed: @gas_type, gasUsed: @gas_type,
gasPrice: @wei_type,
logs: %{ logs: %{
type: "array", type: "array",
array_type: @logs_details array_type: @logs_details

@ -126,7 +126,7 @@ defmodule BlockScoutWeb.Notifier do
""" """
def broadcast_blocks_indexed_ratio(ratio, finished?) do def broadcast_blocks_indexed_ratio(ratio, finished?) do
Endpoint.broadcast("blocks:indexing", "index_status", %{ Endpoint.broadcast("blocks:indexing", "index_status", %{
ratio: ratio, ratio: Decimal.to_string(ratio),
finished: finished? finished: finished?
}) })
end end

@ -2,6 +2,7 @@ defmodule BlockScoutWeb.Router do
use BlockScoutWeb, :router use BlockScoutWeb, :router
alias BlockScoutWeb.Plug.GraphQL alias BlockScoutWeb.Plug.GraphQL
alias BlockScoutWeb.{ApiRouter, WebRouter}
forward("/wobserver", Wobserver.Web.Router) forward("/wobserver", Wobserver.Web.Router)
forward("/admin", BlockScoutWeb.AdminRouter) forward("/admin", BlockScoutWeb.AdminRouter)
@ -18,249 +19,54 @@ defmodule BlockScoutWeb.Router do
plug(:accepts, ["json"]) plug(:accepts, ["json"])
end end
scope "/api/v1", BlockScoutWeb.API.V1, as: :api_v1 do forward("/api", ApiRouter)
pipe_through(:api)
get("/supply", SupplyController, :supply) if Application.get_env(:block_scout_web, ApiRouter)[:reading_enabled] do
# Needs to be 200 to support the schema introspection for graphiql
@max_complexity 200
get("/health", HealthController, :health) forward("/graphql", Absinthe.Plug,
schema: BlockScoutWeb.Schema,
analyze_complexity: true,
max_complexity: @max_complexity
)
resources("/decompiled_smart_contract", DecompiledSmartContractController, only: [:create]) forward("/graphiql", Absinthe.Plug.GraphiQL,
resources("/verified_smart_contracts", VerifiedSmartContractController, only: [:create]) schema: BlockScoutWeb.Schema,
interface: :advanced,
default_query: GraphQL.default_query(),
socket: BlockScoutWeb.UserSocket,
analyze_complexity: true,
max_complexity: @max_complexity
)
else
scope "/", BlockScoutWeb do
pipe_through(:browser)
get("/api_docs", PageNotFoundController, :index)
get("/eth_rpc_api_docs", PageNotFoundController, :index)
end
end end
scope "/verify_smart_contract" do scope "/", BlockScoutWeb do
pipe_through(:api) pipe_through(:browser)
post("/contract_verifications", BlockScoutWeb.AddressContractVerificationController, :create) get("/api_docs", APIDocsController, :index)
get("/eth_rpc_api_docs", APIDocsController, :eth_rpc)
end end
scope "/api", BlockScoutWeb.API.RPC do scope "/verify_smart_contract" do
pipe_through(:api) pipe_through(:api)
alias BlockScoutWeb.API.RPC post("/contract_verifications", BlockScoutWeb.AddressContractVerificationController, :create)
post("/eth_rpc", EthController, :eth_request)
forward("/", RPCTranslator, %{
"block" => RPC.BlockController,
"account" => RPC.AddressController,
"logs" => RPC.LogsController,
"token" => RPC.TokenController,
"stats" => RPC.StatsController,
"contract" => RPC.ContractController,
"transaction" => RPC.TransactionController
})
end
# Needs to be 200 to support the schema introspection for graphiql
@max_complexity 200
forward("/graphql", Absinthe.Plug,
schema: BlockScoutWeb.Schema,
analyze_complexity: true,
max_complexity: @max_complexity
)
forward("/graphiql", Absinthe.Plug.GraphiQL,
schema: BlockScoutWeb.Schema,
interface: :advanced,
default_query: GraphQL.default_query(),
socket: BlockScoutWeb.UserSocket,
analyze_complexity: true,
max_complexity: @max_complexity
)
# Disallows Iframes (write routes)
scope "/", BlockScoutWeb do
pipe_through(:browser)
end end
# Allows Iframes (read-only routes) if Application.get_env(:block_scout_web, WebRouter)[:enabled] do
scope "/", BlockScoutWeb do forward("/", BlockScoutWeb.WebRouter)
pipe_through([:browser, BlockScoutWeb.Plug.AllowIframe]) else
scope "/", BlockScoutWeb do
resources("/", ChainController, only: [:show], singleton: true, as: :chain) pipe_through(:browser)
resources("/market_history_chart", Chain.MarketHistoryChartController,
only: [:show],
singleton: true
)
resources "/blocks", BlockController, only: [:index, :show], param: "hash_or_number" do forward("/", APIDocsController, :index)
resources("/transactions", BlockTransactionController, only: [:index], as: :transaction)
end end
get("/reorgs", BlockController, :reorg, as: :reorg)
get("/uncles", BlockController, :uncle, as: :uncle)
resources("/pending_transactions", PendingTransactionController, only: [:index])
resources("/recent_transactions", RecentTransactionsController, only: [:index])
get("/txs", TransactionController, :index)
resources "/tx", TransactionController, only: [:show] do
resources(
"/internal_transactions",
TransactionInternalTransactionController,
only: [:index],
as: :internal_transaction
)
resources(
"/raw_trace",
TransactionRawTraceController,
only: [:index],
as: :raw_trace
)
resources("/logs", TransactionLogController, only: [:index], as: :log)
resources("/token_transfers", TransactionTokenTransferController,
only: [:index],
as: :token_transfer
)
end
resources("/accounts", AddressController, only: [:index])
resources "/address", AddressController, only: [:show] do
resources("/transactions", AddressTransactionController, only: [:index], as: :transaction)
resources(
"/internal_transactions",
AddressInternalTransactionController,
only: [:index],
as: :internal_transaction
)
resources(
"/validations",
AddressValidationController,
only: [:index],
as: :validation
)
resources(
"/contracts",
AddressContractController,
only: [:index],
as: :contract
)
resources(
"/decompiled_contracts",
AddressDecompiledContractController,
only: [:index],
as: :decompiled_contract
)
resources(
"/logs",
AddressLogsController,
only: [:index],
as: :logs
)
resources(
"/contract_verifications",
AddressContractVerificationController,
only: [:new],
as: :verify_contract
)
resources(
"/read_contract",
AddressReadContractController,
only: [:index, :show],
as: :read_contract
)
resources("/tokens", AddressTokenController, only: [:index], as: :token) do
resources(
"/token_transfers",
AddressTokenTransferController,
only: [:index],
as: :transfers
)
end
resources(
"/token_balances",
AddressTokenBalanceController,
only: [:index],
as: :token_balance
)
resources(
"/coin_balances",
AddressCoinBalanceController,
only: [:index],
as: :coin_balance
)
resources(
"/coin_balances/by_day",
AddressCoinBalanceByDayController,
only: [:index],
as: :coin_balance_by_day
)
end
resources "/tokens", Tokens.TokenController, only: [:show], as: :token do
resources(
"/token_transfers",
Tokens.TransferController,
only: [:index],
as: :transfer
)
resources(
"/read_contract",
Tokens.ReadContractController,
only: [:index],
as: :read_contract
)
resources(
"/token_holders",
Tokens.HolderController,
only: [:index],
as: :holder
)
resources(
"/inventory",
Tokens.InventoryController,
only: [:index],
as: :inventory
)
end
resources(
"/smart_contracts",
SmartContractController,
only: [:index, :show],
as: :smart_contract
)
get("/search", ChainController, :search)
get("/search_logs", AddressLogsController, :search_logs)
get("/transactions_csv", AddressTransactionController, :transactions_csv)
get("/token_autocomplete", ChainController, :token_autocomplete)
get("/token_transfers_csv", AddressTransactionController, :token_transfers_csv)
get("/chain_blocks", ChainController, :chain_blocks, as: :chain_blocks)
get("/api_docs", APIDocsController, :index)
get("/eth_rpc_api_docs", APIDocsController, :eth_rpc)
get("/*path", PageNotFoundController, :index)
end end
end end

@ -137,7 +137,7 @@ defmodule BlockScoutWeb.Schema.Types do
field(:r, :decimal) field(:r, :decimal)
field(:s, :decimal) field(:s, :decimal)
field(:status, :status) field(:status, :status)
field(:v, :integer) field(:v, :decimal)
field(:value, :wei) field(:value, :wei)
field(:from_address_hash, :address_hash) field(:from_address_hash, :address_hash)
field(:to_address_hash, :address_hash) field(:to_address_hash, :address_hash)

@ -79,7 +79,7 @@
<%= gettext("Incoming Transactions") %> <%= gettext("Incoming Transactions") %>
<% else %> <% else %>
<span data-selector="transaction-count"> <span data-selector="transaction-count">
<%= Cldr.Number.to_string!(@transaction_count, format: "#,###") %> <%= BlockScoutWeb.Cldr.Number.to_string!(@transaction_count, format: "#,###") %>
</span> </span>
<%= gettext("Transactions Sent") %> <%= gettext("Transactions Sent") %>
<% end %> <% end %>
@ -92,7 +92,7 @@
<%= if validator?(@validation_count) do %> <%= if validator?(@validation_count) do %>
<span class="address-detail-item"> <span class="address-detail-item">
<span data-selector="validation-count"> <span data-selector="validation-count">
<%= Cldr.Number.to_string!(@validation_count, format: "#,###") %> <%= BlockScoutWeb.Cldr.Number.to_string!(@validation_count, format: "#,###") %>
</span> </span>
<%= gettext("Blocks Validated") %> <%= gettext("Blocks Validated") %>
</span> </span>
@ -104,8 +104,7 @@
<%= if contract?(@address) do %> <%= if contract?(@address) do %>
<span class="text-muted" data-test="address_contract_creator"> <span class="text-muted" data-test="address_contract_creator">
<%= if from_address_hash do %> <%= if from_address_hash do %>
<%= gettext "Created by" %> <%= gettext "Created by" %> <%= link(
<%= link(
trimmed_hash(from_address_hash(@address)), trimmed_hash(from_address_hash(@address)),
to: address_path(@conn, :show, from_address_hash(@address)) to: address_path(@conn, :show, from_address_hash(@address))
) %> ) %>

@ -11,10 +11,10 @@
) %> ) %>
</h3> </h3>
</dd> </dd>
<dt class="col-md-2"><%= gettext "Decoded" %></dt>
<dd class="col-md-10">
<%= case decode(@log, @log.transaction) do %> <%= case decode(@log, @log.transaction) do %>
<% {:error, :contract_not_verified} -> %> <% {:error, :contract_not_verified} -> %>
<dt class="col-md-2"><%= gettext "Decoded" %></dt>
<dd class="col-md-10">
<div class="alert alert-info"> <div class="alert alert-info">
<%= gettext "To see decoded input data, the contract must be verified." %> <%= gettext "To see decoded input data, the contract must be verified." %>
<%= case @log.transaction do %> <%= case @log.transaction do %>
@ -25,10 +25,14 @@
<% end %> <% end %>
</div> </div>
<% {:error, :could_not_decode} -> %> <% {:error, :could_not_decode} -> %>
<dt class="col-md-2"><%= gettext "Decoded" %></dt>
<dd class="col-md-10">
<div class="alert alert-danger"> <div class="alert alert-danger">
<%= gettext "Failed to decode log data." %> <%= gettext "Failed to decode log data." %>
</div> </div>
<% {:ok, method_id, text, mapping} -> %> <% {:ok, method_id, text, mapping} -> %>
<dt class="col-md-2"><%= gettext "Decoded" %></dt>
<dd class="col-md-10">
<table summary="Transaction Info" class="table thead-light table-bordered transaction-input-table"> <table summary="Transaction Info" class="table thead-light table-bordered transaction-input-table">
<tr> <tr>
<td>Method Id</td> <td>Method Id</td>
@ -78,7 +82,7 @@
<% end %> <% end %>
</table> </table>
</div> </div>
<% _ -> %> <% _ -> %>
<%= nil %> <%= nil %>
<% end %> <% end %>
<dt class="col-md-2"><%= gettext "Topics" %></dt> <dt class="col-md-2"><%= gettext "Topics" %></dt>

@ -0,0 +1,182 @@
<div class="api-doc-list-item" >
<div class="api-doc-list-item-contents" href="#<%= @action %>">
<div class="api-doc-list-item-description">
<h3 class="api-doc-list-item-title"><%= @action %></h3>
<p class="api-doc-list-item-contents"><%= raw @info.notes %></p>
<span class="api-doc-list-item-query api-text-monospace api-text-monospace-background btn"
data-clipboard-text="curl -X POST --data '{&quot;id&quot;:0,&quot;jsonrpc&quot;:&quot;2.0&quot;,&quot;method&quot;: &quot;<%= @action %>&quot;, params: []}'"
>
curl -X POST --data '{"id":0,"jsonrpc":"2.0","method": "<%= @action %>", params: []}'
</span>
<p class="api-doc-list-item-text">
<div class="tile tile-muted p-1">
<pre
class="m-2"
data-json='<%= @info.example %>'
></pre>
</div>
</p>
</div>
<div class="api-doc-list-item-controls"
aria-controls="<%= @action %>"
aria-expanded="false"
data-toggle="collapse"
href="#<%= @action %>">
<div class="api-doc-list-item-controls-badges">
<span class="badge badge-neutral api-badge"><%= gettext "POST" %></span>
</div>
<span class="api-doc-list-item-controls-view-more">
<span class="api-doc-list-item-controls-view-more-open">More Details <span class="fa fa-chevron-down"></span></span>
<span class="api-doc-list-item-controls-view-more-close">Hide Details <span class="fa fa-chevron-up"></span></span>
</span>
</div>
</div>
<!-- Parameters -->
<div
class="collapse multi-collapse api-doc-parameters-container"
id="<%= @action %>"
>
<h3 class="card-title margin-bottom-md">
<%= gettext "Parameters" %>
<button
class="btn-full-primary float-right"
data-action="<%= @action %>"
data-module="eth"
data-selector='<%= "eth-#{@action}-btn-try-api" %>'
role="button"
><%= gettext "Try it out" %></button>
<button
class="collapse btn-line float-right"
data-action="<%= @action %>"
data-module="eth"
data-selector='<%= "eth-#{@action}-btn-try-api-cancel" %>'
role="button"
><%= gettext "Cancel" %></button>
</h3>
<!-- Parameters description list -->
<div class="api-doc-parameters-list">
<div class="row d-none d-md-flex">
<h4 class="col-2 api-doc-parameters-list-title"><%= gettext "Name" %></h4>
<h3 class="col-10 api-doc-parameters-list-title"><%= gettext "Description" %></h3>
</div>
<!-- Params -->
<%= for param <- @info.params do %>
<div class="row api-doc-parameters-list-item">
<div class="col-sm-4 col-md-2">
<h5 class="api-doc-parameters-list-item-title">
<%= param.name %>
<%= if param.required do %>
<span class="align-text-bottom text-danger">
*<small><%= gettext "required" %></small>
</span>
<% end %>
</h5>
</div>
<div class="col-sm-8 col-md-10">
<p class="api-doc-parameters-list-item-description"><%= param.description %></p>
<input
class="collapse form-control border-rounded <%= if param.required && !param.default, do: "form-control-danger is-invalid" %>"
data-parameter-type='<%= param.type %>'
data-required='<%= if param.required, do: "true", else: "false" %>'
data-selector='<%= "eth-#{@action}-try-api-ui" %>'
type="text",
value='<%= param.default %>'
/>
</div>
</div>
<% end %>
<!-- Buttons for Other / Extra -->
<div class="row">
<div class="offset-sm-4 offset-md-2 col-10">
<button
class="collapse button button-primary"
data-action="<%= @action %>"
data-module="eth"
data-selector='<%= "eth-#{@action}-try-api-ui" %>'
data-try-eth-api-ui-button-type="execute"
role="button"
><%= gettext "Execute" %></button>
<button
class="collapse button button-secondary"
data-action="<%= @action %>"
data-module="eth"
data-selector='<%= "eth-#{@action}-btn-try-api-clear" %>'
role="button"
><%= gettext "Clear" %></button>
</div>
</div>
<!-- CURL / Request URL / Server Response -->
<div
class="tile text-dark mt-5 collapse"
data-selector='<%= "eth-#{@action}-try-api-ui-result" %>'
>
<div class="mb-3">
<h5 class="api-doc-parameters-list-item-title"><%= gettext "Curl" %></h5>
<div class="tile tile-muted p-1">
<pre
class="m-2"
data-selector='<%= "eth-#{@action}-curl" %>'
></pre>
</div>
</div>
<h5 class="api-doc-parameters-list-item-title"><%= gettext "Server Response" %></h5>
<div class="row">
<h4 class="col-2 api-doc-parameters-list-title"><%= gettext "Code" %></h4>
<h4 class="col-10 api-doc-parameters-list-title"><%= gettext "Details" %></h4>
</div>
<div class="row">
<div
class="col-2 pr-0 pr-md-2 col-md-2"
><strong data-selector='<%= "eth-#{@action}-server-response-code" %>'></strong></div>
<div class="col-10 col-md-10">
<p class="api-doc-parameters-list-item-description"><%= gettext "Response Body" %></p>
<div class="tile tile-muted p-1 card-server-response-body">
<pre
class="m-2"
data-selector='<%= "eth-#{@action}-server-response-body" %>'
></pre>
</div>
</div>
</div>
</div>
</div>
<!-- Responses -->
<h3 class="card-title margin-bottom-md"><%= gettext "Responses" %></h3>
<div class="row api-doc-parameters-list-item mb-0">
<h4 class="col-2 api-doc-parameters-list-title"><%= gettext "Code" %></h4>
<h5 class="col-10 api-doc-parameters-list-title"><%= gettext "Description" %></h5>
</div>
<div class="row api-doc-parameters-list-item">
<div class="col-2 pr-0 pr-md-2"><strong>200</strong></div>
<div class="col-10">
<div class="tile tile-muted p-1 mb-3">
<pre class="m-2"><strong>successful operation</strong></pre>
</div>
<!-- Tabs -->
<ul
class="nav nav-pills mb-3"
role="tablist"
>
<li class="nav-item">
<a class="nav-link api-doc-tab active"><%= gettext "Example Value" %></a>
</li>
</ul>
<!-- Tab Content -->
<div class="tab-content">
<!-- Example Value Tab -->
<div class="tab-pane fade show active">
<div class="tile tile-muted p-1">
<pre
class="m-2"
data-json='<%= @info.result %>'
></pre>
</div>
</div>
</div>
</div>
</div>
</div>
</div>

@ -2,7 +2,7 @@
<div class="card"> <div class="card">
<div class="card-body"> <div class="card-body">
<h1 class="card-title margin-bottom-sm"><%= gettext("ETH RPC API Documentation") %></h2> <h1 class="card-title margin-bottom-sm"><%= gettext("ETH RPC API Documentation") %></h2>
<p class="api-text-monospace" data-endpoint-url="<%= blockscout_url() %>/api/eth_rpc">[ <%= gettext "Base URL:" %> <%= blockscout_url() %>/api/eth_rpc ]</p> <p class="api-text-monospace" data-endpoint-url="<%= eth_rpc_api_url() %>">[ <%= gettext "Base URL:" %> <%= eth_rpc_api_url()%> ]</p>
<p class="card-subtitle margin-bottom-0"> <p class="card-subtitle margin-bottom-0">
<%= gettext "This API is provided to support some rpc methods in the exact format specified for ethereum nodes, which can be found " %> <%= gettext "This API is provided to support some rpc methods in the exact format specified for ethereum nodes, which can be found " %>
@ -17,20 +17,8 @@
</div> </div>
</div> </div>
<div class="card"> <div class="card">
<div class="card-body"> <%= for {method, info} <- Map.to_list(@documentation) do %>
<table class="table"> <%= render "_eth_rpc_item.html", action: method, info: info %>
<tr> <% end %>
<th>Supported Method</th>
<th>Notes</th>
<th>Parameters example</th>
</tr>
<%= for {method, info} <- Map.to_list(@documentation) do %>
<tr>
<td> <a href="https://github.com/ethereum/wiki/wiki/JSON-RPC#<%= method %>"> <%= method %> </a> </td>
<td> <%= Map.get(info, :notes, "N/A") %> </td>
<td> <%= Map.get(info, :example, "N/A") %> </td>
</tr>
<% end %>
</table>
</div> </div>
</section> </section>

@ -2,7 +2,7 @@
<div class="card"> <div class="card">
<div class="card-body"> <div class="card-body">
<h1 class="card-title margin-bottom-sm"><%= gettext("API Documentation") %></h2> <h1 class="card-title margin-bottom-sm"><%= gettext("API Documentation") %></h2>
<p class="api-text-monospace" data-endpoint-url="<%= blockscout_url() %>/api">[ <%= gettext "Base URL:" %> <%= blockscout_url() %>/api ]</p> <p class="api-text-monospace" data-endpoint-url="<%= api_url() %>">[ <%= gettext "Base URL:" %> <%= api_url()%> ]</p>
<p class="card-subtitle margin-bottom-0"><%= gettext "This API is provided for developers transitioning their applications from Etherscan to BlockScout. It supports GET and POST requests." %></p> <p class="card-subtitle margin-bottom-0"><%= gettext "This API is provided for developers transitioning their applications from Etherscan to BlockScout. It supports GET and POST requests." %></p>
</div> </div>
<div class="api-anchors-list"> <div class="api-anchors-list">

@ -57,7 +57,7 @@
<dl class="row"> <dl class="row">
<dt class="col-sm-3 text-muted"><%= gettext "Difficulty" %></dt> <dt class="col-sm-3 text-muted"><%= gettext "Difficulty" %></dt>
<dd class="col-sm-9"> <dd class="col-sm-9">
<span><%= @block.difficulty |> Cldr.Number.to_string! %></span> <span><%= @block.difficulty |> BlockScoutWeb.Cldr.Number.to_string! %></span>
</dd> </dd>
</dl> </dl>
@ -65,7 +65,7 @@
<!-- Total Difficulty --> <!-- Total Difficulty -->
<dl class="row"> <dl class="row">
<dt class="col-sm-3 text-muted"><%= gettext "Total Difficulty" %></dt> <dt class="col-sm-3 text-muted"><%= gettext "Total Difficulty" %></dt>
<dd class="col-sm-9"><span><%= @block.total_difficulty |> Cldr.Number.to_string! %></span></dd> <dd class="col-sm-9"><span><%= @block.total_difficulty |> BlockScoutWeb.Cldr.Number.to_string! %></span></dd>
</dl> </dl>
<!-- Nonce --> <!-- Nonce -->
@ -97,14 +97,14 @@
<dl class="row"> <dl class="row">
<dt class="col-sm-3 text-muted"><%= gettext "Gas Used" %></dt> <dt class="col-sm-3 text-muted"><%= gettext "Gas Used" %></dt>
<dd class="col-sm-9"> <dd class="col-sm-9">
<span><%= @block.gas_used |> Cldr.Number.to_string! %></span> <span><%= @block.gas_used |> BlockScoutWeb.Cldr.Number.to_string! %></span>
<span class="text-muted">(<%= (Decimal.to_integer(@block.gas_used) / Decimal.to_integer(@block.gas_limit)) |> Cldr.Number.to_string!(format: "#.#%") %>)</span> <span class="text-muted">(<%= (Decimal.to_integer(@block.gas_used) / Decimal.to_integer(@block.gas_limit)) |> BlockScoutWeb.Cldr.Number.to_string!(format: "#.#%") %>)</span>
</dt> </dt>
</dl> </dl>
<dl class="row mb-0"> <dl class="row mb-0">
<dt class="col-sm-3 text-muted"><%= gettext "Gas Limit" %></dt> <dt class="col-sm-3 text-muted"><%= gettext "Gas Limit" %></dt>
<dd class="col-sm-9"> <dd class="col-sm-9">
<span><%= Cldr.Number.to_string!(@block.gas_limit) %></span> <span><%= BlockScoutWeb.Cldr.Number.to_string!(@block.gas_limit) %></span>
</dd> </dd>
</dl> </dl>
<% end %> <% end %>
@ -146,10 +146,10 @@
<h2 class="card-title balance-card-title"><%= gettext "Gas Used" %></h2> <h2 class="card-title balance-card-title"><%= gettext "Gas Used" %></h2>
<div class="text-right"> <div class="text-right">
<h3 class="address-balance-text"> <h3 class="address-balance-text">
<%= @block.gas_used |> Cldr.Number.to_string! %> <%= @block.gas_used |> BlockScoutWeb.Cldr.Number.to_string! %>
<span class="text-muted">(<%= (Decimal.to_integer(@block.gas_used) / Decimal.to_integer(@block.gas_limit)) |> Cldr.Number.to_string!(format: "#.#%") %>)</span> <span class="text-muted">(<%= (Decimal.to_integer(@block.gas_used) / Decimal.to_integer(@block.gas_limit)) |> BlockScoutWeb.Cldr.Number.to_string!(format: "#.#%") %>)</span>
</h3> </h3>
<p class="address-current-balance"><%= @block.gas_limit |> Cldr.Number.to_string! %><%= gettext "Gas Limit" %></p> <p class="address-current-balance"><%= @block.gas_limit |> BlockScoutWeb.Cldr.Number.to_string! %><%= gettext "Gas Limit" %></p>
</div> </div>
<% end %> <% end %>
</div> </div>

@ -20,8 +20,7 @@
</div> </div>
<%= if BlockScoutWeb.BlockView.show_reward?(@block.rewards) do %> <%= if BlockScoutWeb.BlockView.show_reward?(@block.rewards) do %>
<div class="text-truncate"> <div class="text-truncate">
<%= gettext "Reward" %> <%= gettext "Reward" %> <%= BlockScoutWeb.BlockView.combined_rewards_value(@block) %>
<%= BlockScoutWeb.BlockView.combined_rewards_value(@block) %>
</div> </div>
<% end %> <% end %>
</div> </div>

@ -56,7 +56,7 @@
<%= gettext "Total transactions" %> <%= gettext "Total transactions" %>
</span> </span>
<span class="dashboard-banner-network-stats-value" data-selector="transaction-count"> <span class="dashboard-banner-network-stats-value" data-selector="transaction-count">
<%= Cldr.Number.to_string!(@transaction_estimated_count, format: "#,###") %> <%= BlockScoutWeb.Cldr.Number.to_string!(@transaction_estimated_count, format: "#,###") %>
</span> </span>
</div> </div>
<div class="dashboard-banner-network-stats-item dashboard-banner-network-stats-item-3"> <div class="dashboard-banner-network-stats-item dashboard-banner-network-stats-item-3">
@ -64,7 +64,7 @@
<%= gettext "Total blocks" %> <%= gettext "Total blocks" %>
</span> </span>
<span class="dashboard-banner-network-stats-value" data-selector="block-count"> <span class="dashboard-banner-network-stats-value" data-selector="block-count">
<%= Cldr.Number.to_string!(@block_count, format: "#,###") %> <%= BlockScoutWeb.Cldr.Number.to_string!(@block_count, format: "#,###") %>
</span> </span>
</div> </div>
<div class="dashboard-banner-network-stats-item dashboard-banner-network-stats-item-4"> <div class="dashboard-banner-network-stats-item dashboard-banner-network-stats-item-4">
@ -72,7 +72,7 @@
<%= gettext "Wallet addresses" %> <%= gettext "Wallet addresses" %>
</span> </span>
<span class="dashboard-banner-network-stats-value" data-selector="address-count"> <span class="dashboard-banner-network-stats-value" data-selector="address-count">
<%= Cldr.Number.to_string!(@address_count, format: "#,###") %> <%= BlockScoutWeb.Cldr.Number.to_string!(@address_count, format: "#,###") %>
</span> </span>
</div> </div>
</div> </div>

@ -14,15 +14,14 @@
<!-- Pagination --> <!-- Pagination -->
<ul class="pagination"> <ul class="pagination">
<!-- First --> <!-- First -->
<%= if assigns[:first_page_path] do %> <li class="page-item">
<li class="page-item"> <a
<a disabled
<%= if !assigns[:first_page_path] do %>disabled<% end %> class="page-link"
class="page-link" href
href='<%= "#{assigns[:first_page_path]}" %>' data-first-page-button
>First</a> >First</a>
</li> </li>
<% end %>
<!-- Previous --> <!-- Previous -->
<li class="page-item"> <li class="page-item">
<a <a

@ -1,4 +1,4 @@
<div data-selector="loading-message" class="tile tile-type-loading"> <div data-loading-message data-selector="loading-message" class="tile tile-type-loading">
<div class="row tile-body"> <div class="row tile-body">
<div class="tile-transaction-type-block col-md-2 d-flex flex-row flex-md-column"> <div class="tile-transaction-type-block col-md-2 d-flex flex-row flex-md-column">
<span class="tile-label"> <span class="tile-label">
@ -22,7 +22,7 @@
</div> </div>
</div> </div>
</div> </div>
<div data-selector="loading-message" class="tile tile-type-loading"> <div data-loading-message data-selector="loading-message" class="tile tile-type-loading">
<div class="row tile-body"> <div class="row tile-body">
<div class="tile-transaction-type-block col-md-2 d-flex flex-row flex-md-column"> <div class="tile-transaction-type-block col-md-2 d-flex flex-row flex-md-column">
<span class="tile-label"> <span class="tile-label">
@ -46,7 +46,7 @@
</div> </div>
</div> </div>
</div> </div>
<div data-selector="loading-message" class="tile tile-type-loading"> <div data-loading-message data-selector="loading-message" class="tile tile-type-loading">
<div class="row tile-body"> <div class="row tile-body">
<div class="tile-transaction-type-block col-md-2 d-flex flex-row flex-md-column"> <div class="tile-transaction-type-block col-md-2 d-flex flex-row flex-md-column">
<span class="tile-label"> <span class="tile-label">

@ -17,8 +17,7 @@
</span> </span>
<span class="d-flex flex-md-row flex-column mt-3 mt-md-0"> <span class="d-flex flex-md-row flex-column mt-3 mt-md-0">
<span class="tile-title"> <span class="tile-title">
<%= BlockScoutWeb.TransactionView.value(@internal_transaction, include_label: false) %> <%= BlockScoutWeb.TransactionView.value(@internal_transaction, include_label: false) %> <%= gettext "Ether" %>
<%= gettext "Ether" %>
</span> </span>
</span> </span>
</div> </div>

@ -3,7 +3,7 @@
<!-- Logo --> <!-- Logo -->
<div class="row footer-logo-row"> <div class="row footer-logo-row">
<div class="col-md-12"> <div class="col-md-12">
<%= link to: chain_path(@conn, :show), class: "footer-brand" do %> <%= link to: webapp_url(@conn), class: "footer-brand" do %>
<img class="footer-logo" src="<%= logo_footer() %>" alt="<%= subnetwork_title() %>" /> <img class="footer-logo" src="<%= logo_footer() %>" alt="<%= subnetwork_title() %>" />
<% end %> <% end %>
</div> </div>

@ -5,7 +5,7 @@
<span class="radio-icon"></span> <span class="radio-icon"></span>
</span> </span>
<span class="network-selector-item-content"> <span class="network-selector-item-content">
<span class='network-selector-item-icon network-selector-item-icon-<%= String.downcase(String.replace(@title, " ", "-")) %>' style="background-image: url('/images/network-selector-icons/<%= String.downcase(String.replace(@title, " ", "-")) %>.png');"></span> <span class='network-selector-item-icon network-selector-item-icon-<%= String.downcase(String.replace(@title, " ", "-")) %>'></span>
<span class="network-selector-item-title"> <span class="network-selector-item-title">
<%= @title %> <%= @title %>
</span> </span>

@ -1,3 +1,7 @@
<link rel="preload" href="/css/awesomplete.css" as="style" onload="this.rel='stylesheet'">
<noscript><link rel="stylesheet" href="/css/awesomplete.css"></noscript>
<script src="/js/awesomplete.min.js"></script>
<script src="/js/awesomplete-util.min.js"></script>
<nav class="navbar navbar-dark navbar-expand-lg navbar-primary" data-selector="navbar" id="top-navbar"> <nav class="navbar navbar-dark navbar-expand-lg navbar-primary" data-selector="navbar" id="top-navbar">
<script> <script>
if (localStorage.getItem("current-color-mode") === "dark") { if (localStorage.getItem("current-color-mode") === "dark") {
@ -5,7 +9,7 @@
} }
</script> </script>
<div class="container-fluid navbar-container"> <div class="container-fluid navbar-container">
<%= link to: chain_path(@conn, :show), class: "navbar-brand", "data-test": "header_logo" do %> <%= link to: webapp_url(@conn), class: "navbar-brand", "data-test": "header_logo" do %>
<img class="navbar-logo" id="navbar-logo" src="<%= logo() %>" alt="<%= subnetwork_title() %>" /> <img class="navbar-logo" id="navbar-logo" src="<%= logo() %>" alt="<%= subnetwork_title() %>" />
<% end %> <% end %>
<script> <script>
@ -18,79 +22,83 @@
</button> </button>
<div class="collapse navbar-collapse" id="navbarSupportedContent"> <div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav"> <ul class="navbar-nav">
<li class="nav-item dropdown"> <%= if Application.get_env(:block_scout_web, BlockScoutWeb.WebRouter)[:enabled] do %>
<a class="nav-link topnav-nav-link dropdown-toggle" href="#" id="navbarBlocksDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"> <li class="nav-item dropdown">
<span class="nav-link-icon"> <a class="nav-link topnav-nav-link dropdown-toggle" href="#" id="navbarBlocksDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<%= render BlockScoutWeb.IconsView, "_block_icon.html" %> <span class="nav-link-icon">
</span> <%= render BlockScoutWeb.IconsView, "_block_icon.html" %>
<%= gettext("Blocks") %> </span>
</a>
<div class="dropdown-menu" aria-labelledby="navbarBlocksDropdown">
<%= link to: block_path(@conn, :index), class: "dropdown-item #{tab_status("blocks", @conn.request_path)}" do %>
<%= gettext("Blocks") %> <%= gettext("Blocks") %>
</a>
<div class="dropdown-menu" aria-labelledby="navbarBlocksDropdown">
<%= link to: block_path(@conn, :index), class: "dropdown-item #{tab_status("blocks", @conn.request_path)}" do %>
<%= gettext("Blocks") %>
<% end %>
<%= link to: uncle_path(@conn, :uncle), class: "dropdown-item #{tab_status("uncles", @conn.request_path)}" do %>
<%= gettext("Uncles") %>
<% end %>
<%= link to: reorg_path(@conn, :reorg), class: "dropdown-item #{tab_status("reorgs", @conn.request_path)}" do %>
<%= gettext("Forked Blocks (Reorgs)") %>
<% end %>
</div>
</li>
<li class="nav-item dropdown">
<a href="#" role="button" id="navbarTransactionsDropdown" class="nav-link topnav-nav-link dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<span class="nav-link-icon">
<%= render BlockScoutWeb.IconsView, "_transaction_icon.html" %>
</span>
<%= gettext("Transactions") %>
</a>
<div class="dropdown-menu" aria-labeledby="navbarTransactionsDropdown">
<%= link(
gettext("Validated"),
class: "dropdown-item #{tab_status("txs", @conn.request_path)}",
to: transaction_path(@conn, :index)
) %>
<%= link(
gettext("Pending"),
class: "dropdown-item #{tab_status("pending_transactions", @conn.request_path)}",
"data-test": "pending_transactions_link",
to: pending_transaction_path(@conn, :index)
) %>
</div>
</li>
<li class="nav-item">
<%= link to: address_path(@conn, :index), class: "nav-link topnav-nav-link #{tab_status("accounts", @conn.request_path)}" do %>
<span class="nav-link-icon">
<%= render BlockScoutWeb.IconsView, "_accounts_icon.html" %>
</span>
<%= gettext("Accounts") %>
<% end %> <% end %>
<%= link to: uncle_path(@conn, :uncle), class: "dropdown-item #{tab_status("uncles", @conn.request_path)}" do %> </li>
<%= gettext("Uncles") %> <% end %>
<% end %> <%= if Application.get_env(:block_scout_web, BlockScoutWeb.ApiRouter)[:reading_enabled] || Application.get_env(:block_scout_web, :api_url) do %>
<%= link to: reorg_path(@conn, :reorg), class: "dropdown-item #{tab_status("reorgs", @conn.request_path)}" do %> <li class="nav-item dropdown">
<%= gettext("Forked Blocks (Reorgs)") %> <a href="#" role="button" id="navbarAPIsDropdown" class="nav-link topnav-nav-link dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<% end %> <span class="nav-link-icon">
</div> <%= render BlockScoutWeb.IconsView, "_api_icon.html" %>
</li> </span>
<li class="nav-item dropdown"> <%= gettext("APIs") %>
<a href="#" role="button" id="navbarTransactionsDropdown" class="nav-link topnav-nav-link dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"> </a>
<span class="nav-link-icon"> <div class="dropdown-menu" aria-labeledby="navbarTransactionsDropdown">
<%= render BlockScoutWeb.IconsView, "_transaction_icon.html" %> <%= link(
</span> gettext("GraphQL"),
<%= gettext("Transactions") %> class: "dropdown-item #{tab_status("graphiql", @conn.request_path)}",
</a> to: api_url() <> "/graphiql"
<div class="dropdown-menu" aria-labeledby="navbarTransactionsDropdown"> ) %>
<%= link( <%= link(
gettext("Validated"), gettext("RPC"),
class: "dropdown-item #{tab_status("txs", @conn.request_path)}", class: "dropdown-item #{tab_status("api_docs", @conn.request_path)}",
to: transaction_path(@conn, :index) to: api_url() <> api_docs_path(@conn, :index)
) %> ) %>
<%= link( <%= link(
gettext("Pending"), gettext("Eth RPC"),
class: "dropdown-item #{tab_status("pending_transactions", @conn.request_path)}", class: "dropdown-item #{tab_status("eth_rpc_api_docs", @conn.request_path)}",
"data-test": "pending_transactions_link", to: api_url() <> api_docs_path(@conn, :eth_rpc)
to: pending_transaction_path(@conn, :index) ) %>
) %> </div>
</div> </li>
</li> <% end %>
<li class="nav-item">
<%= link to: address_path(@conn, :index), class: "nav-link topnav-nav-link #{tab_status("accounts", @conn.request_path)}" do %>
<span class="nav-link-icon">
<%= render BlockScoutWeb.IconsView, "_accounts_icon.html" %>
</span>
<%= gettext("Accounts") %>
<% end %>
</li>
<li class="nav-item dropdown">
<a href="#" role="button" id="navbarAPIsDropdown" class="nav-link topnav-nav-link dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<span class="nav-link-icon">
<%= render BlockScoutWeb.IconsView, "_api_icon.html" %>
</span>
<%= gettext("APIs") %>
</a>
<div class="dropdown-menu" aria-labeledby="navbarTransactionsDropdown">
<%= link(
gettext("GraphQL"),
class: "dropdown-item #{tab_status("graphiql", @conn.request_path)}",
to: "/graphiql"
) %>
<%= link(
gettext("RPC"),
class: "dropdown-item #{tab_status("api_docs", @conn.request_path)}",
to: api_docs_path(@conn, :index)
) %>
<%= link(
gettext("Eth RPC"),
class: "dropdown-item #{tab_status("api_docs", @conn.request_path)}",
to: api_docs_path(@conn, :eth_rpc)
) %>
</div>
</li>
<li class="nav-item dropdown nav-item-networks"> <li class="nav-item dropdown nav-item-networks">
<a class="nav-link topnav-nav-link active-icon <%= if dropdown_nets() != [], do: "dropdown-toggle js-show-network-selector" %>" href="#" id="navbarDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"> <a class="nav-link topnav-nav-link active-icon <%= if dropdown_nets() != [], do: "dropdown-toggle js-show-network-selector" %>" href="#" id="navbarDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<span class="nav-link-icon"> <span class="nav-link-icon">
@ -107,40 +115,42 @@
</svg> </svg>
</button> </button>
<!-- Search navbar --> <!-- Search navbar -->
<div class="search-form d-lg-flex d-inline-block" style="background-color: #22223a"> <%= if Application.get_env(:block_scout_web, BlockScoutWeb.WebRouter)[:enabled] do %>
<%= form_for @conn, chain_path(@conn, :search), [class: "form-inline my-2 my-lg-0", method: :get, enforce_utf8: false], fn f -> %> <div class="search-form d-lg-flex d-inline-block" style="background-color: #22223a">
<div class="input-group" title='<%= gettext("Search by address, token symbol name, transaction hash, or block number") %>'> <%= form_for @conn, chain_path(@conn, :search), [class: "form-inline my-2 my-lg-0", method: :get, enforce_utf8: false], fn f -> %>
<%= awesomplete(f, :q, <div class="input-group" title='<%= gettext("Search by address, token symbol name, transaction hash, or block number") %>'>
[ <%= awesomplete(f, :q,
class: "form-control me auto", [
placeholder: gettext("Search by address, token symbol name, transaction hash, or block number"), class: "form-control me auto",
"aria-describedby": "search-icon", placeholder: gettext("Search by address, token symbol name, transaction hash, or block number"),
"aria-label": gettext("Search"), "aria-describedby": "search-icon",
"data-test": "search_input" "aria-label": gettext("Search"),
], "data-test": "search_input"
[ url: "#{chain_path(@conn, :token_autocomplete)}?q=", ],
limit: 0, [ url: "#{chain_path(@conn, :token_autocomplete)}?q=",
minChars: 2, limit: 0,
value: "contract_address_hash", minChars: 2,
label: "contract_address_hash", value: "contract_address_hash",
descrSearch: true, label: "contract_address_hash",
descr: "symbol" descrSearch: true,
]) %> descr: "symbol"
<div class="input-group-append"> ]) %>
<button class="input-group-text" id="search-icon"> <div class="input-group-append">
<%= render BlockScoutWeb.IconsView, "_search_icon.html" %> <button class="input-group-text" id="search-icon">
</button> <%= render BlockScoutWeb.IconsView, "_search_icon.html" %>
</div> </button>
</div> </div>
<button class="btn btn-outline-success my-2 my-sm-0 sr-only hidden" type="submit"><%= gettext "Search" %></button> </div>
<script> <button class="btn btn-outline-success my-2 my-sm-0 sr-only hidden" type="submit"><%= gettext "Search" %></button>
if (localStorage.getItem("current-color-mode") === "dark") { <script>
document.getElementById("q").style.backgroundColor = "#22223a"; if (localStorage.getItem("current-color-mode") === "dark") {
document.getElementById("q").style.borderColor = "#22223a"; document.getElementById("q").style.backgroundColor = "#22223a";
} document.getElementById("q").style.borderColor = "#22223a";
</script> }
<% end %> </script>
</div> <% end %>
</div>
<% end %>
</div> </div>
</div> </div>
</nav> </nav>
@ -149,6 +159,5 @@
if (localStorage.getItem("current-color-mode") === "dark") { if (localStorage.getItem("current-color-mode") === "dark") {
var modeChanger = document.getElementById("dark-mode-changer"); var modeChanger = document.getElementById("dark-mode-changer");
modeChanger.className += " " + "dark-mode-changer--dark"; modeChanger.className += " " + "dark-mode-changer--dark";
document.body.className += " " + "dark-theme-applied";
} }
</script> </script>

@ -5,6 +5,8 @@
<meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="<%= static_path(@conn, "/css/app.css") %>"> <link rel="stylesheet" href="<%= static_path(@conn, "/css/app.css") %>">
<link rel="preload" href="/css/non-critical.css" as="style" onload="this.onload=null;this.rel='stylesheet'">
<noscript><link rel="stylesheet" href="/css/non-critical.css"></noscript>
<link rel="apple-touch-icon" sizes="180x180" href="<%= static_path(@conn, "/apple-touch-icon.png") %>"> <link rel="apple-touch-icon" sizes="180x180" href="<%= static_path(@conn, "/apple-touch-icon.png") %>">
<link rel="icon" type="image/png" sizes="32x32" href="<%= static_path(@conn, "/favicon-32x32.png") %>"> <link rel="icon" type="image/png" sizes="32x32" href="<%= static_path(@conn, "/favicon-32x32.png") %>">
@ -16,18 +18,18 @@
<meta name="msapplication-config" content="<%= static_path(@conn, "/browserconfig.xml") %>"> <meta name="msapplication-config" content="<%= static_path(@conn, "/browserconfig.xml") %>">
<meta name="theme-color" content="#ffffff"> <meta name="theme-color" content="#ffffff">
<link rel="stylesheet" href="https://nico-amsterdam.github.io/awesomplete-util/css/awesomplete.css">
<script src="https://nico-amsterdam.github.io/awesomplete-util/js/awesomplete.min.js"></script>
<script src="https://nico-amsterdam.github.io/awesomplete-util/js/awesomplete-util.min.js"></script>
<%= render_existing(@view_module, "_metatags.html", assigns) || render("_default_title.html") %> <%= render_existing(@view_module, "_metatags.html", assigns) || render("_default_title.html") %>
</head> </head>
<body> <body>
<script> <script>
if (localStorage.getItem("current-color-mode") === "dark") { function applyDarkMode() {
document.body.style.backgroundColor = "#1c1d31"; if (localStorage.getItem("current-color-mode") === "dark") {
document.body.className += " " + "dark-theme-applied";
document.body.style.backgroundColor = "#1c1d31";
}
} }
window.onload = applyDarkMode()
</script> </script>
<div class="layout-container"> <div class="layout-container">
<%= if not Explorer.Chain.finished_indexing?() do %> <%= if not Explorer.Chain.finished_indexing?() do %>

@ -11,7 +11,7 @@
</span> </span>
<%= if show_total_supply_percentage?(@token.total_supply) do %> <%= if show_total_supply_percentage?(@token.total_supply) do %>
(<%= total_supply_percentage(@token_balance.value, @token.total_supply) %>) <%= total_supply_percentage(@token_balance.value, @token.total_supply) %>
<% end %> <% end %>
</span> </span>
</div> </div>

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save