From ad3120bf6b4e09692269b8c9a1baf6bde55a2daa Mon Sep 17 00:00:00 2001 From: Alex Mickelson Date: Mon, 16 Mar 2026 20:21:09 -0600 Subject: [PATCH] view --- .../live/battle_components.ex | 84 ++++ lib/cobblemon_ui_web/live/dashboard_live.ex | 387 +----------------- .../live/pokemon_components.ex | 292 +++++++++++++ 3 files changed, 380 insertions(+), 383 deletions(-) create mode 100644 lib/cobblemon_ui_web/live/battle_components.ex create mode 100644 lib/cobblemon_ui_web/live/pokemon_components.ex diff --git a/lib/cobblemon_ui_web/live/battle_components.ex b/lib/cobblemon_ui_web/live/battle_components.ex new file mode 100644 index 0000000..7a1b5c1 --- /dev/null +++ b/lib/cobblemon_ui_web/live/battle_components.ex @@ -0,0 +1,84 @@ +defmodule CobblemonUiWeb.BattleComponents do + use CobblemonUiWeb, :html + + attr :battle, :map, required: true + attr :player_id, :string, required: true + + def battle_panel(assigns) do + ~H""" +
+
+ + Active Battle +
+
+ + <%= for actor <- @battle.actors do %> +
+
+
+ <%= if actor.type == "player" do %> + <.icon name="hero-user" class="size-3.5 text-primary" /> + <% else %> + <.icon name="hero-cpu-chip" class="size-3.5 text-warning" /> + <% end %> +
+
+

{actor.name}

+

+ {actor.type} +

+
+
+ <%= for poke <- actor.active_pokemon do %> +
+
+
+ {poke.species} + + Lv.{poke.level} + +
+ + {poke.hp}/{poke.max_hp} + +
+
+
"bg-base-content/20" + poke.hp / poke.max_hp > 0.5 -> "bg-success" + poke.hp / poke.max_hp > 0.2 -> "bg-warning" + true -> "bg-error" + end + ]} + style={"width: #{if poke.max_hp > 0, do: Float.round(poke.hp / poke.max_hp * 100, 1), else: 0}%"} + /> +
+
+ <% end %> +
+ <% end %> +
+
+ """ + end +end diff --git a/lib/cobblemon_ui_web/live/dashboard_live.ex b/lib/cobblemon_ui_web/live/dashboard_live.ex index 176bd86..cec28bc 100644 --- a/lib/cobblemon_ui_web/live/dashboard_live.ex +++ b/lib/cobblemon_ui_web/live/dashboard_live.ex @@ -1,6 +1,9 @@ defmodule CobblemonUiWeb.DashboardLive do use CobblemonUiWeb, :live_view + import CobblemonUiWeb.PokemonComponents + import CobblemonUiWeb.BattleComponents + @impl true def mount(_params, _session, socket) do if connected?(socket) do @@ -313,7 +316,7 @@ defmodule CobblemonUiWeb.DashboardLive do
- <%!-- Pokémon detail panel --%> + <%!-- Pokemon detail panel --%> <.pokemon_detail :if={@selected_pokemon} pokemon={@selected_pokemon} @@ -328,356 +331,6 @@ defmodule CobblemonUiWeb.DashboardLive do """ end - # --- Components --- - - attr :pokemon, :map, required: true - attr :index, :integer, required: true - attr :compact, :boolean, default: false - attr :tier, :string, default: nil - - defp pokemon_card(%{pokemon: nil} = assigns) do - ~H""" -
- Empty -
- """ - end - - defp pokemon_card(assigns) do - ~H""" -
-
-
-

- {@pokemon.species || "Unknown"} -

-

- Lv. {@pokemon.level || "?"} -

