Merge branch 'develop' of https://github.com/lefnire/habitrpg into develop

This commit is contained in:
Matteo Pagliazzi 2013-09-08 11:59:35 +02:00
commit 5d160c7f21
25 changed files with 283 additions and 312 deletions

View file

@ -38,13 +38,13 @@ module.exports = function(grunt) {
'public/js/services/sharedServices.js',
'public/js/services/userServices.js',
'public/js/services/groupServices.js',
'public/js/services/memberServices.js',
'public/js/filters/filters.js',
'public/js/directives/directives.js',
'public/js/controllers/authCtrl.js',
'public/js/controllers/characterCtrl.js',
'public/js/controllers/menuCtrl.js',
'public/js/controllers/notificationCtrl.js',
'public/js/controllers/rootCtrl.js',

View file

@ -7,6 +7,7 @@
"NODE_DB_URI":"mongodb://localhost/habitrpg",
"NODE_ENV":"development",
"SESSION_SECRET":"YOUR SECRET HERE",
"ADMIN_EMAIL": "you@yours.com",
"SMTP_USER":"user@domain.com",
"SMTP_PASS":"password",
"SMTP_SERVICE":"Gmail",

View file

@ -53,6 +53,7 @@
},
"scripts": {
"test": "mocha test/api.mocha.coffee",
"start": "grunt run:dev",
"postinstall": "./node_modules/bower/bin/bower install -f"
}
}

View file

@ -27,13 +27,13 @@
margin-right:5px
.label-elite
background-color: #5DF644
background-color: #077409
color: black
.label-champion
background-color: #0070dd
background-color: #125BA2
color: white
.label-master
background-color: #a335ee
.label-royal
background-color: #7313B4
color: white
#market-tab

View file

@ -1,7 +1,7 @@
"use strict";
window.habitrpg = angular.module('habitrpg',
['ngRoute', 'ngResource', 'ngSanitize', 'userServices', 'groupServices', 'sharedServices', 'authServices', 'notificationServices', 'ui.bootstrap', 'ui.keypress'])
['ngRoute', 'ngResource', 'ngSanitize', 'userServices', 'groupServices', 'memberServices', 'sharedServices', 'authServices', 'notificationServices', 'ui.bootstrap', 'ui.keypress'])
.constant("API_URL", "")
.constant("STORAGE_USER_ID", 'habitrpg-user')

View file

@ -1,40 +0,0 @@
'use strict';
/**
* The character controller:
*
*/
habitrpg.controller('CharacterCtrl',
['$scope', '$location', 'User',
function($scope, $location, User) {
$scope.user = User.user;
$scope.equipped = function(user, type) {
var tier = (user.backer && user.backer.tier)
return window.habitrpgShared.helpers.equipped(type, user.items[type], user.preferences, tier);
}
$scope.$watch('user.tasks', function(){
$scope.hpPercent = function(hp) {
return (hp / 50) * 100;
}
$scope.expPercent = function(exp, level) {
return (exp / window.habitrpgShared.algos.tnl(level)) * 100;
}
})
$scope.floor = Math.floor;
$scope.count = function(arr) {
return _.size(arr);
}
$scope.tnl = window.habitrpgShared.algos.tnl;
$scope.showUserAvatar = function() {
$('.userAvatar').show()
}
}
]);

View file

