diff --git a/Gemfile b/Gemfile index 182c1134..17deeed4 100644 --- a/Gemfile +++ b/Gemfile @@ -25,6 +25,8 @@ gem 'rdiscount', '~> 1.6.5' gem 'RocketAMF', '~> 0.2.1' gem 'will_paginate', '~> 3.0.pre2' +gem 'jammit', '~> 0.5.3' + group :test do gem 'factory_girl_rails', '~> 1.0' gem 'rspec-rails', '~> 2.0.0.beta.22' diff --git a/Gemfile.lock b/Gemfile.lock index d9ae3746..3310f168 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -67,6 +67,7 @@ GEM arel (1.0.1) activesupport (~> 3.0.0) builder (2.1.2) + closure-compiler (0.3.3) compass (0.10.5) haml (>= 3.0.4) diff-lcs (1.1.2) @@ -78,6 +79,9 @@ GEM rails (>= 3.0.0.beta4) haml (3.0.21) i18n (0.4.1) + jammit (0.5.3) + closure-compiler (>= 0.1.0) + yui-compressor (>= 0.9.1) mail (2.2.6.1) activesupport (>= 2.3.6) mime-types @@ -122,6 +126,7 @@ GEM polyglot (>= 0.3.1) tzinfo (0.3.23) will_paginate (3.0.pre2) + yui-compressor (0.9.1) PLATFORMS ruby @@ -136,6 +141,7 @@ DEPENDENCIES eventmachine! factory_girl_rails (~> 1.0) haml (~> 3.0.18) + jammit (~> 0.5.3) mysqlplus! rack-fiber_pool rails (= 3.0.0) diff --git a/app/controllers/items_controller.rb b/app/controllers/items_controller.rb index ca35e5a4..bf0acf9d 100644 --- a/app/controllers/items_controller.rb +++ b/app/controllers/items_controller.rb @@ -10,10 +10,10 @@ class ItemsController < ApplicationController else per_page = nil end - @results = Item.search(@query).alphabetize.paginate :page => params[:page], :per_page => per_page + @items = Item.search(@query).alphabetize.paginate :page => params[:page], :per_page => per_page respond_to do |format| format.html { render } - format.js { render :json => {:items => @results, :total_pages => @results.total_pages}, :callback => params[:callback] } + format.js { render :json => {:items => @items, :total_pages => @items.total_pages}, :callback => params[:callback] } end rescue respond_to do |format| @@ -21,6 +21,11 @@ class ItemsController < ApplicationController format.js { render :json => {:error => $!.message}, :callback => params[:callback] } end end + elsif params.has_key?(:ids) && params[:ids].is_a?(Array) + @items = Item.find(params[:ids]) + respond_to do |format| + format.json { render :json => @items } + end else respond_to do |format| format.html { render } diff --git a/app/controllers/outfits_controller.rb b/app/controllers/outfits_controller.rb new file mode 100644 index 00000000..14b40bba --- /dev/null +++ b/app/controllers/outfits_controller.rb @@ -0,0 +1,5 @@ +class OutfitsController < ApplicationController + def edit + render :layout => false + end +end diff --git a/app/controllers/pet_attributes_controller.rb b/app/controllers/pet_attributes_controller.rb new file mode 100644 index 00000000..6b6ba936 --- /dev/null +++ b/app/controllers/pet_attributes_controller.rb @@ -0,0 +1,8 @@ +class PetAttributesController < ApplicationController + def index + render :json => { + :color => Color.all, + :species => Species.all + } + end +end diff --git a/app/controllers/pet_types_controller.rb b/app/controllers/pet_types_controller.rb index d56f93d2..cc1dacd0 100644 --- a/app/controllers/pet_types_controller.rb +++ b/app/controllers/pet_types_controller.rb @@ -1,7 +1,10 @@ class PetTypesController < ApplicationController def show pet_type = PetType.find_by_color_id_and_species_id(params[:color_id], params[:species_id]) - raise ActiveRecord::RecordNotFound unless pet_type - render :json => pet_type + if pet_type + render :json => pet_type.as_json(:for => params[:for]) + else + render :json => nil + end end end diff --git a/app/controllers/pets_controller.rb b/app/controllers/pets_controller.rb index fb37b2a7..63fa8c2f 100644 --- a/app/controllers/pets_controller.rb +++ b/app/controllers/pets_controller.rb @@ -1,7 +1,7 @@ -class PetsController < ActionController::Base +class PetsController < ApplicationController def show @pet = Pet.load(params[:id]) @pet.save - redirect_to @pet.wardrobe_url + redirect_to wardrobe_path(:anchor => @pet.wardrobe_query) end end diff --git a/app/controllers/swf_assets_controller.rb b/app/controllers/swf_assets_controller.rb index f403f8c5..c07ac358 100644 --- a/app/controllers/swf_assets_controller.rb +++ b/app/controllers/swf_assets_controller.rb @@ -10,6 +10,18 @@ class SwfAssetsController < ApplicationController @swf_assets = @swf_assets.fitting_standard_body_ids json = @swf_assets.all.group_by(&:body_id) end + elsif params[:body_id] + swf_assets = SwfAsset.arel_table + rels = ParentSwfAssetRelationship.arel_table + @swf_assets = SwfAsset.select('swf_assets.*, parents_swf_assets.parent_id'). + fitting_body_id(params[:body_id]). + joins(:object_asset_relationships). + where(rels[:parent_id].in(params[:item_ids])) + json = @swf_assets.map { |a| a.as_json(:parent_id => a.parent_id.to_i, :for => 'wardrobe') } + elsif params[:pet_state_id] + @swf_assets = PetState.find(params[:pet_state_id]).swf_assets.all + pet_state_id = params[:pet_state_id].to_i + json = @swf_assets.map { |a| a.as_json(:parent_id => pet_state_id, :for => 'wardrobe') } elsif params[:pet_type_id] @swf_assets = PetType.find(params[:pet_type_id]).pet_states.first.swf_assets end diff --git a/app/models/item.rb b/app/models/item.rb index a6d651c2..8253bcea 100644 --- a/app/models/item.rb +++ b/app/models/item.rb @@ -3,7 +3,7 @@ class Item < ActiveRecord::Base has_many :parent_swf_asset_relationships, :foreign_key => 'parent_id', :conditions => {:swf_asset_type => SwfAssetType} - has_many :swf_assets, :through => :parent_swf_asset_relationships + has_many :swf_assets, :through => :parent_swf_asset_relationships, :source => :object_asset attr_writer :current_body_id @@ -118,7 +118,7 @@ class Item < ActiveRecord::Base swf_assets = SwfAsset.arel_table ids_to_delete = self.parent_swf_asset_relationships. select(:id). - joins(:swf_asset). + joins(:object_asset). where(rels[:swf_asset_id].in(new_swf_asset_ids).not). where(swf_assets[:body_id].in([@current_body_id, 0])). map(&:id) @@ -208,7 +208,7 @@ class Item < ActiveRecord::Base relationship.swf_asset_type = SwfAssetType relationship.swf_asset_id = swf_asset.id end - relationship.swf_asset = swf_asset + relationship.object_asset = swf_asset relationships_by_item_id[item_id] ||= [] relationships_by_item_id[item_id] << relationship end diff --git a/app/models/parent_swf_asset_relationship.rb b/app/models/parent_swf_asset_relationship.rb index f44abfdc..6b5b8d47 100644 --- a/app/models/parent_swf_asset_relationship.rb +++ b/app/models/parent_swf_asset_relationship.rb @@ -1,7 +1,12 @@ class ParentSwfAssetRelationship < ActiveRecord::Base set_table_name 'parents_swf_assets' - belongs_to :swf_asset + belongs_to :biology_asset, :class_name => 'SwfAsset', :foreign_key => 'swf_asset_id', :conditions => {:type => 'biology'} + belongs_to :object_asset, :class_name => 'SwfAsset', :foreign_key => 'swf_asset_id', :conditions => {:type => 'object'} + + def swf_asset + self.swf_asset_type == 'biology' ? self.biology_asset : self.object_asset + end def item parent diff --git a/app/models/pet.rb b/app/models/pet.rb index df9a6fbc..82b3aec4 100644 --- a/app/models/pet.rb +++ b/app/models/pet.rb @@ -35,19 +35,14 @@ class Pet < ActiveRecord::Base true end - def wardrobe_url - uri = URI::HTTP.build({ - :host => RemoteImpressHost, - :path => WARDROBE_PATH, - :fragment => { - :name => self.name, - :color => self.pet_type.color.id, - :species => self.pet_type.species.id, - :state => self.pet_state.id, - :objects => self.items.map(&:id) - }.to_query - }) - uri.to_s + def wardrobe_query + { + :name => self.name, + :color => self.pet_type.color.id, + :species => self.pet_type.species.id, + :state => self.pet_state.id, + :objects => self.items.map(&:id) + }.to_query end before_save do diff --git a/app/models/pet_attribute.rb b/app/models/pet_attribute.rb index 5697333a..5f70a6d3 100644 --- a/app/models/pet_attribute.rb +++ b/app/models/pet_attribute.rb @@ -1,4 +1,11 @@ class PetAttribute < StaticResource + def as_json(options={}) + { + :id => self.id, + :name => self.name.capitalize + } + end + def self.find_by_name(name) @objects_by_name[name.downcase] end diff --git a/app/models/pet_state.rb b/app/models/pet_state.rb index 5f501da7..8c459c5a 100644 --- a/app/models/pet_state.rb +++ b/app/models/pet_state.rb @@ -3,7 +3,7 @@ class PetState < ActiveRecord::Base has_many :parent_swf_asset_relationships, :foreign_key => 'parent_id', :conditions => {:swf_asset_type => SwfAssetType} - has_many :swf_assets, :through => :parent_swf_asset_relationships + has_many :swf_assets, :through => :parent_swf_asset_relationships, :source => :biology_asset belongs_to :pet_type @@ -63,7 +63,7 @@ class PetState < ActiveRecord::Base relationship.swf_asset_type = SwfAssetType relationship.swf_asset_id = swf_asset.id end - relationship.swf_asset = swf_asset + relationship.biology_asset = swf_asset relationships << relationship end end diff --git a/app/models/pet_type.rb b/app/models/pet_type.rb index 7ff5fa87..c02e3feb 100644 --- a/app/models/pet_type.rb +++ b/app/models/pet_type.rb @@ -25,7 +25,9 @@ class PetType < ActiveRecord::Base } def as_json(options={}) - {:id => id, :body_id => body_id} + json = {:id => id, :body_id => body_id} + json[:pet_state_ids] = self.pet_state_ids if options[:for] == 'wardrobe' + json end def color_id=(new_color_id) diff --git a/app/models/swf_asset.rb b/app/models/swf_asset.rb index 3d7ef07e..c9f8fd53 100644 --- a/app/models/swf_asset.rb +++ b/app/models/swf_asset.rb @@ -3,6 +3,9 @@ class SwfAsset < ActiveRecord::Base LOCAL_ASSET_DIR = Rails.root.join('public', PUBLIC_ASSET_DIR) set_inheritance_column 'inheritance_type' + has_many :object_asset_relationships, :class_name => 'ParentSwfAssetRelationship', + :conditions => {:swf_asset_type => 'object'} + delegate :depth, :to => :zone scope :fitting_body_id, lambda { |body_id| @@ -19,13 +22,21 @@ class SwfAsset < ActiveRecord::Base end def as_json(options={}) - { + json = { :id => id, :depth => depth, - :local_url => local_url, :body_id => body_id, - :zone_id => zone_id + :zone_id => zone_id, + :zones_restrict => zones_restrict, + :is_body_specific => body_specific? } + if options[:for] == 'wardrobe' + json[:local_path] = local_url + else + json[:local_url] = local_url + end + json[:parent_id] = options[:parent_id] if options[:parent_id] + json end def body_specific? @@ -78,7 +89,7 @@ class SwfAsset < ActiveRecord::Base before_save do # If an asset body ID changes, that means more than one body ID has been # linked to it, meaning that it's probably wearable by all bodies. - self.body_id = 0 if self.body_id_changed? || !self.body_specific? + self.body_id = 0 if !self.body_specific? || (!self.new_record? && self.body_id_changed?) end private diff --git a/app/views/items/index.html.haml b/app/views/items/index.html.haml index 41e331e2..b2bb7eaa 100644 --- a/app/views/items/index.html.haml +++ b/app/views/items/index.html.haml @@ -1,5 +1,5 @@ -- if @results - - if @results.empty? +- if @items + - if @items.empty? :markdown We couldn't find any wearables that matched **#{@query}**. Sorry! @@ -8,9 +8,9 @@ [1]: http://impress.openneo.net/ - else - = will_paginate @results - = render @results - = will_paginate @results + = will_paginate @items + = render @items + = will_paginate @items - else #search-help %h2 Find what you're looking for diff --git a/app/views/outfits/edit.html.haml b/app/views/outfits/edit.html.haml new file mode 100644 index 00000000..073734f0 --- /dev/null +++ b/app/views/outfits/edit.html.haml @@ -0,0 +1,120 @@ +!!! +%html + %head + %title Dress to Impress - Planning an outfit + /[if lt IE 9] + = javascript_include_tag "http://html5shiv.googlecode.com/svn/trunk/html5.js" + = stylesheet_link_tag "http://#{RemoteImpressHost}/assets/css/clean.css" + %body.standard.outfits-edit.fullscreen + #container + %a#home-link{:href => "/"} + %span Dress to Impress + %h1#title Planning an outfit + #pet-type-not-found.possible-error + We haven't seen that combination before. Have you? + Submit the pet's name if you have! + #preview-toolbar + %form#pet-type-form + %select{:name => "color"} + %select{:name => "species"} + %input{:type => "submit", :value => "Go"}/ + %form#pet-state-form + Gender/Emotions: + %ul + #sharing + %input#short-url-response{:type => "text", :value => "http://www.example.com/"}/ + %button#short-url-button + Short URL + #share-button-wrapper + %button#share-button.addthis_button + %img{:src => "http://s7.addthis.com/static/t00/logo1414.gif"}/ + Share + #preview + #preview-swf + %p Flash and Javascript (but not Java!) are required to preview outfits. + %p If this message stays after the page is done loading, check those first. + #preview-sidebar + #preview-closet + %h2 Closet + %ul + %p#fullscreen-copyright + Images © 2000-2010 Neopets, Inc. All Rights Reserved. + Used With Permission + %form#preview-search-form + %header + %h2 Add an item + %input{:name => "query", :placeholder => "Search items...", :type => "search"}/ + %input{:type => "submit", :value => "Go"}/ + #preview-search-form-pagination + %a#preview-search-form-clear{:href => "#"} clear + %dl#preview-search-form-help + %div + %dt kreludor "altador cup" -background + %dd + returns any item with the word "kreludor" and the phrase "altador cup" + in it, but not the word "background" + %div + %dt collar -is:nc -is:pb + %dd + returns any item with the word "collar" in it, but is not from the NC + mall, and is not from a deluxe paint brush set + %div + %dt + %span species: + %span.search-helper{"data-search-filter" => "species"} Acara + %dd + returns any item a + %span.search-helper{"data-search-filter" => "species"} Acara + can wear + %div + %dt + %span type: + %span.search-helper{"data-search-filter" => "type"} background + %dd + returns any item that fills a + %span.search-helper{"data-search-filter" => "type"} background + zone + #preview-search-form-loading Loading... + #preview-search-form-error.possible-error + #preview-search-form-no-results + No results for "" + %ul + #no-assets-full-message + We haven't seen this item on this body type before. Have you? Submit its name on the home page if you have! + /[if IE] + + %script{:src => "http://ajax.googleapis.com/ajax/libs/jquery/1.4.0/jquery.min.js", :type => "text/javascript"} + %script{:src => "http://ajax.googleapis.com/ajax/libs/swfobject/2.2/swfobject.js", :type => "text/javascript"} + %script{:src => "http://bit.ly/javascript-api.js?version=latest&login=openneo&apiKey=R_4d0438829b7a99860de1d3edf55d8dc8", :type => "text/javascript"} + %script{:src => "http://s7.addthis.com/js/250/addthis_widget.js#username=openneo", :type => "text/javascript"} + %script{:src => "http://#{RemoteImpressHost}/assets/timestamped/js/jquery.jgrowl-v1278204174.js", :type => "text/javascript"} + = include_javascripts :edit_outfit_package + #userbar + #userbar-login-with Login with: + %ul#userbar-auth-servers + %li + %a{:href => "http://id.openneo.net/?app=impress&path=%2Fwardrobe&session_id=a9ddeffb83a923447e2f40a2fe8387dc"} + %img{:src => "http://id.openneo.net/favicon.png"}/ + %span OpenNeo ID + #footer + %ul + %li + %a{:href => "http://openneo.net/", :target => "_blank"} OpenNeo + %li + %a{:href => "http://blog.openneo.net/", :target => "_blank"} Blog + %li + %a{:href => "http://forum.openneo.net/", :target => "_blank"} Forum + %li + %a{:href => "http://github.com/matchu/openneo-impress"} The Source Code + %li + %a{:href => "/terms.html"} Terms of Use + %div + Contact: + %ul + %li + %a{:href => "http://openneo.uservoice.com/forums/40720-dress-to-impress"} Feedback + %li + %a{:href => "mailto:webmaster@openneo.net"} Questions, comments, bug reports + %p + Images © 2000-2010 Neopets, Inc. All Rights Reserved. + Used With Permission diff --git a/config/assets.yml b/config/assets.yml new file mode 100644 index 00000000..5e430863 --- /dev/null +++ b/config/assets.yml @@ -0,0 +1,6 @@ +embed_assets: on + +javascripts: + edit_outfit_package: + - public/javascripts/wardrobe.js + - public/javascripts/outfits/edit.js diff --git a/config/routes.rb b/config/routes.rb index b2414ea3..3ce5b77d 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,15 +1,21 @@ OpenneoImpressItems::Application.routes.draw do |map| match '/' => 'items#index', :as => :items match '/index.js' => 'items#index', :format => :js + match '/items.json' => 'items#index', :format => :json match '/item_zone_sets.js' => 'ItemZoneSets#index' + match '/bodies/:body_id/swf_assets.json' => 'swf_assets#index', :as => :body_swf_assets match '/items/:item_id/swf_assets.json' => 'swf_assets#index', :as => :item_swf_assets match '/items/:item_id/bodies/:body_id/swf_assets.json' => 'swf_assets#index', :as => :item_swf_assets_for_body_id match '/pet_types/:pet_type_id/swf_assets.json' => 'swf_assets#index', :as => :pet_type_swf_assets + match '/pet_states/:pet_state_id/swf_assets.json' => 'swf_assets#index', :as => :pet_state_swf_assets match '/species/:species_id/color/:color_id/pet_type.json' => 'pet_types#show' + resources :items, :only => [:index] + resources :pet_attributes, :only => [:index] resources :pets, :only => [:show] + match '/wardrobe' => 'outfits#edit', :as => :wardrobe match '/:id' => 'items#show', :as => :item end diff --git a/public/javascripts/outfits/edit.js b/public/javascripts/outfits/edit.js new file mode 100644 index 00000000..6c3325dc --- /dev/null +++ b/public/javascripts/outfits/edit.js @@ -0,0 +1,733 @@ +var Partial = {}, main_wardrobe, + View = Wardrobe.getStandardView({ + Preview: { + swf_url: '/swfs/preview.swf?v=0.12', + wrapper: $('#preview'), + placeholder: $('#preview-swf') + } + }); + +Partial.ItemSet = function ItemSet(wardrobe, selector) { + var item_set = this, ul = $(selector), items = [], setClosetItems, + setOutfitItems, setOutfitItemsControls, no_assets_full_message = $('#no-assets-full-message'), + container = $('#container'); + + Partial.ItemSet.setWardrobe(wardrobe); + + function prepSetSpecificItems(type) { + return function (specific_items) { + var item, worn, li; + for(var i = 0; i < items.length; i++) { + item = items[i]; + in_set = $.inArray(item, specific_items) != -1; + li = $('li.object-' + item.id).toggleClass(type, in_set). + data('item', item).data(type, in_set).children('ul'). + children('li.control-set-for-' + type).remove().end() + [type == 'worn' ? 'prepend' : 'append'] + (Partial.ItemSet.CONTROL_SETS[type][in_set].clone()); + } + } + } + + setClosetItems = prepSetSpecificItems('closeted'); + + setOutfitItemsControls = prepSetSpecificItems('worn'); + setOutfitItems = function (specific_items) { + setOutfitItemsControls(specific_items); + setHasAssets(specific_items); + } + + function setHasAssets(specific_items) { + var item, no_assets, li, no_assets_message; + for(var i = 0, l = specific_items.length; i < l; i++) { + item = specific_items[i]; + no_assets = item.couldNotLoadAssetsFitting(wardrobe.outfit.pet_type); + li = $('li.object-' + item.id).toggleClass('no-assets', no_assets); + (function (li) { + no_assets_message = li.find('span.no-assets-message'); + no_assets_message.remove(); + if(no_assets) { + $('', {'class': 'no-assets-message', text: 'No data yet'}).appendTo(li); + } + })(li); + } + } + + this.setItems = function (new_items) { + var item, li, controls, info_link; + items = new_items; + ul.children().remove(); + for(var i = 0; i < items.length; i++) { + item = items[i]; + li = $('
  • ', {'class': 'object object-' + item.id}); + img = $('', { + 'src': item.thumbnail_url, + 'alt': item.description, + 'title': item.description + }); + controls = $('