Show which zones each item occupies in search result cards. Zone badges appear as small, muted labels (e.g., "Background", "Hat", "Static") after the NC/NP and date badges. Items that occupy multiple zones show multiple badges. Zone badges only appear in search results, not in outfit items, since outfit items are already grouped by zone which provides sufficient context. Implementation: - Created zones badge partial to render zone labels from item appearances - Updated controller to load item appearances with zone data for search results - Pass appearance data through the rendering chain (search_results → item_card → item_card_content) - Added subtle CSS styling for zone badges (light gray background, gray text) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
195 lines
6.9 KiB
Ruby
195 lines
6.9 KiB
Ruby
class WardrobeController < ApplicationController
|
|
prepend_view_path Rails.root.join("app/views/wardrobe")
|
|
|
|
def show
|
|
# Load saved outfit if an ID is provided (e.g. /outfits/:id/v2)
|
|
@saved_outfit = Outfit.find(params[:id]) if params[:id].present?
|
|
|
|
# If visiting a saved outfit with no state params, redirect with the
|
|
# outfit's state as query params. This keeps URL-as-source-of-truth simple:
|
|
# the rest of the action always reads from params.
|
|
if @saved_outfit && !outfit_state_params_present?
|
|
redirect_to wardrobe_v2_outfit_path(@saved_outfit, **@saved_outfit.wardrobe_params)
|
|
return
|
|
end
|
|
|
|
# Set the form target path for all wardrobe forms
|
|
@wardrobe_path = @saved_outfit ? wardrobe_v2_outfit_path(@saved_outfit) : wardrobe_v2_path
|
|
|
|
# Get selected species and color from params, or default to Blue Acara
|
|
@selected_species = params[:species] ? Species.find_by_id(params[:species]) : Species.find_by_name("Acara")
|
|
@selected_color = params[:color] ? Color.find_by_id(params[:color]) : Color.find_by_name("Blue")
|
|
|
|
# Load valid colors for the selected species (colors that have existing pet types)
|
|
@species = Species.alphabetical
|
|
@colors = @selected_species.compatible_colors
|
|
|
|
# Find the best pet type for this species+color combo
|
|
# If the exact combo doesn't exist, this will fall back to a simple color
|
|
@pet_type = PetType.for_species_and_color(
|
|
species_id: @selected_species.id,
|
|
color_id: @selected_color.id
|
|
)
|
|
|
|
# Use the pet type's actual color as the selected color
|
|
# (might differ from requested color if we fell back to a simple color)
|
|
@selected_color = @pet_type&.color
|
|
|
|
# Get the selected pose from params, or default to nil (will use canonical)
|
|
@selected_pose = params[:pose]
|
|
|
|
# Find the pet state for the selected pose, or use canonical
|
|
@pet_state = if @pet_type && @selected_pose.present?
|
|
@pet_type.pet_states.with_pose(@selected_pose).first || @pet_type.canonical_pet_state
|
|
else
|
|
@pet_type&.canonical_pet_state
|
|
end
|
|
|
|
# If we found a pet_state, use its actual pose as the selected pose
|
|
@selected_pose = @pet_state&.pose
|
|
|
|
# Load all available poses for this pet type (for the pose picker)
|
|
@available_poses = @pet_type ? available_poses_for(@pet_type) : {}
|
|
|
|
# Preload the layers for all available poses so the thumbnails render efficiently
|
|
if @pet_type
|
|
pose_pet_states = @available_poses.values.compact
|
|
SwfAsset.preload_manifests(pose_pet_states.flat_map(&:swf_assets))
|
|
end
|
|
|
|
# Load alt style from params, scoped to the current species
|
|
@alt_style = if params[:style].present? && @selected_species
|
|
AltStyle.where(species_id: @selected_species.id).find_by(id: params[:style])
|
|
end
|
|
|
|
# Load all available alt styles for this species (for the style picker)
|
|
@available_alt_styles = @selected_species ?
|
|
AltStyle.where(species_id: @selected_species.id).by_name_grouped : []
|
|
|
|
# Load items from the objects[] and closet[] parameters
|
|
worn_item_ids = params[:objects] || []
|
|
closeted_item_ids = params[:closet] || []
|
|
worn_items = Item.where(id: worn_item_ids)
|
|
closeted_items = Item.where(id: closeted_item_ids)
|
|
|
|
# Build the outfit
|
|
@outfit = Outfit.new(
|
|
name: @saved_outfit ? @saved_outfit.name : (params[:name].presence || "Untitled outfit"),
|
|
pet_state: @pet_state,
|
|
alt_style: @alt_style,
|
|
worn_items: worn_items,
|
|
closeted_items: closeted_items,
|
|
)
|
|
|
|
# Preload the manifests for all visible layers, so they load efficiently
|
|
# in parallel rather than sequentially when rendering
|
|
SwfAsset.preload_manifests(@outfit.visible_layers)
|
|
|
|
# Also preload alt style layer manifests for the style picker thumbnails
|
|
SwfAsset.preload_manifests(@alt_style.swf_assets.to_a) if @alt_style
|
|
|
|
# Compute saved outfit state for the view
|
|
if @saved_outfit
|
|
@has_unsaved_changes = !@outfit.same_wardrobe_state_as?(@saved_outfit)
|
|
@is_owner = user_signed_in? && current_user.id == @saved_outfit.user_id
|
|
else
|
|
@has_unsaved_changes = false
|
|
end
|
|
|
|
# Handle search mode
|
|
@search_mode = params[:q].present?
|
|
if @search_mode
|
|
search_filters = build_search_filters(params[:q], @outfit)
|
|
query_params = ActionController::Parameters.new(
|
|
search_filters.each_with_index.map { |filter, i| [i.to_s, filter] }.to_h
|
|
)
|
|
@query = Item::Search::Query.from_params(query_params, current_user)
|
|
@search_results = @query.results.paginate(page: params.dig(:q, :page), per_page: 30)
|
|
|
|
# Load appearances for search results to get zone information
|
|
target = @alt_style || @pet_type
|
|
if target
|
|
@appearances_by_item_id = Item.appearances_for(@search_results, target, swf_asset_includes: [:zone])
|
|
end
|
|
end
|
|
|
|
render layout: false
|
|
end
|
|
|
|
private
|
|
|
|
# Returns a hash of pose => pet_state for all the main poses,
|
|
# indicating which poses are available for this pet type.
|
|
# Uses the same logic as the Rainbow Pool to pick the "canonical" pet state
|
|
# for each pose when multiple states exist.
|
|
def available_poses_for(pet_type)
|
|
poses_hash = {}
|
|
|
|
# Group all pet states by pose, then pick the best one for each pose
|
|
# using emotion_order (same logic as Rainbow Pool)
|
|
pet_type.pet_states.emotion_order.group_by(&:pose).each do |pose, states|
|
|
# Only include the main poses (skip UNKNOWN, UNCONVERTED, etc.)
|
|
if PetState::MAIN_POSES.include?(pose)
|
|
poses_hash[pose] = states.first
|
|
end
|
|
end
|
|
|
|
# Ensure all main poses are in the hash, even if nil
|
|
PetState::MAIN_POSES.each do |pose|
|
|
poses_hash[pose] ||= nil
|
|
end
|
|
|
|
poses_hash
|
|
end
|
|
|
|
def outfit_state_params_present?
|
|
params[:species].present? || params[:color].present? || params[:objects].present? || params[:closet].present?
|
|
end
|
|
|
|
def build_search_filters(query_params, outfit)
|
|
filters = []
|
|
|
|
# Add name filter if present
|
|
if query_params[:name].present?
|
|
filters << { key: "name", value: query_params[:name] }
|
|
end
|
|
|
|
# Add item kind filter if present
|
|
if query_params[:item_kind].present?
|
|
case query_params[:item_kind]
|
|
when "nc"
|
|
filters << { key: "is_nc", value: "true" }
|
|
when "np"
|
|
filters << { key: "is_np", value: "true" }
|
|
when "pb"
|
|
filters << { key: "is_pb", value: "true" }
|
|
end
|
|
end
|
|
|
|
# Add zone filter if present
|
|
if query_params[:zone].present?
|
|
filters << { key: "occupied_zone_set_name", value: query_params[:zone] }
|
|
end
|
|
|
|
# Always add auto-filter for items that fit the current pet
|
|
pet_type = outfit.pet_type
|
|
if pet_type
|
|
fit_filter = {
|
|
key: "fits",
|
|
value: {
|
|
species_id: pet_type.species_id.to_s,
|
|
color_id: pet_type.color_id.to_s
|
|
}
|
|
}
|
|
|
|
# Include alt_style_id if present
|
|
if outfit.alt_style_id.present?
|
|
fit_filter[:value][:alt_style_id] = outfit.alt_style_id.to_s
|
|
end
|
|
|
|
filters << fit_filter
|
|
end
|
|
|
|
filters
|
|
end
|
|
end
|