-
-
- <.tier_badge :if={@tier} tier={@tier} species={@pokemon.species} compact={@compact} /> -
- <.icon - name="hero-sparkles" - class={[ - "text-warning", - if(@compact, do: "size-3", else: "size-4") - ]} - /> -
-
-
-
- - {@pokemon.nature} - - - {gender_symbol(@pokemon.gender)} - -
-
- """ - end - - attr :tier, :string, required: true - attr :species, :string, required: true - attr :compact, :boolean, default: false - - defp tier_badge(assigns) do - ~H""" - "bg-red-500/20 text-red-400 ring-1 ring-red-500/30 hover:bg-red-500/30" - "A" -> "bg-orange-500/20 text-orange-400 ring-1 ring-orange-500/30 hover:bg-orange-500/30" - "B" -> "bg-yellow-500/20 text-yellow-400 ring-1 ring-yellow-500/30 hover:bg-yellow-500/30" - "C" -> "bg-green-500/20 text-green-400 ring-1 ring-green-500/30 hover:bg-green-500/30" - "D" -> "bg-blue-500/20 text-blue-400 ring-1 ring-blue-500/30 hover:bg-blue-500/30" - _ -> "bg-base-300/30 text-base-content/40 ring-1 ring-base-300/40" - end - ]} - > - {@tier} - - """ - end - - attr :pokemon, :map, required: true - attr :tier, :string, default: nil - - defp pokemon_detail(assigns) do - ~H""" -
- <%!-- Detail header --%> -
-
-
- <.icon name="hero-bolt" class="size-4 text-primary/70" /> -
-
-

- {@pokemon.species || "Unknown"} - <.tier_badge :if={@tier} tier={@tier} species={@pokemon.species} /> - - ★ - -

-

- Level {@pokemon.level || "?"} · {String.capitalize(@pokemon.form || "default")} form -

-
-
- -
- -
- <%!-- Info Column --%> -
-
-

- Details -

-
- <.stat_pill label="Nature" value={@pokemon.nature} /> - <.stat_pill label="Ability" value={@pokemon.ability} /> - <.stat_pill label="Gender" value={@pokemon.gender} /> - <.stat_pill label="Friendship" value={@pokemon.friendship} /> -
-
- - <%!-- Moves --%> -
-

- Moves -

-
-
- {format_move(move)} -
-
- No moves -
-
-
-
- - <%!-- Stats Column --%> -
- <%!-- IVs --%> -
-

- IVs -

-
- <.stat_bar - :for={{stat, val} <- stat_list(@pokemon.ivs)} - label={format_stat(stat)} - value={val} - max={31} - /> -
-
- - <%!-- EVs --%> -
-

- EVs - - ({ev_total(@pokemon.evs)}/510) - -

-
- <.stat_bar - :for={{stat, val} <- stat_list(@pokemon.evs)} - label={format_stat(stat)} - value={val} - max={252} - /> -
-
-
-
-
- """ - end - - attr :label, :string, required: true - attr :value, :any, required: true - - defp stat_pill(assigns) do - ~H""" -
-

{@label}

-

{@value || "—"}

-
- """ - end - - attr :label, :string, required: true - attr :value, :integer, required: true - attr :max, :integer, required: true - - defp stat_bar(assigns) do - pct = if assigns.max > 0, do: min(assigns.value / assigns.max * 100, 100), else: 0 - - color = - cond do - pct >= 90 -> "bg-success" - pct >= 60 -> "bg-info" - pct >= 30 -> "bg-warning" - true -> "bg-error/70" - end - - assigns = assign(assigns, pct: pct, color: color) - - ~H""" -
- - {@label} - -
-
-
- - {@value} - -
- """ - end - - # --- Battle components --- - - attr :battle, :map, required: true - attr :player_id, :string, required: true - - defp battle_panel(assigns) do - ~H""" -
-
- - Active Battle -
-
- - <%= for actor <- @battle.actors do %> -
-
-
- <%= if actor.type == "player" do %> - <.icon name="hero-user" class="size-3.5 text-primary" /> - <% else %> - <.icon name="hero-cpu-chip" class="size-3.5 text-warning" /> - <% end %> -
-
-

{actor.name}

-

- {actor.type} -

