class ItemsController < ApplicationController before_action :set_query before_action :support_staff_only, except: [:index, :show, :sources] rescue_from Item::Search::Error, :with => :search_error def index if @query if params[:per_page] per_page = params[:per_page].to_i per_page = 50 if per_page && per_page > 50 else per_page = 30 end @items = @query.results.paginate( page: params[:page], per_page: per_page) assign_closeted!(@items) respond_to do |format| format.html { @campaign = Fundraising::Campaign.current rescue nil if @items.count == 1 redirect_to @items.first else render end } format.json { render json: { items: @items.as_json( methods: [:nc?, :pb?, :owned?, :wanted?], include: { restricted_zones: { only: [:id, :depth, :label], methods: [:is_commonly_used_by_items], }, }, ), appearances: load_appearances.as_json( include: { swf_assets: { only: [:id, :remote_id, :body_id], include: { zone: { only: [:id, :depth, :label], methods: [:is_commonly_used_by_items], }, restricted_zones: { only: [:id, :depth, :label], methods: [:is_commonly_used_by_items], }, }, methods: [:urls, :known_glitches], }, } ), total_pages: @items.total_pages, query: @query.to_s, } } end elsif params.has_key?(:ids) && params[:ids].is_a?(Array) @items = Item.find(params[:ids]) assign_closeted!(@items) respond_to do |format| format.json { render json: @items } end else respond_to do |format| format.html { @campaign = Fundraising::Campaign.current rescue nil @newest_items = Item.newest.limit(18) } end end end def show @item = Item.find params[:id] respond_to do |format| format.html do @trades = @item.closet_hangers.trading.user_is_active.to_trades @contributors_with_counts = @item.contributors_with_counts 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 @selected_preview_pet_type = load_selected_preview_pet_type @preview_outfit = Outfit.new( pet_state: load_preview_pet_type.canonical_pet_state, worn_items: [@item], ) @preview_error = validate_preview @all_appearances = @item.appearances @appearances_by_occupied_zone = @item.appearances_by_occupied_zone. sort_by { |z, a| z.label } @selected_item_appearance = @preview_outfit.item_appearances.first @preview_pet_type_options = PetType.where(color: @preview_outfit.color). includes(:species).merge(Species.alphabetical) end format.gif do expires_in 1.month redirect_to @item.thumbnail_url, allow_other_host: true end end end def edit @item = Item.find params[:id] render layout: "application" end def update @item = Item.find params[:id] if @item.update(item_params) flash[:notice] = "\"#{@item.name}\" successfully saved!" redirect_to @item else render action: "edit", layout: "application" end end def sources # Load all the items, then group them by source. item_ids = params[:ids].split(",") @all_items = Item.where(id: item_ids).includes(:nc_mall_record). includes(:dyeworks_base_item).order(:name).limit(50) @items = @all_items.group_by(&:source).tap { |i| i.default = [] } assign_closeted!(@all_items) if @all_items.empty? render file: "public/404.html", status: :not_found, layout: nil return end # For Dyeworks items whose base is currently in the NC Mall, preload their # trade values. We'll use this to determine which ones are fully buyable rn # (because Owls tracks this data and we don't). Item.preload_nc_trade_values(@items[:dyeworks]) # Start loading the NC trade values for the non-Mall NC items. trade_values_task = Async { Item.preload_nc_trade_values(@items[:other_nc]) } # 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 = @items[:pb].group_by(&:pb_color). sort_by { |color, items| color&.name }.to_h colors_without_thumbnails = @pb_items_by_color.keys. select(&:present?).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 # Create a second value that only include the items the user *needs*: that # is, that they don't already own. @items_needed = @items.transform_values { |items| items.reject(&:owned?) } @pb_items_needed_by_color = @pb_items_by_color.transform_values { |items| items.reject(&:owned?) } # Finish loading the NC trade values. trade_values_task.wait render layout: "application" end protected def item_params params.require(:item).permit( :name, :thumbnail_url, :description, :modeling_status_hint, :is_manually_nc ).tap do |p| p[:modeling_status_hint] = nil if p[:modeling_status_hint] == "" end end def assign_closeted!(items) current_user.assign_closeted_to_items!(items) if user_signed_in? end def load_appearances appearance_params = params[:with_appearances_for] return {} if appearance_params.blank? if appearance_params[:alt_style_id].present? target = Item::Search::Query.load_alt_style_by_id( appearance_params[:alt_style_id]) else target = Item::Search::Query.load_pet_type_by_color_and_species( appearance_params[:color_id], appearance_params[:species_id]) end target.appearances_for(@items, swf_asset_includes: [:zone]). tap do |appearances| # Preload the manifests for these SWF assets concurrently, rather than # loading them in sequence when we generate the JSON. swf_assets = appearances.values.map(&:swf_assets).flatten SwfAsset.preload_manifests(swf_assets) end end def load_selected_preview_pet_type color_id = params.dig(:preview, :color_id) species_id = params.dig(:preview, :species_id) return load_default_preview_pet_type if color_id.nil? || species_id.nil? PetType.find_or_initialize_by(color_id:, species_id:).tap do |pet_type| if pet_type.persisted? cookies["preferred-preview-color-id"] = color_id cookies["preferred-preview-species-id"] = species_id end end end def load_preview_pet_type if @selected_preview_pet_type.persisted? @selected_preview_pet_type else load_default_preview_pet_type end end def load_default_preview_pet_type @item.compatible_pet_types. preferring_species(cookies["preferred-preview-species-id"] || ""). preferring_color(cookies["preferred-preview-color-id"] || ""). preferring_simple.first || PetType.matching_name("Blue", "Acara").first! end def validate_preview if @selected_preview_pet_type.new_record? :pet_type_does_not_exist elsif @preview_outfit.item_appearances.any?(&:empty?) :no_item_data end end def search_error(e) @items = [] @query = params[:q] respond_to do |format| format.html { flash.now[:alert] = e.message; render } format.json { render :json => {error: e.message} } end end def set_query q = params[:q] if q.is_a?(String) @query = Item::Search::Query.from_text(q, current_user) elsif q.is_a?(ActionController::Parameters) @query = Item::Search::Query.from_params(q, current_user) end end end