Compare commits

...

18 commits

Author SHA1 Message Date
42b1bf3e8c Don't overflow long usernames in the trades table
They can just ellipsis, that's fine, the full name is not essential
when you're just scanning the list; you're gonna pop it open and see
the full name during contact anyway.
2024-01-21 06:52:51 -08:00
5d577db649 Oops, load the data for the bulk item quantity form on the trades page! 2024-01-21 06:42:24 -08:00
8e3d2b994f Oops, link to the user lists page for the not-in-a-list case 2024-01-21 06:40:20 -08:00
507b346c2c Move item lists bulk management form to an openable dialog in the header 2024-01-21 06:20:32 -08:00
77d88e50a6 Oh right, make the item kind badges translatable!
Also make it an abbr, which means we need to override the default
text-decoration on it
2024-01-21 05:41:55 -08:00
abe2747b93 Oh right, PB is another kind of item! 2024-01-21 05:23:53 -08:00
31468c9682 Replace rarity on item page with NC/NP badge, styled after 2020 2024-01-21 05:20:08 -08:00
e8832f2c36 Move item description into header 2024-01-21 05:07:45 -08:00
e4fb067e45 Remove old trade hangers UI from item page 2024-01-21 04:49:06 -08:00
b9bb697ca1 Add trade counts to item page tab navigation 2024-01-21 04:45:22 -08:00
eb6f196b15 Add tab navigation to get to item trade pages 2024-01-21 04:40:25 -08:00
4b9e11fc2a Sort trades in vaguely-recent order
This logic is copied from DTI 2020! Though I didn't include the part
where we highlight trade matches yet!
2024-01-21 03:59:06 -08:00
bfb11e94e3 Fix style bug in trades page
Oops, I forgot this import! It worked when rebuilding styles in dev,
but not once I reloaded the server.
2024-01-21 03:58:33 -08:00
c6927c2ce8 Add basic styles to trades page
Still not accessible via links, just exists!
2024-01-21 03:38:02 -08:00
402e3d4afb Basic trade hangers page, just content and without style
We're just getting started but there we go!! No links or styles yet,
just getting it done!
2024-01-21 03:10:06 -08:00
a03ae90697 Move more of the trade-fetching logic into the model
It was a bit tricky to figure out the right API for this, since I'm
looking ahead to the possibility of splitting these across multiple
pages with more detail, like we do in DTI 2020.

What I like about this API is that the caller gets to apply, or not
apply, whatever scopes they want to the underlying hanger set (like
`includes` or `order`), without violating the usual syntax by e.g.
passing it as a parameter to a method.
2024-01-21 00:39:20 -08:00
80e158caf7 Remove extra item zones section from item page
This is in the previewer UI now, we can remove this extra one!
2024-01-20 23:59:08 -08:00
b1c1bea7be Remove unused data in items#show controller
Oh right, this was for the old pet previewer. Goodbye!
2024-01-20 23:53:30 -08:00
23 changed files with 502 additions and 376 deletions

View file

@ -0,0 +1,22 @@
const userListSections = document.querySelectorAll(
".item-header .user-lists-section");
for (const section of userListSections) {
try {
const dialog = section.querySelector("dialog");
const opener = section.querySelector(".dialog-opener");
const closer = section.querySelector(".dialog-closer");
if (dialog.showModal) { // check browser support
opener.addEventListener("click", (event) => {
dialog.show();
event.preventDefault();
});
document.body.addEventListener("click", (event) => {
if (dialog.open && !section.contains(event.target)) {
dialog.close();
}
});
}
} catch (error) {
console.error(`Error applying dialog behavior to item header:`, error);
}
}

View file

@ -1,6 +1,6 @@
@import "partials/campaign-progress" @import "partials/campaign-progress"
body.items body.items, body.item_trades
+campaign-progress +campaign-progress
text-align: center text-align: center

View file

@ -17,6 +17,7 @@
@import items @import items
@import items/index @import items/index
@import items/show @import items/show
@import item_trades/index
@import outfits/index @import outfits/index
@import outfits/new @import outfits/new
@import pets/bulk @import pets/bulk