-
-
- <%= for poke <- actor.active_pokemon do %> -
-
-
- {poke.species} - - Lv.{poke.level} - -
- {poke.hp}/{poke.max_hp} -
-
-
"bg-base-content/20" - poke.hp / poke.max_hp > 0.5 -> "bg-success" - poke.hp / poke.max_hp > 0.2 -> "bg-warning" - true -> "bg-error" - end - ]} - style={"width: #{if poke.max_hp > 0, do: Float.round(poke.hp / poke.max_hp * 100, 1), else: 0}%"} - /> -
-
- <% end %> -
- <% end %> -
-
- """ - end - # --- Helpers --- defp party_count(%{party: party}), do: Enum.count(party, &(not is_nil(&1))) @@ -694,38 +347,6 @@ defmodule CobblemonUiWeb.DashboardLive do offset + slot_index end - defp gender_symbol("male"), do: "♂" - defp gender_symbol("female"), do: "♀" - defp gender_symbol(_), do: "—" - - defp gender_color("male"), do: "text-info" - defp gender_color("female"), do: "text-error" - defp gender_color(_), do: "text-base-content/40" - - defp format_move(move) when is_binary(move), do: String.replace(move, "_", " ") - defp format_move(_), do: "—" - - defp format_stat(:hp), do: "HP" - defp format_stat(:attack), do: "ATK" - defp format_stat(:defense), do: "DEF" - defp format_stat(:special_attack), do: "SPA" - defp format_stat(:special_defense), do: "SPD" - defp format_stat(:speed), do: "SPE" - defp format_stat(other), do: to_string(other) - - defp stat_list(stats) when is_map(stats) do - [:hp, :attack, :defense, :special_attack, :special_defense, :speed] - |> Enum.map(fn key -> {key, Map.get(stats, key, 0)} end) - end - - defp stat_list(_), do: [] - - defp ev_total(evs) when is_map(evs) do - evs |> Map.values() |> Enum.sum() - end - - defp ev_total(_), do: 0 - defp player_name(players, uuid) do case Enum.find(players, fn p -> p.uuid == uuid end) do %{name: name} when is_binary(name) -> name diff --git a/lib/cobblemon_ui_web/live/pokemon_components.ex b/lib/cobblemon_ui_web/live/pokemon_components.ex new file mode 100644 index 0000000..9f41ba6 --- /dev/null +++ b/lib/cobblemon_ui_web/live/pokemon_components.ex @@ -0,0 +1,292 @@ +defmodule CobblemonUiWeb.PokemonComponents do + use CobblemonUiWeb, :html + + attr :pokemon, :map, required: true + attr :index, :integer, required: true + attr :compact, :boolean, default: false + attr :tier, :string, default: nil + + def pokemon_card(%{pokemon: nil} = assigns) do + ~H""" +
+ Empty +
+ """ + end + + def pokemon_card(assigns) do + ~H""" +
+
+
+

+ {@pokemon.species || "Unknown"} +

+

+ Lv. {@pokemon.level || "?"} +

+
+
+ <.tier_badge :if={@tier} tier={@tier} species={@pokemon.species} compact={@compact} /> +
+ <.icon + name="hero-sparkles" + class={[ + "text-warning", + if(@compact, do: "size-3", else: "size-4") + ]} + /> +
+
+
+
+ + {@pokemon.nature} + + + {gender_symbol(@pokemon.gender)} + +
+
+ """ + end + + attr :tier, :string, required: true + attr :species, :string, required: true + attr :compact, :boolean, default: false + + def tier_badge(assigns) do + ~H""" + "bg-red-500/20 text-red-400 ring-1 ring-red-500/30 hover:bg-red-500/30" + "A" -> "bg-orange-500/20 text-orange-400 ring-1 ring-orange-500/30 hover:bg-orange-500/30" + "B" -> "bg-yellow-500/20 text-yellow-400 ring-1 ring-yellow-500/30 hover:bg-yellow-500/30" + "C" -> "bg-green-500/20 text-green-400 ring-1 ring-green-500/30 hover:bg-green-500/30" + "D" -> "bg-blue-500/20 text-blue-400 ring-1 ring-blue-500/30 hover:bg-blue-500/30" + _ -> "bg-base-300/30 text-base-content/40 ring-1 ring-base-300/40" + end + ]} + > + {@tier} + + """ + end + + attr :pokemon, :map, required: true + attr :tier, :string, default: nil + + def pokemon_detail(assigns) do + ~H""" +
+ <%!-- Detail header --%> +
+
+
+ <.icon name="hero-bolt" class="size-4 text-primary/70" /> +
+
+

