Compare commits

..

3 commits

Author SHA1 Message Date
5e6c2c3c3f Put the new item preview in a Turbo frame
Nice, gotta say, this is a pretty neat way of making things feel more
app-y! There's some missing pieces here about like, loading state etc,
but the vibes are pretty good, and the implementation was dead-easy!
2024-07-01 13:48:20 -07:00
a8b4f9be65 [WIP] Add species/color picker for simplified item page preview
Still a lot missing here, like choosing the right default for Baby etc
items, and saving the user's preferences. But it's a start!
2024-06-30 23:34:27 -07:00
7733e9a8c4 [WIP] Start replacing item page preview with simpler HTML-based version
Just stripping out the big React component, and having Rails output it!

There's a lot of work rn in extracting the Impress 2020 dependency from
the `wardrobe-2020` React app, and I'm just curious to see if we can
simplify it at all by pulling this stuff *way* back to basics, and
deleting the item page part of `wardrobe-2020` altogether.

In this draft, we regress a lot of functionality: it just shows the
item on a Blue Acara, with no ability to change it! I'm gonna play with
putting more of that back in.

I also haven't actually removed any of the item page React code; I just
stopped calling it. That can be a cleanup for another time, once we're
confident in this experiment!
2024-06-30 23:09:28 -07:00
5 changed files with 94 additions and 123 deletions

View file

