From 999350522b4d6bd5aca6c184dcd0904f441c15dc Mon Sep 17 00:00:00 2001 From: Axel Clark Date: Sun, 7 Apr 2024 14:35:26 -0700 Subject: [PATCH] List users for chat * List users with Phoenix presence * Move messages list out of form component * See #1299 --- lib/ex338/application.ex | 1 + lib/ex338/chats.ex | 4 +- .../live/championship_live/chat_component.ex | 103 ++++--------- lib/ex338_web/live/championship_live/show.ex | 141 +++++++++++++++++- lib/ex338_web/presence.ex | 16 ++ .../live/championship_live/show_test.exs | 1 + 6 files changed, 185 insertions(+), 81 deletions(-) create mode 100644 lib/ex338_web/presence.ex diff --git a/lib/ex338/application.ex b/lib/ex338/application.ex index cf08bb52..3223423a 100644 --- a/lib/ex338/application.ex +++ b/lib/ex338/application.ex @@ -26,6 +26,7 @@ defmodule Ex338.Application do Ex338.Repo, {Phoenix.PubSub, pubsub_options}, Ex338Web.Telemetry, + Ex338Web.Presence, # Start the endpoint when the application starts Ex338Web.Endpoint, {Oban, Application.fetch_env!(:ex338, Oban)} diff --git a/lib/ex338/chats.ex b/lib/ex338/chats.ex index 9e1d2221..22ea39be 100644 --- a/lib/ex338/chats.ex +++ b/lib/ex338/chats.ex @@ -58,6 +58,6 @@ defmodule Ex338.Chats do Phoenix.PubSub.broadcast(Ex338.PubSub, topic(message), {__MODULE__, event}) end - defp topic(%Message{} = message), do: "chat:#{message.chat_id}" - defp topic(%Chat{} = chat), do: "chat:#{chat.id}" + def topic(%Message{} = message), do: "chat:#{message.chat_id}" + def topic(%Chat{} = chat), do: "chat:#{chat.id}" end diff --git a/lib/ex338_web/live/championship_live/chat_component.ex b/lib/ex338_web/live/championship_live/chat_component.ex index b05df332..9337eba7 100644 --- a/lib/ex338_web/live/championship_live/chat_component.ex +++ b/lib/ex338_web/live/championship_live/chat_component.ex @@ -52,83 +52,38 @@ defmodule Ex338Web.ChampionshipLive.ChatComponent do @impl true def render(assigns) do ~H""" -
-
-
    - <.comment :for={{id, message} <- @messages} id={id} message={message} /> -
