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/clean/mixins"
|
||||||
@import "../partials/item_header"
|
@import "../partials/item_header"
|
||||||
|
|
||||||
|
@import "../application/outfit-viewer"
|
||||||
|
|
||||||
#container
|
#container
|
||||||
width: 900px // A bit more generous to the preview area!
|
width: 900px // A bit more generous to the preview area!
|
||||||
|
|
||||||
|
@ -78,93 +80,10 @@
|
||||||
width: var(--natural-width)
|
width: var(--natural-width)
|
||||||
|
|
||||||
outfit-viewer
|
outfit-viewer
|
||||||
display: block
|
|
||||||
position: relative
|
|
||||||
width: 300px
|
width: 300px
|
||||||
height: 300px
|
height: 300px
|
||||||
border: 1px solid $module-border-color
|
border: 1px solid $module-border-color
|
||||||
border-radius: 1em
|
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
|
.error-indicator
|
||||||
font-size: 85%
|
font-size: 85%
|
||||||
|
@ -179,16 +98,8 @@ outfit-viewer
|
||||||
//
|
//
|
||||||
// We only apply the delay here, not on the base styles, because fading
|
// We only apply the delay here, not on the base styles, because fading
|
||||||
// *out* on load should be instant.
|
// *out* on load should be instant.
|
||||||
#item-preview[busy] outfit-viewer, outfit-viewer:has(outfit-layer:state(loading))
|
#item-preview[busy] outfit-viewer
|
||||||
cursor: wait
|
+outfit-viewer-loading
|
||||||
|
|
||||||
.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:has(outfit-layer:state(error))
|
#item-preview:has(outfit-layer:state(error))
|
||||||
outfit-viewer
|
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
|
class PetTypesController < ApplicationController
|
||||||
def show
|
def index
|
||||||
@pet_type = PetType.
|
@species_names = Species.order(:name).map(&:human_name)
|
||||||
where(species_id: params[:species_id]).
|
@color_names = Color.order(:name).map(&:human_name)
|
||||||
where(color_id: params[:color_id]).
|
|
||||||
first
|
|
||||||
|
|
||||||
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
|
||||||
end
|
end
|
||||||
|
|
|
@ -14,19 +14,30 @@ module ItemsHelper
|
||||||
}
|
}
|
||||||
|
|
||||||
Sizes = {
|
Sizes = {
|
||||||
face: 1,
|
face: 1, # 50x50
|
||||||
thumb: 2,
|
face_3x: 6, # 150x150
|
||||||
zoom: 3,
|
|
||||||
full: 4,
|
thumb: 2, # 150x150
|
||||||
face_2x: 6,
|
full: 4, # 300x300
|
||||||
|
large: 5, # 500x500
|
||||||
|
xlarge: 7, # 640x640
|
||||||
|
|
||||||
|
zoom: 3, # 80x80
|
||||||
|
autocrop: 9, # <varies>
|
||||||
|
}
|
||||||
|
|
||||||
|
SizeUpgrades = {
|
||||||
|
face: :face_3x,
|
||||||
|
thumb: :full,
|
||||||
|
full: :xlarge,
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
def pet_type_image_url(pet_type, emotion: :happy, size: :face)
|
def pet_type_image_url(pet_type, emotion: :happy, size: :face)
|
||||||
PetTypeImage::Template.expand(
|
PetTypeImage::Template.expand(
|
||||||
hash: pet_type.basic_image_hash || pet_type.image_hash,
|
hash: pet_type.basic_image_hash || pet_type.image_hash,
|
||||||
emotion: PetTypeImage::Emotions[emotion],
|
emotion: PetTypeImage::Emotions.fetch(emotion),
|
||||||
size: PetTypeImage::Sizes[size],
|
size: PetTypeImage::Sizes.fetch(size),
|
||||||
).to_s
|
).to_s
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -246,8 +257,10 @@ module ItemsHelper
|
||||||
|
|
||||||
def pet_type_image(pet_type, emotion, size, **options)
|
def pet_type_image(pet_type, emotion, size, **options)
|
||||||
src = pet_type_image_url(pet_type, emotion:, size:)
|
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
|
end
|
||||||
|
|
||||||
image_tag(src, srcset:, **options)
|
image_tag(src, srcset:, **options)
|
||||||
|
|
|
@ -69,5 +69,17 @@ module OutfitsHelper
|
||||||
options = {:spellcheck => false, :id => nil}.merge(options)
|
options = {:spellcheck => false, :id => nil}.merge(options)
|
||||||
text_field_tag 'name', nil, options
|
text_field_tag 'name', nil, options
|
||||||
end
|
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
|
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)
|
def swf_asset_ids=(ids)
|
||||||
self['swf_asset_ids'] = ids
|
self['swf_asset_ids'] = ids
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_assets!
|
def handle_assets!
|
||||||
@parent_swf_asset_relationships_to_update.each do |rel|
|
@parent_swf_asset_relationships_to_update.each do |rel|
|
||||||
rel.swf_asset.save!
|
rel.swf_asset.save!
|
||||||
|
@ -118,6 +118,10 @@ class PetState < ApplicationRecord
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def to_param
|
||||||
|
"#{id}-#{pose.split('_').map(&:capitalize).join('-')}"
|
||||||
|
end
|
||||||
|
|
||||||
def self.from_pet_type_and_biology_info(pet_type, info)
|
def self.from_pet_type_and_biology_info(pet_type, info)
|
||||||
swf_asset_ids = []
|
swf_asset_ids = []
|
||||||
info.each do |zone_id, asset_info|
|
info.each do |zone_id, asset_info|
|
||||||
|
|
|
@ -15,6 +15,10 @@ class PetType < ApplicationRecord
|
||||||
species = Species.find_by_name!(species_name)
|
species = Species.find_by_name!(species_name)
|
||||||
where(color_id: color.id, species_id: species.id)
|
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) {
|
scope :preferring_species, ->(species_id) {
|
||||||
joins(:species).order([Arel.sql("species_id = ? DESC"), species_id])
|
joins(:species).order([Arel.sql("species_id = ? DESC"), species_id])
|
||||||
}
|
}
|
||||||
|
@ -108,6 +112,10 @@ class PetType < ApplicationRecord
|
||||||
Item.appearances_for(item, self, ...)
|
Item.appearances_for(item, self, ...)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def to_param
|
||||||
|
"#{color.human_name}-#{species.human_name}"
|
||||||
|
end
|
||||||
|
|
||||||
def self.all_by_ids_or_children(ids, pet_states)
|
def self.all_by_ids_or_children(ids, pet_states)
|
||||||
pet_states_by_pet_type_id = {}
|
pet_states_by_pet_type_id = {}
|
||||||
pet_states.each do |pet_state|
|
pet_states.each do |pet_state|
|
||||||
|
|
|
@ -21,6 +21,6 @@
|
||||||
- if swf_asset.canvas_movie?
|
- if swf_asset.canvas_movie?
|
||||||
%iframe{src: swf_asset_path(swf_asset, playing: outfit_viewer_is_playing ? true : nil)}
|
%iframe{src: swf_asset_path(swf_asset, playing: outfit_viewer_is_playing ? true : nil)}
|
||||||
- elsif swf_asset.image_url.present?
|
- elsif swf_asset.image_url.present?
|
||||||
= image_tag swf_asset.image_url, alt: ""
|
= image_tag swf_asset.image_url, alt: "", loading: "lazy"
|
||||||
- else
|
- 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
|
= turbo_frame_tag "item-preview" do
|
||||||
.preview-area
|
.preview-area
|
||||||
= render partial: "outfit_viewer", locals: {outfit: @preview_outfit}
|
= outfit_viewer @preview_outfit
|
||||||
.error-indicator
|
.error-indicator
|
||||||
💥 We couldn't load all of this outfit. Try again?
|
💥 We couldn't load all of this outfit. Try again?
|
||||||
= link_to wardrobe_path(params: @preview_outfit.wardrobe_params),
|
= link_to wardrobe_path(params: @preview_outfit.wardrobe_params),
|
||||||
|
@ -122,6 +122,8 @@
|
||||||
|
|
||||||
- content_for :stylesheets do
|
- content_for :stylesheets do
|
||||||
= stylesheet_link_tag "application/hanger-spinner"
|
= 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 "layouts/items"
|
||||||
= page_stylesheet_link_tag "items/show"
|
= 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
|
end
|
||||||
resources :alt_styles, path: 'alt-styles', only: [:index]
|
resources :alt_styles, path: 'alt-styles', only: [:index]
|
||||||
resources :swf_assets, path: 'swf-assets', only: [:show]
|
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!
|
# Loading and modeling pets!
|
||||||
post '/pets/load' => 'pets#load', :as => :load_pet
|
post '/pets/load' => 'pets#load', :as => :load_pet
|
||||||
|
|
Loading…
Reference in a new issue