@ -43,7 +43,6 @@
/* When item names get long, don't let the buttons wrap to give the
* item names more space. The names should wrap more instead! */
text-wrap: nowrap
margin: .25em
tbody
tr
@ -56,12 +55,14 @@
th
text-align: left
.name-cell
text-wrap: nowrap
.thumbnail-cell img
outline: 1px solid $soft-border-color
.actions-cell
button, a.button
/* Bootstrap's Purple 600 */
+awesome-button-color(#59359a)
tr[data-item-owned]
color: #aaa
@ -114,15 +115,6 @@
text-decoration-line: underline
text-decoration-style: dotted
.actions-cell
button, a.button
&[data-action-kind=bulk-nc-mall]
/* Bootstrap's Purple 600 */
+awesome-button-color(#59359a)
&[data-complexity="high"]
width: 70%
/* For wearable items that belong to a specific set that all come together,
* like a Paint Brush. */
&[data-group-type="bundle"]

View file

@ -194,34 +194,50 @@ module ItemsHelper
end
def dyeworks_nc_total_for(items)
dyeworks_items_nc_total_for(items) + dyeworks_potions_nc_total(items.size)
end
def dyeworks_items_nc_total_for(items)
nc_total_for items.map(&:dyeworks_base_item)
end
def dyeworks_average_num_potions_for(items)
# Compute the number of expected potions for each (inverse of the odds),
# sum them, then round up.
items.map { |i| 1 / i.dyeworks_odds }.sum.ceil
def dyeworks_potions_nc_total(num_items)
dyeworks_potions_nc_breakdown(num_items)[:nc_total]
end
def dyeworks_estimated_potions_cost_for(items)
# NOTE: You could do bundles too, but let's just keep it simple.
dyeworks_average_num_potions_for(items) * 125
def dyeworks_potions_nc_summary(num_items)
dyeworks_potions_nc_breakdown(num_items)[:summary]
end
def complexity_for(items)
max_name_length = items.map(&:name).map(&:length).max
max_name_length >= 40 ? "high" : "low"
end
def dyeworks_potions_nc_breakdown(num_items)
nc_total = 0
summaries = []
def probability(p)
case p
when 1
"100%"
when 0
"0%"
else
"#{p.numerator} in #{p.denominator}"
# For every 10 potions, buy a 10-Bundle for 900 NC.
while num_items >= 10
nc_total += 900
summaries << "10-Bundle (900 NC)"
num_items -= 10
end
# For every remaining 5 potions, buy a 5-Bundle for 500 NC.
while num_items >= 5
nc_total += 500
summaries << "5-Bundle (500 NC)"
num_items -= 5
end
# For every remaining potion, buy each directly for 125 NC.
if num_items >= 1
nc_total += num_items * 125
summaries << "#{pluralize num_items, "potion"} (#{num_items * 125} NC)"
num_items = 0
end
summaries << "0 NC" if summaries.empty?
summary = summaries.join(", ")
{nc_total:, summary:}
end
private

View file

@ -17,8 +17,6 @@ class Item < ApplicationRecord
has_many :swf_assets, :through => :parent_swf_asset_relationships
belongs_to :dyeworks_base_item, class_name: "Item",
default: -> { inferred_dyeworks_base_item }, optional: true
has_many :dyeworks_variants, class_name: "Item",
inverse_of: :dyeworks_base_item
attr_writer :current_body_id, :owned, :wanted

View file

@ -70,39 +70,19 @@ class Item
match(DYEWORKS_LIMITED_FINAL_DATE_PATTERN)
return nil if match.nil?
# Parse this "<Month> <Day>" date as the *next* such date, with some
# wiggle room for the possibility that it recently passed and Owls hasn't
# updated yet: parse it as this year at first, then add a year if that
# turns out to be more than 3 months ago. (That way, if it's currently
# December 2024, then events ending in Jan will be read as Jan 2025, and
# events ending in Nov will be read as Nov 2024.)
# Parse this "<Month> <Day>" date as the *next* such date: parse it as
# this year at first, then add a year if it turns out to be in the past.
#
# NOTE: This could return strange results if the Owls date contains
# something surprising! But the heuristic nature helps with e.g.
# flexibility if they abbreviate months, so let's lean into `Date.parse`.
begin
match => {month:, day:}
date = Date.parse("#{month} #{day}, #{Date.today.year}")
date += 1.year if date < Date.today - 3.months
rescue Date::Error
Rails.logger.warn "Could not parse Dyeworks final date: " +
"#{nc_trade_value.value_text.inspect}"
return nil
end
match => {month:, day:}
date = Date.parse("#{month} #{day}, #{Date.today.year}")
date += 1.year if date < Date.today
date
end
# The probability of getting this item when dyeing the base item.
def dyeworks_odds
return nil unless dyeworks?
num_variants = dyeworks_base_item.dyeworks_variants.count
raise "Item's Dyeworks base has *no* variants??" if num_variants < 1
Rational(1, num_variants)
end
# Infer what base item this Dyeworks item probably relates to, based on
# their names. We only use this when a new item is modeled to initialize
# the `dyeworks_base_item` relationship in the database; after that, we

View file

@ -11,20 +11,17 @@
[nc]: https://secure.nc.neopets.com/get-neocash
[gc]: https://secure.nc.neopets.com/nickcash-cards
%table.item-list{"data-complexity": complexity_for(@items[:nc_mall])}
%table.item-list
%thead
%tr
%td
%th.name-cell
%th
Total: #{nc_total_for @items_needed[:nc_mall]} NC
(#{pluralize @items_needed[:nc_mall].size, "item"})
%td.actions-cell
- if @items_needed[:nc_mall].present?
%button{
onclick: "alert('Todo!')",
data: {"action-kind": "bulk-nc-mall"},
}
%button{onclick: "alert('Todo!')"}
= cart_icon alt: ""
Buy all in NC Mall
%tbody
@ -37,41 +34,34 @@
- if @items[:dyeworks].present?
%h2 Dyeworks items
:markdown
These are recolored "Dyeworks" variants of items. Dyeworks is a game of
chance: if you have the "base" item and a Dyeworks Hue Brew Potion, you can
[combine them][dyeworks] to receive a random color variant. You keep both
the new item *and* the base item.
If you don't get the color you want, you can use another potion to try
again. It's also common for users to exchange the variants they don't want
via "NC Trading". Potions can be bought individually, or in bundles of 5
or 10.
These are recolored "Dyeworks" variants of items. First get the "base"
item, then get a Dyeworks Hue Brew Potion, and combine them in the
[Dyeworks][dyeworks] section of the NC Mall! Potions can also be bought in
bundles of 5 or 10.
[dyeworks]: https://www.neopets.com/mall/dyeworks/
%table.item-list{"data-complexity": complexity_for(@items[:dyeworks])}
%table.item-list
%thead
%tr
%td.thumbnail-cell
= image_tag "https://images.neopets.com/items/mall_80x80_cleaning.gif",
alt: "Dyeworks Hue Brew Potion"
%th.name-cell
%th
Total: #{dyeworks_nc_total_for @items_needed[:dyeworks]} NC
+
%span.price-breakdown{
title: "At least #{pluralize @items_needed[:dyeworks].size, 'potion'}, " +
"average " +
"#{dyeworks_average_num_potions_for @items_needed[:dyeworks]}, " +
"could be more. 125 NC per potion, but cheaper in bundles."
}
?? potions
(~#{dyeworks_estimated_potions_cost_for @items_needed[:dyeworks]} NC)
= surround "(", ")" do
%span.price-breakdown{
title: "#{dyeworks_items_nc_total_for(@items_needed[:dyeworks])} NC"
}<
#{pluralize @items_needed[:dyeworks].size, "item"}
+
%span.price-breakdown{
title: dyeworks_potions_nc_summary(@items_needed[:dyeworks].size)
}<
#{pluralize @items_needed[:dyeworks].size, "potion"}
%td.actions-cell
- if @items_needed[:dyeworks].present?
%button{
onclick: "alert('Todo!')",
data: {"action-kind": "bulk-nc-mall"},
}
%button{onclick: "alert('Todo!')"}
= cart_icon alt: ""
Buy all in NC Mall
%tbody
@ -80,40 +70,36 @@
- base_item = item.dyeworks_base_item
- content_for :subtitle, flush: true do
= link_to base_item, target: "_blank" do
#{probability item.dyeworks_odds} chance
- if item.dyeworks_permanent?
%span.dyeworks-timeframe{
title: "This recipe is NOT currently scheduled to be removed " +
"from Dyeworks. It might not stay forever, but it's also " +
"not part of a known limited-time event, like most " +
"Dyeworks items are. (Thanks Owls team!)"
}
(Always available)
- elsif item.dyeworks_limited_final_date.present?
%span.dyeworks-timeframe{
title: "This recipe is part of a limited-time Dyeworks " +
"event. The last day you can dye this is " +
"#{item.dyeworks_limited_final_date.to_fs(:long)}. " +
"(Thanks Owls team!)"
}
(Limited-time: #{item.dyeworks_limited_final_date.to_fs(:month_and_day)})
- elsif item.dyeworks_limited?
%span.dyeworks-timeframe{
title: "This recipe is part of a limited-time Dyeworks " +
"event, and is scheduled to be removed from the NC Mall " +
"soon. (Thanks Owls team!)"
}
(Limited-time)
= link_to base_item.name, base_item, target: "_blank"
+ 1 potion
- if item.dyeworks_permanent?
%span.dyeworks-timeframe{
title: "This recipe is NOT currently scheduled to be removed " +
"from Dyeworks. It might not stay forever, but it's also " +
"not part of a known limited-time event, like most " +
"Dyeworks items are. (Thanks Owls team!)"
}
(Permanent)
- elsif item.dyeworks_limited_final_date.present?
%span.dyeworks-timeframe{
title: "This recipe is part of a limited-time Dyeworks " +
"event. The last day you can dye this is " +
"#{item.dyeworks_limited_final_date.to_fs(:month_and_day)}. " +
"(Thanks Owls team!)"
}
(Thru #{item.dyeworks_limited_final_date.to_fs(:month_and_day)})
- elsif item.dyeworks_limited?
%span.dyeworks-timeframe{
title: "This recipe is part of a limited-time Dyeworks " +
"event, and is scheduled to be removed from the NC Mall " +
"soon. (Thanks Owls team!)"
}
(Limited-time)
%button{onclick: "alert('Todo!')"}
= cart_icon alt: ""
Buy base (#{item.dyeworks_base_item.current_nc_price} NC)
= button_link_to "NC Trades",
item_trades_path(item, type: "offering"),
target: "_blank", icon: search_icon
- if @items[:np].present?
%h2 Neopoint items
:markdown
@ -126,11 +112,11 @@
[tp]: https://www.neopets.com/island/tradingpost.phtml?type=browse
[ag]: https://www.neopets.com/genie.phtml
%table.item-list{"data-complexity": complexity_for(@items[:np])}
%table.item-list
%thead
%tr
%td
%th.name-cell{colspan: 2}
%th{colspan: 2}
Total: #{pluralize @items_needed[:np].size, "item"}
%tbody
- @items[:np].each do |item|
@ -153,7 +139,6 @@
%table.item-list{
"data-group-type": "bundle",
"data-group-owned": items.all?(&:owned?),
"data-complexity": complexity_for(items),
}
%thead
%tr
@ -210,10 +195,10 @@
[owls]: https://www.neopets.com/~owls
%table.item-list{"data-complexity": complexity_for(@items[:other_nc])}
%table.item-list
%thead
%td
%th.name-cell{colspan: 2}
%th{colspan: 2}
Total: #{pluralize @items_needed[:other_nc].size, "item"}
%tbody
- @items[:other_nc].each do |item|