View file

@ -0,0 +1,29 @@
@import "../partials/item_header"
body.item_trades-index
.item-header
+item-header
.item-subpage-title
text-align: left
margin-bottom: .5em
.trades-table
text-align: left
width: 100%
table-layout: fixed
th, td
&:nth-child(1), &:nth-child(2)
width: 15ch
overflow: hidden
text-overflow: ellipsis
.trade-list-names
list-style: none
li
display: inline
&:not(:last-child)::after
content: ", "

View file

@ -1,152 +1,11 @@
@import "../partials/clean/constants" @import "../partials/clean/constants"
@import "../partials/clean/mixins" @import "../partials/clean/mixins"
@import "../partials/item_header"
body.items-show body.items-show
#item-header .item-header
border-bottom: 1px solid $module-border-color +item-header
margin-bottom: 1em
padding: 1em 0
display: grid
grid-template-areas: "img gap1" "img name" "img links" "img gap2"
align-items: center
justify-content: center
column-gap: 1em
row-gap: .5em
#item-thumbnail
grid-area: img
border: 1px solid $module-border-color
height: 80px
width: 80px
#item-name
grid-area: name
text-align: left
line-height: 100%
#item-links
grid-area: links
text-align: left
a
font-size: 75%
margin-left: 1em
#item-name
margin-bottom: 0
#item-info-section
display: grid
grid-template-areas: "info form"
grid-template-columns: 1fr auto
#item-info
grid-area: info
#item-zones
font:
family: $text-font
size: 85%
margin-bottom: 1em
p
display: inline
&:first-child
margin-right: 1em
#your-items-form
grid-area: form
border: 1px solid $module-border-color
font-size: 85%
margin-bottom: 3em
margin-left: 1em
padding: 1em
width: 30em
// compete with #trade-hangers
position: relative
z-index: 2
h3
font-size: 150%
font-weight: bold
margin-bottom: .25em
#closet-hangers-ownership-groups
+clearfix
margin-bottom: .5em
div
float: left
margin: 0 5%
text-align: left
width: 40%
li
list-style: none
word-wrap: break-word
label.unlisted
font-style: italic
form
padding: .5em 0
select
width: 9em
input[type=number]
margin-right: .5em
width: 3em
#trade-hangers
font-size: 85%
margin-bottom: 3em
text-align: left
p
position: relative
&:first-child
margin-bottom: .5em
&[data-overflows]
overflow: hidden
.toggle
display: block
&[data-showing-more]
.toggle
.less
display: block
.more
display: none
.toggle
background: white
bottom: 0
cursor: pointer
display: none
font-family: $main-font
padding: 0 1em
position: absolute
right: 0
button
+reset-awesome-button
&:hover
text-decoration: underline
.less
display: none
#item-contributors #item-contributors
+subtle-banner +subtle-banner
clear: both clear: both
@ -172,13 +31,3 @@ body.items-show
.nc-icon .nc-icon
height: 16px height: 16px
width: 16px width: 16px
&.js
#trade-hangers
p
max-height: 3em
overflow: hidden
&.showing-more
max-height: none

View file

