Compare commits

..

7 commits

Author SHA1 Message Date
fdbfa3c03f Fix styles in support form when field is errored
Ah right, `> label` doesn't work with how Rails will wrap broken labels
and inputs each in a `.field_with_errors` element. Fixed, and added
some basic coloring!
2024-11-30 11:50:31 -08:00
252f4f1df1 Add errors helper to support form builder
It still has no good CSS to it, but that's okay, this is just to DRY it
up.
2024-11-30 11:46:19 -08:00
2d3d4051fe Oops, return HTTP Bad Request when item editing fails
Just a subtle thing, but Turbo can be picky about return types, and
won't reload the page with the errors in it if the status is 200.
2024-11-30 11:45:35 -08:00
3cd02baa09 Add thumbnail_input method to support form builder
Just to clean up this relatively common input type!
2024-11-30 11:34:02 -08:00
8347633a84 Add SupportFormBuilder to make the support form templates nicer
Instead of hand-rolling HTML, this offers helpers like `f.field`, to
help ensure the HTML is consistent, and to keep the templates more
focused on the unique form elements.
2024-11-30 11:26:23 -08:00
661a5385f4 Migrate pet state edit form to .support-form class
Most notable change here is extracting the pose option bubbles into a
`data-type="radio-grid"`, and pulling that into the `.support-form`
CSS. My rationale is that, unlike most fields, this field benefits from
being 100%-width, and I don't want to specify that as an override if I
can avoid it, because that's fragile-y.

Instead, I extract this into a generic type of field that
`.support-form` can use (it feels pretty reusable anyway!), and require
the caller to specify how many columns they want as `--num-columns`.
2024-11-30 10:49:47 -08:00
c27477fabe Refactor support-form CSS to be more reusable layout
Specifically, I'm going for a more-vertical layout, cuz I want to bring
PetState over to it, and the weird grid situation wasn't gonna fit the
big pose label radios.
2024-11-30 10:33:58 -08:00
8 changed files with 192 additions and 109 deletions

View file

@ -1,30 +1,70 @@
@import "../partials/clean/constants"
.support-form .support-form
display: flex display: flex
flex-direction: column flex-direction: column
gap: 1em gap: 1em
align-items: flex-start align-items: flex-start
fieldset .fields
list-style-type: none
display: flex
flex-direction: column
gap: .75em
width: 100% width: 100%
display: grid
grid-template-columns: auto 1fr
align-items: center
gap: 1em
> *:nth-child(2n) > li
width: 40rch display: flex
max-width: 100% flex-direction: column
box-sizing: border-box gap: .25em
max-width: 60ch
input[type=url] > label, > .field_with_errors label
font-size: .85em display: block
font-weight: bold
> label, .field-name .field_with_errors
font-weight: bold > label
&:has(+ .radio-field) color: $error-color
align-self: start
.thumbnail-field input[type=text], input[type=url]
border-color: $error-border-color
color: $error-color
&[data-type=radio]
ul
list-style-type: none
&[data-type=radio-grid] // Set the `--num-columns` property to configure!
max-width: none
ul
list-style-type: none
display: grid
grid-template-columns: repeat(var(--num-columns, 1), 1fr)
gap: .25em
label
display: flex
align-items: center
gap: .5em
padding: .5em 1em
border: 1px solid $soft-border-color
border-radius: 1em
input
margin: 0
&:has(:checked)
background: $module-bg-color
border-color: $module-border-color
input[type=text], input[type=url]
width: 100%
min-width: 10ch
box-sizing: border-box
.thumbnail-input
display: flex display: flex
align-items: center align-items: center
gap: .25em gap: .25em
@ -33,14 +73,14 @@
width: 40px width: 40px
height: 40px height: 40px
input fieldset
flex: 1 0 20ch
.radio-field
display: flex display: flex
flex-direction: column flex-direction: column
gap: .25em gap: .25em
legend
font-weight: bold
.field_with_errors .field_with_errors
display: contents display: contents

View file

@ -1,25 +1,5 @@
@import "../partials/clean/constants"
outfit-viewer outfit-viewer
margin: 0 auto margin: 0 auto
.pose-options .fields li[data-type=radio-grid]
list-style-type: none --num-columns: 3
display: grid
grid-template-columns: 1fr 1fr 1fr
gap: .25em
label
display: flex
align-items: center
gap: .5em
padding: .5em 1em
border: 1px solid $soft-border-color
border-radius: 1em
input
margin: 0
&:has(:checked)
background: $module-bg-color
border-color: $module-border-color

View file

@ -124,7 +124,7 @@ class ItemsController < ApplicationController
flash[:notice] = "\"#{@item.name}\" successfully saved!" flash[:notice] = "\"#{@item.name}\" successfully saved!"
redirect_to @item redirect_to @item
else else
render action: "edit", layout: "application" render action: "edit", layout: "application", status: :bad_request
end end
end end

View file

