Wardrobe V2 initial proof-of-concept

This commit is contained in:
Emi Matchu 2025-11-02 07:43:54 +00:00
parent 4e2c99d4dd
commit 276cc1b5ea
5 changed files with 212 additions and 1 deletions

View file

@ -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!");

View file

@ -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

View file

@ -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])

View file

@ -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

View file

@ -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'