Compare commits

...

7 Commits

Author SHA1 Message Date
Trevor Vallender 2170122805 Allow editing of stats 2024-06-27 14:16:17 +01:00
Trevor Vallender 1834854dc0 Add footer 2024-06-27 14:03:34 +01:00
Trevor Vallender e4a896a375 Show roll commands 2024-06-27 09:33:46 +01:00
Trevor Vallender efaf81dfcb Add multiple dice roll types to stats 2024-06-21 15:47:07 +01:00
Trevor Vallender ab5af6e19c Shadow on stats 2024-06-21 13:33:03 +01:00
Trevor Vallender 511f066ab0 Add min/max to stats 2024-06-21 12:46:12 +01:00
Trevor Vallender dd850813cb Prevent moving features beyond section bounds 2024-06-21 11:22:25 +01:00
27 changed files with 282 additions and 31 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 83 KiB

View File

@ -108,3 +108,30 @@
text-decoration: none;
}
}
.stat-value {
box-shadow: inset 10px 10px 15px var(--light-shadow-color), inset -10px -10px 15px var(--light-shadow-color);
position: relative;
}
.stat-min-value:empty, .stat-max-value:empty {
display: none;
}
.stat-min-value, .stat-max-value {
font-size: .3em;
background-color: var(--background-color);
color: var(--header-text-color);
position: absolute;
bottom: 0;
padding: .5em;
}
.stat-min-value {
left: 0;
border-radius: 0 1em 0 0;
}
.stat-max-value {
right: 0;
border-radius: 1em 0 0 0;
}

View File

@ -7,6 +7,7 @@
--header-height: 200px;
--shadow-color: #999;
--light-shadow-color: #CCC;
--secondary-text-color: #777;
--inset-bg-color: #eee;

View File

@ -54,6 +54,7 @@ form.stat-form {
display: block;
margin: 1em auto;
width: fit-content;
text-align: center;
}
.feature-box form.text-field-form {
@ -64,3 +65,19 @@ form.stat-form {
margin-bottom: 1em;
}
}
.inline {
display: flex;
padding: .5em;
align-items: center;
label {
min-width: fit-content;
}
input {
padding: .2em;
height: auto;
min-width: 5em;
font-size: 1em;
}
}

View File

