diff --git a/Gemfile b/Gemfile
index ce79d514..0c091191 100644
--- a/Gemfile
+++ b/Gemfile
@@ -64,6 +64,8 @@ gem "rails-i18n"
gem 'rack-attack', '~> 2.2.0'
+gem 'react-rails', '~> 0.8.0.0'
+
# Needed for the new asset pipeline
group :assets do
diff --git a/Gemfile.lock b/Gemfile.lock
index f249239f..b37a0528 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -222,6 +222,11 @@ GEM
rdiscount (1.6.8)
rdoc (3.12.2)
json (~> 1.4)
+ react-rails (0.8.0.0)
+ execjs
+ rails (>= 3.1)
+ react-source (= 0.8.0)
+ react-source (0.8.0)
redis (3.0.3)
redis-namespace (1.2.1)
redis (~> 3.0.0)
@@ -333,6 +338,7 @@ DEPENDENCIES
rails (= 3.2.16)
rails-i18n
rdiscount (~> 1.6.5)
+ react-rails (~> 0.8.0.0)
resque (~> 1.23.0)
resque-retry (~> 0.1.0)
resque-scheduler (~> 2.0.0.d)
diff --git a/app/assets/javascripts/modeling.js.jsx b/app/assets/javascripts/modeling.js.jsx
new file mode 100644
index 00000000..38fff43f
--- /dev/null
+++ b/app/assets/javascripts/modeling.js.jsx
@@ -0,0 +1,130 @@
+/** @jsx React.DOM */
+
+// Console-polyfill. MIT license.
+// https://github.com/paulmillr/console-polyfill
+// Make it safe to do console.log() always.
+(function (con) {
+ 'use strict';
+ var prop, method;
+ var empty = {};
+ var dummy = function() {};
+ var properties = 'memory'.split(',');
+ var methods = ('assert,count,debug,dir,dirxml,error,exception,group,' +
+ 'groupCollapsed,groupEnd,info,log,markTimeline,profile,profileEnd,' +
+ 'time,timeEnd,trace,warn').split(',');
+ while (prop = properties.pop()) con[prop] = con[prop] || empty;
+ while (method = methods.pop()) con[method] = con[method] || dummy;
+})(window.console = window.console || {});
+
+(function() {
+ var Neopia = {
+ User: {
+ get: function(id) {
+ return Neopia.getJSON("/users/" + id).then(function(response) {
+ return response.users[0];
+ });
+ }
+ },
+ Customization: {
+ get: function(petId) {
+ return Neopia.getJSON("/pets/" + petId + "/customization").then(function(response) {
+ return response.custom_pet;
+ });
+ }
+ },
+ getJSON: function(path) {
+ return $.getJSON(Neopia.API_URL + path);
+ },
+ init: function() {
+ var hostEl = $('meta[name=neopia-host]');
+ if (!hostEl.length) {
+ throw "missing neopia-host meta tag";
+ }
+ var host = hostEl.attr('content');
+ if (!host) {
+ throw "neopia-host meta tag exists, but is empty";
+ }
+ Neopia.API_URL = "http://" + host + "/api/1";
+ }
+ };
+
+ var Modeling = {
+ _modelForItemComponents: [],
+ _customizationsByPetId: {},
+ _addCustomization: function(customization) {
+ this._customizationsByPetId[customization.name] = customization;
+ this._update();
+ },
+ _getCustomizations: function() {
+ var modelCustomizationsByPetId = this._customizationsByPetId;
+ return Object.keys(modelCustomizationsByPetId).map(function(petId) {
+ return modelCustomizationsByPetId[petId];
+ });
+ },
+ _loadPetCustomization: function(neopiaPetId) {
+ return Neopia.Customization.get(neopiaPetId)
+ .done(this._addCustomization.bind(this));
+ },
+ _loadManyPetsCustomizations: function(neopiaPetIds) {
+ return neopiaPetIds.map(this._loadPetCustomization.bind(this));
+ },
+ _loadUserCustomizations: function(neopiaUserId) {
+ return Neopia.User.get(neopiaUserId).then(function(neopiaUser) {
+ return neopiaUser.links.pets;
+ }).then(this._loadManyPetsCustomizations.bind(this));
+ },
+ _loadManyUsersCustomizations: function(neopiaUserIds) {
+ return neopiaUserIds.map(this._loadUserCustomizations.bind(this));
+ },
+ _update: function() {
+ var state = {
+ customizations: this._getCustomizations()
+ };
+ this._modelForItemComponents.forEach(function(c) {
+ c.setState(state);
+ });
+ },
+ init: function() {
+ Neopia.init();
+ this._modelForItemComponents = $('#newest-unmodeled-items li').map(function() {
+ return React.renderComponent(,
+ $(this).find('.models').get(0));
+ }).toArray();
+ var users = ["borovan", "donna"];
+ this._loadManyUsersCustomizations(users);
+ }
+ };
+
+ var ModelForItem = React.createClass({
+ getInitialState: function() {
+ return {customizations: []};
+ },
+ render: function() {
+ function createModelPet(customization) {
+ return ;
+ }
+ var sortedCustomizations = this.state.customizations.sort(function(a, b) {
+ var aName = a.name.toLowerCase();
+ var bName = b.name.toLowerCase();
+ if (aName < bName) return -1;
+ if (aName > bName) return 1;
+ return 0;
+ });
+ return
{sortedCustomizations.map(createModelPet)}
;
+ }
+ });
+
+ var ModelPet = React.createClass({
+ render: function() {
+ var petName = this.props.customization.name;
+ var imageSrc = "http://pets.neopets.com/cpn/" + petName + "/1/1.png";
+ return ;
+ }
+ });
+
+ Modeling.init();
+})();
diff --git a/app/assets/stylesheets/outfits/_new.sass b/app/assets/stylesheets/outfits/_new.sass
index 752e036f..52438ed6 100644
--- a/app/assets/stylesheets/outfits/_new.sass
+++ b/app/assets/stylesheets/outfits/_new.sass
@@ -164,7 +164,7 @@ body.outfits-new
#newest-unmodeled-items
list-style: none
- li
+ > li
+clearfix
margin: .5em 0
@@ -208,13 +208,44 @@ body.outfits-new
height: 80px
width: 80px
+ .missing-bodies, .models
+ margin-left: 82px
+ padding-left: 8px
+ padding-right: 8px
+
.missing-bodies
font-size: 85%
- margin-left: 82px
- padding: .5em 8px
+ padding-bottom: .5em
+ padding-top: .5em
p
font-family: $main-font
margin-bottom: .5em
+
+ .models
+ font-size: 85%
+
+ li
+ display: inline-block
+ margin-bottom: 2px
+ margin-right: 2px
+
+ button
+ +reset-awesome-button
+ +border-radius(4px)
+ +box-shadow(0 1px 3px rgba(0, 0, 0, .5))
+ background: $module-bg-color
+ border: 1px solid $module-border-color
+ padding-right: 8px
+
+ &:active
+ position: relative
+ top: 1px
+
+ img
+ height: 40px
+ margin-right: 8px
+ vertical-align: middle
+ width: 40px
#latest-contribution
+subtle-banner
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index e427f9eb..705983c1 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -87,6 +87,7 @@ module ApplicationHelper
:bitly => 'http://bit.ly/javascript-api.js?version=latest&login=openneo&apiKey=R_4d0438829b7a99860de1d3edf55d8dc8',
:html5 => 'http://html5shim.googlecode.com/svn/trunk/html5.js',
:jquery => 'http://ajax.googleapis.com/ajax/libs/jquery/1.4.3/jquery.min.js',
+ :jquery20 => 'http://ajax.googleapis.com/ajax/libs/jquery/2.0.3/jquery.min.js',
:jquery_tmpl => 'http://ajax.microsoft.com/ajax/jquery.templates/beta1/jquery.tmpl.min.js',
:swfobject => 'http://ajax.googleapis.com/ajax/libs/swfobject/2.2/swfobject.js'
}
diff --git a/app/helpers/outfits_helper.rb b/app/helpers/outfits_helper.rb
index f76a5c2d..ed50c393 100644
--- a/app/helpers/outfits_helper.rb
+++ b/app/helpers/outfits_helper.rb
@@ -50,8 +50,12 @@ module OutfitsHelper
content_tag(:dd, search_query_description(base, filter_key))
end
+ def neopia_host
+ Rails.configuration.neopia_host
+ end
+
def remote_load_pet_path
- "http://#{Rails.configuration.neopia_host}/api/1/pet/customization"
+ "http://#{neopia_host}/api/1/pet/customization"
end
def render_predicted_missing_species_by_color(species_by_color)
diff --git a/app/views/outfits/new.html.haml b/app/views/outfits/new.html.haml
index 1f57426d..f706ca3a 100644
--- a/app/views/outfits/new.html.haml
+++ b/app/views/outfits/new.html.haml
@@ -87,6 +87,7 @@
%span.meter{style: "width: #{@newest_unmodeled_items_predicted_modeled_ratio[item]*100}%"}
.missing-bodies
= render_predicted_missing_species_by_color(@newest_unmodeled_items_predicted_missing_species_by_color[item])
+ .models
- if @newest_modeled_items.present?
%h3= t '.newest_items.modeled.header'
%ul#newest-modeled-items
@@ -110,6 +111,9 @@
%script#preview-pet-not-found-template{:type => 'text/x-jquery-tmpl'}
= t '.preview.pet_not_found'
+- content_for :meta do
+ %meta{name: 'neopia-host', content: neopia_host}
+
- content_for :javascripts do
- = include_javascript_libraries :jquery, :jquery_tmpl
- = javascript_include_tag 'jquery.timeago', 'pet_query', 'outfits/new'
\ No newline at end of file
+ = include_javascript_libraries :jquery20, :jquery_tmpl
+ = javascript_include_tag 'react', 'jquery.timeago', 'pet_query', 'outfits/new', 'modeling'
\ No newline at end of file
diff --git a/config/environments/development.rb b/config/environments/development.rb
index 7cc98857..60312fd4 100644
--- a/config/environments/development.rb
+++ b/config/environments/development.rb
@@ -30,6 +30,8 @@ OpenneoImpressItems::Application.configure do
# Expands the lines which load the assets
config.assets.debug = true
+
+ config.react.variant = :development
end
LocalImpressHost = 'betanewimpress.openneo.net'
diff --git a/config/environments/production.rb b/config/environments/production.rb
index 6f76de21..361292e5 100644
--- a/config/environments/production.rb
+++ b/config/environments/production.rb
@@ -59,6 +59,8 @@ OpenneoImpressItems::Application.configure do
# Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies.
# config.force_ssl = true
+
+ config.react.variant = :production
end
LocalImpressHost = 'newimpress.openneo.net'
diff --git a/vendor/cache/react-rails-0.8.0.0.gem b/vendor/cache/react-rails-0.8.0.0.gem
new file mode 100644
index 00000000..68761c11
Binary files /dev/null and b/vendor/cache/react-rails-0.8.0.0.gem differ
diff --git a/vendor/cache/react-source-0.8.0.gem b/vendor/cache/react-source-0.8.0.gem
new file mode 100644
index 00000000..6ea3fd31
Binary files /dev/null and b/vendor/cache/react-source-0.8.0.gem differ