Updates to frontend and fix a bug where first socket assignment failed
This commit is contained in:
parent
d9943b201d
commit
f6b262ea66
3
.gitignore
vendored
3
.gitignore
vendored
@ -4,5 +4,6 @@
|
|||||||
erl_crash.dump
|
erl_crash.dump
|
||||||
*.ez
|
*.ez
|
||||||
/assets/node_modules/
|
/assets/node_modules/
|
||||||
/priv/static/
|
/priv/static/uploads
|
||||||
|
/priv/static/assets
|
||||||
/deps
|
/deps
|
||||||
|
@ -126,115 +126,16 @@
|
|||||||
white-space: pre-line;
|
white-space: pre-line;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.circle {
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
border-radius: 50%;
|
||||||
|
display: flex; /* or inline-flex */
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
/* Chat css from: https://www.bootdey.com/snippets/view/card-chat-list#html */
|
.chat {
|
||||||
.chat-container {
|
max-height: 50vh;
|
||||||
height: 300px;
|
|
||||||
overflow-y: scroll;
|
overflow-y: scroll;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chat-list {
|
|
||||||
padding: 0;
|
|
||||||
font-size: .8rem;
|
|
||||||
padding-bottom: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.chat-list li {
|
|
||||||
margin-bottom: 10px;
|
|
||||||
overflow: auto;
|
|
||||||
color: #ffffff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.chat-list .chat-img {
|
|
||||||
float: left;
|
|
||||||
width: 48px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.chat-list .chat-img img {
|
|
||||||
-webkit-border-radius: 50px;
|
|
||||||
-moz-border-radius: 50px;
|
|
||||||
border-radius: 50px;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.chat-list .chat-message {
|
|
||||||
-webkit-border-radius: 50px;
|
|
||||||
-moz-border-radius: 50px;
|
|
||||||
border-radius: 50px;
|
|
||||||
background: #5a99ee;
|
|
||||||
display: inline-block;
|
|
||||||
padding: 10px 20px;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.chat-list .chat-message:before {
|
|
||||||
content: "";
|
|
||||||
position: absolute;
|
|
||||||
top: 15px;
|
|
||||||
width: 0;
|
|
||||||
height: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.chat-list .chat-message h5 {
|
|
||||||
margin: 0 0 5px 0;
|
|
||||||
font-weight: 600;
|
|
||||||
line-height: 100%;
|
|
||||||
font-size: .9rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.chat-list .chat-message p {
|
|
||||||
line-height: 18px;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.chat-list .chat-body {
|
|
||||||
margin-left: 20px;
|
|
||||||
float: left;
|
|
||||||
width: 70%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.chat-list .in .chat-message:before {
|
|
||||||
left: -12px;
|
|
||||||
border-bottom: 20px solid transparent;
|
|
||||||
border-right: 20px solid #5a99ee;
|
|
||||||
}
|
|
||||||
|
|
||||||
.chat-list .out .chat-img {
|
|
||||||
float: right;
|
|
||||||
}
|
|
||||||
|
|
||||||
.chat-list .out .chat-body {
|
|
||||||
float: right;
|
|
||||||
margin-right: 20px;
|
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
|
|
||||||
.chat-list .out .chat-message {
|
|
||||||
background: #fc6d4c;
|
|
||||||
}
|
|
||||||
|
|
||||||
.chat-list .out .chat-message:before {
|
|
||||||
right: -12px;
|
|
||||||
border-bottom: 20px solid transparent;
|
|
||||||
border-left: 20px solid #fc6d4c;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card .card-header:first-child {
|
|
||||||
-webkit-border-radius: 0.3rem 0.3rem 0 0;
|
|
||||||
-moz-border-radius: 0.3rem 0.3rem 0 0;
|
|
||||||
border-radius: 0.3rem 0.3rem 0 0;
|
|
||||||
}
|
|
||||||
.card .card-header {
|
|
||||||
background: #17202b;
|
|
||||||
border: 0;
|
|
||||||
font-size: 1rem;
|
|
||||||
padding: .65rem 1rem;
|
|
||||||
position: relative;
|
|
||||||
font-weight: 600;
|
|
||||||
color: #ffffff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.content{
|
|
||||||
margin-top:40px;
|
|
||||||
}
|
|
@ -1,6 +1,6 @@
|
|||||||
// We import the CSS which is extracted to its own file by esbuild.
|
// We import the CSS which is extracted to its own file by esbuild.
|
||||||
// Remove this line if you add a your own CSS build pipeline (e.g postcss).
|
// Remove this line if you add a your own CSS build pipeline (e.g postcss).
|
||||||
import "../css/app.css"
|
import "../css/app.css";
|
||||||
|
|
||||||
// If you want to use Phoenix channels, run `mix help phx.gen.channel`
|
// If you want to use Phoenix channels, run `mix help phx.gen.channel`
|
||||||
// to get started and then uncomment the line below.
|
// to get started and then uncomment the line below.
|
||||||
@ -20,32 +20,32 @@ import "../css/app.css"
|
|||||||
//
|
//
|
||||||
|
|
||||||
// Include phoenix_html to handle method=PUT/DELETE in forms and buttons.
|
// Include phoenix_html to handle method=PUT/DELETE in forms and buttons.
|
||||||
import "phoenix_html"
|
import "phoenix_html";
|
||||||
// Establish Phoenix Socket and LiveView configuration.
|
// Establish Phoenix Socket and LiveView configuration.
|
||||||
import {Socket} from "phoenix"
|
import {Socket} from "phoenix";
|
||||||
import {LiveSocket} from "phoenix_live_view"
|
import {LiveSocket} from "phoenix_live_view";
|
||||||
import topbar from "../vendor/topbar"
|
import topbar from "../vendor/topbar";
|
||||||
|
|
||||||
let csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute("content")
|
let csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute("content");
|
||||||
let liveSocket = new LiveSocket("/live", Socket, {params: {_csrf_token: csrfToken}})
|
let liveSocket = new LiveSocket("/live", Socket, {params: {_csrf_token: csrfToken}});
|
||||||
|
|
||||||
// Show progress bar on live navigation and form submits
|
// Show progress bar on live navigation and form submits
|
||||||
topbar.config({barColors: {0: "#29d"}, shadowColor: "rgba(0, 0, 0, .3)"})
|
topbar.config({barColors: {0: "#29d"}, shadowColor: "rgba(0, 0, 0, .3)"});
|
||||||
window.addEventListener("phx:page-loading-start", info => topbar.show())
|
window.addEventListener("phx:page-loading-start", info => topbar.show());
|
||||||
window.addEventListener("phx:page-loading-stop", info => topbar.hide())
|
window.addEventListener("phx:page-loading-stop", info => topbar.hide());
|
||||||
|
|
||||||
// connect if there are any LiveViews on the page
|
// connect if there are any LiveViews on the page
|
||||||
liveSocket.connect()
|
liveSocket.connect();
|
||||||
|
|
||||||
// expose liveSocket on window for web console debug logs and latency simulation:
|
// expose liveSocket on window for web console debug logs and latency simulation:
|
||||||
// >> liveSocket.enableDebug()
|
// >> liveSocket.enableDebug()
|
||||||
// >> liveSocket.enableLatencySim(1000) // enabled for duration of browser session
|
// >> liveSocket.enableLatencySim(1000) // enabled for duration of browser session
|
||||||
// >> liveSocket.disableLatencySim()
|
// >> liveSocket.disableLatencySim()
|
||||||
window.liveSocket = liveSocket
|
window.liveSocket = liveSocket;
|
||||||
|
|
||||||
// Hack to remove alerts on click
|
// Hack to remove alerts on click
|
||||||
Array.from(window.document.getElementsByClassName('alert')).forEach((x) => x.addEventListener('click', () => x.style.display = "none"))
|
Array.from(window.document.getElementsByClassName('alert')).forEach((x) => x.addEventListener('click', () => x.style.display = "none"));
|
||||||
|
|
||||||
import RoomChat from "./chat"
|
window.userSocket = new Socket("/socket", {params: {_csrf_token: csrfToken}});
|
||||||
|
import RoomChat from "./chat";
|
||||||
window.RoomChat = RoomChat;
|
window.RoomChat = RoomChat;
|
||||||
window.userSocket = new Socket("/socket", {params: {_csrf_token: csrfToken}})
|
|
@ -1,11 +1,110 @@
|
|||||||
let RoomChat = {
|
const gruvboxColors = [
|
||||||
connect(socket, postId) {
|
"#b8bb26",
|
||||||
let channel = socket.channel(`post:${postId}`)
|
"#fabd2f",
|
||||||
|
"#83a598",
|
||||||
|
"#d3869b",
|
||||||
|
"#8ec07c",
|
||||||
|
"#458588",
|
||||||
|
"#cc241d",
|
||||||
|
"#d65d0e",
|
||||||
|
"#bdae93",
|
||||||
|
];
|
||||||
|
const generateGruvboxFromString = (string) =>
|
||||||
|
gruvboxColors[Array.from(string).map((x) => x.charCodeAt(0)).reduce((a, x) => a+x, 0) % gruvboxColors.length];
|
||||||
|
|
||||||
|
const RoomChat = (() => {
|
||||||
|
let channel;
|
||||||
|
const connect = (socket, postId) => {
|
||||||
|
channel = socket.channel(`post:${postId}`);
|
||||||
channel.join()
|
channel.join()
|
||||||
.receive("ok", resp => { console.log("Joined successfully: ", resp) })
|
.receive("ok", resp => { console.log("Joined successfully: ", resp); })
|
||||||
.receive("error", resp => { console.log("Unable to join: ", resp) })
|
.receive("error", resp => { console.log("Unable to join: ", resp); });
|
||||||
return channel;
|
return channel;
|
||||||
},
|
};
|
||||||
}
|
|
||||||
|
const scrollToBottom = (element) => {
|
||||||
|
element.scrollTop = element.scrollHeight;
|
||||||
|
};
|
||||||
|
|
||||||
|
const appendComment = ({user, body, id, user_id, inserted_at}, element) => {
|
||||||
|
const messageElement = document.createElement("div");
|
||||||
|
messageElement.innerHTML = `
|
||||||
|
<div class="d-flex flex-row card border rounded m-2 align-items-center">
|
||||||
|
<div class="m-2">
|
||||||
|
<div class="circle" style="background:${generateGruvboxFromString(user)}">${user.charAt(0)}</div>
|
||||||
|
</div>
|
||||||
|
<div class="m-2">
|
||||||
|
<div class="comment">
|
||||||
|
<div class="comment-header">
|
||||||
|
<span class="comment-username">${user}</span>
|
||||||
|
<span class="text-muted">${new Date(inserted_at).toLocaleString()}</span>
|
||||||
|
</div>
|
||||||
|
<div class="comment-body">
|
||||||
|
${body}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
element.appendChild(messageElement);
|
||||||
|
scrollToBottom(element);
|
||||||
|
};
|
||||||
|
|
||||||
|
const leaveChannel = () => {
|
||||||
|
if (channel) {
|
||||||
|
channel.leave();
|
||||||
|
console.log(channel);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const main = (post_id) => {
|
||||||
|
leaveChannel();
|
||||||
|
const chatWindow = document.getElementById("chat");
|
||||||
|
window.userSocket.connect();
|
||||||
|
channel = connect(window.userSocket, post_id);
|
||||||
|
|
||||||
|
channel.on('shout', (comment) => {
|
||||||
|
appendComment(comment, chatWindow);
|
||||||
|
});
|
||||||
|
|
||||||
|
channel.on('initial-comments', ({comments}) => {
|
||||||
|
comments.forEach((comment) => {
|
||||||
|
appendComment(comment, chatWindow);
|
||||||
|
});
|
||||||
|
scrollToBottom(chatWindow);
|
||||||
|
});
|
||||||
|
|
||||||
|
channel.on('join', ({ user }) => {
|
||||||
|
const joinElement = document.createElement("div");
|
||||||
|
joinElement.innerHTML = `
|
||||||
|
<div class="m-2 card border rounded p-2 text-muted">
|
||||||
|
join: ${user}
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
chatWindow.appendChild(joinElement);
|
||||||
|
scrollToBottom(chatWindow);
|
||||||
|
});
|
||||||
|
|
||||||
|
channel.on('left', ({ user }) => {
|
||||||
|
console.log(user, "left");
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const submitForm = (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
let message = e.target.elements.message.value;
|
||||||
|
if (message) {
|
||||||
|
channel.push("send", {body: message});
|
||||||
|
e.target.elements.message.value = "";
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
return { main, submitForm };
|
||||||
|
})();
|
||||||
|
|
||||||
|
window.addEventListener('load', () => {
|
||||||
|
window.addEventListener('phx:initial-post', (e) => RoomChat.main(e.detail.id));
|
||||||
|
});
|
||||||
|
|
||||||
export default RoomChat;
|
export default RoomChat;
|
@ -24,11 +24,12 @@ defmodule AggieditWeb.PostChannel do
|
|||||||
|> Enum.map(fn c -> Aggiedit.Post.Comment.serialize(c) end)
|
|> Enum.map(fn c -> Aggiedit.Post.Comment.serialize(c) end)
|
||||||
push(socket, "initial-comments", %{:comments => comments})
|
push(socket, "initial-comments", %{:comments => comments})
|
||||||
|
|
||||||
|
broadcast!(socket, "join", %{user: socket.assigns.current_user.username})
|
||||||
{:noreply, socket}
|
{:noreply, socket}
|
||||||
end
|
end
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def handle_in("send", %{"body" => comment}=body, socket) do
|
def handle_in("send", %{"body" => comment}, socket) do
|
||||||
{:ok, comment} = Rooms.comment_post(socket.assigns.post, socket.assigns.current_user, comment)
|
{:ok, comment} = Rooms.comment_post(socket.assigns.post, socket.assigns.current_user, comment)
|
||||||
broadcast!(socket, "shout", Aggiedit.Post.Comment.serialize(comment))
|
broadcast!(socket, "shout", Aggiedit.Post.Comment.serialize(comment))
|
||||||
{:reply, :ok, socket}
|
{:reply, :ok, socket}
|
||||||
|
@ -39,7 +39,9 @@ defmodule AggieditWeb.PostLive.FormComponent do
|
|||||||
filename = "#{upload.uuid}.#{extension}"
|
filename = "#{upload.uuid}.#{extension}"
|
||||||
|
|
||||||
dest = Path.join("priv/static/uploads", filename)
|
dest = Path.join("priv/static/uploads", filename)
|
||||||
|
with :ok <- File.mkdir_p(Path.dirname(dest)) do
|
||||||
File.cp!(data.path, dest)
|
File.cp!(data.path, dest)
|
||||||
|
end
|
||||||
|
|
||||||
{:ok, upload} = Uploads.create_upload(%{
|
{:ok, upload} = Uploads.create_upload(%{
|
||||||
file: filename,
|
file: filename,
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
<div class="d-flex justify-content-center">
|
<div class="d-flex justify-content-center">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div>
|
<div>
|
||||||
@ -6,7 +5,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<%= if Ecto.assoc_loaded?(@post.upload) && !is_nil(@post.upload) do %>
|
<%= if Ecto.assoc_loaded?(@post.upload) && !is_nil(@post.upload) do %>
|
||||||
<img src={Routes.static_path(@socket, "/uploads/#{@post.upload.file}")} class="img-fluid"/>
|
<img src={Routes.static_path(@socket, "/uploads/#{@post.upload.file}")} class="img-fluid" style="max-height: 40vh"/>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
<div class="post-body">
|
<div class="post-body">
|
||||||
@ -16,21 +15,18 @@
|
|||||||
<span><%= live_patch "Edit", to: Routes.post_show_path(@socket, :edit, @room, @post), class: "button" %></span> |
|
<span><%= live_patch "Edit", to: Routes.post_show_path(@socket, :edit, @room, @post), class: "button" %></span> |
|
||||||
<% end %>
|
<% end %>
|
||||||
<span><%= live_redirect "Back", to: Routes.post_index_path(@socket, :index, @room) %></span>
|
<span><%= live_redirect "Back", to: Routes.post_index_path(@socket, :index, @room) %></span>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- chat container from https://www.bootdey.com/snippets/view/card-chat-list#html -->
|
<div class="border rounded p-2 m-2">
|
||||||
<div class="container content mt-2">
|
<div class="chat" id="chat">
|
||||||
<div class="row">
|
|
||||||
<div class="col-xl-6 col-lg-6 col-md-6 col-sm-12 col-12">
|
|
||||||
<div class="card">
|
|
||||||
<div class="card-header">Chat</div>
|
|
||||||
<div class="card-body chat-container">
|
|
||||||
<ul class="chat-list" id="chat">
|
|
||||||
</ul>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<form class="border rounded p-2 m-2" onsubmit="return RoomChat.submitForm(event)">
|
||||||
|
<div class="form-group m-2">
|
||||||
|
<label for="message">Message</label>
|
||||||
|
<input type="text" class="form-control" id="message" name="message" placeholder="Message">
|
||||||
</div>
|
</div>
|
||||||
|
<button type="submit" class="m-2 btn btn-primary">Submit</button>
|
||||||
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -47,45 +43,3 @@
|
|||||||
/>
|
/>
|
||||||
</.modal>
|
</.modal>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<script>
|
|
||||||
const scrollToBottom = (element) => {
|
|
||||||
element.scrollIntoView({ behavior: 'smooth', block: 'end' });
|
|
||||||
};
|
|
||||||
|
|
||||||
const appendComment = ({user, body, id, user_id, inserted_at}, element) => {
|
|
||||||
const messageElement = document.createElement("div");
|
|
||||||
messageElement.innerHTML = `
|
|
||||||
<li class="${user_id == <%= @current_user.id %> ? 'out' : 'in'}" id=${id}>
|
|
||||||
<div class="chat-body">
|
|
||||||
<div class="chat-message">
|
|
||||||
<h5>${user}</h5>
|
|
||||||
<p>${body}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
`;
|
|
||||||
element.appendChild(messageElement);
|
|
||||||
scrollToBottom(element);
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
let channel;
|
|
||||||
window.addEventListener('phx:initial-post', (e) => {
|
|
||||||
const chatWindow = document.getElementById("chat");
|
|
||||||
window.userSocket.connect();
|
|
||||||
channel = window.RoomChat.connect(window.userSocket, e.detail.id);
|
|
||||||
|
|
||||||
channel.on('shout', (comment) => {
|
|
||||||
appendComment(comment, chatWindow);
|
|
||||||
});
|
|
||||||
|
|
||||||
channel.on('initial-comments', ({comments}) => {
|
|
||||||
comments.forEach((comment) => {
|
|
||||||
appendComment(comment, chatWindow);
|
|
||||||
});
|
|
||||||
scrollToBottom(chatWindow);
|
|
||||||
});
|
|
||||||
channel.push("send", {body: "Hello!"});
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
@ -8,6 +8,7 @@
|
|||||||
<%= live_title_tag assigns[:page_title] || "Aggiedit" %>
|
<%= live_title_tag assigns[:page_title] || "Aggiedit" %>
|
||||||
<link phx-track-static rel="stylesheet" href={Routes.static_path(@conn, "/assets/app.css")}/>
|
<link phx-track-static rel="stylesheet" href={Routes.static_path(@conn, "/assets/app.css")}/>
|
||||||
<script phx-track-static type="text/javascript" src={Routes.static_path(@conn, "/assets/app.js")}></script>
|
<script phx-track-static type="text/javascript" src={Routes.static_path(@conn, "/assets/app.js")}></script>
|
||||||
|
<link rel="icon" type="image/x-icon" href={Routes.static_path(@conn, "/favicon.ico")}/>
|
||||||
|
|
||||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">
|
||||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-ka7Sk0Gln4gmtz2MlQnikT1wXgYsOg+OMhuP+IlRH9sENBO0LRn5q+8nbTov4+1p" crossorigin="anonymous"></script>
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-ka7Sk0Gln4gmtz2MlQnikT1wXgYsOg+OMhuP+IlRH9sENBO0LRn5q+8nbTov4+1p" crossorigin="anonymous"></script>
|
||||||
|
6
priv/static/cache_manifest.json
Normal file
6
priv/static/cache_manifest.json
Normal file
File diff suppressed because one or more lines are too long
BIN
priv/static/favicon.ico
Normal file
BIN
priv/static/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 15 KiB |
Loading…
Reference in New Issue
Block a user