summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
authorDavid Gay <david@davidgay.org>2021-05-19 22:53:38 -0400
committerDavid Gay <david@davidgay.org>2021-05-19 22:53:38 -0400
commit9415011b5fd192f1bafeaa9b6eacbb7921382a00 (patch)
treef25d9d633237cae5d7b73166e6612a9b53312714 /app
parentda678b22b5db05554b44234b341fabc9d83ff700 (diff)
Chat
Diffstat (limited to 'app')
-rw-r--r--app/channels/chat_room_channel.rb17
-rw-r--r--app/controllers/chat_messages_controller.rb32
-rw-r--r--app/javascript/channels/chat_room_channel.js22
-rw-r--r--app/javascript/controllers/chat_controller.js42
-rw-r--r--app/models/character.rb1
-rw-r--r--app/models/chat_message.rb15
-rw-r--r--app/models/chat_room.rb27
-rw-r--r--app/views/application/_chat.html.erb13
-rw-r--r--app/views/chat_messages/_list.html.erb3
-rw-r--r--app/views/chat_messages/_message.html.erb47
-rw-r--r--app/views/layouts/application.html.erb1
11 files changed, 216 insertions, 4 deletions
diff --git a/app/channels/chat_room_channel.rb b/app/channels/chat_room_channel.rb
new file mode 100644
index 0000000..1e14b35
--- /dev/null
+++ b/app/channels/chat_room_channel.rb
@@ -0,0 +1,17 @@
+class ChatRoomChannel < ApplicationCable::Channel
+ def self.broadcast_chat_message(chat_message)
+ ActionCable.server.broadcast "chat_room_channel",
+ html: ApplicationController.render(partial: "chat_messages/message",
+ locals: {
+ chat: chat_message
+ })
+ end
+
+ def subscribed
+ stream_from "chat_room_channel"
+ end
+
+ def unsubscribed
+ # Any cleanup needed when channel is unsubscribed
+ end
+end
diff --git a/app/controllers/chat_messages_controller.rb b/app/controllers/chat_messages_controller.rb
new file mode 100644
index 0000000..9112bb7
--- /dev/null
+++ b/app/controllers/chat_messages_controller.rb
@@ -0,0 +1,32 @@
+class ChatMessagesController < ApplicationController
+ def index
+ # TODO: Let's rename this method to #list
+ @chat_messages = ChatMessage.order(created_at: :desc).limit(100).reverse
+ render partial: "chat_messages/list"
+ end
+
+ def create
+ @chat_message = ChatMessage.new(body: chat_message_params[:body],
+ chat_room_id: chat_message_params[:chat_room_id],
+ sender: current_char)
+ # TODO: Make this block less bad
+ respond_to do |format|
+ if @chat_message.save
+ ActionCable.server.broadcast "chat_room_channel",
+ html: render_to_string(partial: "message",
+ locals: {
+ chat_message: @chat_message
+ })
+ format.html { redirect_to character_path(current_char) }
+ format.js
+ else
+ format.html { redirect_to character_path(current_char), alert: "Failed to send chat message." }
+ end
+ end
+ end
+
+ private
+ def chat_message_params
+ params.require(:chat_message).permit(:body, :chat_room_id)
+ end
+end
diff --git a/app/javascript/channels/chat_room_channel.js b/app/javascript/channels/chat_room_channel.js
new file mode 100644
index 0000000..8dfa47f
--- /dev/null
+++ b/app/javascript/channels/chat_room_channel.js
@@ -0,0 +1,22 @@
+import consumer from "./consumer"
+
+consumer.subscriptions.create("ChatRoomChannel", {
+ connected() {
+ // Called when the subscription is ready for use on the server
+ },
+
+ disconnected() {
+ // Called when the subscription has been terminated by the server
+ },
+
+ received(data) {
+ // Called when there's incoming data on the websocket for this channel
+ var node = document.createElement("P");
+ node.innerHTML = data.html;
+ var chatOutputElement = document.getElementById("chat_output");
+ chatOutputElement.appendChild(node);
+ chatOutputElement.scrollTo({
+ top: chatOutputElement.scrollHeight, left: 0, behavior: 'smooth'
+ });
+ }
+});
diff --git a/app/javascript/controllers/chat_controller.js b/app/javascript/controllers/chat_controller.js
new file mode 100644
index 0000000..d9cf1cb
--- /dev/null
+++ b/app/javascript/controllers/chat_controller.js
@@ -0,0 +1,42 @@
+import { Controller } from "stimulus";
+
+export default class extends Controller {
+ static targets = [ "message", "output" ];
+
+ connect() {
+ this.load();
+ }
+
+ send() {
+ let vm = this;
+ // TODO: Temporary hack. Should just run this after default event.
+ setTimeout(function() {
+ vm.messageTarget.value = "";
+ vm.smoothScrollToBottom();
+ }, 100);
+ }
+
+ load() {
+ this.scrollToBottom();
+ if (this.outputTarget.innerHTML.trim() === "") {
+ fetch("/chat_messages")
+ .then(response => response.text())
+ .then(html => {
+ this.outputTarget.innerHTML = html;
+ this.scrollToBottom();
+ });
+ }
+ }
+
+ scrollToBottom() {
+ this.outputTarget.scrollTop = this.outputTarget.scrollHeight;
+ }
+
+ smoothScrollToBottom() {
+ this.outputTarget.scrollTo({
+ top: this.outputTarget.scrollHeight,
+ left: 0,
+ behavior: 'smooth'
+ });
+ }
+}
diff --git a/app/models/character.rb b/app/models/character.rb
index 55ea7ba..9a3569f 100644
--- a/app/models/character.rb
+++ b/app/models/character.rb
@@ -5,6 +5,7 @@ class Character < ApplicationRecord
has_many :character_items
has_many :items, through: :character_items
has_many :character_skills
+ has_many :chat_messages
validates :name, presence: true
validates_length_of :name, maximum: 15, message: "can't be longer than 15 characters"
validates_uniqueness_of :name, message: "is already being used"
diff --git a/app/models/chat_message.rb b/app/models/chat_message.rb
new file mode 100644
index 0000000..e1aae6c
--- /dev/null
+++ b/app/models/chat_message.rb
@@ -0,0 +1,15 @@
+class ChatMessage < ApplicationRecord
+ belongs_to :chat_room
+ belongs_to :sender, class_name: "Character", optional: true
+ belongs_to :target, class_name: "Character", optional: true
+
+ validates :body, length: { minimum: 1, maximum: 255 }
+ validate :sender_has_access
+
+ private
+ def sender_has_access
+ if sender && !sender.user.has_permission_or_higher?(self.chat_room.permission_level)
+ errors.add(:chat, "You don't have permission to send that chat.")
+ end
+ end
+end
diff --git a/app/models/chat_room.rb b/app/models/chat_room.rb
new file mode 100644
index 0000000..17e3f19
--- /dev/null
+++ b/app/models/chat_room.rb
@@ -0,0 +1,27 @@
+class ChatRoom < ApplicationRecord
+ has_many :chat_messages
+ validates :gid, :name, presence: true, uniqueness: true
+
+ attribute :permission_level, :integer, default: 1
+ enum permission_level: {
+ banned: 0,
+ player: 1,
+ helper: 2,
+ builder: 3,
+ admin: 4,
+ developer: 5,
+ owner: 6
+ }
+
+ def self.accessible_to(user)
+ all.select { |x| x.accessible_to?(user) }
+ end
+
+ def accessible_to?(user)
+ user.has_permission_or_higher?(self.permission_level)
+ end
+
+ def short_name
+ self.name[0..6]
+ end
+end
diff --git a/app/views/application/_chat.html.erb b/app/views/application/_chat.html.erb
index 868986b..8abe73c 100644
--- a/app/views/application/_chat.html.erb
+++ b/app/views/application/_chat.html.erb
@@ -1,8 +1,13 @@
-<div class="flex flex-col h-full text-sm">
- <div id="chat_output" class="overflow-auto flex-grow">
- Chat output will go here.
+<div data-controller="chat" class="flex flex-col h-full text-sm">
+ <div data-chat-target="output" id="chat_output" class="overflow-auto flex-grow">
</div>
<div class="flex-none">
- Chat input will go here.
+ <%= form_with model: ChatMessage.new, html: { autocomplete: "off" }, local: false,
+ data: { action: "chat#send" }, class: "flex" do |f| %>
+ <%= f.collection_select :chat_room_id, ChatRoom.accessible_to(current_char.user),
+ :id, :short_name, class: "flex-none" %>
+ <%= f.text_field :body, size: "1", maxlength: 255, required: true,
+ data: { chat_target: "message" }, class: "flex-grow inline-flex" %>
+ <% end %>
</div>
</div>
diff --git a/app/views/chat_messages/_list.html.erb b/app/views/chat_messages/_list.html.erb
new file mode 100644
index 0000000..8b12fd2
--- /dev/null
+++ b/app/views/chat_messages/_list.html.erb
@@ -0,0 +1,3 @@
+<% @chat_messages.each do |chat_message| %>
+ <%= render "message", chat_message: chat_message %>
+<% end %>
diff --git a/app/views/chat_messages/_message.html.erb b/app/views/chat_messages/_message.html.erb
new file mode 100644
index 0000000..e9663ec
--- /dev/null
+++ b/app/views/chat_messages/_message.html.erb
@@ -0,0 +1,47 @@
+<% chat_room_text_class = case chat_message.chat_room.gid
+ when "cosmic" then "text-gray-400"
+ when "trade" then "text-blue-400"
+ when "help" then "text-pink-300"
+ when "system" then "text-red-500"
+ when "achievement" then "text-purple-400"
+ when "news" then "text-yellow-400"
+ else "text-gray-400"
+ end
+
+ chat_room_prefix = case chat_message.chat_room.gid
+ when "cosmic" then "[C]"
+ when "trade" then "[T]"
+ when "help" then "[H]"
+ when "system" then "[S]"
+ when "achievement" then "[A]"
+ when "news" then "[N]"
+ else nil
+ end
+%>
+<p>
+ <span class="<%= chat_room_text_class %>">
+ <span class="text-xs"><%= chat_message.created_at.strftime("%H:%M") %></span>
+ <%= chat_room_prefix %>
+ </span>
+ <% if chat_message.chat_room.gid == "system" %>
+ <span class="text-glow <%= chat_room_text_class %>"><%= chat_message.body %></span>
+ <% elsif chat_message.chat_room.gid == "news" %>
+ <span class="<%= chat_room_text_class %>"><%= chat_message.body %></span>
+ <% elsif chat_message.chat_room.gid == "achievement" %>
+ <%#= render "components/text/title", title: chat_message.target.current_title %>
+ <span class="<%= chat_room_text_class %>">
+ <%# TODO: Sort out this subject/target stuff that I just half-blindly ported over from old Esoterra. %>
+ <%= chat_message.target&.name %> <%= chat_message.body %>
+ </span>
+ <% else %>
+ <% if chat_message.sender %>
+ <%#= render "components/text/title", title: chat_message.sender.current_title %>
+ <% end %>
+ <span class="<%= chat_room_text_class %>">
+ <% if chat_message.sender %>
+ <span class="font-bold"><%= chat_message.sender.name %>:</span>
+ <% end %>
+ <%= chat_message.body %>
+ </span>
+ <% end %>
+</p>
diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb
index 3fe6f28..7ae44a9 100644
--- a/app/views/layouts/application.html.erb
+++ b/app/views/layouts/application.html.erb
@@ -30,6 +30,7 @@
data-turbolinks-permanent>
<%= render "chat" %>
</div>
+ </div>
<% else %>
<div id="game_info_box" class="game-container-box side-box col-span-12 sm:col-span-4"
data-turbolinks-permanent>