summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-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
-rw-r--r--config/routes.rb2
-rw-r--r--data/chat_rooms.yml18
-rw-r--r--db/migrate/20210520013553_create_chat_rooms.rb11
-rw-r--r--db/migrate/20210520014637_create_chat_messages.rb12
-rw-r--r--db/schema.rb25
-rw-r--r--db/seeds.rb5
-rw-r--r--test/fixtures/chat_messages.yml7
-rw-r--r--test/fixtures/chat_rooms.yml9
-rw-r--r--test/models/chat_message_test.rb7
-rw-r--r--test/models/chat_room_test.rb7
21 files changed, 318 insertions, 5 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>
diff --git a/config/routes.rb b/config/routes.rb
index f3096f7..8f0513e 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -11,6 +11,8 @@ Rails.application.routes.draw do
put "users", to: "devise/registrations#update", as: "user_registration"
end
+ resources :chat_messages, only: [:index, :create]
+
resources :activities, only: [:show]
resources :characters, only: [:show, :new, :create] do
diff --git a/data/chat_rooms.yml b/data/chat_rooms.yml
new file mode 100644
index 0000000..e1b2bb0
--- /dev/null
+++ b/data/chat_rooms.yml
@@ -0,0 +1,18 @@
+system:
+ name: "System"
+ permission_level: 5
+achievement:
+ name: "Achievement"
+ permission_level: 5
+news:
+ name: "News"
+ permission_level: 5
+cosmic:
+ name: "Cosmic"
+ permission_level: 1
+trade:
+ name: "Trade"
+ permission_level: 1
+help:
+ name: "Help"
+ permission_level: 1
diff --git a/db/migrate/20210520013553_create_chat_rooms.rb b/db/migrate/20210520013553_create_chat_rooms.rb
new file mode 100644
index 0000000..fadba64
--- /dev/null
+++ b/db/migrate/20210520013553_create_chat_rooms.rb
@@ -0,0 +1,11 @@
+class CreateChatRooms < ActiveRecord::Migration[6.1]
+ def change
+ create_table :chat_rooms do |t|
+ t.string :gid
+ t.string :name
+ t.integer :permission_level
+
+ t.timestamps
+ end
+ end
+end
diff --git a/db/migrate/20210520014637_create_chat_messages.rb b/db/migrate/20210520014637_create_chat_messages.rb
new file mode 100644
index 0000000..a020104
--- /dev/null
+++ b/db/migrate/20210520014637_create_chat_messages.rb
@@ -0,0 +1,12 @@
+class CreateChatMessages < ActiveRecord::Migration[6.1]
+ def change
+ create_table :chat_messages do |t|
+ t.references :chat_room, null: false, foreign_key: true
+ t.references :sender, foreign_key: { to_table: :characters }
+ t.references :target, foreign_key: { to_table: :characters }
+ t.text :body
+
+ t.timestamps
+ end
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 4f75f04..ee68bb9 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema.define(version: 2021_05_20_013124) do
+ActiveRecord::Schema.define(version: 2021_05_20_014637) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ -69,6 +69,26 @@ ActiveRecord::Schema.define(version: 2021_05_20_013124) do
t.index ["user_id"], name: "index_characters_on_user_id"
end
+ create_table "chat_messages", force: :cascade do |t|
+ t.bigint "chat_room_id", null: false
+ t.bigint "sender_id"
+ t.bigint "target_id"
+ t.text "body"
+ t.datetime "created_at", precision: 6, null: false
+ t.datetime "updated_at", precision: 6, null: false
+ t.index ["chat_room_id"], name: "index_chat_messages_on_chat_room_id"
+ t.index ["sender_id"], name: "index_chat_messages_on_sender_id"
+ t.index ["target_id"], name: "index_chat_messages_on_target_id"
+ end
+
+ create_table "chat_rooms", force: :cascade do |t|
+ t.string "gid"
+ t.string "name"
+ t.integer "permission_level"
+ t.datetime "created_at", precision: 6, null: false
+ t.datetime "updated_at", precision: 6, null: false
+ end
+
create_table "hearth_amenities", force: :cascade do |t|
t.string "gid"
t.string "name"
@@ -154,6 +174,9 @@ ActiveRecord::Schema.define(version: 2021_05_20_013124) do
add_foreign_key "character_skills", "skills"
add_foreign_key "characters", "activities"
add_foreign_key "characters", "users"
+ add_foreign_key "chat_messages", "characters", column: "sender_id"
+ add_foreign_key "chat_messages", "characters", column: "target_id"
+ add_foreign_key "chat_messages", "chat_rooms"
add_foreign_key "hearths", "characters"
add_foreign_key "users", "characters", column: "active_character_id"
end
diff --git a/db/seeds.rb b/db/seeds.rb
index cf4caa6..7adf0fc 100644
--- a/db/seeds.rb
+++ b/db/seeds.rb
@@ -10,6 +10,11 @@ def load_data_file(path)
YAML.load(File.read(path)).deep_transform_keys(&:to_sym)
end
+load_data_file("data/chat_rooms.yml").map do |gid, hash|
+ chat_room = ChatRoom.find_or_create_by(gid: gid)
+ chat_room.update(hash)
+end
+
load_data_file("data/skills.yml").map do |gid, hash|
skill = Skill.find_or_create_by(gid: gid)
skill.update(hash)
diff --git a/test/fixtures/chat_messages.yml b/test/fixtures/chat_messages.yml
new file mode 100644
index 0000000..0ce7cf8
--- /dev/null
+++ b/test/fixtures/chat_messages.yml
@@ -0,0 +1,7 @@
+# Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html
+
+one:
+ chat_room: one
+
+two:
+ chat_room: two
diff --git a/test/fixtures/chat_rooms.yml b/test/fixtures/chat_rooms.yml
new file mode 100644
index 0000000..8476a5b
--- /dev/null
+++ b/test/fixtures/chat_rooms.yml
@@ -0,0 +1,9 @@
+# Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html
+
+one:
+ name: MyString
+ permission_level: 1
+
+two:
+ name: MyString
+ permission_level: 1
diff --git a/test/models/chat_message_test.rb b/test/models/chat_message_test.rb
new file mode 100644
index 0000000..78428cf
--- /dev/null
+++ b/test/models/chat_message_test.rb
@@ -0,0 +1,7 @@
+require "test_helper"
+
+class ChatMessageTest < ActiveSupport::TestCase
+ # test "the truth" do
+ # assert true
+ # end
+end
diff --git a/test/models/chat_room_test.rb b/test/models/chat_room_test.rb
new file mode 100644
index 0000000..3451e5c
--- /dev/null
+++ b/test/models/chat_room_test.rb
@@ -0,0 +1,7 @@
+require "test_helper"
+
+class ChatRoomTest < ActiveSupport::TestCase
+ # test "the truth" do
+ # assert true
+ # end
+end