@ -1,17 +1,37 @@
"use strict";
habitrpg.controller("GroupsCtrl", ['$scope', '$rootScope', 'Groups', '$http', 'API_URL', '$q', 'User',
function($scope, $rootScope, Groups, $http, API_URL, $q, User) {
habitrpg.controller("GroupsCtrl", ['$scope', '$rootScope', 'Groups', '$http', 'API_URL', '$q', 'User', 'Members', '$location',
function($scope, $rootScope, Groups, $http, API_URL, $q, User, Members, $location) {
$scope.isMember = function(user, group){
return ~(group.members.indexOf(user._id));
}
$scope.groups = Groups.groups;
// ------ Loading ------
$scope.groups = Groups.groups;
$scope.fetchGuilds = Groups.fetchGuilds;
$scope.fetchTavern = Groups.fetchTavern;
// ------ Modals ------
$scope.clickMember = function(uid, forceShow) {
if (User.user._id == uid && !forceShow) {
if ($location.path() == '/tasks') {
$location.path('/options');
} else {
$location.path('/tasks');
}
} else {
// We need the member information up top here, but then we pass it down to the modal controller
// down below. Better way of handling this?
Members.selectMember(uid);
$rootScope.modals.member = true;
}
}
// ------ Invites ------
$scope.invitee = '';
$scope.invite = function(group, uuid){
group.$invite({uuid:uuid}, function(){
@ -22,6 +42,15 @@ habitrpg.controller("GroupsCtrl", ['$scope', '$rootScope', 'Groups', '$http', 'A
}
])
.controller("MemberModalCtrl", ['$scope', '$rootScope', 'Members',
function($scope, $rootScope, Members) {
// We watch Members.selectedMember because it's asynchronously set, so would be a hassle to handle updates here
$scope.$watch( function() { return Members.selectedMember; }, function (member) {
$scope.profile = member;
});
}
])
.controller('ChatCtrl', ['$scope', 'Groups', 'User', function($scope, Groups, User){
$scope._chatMessage = '';
$scope.postChat = function(group, message){
@ -38,10 +67,10 @@ habitrpg.controller("GroupsCtrl", ['$scope', '$rootScope', 'Groups', '$http', 'A
}
$scope.nameTagClasses = function(message){
if (message.contributor) {
if (message.contributor.match(/npc/i) || message.contributor.match(/master/i)) {
return 'label-master'; // master
if (message.contributor.match(/npc/i) || message.contributor.match(/royal/i)) {
return 'label-royal';
} else if (message.contributor.match(/champion/i)) {
return 'label-champion'; // champion
return 'label-champion';
} else if (message.contributor.match(/elite/i)) {
return 'label-success'; //elite
}

View file

@ -18,12 +18,15 @@ habitrpg.controller("MarketCtrl", ['$rootScope', '$scope', 'User', 'API_URL', '$
var message = "Buy this " + (type == 'egg' ? 'egg' : 'hatching potion') + " with " + item.value + " of your " + gems + " Gems?"
if(confirm(message)){
var toPush = type === 'egg' ? item : item.name;
$http.post(API_URL + '/api/v1/market/buy?type=' + type, toPush)
$http.post(API_URL + '/api/v1/market/buy?type=' + type, item)
.success(function(data){
User.user.balance = data.balance;
store.push(toPush);
//$sc¡ope.items = data.items.eggs; // FIXME this isn't updating the UI
// don't know what's going on, but trying to work with the returned data (a) isn't updating the ui, (b) isnt'
// stickign between refreshes until a force-refresh is called (user._v--).
User.user._v--;
User.log({});
//User.user.balance = data.balance;
store.push(type === 'egg' ? item : item.name);
//$scope.items = data.items.eggs; // FIXME this isn't updating the UI
}).error(function(data){
alert(data);
console.error(data);

View file

@ -6,17 +6,4 @@ habitrpg.controller("UserAvatarCtrl", ['$scope', '$location', 'User',
$scope.hideUserAvatar = function() {
$(".userAvatar").hide();
};
$scope.clickAvatar = function(profile) {
if (User.user.id == profile.id) {
if ($location.path() == '/tasks') {
$location.path('/options');
} else {
$location.path('/tasks');
}
} else {
//TODO show party member modal
//$("#avatar-modal-#{uid}").modal('show')
}
}
}]);

View file

@ -5,8 +5,8 @@
*/
angular.module('groupServices', ['ngResource']).
factory('Groups', ['API_URL', '$resource', 'User', '$q',
function(API_URL, $resource, User, $q) {
factory('Groups', ['API_URL', '$resource', 'User', '$q', 'Members',
function(API_URL, $resource, User, $q, Members) {
var Group = $resource(API_URL + '/api/v1/groups/:gid',
{gid:'@_id'},
{
@ -34,6 +34,7 @@ angular.module('groupServices', ['ngResource']).
// But we don't defer triggering Party, since we always need it for the header if nothing else
Group.query({type:'party'}, function(_groups){
partyQ.resolve(_groups[0]);
Members.populate(_groups[0]);
})
return {
@ -42,10 +43,12 @@ angular.module('groupServices', ['ngResource']).
$('#loading-indicator').show();
Group.query({type:'guilds'}, function(_groups){
guildsQ.resolve(_groups);
Members.populate(_groups);
$('#loading-indicator').hide();
})
Group.query({type:'public'}, function(_groups){
publicQ.resolve(_groups);
Members.populate(_groups);
})
}),
@ -54,6 +57,7 @@ angular.module('groupServices', ['ngResource']).
Group.query({type:'tavern'}, function(_groups){
$('#loading-indicator').hide();
tavernQ.resolve(_groups[0]);
Members.populate(_groups[0]);
})
}),

View file

@ -0,0 +1,73 @@
'use strict';
/**
* Services that persists and retrieves user from localStorage.
*/
angular.module('memberServices', ['ngResource']).
factory('Members', ['API_URL', '$resource',
function(API_URL, $resource) {
var members = {};
var Member = $resource(API_URL + '/api/v1/members/:uid', {uid:'@_id'});
return {
members: members,
/**
* Allows us to lazy-load party / group / public members throughout the application.
* @param obj - either a group or an individual member. If it's a group, we lazy-load all of its members.
*/
populate: function(obj){
function populateGroup(group){
_.each(group.members, function(member){
// meaning `populate('members')` wasn't run on the server, so we're getting the "in-database" form of
// the members array, which is just a list of IDs - not the populated objects
if (_.isString(member)) return;
// lazy-load
if (!members[member._id]) members[member._id] = member;
})
}
// Array of groups
if (_.isArray(obj)) {
if (obj[0] && obj[0].members) {
_.each(obj, function(group){
populateGroup(group);
})
}
// Individual Group
} else if (obj.members)
populateGroup(obj);
// individual Member
if (obj._id) {
if (!members[obj._id]) {
members[obj._id] = obj;
}
}
},
selectedMember: undefined,
/**
* Once users are populated, we fetch them throughout the application (eg, modals). This
* either gets them or fetches if not available
* @param uid
*/
selectMember: function(uid) {
var self = this;
if (members[uid]) {
self.selectedMember = members[uid];
} else {
Member.get({uid: uid}, function(member){
self.populate(member); // lazy load for later
self.selectedMember = members[member._id];
});
}
}
}
}
]);

View file

@ -19,6 +19,14 @@ var api = module.exports;
var usernameFields = 'auth.local.username auth.facebook.displayName auth.facebook.givenName auth.facebook.familyName auth.facebook.name';
var partyFields = 'profile preferences items stats achievements party backer flags.rest ' + usernameFields;
api.getMember = function(req, res) {
User.findById(req.params.uid).select(partyFields).exec(function(err, user){
if (err) return res.json(500,{err:err});
if (!user) return res.json(400,{err:'User not found'});
res.json(user);
})
}
/**
* Get groups. If req.query.type privided, returned as an array (so ngResource can use). If not, returned as
* object {guilds, public, party, tavern}. req.query.type can be comma-separated `type=guilds,party`

View file

@ -1,39 +0,0 @@
var nconf = require('nconf');
process.on("uncaughtException", function(error) {
var sendEmail;
sendEmail = function(mailData) {
var nodemailer, smtpTransport;
nodemailer = require("derby-auth/node_modules/nodemailer");
/* create reusable transport method (opens pool of SMTP connections)*/
/* TODO derby-auth isn't currently configurable here, if you need customizations please send pull request*/
smtpTransport = nodemailer.createTransport("SMTP", {
service: nconf.get('SMTP_SERVICE'),
auth: {
user: nconf.get('SMTP_USER'),
pass: nconf.get('SMTP_PASS')
}
});
/* send mail with defined transport object*/
return smtpTransport.sendMail(mailData, function(error, response) {
if (error) {
console.log(error);
} else {
console.log("Message sent: " + response.message);
}
/* shut down the connection pool, no more messages*/
return smtpTransport.close();
});
};
sendEmail({
from: "HabitRPG <admin@habitrpg.com>",
to: "tylerrenelle@gmail.com",
subject: "HabitRPG Error",
text: error.stack
});
return console.log(error.stack);
});

View file

@ -143,8 +143,8 @@ var UserSchema = new Schema({
/*FIXME store as Date?*/
lastCron: {
type: Number,
'default': +(new Date)
type: Date,
'default': Date.now
},
/* FIXME remove?*/

View file

@ -64,6 +64,9 @@ router.post('/groups/:gid/chat', auth.auth, groups.attachGroup, groups.postChat)
//PUT /groups/:gid/chat/:messageId
//DELETE /groups/:gid/chat/:messageId
/* Members */
router.get('/members/:uid', groups.getMember);
// Market
router.post('/market/buy', auth.auth, user.marketBuy);

View file

@ -5,13 +5,23 @@ var http = require("http");
var path = require("path");
var app = express();
var nconf = require('nconf');
var utils = require('./utils');
var middleware = require('./middleware');
var server;
var TWO_WEEKS = 1000 * 60 * 60 * 24 * 14;
// ------------ Setup configurations ------------
require('./config');
require('./errors');
process.on("uncaughtException", function(error) {
// when we hit an error, send it to admin as an email. If no ADMIN_EMAIL is present, just send it to yourself (SMTP_USER)
utils.sendEmail({
from: "HabitRPG <" + nconf.get('SMTP_USER') + ">",
to: nconf.get('ADMIN_EMAIL') || nconf.get('SMTP_USER'),
subject: "HabitRPG Error",
text: error.stack
});
console.error(error.stack);
});
// ------------ MongoDB Configuration ------------
mongoose = require('mongoose');

View file

@ -52,13 +52,13 @@ html
script(type='text/javascript', src='/js/services/sharedServices.js')
script(type='text/javascript', src='/js/services/userServices.js')
script(type='text/javascript', src='/js/services/groupServices.js')
script(type='text/javascript', src='/js/services/memberServices.js')
script(type='text/javascript', src='/js/filters/filters.js')
script(type='text/javascript', src='/js/directives/directives.js')
script(type='text/javascript', src='/js/controllers/authCtrl.js')
script(type='text/javascript', src='/js/controllers/characterCtrl.js')
script(type='text/javascript', src='/js/controllers/menuCtrl.js')
script(type='text/javascript', src='/js/controllers/notificationCtrl.js')
script(type='text/javascript', src='/js/controllers/rootCtrl.js')

View file

@ -1,5 +1,5 @@
li(ng-repeat='message in group.chat', ng-class='{highlight: message.text.indexOf(username(user.auth,user.profile.name)) != -1}')
span.label.chat-message(class='{{nameTagClasses(message)}}', tooltip='{{message.contributor}}')
a.label.chat-message(class='{{nameTagClasses(message)}}', tooltip='{{message.contributor}}', ng-click='clickMember(message.uuid, true)')
| {{message.user}}
span
span(ng-bind-html="message.text | linky:'_blank'") -

View file

@ -1,68 +1,66 @@
div#groups-controller(ng-controller='GroupsCtrl')
// FIXME note, due to https://github.com/angular-ui/bootstrap/issues/783 we can't use nested angular-bootstrap tabs
// Subscribe to that ticket & change this when they fix
// FIXME note, due to https://github.com/angular-ui/bootstrap/issues/783 we can't use nested angular-bootstrap tabs
// Subscribe to that ticket & change this when they fix
ul.nav.nav-tabs
li.active
a(data-target='#groups-party', data-toggle='tab') Party
li
a(data-target='#groups-guilds', data-toggle='tab', ng-click='fetchGuilds()') Guilds
ul.nav.nav-tabs
li.active
a(data-target='#groups-party', data-toggle='tab') Party
li
a(data-target='#groups-guilds', data-toggle='tab', ng-click='fetchGuilds()') Guilds
.tab-content(ng-controller='PartyCtrl')
#groups-party.tab-pane.active
div(ng-show='group._id')
include ./group
div(ng-hide='group._id')
div(ng-show='user.invitations.party')
// #with required for the accept/reject buttons
// {#with _user.invitations.party as :party}
h2 You're Invited To {{user.invitations.party.name}}
a.btn.btn-success(data-type='party', ng-click='join(user.invitations.party)') Accept
a.btn.btn-danger(ng-click='reject(group)') Reject
// {/}
div(ng-hide='user.invitations.party', ng-controller='PartyCtrl')
h2 Create A Party
p
| You are not in a party. You can either create one and invite friends, or if you want to join an existing party, have them enter:
pre.prettyprint.
{{user.id}}
include ./create-group
.tab-content(ng-controller='PartyCtrl')
#groups-party.tab-pane.active
div(ng-show='group._id')
#groups-guilds.tab-pane(ng-controller='GuildsCtrl')
ul.nav.nav-tabs
li.active
a(data-target='#groups-public-guilds', data-toggle='tab') Public Guilds
li(ng-repeat='group in groups.guilds')
a(data-target='#groups-guild-{{group._id}}', data-toggle='tab') {{group.name}}
li
a(data-target='#groups-create-guild', data-toggle='tab') Create Guild
.tab-content
.tab-pane.active#groups-public-guilds
div(ng-repeat='invitation in user.invitations.guilds')
h3 You're Invited To {{invitation.name}}
a.btn.btn-success(data-type='guild', ng-click='join(group)') Accept
a.btn.btn-danger(x-bind='click:rejectInvitation') Reject
// Public Groups
.options-group.option-large.whatever-options
input.option-content(type='text',ng-model='guildSearch', placeholder='Search')
table.table.table-striped
tr(ng-repeat='group in groups.public | filter:guildSearch')
td
ul.pull-right.challenge-accordion-header-specs
li {{group.members.length}} member(s)
li
// join / leave
a.btn.btn-small.btn-danger(ng-show='isMember(user, group)', ng-click='leave(group)')
i.icon-ban-circle
| Leave
a.btn.btn-small.btn-success(ng-hide='isMember(user, group)', ng-click='join(group)')
i.icon-ok
| Join
h4 {{group.name}}
p {{group.description}}
.tab-pane(id='groups-guild-{{group._id}}', ng-repeat='group in groups.guilds')
include ./group
div(ng-hide='group._id')
div(ng-show='user.invitations.party')
// #with required for the accept/reject buttons
// {#with _user.invitations.party as :party}
h2 You're Invited To {{user.invitations.party.name}}
a.btn.btn-success(data-type='party', ng-click='join(user.invitations.party)') Accept
a.btn.btn-danger(ng-click='reject(group)') Reject
// {/}
div(ng-hide='user.invitations.party', ng-controller='PartyCtrl')
h2 Create A Party
p
| You are not in a party. You can either create one and invite friends, or if you want to join an existing party, have them enter:
pre.prettyprint.
{{user.id}}
include ./create-group
#groups-guilds.tab-pane(ng-controller='GuildsCtrl')
ul.nav.nav-tabs
li.active
a(data-target='#groups-public-guilds', data-toggle='tab') Public Guilds
li(ng-repeat='group in groups.guilds')
a(data-target='#groups-guild-{{group._id}}', data-toggle='tab') {{group.name}}
li
a(data-target='#groups-create-guild', data-toggle='tab') Create Guild
.tab-content
.tab-pane.active#groups-public-guilds
div(ng-repeat='invitation in user.invitations.guilds')
h3 You're Invited To {{invitation.name}}
a.btn.btn-success(data-type='guild', ng-click='join(group)') Accept
a.btn.btn-danger(x-bind='click:rejectInvitation') Reject
// Public Groups
.options-group.option-large.whatever-options
input.option-content(type='text',ng-model='guildSearch', placeholder='Search')
table.table.table-striped
tr(ng-repeat='group in groups.public | filter:guildSearch')
td
ul.pull-right.challenge-accordion-header-specs
li {{group.members.length}} member(s)
li
// join / leave
a.btn.btn-small.btn-danger(ng-show='isMember(user, group)', ng-click='leave(group)')
i.icon-ban-circle
| Leave
a.btn.btn-small.btn-success(ng-hide='isMember(user, group)', ng-click='join(group)')
i.icon-ok
| Join
h4 {{group.name}}
p {{group.description}}
.tab-pane(id='groups-guild-{{group._id}}', ng-repeat='group in groups.guilds')
include ./group
.tab-pane#groups-create-guild
include ./create-group
.tab-pane#groups-create-guild
include ./create-group

View file

@ -1,57 +0,0 @@
<market:>
<!-- pets pane -->
<div id="market-tab">
<table><tr>
<td><div class="NPC-Alex pull-left"></div></td>
<td>
<div class="popover static-popover fade right in">
<div class="arrow"></div>
<h3 class="popover-title">Alexander Augustin</h3>
<div class="popover-content">Welcome to the Market! I'm the merchant, <a target="_blank" href="http://www.kickstarter.com/profile/523661924">Alexander</a>. Dying to get that particular pet you're after, but don't want to wait for it to drop? Buy it here!</div>
</div>
</td>
</tr></table>
<h4>Eggs</h4>
{#with _items.pets as :egg}
<table class="tab-pane pet-grid">
<tr>
{#with :egg[0]}<app:pets:egg />{/}
{#with :egg[1]}<app:pets:egg />{/}
{#with :egg[2]}<app:pets:egg />{/}
{#with :egg[3]}<app:pets:egg />{/}
</tr>
<tr>
{#with :egg[4]}<app:pets:egg />{/}
{#with :egg[5]}<app:pets:egg />{/}
{#with :egg[6]}<app:pets:egg />{/}
{#with :egg[7]}<app:pets:egg />{/}
</tr>
<tr>
{#with :egg[8]}<app:pets:egg />{/}
</tr>
</table>
{/}
<h4>Hatching Potions</h4>
{#with _items.hatchingPotions as :hatchingPotion}
<table class="tab-pane pet-grid">
<tr>
{#with :hatchingPotion[0]}<app:pets:hatchingPotion />{/}
{#with :hatchingPotion[1]}<app:pets:hatchingPotion />{/}
{#with :hatchingPotion[2]}<app:pets:hatchingPotion />{/}
{#with :hatchingPotion[3]}<app:pets:hatchingPotion />{/}
</tr>
<tr>
{#with :hatchingPotion[4]}<app:pets:hatchingPotion />{/}
{#with :hatchingPotion[5]}<app:pets:hatchingPotion />{/}
{#with :hatchingPotion[6]}<app:pets:hatchingPotion />{/}
{#with :hatchingPotion[7]}<app:pets:hatchingPotion />{/}
</tr>
<tr>
{#with :hatchingPotion[8]}<app:pets:hatchingPotion />{/}
{#with :hatchingPotion[9]}<app:pets:hatchingPotion />{/}
</tr>
</table>
{/}
</div>

View file

@ -1,4 +1,4 @@
figure.herobox(ng-click='clickAvatar(profile)', data-name='{{username(profile.auth, profile.profile.name)}}', ng-class='{isUser: profile.id==user.id, hasPet: profile.items.pet}', data-level='{{profile.stats.lvl}}', data-uid='{{profile.id}}', rel='popover', data-placement='bottom', data-trigger='hover', data-html='true', data-content="<div ng-hide='profile.id == user.id'> <div class='progress progress-danger' style='height:5px;'> <div class='bar' style='height: 5px; width: {{percent(profile.stats.hp, 50)}}%;'></div> </div> <div class='progress progress-warning' style='height:5px;'> <div class='bar' style='height: 5px; width: {{percent(profile.stats.exp, tnl(profile.stats.lvl))}}%;'></div> </div> <div>Level: {{profile.stats.lvl}}</div> <div>GP: {{profile.stats.gp | number:0}}</div> <div>{{count(profile.items.pets)}} / 90 Pets Found</div> </div>")
figure.herobox(ng-click='clickMember(profile._id)', data-name='{{username(profile.auth, profile.profile.name)}}', ng-class='{isUser: profile.id==user.id, hasPet: profile.items.pet}', data-level='{{profile.stats.lvl}}', data-uid='{{profile.id}}', rel='popover', data-placement='bottom', data-trigger='hover', data-html='true', data-content="<div ng-hide='profile.id == user.id'> <div class='progress progress-danger' style='height:5px;'> <div class='bar' style='height: 5px; width: {{percent(profile.stats.hp, 50)}}%;'></div> </div> <div class='progress progress-warning' style='height:5px;'> <div class='bar' style='height: 5px; width: {{percent(profile.stats.exp, tnl(profile.stats.lvl))}}%;'></div> </div> <div>Level: {{profile.stats.lvl}}</div> <div>GP: {{profile.stats.gp | number:0}}</div> <div>{{count(profile.items.pets)}} / 90 Pets Found</div> </div>")
.character-sprites
span(ng-class='{zzz:profile.flags.rest}')
span(class='{{profile.preferences.gender}}_skin_{{profile.preferences.skin}}')

View file

@ -4,4 +4,5 @@ include ./reroll
include ./death
include ./new-stuff
include ./why-ads
include ./more-gems
include ./more-gems
include ./members

View file

@ -1,40 +0,0 @@
{{#each _membersArray as :member}}
<app:modals:modal modalId="avatar-modal-{{:member.id}}">
<app:avatar:profile profile={{_members[:member.id]}} />
<!-- see above -->
<profile:>
<h2 class='profile-modal-header'>{{username(@profile.auth, @profile.profile.name)}}</h2>
<hr/>
<div class='row-fluid'>
<div class='span6'>
{{#if @profile.profile.imageUrl}}
<img src="{{@profile.profile.imageUrl}}" />
{{/}}
{{#if @profile.profile.blurb}}
<p>{{@profile.profile.blurb}}</p>
{{/}}
{{#if @profile.profile.websites}}
<ul>
{{#each @profile.profile.websites as :website}}
<li>{{:website}}</li>
{{/}}
</ul>
{{/}}
<h3>Stats</h3>
<app:avatar:profile-stats profile={{@profile}} />
</div>
<div class='span6'>
<h3>Achievements</h3>
<app:avatar:achievements profile={{@profile}} />
</div>
</div>
<@footer>
<button data-dismiss="modal" class="btn btn-success">Ok</button>
</@footer>
</app:modals:modal>
{{/}}

View file

@ -0,0 +1,22 @@
div(ng-controller='MemberModalCtrl')
div(modal='modals.member')
.modal-header
h3
span {{username(profile.auth, profile.profile.name)}}
span(ng-show='profile.backer.contributor') - {{profile.backer.contributor}}
.modal-body
app:avatar:profile
.row-fluid
.span6
img(ng-show='profile.profile.imageUrl', ng-src='{{profile.profile.imageUrl}}')
p(ng-show='profile.profile.blurb') {{profile.profile.blurb}}
ul(ng-show='profile.profile.websites')
li(ng-repeat='website in profile.profile.websites') {{website}}
h3 Stats
include ../profiles/stats
.span6
include ../header/avatar
h3 Achievements
include ../profiles/achievements
.modal-footer
button.btn.btn-default(ng-click='modals.member = false') Ok

View file

@ -13,58 +13,65 @@
// <div class='achievement achievement-shield'></div>
// <div class='achievement achievement-karaoke'></div>
.achievement.achievement-helm(ng-if='profile.backer.npc')
div(ng-if='profile.backer.npc')
.achievement.achievement-helm
h5
span.label.label-success {{profile.backer.npc}} NPC
span.label.label-master {{profile.backer.npc}} NPC
small Backed the Kickstarter project at the maximum level!
hr
.achievement.achievement-firefox(ng-if='profile.backer.contributor')
div(ng-class='{muted: !profile.backer.contributor, hidden: user.id!=profile.id}')
h5
span.label.label-inverse(ng-if='profile.backer.contributor') {{profile.backer.contributor}}
span.label(ng-if='!profile.backer.contributor') Contributor
small
| Has contributed to HabitRPG (code, design, pixel art, legal advice, docs, etc). Want this badge? Fix a bug :)
div(ng-if='profile.backer.contributor || user._id == profile._id')
.achievement.achievement-firefox(ng-show='profile.backer.contributor')
div(ng-class='{muted: !profile.backer.contributor}')
h5
span.label.label-inverse(ng-if='profile.backer.contributor') {{profile.backer.contributor}}
span.label(ng-if='!profile.backer.contributor') Contributor
small.
Has contributed to HabitRPG (code, design, pixel art, legal advice, docs, etc). Want this badge? Fix a bug :)
hr
.achievement.achievement-heart(ng-if='profile.backer.tier')
div(ng-if='profile.backer.tier')
.achievement.achievement-heart
h5 Kickstarter Backer - ${{profile.backer.tier}} Tier
small Backed the Kickstarter Project
hr
.achievement.achievement-thermometer(ng-show='profile.achievements.streak')
div(ng-class='{muted: !profile.achievements.streak, hidden: user.id!=profile.id}')
h5 {{profile.achievements.streak || 0 }} Streak Achievement(s)
small
| Has performed {{profile.achievements.streak || 0 }} 21-day streaks on Dailies
div(ng-if='profile.achievements.streak || user._id == profile._id')
.achievement.achievement-thermometer(ng-show='profile.achievements.streak')
div(ng-class='{muted: !profile.achievements.streak}')
h5 {{profile.achievements.streak || 0 }} Streak Achievement(s)
small Has performed {{profile.achievements.streak || 0 }} 21-day streaks on Dailies
hr
.achievement.achievement-cake(ng-if='profile.achievements.originalUser')
div(ng-if='profile.achievements.originalUser')
h5 Original User
small
| Goes way back to the Habit's days of yore (bless them for soldiering through bugs!)
div(ng-if='profile.achievements.ultimateGear || user._id == profile._id')
.achievement.achievement-armor(ng-show='profile.achievements.ultimateGear')
div(ng-class='{muted: !profile.achievements.ultimateGear}')
h5 Ultimate Gear
small Has upgraded to the maximum weapon and armor set
hr
.achievement.achievement-armor(ng-show='profile.achievements.ultimateGear')
div(ng-class='{muted: !profile.achievements.ultimateGear, hidden: user.id!=profile.id}')
h5 Ultimate Gear
small Has upgraded to the maximum weapon and armor set
div(ng-if='profile.achievements.beastMaster || user._id == profile._id')
.achievement.achievement-rat(ng-show='profile.achievements.beastMaster')
div(ng-class='{muted: !profile.achievements.beastMaster}')
h5 Beast Master
small Has found all 90 pets (insanely difficult, give this user props!)
hr
.achievement.achievement-rat(ng-show='profile.achievements.beastMaster')
div(ng-class='{muted: !profile.achievements.beastMaster, hidden: user.id!=profile.id}')
h5 Beast Master
small Has found all 90 pets (insanely difficult, give this user props!)
hr
.achievement.achievement-tree(ng-if='profile.achievements.helpedHabit')
div(ng-if='profile.achievements.helpedHabit')
.achievement.achievement-tree
h5 Helped Habit Grow
small
| Has helped HabitRPG grow by filling out
a(href='http://community.habitrpg.com/node/290', target='_blank') this survey.
small.
Helped HabitRPG grow by filling out <a href='http://community.habitrpg.com/node/290' target='_blank'>this survey.</a>
hr
div(ng-if='profile.achievements.originalUser || profile.achievements.veteranUser')
.achievement.achievement-cake
div(ng-if='profile.achievements.veteran')
h5 Veteran
small
| Has weathered Habit The Grey (our pre Angular website), and has gained many battle-scars from its bugs.
div(ng-if='profile.achievements.originalUser')
h5 Original User!
small
| One of the <em>very</em> original early adopters, Talk about alpha tester!
hr