@ -0,0 +1,65 @@
module SupportFormHelper
class SupportFormBuilder < ActionView::Helpers::FormBuilder
attr_reader :template
delegate :concat, :content_tag, :image_tag, to: :template, private: true
def errors
return nil if object.errors.empty?
error_list = content_tag(:ul) do
object.errors.each do |error|
concat content_tag(:li, error.full_message)
end
end
content_tag(:p) do
concat "Could not save:"
concat error_list
end
end
def fields(&block)
content_tag(:ul, class: "fields", &block)
end
def field(**kwargs, &block)
content_tag(:li, **kwargs, &block)
end
def radio_fieldset(legend, **kwargs, &block)
kwargs.reverse_merge!("data-type": "radio")
field(**kwargs) do
content_tag(:fieldset) do
concat content_tag(:legend, legend)
concat content_tag(:ul, &block)
end
end
end
def radio_field(**kwargs, &block)
content_tag(:li) do
content_tag(:label, **kwargs, &block)
end
end
def radio_grid_fieldset(*args, &block)
radio_fieldset(*args, "data-type": "radio-grid", &block)
end
def thumbnail_input(method)
url = object.send(method)
content_tag(:div, class: "thumbnail-input") do
concat image_tag(url, alt: "Thumbnail") if url.present?
concat url_field(method)
end
end
end
def support_form_with(**kwargs, &block)
kwargs.merge!(
builder: SupportFormBuilder,
class: ["support-form", kwargs[:class]],
)
form_with(**kwargs, &block)
end
end

View file

@ -37,7 +37,7 @@ class AltStyle < ApplicationRecord
# `fits:<New?>-faerie-draik` intentionally will not work, and the canonical # `fits:<New?>-faerie-draik` intentionally will not work, and the canonical
# filter name will be `fits:alt-style-IDNUMBER`, instead. # filter name will be `fits:alt-style-IDNUMBER`, instead.
def series_name def series_name
real_series_name || "<New?>" real_series_name || AltStyle.placeholder_name
end end
def real_series_name=(new_series_name) def real_series_name=(new_series_name)
@ -97,6 +97,10 @@ class AltStyle < ApplicationRecord
thumbnail_url != DEFAULT_THUMBNAIL_URL thumbnail_url != DEFAULT_THUMBNAIL_URL
end end
def self.placeholder_name
"<New?>"
end
# For convenience in the console! # For convenience in the console!
def self.find_by_name(color_name, species_name) def self.find_by_name(color_name, species_name)
color = Color.find_by_name(color_name) color = Color.find_by_name(color_name)

View file

@ -13,21 +13,19 @@
= image_tag @alt_style.preview_image_url, class: "alt-style-preview" = image_tag @alt_style.preview_image_url, class: "alt-style-preview"
= form_with model: @alt_style, class: "support-form" do |f| = support_form_with model: @alt_style, class: "support-form" do |f|
- if @alt_style.errors.any? = f.errors
%p
Could not save: = f.fields do
%ul.errors = f.field do
- @alt_style.errors.each do |error| = f.label :real_series_name, "Series"
%li= error.full_message = f.text_field :real_series_name, autofocus: !@alt_style.real_series_name?,
%fieldset placeholder: AltStyle.placeholder_name
= f.label :real_series_name, "Series"
= f.text_field :real_series_name, autofocus: !@alt_style.real_series_name? = f.field do
= f.label :thumbnail_url, "Thumbnail" = f.label :thumbnail_url, "Thumbnail"
.thumbnail-field = f.thumbnail_input :thumbnail_url
- if @alt_style.thumbnail_url?
= image_tag @alt_style.thumbnail_url
= f.url_field :thumbnail_url
.actions .actions
= f.submit "Save changes" = f.submit "Save changes"
%label{title: "If checked, takes you to the next unlabeled pet style, if any. Useful for labeling in bulk!"} %label{title: "If checked, takes you to the next unlabeled pet style, if any. Useful for labeling in bulk!"}

View file