@ -15,7 +15,7 @@ main {
max-width: 80vw;
display: flex;
flex-direction: column;
padding: 1em;
padding: 1em 1em 4em 1em;
margin: 0 auto;
min-height: calc(100vh - var(--header-height));
}
@ -129,6 +129,12 @@ hr {
background-color: var(--inset-bg-color);
}
.feature-box-icons {
display: flex;
gap: 1em;
flex-direction: row-reverse;
}
.feature-box h2 {
text-align: center;
}
@ -143,7 +149,36 @@ hr {
.roll-button {
border-radius: var(--border-radius);
scale: 1.4;
margin: .5em;
cursor: pointer;
box-shadow: 0 0 5px var(--shadow-color);
}
.roll-command-display {
min-width: fit-content;
font-size: .8em;
}
.roll-command-display:before {
content: "(";
}
.roll-command-display:after {
content: ")";
}
footer {
background-color: #111;
text-align: center;
padding: 1em;
margin: 0 auto;
font-size: .8em;
max-width: 80vw;
a:link, a:visited {
color: var(--header-text-color);
text-decoration: none;
}
img {
display: block;
margin: 0 auto;
}
}

View File

@ -26,6 +26,7 @@ class DiceRollsController < ApplicationController
params.require(:dice_roll).permit(
:rollable_type,
:rollable_id,
:dice_roll_type_id,
)
end
end

View File

@ -3,7 +3,7 @@
class StatsController < ApplicationController
before_action :set_section, only: [ :new, :create ]
before_action :set_character, only: [ :new, :create ]
before_action :set_stat, only: [ :show, :update, :destroy ]
before_action :set_stat, only: [ :show, :edit, :update, :destroy ]
def new
@stat = @section.stats.new
@ -21,9 +21,12 @@ class StatsController < ApplicationController
def show
end
def edit
end
def update
@editable = ActiveModel::Type::Boolean.new.cast(params[:editable])
@stat.update(stat_params)
@stat.update!(stat_params)
end
def destroy
@ -48,6 +51,8 @@ class StatsController < ApplicationController
params.require(:stat).permit(
:name,
:value,
:min_allowed,
:max_allowed,
:roll_command,
character_sheet_feature_attributes: [
:id, :featurable_id, :featurable_type, :character_sheet_section_id, :_destroy,

View File

@ -17,11 +17,7 @@ class CharacterSheetFeature < ApplicationRecord
def move(to:)
return if to == order_index
unless character_sheet_section.character_sheet_features.exists?(order_index: to)
update!(order_index: to)
return
end
return if to < character_sheet_section.lowest_order_index || to > character_sheet_section.highest_order_index
if to < order_index
character_sheet_section.character_sheet_features.where(order_index: to...order_index)

View File

@ -17,4 +17,12 @@ class CharacterSheetSection < ApplicationRecord
length: { maximum: 255 }
scope :top_level, -> { where.missing(:parent_section) }
def lowest_order_index
character_sheet_features.minimum(:order_index) || 1
end
def highest_order_index
character_sheet_features.maximum(:order_index) || 1
end
end

View File

@ -0,0 +1,20 @@
# frozen_string_literal: true
class DiceRollType < ApplicationRecord
belongs_to :rollable, polymorphic: true
validates :name, length: { maximum: 100 }
validates :roll_command, length: { maximum: 100 }
def roll(table)
roller = DiceRoller.new(roll_command, stat: rollable)
end
def value
rollable.value
end
def character
rollable.character
end
end

View File

@ -7,13 +7,20 @@ class Stat < ApplicationRecord
has_one :character, through: :character_sheet_section
has_many :dice_rolls, as: :rollable, dependent: :destroy
has_many :dice_roll_types, as: :rollable, dependent: :destroy
validates :name, presence: true,
length: { maximum: 100 }
validates :min_allowed, numericality: true, allow_nil: true
validates :max_allowed, numericality: true, allow_nil: true
validates :value, presence: true,
numericality: true
validate :validate_roll_command
def rollable?
roll_command.present?
end
def roll(table)
roller = DiceRoller.new(roll_command, stat: self)
result = roller.roll

View File

@ -42,5 +42,11 @@
<%= yield(:main) if content_for?(:main) %>
<%= yield unless content_for?(:main) %>
</main>
<footer>
<%= link_to "https://tablewarez.net", target: "_blank" do %>
<%= t(".made_by") %>
<%= image_tag "tablewarez.png", width: "300px" %>
<% end %>
</footer>
</body>
</html>

View File

@ -17,7 +17,11 @@
<% end %>
<% else %>
<%= link_to stat, data: { turbo_frame: :modal } do %>
<div class="stat-value"><%= stat.value %></div>
<div class="stat-value">
<%= stat.value %>
<div class="stat-min-value"><%= stat.min_allowed %></div>
<div class="stat-max-value"><%= stat.max_allowed %></div>
</div>
<% end %>
<% end %>
</div>

View File

@ -0,0 +1,19 @@
<%= turbo_frame_tag :modal do %>
<div class="feature-box ">
<%= icon_link_to "fa-close", table_character_character_sheet_sections_path(@stat.character.table, @stat.character),
class: "icon-link" %>
<h2><%= @stat.name %></h2>
<%= form_with model: @stat, data: { controller: "auto-update" } do |f| %>
<%= f.label :name %>
<%= f.text_field :name %>
<%= f.label :min_allowed, t(".min_allowed") %>
<%= f.number_field :min_allowed %>
<%= f.label :max_allowed, t(".max_allowed") %>
<%= f.number_field :max_allowed %>
<%= f.label :roll_command %>
<%= f.text_field :roll_command %>
<% end %>
<h4><%= t(".dice_roll_types") %></h4>
<p>TODO: Add additional roll types here</p>
</div>
<% end %>

View File

@ -1,16 +1,38 @@
<%= turbo_frame_tag :modal do %>
<div class="feature-box stat">
<%= icon_link_to "fa-close", table_character_character_sheet_sections_path(@stat.character.table, @stat.character),
class: "icon-link" %>
<div class="feature-box-icons">
<%= icon_link_to "fa-close", table_character_character_sheet_sections_path(@stat.character.table, @stat.character),
class: "icon-link" %>
<%= icon_link_to "fa-edit", edit_stat_path(@stat), class: "icon-link" %>
</div>
<h2><%= @stat.name %></h2>
<%= form_with model: @stat, class: "stat-form", data: { controller: "auto-update" } do |f| %>
<%= f.number_field :value %>
<%= f.number_field :value, min: @stat.min_allowed, max: @stat.max_allowed %>
<div class="inline">
<%= f.label :min_allowed, t(".min_allowed") %>
<%= f.number_field :min_allowed %>
<%= f.label :max_allowed, t(".max_allowed") %>
<%= f.number_field :max_allowed %>
</div>
<% end %>
<%= form_with model: DiceRoll.new, url: table_dice_rolls_path(@stat.character.table), class: "stat-form",
data: { controller: "dice-roll modal-closer", action: "modal-closer#closeModal" } do |f| %>
<%= f.hidden_field :rollable_type, value: "Stat" %>
<%= f.hidden_field :rollable_id, value: @stat.id %>
<%= f.submit "#{t(".roll")}".html_safe, class: "roll-button" %>
<% if @stat.rollable? %>
<%= form_with model: DiceRoll.new, url: table_dice_rolls_path(@stat.character.table), class: "stat-form",
data: { controller: "dice-roll modal-closer", action: "modal-closer#closeModal" } do |f| %>
<%= f.hidden_field :rollable_type, value: "Stat" %>
<%= f.hidden_field :rollable_id, value: @stat.id %>
<div class="inline">
<%= f.submit "#{t(".roll")}".html_safe, class: "roll-button" %>
<div class="roll-command-display"><%= @stat.roll_command %></div>
</div>
<% end %>
<% @stat.dice_roll_types.each do |dice_roll_type| %>
<%= form_with model: DiceRoll.new, url: table_dice_rolls_path(@stat.character.table), class: "stat-form",
data: { controller: "dice-roll modal-closer", action: "modal-closer#closeModal" } do |f| %>
<%= f.hidden_field :rollable_type, value: "DiceRollType" %>
<%= f.hidden_field :rollable_id, value: dice_roll_type.id %>
<%= f.submit "#{t(".roll_type_html", name: dice_roll_type.name, command: dice_roll_type.roll_command)}".html_safe, class: "roll-button" %>
<% end %>
<% end %>
<% end %>
</div>
<% end %>

View File

@ -18,6 +18,7 @@ en:
characters: Characters
profile: Profile
tables: Tables
made_by: Brought to you by
mailer:
greeting: "Hi %{name},"
greeting_without_name: Hi,
@ -140,6 +141,9 @@ en:
stats:
show:
roll: Roll!
roll_type_html: Roll %{name}! <br> <small>%{command}</small>
min_allowed: Min
max_allowed: Max
stat:
down: Move down
up: Move up
@ -147,6 +151,10 @@ en:
confirm_delete: Are you sure you want to delete %{name}?
new:
create_stat: Create stat
edit:
dice_roll_types: Other rolls
min_allowed: Minimum
max_allowed: Maximum
table_invite_mailer:
invite_new_user:
subject: Youve been invited to join a game on Tabletop Companion!

View File

@ -28,7 +28,7 @@ Rails.application.routes.draw do
resources :characters do
resources :character_sheet_sections, only: [ :index, :new, :create ]
end
resources :stats, only: [ :show, :update, :destroy ]
resources :stats, only: [ :show, :edit, :update, :destroy ]
resources :table_invites, only: [ :index, :edit, :update ]
resources :tables do
resources :characters, only: [] do

View File

@ -0,0 +1,8 @@
# frozen_string_literal: true
class AddMinMaxToStats < ActiveRecord::Migration[7.1]
def change
add_column :stats, :min_allowed, :integer
add_column :stats, :max_allowed, :integer
end
end

View File

@ -0,0 +1,16 @@
# frozen_string_literal: true
class CreateDiceRollTypes < ActiveRecord::Migration[7.1]
def change
create_table :dice_roll_types do |t|
t.string :name, null: false
t.string :roll_command, null: false
t.belongs_to :rollable, null: false, polymorphic: true
t.timestamps
end
add_check_constraint :dice_roll_types, "length(name) <= 100", name: "chk_name_max_length"
add_check_constraint :dice_roll_types, "length(roll_command) <= 100", name: "chk_roll_command_max_length"
end
end

16
db/schema.rb generated
View File

@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema[7.1].define(version: 2024_06_21_091420) do
ActiveRecord::Schema[7.1].define(version: 2024_06_21_141219) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@ -88,6 +88,18 @@ ActiveRecord::Schema[7.1].define(version: 2024_06_21_091420) do
t.check_constraint "length(name::text) <= 200", name: "chk_character_name_max_length"
end
create_table "dice_roll_types", force: :cascade do |t|
t.string "name", null: false
t.string "roll_command", null: false
t.string "rollable_type", null: false
t.bigint "rollable_id", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["rollable_type", "rollable_id"], name: "index_dice_roll_types_on_rollable"
t.check_constraint "length(name::text) <= 100", name: "chk_name_max_length"
t.check_constraint "length(roll_command::text) <= 100", name: "chk_roll_command_max_length"
end
create_table "dice_rolls", force: :cascade do |t|
t.string "rollable_type"
t.bigint "rollable_id"
@ -239,6 +251,8 @@ ActiveRecord::Schema[7.1].define(version: 2024_06_21_091420) do
t.string "roll_command"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "min_allowed"
t.integer "max_allowed"
t.check_constraint "length(name::text) <= 100", name: "chk_stat_name_max_length"
end

View File

@ -7,6 +7,18 @@ stats-strength:
featurable: strength (Stat)
order_index: 1
stats-dexterity:
<<: *DEFAULTS
character_sheet_section: stats
featurable: dexterity (Stat)
order_index: 2
stats-constitution:
<<: *DEFAULTS
character_sheet_section: stats
featurable: constitution (Stat)
order_index: 3
text_field:
<<: *DEFAULTS
character_sheet_section: info

View File

@ -10,3 +10,7 @@ subsection:
info:
name: Info
character: nardren
empty:
name: Empty
character: nardren

4
test/fixtures/dice_roll_types.yml vendored Normal file
View File

@ -0,0 +1,4 @@
strength_advantage:
name: with advantage
roll_command: d20+self+d20
rollable: strength (Stat)

View File

@ -2,3 +2,13 @@ strength:
name: Strength
value: 10
roll_command: d20+self
dexterity:
name: Dexterity
value: 10
roll_command: d20+self
constitution:
name: Constitution
value: 10
roll_command: d20+self

View File

@ -3,7 +3,4 @@
require "test_helper"
class CharacterSheetFeatureTest < ActiveSupport::TestCase
# test "the truth" do
# assert true
# end
end

View File

@ -6,4 +6,14 @@ class CharacterSheetSectionTest < ActiveSupport::TestCase
test "name must exist" do
assert_must_exist(character_sheet_sections(:stats), "name")
end
test "returns correct order index" do
assert_equal 3, character_sheet_sections(:stats).highest_order_index
assert_equal 1, character_sheet_sections(:stats).lowest_order_index
end
test "an empty section returns 1 for lowest/highest order index" do
assert_equal 1, character_sheet_sections(:empty).lowest_order_index
assert_equal 1, character_sheet_sections(:empty).highest_order_index
end
end

16
todo.md
View File

@ -1,14 +1,14 @@
- Edit dice roll command, types
- request invite
- don't move down/up top/bottom features
- Weapons/spells: skills?
- Do this have_many :stats?
- Lists
- make dice rolls own model so stats can have multiple rolls (e.g. advantage)
- improve dice roll parsing to allow e.g. choose highest
- Easy add amount to stat
- icons on features
- auto save text fields
- indicate save status on titlebar
- easily edit text fields without entering edit mode
- add roll command to counters (e.g. luck)
- test for rolls controller & system tests
- Show individual dice on dice roll
- Sheets
- Lists
- Weapons/spells
- Character avatars
- default avatars
- add uuid/slug to characters and any other url-visible ids