- -
- <.user_icon name={@current_user.name} class="!mt-0" /> - <.form - id="create-message" - for={@form} - phx-target={@myself} - phx-change="validate" - phx-submit="save" - class="relative flex-auto" - > -
- - <.input - field={@form[:content]} - phx-debounce="blur" - type="commenttextarea" - rows="2" - class="block w-full resize-none border-0 bg-transparent py-1.5 text-gray-900 placeholder:text-gray-400 focus:ring-0 sm:text-sm sm:leading-6" - placeholder="Add your comment..." - /> -
- -
- -
- +
+ <.user_icon name={@current_user.name} class="!mt-0" /> + <.form + id="create-message" + for={@form} + phx-target={@myself} + phx-change="validate" + phx-submit="save" + class="relative flex-auto" + > +
+ + <.input + field={@form[:content]} + phx-debounce="blur" + type="commenttextarea" + rows="2" + class="block w-full resize-none border-0 bg-transparent py-1.5 text-gray-900 placeholder:text-gray-400 focus:ring-0 sm:text-sm sm:leading-6" + placeholder="Add your comment..." + />
-
-
- """ - end - defp comment(%{message: %{user: nil}} = assigns) do - ~H""" -
  • -
    - <.icon name="hero-check-circle" class="h-6 w-6 text-indigo-600" /> -
    -

    - <%= @message.content %> -

    -
  • - """ - end - - defp comment(assigns) do - ~H""" -
  • - <.user_icon name={@message.user.name} /> -
    -
    -
    - <%= @message.user.name %> -
    +
    +
    -

    - <%= @message.content %> -

    -
    -
  • + +
    """ end diff --git a/lib/ex338_web/live/championship_live/show.ex b/lib/ex338_web/live/championship_live/show.ex index 5324e7cf..6a30686f 100644 --- a/lib/ex338_web/live/championship_live/show.ex +++ b/lib/ex338_web/live/championship_live/show.ex @@ -10,6 +10,7 @@ defmodule Ex338Web.ChampionshipLive.Show do alias Ex338.FantasyLeagues alias Ex338.InSeasonDraftPicks alias Ex338Web.ChampionshipLive.ChatComponent + alias Ex338Web.Presence @impl true def mount(_params, _session, socket) do @@ -50,18 +51,38 @@ defmodule Ex338Web.ChampionshipLive.Show do Chats.subscribe(chat, socket.assigns.current_user) end + current_user = socket.assigns.current_user + + if chat && current_user do + Presence.track( + self(), + Chats.topic(chat), + current_user.id, + default_user_presence_payload(current_user) + ) + end + socket |> assign(:chat, chat) |> assign(:message, %Message{}) |> stream(:messages, chat.messages) + |> assign(:users, Presence.list_presences(Chats.topic(chat))) else _ -> socket |> assign(:chat, nil) + |> assign(:users, []) |> stream(:messages, []) end end + defp default_user_presence_payload(user) do + %{ + name: user.name, + user_id: user.id + } + end + @impl true def handle_info(:refresh, socket) do championship = Championships.update_next_in_season_pick(socket.assigns.championship) @@ -112,6 +133,16 @@ defmodule Ex338Web.ChampionshipLive.Show do {:noreply, stream_insert(socket, :messages, message)} end + def handle_info( + %{event: "presence_diff", payload: _payload}, + %{assigns: %{chat: chat}} = socket + ) do + users = + Presence.list_presences(Chats.topic(chat)) + + {:noreply, assign(socket, users: users)} + end + # Implementations defp schedule_refresh do @@ -312,14 +343,14 @@ defmodule Ex338Web.ChampionshipLive.Show do <.section_header> Draft Chat - <.live_component - module={ChatComponent} - id="chat" + <.chat_list + championship={@championship} chat={@chat} + current_user={@current_user} + fantasy_league={@fantasy_league} messages={@streams.messages} message={@message} - current_user={@current_user} - patch={~p"/fantasy_leagues/#{@fantasy_league.id}/championships/#{@championship.id}"} + users={@users} />
    <% end %> @@ -552,6 +583,106 @@ defmodule Ex338Web.ChampionshipLive.Show do """ end + attr :chat, :map, required: true + attr :championship, :map, required: true + attr :current_user, :map, required: true + attr :fantasy_league, :map, required: true + attr :message, :map, required: true + attr :messages, :list, required: true + attr :users, :list, required: true + + def chat_list(assigns) do + ~H""" +
    +
    +
      + <.comment :for={{id, message} <- @messages} id={id} message={message} /> +
    + + <.live_component + module={ChatComponent} + id="chat" + chat={@chat} + message={@message} + current_user={@current_user} + patch={~p"/fantasy_leagues/#{@fantasy_league.id}/championships/#{@championship.id}"} + /> +
    +

    Online Users

    + <%= for user <- @users do %> +

    + + Online + + + <%= user.name %> + +

    + <% end %> +
    +
    +
    + """ + end + + defp comment(%{message: %{user: nil}} = assigns) do + ~H""" +
  • +
    + <.icon name="hero-check-circle" class="h-6 w-6 text-indigo-600" /> +
    +

    + <%= @message.content %> +

    +
  • + """ + end + + defp comment(assigns) do + ~H""" +
  • + <.user_icon name={@message.user.name} /> +
    +
    +
    + <%= @message.user.name %> +
    +
    +

    + <%= @message.content %> +

    +
    +
  • + """ + end + + attr :name, :string, required: true + attr :class, :string, default: nil + + defp user_icon(assigns) do + ~H""" +
    + <%= get_initials(@name) %> +
    + """ + end + + defp get_initials(name) do + name + |> String.split(" ") + |> Enum.take(2) + |> Enum.map_join("", &String.at(&1, 0)) + end + defp get_team_name(%{fantasy_player: %{roster_positions: [position]}}) do position.fantasy_team.team_name end diff --git a/lib/ex338_web/presence.ex b/lib/ex338_web/presence.ex new file mode 100644 index 00000000..ed887e3c --- /dev/null +++ b/lib/ex338_web/presence.ex @@ -0,0 +1,16 @@ +defmodule Ex338Web.Presence do + @moduledoc false + use Phoenix.Presence, + otp_app: :ex338, + pubsub_server: Ex338.PubSub + + alias Ex338Web.Presence + + def list_presences(topic) do + topic + |> Presence.list() + |> Enum.map(fn {_user_id, data} -> + List.first(data[:metas]) + end) + end +end diff --git a/test/ex338_web/live/championship_live/show_test.exs b/test/ex338_web/live/championship_live/show_test.exs index 58be63d5..da991b92 100644 --- a/test/ex338_web/live/championship_live/show_test.exs +++ b/test/ex338_web/live/championship_live/show_test.exs @@ -322,6 +322,7 @@ defmodule Ex338Web.ChampionshipLive.ShowTest do assert has_element?(view, "h3", "Draft") assert has_element?(view, "p", "hello world!") + assert has_element?(view, "p#online-user-#{user.id}", user.name) long_comment = """ In a quiet town nestled between rolling hills and dense forests, a small but spirited