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 :timer.send_interval(1000, self(), :tick) end players = case CobblemonUi.CobblemonFS.list_players() do {:ok, list} -> list end {:ok, assign(socket, page_title: "Cobblemon Dashboard", players: players, selected_player: nil, player_data: nil, battle: nil, selected_pokemon: nil, tier_list: CobblemonUi.TierListScraper.get_tier_list(), view_mode: :party, loading: false, error: nil )} end @impl true def handle_params(%{"uuid" => uuid}, _uri, socket) do case CobblemonUi.CobblemonFS.get_player(uuid) do {:ok, data} -> battle = find_player_battle(uuid) {:noreply, assign(socket, selected_player: uuid, player_data: data, battle: battle, selected_pokemon: nil, error: nil )} {:error, :not_found} -> {:noreply, assign(socket, selected_player: uuid, player_data: nil, error: "Player not found" )} {:error, reason} -> {:noreply, assign(socket, selected_player: uuid, player_data: nil, error: "Error loading player: #{inspect(reason)}" )} end end def handle_params(_params, _uri, socket) do {:noreply, assign(socket, selected_player: nil, player_data: nil, battle: nil, selected_pokemon: nil)} end @impl true def handle_event("select_pokemon", %{"index" => index_str}, socket) do index = String.to_integer(index_str) pokemon = case socket.assigns.view_mode do :party -> Enum.at(socket.assigns.player_data.party, index) :pc -> socket.assigns.player_data.pc |> Enum.flat_map(fn box -> box.pokemon end) |> Enum.at(index) end {:noreply, assign(socket, selected_pokemon: pokemon)} end def handle_event("close_pokemon", _params, socket) do {:noreply, assign(socket, selected_pokemon: nil)} end def handle_event("switch_view", %{"mode" => mode}, socket) 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 {:ok, list} -> list end socket = if uuid = socket.assigns.selected_player do battle = find_player_battle(uuid) case CobblemonUi.CobblemonFS.get_player(uuid) do {:ok, data} -> assign(socket, player_data: data, battle: battle, error: nil) _ -> assign(socket, battle: battle) end else socket end assign(socket, players: players) end @impl true def render(assigns) do ~H"""
<%!-- Player picker --%>
<.icon name="hero-cube-transparent" class="size-6 text-primary" />

Cobblemon

Who are you?

<.icon name="hero-user-group" class="size-10 mx-auto mb-4 text-base-content/20" />

No players found

Check the data directory

<.link :for={player <- @players} patch={~p"/player/#{player.uuid}"} id={"player-#{player.uuid}"} class="group flex items-center gap-3 rounded-xl border border-base-300/40 bg-base-200/20 px-5 py-4 hover:bg-base-200/50 hover:border-primary/30 hover:shadow-md hover:shadow-primary/5 transition-all duration-150" >
<.icon name="hero-user" class="size-5 text-primary/70" />
{player.name || "Unknown"} <.icon name="hero-chevron-right" class="size-4 text-base-content/20 ml-auto shrink-0 group-hover:text-primary/40 transition-colors" />
<%!-- Player view --%>
<%!-- Header --%>
<.link patch={~p"/"} id="back-to-players" class="w-10 h-10 rounded-xl bg-base-200/50 hover:bg-base-200 flex items-center justify-center transition-colors" > <.icon name="hero-arrow-left" class="size-5 text-base-content/50" />

{player_name(@players, @selected_player)}

Player Data Explorer

<%!-- Scrollable content --%>
<.icon name="hero-exclamation-triangle" class="size-5" /> {@error}
<%!-- Stats bar --%>
{party_count(@player_data)} in party
{pc_count(@player_data)} in PC
<%!-- Active battle --%> <.battle_panel :if={@battle} battle={@battle} player_id={@selected_player} tier_list={@tier_list} /> <%!-- View mode tabs --%>
<%!-- Party view --%>
<%= 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 %>
<%!-- PC view --%>

Box {box.box + 1}

<%= 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 %>
PC storage is empty
<%!-- Pokemon detail panel --%> <.pokemon_detail :if={@selected_pokemon} pokemon={@selected_pokemon} tier={Map.get(@tier_list, String.downcase(@selected_pokemon.species || ""), nil)} />
""" end # --- Helpers --- defp party_count(%{party: party}), do: Enum.count(party, &(not is_nil(&1))) defp pc_count(%{pc: pc}), do: Enum.sum(Enum.map(pc, fn b -> Enum.count(b.pokemon, &(not is_nil(&1))) end)) defp pc_global_index(boxes, current_box, slot_index) do offset = boxes |> Enum.filter(fn b -> b.box < current_box end) |> Enum.sum_by(fn b -> length(b.pokemon) end) offset + slot_index end defp player_name(players, uuid) do case Enum.find(players, fn p -> p.uuid == uuid end) do %{name: name} when is_binary(name) -> name _ -> "Unknown" end end defp find_player_battle(uuid) do case CobblemonUi.BattlesApi.list_battles() do {:ok, battles} -> Enum.find(battles, fn battle -> Enum.any?(battle.actors, fn actor -> actor.player_id == uuid end) end) _ -> nil end end end