+ {@pokemon.species || "Unknown"} + <.tier_badge :if={@tier} tier={@tier} species={@pokemon.species} /> + +

+

+ Level {@pokemon.level || "?"} · {String.capitalize(@pokemon.form || "default")} form +

+
+
+ +
+ +
+ <%!-- Info Column --%> +
+
+

+ Details +

+
+ <.stat_pill label="Nature" value={@pokemon.nature} /> + <.stat_pill label="Ability" value={@pokemon.ability} /> + <.stat_pill label="Gender" value={@pokemon.gender} /> + <.stat_pill label="Friendship" value={@pokemon.friendship} /> +
+
+ + <%!-- Moves --%> +
+

+ Moves +

+
+
+ {format_move(move)} +
+
+ No moves +
+
+
+
+ + <%!-- Stats Column --%> +
+ <%!-- IVs --%> +
+

+ IVs +

+
+ <.stat_bar + :for={{stat, val} <- stat_list(@pokemon.ivs)} + label={format_stat(stat)} + value={val} + max={31} + /> +
+
+ + <%!-- EVs --%> +
+

+ EVs + + ({ev_total(@pokemon.evs)}/510) + +

+
+ <.stat_bar + :for={{stat, val} <- stat_list(@pokemon.evs)} + label={format_stat(stat)} + value={val} + max={252} + /> +
+
+
+
+
+ """ + end + + attr :label, :string, required: true + attr :value, :any, required: true + + def stat_pill(assigns) do + ~H""" +
+

{@label}

+

{@value || "—"}

+
+ """ + end + + attr :label, :string, required: true + attr :value, :integer, required: true + attr :max, :integer, required: true + + def stat_bar(assigns) do + pct = if assigns.max > 0, do: min(assigns.value / assigns.max * 100, 100), else: 0 + + color = + cond do + pct >= 90 -> "bg-success" + pct >= 60 -> "bg-info" + pct >= 30 -> "bg-warning" + true -> "bg-error/70" + end + + assigns = assign(assigns, pct: pct, color: color) + + ~H""" +
+ + {@label} + +
+
+
+ + {@value} + +
+ """ + end + + # --- Helpers --- + + defp gender_symbol("male"), do: "♂" + defp gender_symbol("female"), do: "♀" + defp gender_symbol(_), do: "—" + + defp gender_color("male"), do: "text-info" + defp gender_color("female"), do: "text-error" + defp gender_color(_), do: "text-base-content/40" + + defp format_move(move) when is_binary(move), do: String.replace(move, "_", " ") + defp format_move(_), do: "—" + + defp format_stat(:hp), do: "HP" + defp format_stat(:attack), do: "ATK" + defp format_stat(:defense), do: "DEF" + defp format_stat(:special_attack), do: "SPA" + defp format_stat(:special_defense), do: "SPD" + defp format_stat(:speed), do: "SPE" + defp format_stat(other), do: to_string(other) + + defp stat_list(stats) when is_map(stats) do + [:hp, :attack, :defense, :special_attack, :special_defense, :speed] + |> Enum.map(fn key -> {key, Map.get(stats, key, 0)} end) + end + + defp stat_list(_), do: [] + + defp ev_total(evs) when is_map(evs), do: evs |> Map.values() |> Enum.sum() + defp ev_total(_), do: 0 +end