@ -8,50 +8,49 @@
you change something, but it doesn't match what we're seeing on Neopets.com, you change something, but it doesn't match what we're seeing on Neopets.com,
it will probably be reverted automatically when someone models it. it will probably be reverted automatically when someone models it.
= form_with model: @item, class: "support-form" do |f| = support_form_with model: @item, class: "support-form" do |f|
- if @item.errors.any? = f.errors
%p
Could not save: = f.fields do
%ul.errors = f.field do
- @item.errors.each do |error| = f.label :name
%li= error.full_message = f.text_field :name
%fieldset
= f.label :name = f.field do
= f.text_field :name = f.label :thumbnail_url, "Thumbnail"
= f.label :thumbnail_url, "Thumbnail" = f.thumbnail_input :thumbnail_url
.thumbnail-field
- if @item.thumbnail_url? = f.field do
= image_tag @item.thumbnail_url = f.label :description
= f.url_field :thumbnail_url = f.text_field :description
= f.label :description
= f.text_field :description = f.radio_fieldset "Item kind" do
.field-name Item kind = f.radio_field title: "NC items generally have a rarity value of 500.\nPaintbrush items generally contain a special message in the description." do
.radio-field
%label{title: "NC items generally have a rarity value of 500.\nPaintbrush items generally contain a special message in the description."}
= f.radio_button :is_manually_nc, false = f.radio_button :is_manually_nc, false
Automatic: Based on rarity and description Automatic: Based on rarity and description
%label{title: "Use this when Neopets releases an NC item, but labels the rarity as something other than 500, usually by mistake."} = f.radio_field title: "Use this when Neopets releases an NC item, but labels the rarity as something other than 500, usually by mistake." do
= f.radio_button :is_manually_nc, true = f.radio_button :is_manually_nc, true
Manually NC: From the NC Mall, but not r500 Manually NC: From the NC Mall, but not r500
.field-name Modeling status
.radio-field = f.radio_fieldset "Modeling status" do
%label{title: "If we fit two or more species of a standard color, assume we also fit the other standard-color pets that were released at the time.\nRepeat for special colors like Baby and Maraquan."} = f.radio_field title: "If we fit two or more species of a standard color, assume we also fit the other standard-color pets that were released at the time.\nRepeat for special colors like Baby and Maraquan." do
= f.radio_button :modeling_status_hint, "" = f.radio_button :modeling_status_hint, ""
Automatic: Fits 2+ species &rarr; Should fit all Automatic: Fits 2+ species &rarr; Should fit all
%label{title: "Use this when e.g. there simply is no Acara version of the item."} = f.radio_field title: "Use this when e.g. there simply is no Acara version of the item." do
= f.radio_button :modeling_status_hint, "done" = f.radio_button :modeling_status_hint, "done"
Done: Neopets.com is missing some models Done: Neopets.com is missing some models
%label{title: "Use this when e.g. this fits the Blue Vandagyre even though it's a Maraquan item.\nBehaves identically to Done, but helps us remember why we did this!"} = f.radio_field title: "Use this when e.g. this fits the Blue Vandagyre even though it's a Maraquan item.\nBehaves identically to Done, but helps us remember why we did this!" do
= f.radio_button :modeling_status_hint, "glitchy" = f.radio_button :modeling_status_hint, "glitchy"
Glitchy: Neopets.com has <em>too many</em> models Glitchy: Neopets.com has <em>too many</em> models
.field-name Body fit
.radio-field = f.radio_fieldset "Body fit" do
%label{title: "When an asset in a zone like Background is modeled, assume it fits all pets the same, and assign it body ID \#0.\nOtherwise, assume it fits only the kind of pet it was modeled on."} = f.radio_field title: "When an asset in a zone like Background is modeled, assume it fits all pets the same, and assign it body ID \#0.\nOtherwise, assume it fits only the kind of pet it was modeled on." do
= f.radio_button :explicitly_body_specific, false = f.radio_button :explicitly_body_specific, false
Automatic: Some zones fit all species Automatic: Some zones fit all species
%label{title: "Use this when an item uses a generally-universal zone like Static, but is body-specific regardless. \"Encased in Ice\" is one example.\nThis prevents these uncommon items from breaking every time they're modeled."} = f.radio_field title: "Use this when an item uses a generally-universal zone like Static, but is body-specific regardless. \"Encased in Ice\" is one example.\nThis prevents these uncommon items from breaking every time they're modeled." do
= f.radio_button :explicitly_body_specific, true = f.radio_button :explicitly_body_specific, true
Body-specific: Fits all species differently Body-specific: Fits all species differently
.actions .actions
= f.submit "Save changes" = f.submit "Save changes"

View file

@ -17,31 +17,28 @@
= outfit_viewer pet_state: @pet_state = outfit_viewer pet_state: @pet_state
= form_with model: [@pet_type, @pet_state] do |f| = support_form_with model: [@pet_type, @pet_state] do |f|
- if @pet_state.errors.any? = f.errors
%p
Could not save: = f.fields do
%ul.errors = f.radio_grid_fieldset "Pose" do
- @pet_state.errors.each do |error| - pose_options.each do |pose|
%li= error.full_message = f.radio_field do
%dl = f.radio_button :pose, pose
%dt Pose = pose_name(pose)
%dd
%ul.pose-options = f.field do
- pose_options.each do |pose| = f.label :glitched, "Gliched?"
%li
%label
= f.radio_button :pose, pose
= pose_name pose
%dt Glitched?
%dd
= f.select :glitched, [["✅ Not marked as Glitched", false], = f.select :glitched, [["✅ Not marked as Glitched", false],
["👾 Yes, it's bad news bonko'd", true]] ["👾 Yes, it's bad news bonko'd", true]]
= f.submit "Save"
.actions
= f.submit "Save changes"
- content_for :stylesheets do - content_for :stylesheets do
= stylesheet_link_tag "application/breadcrumbs" = stylesheet_link_tag "application/breadcrumbs"
= stylesheet_link_tag "application/outfit-viewer" = stylesheet_link_tag "application/outfit-viewer"
= stylesheet_link_tag "application/support-form"
= page_stylesheet_link_tag "pet_states/edit" = page_stylesheet_link_tag "pet_states/edit"
- content_for :javascripts do - content_for :javascripts do