Compare commits
7 commits
ecde507b60
...
d34bebc336
Author | SHA1 | Date | |
---|---|---|---|
d34bebc336 | |||
4e11ee4da7 | |||
1a76923ce6 | |||
a8648fc9c1 | |||
1b03c2caed | |||
1bd5598b64 | |||
189cb6a132 |
10 changed files with 162 additions and 28 deletions
|
@ -4,6 +4,9 @@
|
|||
.item-list
|
||||
border-collapse: collapse
|
||||
border: 1px solid $soft-border-color
|
||||
width: 60%
|
||||
table-layout: auto
|
||||
margin-bottom: 2em
|
||||
|
||||
td, th
|
||||
border-top: 1px solid $soft-border-color
|
||||
|
@ -17,20 +20,29 @@
|
|||
&:last-child
|
||||
padding-right: .5em
|
||||
|
||||
.thumbnail-cell img
|
||||
display: block
|
||||
.thumbnail-cell
|
||||
width: 2.5em
|
||||
height: 2.5em
|
||||
|
||||
img
|
||||
display: block
|
||||
width: 100%
|
||||
height: 100%
|
||||
|
||||
.name-cell a
|
||||
text-decoration: none
|
||||
&:hover, &:focus
|
||||
text-decoration: underline
|
||||
|
||||
.actions-cell
|
||||
text-align: right
|
||||
padding-left: 1em
|
||||
font-size: 85%
|
||||
|
||||
.name-cell a
|
||||
text-decoration: none
|
||||
&:hover
|
||||
text-decoration: underline
|
||||
tbody
|
||||
tr
|
||||
&:hover, &:focus-within
|
||||
background: rgba($module-bg-color, 0.5)
|
||||
|
||||
thead
|
||||
background: $module-bg-color
|
||||
|
@ -38,6 +50,23 @@
|
|||
th
|
||||
text-align: left
|
||||
|
||||
.thumbnail-cell img
|
||||
outline: 1px solid $soft-border-color
|
||||
|
||||
.actions-cell button
|
||||
/* Bootstrap's Purple 600 */
|
||||
+awesome-button-color(#59359a)
|
||||
|
||||
.special-color-explanation
|
||||
text-wrap: balance
|
||||
font-style: italic
|
||||
|
||||
/* For wearable items that belong to a specific set that all come together,
|
||||
* like a Paint Brush. */
|
||||
&[data-group-type="bundle"]
|
||||
tbody
|
||||
.thumbnail-cell
|
||||
opacity: 0.65
|
||||
|
||||
tr:hover .thumbnail-cell
|
||||
opacity: 0.85
|
||||
|
|
|
@ -114,18 +114,36 @@ class ItemsController < ApplicationController
|
|||
|
||||
def sources
|
||||
item_ids = params[:ids].split(",")
|
||||
@items = Item.where(id: item_ids).includes(:nc_mall_record).order(:name)
|
||||
@items = Item.where(id: item_ids).includes(:nc_mall_record).order(:name).
|
||||
limit(50)
|
||||
|
||||
if @items.empty?
|
||||
render file: "public/404.html", status: :not_found, layout: nil
|
||||
return
|
||||
end
|
||||
|
||||
# Group the items by category!
|
||||
@nc_mall_items = @items.select(&:currently_in_mall?)
|
||||
@other_nc_items = @items.select(&:nc?).reject(&:currently_in_mall?)
|
||||
@np_items = @items.select(&:np?)
|
||||
@pb_items = @items.select(&:pb?)
|
||||
|
||||
# Also, PB items have some special handling: we group them by color, then
|
||||
# load example pet types for the colors that don't have paint brushes.
|
||||
@pb_items_by_color = @pb_items.group_by(&:pb_color).
|
||||
sort_by { |color, items| color.name }.to_h
|
||||
|
||||
colors_without_thumbnails =
|
||||
@pb_items_by_color.keys.reject(&:pb_item_thumbnail_url?)
|
||||
|
||||
@pb_color_pet_types = colors_without_thumbnails.map do |color|
|
||||
# Infer the ideal species from the first item we can, then try to find a
|
||||
# matching pet type to use as the thumbnail, if needed.
|
||||
species = @pb_items_by_color[color].map(&:pb_species).select(&:present?)
|
||||
.first
|
||||
[color, color.example_pet_type(preferred_species: species)]
|
||||
end.to_h
|
||||
|
||||
render layout: "application"
|
||||
end
|
||||
|
||||
|
|
|
@ -2,23 +2,34 @@ require "addressable/template"
|
|||
|
||||
module ItemsHelper
|
||||
module PetTypeImage
|
||||
Format = 'https://pets.neopets.com/cp/%s/%i/%i.png'
|
||||
Template = Addressable::Template.new(
|
||||
"https://pets.neopets.com/cp/{hash}/{emotion}/{size}.png"
|
||||
)
|
||||
|
||||
Emotions = {
|
||||
:happy => 1,
|
||||
:sad => 2,
|
||||
:angry => 3,
|
||||
:ill => 4
|
||||
happy: 1,
|
||||
sad: 2,
|
||||
angry: 3,
|
||||
ill: 4,
|
||||
}
|
||||
|
||||
Sizes = {
|
||||
:face => 1,
|
||||
:thumb => 2,
|
||||
:zoom => 3,
|
||||
:full => 4
|
||||
face: 1,
|
||||
thumb: 2,
|
||||
zoom: 3,
|
||||
full: 4,
|
||||
face_2x: 6,
|
||||
}
|
||||
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],
|
||||
).to_s
|
||||
end
|
||||
|
||||
def standard_species_search_links
|
||||
build_on_pet_types(Species.alphabetical) do |pet_type|
|
||||
image = pet_type_image(pet_type, :happy, :zoom)
|
||||
|
@ -97,8 +108,9 @@ module ItemsHelper
|
|||
SHOP_WIZARD_URL_TEMPLATE = Addressable::Template.new(
|
||||
"https://www.neopets.com/shops/wizard.phtml{?string}"
|
||||
)
|
||||
def shop_wizard_url_for(item)
|
||||
SHOP_WIZARD_URL_TEMPLATE.expand(string: item.name).to_s
|
||||
def shop_wizard_url_for(item_or_name)
|
||||
item_or_name = item_or_name.name if item_or_name.is_a? Item
|
||||
SHOP_WIZARD_URL_TEMPLATE.expand(string: item_or_name).to_s
|
||||
end
|
||||
|
||||
SUPER_SHOP_WIZARD_URL_TEMPLATE = Addressable::Template.new(
|
||||
|
@ -111,8 +123,9 @@ module ItemsHelper
|
|||
TRADING_POST_URL_TEMPLATE = Addressable::Template.new(
|
||||
"https://www.neopets.com/island/tradingpost.phtml?type=browse&criteria=item_exact{&search_string}"
|
||||
)
|
||||
def trading_post_url_for(item)
|
||||
TRADING_POST_URL_TEMPLATE.expand(search_string: item.name).to_s
|
||||
def trading_post_url_for(item_or_name)
|
||||
item_or_name = item_or_name.name if item_or_name.is_a? Item
|
||||
TRADING_POST_URL_TEMPLATE.expand(search_string: item_or_name).to_s
|
||||
end
|
||||
|
||||
AUCTION_GENIE_URL_TEMPLATE = Addressable::Template.new(
|
||||
|
@ -155,9 +168,7 @@ module ItemsHelper
|
|||
end
|
||||
|
||||
def pet_type_image(pet_type, emotion, size)
|
||||
emotion_id = PetTypeImage::Emotions[emotion]
|
||||
size_id = PetTypeImage::Sizes[size]
|
||||
src = sprintf(PetTypeImage::Format, pet_type.basic_image_hash, emotion_id, size_id)
|
||||
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)
|
||||
end
|
||||
|
|
|
@ -21,6 +21,11 @@ class Color < ApplicationRecord
|
|||
end
|
||||
end
|
||||
|
||||
def example_pet_type(preferred_species: Species.first)
|
||||
pet_types.order([Arel.sql("species_id = ? DESC"), preferred_species.id],
|
||||
"species_id ASC").first
|
||||
end
|
||||
|
||||
def unfunny_human_name
|
||||
if name
|
||||
name.split(' ').map { |word| word.capitalize }.join(' ')
|
||||
|
|
|
@ -179,6 +179,36 @@ class Item < ApplicationRecord
|
|||
nc_mall_record&.current_price
|
||||
end
|
||||
|
||||
# If this is a PB item, return the corresponding Color, inferred from the
|
||||
# item name. If it's not a PB item, or we fail to infer, return nil.
|
||||
def pb_color
|
||||
return nil unless pb?
|
||||
|
||||
# NOTE: To handle colors like "Royalboy", where the items aren't consistent
|
||||
# with the color name regarding whether or not there's spaces, we remove
|
||||
# all spaces from the item name and color name when matching. We also
|
||||
# hackily handle the fact that "Elderlyboy" color has items named "Elderly
|
||||
# Male" (and same for Girl/Female) by replacing those words, too. These
|
||||
# hacks could cause false matches in theory, but I'm not aware of any rn!
|
||||
normalized_name = name.downcase.gsub("female", "girl").gsub("male", "boy").
|
||||
gsub(/\s/, "")
|
||||
|
||||
Color.order(:name).
|
||||
find { |c| normalized_name.include?(c.name.downcase.gsub(/\s/, "")) }
|
||||
end
|
||||
|
||||
# If this is a PB item, return the corresponding Species, inferred from the
|
||||
# item name. If it's not a PB item, or we fail to infer, return nil.
|
||||
def pb_species
|
||||
return nil unless pb?
|
||||
normalized_name = name.downcase
|
||||
Species.order(:name).find { |s| normalized_name.include?(s.name.downcase) }
|
||||
end
|
||||
|
||||
def pb_item_name
|
||||
pb_color&.pb_item_name
|
||||
end
|
||||
|
||||
def restricted_zones(options={})
|
||||
options[:scope] ||= Zone.all
|
||||
options[:scope].find(restricted_zone_ids)
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
%tr
|
||||
%td.thumbnail-cell= link_to item_thumbnail_for(item), item, target: "_blank"
|
||||
%td.thumbnail-cell
|
||||
= link_to item_thumbnail_for(item), item, target: "_blank",
|
||||
tabindex: "-1"
|
||||
%td.name-cell= link_to item.name, item, target: "_blank"
|
||||
%td.actions-cell= yield
|
||||
|
|
|
@ -54,13 +54,40 @@
|
|||
target: "_blank", icon: search_icon
|
||||
|
||||
- if @pb_items.present?
|
||||
%h2 Paintbrush items
|
||||
%h2 Paint Brush items
|
||||
:markdown
|
||||
These items are part of a paintbrush set. Once you paint your pet,
|
||||
These items are part of a paint brush set. Once you paint your pet,
|
||||
these items will be semi-permanently added to your Closet, even if your
|
||||
pet changes color again! You can use this to mix-and-match styles for
|
||||
"cross-paint" outfits.
|
||||
= render @pb_items
|
||||
- @pb_items_by_color.each do |color, items|
|
||||
%table.item-list{"data-group-type": "bundle"}
|
||||
%thead
|
||||
%td.thumbnail-cell
|
||||
- if color.pb_item_thumbnail_url?
|
||||
= image_tag color.pb_item_thumbnail_url,
|
||||
alt: "Item thumbnail for #{color.pb_item_name}"
|
||||
- else
|
||||
= image_tag pet_type_image_url(@pb_color_pet_types[color], size: :face),
|
||||
srcset: ["#{pet_type_image_url(@pb_color_pet_types[color], size: :face_2x)} 2x"],
|
||||
alt: @pb_color_pet_types[color].human_name
|
||||
%th
|
||||
#{color.pb_item_name || color.name.humanize}
|
||||
(#{pluralize items.size, "item"})
|
||||
%td.actions-cell
|
||||
- if color.pb_item_name?
|
||||
= button_link_to "Shops",
|
||||
shop_wizard_url_for(color.pb_item_name),
|
||||
target: "_blank", icon: search_icon
|
||||
= button_link_to "Trades",
|
||||
trading_post_url_for(color.pb_item_name),
|
||||
target: "_blank", icon: search_icon
|
||||
- else
|
||||
.special-color-explanation
|
||||
Get via Lab Ray, morphing potions, etc.
|
||||
%tbody
|
||||
- items.each do |item|
|
||||
= render "item_list_row", item:
|
||||
|
||||
- if @other_nc_items.present?
|
||||
%h2 Neocash items (Capsules, Dyeworks, events, retired, etc.)
|
||||
|
|
5
db/migrate/20240522222040_add_pb_item_name_to_colors.rb
Normal file
5
db/migrate/20240522222040_add_pb_item_name_to_colors.rb
Normal file
|
@ -0,0 +1,5 @@
|
|||
class AddPbItemNameToColors < ActiveRecord::Migration[7.1]
|
||||
def change
|
||||
add_column :colors, :pb_item_name, :string
|
||||
end
|
||||
end
|
|
@ -0,0 +1,5 @@
|
|||
class AddPbItemThumbnailUrlToColors < ActiveRecord::Migration[7.1]
|
||||
def change
|
||||
add_column :colors, :pb_item_thumbnail_url, :string
|
||||
end
|
||||
end
|
|
@ -10,7 +10,7 @@
|
|||
#
|
||||
# It's strongly recommended that you check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema[7.1].define(version: 2024_05_11_003019) do
|
||||
ActiveRecord::Schema[7.1].define(version: 2024_05_22_233638) do
|
||||
create_table "alt_styles", charset: "utf8mb4", collation: "utf8mb4_unicode_520_ci", force: :cascade do |t|
|
||||
t.integer "species_id", null: false
|
||||
t.integer "color_id", null: false
|
||||
|
@ -76,6 +76,8 @@ ActiveRecord::Schema[7.1].define(version: 2024_05_11_003019) do
|
|||
t.boolean "standard"
|
||||
t.boolean "prank", default: false, null: false
|
||||
t.string "name", null: false
|
||||
t.string "pb_item_name"
|
||||
t.string "pb_item_thumbnail_url"
|
||||
end
|
||||
|
||||
create_table "contributions", id: :integer, charset: "utf8mb4", collation: "utf8mb4_unicode_520_ci", force: :cascade do |t|
|
||||
|
|
Loading…
Reference in a new issue