2014-01-03 13:11:02 -08:00
|
|
|
|
/** @jsx React.DOM */
|
|
|
|
|
|
2014-01-20 11:56:19 -08:00
|
|
|
|
(function($, I18n) {
|
2014-01-04 13:06:08 -08:00
|
|
|
|
// Console-polyfill. MIT license.
|
|
|
|
|
// https://github.com/paulmillr/console-polyfill
|
|
|
|
|
// Make it safe to do console.log() always.
|
|
|
|
|
var console = (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;
|
|
|
|
|
return con;
|
|
|
|
|
})(window.console || {});
|
|
|
|
|
|
2014-01-03 13:11:02 -08:00
|
|
|
|
var Neopia = {
|
|
|
|
|
User: {
|
|
|
|
|
get: function(id) {
|
2014-01-18 19:54:11 -08:00
|
|
|
|
return $.ajax({
|
|
|
|
|
dataType: "json",
|
|
|
|
|
url: Neopia.API_URL + "/users/" + id,
|
|
|
|
|
useCSRFProtection: false
|
|
|
|
|
}).then(function(response) {
|
2014-01-03 13:11:02 -08:00
|
|
|
|
return response.users[0];
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
Customization: {
|
2014-01-17 08:15:56 -08:00
|
|
|
|
request: function(petId, type) {
|
|
|
|
|
return $.ajax({
|
|
|
|
|
dataType: "json",
|
|
|
|
|
type: type,
|
2014-01-18 19:54:11 -08:00
|
|
|
|
url: Neopia.API_URL + "/pets/" + petId + "/customization",
|
|
|
|
|
useCSRFProtection: false
|
2014-01-03 13:11:02 -08:00
|
|
|
|
});
|
2014-01-17 08:15:56 -08:00
|
|
|
|
},
|
|
|
|
|
get: function(petId) {
|
|
|
|
|
return this.request(petId, "GET");
|
|
|
|
|
},
|
|
|
|
|
post: function(petId) {
|
|
|
|
|
return this.request(petId, "POST");
|
2014-01-03 13:11:02 -08:00
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
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";
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2014-01-17 09:12:56 -08:00
|
|
|
|
var ImpressUser = (function() {
|
|
|
|
|
var userSignedIn = ($('meta[name=user-signed-in]').attr('content') === 'true');
|
|
|
|
|
if (userSignedIn) {
|
2014-01-18 19:54:11 -08:00
|
|
|
|
var currentUserId = $('meta[name=current-user-id').attr('content');
|
2014-01-17 09:12:56 -08:00
|
|
|
|
return {
|
2014-01-18 19:54:11 -08:00
|
|
|
|
addNeopetsUsername: function(username) {
|
|
|
|
|
return $.ajax({
|
|
|
|
|
url: '/user/' + currentUserId + '/neopets-connections',
|
|
|
|
|
type: 'POST',
|
|
|
|
|
data: {neopets_connection: {neopets_username: username}}
|
|
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
removeNeopetsUsername: function(username) {
|
|
|
|
|
return $.ajax({
|
2014-01-18 20:50:14 -08:00
|
|
|
|
url: '/user/' + currentUserId + '/neopets-connections/' + encodeURIComponent(username),
|
2014-01-18 19:54:11 -08:00
|
|
|
|
type: 'POST',
|
|
|
|
|
data: {_method: 'DELETE'}
|
|
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
getNeopetsUsernames: function() {
|
|
|
|
|
return JSON.parse($('#modeling-neopets-users').attr('data-usernames'));
|
|
|
|
|
}
|
2014-01-17 09:12:56 -08:00
|
|
|
|
};
|
|
|
|
|
} else {
|
|
|
|
|
return {
|
|
|
|
|
_key: "guestNeopetsUsernames",
|
|
|
|
|
_setNeopetsUsernames: function(usernames) {
|
|
|
|
|
localStorage.setItem(this._key, JSON.stringify(usernames));
|
|
|
|
|
},
|
|
|
|
|
addNeopetsUsername: function(username) {
|
|
|
|
|
this._setNeopetsUsernames(this.getNeopetsUsernames().concat([username]));
|
|
|
|
|
},
|
|
|
|
|
removeNeopetsUsername: function(username) {
|
|
|
|
|
this._setNeopetsUsernames(this.getNeopetsUsernames().filter(function(u) {
|
|
|
|
|
return u !== username;
|
|
|
|
|
}));
|
|
|
|
|
},
|
|
|
|
|
getNeopetsUsernames: function() {
|
|
|
|
|
return JSON.parse(localStorage.getItem(this._key)) || [];
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
})();
|
|
|
|
|
|
2014-01-03 13:11:02 -08:00
|
|
|
|
var Modeling = {
|
|
|
|
|
_customizationsByPetId: {},
|
2014-01-04 12:45:27 -08:00
|
|
|
|
_customizations: [],
|
2014-01-17 08:15:56 -08:00
|
|
|
|
_itemsById: {},
|
2014-01-04 12:45:27 -08:00
|
|
|
|
_items: [],
|
2014-01-17 09:12:56 -08:00
|
|
|
|
_usersComponent: {setState: function() {}},
|
|
|
|
|
_neopetsUsernamesPresenceMap: {},
|
2014-01-03 13:11:02 -08:00
|
|
|
|
_addCustomization: function(customization) {
|
2014-01-17 08:15:56 -08:00
|
|
|
|
// Set all equipped, interesting items' statuses as success and cross
|
|
|
|
|
// them off the list.
|
|
|
|
|
var itemsById = this._itemsById;
|
|
|
|
|
var equippedByZone = customization.custom_pet.equipped_by_zone;
|
|
|
|
|
var closetItems = customization.closet_items;
|
|
|
|
|
Object.keys(equippedByZone).forEach(function(zoneId) {
|
|
|
|
|
var equippedClosetId = equippedByZone[zoneId].closet_obj_id;
|
|
|
|
|
var equippedObjectId = closetItems[equippedClosetId].obj_info_id;
|
|
|
|
|
if (itemsById.hasOwnProperty(equippedObjectId)) {
|
|
|
|
|
customization.statusByItemId[equippedObjectId] = "success";
|
|
|
|
|
itemsById[equippedObjectId].el.find("span[data-body-id=" +
|
|
|
|
|
customization.custom_pet.body_id + "]").addClass("modeled")
|
2014-01-20 11:56:19 -08:00
|
|
|
|
.attr("title", I18n.modeledBodyTitle);
|
2014-01-17 08:15:56 -08:00
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
this._customizationsByPetId[customization.custom_pet.name] = customization;
|
2014-01-04 12:45:27 -08:00
|
|
|
|
this._customizations = this._buildCustomizations();
|
2014-01-17 09:12:56 -08:00
|
|
|
|
this._updateCustomizations();
|
2014-01-03 13:11:02 -08:00
|
|
|
|
},
|
2014-01-17 08:15:56 -08:00
|
|
|
|
_addNewCustomization: function(customization) {
|
|
|
|
|
customization.loadingForItemId = null;
|
|
|
|
|
customization.statusByItemId = {};
|
|
|
|
|
this._addCustomization(customization);
|
|
|
|
|
},
|
2014-01-04 12:45:27 -08:00
|
|
|
|
_buildCustomizations: function() {
|
2014-01-03 13:11:02 -08:00
|
|
|
|
var modelCustomizationsByPetId = this._customizationsByPetId;
|
|
|
|
|
return Object.keys(modelCustomizationsByPetId).map(function(petId) {
|
|
|
|
|
return modelCustomizationsByPetId[petId];
|
|
|
|
|
});
|
|
|
|
|
},
|
2014-01-04 12:45:27 -08:00
|
|
|
|
_createItems: function($) {
|
2014-01-17 08:15:56 -08:00
|
|
|
|
var itemsById = this._itemsById;
|
2014-01-04 12:45:27 -08:00
|
|
|
|
this._items = $('#newest-unmodeled-items li').map(function() {
|
|
|
|
|
var el = $(this);
|
2014-01-17 08:15:56 -08:00
|
|
|
|
var item = {
|
2014-01-04 12:45:27 -08:00
|
|
|
|
el: el,
|
|
|
|
|
id: el.attr('data-item-id'),
|
2014-01-17 08:15:56 -08:00
|
|
|
|
name: el.find('h2').text(),
|
2014-01-04 12:45:27 -08:00
|
|
|
|
missingBodyIdsPresenceMap: el.find('span[data-body-id]').toArray().reduce(function(map, node) {
|
|
|
|
|
map[$(node).attr('data-body-id')] = true;
|
|
|
|
|
return map;
|
|
|
|
|
}, {})
|
|
|
|
|
};
|
2014-01-17 08:15:56 -08:00
|
|
|
|
item.component = React.renderComponent(<ModelForItem item={item} />,
|
|
|
|
|
el.find('.models').get(0));
|
|
|
|
|
itemsById[item.id] = item;
|
|
|
|
|
return item;
|
2014-01-04 12:45:27 -08:00
|
|
|
|
}).toArray();
|
|
|
|
|
},
|
2014-01-03 13:11:02 -08:00
|
|
|
|
_loadPetCustomization: function(neopiaPetId) {
|
|
|
|
|
return Neopia.Customization.get(neopiaPetId)
|
2014-01-17 08:15:56 -08:00
|
|
|
|
.done(this._addNewCustomization.bind(this))
|
2014-01-09 13:50:03 -08:00
|
|
|
|
.fail(function() {
|
|
|
|
|
console.error("couldn't load pet %s", neopiaPetId);
|
|
|
|
|
});
|
2014-01-03 13:11:02 -08:00
|
|
|
|
},
|
|
|
|
|
_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;
|
2014-01-09 13:50:03 -08:00
|
|
|
|
}).then(this._loadManyPetsCustomizations.bind(this)).fail(function() {
|
|
|
|
|
console.error("couldn't load user %s's customizations", neopiaUserId);
|
|
|
|
|
});
|
2014-01-03 13:11:02 -08:00
|
|
|
|
},
|
2014-01-17 08:15:56 -08:00
|
|
|
|
_startLoading: function(neopiaPetId, itemId) {
|
|
|
|
|
var customization = this._customizationsByPetId[neopiaPetId];
|
|
|
|
|
customization.loadingForItemId = itemId;
|
|
|
|
|
customization.statusByItemId[itemId] = "loading";
|
2014-01-17 09:12:56 -08:00
|
|
|
|
this._updateCustomizations();
|
2014-01-17 08:15:56 -08:00
|
|
|
|
},
|
|
|
|
|
_stopLoading: function(neopiaPetId, itemId, status) {
|
|
|
|
|
var customization = this._customizationsByPetId[neopiaPetId];
|
|
|
|
|
customization.loadingForItemId = null;
|
|
|
|
|
customization.statusByItemId[itemId] = status;
|
2014-01-17 09:12:56 -08:00
|
|
|
|
this._updateCustomizations();
|
2014-01-17 08:15:56 -08:00
|
|
|
|
},
|
2014-01-17 09:12:56 -08:00
|
|
|
|
_updateCustomizations: function() {
|
|
|
|
|
var neopetsUsernamesPresenceMap = this._neopetsUsernamesPresenceMap;
|
|
|
|
|
var liveCustomizations = this._customizations.filter(function(c) {
|
|
|
|
|
return neopetsUsernamesPresenceMap[c.custom_pet.owner];
|
|
|
|
|
});
|
2014-01-04 12:45:27 -08:00
|
|
|
|
this._items.forEach(function(item) {
|
2014-01-17 09:12:56 -08:00
|
|
|
|
var filteredCustomizations = liveCustomizations.filter(function(c) {
|
2014-01-17 08:15:56 -08:00
|
|
|
|
return item.missingBodyIdsPresenceMap[c.custom_pet.body_id];
|
2014-01-04 12:45:27 -08:00
|
|
|
|
});
|
|
|
|
|
item.component.setState({customizations: filteredCustomizations});
|
2014-01-03 13:11:02 -08:00
|
|
|
|
});
|
|
|
|
|
},
|
2014-01-17 09:12:56 -08:00
|
|
|
|
_updateUsernames: function() {
|
|
|
|
|
var usernames = Object.keys(this._neopetsUsernamesPresenceMap);
|
|
|
|
|
this._usersComponent.setState({usernames: usernames});
|
|
|
|
|
},
|
2014-01-04 12:45:27 -08:00
|
|
|
|
init: function($) {
|
2014-01-03 13:11:02 -08:00
|
|
|
|
Neopia.init();
|
2014-01-04 12:45:27 -08:00
|
|
|
|
this._createItems($);
|
2014-01-17 09:12:56 -08:00
|
|
|
|
var usersEl = $('#modeling-neopets-users');
|
|
|
|
|
this._usersComponent = React.renderComponent(<NeopetsUsernamesForm />,
|
|
|
|
|
usersEl.get(0));
|
|
|
|
|
var usernames = ImpressUser.getNeopetsUsernames();
|
2014-01-18 19:54:11 -08:00
|
|
|
|
usernames.forEach(this._registerUsername.bind(this));
|
|
|
|
|
this._updateUsernames();
|
2014-01-17 08:15:56 -08:00
|
|
|
|
},
|
|
|
|
|
model: function(neopiaPetId, itemId) {
|
|
|
|
|
var oldCustomization = this._customizationsByPetId[neopiaPetId];
|
|
|
|
|
var itemsById = this._itemsById;
|
|
|
|
|
this._startLoading(neopiaPetId, itemId);
|
|
|
|
|
return Neopia.Customization.post(neopiaPetId)
|
|
|
|
|
.done(function(newCustomization) {
|
|
|
|
|
// Add this field as null for consistency.
|
|
|
|
|
newCustomization.loadingForItemId = null;
|
|
|
|
|
|
|
|
|
|
// Copy previous statuses.
|
|
|
|
|
newCustomization.statusByItemId = oldCustomization.statusByItemId;
|
|
|
|
|
|
|
|
|
|
// Set the attempted item's status as unworn (to possibly be
|
|
|
|
|
// overridden by the upcoming loop in _addCustomization).
|
|
|
|
|
newCustomization.statusByItemId[itemId] = "unworn";
|
|
|
|
|
|
|
|
|
|
// Now, finally, let's overwrite the old customization with the new.
|
|
|
|
|
Modeling._addCustomization(newCustomization);
|
|
|
|
|
})
|
|
|
|
|
.fail(function() {
|
|
|
|
|
Modeling._stopLoading(neopiaPetId, itemId, "error");
|
|
|
|
|
});
|
2014-01-17 09:12:56 -08:00
|
|
|
|
},
|
2014-01-18 19:54:11 -08:00
|
|
|
|
_registerUsername: function(username) {
|
|
|
|
|
this._neopetsUsernamesPresenceMap[username] = true;
|
|
|
|
|
this._loadUserCustomizations(username);
|
|
|
|
|
this._updateUsernames();
|
|
|
|
|
},
|
2014-01-17 09:12:56 -08:00
|
|
|
|
addUsername: function(username) {
|
|
|
|
|
if (typeof this._neopetsUsernamesPresenceMap[username] === 'undefined') {
|
|
|
|
|
ImpressUser.addNeopetsUsername(username);
|
2014-01-18 19:54:11 -08:00
|
|
|
|
this._registerUsername(username);
|
2014-01-17 09:12:56 -08:00
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
removeUsername: function(username) {
|
|
|
|
|
if (this._neopetsUsernamesPresenceMap[username]) {
|
|
|
|
|
ImpressUser.removeNeopetsUsername(username);
|
2014-01-18 19:54:11 -08:00
|
|
|
|
delete this._neopetsUsernamesPresenceMap[username];
|
2014-01-17 09:12:56 -08:00
|
|
|
|
this._updateCustomizations();
|
|
|
|
|
this._updateUsernames();
|
|
|
|
|
}
|
2014-01-03 13:11:02 -08:00
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
var ModelForItem = React.createClass({
|
|
|
|
|
getInitialState: function() {
|
|
|
|
|
return {customizations: []};
|
|
|
|
|
},
|
|
|
|
|
render: function() {
|
2014-01-17 08:15:56 -08:00
|
|
|
|
var item = this.props.item;
|
2014-01-03 13:11:02 -08:00
|
|
|
|
function createModelPet(customization) {
|
|
|
|
|
return <ModelPet customization={customization}
|
2014-01-17 08:15:56 -08:00
|
|
|
|
item={item}
|
|
|
|
|
key={customization.custom_pet.name} />;
|
2014-01-03 13:11:02 -08:00
|
|
|
|
}
|
2014-01-17 09:12:56 -08:00
|
|
|
|
var sortedCustomizations = this.state.customizations.slice(0).sort(function(a, b) {
|
2014-01-17 08:15:56 -08:00
|
|
|
|
var aName = a.custom_pet.name.toLowerCase();
|
|
|
|
|
var bName = b.custom_pet.name.toLowerCase();
|
2014-01-03 13:11:02 -08:00
|
|
|
|
if (aName < bName) return -1;
|
|
|
|
|
if (aName > bName) return 1;
|
|
|
|
|
return 0;
|
|
|
|
|
});
|
|
|
|
|
return <ul>{sortedCustomizations.map(createModelPet)}</ul>;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
var ModelPet = React.createClass({
|
|
|
|
|
render: function() {
|
2014-01-17 08:15:56 -08:00
|
|
|
|
var petName = this.props.customization.custom_pet.name;
|
|
|
|
|
var status = this.props.customization.statusByItemId[this.props.item.id];
|
|
|
|
|
var loadingForItemId = this.props.customization.loadingForItemId;
|
|
|
|
|
var disabled = (status === "loading"
|
|
|
|
|
|| status === "success");
|
|
|
|
|
if (loadingForItemId !== null && loadingForItemId !== this.props.item.id) {
|
|
|
|
|
disabled = true;
|
|
|
|
|
}
|
|
|
|
|
var itemName = this.props.item.name;
|
2014-01-03 13:11:02 -08:00
|
|
|
|
var imageSrc = "http://pets.neopets.com/cpn/" + petName + "/1/1.png";
|
2014-01-20 11:56:19 -08:00
|
|
|
|
var title = I18n.pet.title
|
|
|
|
|
.replace(/%{pet}/g, petName)
|
|
|
|
|
.replace(/%{item}/g, itemName);
|
|
|
|
|
var statusMessage = I18n.pet.status[status] || "";
|
2014-01-17 08:15:56 -08:00
|
|
|
|
return <li data-status={status}><button onClick={this.handleClick} title={title} disabled={disabled}>
|
2014-01-03 13:11:02 -08:00
|
|
|
|
<img src={imageSrc} />
|
2014-01-17 08:15:56 -08:00
|
|
|
|
<div>
|
|
|
|
|
<span className="pet-name">{petName}</span>
|
|
|
|
|
<span className="message">{statusMessage}</span>
|
|
|
|
|
</div>
|
2014-01-03 13:11:02 -08:00
|
|
|
|
</button></li>;
|
2014-01-17 08:15:56 -08:00
|
|
|
|
},
|
|
|
|
|
handleClick: function(e) {
|
|
|
|
|
Modeling.model(this.props.customization.custom_pet.name, this.props.item.id);
|
2014-01-03 13:11:02 -08:00
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
2014-01-17 09:12:56 -08:00
|
|
|
|
var NeopetsUsernamesForm = React.createClass({
|
|
|
|
|
getInitialState: function() {
|
|
|
|
|
return {usernames: [], newUsername: ""};
|
|
|
|
|
},
|
|
|
|
|
render: function() {
|
|
|
|
|
function buildUsernameItem(username) {
|
|
|
|
|
return <NeopetsUsernameItem username={username} key={username} />;
|
|
|
|
|
}
|
|
|
|
|
return <div>
|
|
|
|
|
<ul>{this.state.usernames.slice(0).sort().map(buildUsernameItem)}</ul>
|
|
|
|
|
<form onSubmit={this.handleSubmit}>
|
2014-01-20 11:56:19 -08:00
|
|
|
|
<input type="text" placeholder={I18n.neopetsUsernamesForm.label}
|
2014-01-17 09:12:56 -08:00
|
|
|
|
onChange={this.handleChange}
|
|
|
|
|
value={this.state.newUsername} />
|
2014-01-20 11:56:19 -08:00
|
|
|
|
<button type="submit">{I18n.neopetsUsernamesForm.submit}</button></form></div>;
|
2014-01-17 09:12:56 -08:00
|
|
|
|
},
|
|
|
|
|
handleChange: function(e) {
|
|
|
|
|
this.setState({newUsername: e.target.value});
|
|
|
|
|
},
|
|
|
|
|
handleSubmit: function(e) {
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
this.state.newUsername = $.trim(this.state.newUsername);
|
|
|
|
|
if (this.state.newUsername.length) {
|
|
|
|
|
Modeling.addUsername(this.state.newUsername);
|
|
|
|
|
this.setState({newUsername: ""});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
var NeopetsUsernameItem = React.createClass({
|
|
|
|
|
render: function() {
|
|
|
|
|
return <li>{this.props.username} <button onClick={this.handleClick}>×</button></li>
|
|
|
|
|
},
|
|
|
|
|
handleClick: function(e) {
|
|
|
|
|
Modeling.removeUsername(this.props.username);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
2014-01-04 12:45:27 -08:00
|
|
|
|
Modeling.init($);
|
2014-01-20 11:56:19 -08:00
|
|
|
|
})(jQuery, ModelingI18n);
|