Merge pull request #3224 from poanetwork/vb-top-tokens-page

Top tokens page
pull/3226/head
Victor Baranov 4 years ago committed by GitHub
commit 68ea9c1ea1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      CHANGELOG.md
  2. 2
      apps/block_scout_web/assets/css/components/_navbar.scss
  3. 15
      apps/block_scout_web/lib/block_scout_web/chain.ex
  4. 60
      apps/block_scout_web/lib/block_scout_web/controllers/tokens/tokens_controller.ex
  5. 4
      apps/block_scout_web/lib/block_scout_web/templates/icons/_tokens_icon.html.eex
  6. 8
      apps/block_scout_web/lib/block_scout_web/templates/layout/_topnav.html.eex
  7. 1
      apps/block_scout_web/lib/block_scout_web/templates/layout/app.html.eex
  8. 36
      apps/block_scout_web/lib/block_scout_web/templates/tokens/_tile.html.eex
  9. 45
      apps/block_scout_web/lib/block_scout_web/templates/tokens/index.html.eex
  10. 8
      apps/block_scout_web/lib/block_scout_web/views/tokens_view.ex
  11. 2
      apps/block_scout_web/lib/block_scout_web/web_router.ex
  12. 35
      apps/explorer/lib/explorer/chain.ex

@ -1,6 +1,7 @@
## Current ## Current
### Features ### Features
- [#3224](https://github.com/poanetwork/blockscout/pull/3224) - Top tokens page
### Fixes ### Fixes
- [#3220](https://github.com/poanetwork/blockscout/pull/3220) - Allow interaction with navbar menu at block-not-found page - [#3220](https://github.com/poanetwork/blockscout/pull/3220) - Allow interaction with navbar menu at block-not-found page

@ -155,7 +155,7 @@ $navbar-logo-width: auto !default;
width: 340px; width: 340px;
} }
@media (min-width: 1366px) { @media (min-width: 1366px) {
width: 500px; width: 380px;
} }
@media (min-width: 1440px) { @media (min-width: 1440px) {
width: 580px; width: 580px;

@ -24,6 +24,7 @@ defmodule BlockScoutWeb.Chain do
Block, Block,
InternalTransaction, InternalTransaction,
Log, Log,
Token,
TokenTransfer, TokenTransfer,
Transaction, Transaction,
Wei Wei
@ -107,6 +108,16 @@ defmodule BlockScoutWeb.Chain do
end end
end end
def paging_options(%{"contract_address_hash" => contract_address_hash, "holder_count" => holder_count}) do
with {holder_count, ""} <- Integer.parse(holder_count),
{:ok, contract_address_hash} <- string_to_address_hash(contract_address_hash) do
[paging_options: %{@default_paging_options | key: {holder_count, contract_address_hash}}]
else
_ ->
[paging_options: @default_paging_options]
end
end
def paging_options(%{ def paging_options(%{
"block_number" => block_number_string, "block_number" => block_number_string,
"transaction_index" => transaction_index_string, "transaction_index" => transaction_index_string,
@ -205,6 +216,10 @@ defmodule BlockScoutWeb.Chain do
%{"hash" => hash, "fetched_coin_balance" => Decimal.to_string(fetched_coin_balance.value)} %{"hash" => hash, "fetched_coin_balance" => Decimal.to_string(fetched_coin_balance.value)}
end end
defp paging_params(%Token{contract_address_hash: contract_address_hash, holder_count: holder_count}) do
%{"contract_address_hash" => contract_address_hash, "holder_count" => holder_count}
end
defp paging_params({%Reward{block: %{number: number}}, _}) do defp paging_params({%Reward{block: %{number: number}}, _}) do
%{"block_number" => number, "index" => 0} %{"block_number" => number, "index" => 0}
end end

@ -0,0 +1,60 @@
defmodule BlockScoutWeb.TokensController do
use BlockScoutWeb, :controller
import BlockScoutWeb.Chain, only: [paging_options: 1, next_page_params: 3, split_list_by_page: 1]
alias BlockScoutWeb.TokensView
alias Explorer.Chain
alias Phoenix.View
def index(conn, %{"type" => "JSON"} = params) do
tokens =
params
|> paging_options()
|> Chain.list_top_tokens()
{tokens_page, next_page} = split_list_by_page(tokens)
next_page_path =
case next_page_params(next_page, tokens_page, params) do
nil ->
nil
next_page_params ->
tokens_path(
conn,
:index,
Map.delete(next_page_params, "type")
)
end
items =
tokens_page
|> Enum.with_index(1)
|> Enum.map(fn {token, index} ->
View.render_to_string(
TokensView,
"_tile.html",
token: token,
index: index
)
end)
json(
conn,
%{
items: items,
next_page_path: next_page_path
}
)
end
def index(conn, _params) do
total_supply = Chain.total_supply()
render(conn, "index.html",
current_path: current_path(conn),
total_supply: total_supply
)
end
end

@ -0,0 +1,4 @@
<?xml version="1.0"?>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" enable-background="new 0 0 24 24" width="16" height="16">
<path fill="#A3A9B5" fill-rule="evenodd" d="M 16.5 0 C 12.357864 0 9 1.790861 9 4 C 9 6.209139 12.357864 8 16.5 8 C 20.642136 8 24 6.209139 24 4 C 24 1.790861 20.642136 0 16.5 0 z M 9.0625 5.5 C 9.0235 5.664 9 5.83 9 6 L 9 7 C 9 7.849 9.49775 8.63325 10.34375 9.28125 C 11.80575 9.57825 13.06825 10.10225 14.03125 10.78125 C 14.80225 10.92425 15.638 11 16.5 11 C 20.642 11 24 9.209 24 7 L 24 6 C 24 5.83 23.9765 5.664 23.9375 5.5 C 23.4755 7.473 20.324 9 16.5 9 C 12.676 9 9.5245 7.473 9.0625 5.5 z M 23.9375 8.5 C 23.4755 10.473 20.324 12 16.5 12 C 16.079 12 15.68325 11.9735 15.28125 11.9375 C 15.74625 12.5585 15.997 13.266 16 14 C 16.166 14.005 16.331 14 16.5 14 C 20.642 14 24 12.209 24 10 L 24 9 C 24 8.83 23.9765 8.664 23.9375 8.5 z M 7.5 10 C 3.358 10 0 11.791 0 14 C 0 16.209 3.358 18 7.5 18 C 11.642 18 15 16.209 15 14 C 15 11.791 11.642 10 7.5 10 z M 23.9375 11.5 C 23.4755 13.473 20.324 15 16.5 15 C 16.331 15 16.166 15.005 16 15 L 16 17 C 16.166 17.005 16.331 17 16.5 17 C 20.642 17 24 15.209 24 13 L 24 12 C 24 11.83 23.9765 11.664 23.9375 11.5 z M 23.9375 14.5 C 23.4755 16.473 20.324 18 16.5 18 C 16.331 18 16.166 18.005 16 18 L 16 20 C 16.166 20.005 16.331 20 16.5 20 C 20.642 20 24 18.209 24 16 L 24 15 C 24 14.83 23.9765 14.664 23.9375 14.5 z M 0.0625 15.5 C 0.0235 15.664 0 15.83 0 16 L 0 17 C 0 19.209 3.358 21 7.5 21 C 11.642 21 15 19.209 15 17 L 15 16 C 15 15.83 14.9765 15.664 14.9375 15.5 C 14.4755 17.473 11.324 19 7.5 19 C 3.676 19 0.5245 17.473 0.0625 15.5 z M 0.0625 18.5 C 0.0235 18.664 0 18.83 0 19 L 0 20 C 0 22.209 3.358 24 7.5 24 C 11.642 24 15 22.209 15 20 L 15 19 C 15 18.83 14.9765 18.664 14.9375 18.5 C 14.4755 20.473 11.324 22 7.5 22 C 3.676 22 0.5245 20.473 0.0625 18.5 z"/>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

@ -75,6 +75,14 @@
<%= gettext("Accounts") %> <%= gettext("Accounts") %>
<% end %> <% end %>
</li> </li>
<li class="nav-item">
<%= link to: tokens_path(@conn, :index), class: "nav-link topnav-nav-link #{tab_status("tokens", @conn.request_path)}" do %>
<span class="nav-link-icon">
<%= render BlockScoutWeb.IconsView, "_tokens_icon.html" %>
</span>
<%= gettext("Tokens") %>
<% end %>
</li>
<% end %> <% end %>
<%= if Application.get_env(:block_scout_web, BlockScoutWeb.ApiRouter)[:reading_enabled] || Application.get_env(:block_scout_web, :api_url) do %> <%= if Application.get_env(:block_scout_web, BlockScoutWeb.ApiRouter)[:reading_enabled] || Application.get_env(:block_scout_web, :api_url) do %>
<li class="nav-item dropdown"> <li class="nav-item dropdown">

@ -70,6 +70,7 @@
@view_module != Elixir.BlockScoutWeb.BlockView && @view_module != Elixir.BlockScoutWeb.BlockView &&
@view_module != Elixir.BlockScoutWeb.BlockTransactionView && @view_module != Elixir.BlockScoutWeb.BlockTransactionView &&
@view_module != Elixir.BlockScoutWeb.AddressView && @view_module != Elixir.BlockScoutWeb.AddressView &&
@view_module != Elixir.BlockScoutWeb.TokensView &&
@view_module != Elixir.BlockScoutWeb.TransactionView && @view_module != Elixir.BlockScoutWeb.TransactionView &&
@view_module != Elixir.BlockScoutWeb.PendingTransactionView && @view_module != Elixir.BlockScoutWeb.PendingTransactionView &&
@view_module != Elixir.BlockScoutWeb.TransactionInternalTransactionView && @view_module != Elixir.BlockScoutWeb.TransactionInternalTransactionView &&

@ -0,0 +1,36 @@
<tr>
<td class="stakes-td">
<!-- incremented number by order in the list -->
<span class="color-lighten">
<%= @index %>
</span>
</td>
<td class="stakes-td">
<% token = "#{@token.name} (#{@token.symbol})" %>
<%= link(token,
to: token_path(BlockScoutWeb.Endpoint, :show, @token.contract_address_hash),
"data-test": "token_link",
class: "text-truncate") %>
</td>
<td class="stakes-td">
<%= render BlockScoutWeb.AddressView,
"_link.html",
address: @token.contract_address,
contract: true,
use_custom_tooltip: false
%>
</td>
<td class="stakes-td">
<%= if decimals?(@token) do %>
<span data-test="token_supply"><%= format_according_to_decimals(@token.total_supply, @token.decimals) %></span>
<% else %>
<span data-test="token_supply"><%= format_integer_to_currency(@token.total_supply) %></span>
<% end %> <%= @token.symbol %>
</td>
<td class="stakes-td">
<span class="mr-4">
<span data-test="transaction_count">
<%= @token.holder_count %>
</span>
</td>
</tr>

@ -0,0 +1,45 @@
<section class="container">
<div class="card">
<div class="card-body" data-async-load data-async-listing="<%= @current_path %>">
<h1 class="card-title"><%= gettext "Tokens" %></h1>
<%= render BlockScoutWeb.CommonComponentsView, "_pagination_container.html", position: "top", cur_page_number: "1", show_pagination_limit: true, data_next_page_button: true, data_prev_page_button: true %>
<div class="addresses-table-container">
<div class="stakes-table-container">
<table>
<thead>
<tr>
<th class="stakes-table-th">
<div class="stakes-table-th-content">&nbsp;</div>
</th>
<th class="stakes-table-th">
<div class="stakes-table-th-content">Token</div>
</th>
<th class="stakes-table-th">
<div class="stakes-table-th-content">Address</div>
</th>
<th class="stakes-table-th">
<div class="stakes-table-th-content">
Total Supply
</div>
</th>
<th class="stakes-table-th">
<div class="stakes-table-th-content">
Holders Count
</div>
</th>
</tr>
</thead>
<tbody data-items data-selector="top-tokens-list">
<%= render BlockScoutWeb.CommonComponentsView, "_table-loader.html", total_supply: @total_supply %>
</tbody>
</table>
</div>
</div>
<%= render BlockScoutWeb.CommonComponentsView, "_pagination_container.html", position: "bottom", cur_page_number: "1", show_pagination_limit: true, data_next_page_button: true, data_prev_page_button: true %>
</div>
</div>
<script defer data-cfasync="false" src="<%= static_path(@conn, "/js/async-listing-load.js") %>"></script>
</section>

@ -0,0 +1,8 @@
defmodule BlockScoutWeb.TokensView do
use BlockScoutWeb, :view
alias Explorer.Chain.Token
def decimals?(%Token{decimals: nil}), do: false
def decimals?(%Token{decimals: _}), do: true
end

@ -73,6 +73,8 @@ defmodule BlockScoutWeb.WebRouter do
resources("/accounts", AddressController, only: [:index]) resources("/accounts", AddressController, only: [:index])
resources("/tokens", TokensController, only: [:index])
resources "/address", AddressController, only: [:show] do resources "/address", AddressController, only: [:show] do
resources("/transactions", AddressTransactionController, only: [:index], as: :transaction) resources("/transactions", AddressTransactionController, only: [:index], as: :transaction)

@ -1713,6 +1713,31 @@ defmodule Explorer.Chain do
|> Repo.all() |> Repo.all()
end end
@doc """
Lists the top `t:Explorer.Chain.Token.t/0`'s'.
"""
@spec list_top_tokens :: [{Token.t(), non_neg_integer()}]
def list_top_tokens(options \\ []) do
paging_options = Keyword.get(options, :paging_options, @default_paging_options)
fetch_top_tokens(paging_options)
end
defp fetch_top_tokens(paging_options) do
base_query =
from(t in Token,
where: t.total_supply > ^0,
order_by: [desc: t.holder_count],
preload: [:contract_address]
)
base_query
|> page_tokens(paging_options)
|> limit(^paging_options.page_size)
|> Repo.all()
end
@doc """ @doc """
Calls `reducer` on a stream of `t:Explorer.Chain.Block.t/0` without `t:Explorer.Chain.Block.Reward.t/0`. Calls `reducer` on a stream of `t:Explorer.Chain.Block.t/0` without `t:Explorer.Chain.Block.Reward.t/0`.
""" """
@ -3136,6 +3161,16 @@ defmodule Explorer.Chain do
) )
end end
defp page_tokens(query, %PagingOptions{key: nil}), do: query
defp page_tokens(query, %PagingOptions{key: {holder_count, contract_address_hash}}) do
from(token in query,
where:
(token.holder_count == ^holder_count and token.contract_address_hash > ^contract_address_hash) or
token.holder_count < ^holder_count
)
end
defp page_blocks(query, %PagingOptions{key: nil}), do: query defp page_blocks(query, %PagingOptions{key: nil}), do: query
defp page_blocks(query, %PagingOptions{key: {block_number}}) do defp page_blocks(query, %PagingOptions{key: {block_number}}) do

Loading…
Cancel
Save