From 276cc1b5ea1be7e37629194bb2f52b7091d32fb9 Mon Sep 17 00:00:00 2001 From: Emi Matchu Date: Sun, 2 Nov 2025 07:43:54 +0000 Subject: [PATCH] Wardrobe V2 initial proof-of-concept --- app/assets/javascripts/outfits/new_v2.js | 6 ++ app/assets/stylesheets/outfits/new_v2.sass | 106 +++++++++++++++++++++ app/controllers/outfits_controller.rb | 32 ++++++- app/views/outfits/new_v2.html.haml | 68 +++++++++++++ config/routes.rb | 1 + 5 files changed, 212 insertions(+), 1 deletion(-) create mode 100644 app/assets/javascripts/outfits/new_v2.js create mode 100644 app/assets/stylesheets/outfits/new_v2.sass create mode 100644 app/views/outfits/new_v2.html.haml diff --git a/app/assets/javascripts/outfits/new_v2.js b/app/assets/javascripts/outfits/new_v2.js new file mode 100644 index 00000000..cc202d01 --- /dev/null +++ b/app/assets/javascripts/outfits/new_v2.js @@ -0,0 +1,6 @@ +// Wardrobe v2 - Simple Rails+Turbo outfit editor +// +// This page uses Turbo Frames for instant updates when changing species/color. +// The outfit_viewer Web Component handles the pet rendering. + +console.log("Wardrobe v2 loaded!"); diff --git a/app/assets/stylesheets/outfits/new_v2.sass b/app/assets/stylesheets/outfits/new_v2.sass new file mode 100644 index 00000000..6fd17975 --- /dev/null +++ b/app/assets/stylesheets/outfits/new_v2.sass @@ -0,0 +1,106 @@ +body.wardrobe-v2 + margin: 0 + padding: 0 + height: 100vh + overflow: hidden + font-family: sans-serif + +.wardrobe-container + display: flex + height: 100vh + background: #000 + + @media (max-width: 800px) + flex-direction: column + +.outfit-preview-section + flex: 1 + display: flex + align-items: center + justify-content: center + background: #000 + position: relative + min-height: 400px + + outfit-viewer + width: 100% + height: 100% + + .no-preview-message + color: white + text-align: center + padding: 2rem + font-size: 1.2rem + +.outfit-controls-section + width: 400px + background: #fff + padding: 2rem + overflow-y: auto + box-shadow: -2px 0 10px rgba(0, 0, 0, 0.3) + + @media (max-width: 800px) + width: 100% + max-height: 40vh + + h1 + margin-top: 0 + font-size: 1.75rem + color: #448844 + + h2 + font-size: 1.25rem + color: #448844 + margin-top: 2rem + +.species-color-picker + margin: 1.5rem 0 + + .form-group + margin-bottom: 1rem + + label + display: block + font-weight: bold + margin-bottom: 0.5rem + color: #333 + + select + width: 100% + padding: 0.5rem + font-size: 1rem + border: 1px solid #ccc + border-radius: 4px + font-family: inherit + + &:focus + outline: none + border-color: #448844 + +.current-selection + padding: 1rem + background: #f0f0f0 + border-radius: 4px + margin: 1rem 0 + + p + margin: 0 + color: #666 + + strong + color: #000 + +.worn-items + margin-top: 2rem + + ul + list-style: none + padding: 0 + margin: 0.5rem 0 + + li + padding: 0.5rem + background: #f9f9f9 + margin-bottom: 0.5rem + border-radius: 4px + color: #333 diff --git a/app/controllers/outfits_controller.rb b/app/controllers/outfits_controller.rb index 97ca9cb8..c7cb687d 100644 --- a/app/controllers/outfits_controller.rb +++ b/app/controllers/outfits_controller.rb @@ -68,7 +68,7 @@ class OutfitsController < ApplicationController end @species_count = Species.count - + @latest_contribution = Contribution.recent.first Contribution.preload_contributeds_and_parents([@latest_contribution].compact) @@ -77,6 +77,36 @@ class OutfitsController < ApplicationController @campaign = Fundraising::Campaign.current rescue nil end + def new_v2 + @colors = Color.alphabetical + @species = Species.alphabetical + + # Get selected species and color from params, or default to Blue Acara + species_id = params[:species] || Species.find_by_name("Acara")&.id + color_id = params[:color] || Color.find_by_name("Blue")&.id + + # Find the pet type (species+color combination) + @selected_species = Species.find_by(id: species_id) + @selected_color = Color.find_by(id: color_id) + @pet_type = PetType.find_by(species_id: species_id, color_id: color_id) if @selected_species && @selected_color + + # Load items from the objects[] parameter + item_ids = params[:objects] || [] + items = Item.where(id: item_ids) + + # Build the outfit + @outfit = Outfit.new( + pet_state: @pet_type&.canonical_pet_state, + worn_items: 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) if @outfit.pet_state + + render layout: false + end + def show @outfit = Outfit.find(params[:id]) diff --git a/app/views/outfits/new_v2.html.haml b/app/views/outfits/new_v2.html.haml new file mode 100644 index 00000000..e683238a --- /dev/null +++ b/app/views/outfits/new_v2.html.haml @@ -0,0 +1,68 @@ +- title "Wardrobe v2" + +!!! 5 +%html + %head + %meta{charset: 'utf-8'} + %meta{name: 'viewport', content: 'width=device-width, initial-scale=1'} + %title= yield :title + %link{href: image_path('favicon.png'), rel: 'icon'} + = stylesheet_link_tag "application/hanger-spinner" + = stylesheet_link_tag "application/outfit-viewer" + = page_stylesheet_link_tag "outfits/new_v2" + = csrf_meta_tags + = javascript_include_tag "application", async: true + = javascript_include_tag "idiomorph", async: true + = javascript_include_tag "outfit-viewer", async: true + = javascript_include_tag "outfits/new_v2", async: true + %body.wardrobe-v2 + = turbo_frame_tag "outfit-editor" do + .wardrobe-container + .outfit-preview-section + - if @outfit.pet_state + = outfit_viewer @outfit + - elsif @pet_type.nil? + .no-preview-message + %p + We haven't seen this kind of pet before! Try a different species/color + combination. + - else + .no-preview-message + %p Loading... + + .outfit-controls-section + %h1 Customize your pet + + .species-color-picker + = form_with url: wardrobe_v2_path, method: :get do |f| + .form-group + = label_tag :species, "Species:" + = select_tag :species, + options_from_collection_for_select(@species, "id", "human_name", + @selected_species&.id), + onchange: "this.form.requestSubmit()" + + .form-group + = label_tag :color, "Color:" + = select_tag :color, + options_from_collection_for_select(@colors, "id", "human_name", + @selected_color&.id), + onchange: "this.form.requestSubmit()" + + -# Preserve item IDs in the URL + - if params[:objects].present? + - params[:objects].each do |item_id| + = hidden_field_tag "objects[]", item_id + + - if @outfit.pet_state + .current-selection + %p + Currently showing: + %strong= "#{@selected_color.human_name} #{@selected_species.human_name}" + + - if @outfit.worn_items.any? + .worn-items + %h2 Items (#{@outfit.worn_items.size}) + %ul + - @outfit.worn_items.each do |item| + %li= item.name diff --git a/config/routes.rb b/config/routes.rb index 89aebd8c..0c951f2a 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -10,6 +10,7 @@ OpenneoImpressItems::Application.routes.draw do # TODO: It's a bit silly that outfits/new points to outfits#edit. # Should we refactor the controller/view structure here? get '/outfits/new', to: 'outfits#edit', as: :wardrobe + get '/outfits/new/v2', to: 'outfits#new_v2', as: :wardrobe_v2 get '/wardrobe' => redirect('/outfits/new') get '/start/:color_name/:species_name' => 'outfits#start'