@ -0,0 +1,158 @@
@import "../partials/clean/constants"
@import "../partials/clean/mixins"
=item-header
border-bottom: 1px solid $module-border-color
margin-top: 1em
margin-bottom: 1em
.item-header-main
display: grid
grid-template-areas: "img gap1" "img name" "img links" "img lists" "img gap2" "nav nav"
align-items: center
justify-content: center
column-gap: 1em
row-gap: .5em
.item-thumbnail
grid-area: img
border: 1px solid $module-border-color
height: 80px
width: 80px
.item-name
grid-area: name
text-align: left
line-height: 100%
margin-bottom: 0
.item-links
grid-area: links
font-size: 85%
text-align: left
display: flex
align-items: center
gap: 1em
.item-kind
padding: .25em .5em
border-radius: .25em
cursor: help
text-decoration: none
font-weight: bold
line-height: 1
// These colors are copied from DTI 2020, for initial consistency!
// They're based on the Chakra UI colors, which I think are in turn the
// Bootstrap colors? Or something?
&[data-type=nc]
background: #E9D8FD
color: #44337A
&[data-type=pb]
background: #FEEBC8
color: #7B341E
&[data-type=np]
background: #E2E8F0
color: #1A202C
.user-lists-section
grid-area: lists
font-size: 85%
text-align: left
.dialog-opener
&::after
content: " "
dialog
background: $background-color
border: 1px solid $module-border-color
border-radius: .5em
padding: 1em
width: 30em
text-align: center
z-index: 2
margin-top: .5em
box-shadow: 0px 1px 4px #666
h3
font-size: 150%
font-weight: bold
margin-bottom: .25em
.closet-hangers-ownership-groups
+clearfix
margin-bottom: .5em
div
float: left
margin: 0 5%
text-align: left
width: 40%
li
list-style: none
word-wrap: break-word
label.unlisted
font-style: italic
form
padding: .5em 0
select
width: 9em
input[type=number]
margin-right: .5em
width: 3em
.item-description
margin-top: .5em
margin-bottom: 1em
.item-subpages-nav
display: flex
align-items: flex-end
.preview-link
margin-right: auto
.trades-section
display: flex
gap: .5em
header
align-self: center
font-weight: bold
&::after
content: ":"
ul
align-self: flex-end
list-style: none
display: flex
gap: .5em
li
display: block
a
display: block
border: 1px solid $module-border-color
border-bottom: 0
border-radius: .5em .5em 0 0
padding: .5em 1em
text-decoration: none
&:hover, &:focus
text-decoration: underline
&[data-is-current=true]
background: $module-bg-color
font-weight: bold

View file

@ -0,0 +1,29 @@
class ItemTradesController < ApplicationController
def index
@item = Item.find params[:item_id]
@type = type_from_params
@item_trades = @item.closet_hangers.trading.includes(:user, :list).
user_is_active.order('users.last_trade_activity_at DESC').to_trades
@trades = @item_trades[@type]
if user_signed_in?
@current_user_lists = current_user.closet_lists.alphabetical.
group_by_owned
@current_user_quantities = current_user.item_quantities_for(@item)
end
render layout: 'items'
end
def type_from_params
case params[:type]
when 'offering'
:offering
when 'seeking'
:seeking
else
raise ArgumentError, "unexpected trades type: #{params[:type].inspect}"
end
end
end

View file

@ -58,49 +58,15 @@ class ItemsController < ApplicationController
respond_to do |format| respond_to do |format|
format.html do format.html do
@occupied_zones = @item.occupied_zones( @trades = @item.closet_hangers.trading.user_is_active.to_trades
scope: Zone.includes_translations.alphabetical
)
@restricted_zones = @item.restricted_zones(
scope: Zone.includes_translations.alphabetical
)
@contributors_with_counts = @item.contributors_with_counts @contributors_with_counts = @item.contributors_with_counts
@supported_species_ids = @item.supported_species_ids
@basic_colored_pet_types_by_species_id = PetType.special_color_or_basic(@item.special_color).
includes_child_translations.group_by(&:species)
trading_closet_hangers = @item.closet_hangers.trading.includes(:user).
user_is_active.order('users.last_trade_activity_at DESC')
owned_trading_hangers = trading_closet_hangers.filter { |c| c.owned? }
wanted_trading_hangers = trading_closet_hangers.filter { |c| c.wanted? }
@trading_users_by_owned = {
true => owned_trading_hangers.map(&:user).uniq,
false => wanted_trading_hangers.map(&:user).uniq,
}
if user_signed_in? if user_signed_in?
# Empty arrays are important so that we can loop over this and still @current_user_lists = current_user.closet_lists.alphabetical.
# show the generic no-list case group_by_owned
@current_user_lists = {true => [], false => []} @current_user_quantities = current_user.item_quantities_for(@item)
current_user.closet_lists.alphabetical.each do |list|
@current_user_lists[list.hangers_owned] << list
end
@current_user_quantities = Hash.new(0) # default is zero
hangers = current_user.closet_hangers.where(item_id: @item.id).
select([:owned, :list_id, :quantity])
hangers.each do |hanger|
key = hanger.list_id || hanger.owned
@current_user_quantities[key] = hanger.quantity
end
end end
end end
format.gif do format.gif do

