Add 'Token Inventory' page and tab control

* The pagination at the 'Tokens.InventoryController' doesn't follow the 'BlockScoutWeb.Chain', because we have two different 'TokenTransfers' listings that are ordered in different ways.
The 'Transfers' page is ordered from the most recent to the older and the 'Inventory' page is ordered according to the 'token_id'.
pull/774/head
Amanda Sposito 6 years ago committed by Amanda Sposito
parent 92a241f4ac
commit d3c47e2f86
  1. 27
      apps/block_scout_web/assets/css/components/_tile.scss
  2. 4
      apps/block_scout_web/lib/block_scout_web/chain.ex
  3. 52
      apps/block_scout_web/lib/block_scout_web/controllers/tokens/inventory_controller.ex
  4. 7
      apps/block_scout_web/lib/block_scout_web/router.ex
  5. 28
      apps/block_scout_web/lib/block_scout_web/templates/tokens/inventory/_token.html.eex
  6. 42
      apps/block_scout_web/lib/block_scout_web/templates/tokens/inventory/index.html.eex
  7. 18
      apps/block_scout_web/lib/block_scout_web/templates/tokens/overview/_tabs.html.eex
  8. 5
      apps/block_scout_web/lib/block_scout_web/views/tokens/inventory_view.ex
  9. 6
      apps/block_scout_web/lib/block_scout_web/views/tokens/overview_view.ex
  10. 14
      apps/block_scout_web/test/block_scout_web/views/tokens/overview_view_test.exs

@ -63,6 +63,28 @@
}
}
&-unique-token {
border-left: 4px solid $orange;
padding: 35px 0;
.tile-label {
color: $orange;
}
}
&-unique-token-image{
border-left: 4px solid $orange;
padding: 0;
.tile-label {
color: $orange;
}
& .tile-content {
padding: 45px 0;
}
}
&-internal-transaction {
border-left: 4px solid $teal;
@ -150,3 +172,8 @@
margin: 0;
}
}
.tile-image {
max-width: 140px;
max-height: 140px;
}

@ -29,6 +29,10 @@ defmodule BlockScoutWeb.Chain do
@page_size 50
@default_paging_options %PagingOptions{page_size: @page_size + 1}
def default_paging_options do
@default_paging_options
end
def current_filter(%{paging_options: paging_options} = params) do
params
|> Map.get("filter")

@ -0,0 +1,52 @@
defmodule BlockScoutWeb.Tokens.InventoryController do
use BlockScoutWeb, :controller
alias Explorer.Chain
alias Explorer.Chain.TokenTransfer
import BlockScoutWeb.Chain, only: [split_list_by_page: 1, default_paging_options: 0]
def index(conn, %{"token_id" => address_hash_string} = params) do
with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string),
{:ok, token} <- Chain.token_from_address_hash(address_hash) do
unique_tokens =
Chain.address_to_unique_tokens(
token.contract_address_hash,
unique_tokens_paging_options(params)
)
{unique_tokens_paginated, next_page} = split_list_by_page(unique_tokens)
render(
conn,
"index.html",
token: token,
unique_tokens: unique_tokens_paginated,
total_token_transfers: Chain.count_token_transfers_from_token_hash(address_hash),
total_token_holders: Chain.count_token_holders_from_token_hash(address_hash),
next_page_params: unique_tokens_next_page(next_page, unique_tokens_paginated, params)
)
else
:error ->
not_found(conn)
{:error, :not_found} ->
not_found(conn)
end
end
defp unique_tokens_paging_options(%{"unique_token" => token_id}),
do: [paging_options: %{default_paging_options() | key: {token_id}}]
defp unique_tokens_paging_options(_params), do: [paging_options: default_paging_options()]
defp unique_tokens_next_page([], _list, _params), do: nil
defp unique_tokens_next_page(_, list, params) do
Map.merge(params, paging_params(List.last(list)))
end
defp paging_params(%TokenTransfer{token_id: token_id}) do
%{"unique_token" => Decimal.to_integer(token_id)}
end
end

