Compare commits

..

10 commits

Author SHA1 Message Date
4bcc3aaebb Limit Rainbow Pool filter dropdown size
cuz the "Prismatic Pink: Nostalgic" stuff is gonna get pretty long if
we just do the default behavior of letting it grow to max content size!
2024-09-30 17:42:52 -07:00
5890e52e53 Use full name when showing Alt Styles in the list 2024-09-30 17:41:21 -07:00
dd8426fefd Paginate Alt Styles, sort by most recent first-seen date 2024-09-30 17:35:18 -07:00
2a9818b2d1 Add series name filter to Alt Styles filter form
Right now this just is Nostalgic, but I'll label the rest soon!
2024-09-30 17:34:31 -07:00
0b72b5568c Add edit form for Alt Styles, for Support staff only
We'll need this to fix up the series names and thumbnails for the new
prismatic styles!
2024-09-30 17:21:45 -07:00
86e1f31231 Only show *relevant* colors in Alt Styles filters
That is, there is no 8-bit alt style, so don't bother including it in
the filter form; same for most other colors.
2024-09-30 16:35:58 -07:00
a99fb3ec02 Use Rainbow Pool styles for Alt Styles lists 2024-09-30 16:24:43 -07:00
d11c18129d Refactor Rainbow Pool to use shared styles for the list elements
The lists of pet types and pet states had very similar styles, which I
mostly copy-pasted. Now that I want to use them for Alt Styles too, I'm
refactoring!
2024-09-30 16:21:47 -07:00
0958111341 Share styles between pet types and alt styles as "rainbow-pool" CSS 2024-09-30 16:10:26 -07:00
775baa250b Add filter form to alt styles page
Oh wow, alt styles are getting some real work! I'll improve both the
user-facing and Support-facing tooling, to better handle the complexity.
2024-09-30 16:06:22 -07:00
21 changed files with 220 additions and 99 deletions

View file

@ -74,7 +74,7 @@ $container_width: 800px
input, button, select, label
cursor: pointer
input[type=text], input[type=password], input[type=search], input[type=number], input[type=email], select, textarea
input[type=text], input[type=password], input[type=search], input[type=number], input[type=email], input[type=url], select, textarea
border-radius: 3px
background: #fff
border: 1px solid $input-border-color

View file

@ -1,18 +0,0 @@
body.alt_styles-index
.alt-styles-header
margin-top: 1em
margin-bottom: .5em
.alt-styles-list
list-style: none
display: flex
flex-wrap: wrap
gap: 1.5em
.alt-style
text-align: center
width: 80px
.alt-style-thumbnail
width: 80px
height: 80px

View file

@ -0,0 +1,43 @@
.alt-style-preview
width: 300px
height: 300px
margin: 0 auto
.alt-style-form
display: flex
flex-direction: column
gap: 1em
align-items: flex-start
fieldset
width: 100%
display: grid
grid-template-columns: auto 1fr
align-items: center
gap: 1em
> *:nth-child(2n)
width: 40rch
max-width: 100%
box-sizing: border-box
input[type=url]
font-size: .85em
label
font-weight: bold
.thumbnail-field
display: flex
align-items: center
gap: .25em
img
width: 40px
height: 40px
input
flex: 1 0 20ch
.field_with_errors
display: contents

View file

@ -0,0 +1,3 @@
.rainbow-pool-list
.name span
display: inline-block

View file

@ -8,7 +8,6 @@
@import partials/jquery.jgrowl
@import alt_styles/index
@import closet_hangers/index
@import closet_lists/form
@import neopets_page_import_tasks/new

View file

