[#1465] add member modals back in. Not all info is present, just a bit more

work. Can now click guild & tavern members! does lazy-loading of members
so we don't have to load all their information at once
This commit is contained in:
Tyler Renelle
2013-09-07 12:45:39 -04:00
parent 28e2cb32f2
commit 91d1cdf361
17 changed files with 229 additions and 156 deletions

View File

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

View File

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

View File

@@ -1,7 +1,7 @@
"use strict"; "use strict";
window.habitrpg = angular.module('habitrpg', 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("API_URL", "")
.constant("STORAGE_USER_ID", 'habitrpg-user') .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,38 @@
"use strict"; "use strict";
habitrpg.controller("GroupsCtrl", ['$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) { function($scope, $rootScope, Groups, $http, API_URL, $q, User, Members, $location) {
$scope.isMember = function(user, group){ $scope.isMember = function(user, group){
return ~(group.members.indexOf(user._id)); return ~(group.members.indexOf(user._id));
} }
$scope.groups = Groups.groups; // ------ Loading ------
$scope.groups = Groups.groups;
$scope.fetchGuilds = Groups.fetchGuilds; $scope.fetchGuilds = Groups.fetchGuilds;
$scope.fetchTavern = Groups.fetchTavern; $scope.fetchTavern = Groups.fetchTavern;
// ------ Modals ------
$scope.clickMember = function(uid) {
if (User.user._id == uid) {
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?
debugger
Members.selectMember(uid);
$rootScope.modals.member = true;
}
}
// ------ Invites ------
$scope.invitee = ''; $scope.invitee = '';
$scope.invite = function(group, uuid){ $scope.invite = function(group, uuid){
group.$invite({uuid:uuid}, function(){ group.$invite({uuid:uuid}, function(){
@@ -22,6 +43,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){ .controller('ChatCtrl', ['$scope', 'Groups', 'User', function($scope, Groups, User){
$scope._chatMessage = ''; $scope._chatMessage = '';
$scope.postChat = function(group, message){ $scope.postChat = function(group, message){

View File

@@ -6,17 +6,4 @@ habitrpg.controller("UserAvatarCtrl", ['$scope', '$location', 'User',
$scope.hideUserAvatar = function() { $scope.hideUserAvatar = function() {
$(".userAvatar").hide(); $(".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']). angular.module('groupServices', ['ngResource']).
factory('Groups', ['API_URL', '$resource', 'User', '$q', factory('Groups', ['API_URL', '$resource', 'User', '$q', 'Members',
function(API_URL, $resource, User, $q) { function(API_URL, $resource, User, $q, Members) {
var Group = $resource(API_URL + '/api/v1/groups/:gid', var Group = $resource(API_URL + '/api/v1/groups/:gid',
{gid:'@_id'}, {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 // But we don't defer triggering Party, since we always need it for the header if nothing else
Group.query({type:'party'}, function(_groups){ Group.query({type:'party'}, function(_groups){
partyQ.resolve(_groups[0]); partyQ.resolve(_groups[0]);
Members.populate(_groups[0]);
}) })
return { return {
@@ -42,10 +43,12 @@ angular.module('groupServices', ['ngResource']).
$('#loading-indicator').show(); $('#loading-indicator').show();
Group.query({type:'guilds'}, function(_groups){ Group.query({type:'guilds'}, function(_groups){
guildsQ.resolve(_groups); guildsQ.resolve(_groups);
Members.populate(_groups);
$('#loading-indicator').hide(); $('#loading-indicator').hide();
}) })
Group.query({type:'public'}, function(_groups){ Group.query({type:'public'}, function(_groups){
publicQ.resolve(_groups); publicQ.resolve(_groups);
Members.populate(_groups);
}) })
}), }),
@@ -54,6 +57,7 @@ angular.module('groupServices', ['ngResource']).
Group.query({type:'tavern'}, function(_groups){ Group.query({type:'tavern'}, function(_groups){
$('#loading-indicator').hide(); $('#loading-indicator').hide();
tavernQ.resolve(_groups[0]); 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 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; 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 * 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` * object {guilds, public, party, tavern}. req.query.type can be comma-separated `type=guilds,party`

View File

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

View File

@@ -52,13 +52,13 @@ html
script(type='text/javascript', src='/js/services/sharedServices.js') 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/userServices.js')
script(type='text/javascript', src='/js/services/groupServices.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/filters/filters.js')
script(type='text/javascript', src='/js/directives/directives.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/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/menuCtrl.js')
script(type='text/javascript', src='/js/controllers/notificationCtrl.js') script(type='text/javascript', src='/js/controllers/notificationCtrl.js')
script(type='text/javascript', src='/js/controllers/rootCtrl.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}') 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)')
| {{message.user}} | {{message.user}}
span span
span(ng-bind-html="message.text | linky:'_blank'") - span(ng-bind-html="message.text | linky:'_blank'") -

View File

@@ -1,5 +1,3 @@
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 // 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 // Subscribe to that ticket & change this when they fix

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 .character-sprites
span(ng-class='{zzz:profile.flags.rest}') span(ng-class='{zzz:profile.flags.rest}')
span(class='{{profile.preferences.gender}}_skin_{{profile.preferences.skin}}') span(class='{{profile.preferences.gender}}_skin_{{profile.preferences.skin}}')

View File

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

View File

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

View File

@@ -0,0 +1,21 @@
div(ng-controller='MemberModalCtrl')
div(modal='modals.member')
//-.modal-header
h3 username
.modal-body
h2.profile-modal-header {{username(profile.auth, profile.profile.name)}}
app:avatar:profile
hr
.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
app:avatar:profile-stats
.span6
h3 Achievements
app:avatar:achievements
.modal-footer
button.btn.btn-default(ng-click='modals.member = false') Ok