Compare commits
18 commits
68578aa929
...
42b1bf3e8c
Author | SHA1 | Date | |
---|---|---|---|
42b1bf3e8c | |||
5d577db649 | |||
8e3d2b994f | |||
507b346c2c | |||
77d88e50a6 | |||
abe2747b93 | |||
31468c9682 | |||
e8832f2c36 | |||
e4fb067e45 | |||
b9bb697ca1 | |||
eb6f196b15 | |||
4b9e11fc2a | |||
bfb11e94e3 | |||
c6927c2ce8 | |||
402e3d4afb | |||
a03ae90697 | |||
80e158caf7 | |||
b1c1bea7be |
23 changed files with 502 additions and 376 deletions
22
app/assets/javascripts/items/item_header.js
Normal file
22
app/assets/javascripts/items/item_header.js
Normal 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);
|
||||
}
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
@import "partials/campaign-progress"
|
||||
|
||||
body.items
|
||||
body.items, body.item_trades
|
||||
+campaign-progress
|
||||
|
||||
text-align: center
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
@import items
|
||||
@import items/index
|
||||
@import items/show
|
||||
@import item_trades/index
|
||||
@import outfits/index
|
||||
@import outfits/new
|
||||
@import pets/bulk
|
||||
|
|
29
app/assets/stylesheets/item_trades/_index.sass
Normal file
29
app/assets/stylesheets/item_trades/_index.sass
Normal 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: ", "
|
|
@ -1,151 +1,10 @@
|
|||
@import "../partials/clean/constants"
|
||||
@import "../partials/clean/mixins"
|
||||
@import "../partials/item_header"
|
||||
|
||||
body.items-show
|
||||
#item-header
|
||||
border-bottom: 1px solid $module-border-color
|
||||
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-header
|
||||
+item-header
|
||||
|
||||
#item-contributors
|
||||
+subtle-banner
|
||||
|
@ -172,13 +31,3 @@ body.items-show
|
|||
.nc-icon
|
||||
height: 16px
|
||||
width: 16px
|
||||
|
||||
&.js
|
||||
#trade-hangers
|
||||
p
|
||||
max-height: 3em
|
||||
overflow: hidden
|
||||
|
||||
&.showing-more
|
||||
max-height: none
|
||||
|
||||
|
|
158
app/assets/stylesheets/partials/_item_header.sass
Normal file
158
app/assets/stylesheets/partials/_item_header.sass
Normal 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
|
29
app/controllers/item_trades_controller.rb
Normal file
29
app/controllers/item_trades_controller.rb
Normal 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
|
|
@ -58,49 +58,15 @@ class ItemsController < ApplicationController
|
|||
|
||||
respond_to do |format|
|
||||
format.html do
|
||||
@occupied_zones = @item.occupied_zones(
|
||||
scope: Zone.includes_translations.alphabetical
|
||||
)
|
||||
@restricted_zones = @item.restricted_zones(
|
||||
scope: Zone.includes_translations.alphabetical
|
||||
)
|
||||
@trades = @item.closet_hangers.trading.user_is_active.to_trades
|
||||
|
||||
@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?
|
||||
# Empty arrays are important so that we can loop over this and still
|
||||
# show the generic no-list case
|
||||
@current_user_lists = {true => [], false => []}
|
||||
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
|
||||
@current_user_lists = current_user.closet_lists.alphabetical.
|
||||
group_by_owned
|
||||
@current_user_quantities = current_user.item_quantities_for(@item)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
format.gif do
|
||||
|
|
26
app/helpers/item_trades_helper.rb
Normal file
26
app/helpers/item_trades_helper.rb
Normal 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
|
|
@ -50,10 +50,6 @@ module ItemsHelper
|
|||
content_tag :div, content, :class => 'closeted-icons'
|
||||
end
|
||||
|
||||
def list_zones(zones, method=:label)
|
||||
zones.map(&method).join(', ')
|
||||
end
|
||||
|
||||
def nc_icon
|
||||
image_tag 'nc.png', :title => t('items.item.nc.description'),
|
||||
:alt => t('items.item.nc.abbr'), :class => 'nc-icon'
|
||||
|
@ -83,17 +79,6 @@ module ItemsHelper
|
|||
"https://www.neopets.com/genie.phtml?type=process_genie&criteria=exact&auctiongenie=#{CGI::escape item.name}"
|
||||
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)
|
||||
" (×#{count})".html_safe if count > 1
|
||||
end
|
||||
|
|
|
@ -13,29 +13,3 @@ ReactDOM.render(
|
|||
</AppProvider>,
|
||||
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);
|
||||
}
|
||||
|
|
|
@ -153,6 +153,43 @@ class ClosetHanger < ApplicationRecord
|
|||
# If quantity is zero and there's no hanger, good. Do nothing.
|
||||
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
|
||||
|
||||
def list_belongs_to_user
|
||||
|
|
|
@ -87,6 +87,13 @@ class ClosetList < ApplicationRecord
|
|||
end
|
||||
end
|
||||
|
||||
def self.group_by_owned
|
||||
h = all.group_by(&:hangers_owned?)
|
||||
h[true] ||= []
|
||||
h[false] ||= []
|
||||
h
|
||||
end
|
||||
|
||||
include VisibilityMethods
|
||||
|
||||
class Null
|
||||
|
|
|
@ -278,17 +278,6 @@ class Item < ApplicationRecord
|
|||
write_attribute('species_support_ids', replacement)
|
||||
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)
|
||||
species_support_ids.blank? || species_support_ids.include?(species.id)
|
||||
end
|
||||
|
|
|
@ -175,6 +175,18 @@ class User < ApplicationRecord
|
|||
contact_neopets_connection.try(:neopets_username)
|
||||
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
|
||||
touch(:last_trade_activity_at)
|
||||
end
|
||||
|
|
36
app/views/item_trades/index.html.haml
Normal file
36
app/views/item_trades/index.html.haml
Normal 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")
|
90
app/views/items/_item_header.haml
Normal file
90
app/views/items/_item_header.haml
Normal 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'
|
|
@ -1,84 +1,10 @@
|
|||
- title @item.name
|
||||
- canonical_path @item
|
||||
|
||||
%header#item-header
|
||||
= image_tag @item.thumbnail_url, :id => 'item-thumbnail'
|
||||
%h2#item-name= @item.name
|
||||
%nav#item-links
|
||||
= 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')
|
||||
= render partial: "item_header",
|
||||
locals: {item: @item, trades: @trades, current_subpage: "preview",
|
||||
current_user_lists: @current_user_lists,
|
||||
current_user_quantities: @current_user_quantities}
|
||||
|
||||
#outfit-preview-root{'data-item-id': @item.id}
|
||||
|
||||
|
|
|
@ -270,7 +270,6 @@ en-MEEP:
|
|||
description: You want this meepit
|
||||
|
||||
show:
|
||||
rarity: Meepity
|
||||
resources:
|
||||
jn_items: JN Meepits
|
||||
shop_wizard: Meep Wizard
|
||||
|
@ -285,18 +284,6 @@ en-MEEP:
|
|||
occupied_header: Occupeeps
|
||||
restricted_header: Restreeps
|
||||
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:
|
||||
header: Meepview
|
||||
customize_more: Customize meep
|
||||
|
|
|
@ -298,7 +298,16 @@ en:
|
|||
description: You want this item
|
||||
|
||||
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:
|
||||
jn_items: JN Items
|
||||
owls: "Owls: %{value}"
|
||||
|
@ -307,6 +316,7 @@ en:
|
|||
trading_post: Trades
|
||||
auction_genie: Auctions
|
||||
closet_hangers:
|
||||
button: Add to your lists
|
||||
header_html: Track this in %{user_items_link}
|
||||
quantity_label: How many?
|
||||
submit: Save to Your Items
|
||||
|
@ -314,18 +324,12 @@ en:
|
|||
occupied_header: Occupies
|
||||
restricted_header: Restricts
|
||||
none: None
|
||||
trading_users:
|
||||
header:
|
||||
owned:
|
||||
zero: We don't know anyone who has this item up for trade.
|
||||
one: "1 user has this item up for trade:"
|
||||
other: "%{count} users have this item up for trade:"
|
||||
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
|
||||
subpages_nav:
|
||||
preview: Preview
|
||||
trades:
|
||||
header: Trades
|
||||
offering: Offering (%{count})
|
||||
seeking: Seeking (%{count})
|
||||
preview:
|
||||
header: Preview
|
||||
customize_more: Customize more
|
||||
|
@ -369,6 +373,27 @@ en:
|
|||
user_wants: wants
|
||||
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:
|
||||
create:
|
||||
success: Page %{index} saved!
|
||||
|
|
|
@ -209,7 +209,6 @@ es:
|
|||
abbr: Buscado
|
||||
description: Quieres este objeto
|
||||
show:
|
||||
rarity: Rareza
|
||||
resources:
|
||||
jn_items: Objetos de JN
|
||||
shop_wizard: Asistente de Tiendas
|
||||
|
@ -224,18 +223,6 @@ es:
|
|||
occupied_header: Ocupa
|
||||
restricted_header: Restringe
|
||||
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:
|
||||
header: Previsualizar
|
||||
customize_more: Personalizar más
|
||||
|
|
|
@ -209,7 +209,6 @@ pt:
|
|||
abbr: Procura
|
||||
description: Você procura esse item
|
||||
show:
|
||||
rarity: Raridade
|
||||
resources:
|
||||
jn_items: JN Itens
|
||||
shop_wizard: Mágico Pecincheiro
|
||||
|
@ -224,18 +223,6 @@ pt:
|
|||
occupied_header: Ocupações
|
||||
restricted_header: Restrições
|
||||
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:
|
||||
header: Pré-Visualizar
|
||||
customize_more: Personalize mais
|
||||
|
|
|
@ -18,7 +18,11 @@ OpenneoImpressItems::Application.routes.draw do
|
|||
|
||||
# Our customization data! Both the item pages, and JSON API endpoints.
|
||||
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]
|
||||
|
||||
collection do
|
||||
get :needed
|
||||
end
|
||||
|
|
Loading…
Reference in a new issue