show evolutions as well
All checks were successful
Build and Deploy / Build & Push Image (push) Successful in 33s

This commit is contained in:
2026-03-18 20:28:06 -06:00
parent a8e3c9b1cc
commit d48f23b022
7 changed files with 4431 additions and 106 deletions

View File

@@ -6,6 +6,7 @@ defmodule CobblemonUiWeb.BattleComponents do
attr :battle, :map, required: true
attr :player_id, :string, required: true
attr :tier_list, :map, default: %{}
attr :evolutions, :map, default: %{}
def battle_panel(assigns) do
~H"""
@@ -51,12 +52,15 @@ defmodule CobblemonUiWeb.BattleComponents do
</div>
<%= for poke <- actor.active_pokemon do %>
<% tier = Map.get(@tier_list, String.downcase(poke.species || ""), nil) %>
<% species_key = String.downcase(poke.species || "") %>
<% is_opponent = actor.player_id != @player_id %>
<% evos = if is_opponent, do: Map.get(@evolutions, species_key, []), else: [] %>
<div class="rounded-md bg-base-100/50 border border-base-300/30 px-3 py-2">
<div class="flex items-center gap-3">
<%!-- Sprite --%>
<div class="w-14 h-14 rounded-lg bg-base-300/20 flex items-center justify-center shrink-0 overflow-hidden">
<img
src={"https://img.rankedboost.com/wp-content/plugins/k-Pokemon/assets/sprites-official/#{String.downcase(poke.species || "")}.png"}
src={"https://img.rankedboost.com/wp-content/plugins/k-Pokemon/assets/sprites-official/#{species_key}.png"}
alt={poke.species}
class="w-12 h-12 object-contain drop-shadow-sm"
/>
@@ -64,7 +68,9 @@ defmodule CobblemonUiWeb.BattleComponents do
<%!-- Info --%>
<div class="flex-1 min-w-0">
<div class="flex items-center gap-1.5 mb-1">
<span class="text-sm font-bold text-base-content capitalize">{poke.species}</span>
<span class="text-sm font-bold text-base-content capitalize">
{poke.species}
</span>
<span class="text-[10px] text-base-content/40 bg-base-300/40 px-1.5 py-0.5 rounded font-mono">
Lv.{poke.level}
</span>
@@ -92,6 +98,31 @@ defmodule CobblemonUiWeb.BattleComponents do
</div>
</div>
</div>
<%!-- Evolution info for opponent Pokémon --%>
<div :if={evos != []} class="mt-2 pt-2 border-t border-base-300/20">
<div class="flex items-center gap-1.5 mb-1.5">
<.icon name="hero-arrow-trending-up" class="size-3 text-info/60" />
<span class="text-[10px] uppercase font-semibold tracking-wide text-info/60">
Evolves into
</span>
</div>
<div class="flex flex-wrap gap-1.5">
<%= for evo <- evos do %>
<% evo_tier = Map.get(@tier_list, evo, nil) %>
<div class="flex items-center gap-1.5 rounded-md bg-info/5 border border-info/15 px-2 py-1">
<img
src={"https://img.rankedboost.com/wp-content/plugins/k-Pokemon/assets/sprites-official/#{evo}.png"}
alt={evo}
class="w-6 h-6 object-contain"
/>
<span class="text-xs font-semibold text-base-content/70 capitalize">
{evo}
</span>
<.tier_badge :if={evo_tier} tier={evo_tier} species={evo} compact={true} />
</div>
<% end %>
</div>
</div>
</div>
<% end %>
</div>

View File

@@ -64,7 +64,8 @@ defmodule CobblemonUiWeb.DashboardLive do
end
def handle_params(_params, _uri, socket) do
{:noreply, assign(socket, selected_player: nil, player_data: nil, battle: nil, selected_pokemon: nil)}
{:noreply,
assign(socket, selected_player: nil, player_data: nil, battle: nil, selected_pokemon: nil)}
end
@impl true
@@ -93,13 +94,11 @@ defmodule CobblemonUiWeb.DashboardLive do
{:noreply, assign(socket, view_mode: String.to_existing_atom(mode), selected_pokemon: nil)}
end
@impl true
def handle_info(:tick, socket) do
{:noreply, do_refresh(socket)}
end
defp do_refresh(socket) do
players =
case CobblemonUi.CobblemonFS.list_players() do
@@ -127,7 +126,6 @@ defmodule CobblemonUiWeb.DashboardLive do
<Layouts.app flash={@flash}>
<div class="h-screen flex flex-col">
<div class="max-w-4xl w-full mx-auto px-4 sm:px-6 lg:px-8 flex flex-col flex-1 min-h-0">
<%!-- Player picker --%>
<div :if={is_nil(@selected_player)} class="flex-1 overflow-y-auto py-8">
<div class="flex items-center justify-between mb-8">
@@ -164,7 +162,10 @@ defmodule CobblemonUiWeb.DashboardLive do
<span class="text-base font-semibold text-base-content/80 group-hover:text-base-content truncate">
{player.name || "Unknown"}
</span>
<.icon name="hero-chevron-right" class="size-4 text-base-content/20 ml-auto shrink-0 group-hover:text-primary/40 transition-colors" />
<.icon
name="hero-chevron-right"
class="size-4 text-base-content/20 ml-auto shrink-0 group-hover:text-primary/40 transition-colors"
/>
</.link>
</div>
</div>
@@ -192,114 +193,117 @@ defmodule CobblemonUiWeb.DashboardLive do
<%!-- Scrollable content --%>
<div class="flex-1 overflow-y-auto py-6">
<div
:if={@error}
class="rounded-xl border border-error/30 bg-error/10 px-5 py-4 mb-6"
>
<div class="flex items-center gap-2 text-error">
<.icon name="hero-exclamation-triangle" class="size-5" />
<span class="text-sm font-medium">{@error}</span>
</div>
</div>
<div :if={@player_data}>
<%!-- Stats bar --%>
<div class="flex items-center gap-4 text-sm text-base-content/50 mb-6">
<div class="flex items-center gap-1.5">
<div class="w-2 h-2 rounded-full bg-success/60" />
<span>{party_count(@player_data)} in party</span>
</div>
<div class="flex items-center gap-1.5">
<div class="w-2 h-2 rounded-full bg-info/60" />
<span>{pc_count(@player_data)} in PC</span>
<div
:if={@error}
class="rounded-xl border border-error/30 bg-error/10 px-5 py-4 mb-6"
>
<div class="flex items-center gap-2 text-error">
<.icon name="hero-exclamation-triangle" class="size-5" />
<span class="text-sm font-medium">{@error}</span>
</div>
</div>
<%!-- Active battle --%>
<.battle_panel :if={@battle} battle={@battle} player_id={@selected_player} tier_list={@tier_list} />
<%!-- View mode tabs --%>
<div class="flex items-center gap-1 mb-5 p-1 rounded-lg bg-base-200/40 w-fit">
<button
id="tab-party"
phx-click="switch_view"
phx-value-mode="party"
class={[
"px-4 py-1.5 rounded-md text-sm font-medium transition-all duration-150",
if(@view_mode == :party,
do: "bg-base-100 text-base-content shadow-sm",
else: "text-base-content/50 hover:text-base-content/70"
)
]}
>
Party
</button>
<button
id="tab-pc"
phx-click="switch_view"
phx-value-mode="pc"
class={[
"px-4 py-1.5 rounded-md text-sm font-medium transition-all duration-150",
if(@view_mode == :pc,
do: "bg-base-100 text-base-content shadow-sm",
else: "text-base-content/50 hover:text-base-content/70"
)
]}
>
PC Storage
</button>
</div>
<%!-- Party view --%>
<div :if={@view_mode == :party}>
<div class="grid grid-cols-2 sm:grid-cols-3 gap-3">
<%= for {pokemon, idx} <- Enum.with_index(@player_data.party), not is_nil(pokemon) do %>
<.pokemon_card
pokemon={pokemon}
index={idx}
tier={Map.get(@tier_list, String.downcase(pokemon.species || ""), nil)}
/>
<% end %>
<div :if={@player_data}>
<%!-- Stats bar --%>
<div class="flex items-center gap-4 text-sm text-base-content/50 mb-6">
<div class="flex items-center gap-1.5">
<div class="w-2 h-2 rounded-full bg-success/60" />
<span>{party_count(@player_data)} in party</span>
</div>
<div class="flex items-center gap-1.5">
<div class="w-2 h-2 rounded-full bg-info/60" />
<span>{pc_count(@player_data)} in PC</span>
</div>
</div>
</div>
<%!-- PC view --%>
<div :if={@view_mode == :pc}>
<div :for={box <- @player_data.pc} class="mb-6">
<h3 class="text-sm font-semibold text-base-content/50 uppercase tracking-wider mb-3">
Box {box.box + 1}
</h3>
<div class="grid grid-cols-3 sm:grid-cols-5 lg:grid-cols-6 gap-2">
<%= for {pokemon, idx} <- Enum.with_index(box.pokemon), not is_nil(pokemon) do %>
<%!-- Active battle --%>
<.battle_panel
:if={@battle}
battle={@battle}
player_id={@selected_player}
tier_list={@tier_list}
evolutions={opponent_evolutions(@battle, @selected_player)}
/>
<%!-- View mode tabs --%>
<div class="flex items-center gap-1 mb-5 p-1 rounded-lg bg-base-200/40 w-fit">
<button
id="tab-party"
phx-click="switch_view"
phx-value-mode="party"
class={[
"px-4 py-1.5 rounded-md text-sm font-medium transition-all duration-150",
if(@view_mode == :party,
do: "bg-base-100 text-base-content shadow-sm",
else: "text-base-content/50 hover:text-base-content/70"
)
]}
>
Party
</button>
<button
id="tab-pc"
phx-click="switch_view"
phx-value-mode="pc"
class={[
"px-4 py-1.5 rounded-md text-sm font-medium transition-all duration-150",
if(@view_mode == :pc,
do: "bg-base-100 text-base-content shadow-sm",
else: "text-base-content/50 hover:text-base-content/70"
)
]}
>
PC Storage
</button>
</div>
<%!-- Party view --%>
<div :if={@view_mode == :party}>
<div class="grid grid-cols-2 sm:grid-cols-3 gap-3">
<%= for {pokemon, idx} <- Enum.with_index(@player_data.party), not is_nil(pokemon) do %>
<.pokemon_card
pokemon={pokemon}
index={pc_global_index(@player_data.pc, box.box, idx)}
index={idx}
tier={Map.get(@tier_list, String.downcase(pokemon.species || ""), nil)}
compact
/>
<% end %>
</div>
</div>
<div
:if={@player_data.pc == []}
class="text-center py-12 text-base-content/30 text-sm"
>
PC storage is empty
<%!-- PC view --%>
<div :if={@view_mode == :pc}>
<div :for={box <- @player_data.pc} class="mb-6">
<h3 class="text-sm font-semibold text-base-content/50 uppercase tracking-wider mb-3">
Box {box.box + 1}
</h3>
<div class="grid grid-cols-3 sm:grid-cols-5 lg:grid-cols-6 gap-2">
<%= for {pokemon, idx} <- Enum.with_index(box.pokemon), not is_nil(pokemon) do %>
<.pokemon_card
pokemon={pokemon}
index={pc_global_index(@player_data.pc, box.box, idx)}
tier={Map.get(@tier_list, String.downcase(pokemon.species || ""), nil)}
compact
/>
<% end %>
</div>
</div>
<div
:if={@player_data.pc == []}
class="text-center py-12 text-base-content/30 text-sm"
>
PC storage is empty
</div>
</div>
<%!-- Pokemon detail panel --%>
<.pokemon_detail
:if={@selected_pokemon}
pokemon={@selected_pokemon}
tier={Map.get(@tier_list, String.downcase(@selected_pokemon.species || ""), nil)}
/>
</div>
<%!-- Pokemon detail panel --%>
<.pokemon_detail
:if={@selected_pokemon}
pokemon={@selected_pokemon}
tier={Map.get(@tier_list, String.downcase(@selected_pokemon.species || ""), nil)}
/>
</div>
</div>
</div>
</div>
</div>
</Layouts.app>
@@ -329,6 +333,24 @@ defmodule CobblemonUiWeb.DashboardLive do
end
end
defp opponent_evolutions(nil, _player_id), do: %{}
defp opponent_evolutions(battle, player_id) do
battle.actors
|> Enum.reject(fn actor -> actor.player_id == player_id end)
|> Enum.flat_map(fn actor -> actor.active_pokemon end)
|> Enum.reduce(%{}, fn poke, acc ->
species = String.downcase(poke.species || "")
if species != "" do
evolutions = CobblemonUi.EvolutionApi.get_evolutions(species)
Map.put(acc, species, evolutions)
else
acc
end
end)
end
defp find_player_battle(uuid) do
case CobblemonUi.BattlesApi.list_battles() do
{:ok, battles} ->