@ -1,6 +1,6 @@
@import "../partials/clean/constants"
.pet-filters
.rainbow-pool-filters
fieldset
display: flex
flex-direction: row
@ -12,19 +12,20 @@
display: contents
font-weight: bold
[role=navigation]
margin-block: .5em
text-align: center
select
width: 16ch
.pet-types
.rainbow-pool-list
list-style-type: none
display: flex
flex-wrap: wrap
justify-content: center
gap: .5em
--preview-base-width: 150px
> li
width: 150px
width: var(--preview-base-width)
max-width: calc(50% - .25em)
min-width: 150px
box-sizing: border-box
@ -40,7 +41,7 @@
outline: 1px solid $module-border-color
background: $module-bg-color
img
.preview
width: 100%
height: auto
aspect-ratio: 1 / 1
@ -53,3 +54,9 @@
margin: 0 auto
position: relative
z-index: 1
.rainbow-pool-pagination
margin-block: .5em
display: flex
justify-content: center
gap: 1em

View file

@ -1,43 +1,7 @@
@import "../partials/clean/constants"
.pet-states
list-style-type: none
display: flex
flex-wrap: wrap
justify-content: center
gap: .5em
> li
width: 200px
max-width: calc(50% - .25em)
min-width: 150px
box-sizing: border-box
text-align: center
a
display: block
border-radius: 1em
padding: .5em
background: white
text-decoration: none
&:hover
outline: 1px solid $module-border-color
background: $module-bg-color
outfit-viewer
width: 100%
height: auto
aspect-ratio: 1 / 1
position: relative
z-index: 0
margin-bottom: -1em
.name
background: inherit
padding: .25em .5em
border-radius: .5em
position: relative
z-index: 1
.rainbow-pool-list
--preview-base-width: 200px
.glitched
cursor: help

View file

@ -1,14 +1,28 @@
class AltStylesController < ApplicationController
before_action :support_staff_only, except: [:index]
def index
@alt_styles = AltStyle.includes(:species, :color, :swf_assets).
order(:species_id, :color_id)
@all_alt_styles = AltStyle.includes(:species, :color)
if params[:species_id]
@species = Species.find(params[:species_id])
@alt_styles = @alt_styles.merge(@species.alt_styles)
end
@all_colors = @all_alt_styles.map(&:color).uniq.sort_by(&:name)
@all_species = @all_alt_styles.map(&:species).uniq.sort_by(&:name)
# We're going to link to the HTML5 image URL, so make sure we have all the
@all_series_names = @all_alt_styles.map(&:series_name).uniq.sort
@all_color_names = @all_colors.map(&:human_name)
@all_species_names = @all_species.map(&:human_name)
@series_name = params[:series]
@color = find_color
@species = find_species
@alt_styles = @all_alt_styles.includes(:swf_assets).
by_creation_date.order(:color_id, :species_id, :series_name).
paginate(page: params[:page], per_page: 30)
@alt_styles.where!(series_name: @series_name) if @series_name.present?
@alt_styles.merge!(@color.alt_styles) if @color
@alt_styles.merge!(@species.alt_styles) if @species
# We're using the HTML5 image for our preview, so make sure we have all the
# manifests ready!
SwfAsset.preload_manifests @alt_styles.map(&:swf_assets).flatten
@ -30,4 +44,39 @@ class AltStylesController < ApplicationController
}
end
end
def edit
@alt_style = AltStyle.find params[:id]
end
def update
@alt_style = AltStyle.find params[:id]
if @alt_style.update(alt_style_params)
flash[:notice] = "\"#{@alt_style.full_name}\" successfully saved!"
redirect_to alt_styles_path
else
render action: :edit, status: :bad_request
end
end
protected
def alt_style_params
params.require(:alt_style).permit(:series_name, :thumbnail_url)
end
def find_color
if params[:color]
Color.find_by(name: params[:color])
end
end
def find_species
if params[:species_id]
Species.find_by(id: params[:species_id])
elsif params[:species]
Species.find_by(name: params[:species])
end
end
end

View file

@ -0,0 +1,9 @@
module AltStylesHelper
def view_or_edit_alt_style_url(alt_style)
if support_staff?
edit_alt_style_path alt_style
else
alt_style.preview_image_url
end
end
end

View file