View file

@ -0,0 +1,26 @@
module ItemTradesHelper
def vague_trade_timestamp(last_trade_activity_at)
if last_trade_activity_at >= 1.week.ago
translate "item_trades.index.table.last_active.this_week"
else
last_trade_activity_at.strftime("%b %Y")
end
end
def sorted_vaguely_by_trade_activity(trades)
# First, sort the list in ascending order.
trades_ascending = trades.sort_by do |trade|
if trade.user.last_trade_activity_at >= 1.week.ago
# Sort recent trades in a random order, but still collectively as the
# most recent. (This discourages spamming updates to game the system!)
[1, rand]
else
# Sort older trades by last trade activity.
[0, trade.user.last_trade_activity_at]
end
end
# Then, reverse it!
trades_ascending.reverse!
end
end

View file

@ -49,10 +49,6 @@ module ItemsHelper
content_tag :div, content, :class => 'closeted-icons' content_tag :div, content, :class => 'closeted-icons'
end end
def list_zones(zones, method=:label)
zones.map(&method).join(', ')
end
def nc_icon def nc_icon
image_tag 'nc.png', :title => t('items.item.nc.description'), image_tag 'nc.png', :title => t('items.item.nc.description'),
@ -83,17 +79,6 @@ module ItemsHelper
"https://www.neopets.com/genie.phtml?type=process_genie&criteria=exact&auctiongenie=#{CGI::escape item.name}" "https://www.neopets.com/genie.phtml?type=process_genie&criteria=exact&auctiongenie=#{CGI::escape item.name}"
end end
def trading_users_header(owned, count)
ownership_key = owned ? 'owned' : 'wanted'
translate ".trading_users.header.#{ownership_key}", :count => count
end
def render_trading_users(owned)
@trading_users_by_owned[owned].map do |user|
link_to user.name, user_closet_hangers_path(user)
end.to_sentence.html_safe
end
def format_contribution_count(count) def format_contribution_count(count)
" (&times;#{count})".html_safe if count > 1 " (&times;#{count})".html_safe if count > 1
end end

View file

@ -13,29 +13,3 @@ ReactDOM.render(
</AppProvider>, </AppProvider>,
rootNode, rootNode,
); );
try {
const tradeHangers = document.querySelector("#trade-hangers");
const tradeSections = document.querySelectorAll("#trade-hangers p");
for (const section of tradeSections) {
const oneLine = parseFloat(getComputedStyle(section)['line-height']);
const maxHeight = Math.ceil(oneLine * 2);
if (section.clientHeight > maxHeight) {
section.style.maxHeight = `${maxHeight}px`;
section.setAttribute("data-overflows", "");
}
section.querySelector(".more")?.addEventListener("click", (event) => {
section.setAttribute("data-showing-more", "");
section.style.maxHeight = "none";
});
section.querySelector(".less")?.addEventListener("click", (event) => {
section.removeAttribute("data-showing-more");
section.style.maxHeight = `${maxHeight}px`;
});
}
} catch (error) {
console.error("Error applying trade list more/less toggle", error);
}

View file

@ -153,6 +153,43 @@ class ClosetHanger < ApplicationRecord
# If quantity is zero and there's no hanger, good. Do nothing. # If quantity is zero and there's no hanger, good. Do nothing.
end end
# Use this with a scoped relation to convert it into a list of trades, e.g.
# `item.hangers.trading.to_trades`.
#
# A trade includes the user who's trading, and the available closet hangers
# (which you can use to get e.g. the list name).
#
# We don't preload anything here - if you want user names or list names, you
# should `includes` them in the hanger scope first, to avoid extra queries!
def self.to_trades
# Let's ensure that the `trading` filter is applied, to avoid data leaks.
# (I still recommend doing it at the call site too for clarity, though!)
all_trading_hangers = trading.to_a
owned_hangers = all_trading_hangers.filter(&:owned?)
wanted_hangers = all_trading_hangers.filter(&:wanted?)
# Group first into offering vs seeking, then by user.
offering, seeking = [owned_hangers, wanted_hangers].map do |hangers|
hangers.group_by(&:user_id).map do |user_id, user_hangers|
Trade.new(user_id, user_hangers)
end
end
{offering: offering, seeking: seeking}
end
Trade = Struct.new('Trade', :user_id, :hangers) do
def user
# Take advantage of `includes(:user)` on the hangers, if applied.
hangers.first.user
end
def lists
hangers.map(&:list).filter(&:present?)
end
end
protected protected
def list_belongs_to_user def list_belongs_to_user

