-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Add chats and messages * Add fantasy league drafts * Add liveview test and chat component * broadcast messages
- Loading branch information
Showing
18 changed files
with
693 additions
and
7 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
const ChatScrollToBottom = { | ||
mounted() { | ||
this.el.scrollTo(0, this.el.scrollHeight) | ||
}, | ||
|
||
updated() { | ||
const pixelsBelowBottom = | ||
this.el.scrollHeight - this.el.clientHeight - this.el.scrollTop | ||
|
||
if (pixelsBelowBottom < this.el.clientHeight * 0.3) { | ||
this.el.scrollTo(0, this.el.scrollHeight) | ||
} | ||
}, | ||
} | ||
|
||
export default ChatScrollToBottom |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,7 @@ | ||
import SortableInputsFor from "./sortable-hooks" | ||
import ChatScrollToBottom from "./chat-hooks" | ||
|
||
export default { | ||
SortableInputsFor, | ||
ChatScrollToBottom, | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
defmodule Ex338.Chats do | ||
@moduledoc """ | ||
The Chats context. | ||
""" | ||
|
||
import Ecto.Query | ||
|
||
alias Ex338.Accounts.User | ||
alias Ex338.Chats.Chat | ||
alias Ex338.Chats.Message | ||
alias Ex338.Repo | ||
|
||
def subscribe(%Chat{} = chat, %User{}) do | ||
Phoenix.PubSub.subscribe(Ex338.PubSub, topic(chat)) | ||
end | ||
|
||
def subscribe(_chat, nil), do: nil | ||
|
||
def create_chat(chat_params) do | ||
%Chat{} | ||
|> Chat.changeset(chat_params) | ||
|> Repo.insert() | ||
end | ||
|
||
def create_message(message_params) do | ||
%Message{} | ||
|> Message.changeset(message_params) | ||
|> Repo.insert() | ||
|> case do | ||
{:ok, message} -> | ||
message = Repo.preload(message, :user) | ||
broadcast(message, %Ex338.Events.MessageCreated{message: message}) | ||
{:ok, message} | ||
|
||
other -> | ||
other | ||
end | ||
end | ||
|
||
def change_message(%Message{} = message, attrs \\ %{}) do | ||
Message.changeset(message, attrs) | ||
end | ||
|
||
def list_chats do | ||
Repo.all(Chat) | ||
end | ||
|
||
def get_chat(chat_id) do | ||
query = | ||
from c in Chat, | ||
where: c.id == ^chat_id, | ||
preload: [messages: :user] | ||
|
||
Repo.one(query) | ||
end | ||
|
||
defp broadcast(%Message{} = message, event) 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}" | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
defmodule Ex338.Chats.Chat do | ||
@moduledoc false | ||
use Ecto.Schema | ||
|
||
import Ecto.Changeset | ||
|
||
alias Ex338.Chats.Message | ||
|
||
schema "chats" do | ||
field :room_name, :string | ||
|
||
has_many :messages, Message, preload_order: [asc: :inserted_at] | ||
timestamps() | ||
end | ||
|
||
@doc """ | ||
Builds a changeset based on the `struct` and `params`. | ||
""" | ||
def changeset(struct, params \\ %{}) do | ||
struct | ||
|> cast(params, [:room_name]) | ||
|> validate_required([:room_name]) | ||
|> unique_constraint(:room_name) | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
defmodule Ex338.Chats.Message do | ||
@moduledoc false | ||
use Ecto.Schema | ||
|
||
import Ecto.Changeset | ||
|
||
schema "messages" do | ||
field :content, :string | ||
belongs_to :user, Ex338.Accounts.User | ||
belongs_to :chat, Ex338.Chats.Chat | ||
|
||
timestamps() | ||
end | ||
|
||
@doc false | ||
def changeset(message, attrs \\ %{}) do | ||
message | ||
|> cast(attrs, [:content, :user_id, :chat_id]) | ||
|> validate_required([:content, :chat_id]) | ||
|> validate_length(:content, min: 1, max: 280) | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
defmodule Ex338.Events do | ||
@moduledoc """ | ||
Defines Event structs for use within the pubsub system. | ||
""" | ||
defmodule MessageCreated do | ||
@moduledoc false | ||
defstruct message: nil | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
defmodule Ex338.FantasyLeagues.FantasyLeagueDraft do | ||
@moduledoc false | ||
use Ecto.Schema | ||
|
||
import Ecto.Changeset | ||
|
||
schema "fantasy_league_drafts" do | ||
belongs_to(:fantasy_league, Ex338.FantasyLeagues.FantasyLeague) | ||
belongs_to(:championship, Ex338.Championships.Championship) | ||
belongs_to(:chat, Ex338.Chats.Chat) | ||
|
||
timestamps() | ||
end | ||
|
||
@doc false | ||
def changeset(fantasy_league_draft, attrs) do | ||
fantasy_league_draft | ||
|> cast(attrs, [:chat_id, :championship_id, :fantasy_league_id]) | ||
|> validate_required([:fantasy_league_id, :chat_id]) | ||
|> unique_constraint([:championship_id, :fantasy_league_id]) | ||
|> unique_constraint([:chat_id]) | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,155 @@ | ||
defmodule Ex338Web.ChampionshipLive.ChatComponent do | ||
@moduledoc false | ||
use Ex338Web, :live_component | ||
|
||
alias Ex338.Chats | ||
|
||
@impl true | ||
def update(%{message: message} = assigns, socket) do | ||
changeset = Chats.change_message(message) | ||
|
||
{:ok, | ||
socket | ||
|> assign(assigns) | ||
|> assign(:message, message) | ||
|> assign_form(changeset)} | ||
end | ||
|
||
defp assign_form(socket, %Ecto.Changeset{} = changeset) do | ||
assign(socket, :form, to_form(changeset)) | ||
end | ||
|
||
@impl true | ||
def handle_event("validate", %{"message" => message_params}, socket) do | ||
message_params = add_chat_and_user_to_params(message_params, socket) | ||
|
||
changeset = | ||
socket.assigns.message | ||
|> Chats.change_message(message_params) | ||
|> Map.put(:action, :validate) | ||
|
||
{:noreply, assign_form(socket, changeset)} | ||
end | ||
|
||
def handle_event("save", %{"message" => message_params}, socket) do | ||
message_params = add_chat_and_user_to_params(message_params, socket) | ||
|
||
case Chats.create_message(message_params) do | ||
{:ok, _message} -> | ||
{:noreply, push_patch(socket, to: socket.assigns.patch)} | ||
|
||
{:error, %Ecto.Changeset{} = changeset} -> | ||
{:noreply, assign_form(socket, changeset)} | ||
end | ||
end | ||
|
||
defp add_chat_and_user_to_params(params, socket) do | ||
params | ||
|> Map.put("user_id", socket.assigns.current_user.id) | ||
|> Map.put("chat_id", socket.assigns.chat.id) | ||
end | ||
|
||
@impl true | ||
def render(assigns) do | ||
~H""" | ||
<div class="overflow-hidden bg-white shadow sm:rounded-lg"> | ||
<div class="px-4 py-5 border-b border-gray-200 sm:px-6"> | ||
<ul | ||
id="messages" | ||
phx-update="stream" | ||
role="list" | ||
phx-hook="ChatScrollToBottom" | ||
class="space-y-4 flex flex-col h-[800px] overflow-y-auto overflow-x-hidden pb-6" | ||
> | ||
<.comment :for={{id, message} <- @messages} id={id} message={message} /> | ||
</ul> | ||
<div class="flex gap-x-3"> | ||
<.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" | ||
> | ||
<div class="overflow-hidden rounded-lg pb-12 shadow-sm ring-1 ring-inset ring-gray-300 focus-within:ring-2 focus-within:ring-indigo-600"> | ||
<label for="comment" class="sr-only">Add your comment</label> | ||
<.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..." | ||
/> | ||
</div> | ||
<div class="absolute inset-x-0 bottom-0 flex justify-end py-2 pl-3 pr-2"> | ||
<button | ||
type="submit" | ||
class="rounded-md bg-white px-2.5 py-1.5 text-sm font-semibold text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50" | ||
> | ||
Comment | ||
</button> | ||
</div> | ||
</.form> | ||
</div> | ||
</div> | ||
</div> | ||
""" | ||
end | ||
|
||
defp comment(%{message: %{user: nil}} = assigns) do | ||
~H""" | ||
<li id={@id} class="flex gap-x-4"> | ||
<div class="flex h-6 w-6 flex-none items-center justify-center bg-white"> | ||
<.icon name="hero-check-circle" class="h-6 w-6 text-indigo-600" /> | ||
</div> | ||
<p class="flex-auto py-0.5 text-xs leading-5 text-gray-500"> | ||
<%= @message.content %> | ||
</p> | ||
</li> | ||
""" | ||
end | ||
|
||
defp comment(assigns) do | ||
~H""" | ||
<li id={@id} class="flex gap-x-4"> | ||
<.user_icon name={@message.user.name} /> | ||
<div class="flex-auto"> | ||
<div class="flex justify-between items-start gap-x-4"> | ||
<div class="text-xs leading-5 font-medium text-gray-900"> | ||
<%= @message.user.name %> | ||
</div> | ||
</div> | ||
<p class="text-sm leading-6 text-gray-500"> | ||
<%= @message.content %> | ||
</p> | ||
</div> | ||
</li> | ||
""" | ||
end | ||
|
||
attr :name, :string, required: true | ||
attr :class, :string, default: nil | ||
|
||
defp user_icon(assigns) do | ||
~H""" | ||
<div class={[ | ||
"h-6 w-6 flex flex-shrink-0 items-center justify-center bg-gray-600 rounded-full text-xs font-medium text-white", | ||
@class | ||
]}> | ||
<%= get_initials(@name) %> | ||
</div> | ||
""" | ||
end | ||
|
||
defp get_initials(name) do | ||
name | ||
|> String.split(" ") | ||
|> Enum.take(2) | ||
|> Enum.map_join("", &String.at(&1, 0)) | ||
end | ||
end |
Oops, something went wrong.