@ -70,16 +70,11 @@ module OutfitsHelper
text_field_tag 'name', nil, options
end
def outfit_viewer(outfit_or_options)
outfit = if outfit_or_options.is_a? Hash
Outfit.new(outfit_or_options)
elsif outfit_or_options.is_a? Outfit
outfit_or_options
else
raise TypeError, "must be an outfit or hash of options to create one"
end
def outfit_viewer(outfit=nil, pet_state: nil, **html_options)
outfit = Outfit.new(pet_state:) if outfit.nil? && pet_state.present?
raise "outfit_viewer must have outfit or pet state" if outfit.nil?
render partial: "outfit_viewer", locals: {outfit:}
render partial: "outfit_viewer", locals: {outfit:, html_options:}
end
end

View file

@ -9,6 +9,8 @@ class AltStyle < ApplicationRecord
has_many :contributions, as: :contributed, inverse_of: :contributed
validates :body_id, presence: true
validates :series_name, presence: true, allow_nil: true
validates :thumbnail_url, presence: true
before_create :infer_series_name
before_create :infer_thumbnail_url
@ -18,12 +20,17 @@ class AltStyle < ApplicationRecord
species = Species.find_by_name!(species_name)
where(series_name:, color_id: color.id, species_id: species.id)
}
scope :by_creation_date, -> {
order("DATE(created_at) DESC")
}
def name
def pet_name
I18n.translate('pet_types.human_name', color_human_name: color.human_name,
species_human_name: species.human_name)
end
alias_method :name, :pet_name
# If the series_name hasn't yet been set manually by support staff, show the
# string "<New?>" instead. But it won't be searchable by that string—that is,
# `fits:<New?>-faerie-draik` intentionally will not work, and the canonical
@ -42,6 +49,10 @@ class AltStyle < ApplicationRecord
"#{series_name} #{color.human_name}"
end
def full_name
"#{series_name} #{name}"
end
def preview_image_url
swf_asset = swf_assets.first
return nil if swf_asset.nil?

View file

@ -1,5 +1,6 @@
class Color < ApplicationRecord
has_many :pet_types
has_many :alt_styles
scope :alphabetical, -> { order(:name) }
scope :basic, -> { where(basic: true) }

View file

@ -1,4 +1,6 @@
%li.alt-style
= link_to alt_style.preview_image_url do
= image_tag alt_style.thumbnail_url, class: 'alt-style-thumbnail'
.alt-style-name= alt_style.name
%li
= link_to view_or_edit_alt_style_url(alt_style) do
= image_tag alt_style.preview_image_url, class: "preview", loading: "lazy"
.name
%span= alt_style.series_name
%span= alt_style.pet_name

View file

@ -0,0 +1,35 @@
- title @alt_style.full_name
- use_responsive_design
%ol.breadcrumbs
%li= link_to "Alt Styles", alt_styles_path
%li
= link_to @alt_style.color.human_name,
alt_styles_path(color: @alt_style.color.human_name)
%li{"data-relation-to-prev": "sibling"}
= link_to @alt_style.species.human_name,
alt_styles_path(species: @alt_style.species.human_name)
%li= @alt_style.series_name
= image_tag @alt_style.preview_image_url, class: "alt-style-preview"
= form_with model: @alt_style, class: "alt-style-form" do |f|
- if @alt_style.errors.any?
%p
Could not save:
%ul.errors
- @alt_style.errors.each do |error|
%li= error.full_message
%fieldset
= f.label :series_name, "Series"
= f.text_field :series_name
= f.label :thumbnail_url, "Thumbnail"
.thumbnail-field
- if @alt_style.thumbnail_url?
= image_tag @alt_style.thumbnail_url
= f.url_field :thumbnail_url
= f.submit "Save changes"
- content_for :stylesheets do
= stylesheet_link_tag "application/breadcrumbs"
= page_stylesheet_link_tag "alt_styles/edit"

View file