View file

@ -87,6 +87,13 @@ class ClosetList < ApplicationRecord
end end
end end
def self.group_by_owned
h = all.group_by(&:hangers_owned?)
h[true] ||= []
h[false] ||= []
h
end
include VisibilityMethods include VisibilityMethods
class Null class Null

View file

@ -278,17 +278,6 @@ class Item < ApplicationRecord
write_attribute('species_support_ids', replacement) write_attribute('species_support_ids', replacement)
end end
def supported_species_ids
return Species.select([:id]).map(&:id) if modeled_body_ids.include?(0)
pet_types = PetType.where(:body_id => modeled_body_ids).select('DISTINCT species_id')
species_ids = pet_types.map(&:species_id)
# If there are multiple known supported species, it probably supports them
# all. (I've never heard of only a handful of species being supported :P)
species_ids.size >= 2 ? Species.select([:id]).map(&:id) : species_ids
end
def support_species?(species) def support_species?(species)
species_support_ids.blank? || species_support_ids.include?(species.id) species_support_ids.blank? || species_support_ids.include?(species.id)
end end

View file

@ -175,6 +175,18 @@ class User < ApplicationRecord
contact_neopets_connection.try(:neopets_username) contact_neopets_connection.try(:neopets_username)
end end
def item_quantities_for(item_id)
quantities = Hash.new(0)
hangers = closet_hangers.where(item_id: item_id).
select([:owned, :list_id, :quantity])
hangers.each do |hanger|
quantities[hanger.list_id || hanger.owned?] = hanger.quantity
end
quantities
end
def log_trade_activity def log_trade_activity
touch(:last_trade_activity_at) touch(:last_trade_activity_at)
end end

View file

@ -0,0 +1,36 @@
- title t(".title.#{@type}")
- hide_title_header
= render partial: "items/item_header",
locals: {item: @item, trades: @item_trades,
current_subpage: "trades_#{@type}",
current_user_lists: @current_user_lists,
current_user_quantities: @current_user_quantities}
%h2.item-subpage-title= t(".title.#{@type}")
- if @trades.present?
%table.trades-table
%thead
%tr
%th= t(".table.headings.last_active")
%th= t(".table.headings.user.#{@type}")
%th= t(".table.headings.lists")
%tbody
- sorted_vaguely_by_trade_activity(@trades).each do |trade|
%tr
%td
= vague_trade_timestamp trade.user.last_trade_activity_at
%td= trade.user.name
%td
- if trade.lists.present?
%ul.trade-list-names
- trade.lists.each do |list|
%li= link_to list.name, user_closet_hangers_path(trade.user,
anchor: "closet-list-#{list.id}")
- else
= link_to t(".table.not_in_a_list.#{@type}"), user_closet_hangers_path(trade.user,
anchor: "closet-hangers-group-#{@type == :offering}"),
class: "not-in-a-list"
- else
%p= t(".no_trades_yet")

View file

