impress/app/controllers/wardrobe_controller.rb
Emi Matchu 8d728c84d1 [WV2] Add zone badges to search results
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>
2026-02-15 20:54:00 -08:00

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