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.
|
// When the species face picker changes, update and submit the main picker form.
|
||||||
document.addEventListener("click", (e) => {
|
document.addEventListener("change", (e) => {
|
||||||
if (!e.target.matches(".species-face-picker input[type=radio]")) return;
|
if (!e.target.matches("species-face-picker")) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const mainPicker = document.querySelector("#item-preview .species-color-picker");
|
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.
|
class SpeciesFacePicker extends HTMLElement {
|
||||||
for (const options of document.querySelectorAll(".species-face-picker-options")) {
|
connectedCallback() {
|
||||||
options.removeAttribute("inert");
|
this.addEventListener("click", this.#handleClick);
|
||||||
options.removeAttribute("aria-hidden");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
border-color: $error-border-color
|
||||||
color: $error-color
|
color: $error-color
|
||||||
|
|
||||||
.species-face-picker
|
species-face-picker
|
||||||
|
display: block
|
||||||
position: relative
|
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]
|
input[type=radio]
|
||||||
position: absolute
|
position: absolute
|
||||||
left: -10000px
|
left: -10000px
|
||||||
|
@ -172,21 +209,30 @@ body.items-show
|
||||||
height: 1px
|
height: 1px
|
||||||
overflow: hidden
|
overflow: hidden
|
||||||
|
|
||||||
&:not(:checked) + img
|
&:checked + img
|
||||||
opacity: .5
|
opacity: 1
|
||||||
|
filter: saturate(110%)
|
||||||
|
|
||||||
|
&:disabled + img
|
||||||
|
opacity: .6
|
||||||
|
filter: saturate(0%)
|
||||||
|
|
||||||
|
label:has(input[type=radio]:disabled)
|
||||||
|
cursor: not-allowed
|
||||||
|
|
||||||
noscript
|
noscript
|
||||||
position: absolute
|
position: absolute
|
||||||
inset: 0
|
inset: 0
|
||||||
background: rgba(white, .75)
|
background: rgba(white, .75)
|
||||||
z-index: 1
|
z-index: 1
|
||||||
|
cursor: auto
|
||||||
|
|
||||||
display: flex
|
display: flex
|
||||||
align-items: center
|
align-items: center
|
||||||
justify-content: center
|
justify-content: center
|
||||||
text-align: center
|
text-align: center
|
||||||
|
|
||||||
&:has(.species-face-picker-options[inert])
|
&:has(species-face-picker-options[inert])
|
||||||
cursor: wait
|
cursor: wait
|
||||||
|
|
||||||
.item-zones-info
|
.item-zones-info
|
||||||
|
|
|
@ -33,7 +33,9 @@ module ItemsHelper
|
||||||
def standard_species_search_links
|
def standard_species_search_links
|
||||||
all_species = Species.alphabetical.map(&:id)
|
all_species = Species.alphabetical.map(&:id)
|
||||||
PetType.random_basic_per_species(all_species).map do |pet_type|
|
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}"
|
query = "species:#{pet_type.species.name}"
|
||||||
link_to(image, items_path(:q => query))
|
link_to(image, items_path(:q => query))
|
||||||
end.join.html_safe
|
end.join.html_safe
|
||||||
|
@ -222,6 +224,18 @@ module ItemsHelper
|
||||||
cookies["DTIOutfitViewerIsPlaying"] == "true"
|
cookies["DTIOutfitViewerIsPlaying"] == "true"
|
||||||
end
|
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)
|
def item_zone_partial_fit?(appearances_in_zone, all_appearances)
|
||||||
appearances_in_zone.size < all_appearances.size
|
appearances_in_zone.size < all_appearances.size
|
||||||
end
|
end
|
||||||
|
@ -230,12 +244,13 @@ module ItemsHelper
|
||||||
appearances_in_zone.map(&:species).uniq.map(&:human_name).sort.join(", ")
|
appearances_in_zone.map(&:species).uniq.map(&:human_name).sort.join(", ")
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
def pet_type_image(pet_type, emotion, size, **options)
|
||||||
|
|
||||||
def pet_type_image(pet_type, emotion, size)
|
|
||||||
src = pet_type_image_url(pet_type, emotion:, size:)
|
src = pet_type_image_url(pet_type, emotion:, size:)
|
||||||
human_name = pet_type.species.name.humanize
|
srcset = if size == :face
|
||||||
image_tag(src, :alt => human_name, :title => human_name)
|
[[pet_type_image_url(pet_type, emotion:, size: :face_2x), "2x"]]
|
||||||
|
end
|
||||||
|
|
||||||
|
image_tag(src, srcset:, **options)
|
||||||
end
|
end
|
||||||
|
|
||||||
def item_header_user_lists_form_state
|
def item_header_user_lists_form_state
|
||||||
|
|
|
@ -502,7 +502,7 @@ class Item < ApplicationRecord
|
||||||
Appearance = Struct.new(:item, :body, :swf_assets) do
|
Appearance = Struct.new(:item, :body, :swf_assets) do
|
||||||
include ActiveModel::Serializers::JSON
|
include ActiveModel::Serializers::JSON
|
||||||
delegate :present?, :empty?, to: :swf_assets
|
delegate :present?, :empty?, to: :swf_assets
|
||||||
delegate :species, to: :body
|
delegate :species, :fits?, :fits_all?, to: :body
|
||||||
|
|
||||||
def attributes
|
def attributes
|
||||||
{item:, body:, swf_assets:}
|
{item:, body:, swf_assets:}
|
||||||
|
@ -522,6 +522,14 @@ class Item < ApplicationRecord
|
||||||
def attributes
|
def attributes
|
||||||
{id:, species:}
|
{id:, species:}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def fits_all?
|
||||||
|
id == 0
|
||||||
|
end
|
||||||
|
|
||||||
|
def fits?(target)
|
||||||
|
fits_all? || target.body_id == id
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def appearances
|
def appearances
|
||||||
|
|
|
@ -33,19 +33,24 @@
|
||||||
"id", "human_name", @selected_preview_pet_type.species_id)
|
"id", "human_name", @selected_preview_pet_type.species_id)
|
||||||
= submit_tag "Go", name: nil
|
= submit_tag "Go", name: nil
|
||||||
|
|
||||||
%form.species-face-picker
|
%species-face-picker
|
||||||
%noscript
|
%noscript
|
||||||
This fancy species picker requires Javascript, but you can still use the
|
This fancy species picker requires Javascript, but you can still use the
|
||||||
dropdowns instead!
|
dropdowns instead!
|
||||||
.species-face-picker-options{
|
%species-face-picker-options{
|
||||||
"inert": true, # waits for JS to remove
|
inert: true, # waits for JS to remove
|
||||||
"aria-hidden": true, # waits for JS to remove
|
"aria-hidden": true, # waits for JS to remove
|
||||||
}
|
}
|
||||||
- @preview_pet_type_options.each do |pet_type|
|
- @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,
|
= radio_button_tag "species_face_id", pet_type.species_id,
|
||||||
checked: pet_type == @preview_outfit.pet_type
|
checked: pet_type == @preview_outfit.pet_type,
|
||||||
= pet_type_image pet_type, :happy, :face
|
disabled: !item_fits?(@item, pet_type)
|
||||||
|
= pet_type_image pet_type,
|
||||||
|
item_fits?(@item, pet_type) ? :happy : :sad,
|
||||||
|
:face
|
||||||
|
|
||||||
.item-zones-info
|
.item-zones-info
|
||||||
%section
|
%section
|
||||||
|
|
Loading…
Reference in a new issue