@ -135,6 +135,13 @@ defmodule BlockScoutWeb.Router do
only: [:index],
as: :holder
)
resources(
"/inventory",
Tokens.InventoryController,
only: [:index],
as: :inventory
)
end
resources(

@ -0,0 +1,28 @@
<!-- use "tile-type-unique-token-image" to token with images -->
<div class="tile tile-type-unique-token fade-in">
<div class="row">
<div class="pl-5 col-md-2 d-flex flex-column align-items-left justify-content-start justify-content-lg-center">
<!-- substitute the span with <img class="tile-image" /> to token with images -->
<span class="tile-label"><%= gettext "Unique Token" %></span>
</div>
<div class="tile-content col-md-7 col-lg-8 d-flex flex-column">
<span class="d-flex flex-md-row flex-column mt-3 mt-md-0">
<span class="mr-1"><%= gettext "Token ID" %>:</span>
<span class="tile-title">
<%= @token_transfer.token_id %>
</span>
</span>
<span class="d-flex flex-md-row flex-column mt-3 mt-md-0">
<span class="mr-1"><%= gettext "Owner Address" %>:</span>
<span class="tile-title">
<%= render BlockScoutWeb.AddressView,
"_link.html",
address: @token_transfer.to_address,
contract: false %>
</span>
</span>
</div>
</div>
</div>

@ -0,0 +1,42 @@
<section class="container">
<%= render(
OverviewView,
"_details.html",
token: @token,
total_token_transfers: @total_token_transfers,
total_token_holders: @total_token_holders,
conn: @conn
) %>
<section>
<div class="card">
<div class="card-header">
<%= render OverviewView, "_tabs.html", assigns %>
</div>
<div class="card-body">
<h2 class="card-title"><%= gettext "Inventory" %></h2>
<%= if Enum.any?(@unique_tokens) do %>
<%= for token_transfer <- @unique_tokens do %>
<%= render "_token.html", token_transfer: token_transfer %>
<% end %>
<% else %>
<div class="tile tile-muted text-center">
<span data-selector="empty-transactions-list">
<%= gettext "There are no tokens." %>
</span>
</div>
<% end %>
<%= if @next_page_params do %>
<%= link(
gettext("Next Page"),
class: "button button-secondary button-small float-right mt-4",
to: token_inventory_path(@conn, :index, @token.contract_address_hash, @next_page_params)
) %>
<% end %>
</div>
</div>
</section>
</section>

@ -25,6 +25,16 @@
to: token_holder_path(@conn, :index, @token.contract_address_hash)
) %>
</li>
<%= if display_inventory?(@token) do %>
<li class="nav-item">
<%= link(
gettext("Inventory"),
class: "nav-link #{tab_status("inventory", @conn.request_path)}",
to: token_inventory_path(@conn, :index, @token.contract_address_hash)
) %>
</li>
<% end %>
</ul>
<!-- MOBILE DROPDOWN NAV -->
@ -56,6 +66,14 @@
class: "dropdown-item #{tab_status("token_holders", @conn.request_path)}",
to: token_holder_path(@conn, :index, @token.contract_address_hash)
) %>
<%= if display_inventory?(@token) do %>
<%= link(
gettext("Inventory"),
class: "dropdown-item #{tab_status("inventory", @conn.request_path)}",
to: token_inventory_path(@conn, :index, @token.contract_address_hash)
) %>
<% end %>
</div>
</li>
</ul>

@ -0,0 +1,5 @@
defmodule BlockScoutWeb.Tokens.InventoryView do
use BlockScoutWeb, :view
alias BlockScoutWeb.Tokens.OverviewView
end

@ -4,7 +4,7 @@ defmodule BlockScoutWeb.Tokens.OverviewView do
alias Explorer.Chain.Token
alias BlockScoutWeb.Tokens.TransferView
@tabs ["token_transfers", "token_holders", "read_contract"]
@tabs ["token_transfers", "token_holders", "read_contract", "inventory"]
def decimals?(%Token{decimals: nil}), do: false
def decimals?(%Token{decimals: _}), do: true
@ -34,4 +34,8 @@ defmodule BlockScoutWeb.Tokens.OverviewView do
defp tab_name(["token_transfers"]), do: gettext("Token Transfers")
defp tab_name(["token_holders"]), do: gettext("Token Holders")
defp tab_name(["read_contract"]), do: gettext("Read Contract")
defp tab_name(["inventory"]), do: gettext("Inventory")
def display_inventory?(%Token{type: "ERC-721"}), do: true
def display_inventory?(_), do: false
end

@ -64,4 +64,18 @@ defmodule BlockScoutWeb.Tokens.OverviewViewTest do
assert OverviewView.current_tab_name(read_contract_path) == "Read Contract"
end
end
describe "display_inventory?/1" do
test "returns true when token is unique" do
token = insert(:token, type: "ERC-721")
assert OverviewView.display_inventory?(token) == true
end
test "returns false when token is not unique" do
token = insert(:token, type: "ERC-20")
assert OverviewView.display_inventory?(token) == false
end
end
end

Loading…
Cancel
Save