@ -0,0 +1,90 @@
- raise ArgumentError unless defined? item
- raise ArgumentError unless defined? trades
- raise ArgumentError unless defined? current_user_lists
- raise ArgumentError unless defined? current_user_quantities
- raise ArgumentError unless defined? current_subpage
%header.item-header
.item-header-main
= image_tag item.thumbnail_url, class: 'item-thumbnail'
%h2.item-name= item.name
%nav.item-links
- if item.nc?
%abbr.item-kind{'data-type' => 'nc', title: t('items.show.item_kinds.nc.description')}
= t('items.show.item_kinds.nc.label')
- elsif item.pb?
%abbr.item-kind{'data-type' => 'pb', title: t('items.show.item_kinds.pb.description')}
= t('items.show.item_kinds.pb.label')
- else
%abbr.item-kind{'data-type' => 'np', title: t('items.show.item_kinds.np.description')}
= t('items.show.item_kinds.np.label')
= link_to t('items.show.resources.jn_items'), jn_items_url_for(item)
- if item.nc_trade_value
= link_to t('items.show.resources.owls', value: item.nc_trade_value.value_text),
"https://www.neopets.com/~owls",
title: nc_trade_value_updated_at_text(item.nc_trade_value)
- unless item.nc?
= link_to t('items.show.resources.shop_wizard'), shop_wizard_url_for(item)
= link_to t('items.show.resources.super_shop_wizard'), super_shop_wizard_url_for(item)
= link_to t('items.show.resources.trading_post'), trading_post_url_for(item)
= link_to t('items.show.resources.auction_genie'), auction_genie_url_for(item)
- if user_signed_in?
.user-lists-section
= link_to t('items.show.closet_hangers.button'),
user_closet_hangers_path(current_user),
class: 'dialog-opener'
%dialog
%h3
= t 'items.show.closet_hangers.header_html',
user_items_link: link_to(t('your_items'),
user_closet_hangers_path(current_user))
= form_tag update_quantities_user_item_closet_hangers_path(:user_id => current_user, :item_id => item), :method => :put do
.closet-hangers-ownership-groups
- current_user_lists.each do |owned, lists|
%div
%h4= closet_lists_group_name(:you, owned)
%ul
- lists.each_with_index do |list, index|
%li
= number_field_tag "quantity[#{list.id}]",
current_user_quantities[list.id], min: 0,
autofocus: owned && index == 0
= label_tag "quantity[#{list.id}]", list.name
%li
= number_field_tag "quantity[#{owned}]",
current_user_quantities[owned], min: 0,
autofocus: owned && lists.empty?
- unless lists.empty?
= label_tag "quantity[#{owned}]",
t('closet_lists.unlisted_name'),
:class => 'unlisted'
- else
= label_tag "quantity[#{owned}]",
t('items.show.closet_hangers.quantity_label')
= submit_tag t('items.show.closet_hangers.submit')
%p.item-description= item.description
%nav.item-subpages-nav
= link_to t('items.show.subpages_nav.preview'), item,
class: ['preview-link'], 'data-is-current' => current_subpage == 'preview'
.trades-section
%header= t('items.show.subpages_nav.trades.header')
%ul
%li
= link_to t('items.show.subpages_nav.trades.offering',
count: trades[:offering].size),
item_trades_path(item, type: 'offering'),
'data-is-current' => current_subpage == 'trades_offering'
%li
= link_to t('items.show.subpages_nav.trades.seeking',
count: trades[:offering].size),
item_trades_path(item, type: 'seeking'),
'data-is-current' => current_subpage == 'trades_seeking'
- content_for :javascripts do
= javascript_include_tag 'items/item_header'

View file

