Compare commits
5 commits
a184c75575
...
77ff55353c
Author | SHA1 | Date | |
---|---|---|---|
77ff55353c | |||
a88fc14bd7 | |||
9f44fd47e4 | |||
4c44f8d6a4 | |||
2b2bffd9da |
5 changed files with 133 additions and 24 deletions
|
@ -1,6 +1,6 @@
|
|||
// When the species *face* picker changes, update and submit the main picker form.
|
||||
document.addEventListener("click", (e) => {
|
||||
if (!e.target.matches(".species-face-picker input[type=radio]")) return;
|
||||
// When the species face picker changes, update and submit the main picker form.
|
||||
document.addEventListener("change", (e) => {
|
||||
if (!e.target.matches("species-face-picker")) return;
|
||||
|
||||
try {
|
||||
const mainPicker = document.querySelector("#item-preview .species-color-picker");
|
||||
|
@ -14,8 +14,43 @@ document.addEventListener("click", (e) => {
|
|||
}
|
||||
});
|
||||
|
||||
// Now that the face picker is ready to go, mark it as usable.
|
||||
for (const options of document.querySelectorAll(".species-face-picker-options")) {
|
||||
options.removeAttribute("inert");
|
||||
options.removeAttribute("aria-hidden");
|
||||
class SpeciesFacePicker extends HTMLElement {
|
||||
connectedCallback() {
|
||||
this.addEventListener("click", this.#handleClick);
|
||||
}
|
||||
|
||||
get value() {
|
||||
return this.querySelector("input[type=radio]:checked")?.value;
|
||||
}
|
||||
|
||||
#handleClick(e) {
|
||||
if (e.target.matches("input[type=radio]")) {
|
||||
this.dispatchEvent(new Event("change", {bubbles: true}));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class SpeciesFacePickerOptions extends HTMLElement {
|
||||
static observedAttributes = ["inert", "aria-hidden"];
|
||||
|
||||
connectedCallback() {
|
||||
// Once this component is loaded, we stop being inert and aria-hidden. We're ready!
|
||||
this.#activate();
|
||||
}
|
||||
|
||||
attributeChangedCallback() {
|
||||
// If a Turbo Frame tries to morph us into being inert again, activate again!
|
||||
// (It's important that the server's HTML always return `inert`, for progressive
|
||||
// enhancement; and it's important to morph this element, so radio focus state
|
||||
// is preserved. To thread that needle, we have to monitor and remove!)
|
||||
this.#activate();
|
||||
}
|
||||
|
||||
#activate() {
|
||||
this.removeAttribute("inert");
|
||||
this.removeAttribute("aria-hidden");
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("species-face-picker", SpeciesFacePicker);
|
||||
customElements.define("species-face-picker-options", SpeciesFacePickerOptions);
|
||||
|
|
|
@ -161,9 +161,46 @@ body.items-show
|
|||
border-color: $error-border-color
|
||||
color: $error-color
|
||||
|
||||
.species-face-picker
|
||||
species-face-picker
|
||||
display: block
|
||||
position: relative
|
||||
|
||||
species-face-picker-options
|
||||
display: flex
|
||||
flex-wrap: wrap
|
||||
|
||||
img
|
||||
width: 50px
|
||||
height: 50px
|
||||
transition: all 0.2s
|
||||
|
||||
// Calm down the default color, just a smidge! There's a lot of color
|
||||
// on this page already, y'know?
|
||||
opacity: .9
|
||||
filter: saturate(90%)
|
||||
|
||||
label
|
||||
display: flex
|
||||
overflow: hidden
|
||||
transition: all 0.2s
|
||||
position: relative
|
||||
line-height: 1
|
||||
|
||||
// NOTE: The box-shadows here were copy-pasted from Impress 2020, which uses
|
||||
// Chakra UI's styling system to generate them! (The colors are from their
|
||||
// color palette, too.)
|
||||
&:has(input:checked)
|
||||
border-radius: 6px
|
||||
z-index: 1
|
||||
background: #9AE6B4
|
||||
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1),0 10px 10px -5px rgba(0, 0, 0, 0.04), #2F855A 0 0 2px 2px
|
||||
transform: scale(1.1)
|
||||
|
||||
&:has(input:focus)
|
||||
background: #BEE3F8
|
||||
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1),0 10px 10px -5px rgba(0, 0, 0, 0.04), #4299e1 0 0 0 3px
|
||||
transform: scale(1.2)
|
||||
|
||||
input[type=radio]
|
||||
position: absolute
|
||||
left: -10000px
|
||||
|
@ -172,21 +209,30 @@ body.items-show
|
|||
height: 1px
|
||||
overflow: hidden
|
||||
|
||||
&:not(:checked) + img
|
||||
opacity: .5
|
||||
&:checked + img
|
||||
opacity: 1
|
||||
filter: saturate(110%)
|
||||
|
||||
&:disabled + img
|
||||
opacity: .6
|
||||
filter: saturate(0%)
|
||||
|
||||
label:has(input[type=radio]:disabled)
|
||||
cursor: not-allowed
|
||||
|
||||
noscript
|
||||
position: absolute
|
||||
inset: 0
|
||||
background: rgba(white, .75)
|
||||
z-index: 1
|
||||
cursor: auto
|
||||
|
||||
display: flex
|
||||
align-items: center
|
||||
justify-content: center
|
||||
text-align: center
|
||||
|
||||
&:has(.species-face-picker-options[inert])
|
||||
&:has(species-face-picker-options[inert])
|
||||
cursor: wait
|
||||
|
||||
.item-zones-info
|
||||
|
|
|
@ -33,7 +33,9 @@ module ItemsHelper
|
|||
def standard_species_search_links
|
||||
all_species = Species.alphabetical.map(&:id)
|
||||
PetType.random_basic_per_species(all_species).map do |pet_type|
|
||||
image = pet_type_image(pet_type, :happy, :zoom)
|
||||
human_name = pet_type.species.human_name
|
||||
image = pet_type_image pet_type, :happy, :zoom,
|
||||
alt: human_name, title: human_name
|
||||
query = "species:#{pet_type.species.name}"
|
||||
link_to(image, items_path(:q => query))
|
||||
end.join.html_safe
|
||||
|
@ -222,6 +224,18 @@ module ItemsHelper
|
|||
cookies["DTIOutfitViewerIsPlaying"] == "true"
|
||||
end
|
||||
|
||||
def item_fits?(item, pet_type)
|
||||
item.appearances.any? { |a| a.fits? pet_type }
|
||||
end
|
||||
|
||||
def species_face_tooltip(pet_type, item)
|
||||
if item_fits?(item, pet_type)
|
||||
"#{pet_type.species.human_name}"
|
||||
else
|
||||
"#{pet_type.species.human_name}: No data yet"
|
||||
end
|
||||
end
|
||||
|
||||
def item_zone_partial_fit?(appearances_in_zone, all_appearances)
|
||||
appearances_in_zone.size < all_appearances.size
|
||||
end
|
||||
|
@ -230,12 +244,13 @@ module ItemsHelper
|
|||
appearances_in_zone.map(&:species).uniq.map(&:human_name).sort.join(", ")
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def pet_type_image(pet_type, emotion, size)
|
||||
def pet_type_image(pet_type, emotion, size, **options)
|
||||
src = pet_type_image_url(pet_type, emotion:, size:)
|
||||
human_name = pet_type.species.name.humanize
|
||||
image_tag(src, :alt => human_name, :title => human_name)
|
||||
srcset = if size == :face
|
||||
[[pet_type_image_url(pet_type, emotion:, size: :face_2x), "2x"]]
|
||||
end
|
||||
|
||||
image_tag(src, srcset:, **options)
|
||||
end
|
||||
|
||||
def item_header_user_lists_form_state
|
||||
|
|
|
@ -502,7 +502,7 @@ class Item < ApplicationRecord
|
|||
Appearance = Struct.new(:item, :body, :swf_assets) do
|
||||
include ActiveModel::Serializers::JSON
|
||||
delegate :present?, :empty?, to: :swf_assets
|
||||
delegate :species, to: :body
|
||||
delegate :species, :fits?, :fits_all?, to: :body
|
||||
|
||||
def attributes
|
||||
{item:, body:, swf_assets:}
|
||||
|
@ -522,6 +522,14 @@ class Item < ApplicationRecord
|
|||
def attributes
|
||||
{id:, species:}
|
||||
end
|
||||
|
||||
def fits_all?
|
||||
id == 0
|
||||
end
|
||||
|
||||
def fits?(target)
|
||||
fits_all? || target.body_id == id
|
||||
end
|
||||
end
|
||||
|
||||
def appearances
|
||||
|
|
|
@ -33,19 +33,24 @@
|
|||
"id", "human_name", @selected_preview_pet_type.species_id)
|
||||
= submit_tag "Go", name: nil
|
||||
|
||||
%form.species-face-picker
|
||||
%species-face-picker
|
||||
%noscript
|
||||
This fancy species picker requires Javascript, but you can still use the
|
||||
dropdowns instead!
|
||||
.species-face-picker-options{
|
||||
"inert": true, # waits for JS to remove
|
||||
%species-face-picker-options{
|
||||
inert: true, # waits for JS to remove
|
||||
"aria-hidden": true, # waits for JS to remove
|
||||
}
|
||||
- @preview_pet_type_options.each do |pet_type|
|
||||
%label
|
||||
%label{
|
||||
title: species_face_tooltip(pet_type, @item),
|
||||
}
|
||||
= radio_button_tag "species_face_id", pet_type.species_id,
|
||||
checked: pet_type == @preview_outfit.pet_type
|
||||
= pet_type_image pet_type, :happy, :face
|
||||
checked: pet_type == @preview_outfit.pet_type,
|
||||
disabled: !item_fits?(@item, pet_type)
|
||||
= pet_type_image pet_type,
|
||||
item_fits?(@item, pet_type) ? :happy : :sad,
|
||||
:face
|
||||
|
||||
.item-zones-info
|
||||
%section
|
||||
|
|
Loading…
Reference in a new issue