@ -1,4 +1,5 @@
- title "Styling Studio"
- use_responsive_design
%p
Here's all the new NC Pet Styles we have! They're available in the app too,
@ -13,6 +14,24 @@
wearable items, there's not a great way for us to get style tokens onto
tradelists… this may change someday, but probably not soon, sorry!
- @alt_styles.group_by(&:species).each do |species, species_styles|
%h2.alt-styles-header= species.human_name
%ul.alt-styles-list= render partial: "alt_style", collection: species_styles
= form_with url: alt_styles_path, method: :get,
class: "rainbow-pool-filters" do |f|
%fieldset
%legend Filter by:
= f.select :series, @all_series_names,
selected: @series_name, include_blank: "Style…"
= f.select :color, @all_color_names,
selected: @color&.human_name, include_blank: "Color…"
= f.select :species, @all_species_names,
selected: @species&.human_name, include_blank: "Species…"
= f.submit "Go"
= will_paginate @alt_styles, class: "rainbow-pool-pagination"
%ul.rainbow-pool-list= render @alt_styles
= will_paginate @alt_styles, class: "rainbow-pool-pagination"
- content_for :stylesheets do
= stylesheet_link_tag "application/rainbow-pool"
= page_stylesheet_link_tag "alt_styles/index"

View file

@ -1,4 +1,5 @@
%outfit-viewer
- html_options = {} unless defined? html_options
= content_tag "outfit-viewer", **html_options do
.loading-indicator= render partial: "hanger_spinner"
%label.play-pause-button{title: "Pause/play animations"}

View file

@ -1,6 +1,6 @@
%li
= link_to useful_pet_state_path(pet_state.pet_type, pet_state) do
= outfit_viewer pet_state:
= outfit_viewer pet_state:, class: "preview"
.name= pose_name pet_state.pose
- if pet_state.glitched?
%span.glitched{title: "Glitched"} 👾

View file

@ -1,4 +1,4 @@
%li
= link_to pet_type do
= pet_type_image pet_type, :happy, :thumb
= pet_type_image pet_type, :happy, :thumb, class: "preview"
.name= pet_type.human_name

View file

@ -1,18 +1,18 @@
- title "Rainbow Pool"
- use_responsive_design
= form_with method: :get, class: "pet-filters" do |form|
= form_with method: :get, class: "rainbow-pool-filters" do |form|
%fieldset
%legend Filter by:
= form.select :color, @color_names, selected: @selected_color&.human_name, include_blank: "Color…"
= form.select :species, @species_names, selected: @selected_species&.human_name, include_blank: "Species…"
= form.submit "Go"
= will_paginate @pet_types
= will_paginate @pet_types, class: "rainbow-pool-pagination"
%ui.pet-types= render @pet_types
%ui.rainbow-pool-list= render @pet_types
= will_paginate @pet_types
= will_paginate @pet_types, class: "rainbow-pool-pagination"
- content_for :stylesheets do
= page_stylesheet_link_tag "pet_types/index"
= stylesheet_link_tag "application/rainbow-pool"

View file

@ -13,7 +13,7 @@
%li
Appearances
%ul.pet-states
%ul.rainbow-pool-list
= render @pet_states[:canonical]
- if @pet_states[:other].present?
@ -26,6 +26,7 @@
= stylesheet_link_tag "application/breadcrumbs"
= stylesheet_link_tag "application/hanger-spinner"
= stylesheet_link_tag "application/outfit-viewer"
= stylesheet_link_tag "application/rainbow-pool"
= page_stylesheet_link_tag "pet_types/show"
- content_for :javascripts do

View file

@ -35,7 +35,7 @@ OpenneoImpressItems::Application.routes.draw do
end
resources :alt_styles, path: 'alt-styles', only: [:index]
end
resources :alt_styles, path: 'alt-styles', only: [:index]
resources :alt_styles, path: 'alt-styles', only: [:index, :edit, :update]
resources :swf_assets, path: 'swf-assets', only: [:show]
resources :pet_types, path: 'rainbow-pool', param: "name",
only: [:index, :show] do