@ -1,84 +1,10 @@
- title @item.name - title @item.name
- canonical_path @item - canonical_path @item
%header#item-header = render partial: "item_header",
= image_tag @item.thumbnail_url, :id => 'item-thumbnail' locals: {item: @item, trades: @trades, current_subpage: "preview",
%h2#item-name= @item.name current_user_lists: @current_user_lists,
%nav#item-links current_user_quantities: @current_user_quantities}
= nc_icon_for(@item)
- unless @item.rarity.blank?
== #{t '.rarity'}: #{@item.rarity_index} (#{@item.rarity})
= link_to t('.resources.jn_items'), jn_items_url_for(@item)
- if @item.nc_trade_value
= link_to t('.resources.owls', value: @item.nc_trade_value.value_text),
"https://www.neopets.com/~owls",
title: nc_trade_value_updated_at_text(@item.nc_trade_value)
- unless @item.nc?
= link_to t('.resources.shop_wizard'), shop_wizard_url_for(@item)
= link_to t('.resources.super_shop_wizard'), super_shop_wizard_url_for(@item)
= link_to t('.resources.trading_post'), trading_post_url_for(@item)
= link_to t('.resources.auction_genie'), auction_genie_url_for(@item)
%section#item-info-section
#item-info
%p= @item.description
#item-zones
%p
%strong #{t '.zones.occupied_header'}:
= list_zones @occupied_zones, :uncertain_label
%p
%strong #{t '.zones.restricted_header'}:
- if @restricted_zones.empty?
= t '.zones.none'
- else
= list_zones @restricted_zones
#trade-hangers
- if Time.now < Date.new(2024, 1, 26)
%p
✨⏳️
%i We now only show recently-updated lists here!
⏳️✨
- [true, false].each do |owned|
%p
%strong
= trading_users_header(owned, @trading_users_by_owned[owned].size)
= render_trading_users(owned)
%span.toggle
%button.more= t '.trading_users.show_more'
%button.less= t '.trading_users.show_less'
- if user_signed_in?
#your-items-form
%h3
= t '.closet_hangers.header_html',
:user_items_link => link_to(t('your_items'),
user_closet_hangers_path(current_user))
= form_tag update_quantities_user_item_closet_hangers_path(:user_id => current_user, :item_id => @item), :method => :put do
#closet-hangers-ownership-groups
- @current_user_lists.each do |owned, lists|
%div
%h4= closet_lists_group_name(:you, owned)
%ul
- lists.each do |list|
%li
= number_field_tag "quantity[#{list.id}]",
@current_user_quantities[list.id], :min => 0
= label_tag "quantity[#{list.id}]", list.name
%li
= number_field_tag "quantity[#{owned}]",
@current_user_quantities[owned], :min => 0
- unless lists.empty?
= label_tag "quantity[#{owned}]",
t('closet_lists.unlisted_name'),
:class => 'unlisted'
- else
= label_tag "quantity[#{owned}]",
t('.closet_hangers.quantity_label')
= submit_tag t('.closet_hangers.submit')
#outfit-preview-root{'data-item-id': @item.id} #outfit-preview-root{'data-item-id': @item.id}

View file

@ -270,7 +270,6 @@ en-MEEP:
description: You want this meepit description: You want this meepit
show: show:
rarity: Meepity
resources: resources:
jn_items: JN Meepits jn_items: JN Meepits
shop_wizard: Meep Wizard shop_wizard: Meep Wizard
@ -285,18 +284,6 @@ en-MEEP:
occupied_header: Occupeeps occupied_header: Occupeeps
restricted_header: Restreeps restricted_header: Restreeps
none: Meepless none: Meepless
trading_users:
header:
owned:
zero: We don't know anymeep who has this item meep for trade.
one: "1 user has this item meep for trade:"
other: "%{count} users have this item meep for trade:"
wanted:
zero: "We don't know anymeep who meeps this item."
one: "1 user meeps this item:"
other: "%{count} users meep this item:"
show_more: meep more
show_less: meep less
preview: preview:
header: Meepview header: Meepview
customize_more: Customize meep customize_more: Customize meep

View file

@ -298,7 +298,16 @@ en:
description: You want this item description: You want this item
show: show:
rarity: Rarity item_kinds:
nc:
label: NC
description: Purchaseable with Neocash
np:
label: NP
description: Purchaseable with Neopoints
pb:
label: PB
description: Only obtainable via paintbrush
resources: resources:
jn_items: JN Items jn_items: JN Items
owls: "Owls: %{value}" owls: "Owls: %{value}"
@ -307,6 +316,7 @@ en:
trading_post: Trades trading_post: Trades
auction_genie: Auctions auction_genie: Auctions
closet_hangers: closet_hangers:
button: Add to your lists
header_html: Track this in %{user_items_link} header_html: Track this in %{user_items_link}
quantity_label: How many? quantity_label: How many?
submit: Save to Your Items submit: Save to Your Items
@ -314,18 +324,12 @@ en:
occupied_header: Occupies occupied_header: Occupies
restricted_header: Restricts restricted_header: Restricts
none: None none: None
trading_users: subpages_nav:
header: preview: Preview
owned: trades:
zero: We don't know anyone who has this item up for trade. header: Trades
one: "1 user has this item up for trade:" offering: Offering (%{count})
other: "%{count} users have this item up for trade:" seeking: Seeking (%{count})
wanted:
zero: "We don't know anyone who wants this item."
one: "1 user wants this item:"
other: "%{count} users want this item:"
show_more: more
show_less: less
preview: preview:
header: Preview header: Preview
customize_more: Customize more customize_more: Customize more
@ -369,6 +373,27 @@ en:
user_wants: wants user_wants: wants
fits_pet_type: fits fits_pet_type: fits
item_trades:
index:
title:
offering: "Trades: Offering"
seeking: "Trades: Seeking"
table:
headings:
last_active: Last active
user:
offering: Owner
seeking: Seeker
lists: Lists
last_active:
this_week: This week
not_in_a_list:
offering: Items they own
seeking: Items they want
no_trades_yet:
No trades yet! To add your name to this page, add this item to one of
your lists, and mark the list as "Trading".
neopets_page_import_tasks: neopets_page_import_tasks:
create: create:
success: Page %{index} saved! success: Page %{index} saved!

View file

@ -209,7 +209,6 @@ es:
abbr: Buscado abbr: Buscado
description: Quieres este objeto description: Quieres este objeto
show: show:
rarity: Rareza
resources: resources:
jn_items: Objetos de JN jn_items: Objetos de JN
shop_wizard: Asistente de Tiendas shop_wizard: Asistente de Tiendas
@ -224,18 +223,6 @@ es:
occupied_header: Ocupa occupied_header: Ocupa
restricted_header: Restringe restricted_header: Restringe
none: Nada none: Nada
trading_users:
header:
owned:
zero: No conocemos a nadie que tenga este objeto para intercambiar.
one: "1 usuario tiene este objeto para intercambiar:"
other: "%{count} usuarios tienen este objeto para intercambiar:"
wanted:
zero: "No conocemos a nadie que busque este objeto."
one: "1 usuario busca este objeto:"
other: "%{count} usuarios buscan este objeto:"
show_more: más
show_less: menos
preview: preview:
header: Previsualizar header: Previsualizar
customize_more: Personalizar más customize_more: Personalizar más

View file

@ -209,7 +209,6 @@ pt:
abbr: Procura abbr: Procura
description: Você procura esse item description: Você procura esse item
show: show:
rarity: Raridade
resources: resources:
jn_items: JN Itens jn_items: JN Itens
shop_wizard: Mágico Pecincheiro shop_wizard: Mágico Pecincheiro
@ -224,18 +223,6 @@ pt:
occupied_header: Ocupações occupied_header: Ocupações
restricted_header: Restrições restricted_header: Restrições
none: Nada none: Nada
trading_users:
header:
owned:
zero: Ninguém quer trocar esse item
one: "1 usuário quer trocar esse item:"
other: "%{count} usuários possuem, e querem trocar esse item:"
wanted:
zero: "Nós não conhecemos ninguém que procure esse item."
one: "1 usuário procura esse item:"
other: "%{count} usuários procuram esse item:"
show_more: mais
show_less: menos
preview: preview:
header: Pré-Visualizar header: Pré-Visualizar
customize_more: Personalize mais customize_more: Personalize mais

View file

@ -18,7 +18,11 @@ OpenneoImpressItems::Application.routes.draw do
# Our customization data! Both the item pages, and JSON API endpoints. # Our customization data! Both the item pages, and JSON API endpoints.
resources :items, :only => [:index, :show] do resources :items, :only => [:index, :show] do
resources :trades, path: 'trades/:type', controller: 'item_trades',
only: [:index], constraints: {type: /offering|seeking/}
resources :appearances, controller: 'item_appearances', only: [:index] resources :appearances, controller: 'item_appearances', only: [:index]
collection do collection do
get :needed get :needed
end end