Compare commits
8 commits
e7148ffae3
...
80307f21f7
Author | SHA1 | Date | |
---|---|---|---|
80307f21f7 | |||
75040ffbf3 | |||
6f45cd0485 | |||
4e33477c65 | |||
b28255cafd | |||
99e8b46157 | |||
734b7fba1d | |||
a1d6961249 |
20 changed files with 426 additions and 112 deletions
110
app/assets/stylesheets/application/outfit-viewer.sass
Normal file
110
app/assets/stylesheets/application/outfit-viewer.sass
Normal file
|
@ -0,0 +1,110 @@
|
|||
@import "../partials/clean/constants"
|
||||
|
||||
// When loading, fade in the loading spinner after a brief delay. We only apply
|
||||
// the delay here, not on the base styles, because fading *out* on load should
|
||||
// be instant.
|
||||
//
|
||||
// This is implemented as a mixin, so that the item page can leverage the same
|
||||
// loading state when loading a new preview altogether. Once CSS container
|
||||
// style queries gain wider support, maybe use that instead.
|
||||
=outfit-viewer-loading
|
||||
cursor: wait
|
||||
|
||||
.loading-indicator
|
||||
opacity: 1
|
||||
transition-delay: 2s
|
||||
|
||||
// If the outfit *starts* in loading state, still delay the fade-in.
|
||||
@starting-style
|
||||
opacity: 0
|
||||
|
||||
outfit-viewer
|
||||
display: block
|
||||
position: relative
|
||||
overflow: hidden
|
||||
|
||||
// These are default widths, expected to often be overridden.
|
||||
width: 300px
|
||||
height: 300px
|
||||
|
||||
// There's no useful text in here, but double-clicking the play/pause
|
||||
// button can cause a weird selection state. Disable text selection.
|
||||
user-select: none
|
||||
-webkit-user-select: none
|
||||
|
||||
outfit-layer
|
||||
display: block
|
||||
position: absolute
|
||||
inset: 0
|
||||
|
||||
// We disable pointer-events most importantly for the iframes, which
|
||||
// will ignore our `cursor: wait` and show a plain cursor for the
|
||||
// inside of its own document. But also, the context menus for these
|
||||
// elements are kinda actively misleading, too!
|
||||
pointer-events: none
|
||||
|
||||
img, iframe
|
||||
width: 100%
|
||||
height: 100%
|
||||
|
||||
.loading-indicator
|
||||
position: absolute
|
||||
z-index: 1000
|
||||
bottom: 0px
|
||||
right: 4px
|
||||
padding: 8px
|
||||
background: radial-gradient(circle closest-side, white 45%, #ffffff00)
|
||||
|
||||
opacity: 0
|
||||
|
||||
.play-pause-button
|
||||
position: absolute
|
||||
z-index: 1001
|
||||
left: 8px
|
||||
bottom: 8px
|
||||
display: none
|
||||
align-items: center
|
||||
justify-content: center
|
||||
color: white
|
||||
background: rgba(0, 0, 0, 0.64)
|
||||
width: 2.5em
|
||||
height: 2.5em
|
||||
border-radius: 100%
|
||||
border: 2px solid transparent
|
||||
transition: all .25s
|
||||
|
||||
.playing-label, .paused-label
|
||||
display: none
|
||||
width: 1em
|
||||
height: 1em
|
||||
|
||||
.play-pause-toggle
|
||||
// Visually hidden
|
||||
clip: rect(0 0 0 0)
|
||||
clip-path: inset(50%)
|
||||
height: 1px
|
||||
overflow: hidden
|
||||
position: absolute
|
||||
white-space: nowrap
|
||||
width: 1px
|
||||
|
||||
&:checked ~ .playing-label
|
||||
display: block
|
||||
|
||||
&:not(:checked) ~ .paused-label
|
||||
display: block
|
||||
|
||||
&:hover, &:has(.play-pause-toggle:focus)
|
||||
border: 2px solid $module-border-color
|
||||
background: $module-bg-color
|
||||
color: $text-color
|
||||
|
||||
&:has(.play-pause-toggle:active)
|
||||
transform: translateY(2px)
|
||||
|
||||
&:has(outfit-layer:state(has-animations))
|
||||
.play-pause-button
|
||||
display: flex
|
||||
|
||||
&:has(outfit-layer:state(loading))
|
||||
+outfit-viewer-loading
|
|
@ -2,6 +2,8 @@
|
|||
@import "../partials/clean/mixins"
|
||||
@import "../partials/item_header"
|
||||
|
||||
@import "../application/outfit-viewer"
|
||||
|
||||
#container
|
||||
width: 900px // A bit more generous to the preview area!
|
||||
|
||||
|
@ -78,93 +80,10 @@
|
|||
width: var(--natural-width)
|
||||
|
||||
outfit-viewer
|
||||
display: block
|
||||
position: relative
|
||||
width: 300px
|
||||
height: 300px
|
||||
border: 1px solid $module-border-color
|
||||
border-radius: 1em
|
||||
overflow: hidden
|
||||
|
||||
// There's no useful text in here, but double-clicking the play/pause
|
||||
// button can cause a weird selection state. Disable text selection.
|
||||
user-select: none
|
||||
-webkit-user-select: none
|
||||
|
||||
outfit-layer
|
||||
display: block
|
||||
position: absolute
|
||||
inset: 0
|
||||
|
||||
// We disable pointer-events most importantly for the iframes, which
|
||||
// will ignore our `cursor: wait` and show a plain cursor for the
|
||||
// inside of its own document. But also, the context menus for these
|
||||
// elements are kinda actively misleading, too!
|
||||
pointer-events: none
|
||||
|
||||
img, iframe
|
||||
width: 100%
|
||||
height: 100%
|
||||
|
||||
.loading-indicator
|
||||
position: absolute
|
||||
z-index: 1000
|
||||
bottom: 0px
|
||||
right: 4px
|
||||
padding: 8px
|
||||
background: radial-gradient(circle closest-side, white 45%, #ffffff00)
|
||||
|
||||
opacity: 0
|
||||
transition: opacity .5s
|
||||
|
||||
.play-pause-button
|
||||
position: absolute
|
||||
z-index: 1001
|
||||
left: 8px
|
||||
bottom: 8px
|
||||
display: none
|
||||
align-items: center
|
||||
justify-content: center
|
||||
color: white
|
||||
background: rgba(0, 0, 0, 0.64)
|
||||
width: 2.5em
|
||||
height: 2.5em
|
||||
border-radius: 100%
|
||||
border: 2px solid transparent
|
||||
transition: all .25s
|
||||
|
||||
.playing-label, .paused-label
|
||||
display: none
|
||||
width: 1em
|
||||
height: 1em
|
||||
|
||||
.play-pause-toggle
|
||||
// Visually hidden
|
||||
clip: rect(0 0 0 0)
|
||||
clip-path: inset(50%)
|
||||
height: 1px
|
||||
overflow: hidden
|
||||
position: absolute
|
||||
white-space: nowrap
|
||||
width: 1px
|
||||
|
||||
&:checked ~ .playing-label
|
||||
display: block
|
||||
|
||||
&:not(:checked) ~ .paused-label
|
||||
display: block
|
||||
|
||||
&:hover, &:has(.play-pause-toggle:focus)
|
||||
border: 2px solid $module-border-color
|
||||
background: $module-bg-color
|
||||
color: $text-color
|
||||
|
||||
&:has(.play-pause-toggle:active)
|
||||
transform: translateY(2px)
|
||||
|
||||
&:has(outfit-layer:state(has-animations))
|
||||
.play-pause-button
|
||||
display: flex
|
||||
|
||||
.error-indicator
|
||||
font-size: 85%
|
||||
|
@ -179,16 +98,8 @@ outfit-viewer
|
|||
//
|
||||
// We only apply the delay here, not on the base styles, because fading
|
||||
// *out* on load should be instant.
|
||||
#item-preview[busy] outfit-viewer, outfit-viewer:has(outfit-layer:state(loading))
|
||||
cursor: wait
|
||||
|
||||
.loading-indicator
|
||||
opacity: 1
|
||||
transition-delay: 2s
|
||||
|
||||
// If the outfit *starts* in loading state, still delay the fade-in.
|
||||
@starting-style
|
||||
opacity: 0
|
||||
#item-preview[busy] outfit-viewer
|
||||
+outfit-viewer-loading
|
||||
|
||||
#item-preview:has(outfit-layer:state(error))
|
||||
outfit-viewer
|
||||
|
|
5
app/assets/stylesheets/pet_states/show.sass
Normal file
5
app/assets/stylesheets/pet_states/show.sass
Normal file
|
@ -0,0 +1,5 @@
|
|||
outfit-viewer
|
||||
margin: 0 auto
|
||||
|
||||
dt
|
||||
cursor: help
|
32
app/assets/stylesheets/pet_types/index.sass
Normal file
32
app/assets/stylesheets/pet_types/index.sass
Normal file
|
@ -0,0 +1,32 @@
|
|||
.pet-types
|
||||
list-style-type: none
|
||||
display: flex
|
||||
flex-wrap: wrap
|
||||
justify-content: center
|
||||
gap: .5em
|
||||
|
||||
> li
|
||||
width: 150px
|
||||
max-width: calc(50% - .25em)
|
||||
min-width: 150px
|
||||
box-sizing: border-box
|
||||
text-align: center
|
||||
|
||||
a
|
||||
display: block
|
||||
border-radius: 1em
|
||||
padding: .5em
|
||||
|
||||
img
|
||||
width: 100%
|
||||
height: auto
|
||||
aspect-ratio: 1 / 1
|
||||
position: relative
|
||||
z-index: -1
|
||||
margin-bottom: -1em
|
||||
|
||||
.name
|
||||
background: white
|
||||
padding: .25em .5em
|
||||
border-radius: .5em
|
||||
margin: 0 auto
|
36
app/assets/stylesheets/pet_types/show.sass
Normal file
36
app/assets/stylesheets/pet_types/show.sass
Normal file
|
@ -0,0 +1,36 @@
|
|||
@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
|
||||
|
||||
outfit-viewer
|
||||
width: 100%
|
||||
height: auto
|
||||
aspect-ratio: 1 / 1
|
||||
position: relative
|
||||
z-index: -1
|
||||
margin-bottom: -1em
|
||||
|
||||
.name
|
||||
background: white
|
||||
padding: .25em .5em
|
||||
border-radius: .5em
|
||||
|
||||
.glitched
|
||||
cursor: help
|
6
app/controllers/pet_states_controller.rb
Normal file
6
app/controllers/pet_states_controller.rb
Normal file
|
@ -0,0 +1,6 @@
|
|||
class PetStatesController < ApplicationController
|
||||
def show
|
||||
@pet_type = PetType.matching_name_param(params[:pet_type_name]).first!
|
||||
@pet_state = @pet_type.pet_states.find(params[:id])
|
||||
end
|
||||
end
|
|
@ -1,10 +1,78 @@
|
|||
class PetTypesController < ApplicationController
|
||||
def show
|
||||
@pet_type = PetType.
|
||||
where(species_id: params[:species_id]).
|
||||
where(color_id: params[:color_id]).
|
||||
first
|
||||
def index
|
||||
@species_names = Species.order(:name).map(&:human_name)
|
||||
@color_names = Color.order(:name).map(&:human_name)
|
||||
|
||||
render json: @pet_type
|
||||
if params[:species].present?
|
||||
@selected_species = Species.find_by!(name: params[:species])
|
||||
@selected_species_name = @selected_species.human_name
|
||||
end
|
||||
if params[:color].present?
|
||||
@selected_color = Color.find_by!(name: params[:color])
|
||||
@selected_color_name = @selected_color.human_name
|
||||
end
|
||||
|
||||
@pet_types = PetType.
|
||||
includes(:color, :species).
|
||||
order(created_at: :desc).
|
||||
paginate(page: params[:page], per_page: 30)
|
||||
|
||||
if @selected_species
|
||||
@pet_types = @pet_types.where(species_id: @selected_species)
|
||||
end
|
||||
if @selected_color
|
||||
@pet_types = @pet_types.where(color_id: @selected_color)
|
||||
end
|
||||
end
|
||||
|
||||
def show
|
||||
@pet_type = find_pet_type
|
||||
|
||||
respond_to do |format|
|
||||
format.html do
|
||||
@pet_states = group_pet_states @pet_type.pet_states
|
||||
end
|
||||
format.json { render json: @pet_type }
|
||||
end
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
# The API-ish route uses IDs, but the human-facing route uses names.
|
||||
def find_pet_type
|
||||
if params[:species_id] && params[:color_id]
|
||||
PetType.find_by!(
|
||||
species_id: params[:species_id],
|
||||
color_id: params[:color_id],
|
||||
)
|
||||
elsif params[:name]
|
||||
color_name, _, species_name = params[:name].rpartition("-")
|
||||
raise ActiveRecord::RecordNotFound if species_name.blank?
|
||||
PetType.matching_name(color_name, species_name).first!
|
||||
else
|
||||
raise "expected params: species_id and color_id, or name"
|
||||
end
|
||||
end
|
||||
|
||||
# The `canonical` pet states are the main ones we want to show: the most
|
||||
# canonical state for each pose. The `other` pet states are, the others!
|
||||
#
|
||||
# If no main poses are available, then we just make all the poses
|
||||
# "canonical", and show the whole mish-mash!
|
||||
MAIN_POSES = %w(HAPPY_FEM HAPPY_MASC SAD_FEM SAD_MASC SICK_FEM SICK_MASC)
|
||||
def group_pet_states(pet_states)
|
||||
pose_groups = pet_states.emotion_order.group_by(&:pose)
|
||||
main_groups = pose_groups.select { |k| MAIN_POSES.include?(k) }.values
|
||||
other_groups = pose_groups.reject { |k| MAIN_POSES.include?(k) }.values
|
||||
|
||||
if main_groups.empty?
|
||||
return {canonical: other_groups.flatten(1).sort_by(&:pose), other: []}
|
||||
end
|
||||
|
||||
canonical = main_groups.map(&:first).sort_by(&:pose)
|
||||
main_others = main_groups.map { |l| l.drop(1) }.flatten(1)
|
||||
other = (main_others + other_groups.flatten(1)).sort_by(&:pose)
|
||||
|
||||
{canonical:, other:}
|
||||
end
|
||||
end
|
||||
|
|
|
@ -14,19 +14,30 @@ module ItemsHelper
|
|||
}
|
||||
|
||||
Sizes = {
|
||||
face: 1,
|
||||
thumb: 2,
|
||||
zoom: 3,
|
||||
full: 4,
|
||||
face_2x: 6,
|
||||
face: 1, # 50x50
|
||||
face_3x: 6, # 150x150
|
||||
|
||||
thumb: 2, # 150x150
|
||||
full: 4, # 300x300
|
||||
large: 5, # 500x500
|
||||
xlarge: 7, # 640x640
|
||||
|
||||
zoom: 3, # 80x80
|
||||
autocrop: 9, # <varies>
|
||||
}
|
||||
|
||||
SizeUpgrades = {
|
||||
face: :face_3x,
|
||||
thumb: :full,
|
||||
full: :xlarge,
|
||||
}
|
||||
end
|
||||
|
||||
def pet_type_image_url(pet_type, emotion: :happy, size: :face)
|
||||
PetTypeImage::Template.expand(
|
||||
hash: pet_type.basic_image_hash || pet_type.image_hash,
|
||||
emotion: PetTypeImage::Emotions[emotion],
|
||||
size: PetTypeImage::Sizes[size],
|
||||
emotion: PetTypeImage::Emotions.fetch(emotion),
|
||||
size: PetTypeImage::Sizes.fetch(size),
|
||||
).to_s
|
||||
end
|
||||
|
||||
|
@ -246,8 +257,10 @@ module ItemsHelper
|
|||
|
||||
def pet_type_image(pet_type, emotion, size, **options)
|
||||
src = pet_type_image_url(pet_type, emotion:, size:)
|
||||
srcset = if size == :face
|
||||
[[pet_type_image_url(pet_type, emotion:, size: :face_2x), "2x"]]
|
||||
|
||||
size_2x = PetTypeImage::SizeUpgrades[size]
|
||||
srcset = if size_2x
|
||||
[[pet_type_image_url(pet_type, emotion:, size: size_2x), "2x"]]
|
||||
end
|
||||
|
||||
image_tag(src, srcset:, **options)
|
||||
|
|
|
@ -69,5 +69,17 @@ module OutfitsHelper
|
|||
options = {:spellcheck => false, :id => nil}.merge(options)
|
||||
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
|
||||
|
||||
render partial: "outfit_viewer", locals: {outfit:}
|
||||
end
|
||||
end
|
||||
|
||||
|
|
22
app/helpers/pet_states_helper.rb
Normal file
22
app/helpers/pet_states_helper.rb
Normal file
|
@ -0,0 +1,22 @@
|
|||
module PetStatesHelper
|
||||
def pose_name(pose)
|
||||
case pose
|
||||
when "HAPPY_FEM"
|
||||
"Happy (Feminine)"
|
||||
when "HAPPY_MASC"
|
||||
"Happy (Masculine)"
|
||||
when "SAD_FEM"
|
||||
"Sad (Feminine)"
|
||||
when "SAD_MASC"
|
||||
"Sad (Masculine)"
|
||||
when "SICK_FEM"
|
||||
"Sick (Feminine)"
|
||||
when "SICK_MASC"
|
||||
"Sick (Masculine)"
|
||||
when "UNCONVERTED"
|
||||
"Unconverted"
|
||||
else
|
||||
"(Unknown)"
|
||||
end
|
||||
end
|
||||
end
|
|
@ -110,7 +110,7 @@ class PetState < ApplicationRecord
|
|||
def swf_asset_ids=(ids)
|
||||
self['swf_asset_ids'] = ids
|
||||
end
|
||||
|
||||
|
||||
def handle_assets!
|
||||
@parent_swf_asset_relationships_to_update.each do |rel|
|
||||
rel.swf_asset.save!
|
||||
|
@ -118,6 +118,10 @@ class PetState < ApplicationRecord
|
|||
end
|
||||
end
|
||||
|
||||
def to_param
|
||||
"#{id}-#{pose.split('_').map(&:capitalize).join('-')}"
|
||||
end
|
||||
|
||||
def self.from_pet_type_and_biology_info(pet_type, info)
|
||||
swf_asset_ids = []
|
||||
info.each do |zone_id, asset_info|
|
||||
|
|
|
@ -15,6 +15,10 @@ class PetType < ApplicationRecord
|
|||
species = Species.find_by_name!(species_name)
|
||||
where(color_id: color.id, species_id: species.id)
|
||||
}
|
||||
scope :matching_name_param, ->(name_param) {
|
||||
color_name, _, species_name = name_param.rpartition("-")
|
||||
matching_name(color_name, species_name)
|
||||
}
|
||||
scope :preferring_species, ->(species_id) {
|
||||
joins(:species).order([Arel.sql("species_id = ? DESC"), species_id])
|
||||
}
|
||||
|
@ -108,6 +112,10 @@ class PetType < ApplicationRecord
|
|||
Item.appearances_for(item, self, ...)
|
||||
end
|
||||
|
||||
def to_param
|
||||
"#{color.human_name}-#{species.human_name}"
|
||||
end
|
||||
|
||||
def self.all_by_ids_or_children(ids, pet_states)
|
||||
pet_states_by_pet_type_id = {}
|
||||
pet_states.each do |pet_state|
|
||||
|
|
|
@ -21,6 +21,6 @@
|
|||
- if swf_asset.canvas_movie?
|
||||
%iframe{src: swf_asset_path(swf_asset, playing: outfit_viewer_is_playing ? true : nil)}
|
||||
- elsif swf_asset.image_url.present?
|
||||
= image_tag swf_asset.image_url, alt: ""
|
||||
= image_tag swf_asset.image_url, alt: "", loading: "lazy"
|
||||
- else
|
||||
/ No movie or image available for SWF asset: #{swf_asset.url}
|
||||
/ No movie or image available for SWF asset: #{swf_asset.url}
|
|
@ -16,7 +16,7 @@
|
|||
|
||||
= turbo_frame_tag "item-preview" do
|
||||
.preview-area
|
||||
= render partial: "outfit_viewer", locals: {outfit: @preview_outfit}
|
||||
= outfit_viewer @preview_outfit
|
||||
.error-indicator
|
||||
💥 We couldn't load all of this outfit. Try again?
|
||||
= link_to wardrobe_path(params: @preview_outfit.wardrobe_params),
|
||||
|
@ -122,6 +122,8 @@
|
|||
|
||||
- content_for :stylesheets do
|
||||
= stylesheet_link_tag "application/hanger-spinner"
|
||||
-# This is imported into items/show directly, to gain access to its mixins.
|
||||
-# = stylesheet_link_tag "application/outfit-viewer"
|
||||
= page_stylesheet_link_tag "layouts/items"
|
||||
= page_stylesheet_link_tag "items/show"
|
||||
|
||||
|
|
6
app/views/pet_states/_pet_state.html.haml
Normal file
6
app/views/pet_states/_pet_state.html.haml
Normal file
|
@ -0,0 +1,6 @@
|
|||
%li
|
||||
= link_to [pet_state.pet_type, pet_state] do
|
||||
= outfit_viewer pet_state:
|
||||
.name= pose_name pet_state.pose
|
||||
- if pet_state.glitched?
|
||||
%span.glitched{title: "Glitched"} 👾
|
36
app/views/pet_states/show.html.haml
Normal file
36
app/views/pet_states/show.html.haml
Normal file
|
@ -0,0 +1,36 @@
|
|||
- title "#{@pet_type.human_name}: #{pose_name @pet_state.pose} [\##{@pet_state.id}]"
|
||||
- use_responsive_design
|
||||
|
||||
= outfit_viewer pet_state: @pet_state
|
||||
|
||||
%dl
|
||||
%dt{title: "Pose usually affects just the eyes and mouth. Neopets " +
|
||||
"genders these as Male/Female, but I don't like those " +
|
||||
"terms for like… it's just eyelashes! Sheesh!"}
|
||||
Pose
|
||||
%dd
|
||||
= pose_name @pet_state.pose
|
||||
- if @pet_state.pose == "UNCONVERTED"
|
||||
(Retired, replaced by #{link_to "Alt Styles", alt_styles_path})
|
||||
|
||||
%dt{title: "This is our own internal ID number, nothing to do with " +
|
||||
"Neopets's official data."}
|
||||
DTI ID
|
||||
%dd= @pet_state.id
|
||||
|
||||
%dt{title: "When we notice a form looks wrong, we mark it Glitched, to " +
|
||||
"tell our systems to prefer other forms for this pose instead."}
|
||||
Glitched?
|
||||
%dd
|
||||
- if @pet_state.glitched?
|
||||
👾 Yes, it's bad news bonko'd
|
||||
- else
|
||||
✅ Not marked as Glitched
|
||||
|
||||
- content_for :stylesheets do
|
||||
= stylesheet_link_tag "application/hanger-spinner"
|
||||
= stylesheet_link_tag "application/outfit-viewer"
|
||||
= stylesheet_link_tag "pet_states/show"
|
||||
|
||||
- content_for :javascripts do
|
||||
= javascript_include_tag "outfit-viewer", async: true
|
4
app/views/pet_types/_pet_type.html.haml
Normal file
4
app/views/pet_types/_pet_type.html.haml
Normal file
|
@ -0,0 +1,4 @@
|
|||
%li
|
||||
= link_to pet_type do
|
||||
= pet_type_image pet_type, :happy, :thumb
|
||||
.name= pet_type.human_name
|
16
app/views/pet_types/index.html.haml
Normal file
16
app/views/pet_types/index.html.haml
Normal file
|
@ -0,0 +1,16 @@
|
|||
- title "Rainbow Pool"
|
||||
- use_responsive_design
|
||||
|
||||
= form_with method: :get do |form|
|
||||
= 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 "Filter"
|
||||
|
||||
= will_paginate @pet_types
|
||||
|
||||
%ui.pet-types= render @pet_types
|
||||
|
||||
= will_paginate @pet_types
|
||||
|
||||
- content_for :stylesheets do
|
||||
= stylesheet_link_tag "pet_types/index"
|
19
app/views/pet_types/show.html.haml
Normal file
19
app/views/pet_types/show.html.haml
Normal file
|
@ -0,0 +1,19 @@
|
|||
- title "#{@pet_type.human_name}"
|
||||
- use_responsive_design
|
||||
|
||||
%ul.pet-states
|
||||
= render @pet_states[:canonical]
|
||||
|
||||
- if @pet_states[:other].present?
|
||||
%details
|
||||
%summary Other
|
||||
%ul.pet-states
|
||||
= render @pet_states[:other]
|
||||
|
||||
- content_for :stylesheets do
|
||||
= stylesheet_link_tag "application/hanger-spinner"
|
||||
= stylesheet_link_tag "application/outfit-viewer"
|
||||
= stylesheet_link_tag "pet_types/show"
|
||||
|
||||
- content_for :javascripts do
|
||||
= javascript_include_tag "outfit-viewer", async: true
|
|
@ -37,6 +37,10 @@ OpenneoImpressItems::Application.routes.draw do
|
|||
end
|
||||
resources :alt_styles, path: 'alt-styles', only: [:index]
|
||||
resources :swf_assets, path: 'swf-assets', only: [:show]
|
||||
resources :pet_types, path: 'rainbow-pool', param: "name",
|
||||
only: [:index, :show] do
|
||||
resources :pet_states, only: [:show], path: "forms"
|
||||
end
|
||||
|
||||
# Loading and modeling pets!
|
||||
post '/pets/load' => 'pets#load', :as => :load_pet
|
||||
|
|
Loading…
Reference in a new issue