Compare commits
8 commits
77ff55353c
...
edcb21558a
Author | SHA1 | Date | |
---|---|---|---|
edcb21558a | |||
176ab20fd1 | |||
0305817cec | |||
4d5b583432 | |||
2e48376c5a | |||
2ea8f16e43 | |||
de99e0236b | |||
6dd8e585a3 |
14 changed files with 154 additions and 38 deletions
|
@ -3,17 +3,36 @@ document.addEventListener("change", (e) => {
|
|||
if (!e.target.matches("species-face-picker")) return;
|
||||
|
||||
try {
|
||||
const mainPicker = document.querySelector("#item-preview .species-color-picker");
|
||||
const mainPickerForm = document.querySelector(
|
||||
"#item-preview species-color-picker form");
|
||||
const mainSpeciesField =
|
||||
mainPicker.querySelector("[name='preview[species_id]']");
|
||||
mainPickerForm.querySelector("[name='preview[species_id]']");
|
||||
mainSpeciesField.value = e.target.value;
|
||||
mainPicker.requestSubmit(); // `submit` doesn't get captured by Turbo!
|
||||
mainPickerForm.requestSubmit(); // `submit` doesn't get captured by Turbo!
|
||||
} catch (error) {
|
||||
e.preventDefault();
|
||||
console.error("Couldn't update species picker: ", error);
|
||||
}
|
||||
});
|
||||
|
||||
class SpeciesColorPicker extends HTMLElement {
|
||||
#internals;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.#internals = this.attachInternals();
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
// Listen for changes to auto-submit the form, then tell CSS about it!
|
||||
this.addEventListener("change", this.#handleChange);
|
||||
this.#internals.states.add("auto-loading");
|
||||
}
|
||||
|
||||
#handleChange(e) {
|
||||
this.querySelector("form").requestSubmit();
|
||||
}
|
||||
}
|
||||
|
||||
class SpeciesFacePicker extends HTMLElement {
|
||||
connectedCallback() {
|
||||
this.addEventListener("click", this.#handleClick);
|
||||
|
@ -52,5 +71,6 @@ class SpeciesFacePickerOptions extends HTMLElement {
|
|||
}
|
||||
}
|
||||
|
||||
customElements.define("species-color-picker", SpeciesColorPicker);
|
||||
customElements.define("species-face-picker", SpeciesFacePicker);
|
||||
customElements.define("species-face-picker-options", SpeciesFacePickerOptions);
|
||||
|
|
|
@ -165,9 +165,12 @@ class OutfitLayer extends HTMLElement {
|
|||
|
||||
#sendMessageToIframe(message) {
|
||||
// If we have no frame or it hasn't loaded, ignore this message.
|
||||
if (this.iframe?.contentWindow == null) {
|
||||
if (this.iframe == null) {
|
||||
return;
|
||||
}
|
||||
if (this.iframe.contentWindow == null) {
|
||||
console.debug(
|
||||
`Ignoring message, frame not loaded: `,
|
||||
`Ignoring message, frame not loaded yet: `,
|
||||
this.iframe,
|
||||
message,
|
||||
);
|
||||
|
|
|
@ -5,9 +5,15 @@ body.items-index, body.items-show, body.items-needed, body.item_trades
|
|||
|
||||
text-align: center
|
||||
|
||||
input[type=text]
|
||||
font-size: 125%
|
||||
width: 15em
|
||||
.item-search-form
|
||||
display: flex
|
||||
gap: .5em
|
||||
justify-content: center
|
||||
|
||||
input[type=text]
|
||||
font-size: 125%
|
||||
width: 15em
|
||||
flex: 0 1 auto
|
||||
|
||||
h1
|
||||
margin-bottom: 1em
|
||||
|
|
12
app/assets/stylesheets/_responsive.sass
Normal file
12
app/assets/stylesheets/_responsive.sass
Normal file
|
@ -0,0 +1,12 @@
|
|||
body.use-responsive-design
|
||||
#container
|
||||
max-width: 100%
|
||||
padding-inline: 1rem
|
||||
box-sizing: border-box
|
||||
|
||||
#home-link
|
||||
margin-left: 1rem
|
||||
padding-inline: 0
|
||||
|
||||
#userbar
|
||||
margin-right: 1rem
|
|
@ -4,6 +4,7 @@
|
|||
@import partials/clean/mixins
|
||||
|
||||
@import layout
|
||||
@import responsive
|
||||
|
||||
@import partials/jquery.jgrowl
|
||||
|
||||
|
|
|
@ -46,7 +46,7 @@ body.items-show
|
|||
border: 1px solid $module-border-color
|
||||
border-radius: 1em
|
||||
overflow: hidden
|
||||
margin: 0 auto .75em
|
||||
margin: 0 auto
|
||||
|
||||
// There's no useful text in here, but double-clicking the play/pause
|
||||
// button can cause a weird selection state. Disable text selection.
|
||||
|
@ -151,22 +151,42 @@ body.items-show
|
|||
.error-indicator
|
||||
display: block
|
||||
|
||||
.species-color-picker
|
||||
species-color-picker
|
||||
.error-icon
|
||||
cursor: help
|
||||
margin-right: .25em
|
||||
|
||||
&[data-is-valid="false"]
|
||||
form[data-is-valid="false"]
|
||||
select
|
||||
border-color: $error-border-color
|
||||
color: $error-color
|
||||
|
||||
// If JS is enabled, but auto-loading isn't ready yet (script loading or
|
||||
// failed?), hide the submit button for .75sec, to give it time to load.
|
||||
@media (scripting: enabled)
|
||||
input[type=submit]
|
||||
position: absolute
|
||||
margin-left: .5em
|
||||
opacity: 0
|
||||
animation: fade-in .25s forwards
|
||||
animation-delay: .75s
|
||||
|
||||
// Once the auto-loading behavior is ready, remove the submit button.
|
||||
&:state(auto-loading)
|
||||
input[type=submit]
|
||||
display: none
|
||||
|
||||
species-face-picker
|
||||
display: block
|
||||
position: relative
|
||||
max-height: 200px // 4 rows of 50px images, and padding will offer a hint of below
|
||||
padding: 10px // leave enough room for the zoomed-in selected face
|
||||
margin-top: -10px
|
||||
overflow: auto
|
||||
|
||||
species-face-picker-options
|
||||
display: flex
|
||||
justify-content: center
|
||||
flex-wrap: wrap
|
||||
|
||||
img
|
||||
|
@ -236,8 +256,6 @@ body.items-show
|
|||
cursor: wait
|
||||
|
||||
.item-zones-info
|
||||
margin-top: .5em
|
||||
|
||||
h3
|
||||
display: inline
|
||||
font: inherit
|
||||
|
@ -261,3 +279,35 @@ body.items-show
|
|||
.zone-species-info
|
||||
font-style: italic
|
||||
text-decoration: underline dotted
|
||||
|
||||
#item-preview
|
||||
display: flex
|
||||
flex-direction: column
|
||||
gap: .75em
|
||||
|
||||
@media (min-width: 600px)
|
||||
display: grid
|
||||
grid-template-areas: "viewer faces" "picker zones"
|
||||
gap: .5em
|
||||
|
||||
outfit-viewer
|
||||
grid-area: viewer
|
||||
width: 350px
|
||||
height: 350px
|
||||
|
||||
species-color-picker
|
||||
grid-area: picker
|
||||
|
||||
species-face-picker
|
||||
grid-area: faces
|
||||
max-height: 350px
|
||||
margin: -10px
|
||||
|
||||
.item-zones-info
|
||||
grid-area: zones
|
||||
|
||||
@keyframes fade-in
|
||||
from
|
||||
opacity: 0
|
||||
to
|
||||
opacity: 1
|
||||
|
|
|
@ -35,6 +35,7 @@
|
|||
text-align: left
|
||||
display: flex
|
||||
align-items: center
|
||||
flex-wrap: wrap
|
||||
gap: 1em
|
||||
|
||||
abbr
|
||||
|
@ -127,6 +128,7 @@
|
|||
.item-subpages-nav
|
||||
display: flex
|
||||
align-items: flex-end
|
||||
gap: 1em
|
||||
|
||||
.preview-link
|
||||
margin-right: auto
|
||||
|
|
|
@ -95,7 +95,7 @@ class ItemsController < ApplicationController
|
|||
sort_by { |z, a| z.label }
|
||||
|
||||
@preview_pet_type_options = PetType.where(color: @preview_outfit.color).
|
||||
joins(:species).merge(Species.alphabetical)
|
||||
includes(:species).merge(Species.alphabetical)
|
||||
end
|
||||
|
||||
format.gif do
|
||||
|
|
|
@ -231,6 +231,15 @@ module ApplicationHelper
|
|||
@hide_title_header = true
|
||||
end
|
||||
|
||||
def use_responsive_design
|
||||
@use_responsive_design = true
|
||||
add_body_class "use-responsive-design"
|
||||
end
|
||||
|
||||
def use_responsive_design?
|
||||
@use_responsive_design || false
|
||||
end
|
||||
|
||||
def signed_in_meta_tag
|
||||
%(<meta name="user-signed-in" content="#{user_signed_in?}">).html_safe
|
||||
end
|
||||
|
|
|
@ -533,6 +533,10 @@ class Item < ApplicationRecord
|
|||
end
|
||||
|
||||
def appearances
|
||||
@appearances ||= build_appearances
|
||||
end
|
||||
|
||||
def build_appearances
|
||||
all_swf_assets = swf_assets.to_a
|
||||
|
||||
# If there are no assets yet, there are no appearances.
|
||||
|
@ -551,10 +555,10 @@ class Item < ApplicationRecord
|
|||
# Otherwise, create an appearance for each real (nonzero) body ID. We don't
|
||||
# generally expect body_id = 0 and body_id != 0 to mix, but if they do,
|
||||
# uhh, let's merge the body_id = 0 ones in?
|
||||
species_by_body_id = Species.with_body_ids(swf_assets_by_body_id.keys)
|
||||
swf_assets_by_body_id.map do |body_id, body_specific_assets|
|
||||
swf_assets_for_body = body_specific_assets + swf_assets_for_all_bodies
|
||||
species = Species.with_body_id(body_id).first!
|
||||
body = Appearance::Body.new(body_id, species)
|
||||
body = Appearance::Body.new(body_id, species_by_body_id[body_id])
|
||||
Appearance.new(self, body, swf_assets_for_body)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -4,11 +4,6 @@ class Species < ApplicationRecord
|
|||
|
||||
scope :alphabetical, -> { order(:name) }
|
||||
|
||||
scope :with_body_id, -> body_id {
|
||||
pt = PetType.arel_table
|
||||
joins(:pet_types).where(pt[:body_id].eq(body_id)).limit(1)
|
||||
}
|
||||
|
||||
def as_json(options={})
|
||||
super({only: [:id, :name], methods: [:human_name]}.merge(options))
|
||||
end
|
||||
|
@ -20,4 +15,15 @@ class Species < ApplicationRecord
|
|||
I18n.translate('species.default_human_name')
|
||||
end
|
||||
end
|
||||
|
||||
# Given a list of body IDs, return a hash from body ID to Species.
|
||||
# (We assume that each body ID belongs to just one species; if not, which
|
||||
# species we return for that body ID is undefined.)
|
||||
def self.with_body_ids(body_ids)
|
||||
species_ids_by_body_id = PetType.where(body_id: body_ids).distinct.
|
||||
pluck(:body_id, :species_id).to_h
|
||||
species_by_id = Species.where(id: species_ids_by_body_id.values).
|
||||
to_h { |s| [s.id, s] }
|
||||
species_ids_by_body_id.transform_values { |id| species_by_id[id] }
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
- title @item.name
|
||||
- canonical_path @item
|
||||
- use_responsive_design
|
||||
|
||||
= render partial: "item_header",
|
||||
locals: {item: @item, trades: @trades, current_subpage: "preview",
|
||||
|
@ -18,20 +19,20 @@
|
|||
.error-indicator
|
||||
💥 We couldn't load all of this outfit. Try again?
|
||||
|
||||
= form_for item_path(@item), method: :get, class: "species-color-picker",
|
||||
data: {"is-valid": @preview_error.nil?} do |f|
|
||||
- if @preview_error == :pet_type_does_not_exist
|
||||
%span.error-icon{title: "We haven't seen this kind of pet before."} ⚠️
|
||||
- elsif @preview_error == :no_item_data
|
||||
%span.error-icon{title: "We haven't seen this item on this pet before."} ⚠️
|
||||
%species-color-picker
|
||||
= form_for item_path(@item), method: :get, data: {"is-valid": @preview_error.nil?} do |f|
|
||||
- if @preview_error == :pet_type_does_not_exist
|
||||
%span.error-icon{title: "We haven't seen this kind of pet before."} ⚠️
|
||||
- elsif @preview_error == :no_item_data
|
||||
%span.error-icon{title: "We haven't seen this item on this pet before."} ⚠️
|
||||
|
||||
= select_tag "preview[color_id]",
|
||||
options_from_collection_for_select(Color.funny.alphabetical,
|
||||
"id", "human_name", @selected_preview_pet_type.color_id)
|
||||
= select_tag "preview[species_id]",
|
||||
options_from_collection_for_select(Species.alphabetical,
|
||||
"id", "human_name", @selected_preview_pet_type.species_id)
|
||||
= submit_tag "Go", name: nil
|
||||
= select_tag "preview[color_id]",
|
||||
options_from_collection_for_select(Color.funny.alphabetical,
|
||||
"id", "human_name", @selected_preview_pet_type.color_id)
|
||||
= select_tag "preview[species_id]",
|
||||
options_from_collection_for_select(Species.alphabetical,
|
||||
"id", "human_name", @selected_preview_pet_type.species_id)
|
||||
= submit_tag "Go", name: nil
|
||||
|
||||
%species-face-picker
|
||||
%noscript
|
||||
|
|
|
@ -13,6 +13,8 @@
|
|||
%link{href: image_path('favicon.png'), rel: 'icon'}
|
||||
= yield :stylesheets
|
||||
= stylesheet_link_tag "application"
|
||||
- if use_responsive_design?
|
||||
%meta{name: "viewport", content: "width=device-width, initial-scale=1"}
|
||||
= yield :meta
|
||||
= open_graph_tags
|
||||
= csrf_meta_tag
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
= image_tag 'https://images.neopets.com/items/mall_floatingneggfaerie.gif'
|
||||
%span= t 'infinite_closet'
|
||||
- content_for :content do
|
||||
= form_tag items_path, :method => :get do
|
||||
= form_tag items_path, method: :get, class: "item-search-form" do
|
||||
= text_field_tag :q, @query.to_s
|
||||
= submit_tag t('.search'), :name => nil
|
||||
= yield
|
||||
|
|
Loading…
Reference in a new issue