Compare commits
2 Commits
9fe186a08d
...
1afecfd781
Author | SHA1 | Date |
---|---|---|
Trevor Vallender | 1afecfd781 | |
Trevor Vallender | 701d69fcc9 |
|
@ -29,3 +29,23 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.character-sheet-section.top-level {
|
||||||
|
background-color: var(--inset-bg-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.character-sheet-section {
|
||||||
|
border: 1px solid var(--background-color);
|
||||||
|
border-radius: var(--border-radius);
|
||||||
|
margin-top: .3em;
|
||||||
|
h4 {
|
||||||
|
background-color: var(--background-color);
|
||||||
|
color: var(--header-text-color);
|
||||||
|
margin: 0;
|
||||||
|
padding: .5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
padding: 1em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,47 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class CharacterSheetSectionsController < ApplicationController
|
||||||
|
before_action :set_character, only: [ :index, :new, :create ]
|
||||||
|
before_action :set_section, only: [ :destroy ]
|
||||||
|
|
||||||
|
def index
|
||||||
|
@sections = @character.character_sheet_sections.top_level
|
||||||
|
end
|
||||||
|
|
||||||
|
def new
|
||||||
|
if params[:parent_section_id].present?
|
||||||
|
@parent_section = @character.character_sheet_sections.find_by(id: params[:parent_section_id])
|
||||||
|
end
|
||||||
|
@section = @character.character_sheet_sections.new
|
||||||
|
end
|
||||||
|
|
||||||
|
def create
|
||||||
|
@section = @character.character_sheet_sections.new(character_sheet_section_params)
|
||||||
|
unless @section.save
|
||||||
|
@parent_section = @section.parent_section
|
||||||
|
render :new, status: :unprocessable_entity
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def destroy
|
||||||
|
@id = helpers.dom_id(@section)
|
||||||
|
@section.destroy
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def set_character
|
||||||
|
@character = Current.user.characters.find(params[:character_id])
|
||||||
|
end
|
||||||
|
|
||||||
|
def set_section
|
||||||
|
@section = Current.user.character_sheet_sections.find(params[:id])
|
||||||
|
end
|
||||||
|
|
||||||
|
def character_sheet_section_params
|
||||||
|
params.require(:character_sheet_section).permit(
|
||||||
|
:name,
|
||||||
|
:parent_section_id,
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
|
@ -4,6 +4,7 @@ class Character < ApplicationRecord
|
||||||
belongs_to :table, optional: true
|
belongs_to :table, optional: true
|
||||||
belongs_to :game_system
|
belongs_to :game_system
|
||||||
belongs_to :user
|
belongs_to :user
|
||||||
|
has_many :character_sheet_sections, dependent: :destroy
|
||||||
|
|
||||||
validates :name, presence: true,
|
validates :name, presence: true,
|
||||||
length: { maximum: 200 }
|
length: { maximum: 200 }
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class CharacterSheetSection < ApplicationRecord
|
||||||
|
belongs_to :character
|
||||||
|
belongs_to :parent_section, optional: true, class_name: "CharacterSheetSection"
|
||||||
|
has_many :character_sheet_subsections, class_name: "CharacterSheetSection", foreign_key: :parent_section_id
|
||||||
|
|
||||||
|
validates :name, presence: true,
|
||||||
|
length: { maximum: 255 }
|
||||||
|
|
||||||
|
scope :top_level, -> { where.missing(:parent_section) }
|
||||||
|
end
|
|
@ -6,6 +6,7 @@ class User < ApplicationRecord
|
||||||
|
|
||||||
has_and_belongs_to_many :site_roles
|
has_and_belongs_to_many :site_roles
|
||||||
has_many :characters, dependent: :destroy
|
has_many :characters, dependent: :destroy
|
||||||
|
has_many :character_sheet_sections, through: :characters
|
||||||
has_many :owned_tables, foreign_key: :owner_id, class_name: "Table"
|
has_many :owned_tables, foreign_key: :owner_id, class_name: "Table"
|
||||||
has_many :players, dependent: :destroy
|
has_many :players, dependent: :destroy
|
||||||
has_many :tables, through: :players
|
has_many :tables, through: :players
|
||||||
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
<div
|
||||||
|
class="<%= class_names("character-sheet-section", { "top-level": character_sheet_section.parent_section.nil? }) %>"
|
||||||
|
id="<%= dom_id(character_sheet_section) %>">
|
||||||
|
<h4><%= character_sheet_section.name %></h4>
|
||||||
|
|
||||||
|
<div class="content">
|
||||||
|
<div id="<%= dom_id(character_sheet_section) %>_sections">
|
||||||
|
<%= render character_sheet_section.character_sheet_subsections %>
|
||||||
|
</div>
|
||||||
|
<div id="<%= dom_id(character_sheet_section) %>_add_section">
|
||||||
|
<%= render partial: "edit_links",
|
||||||
|
locals: { section: character_sheet_section, parent: character_sheet_section, id: nil } %>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
|
@ -0,0 +1,8 @@
|
||||||
|
<%# locals: (section:, parent:, id:) -%>
|
||||||
|
<%= link_to t(".add_subsection"),
|
||||||
|
new_character_character_sheet_section_path(@character, parent_section_id: parent&.id),
|
||||||
|
data: { turbo_stream: true } %>
|
||||||
|
<% unless id == "character_sheet_add_section" %>
|
||||||
|
<%= link_to t(".delete_section", name: section.name), character_sheet_section_path(section),
|
||||||
|
data: { turbo_method: :delete, turbo_confirm: t(".confirm_delete", name: section.name) } %>
|
||||||
|
<% end %>
|
|
@ -0,0 +1,11 @@
|
||||||
|
<section class="inset">
|
||||||
|
<%= form_with model: @section, url: character_character_sheet_sections_path(@character) do |f| %>
|
||||||
|
<%= f.hidden_field :parent_section_id, value: @parent_section&.id %>
|
||||||
|
|
||||||
|
<%= f.label :name %>
|
||||||
|
<%= f.text_field :name %>
|
||||||
|
<%= display_form_errors(@section, :name) %>
|
||||||
|
|
||||||
|
<%= f.submit button_text %>
|
||||||
|
<% end %>
|
||||||
|
</section>
|
|
@ -0,0 +1,15 @@
|
||||||
|
<% content_target = @section.parent_section.present? ? "#{dom_id(@section.parent_section)}_sections"
|
||||||
|
: "character_sheet" %>
|
||||||
|
<%= turbo_stream.append(content_target) do %>
|
||||||
|
<%= render @section %>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
|
<% form_target = @section.parent_section.present? ? "#{dom_id(@section.parent_section)}_add_section"
|
||||||
|
: "character_sheet_add_section" %>
|
||||||
|
<%= turbo_stream.replace(form_target) do %>
|
||||||
|
<% id = @section.parent_section.present? ? "#{dom_id(@section.parent_section)}_add_section"
|
||||||
|
: "character_sheet_add_section" %>
|
||||||
|
<div id=<%= id %>>
|
||||||
|
<%= render partial: "edit_links", locals: { section: @section, parent: @section, id: } %>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
|
@ -0,0 +1 @@
|
||||||
|
<%= turbo_stream.remove(@id) %>
|
|
@ -0,0 +1,16 @@
|
||||||
|
<% content_for :title, t(".character_sheet", name: @character.name) %>
|
||||||
|
|
||||||
|
<h2><%= @character.name %></h2>
|
||||||
|
|
||||||
|
<div id="character_sheet">
|
||||||
|
<% if @sections.any? %>
|
||||||
|
<%= render @sections %>
|
||||||
|
<% else %>
|
||||||
|
<p><%= t(".no_sections") %></p>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="character_sheet_add_section">
|
||||||
|
<%= link_to t(".add_section"), new_character_character_sheet_section_path(@character),
|
||||||
|
data: { turbo_stream: true } %>
|
||||||
|
</div>
|
|
@ -0,0 +1,6 @@
|
||||||
|
<% target = @parent_section.present? ? "#{dom_id(@parent_section)}_add_section" : "character_sheet_add_section" %>
|
||||||
|
<%= turbo_stream.replace(target) do %>
|
||||||
|
<div id="<%= target %>">
|
||||||
|
<%= render partial: "form", locals: { button_text: t(".create_section") } %>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
<h2><%= @character.name %></h2>
|
<h2><%= @character.name %></h2>
|
||||||
|
|
||||||
|
<%= link_to t(".sheet"), character_character_sheet_sections_path(@character) %>
|
||||||
<%= link_to t(".edit"), edit_character_path(@character) %>
|
<%= link_to t(".edit"), edit_character_path(@character) %>
|
||||||
|
|
||||||
<dl>
|
<dl>
|
||||||
|
|
|
@ -33,8 +33,42 @@
|
||||||
22
|
22
|
||||||
],
|
],
|
||||||
"note": ""
|
"note": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"warning_type": "Dynamic Render Path",
|
||||||
|
"warning_code": 15,
|
||||||
|
"fingerprint": "5e59ccbb20f360876317a1d7721a9b32bfae08b038943124e59179505c3325f8",
|
||||||
|
"check_name": "Render",
|
||||||
|
"message": "Render path contains parameter value",
|
||||||
|
"file": "app/views/character_sheet_sections/index.html.erb",
|
||||||
|
"line": 7,
|
||||||
|
"link": "https://brakemanscanner.org/docs/warning_types/dynamic_render_path/",
|
||||||
|
"code": "render(action => Current.user.characters.find(params[:character_id]).character_sheet_sections.top_level, {})",
|
||||||
|
"render_path": [
|
||||||
|
{
|
||||||
|
"type": "controller",
|
||||||
|
"class": "CharacterSheetSectionsController",
|
||||||
|
"method": "index",
|
||||||
|
"line": 9,
|
||||||
|
"file": "app/controllers/character_sheet_sections_controller.rb",
|
||||||
|
"rendered": {
|
||||||
|
"name": "character_sheet_sections/index",
|
||||||
|
"file": "app/views/character_sheet_sections/index.html.erb"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"location": {
|
||||||
|
"type": "template",
|
||||||
|
"template": "character_sheet_sections/index"
|
||||||
|
},
|
||||||
|
"user_input": "params[:character_id]",
|
||||||
|
"confidence": "Weak",
|
||||||
|
"cwe_id": [
|
||||||
|
22
|
||||||
|
],
|
||||||
|
"note": ""
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"updated": "2024-06-05 18:49:27 +0100",
|
"updated": "2024-06-06 14:18:01 +0100",
|
||||||
"brakeman_version": "6.1.2"
|
"brakeman_version": "6.1.2"
|
||||||
}
|
}
|
||||||
|
|
|
@ -55,6 +55,17 @@ en:
|
||||||
destroy:
|
destroy:
|
||||||
success: Successfully deleted “%{name}”.
|
success: Successfully deleted “%{name}”.
|
||||||
error: “%{name}” could not be deleted.
|
error: “%{name}” could not be deleted.
|
||||||
|
character_sheet_sections:
|
||||||
|
edit_links:
|
||||||
|
add_subsection: Add subsection
|
||||||
|
delete_section: Delete %{name}
|
||||||
|
confirm_delete: Are you sure you want to delete this section?
|
||||||
|
index:
|
||||||
|
character_sheet: "%{name}’s character sheet"
|
||||||
|
no_sections: This character sheet has no content
|
||||||
|
add_section: Add section
|
||||||
|
new:
|
||||||
|
create_section: Create section
|
||||||
characters:
|
characters:
|
||||||
index:
|
index:
|
||||||
my_characters: My characters
|
my_characters: My characters
|
||||||
|
@ -67,6 +78,7 @@ en:
|
||||||
success: “%{name}” has been created.
|
success: “%{name}” has been created.
|
||||||
error: Your character could not be created.
|
error: Your character could not be created.
|
||||||
show:
|
show:
|
||||||
|
sheet: Character sheet
|
||||||
edit: Edit character
|
edit: Edit character
|
||||||
owner: Player
|
owner: Player
|
||||||
game_system: Game system
|
game_system: Game system
|
||||||
|
|
|
@ -15,7 +15,10 @@ Rails.application.routes.draw do
|
||||||
resources :password_resets, only: [ :new, :create, :edit, :update ]
|
resources :password_resets, only: [ :new, :create, :edit, :update ]
|
||||||
resources :sessions, only: [ :new, :create, :destroy ]
|
resources :sessions, only: [ :new, :create, :destroy ]
|
||||||
|
|
||||||
resources :characters
|
resources :character_sheet_sections, only: [ :destroy ]
|
||||||
|
resources :characters do
|
||||||
|
resources :character_sheet_sections, only: [ :index, :new, :create ]
|
||||||
|
end
|
||||||
resources :table_invites, only: [ :index, :edit, :update ]
|
resources :table_invites, only: [ :index, :edit, :update ]
|
||||||
resources :tables do
|
resources :tables do
|
||||||
resources :table_invites, only: [ :new, :create ]
|
resources :table_invites, only: [ :new, :create ]
|
||||||
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class CreateCharacterSheetSections < ActiveRecord::Migration[7.1]
|
||||||
|
def change
|
||||||
|
create_table :character_sheet_sections do |t|
|
||||||
|
t.string :name, null: false
|
||||||
|
t.belongs_to :character, null: false, foreign_key: true
|
||||||
|
t.references :parent_section, null: true, foreign_key: { to_table: :character_sheet_sections }
|
||||||
|
|
||||||
|
t.timestamps
|
||||||
|
end
|
||||||
|
|
||||||
|
add_check_constraint :character_sheet_sections, "length(name) <= 255", name: "chk_character_sheet_section_name_max_length"
|
||||||
|
end
|
||||||
|
end
|
|
@ -10,7 +10,7 @@
|
||||||
#
|
#
|
||||||
# It's strongly recommended that you check this file into your version control system.
|
# It's strongly recommended that you check this file into your version control system.
|
||||||
|
|
||||||
ActiveRecord::Schema[7.1].define(version: 2024_06_05_143732) do
|
ActiveRecord::Schema[7.1].define(version: 2024_06_05_175553) do
|
||||||
# These are extensions that must be enabled in order to support this database
|
# These are extensions that must be enabled in order to support this database
|
||||||
enable_extension "plpgsql"
|
enable_extension "plpgsql"
|
||||||
|
|
||||||
|
@ -52,6 +52,17 @@ ActiveRecord::Schema[7.1].define(version: 2024_06_05_143732) do
|
||||||
t.index ["blob_id", "variation_digest"], name: "index_active_storage_variant_records_uniqueness", unique: true
|
t.index ["blob_id", "variation_digest"], name: "index_active_storage_variant_records_uniqueness", unique: true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
create_table "character_sheet_sections", force: :cascade do |t|
|
||||||
|
t.string "name", null: false
|
||||||
|
t.bigint "character_id", null: false
|
||||||
|
t.bigint "parent_section_id"
|
||||||
|
t.datetime "created_at", null: false
|
||||||
|
t.datetime "updated_at", null: false
|
||||||
|
t.index ["character_id"], name: "index_character_sheet_sections_on_character_id"
|
||||||
|
t.index ["parent_section_id"], name: "index_character_sheet_sections_on_parent_section_id"
|
||||||
|
t.check_constraint "length(name::text) <= 255", name: "chk_character_sheet_section_name_max_length"
|
||||||
|
end
|
||||||
|
|
||||||
create_table "characters", force: :cascade do |t|
|
create_table "characters", force: :cascade do |t|
|
||||||
t.string "name", null: false
|
t.string "name", null: false
|
||||||
t.bigint "table_id"
|
t.bigint "table_id"
|
||||||
|
@ -245,6 +256,8 @@ ActiveRecord::Schema[7.1].define(version: 2024_06_05_143732) do
|
||||||
|
|
||||||
add_foreign_key "active_storage_attachments", "active_storage_blobs", column: "blob_id"
|
add_foreign_key "active_storage_attachments", "active_storage_blobs", column: "blob_id"
|
||||||
add_foreign_key "active_storage_variant_records", "active_storage_blobs", column: "blob_id"
|
add_foreign_key "active_storage_variant_records", "active_storage_blobs", column: "blob_id"
|
||||||
|
add_foreign_key "character_sheet_sections", "character_sheet_sections", column: "parent_section_id"
|
||||||
|
add_foreign_key "character_sheet_sections", "characters"
|
||||||
add_foreign_key "characters", "game_systems"
|
add_foreign_key "characters", "game_systems"
|
||||||
add_foreign_key "characters", "tables"
|
add_foreign_key "characters", "tables"
|
||||||
add_foreign_key "characters", "users"
|
add_foreign_key "characters", "users"
|
||||||
|
|
|
@ -6,4 +6,11 @@ class ApplicationSystemTestCase < ActionDispatch::SystemTestCase
|
||||||
driven_by :selenium,
|
driven_by :selenium,
|
||||||
using: :headless_firefox,
|
using: :headless_firefox,
|
||||||
screen_size: [ 1400, 1400 ]
|
screen_size: [ 1400, 1400 ]
|
||||||
|
|
||||||
|
def system_sign_in(user, password: "password")
|
||||||
|
visit new_session_url
|
||||||
|
fill_in attr_name(User, :username), with: users(:trevor).username
|
||||||
|
fill_in attr_name(User, :password), with: "password"
|
||||||
|
click_button I18n.t("sessions.new.log_in")
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -0,0 +1,36 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require "test_helper"
|
||||||
|
|
||||||
|
class CharacterSheetSectionsControllerTest < ActionDispatch::IntegrationTest
|
||||||
|
test "should get index" do
|
||||||
|
sign_in users(:trevor)
|
||||||
|
|
||||||
|
get character_character_sheet_sections_url(characters(:nardren))
|
||||||
|
assert_response :success
|
||||||
|
end
|
||||||
|
|
||||||
|
test "should render new turbo_stream" do
|
||||||
|
sign_in users(:trevor)
|
||||||
|
get new_character_character_sheet_section_url(characters(:nardren)), as: :turbo_stream
|
||||||
|
assert_response :success
|
||||||
|
end
|
||||||
|
|
||||||
|
test "should create character_sheet_section" do
|
||||||
|
sign_in users(:trevor)
|
||||||
|
assert_difference "CharacterSheetSection.count", 1 do
|
||||||
|
post character_character_sheet_sections_url(characters(:nardren)),
|
||||||
|
params: { character_sheet_section: { name: "test" } },
|
||||||
|
as: :turbo_stream
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
test "should destroy character_sheet_section" do
|
||||||
|
user = users(:trevor)
|
||||||
|
sign_in user
|
||||||
|
assert_difference "CharacterSheetSection.count", -1 do
|
||||||
|
delete character_sheet_section_url(user.character_sheet_sections.first),
|
||||||
|
as: :turbo_stream
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,3 @@
|
||||||
|
stats:
|
||||||
|
name: Stats
|
||||||
|
character: nardren
|
|
@ -0,0 +1,9 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require "test_helper"
|
||||||
|
|
||||||
|
class CharacterSheetSectionTest < ActiveSupport::TestCase
|
||||||
|
test "name must exist" do
|
||||||
|
assert_must_exist(character_sheet_sections(:stats), "name")
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,23 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require "application_system_test_case"
|
||||||
|
|
||||||
|
class CharacterSheetTest < ApplicationSystemTestCase
|
||||||
|
test "can add and remove sections on a character sheet" do
|
||||||
|
system_sign_in users(:trevor)
|
||||||
|
|
||||||
|
character = characters(:nardren)
|
||||||
|
visit character_path(characters)
|
||||||
|
click_on I18n.t("characters.show.sheet")
|
||||||
|
assert_text character.name
|
||||||
|
|
||||||
|
click_link(I18n.t("character_sheet_sections.index.add_section"))
|
||||||
|
fill_in attr_name(CharacterSheetSection, :name), with: "Test Section"
|
||||||
|
click_button(I18n.t("character_sheet_sections.new.create_section"))
|
||||||
|
assert_text "Test Section"
|
||||||
|
|
||||||
|
click_link(I18n.t("character_sheet_sections.edit_links.delete_section", name: "Test Section"))
|
||||||
|
accept_confirm
|
||||||
|
assert_no_text "Test Section"
|
||||||
|
end
|
||||||
|
end
|
1
todo.md
1
todo.md
|
@ -1,4 +1,5 @@
|
||||||
- default avatars
|
- default avatars
|
||||||
|
- add uuid/slug to characters and any other url-visible ids
|
||||||
- shared/private notes
|
- shared/private notes
|
||||||
- Add characters to users/tables
|
- Add characters to users/tables
|
||||||
- Character sheets/prototypes
|
- Character sheets/prototypes
|
||||||
|
|
Loading…
Reference in New Issue