diff --git a/assets/js/controllers/groupsCtrl.js b/assets/js/controllers/groupsCtrl.js index f13e4778e3..69b90aec43 100644 --- a/assets/js/controllers/groupsCtrl.js +++ b/assets/js/controllers/groupsCtrl.js @@ -2,26 +2,61 @@ habitrpg - .controller("GroupsCtrl", ['$scope', '$rootScope', 'Groups', '$http', '$location', '$http', 'API_URL', - function($scope, $rootScope, Groups, $http, API_URL) { - $scope._chatMessage = ''; - $scope.groups = Groups.query(function(groups){ - $scope.members = groups.members; + .controller("GroupsCtrl", ['$scope', '$rootScope', 'Groups', '$http', 'API_URL', '$q', + function($scope, $rootScope, Groups, $http, API_URL, $q) { + + // The user may not visit the public guilds, personal guilds, and tavern pages. So + // we defer loading them to the html until they've clicked the tabs + var partyQ = $q.defer(), + guildsQ = $q.defer(), + publicQ = $q.defer(), + tavernQ = $q.defer(); + + $scope.groups = { + party: partyQ.promise, + guilds: guildsQ.promise, + public: publicQ.promise, + tavern: tavernQ.promise + }; + + // But we don't defer triggering Party, since we always need it for the header if nothing else + Groups.query({type:'party'}, function(_groups){ + partyQ.resolve(_groups[0]); + }) + + // Note the _.once() to make sure it can never be called again + $scope.fetchGuilds = _.once(function(){ + $('#loading-indicator').show(); + Groups.query({type:'guilds'}, function(_groups){ + guildsQ.resolve(_groups); + $('#loading-indicator').hide(); + }) + Groups.query({type:'public'}, function(_groups){ + publicQ.resolve(_groups); + }) }); + + $scope.fetchTavern = _.once(function(){ + $('#loading-indicator').show(); + Groups.query({type:'tavern'}, function(_groups){ + $('#loading-indicator').hide(); + tavernQ.resolve(_groups[0]); + }) + }); + + //$scope._chatMessage = ''; $scope.postChat = function(group, message){ //FIXME ng-model makes this painfully slow! using jquery for now, which is really non-angular-like message = $('.chat-textarea').val(); if (_.isEmpty(message)) return $('.chat-btn').addClass('disabled'); - $http.post('/api/v1/groups/'+group._id+'/chat', {message:message}) - .success(function(data){ - //$scope._chatMessage = ''; - $('.chat-textarea').val(''); - group.chat = data; - $('.chat-btn').removeClass('disabled'); - }); + group.$postChat({message:message}, function(data){ + //$scope._chatMessage = ''; + $('.chat-textarea').val(''); + group.chat = data.chat; + $('.chat-btn').removeClass('disabled'); + }); } - $scope.party = true; } ]) @@ -29,6 +64,9 @@ habitrpg function($scope, Groups) { $scope.type = 'guild'; $scope.text = 'Guild'; + $scope.join = function(group){ + + } } ]) @@ -36,17 +74,12 @@ habitrpg function($scope, Groups) { $scope.type = 'party'; $scope.text = 'Party'; - Groups.query(function(groups){ - $scope.group = groups.party; - }) + $scope.group = $scope.groups.party; } ]) .controller("TavernCtrl", ['$scope', 'Groups', function($scope, Groups) { - //FIXME make sure this query is only called once for all these controllers! If not, let's memoize groups at groupServices level - Groups.query(function(groups){ - $scope.group = groups.tavern; - }); + $scope.group = $scope.groups.tavern; } ]) \ No newline at end of file diff --git a/assets/js/services/groupServices.js b/assets/js/services/groupServices.js index 18cb0262d9..caf75ca535 100644 --- a/assets/js/services/groupServices.js +++ b/assets/js/services/groupServices.js @@ -10,8 +10,10 @@ angular.module('groupServices', ['ngResource']). var Group = $resource(API_URL + '/api/v1/groups/:gid', {gid:'@_id'}, { - 'query': {method: "GET", isArray:false} - //postChat: {method: "POST"} + //'query': {method: "GET", isArray:false} + postChat: {method: "POST", url: API_URL + '/api/v1/groups/:gid/chat'}, + join: {method: "POST", url: API_URL + '/api/v1/groups/:gid/join'}, + leave: {method: "POST", url: API_URL + '/api/v1/groups/:gid/leave'} }); return Group; diff --git a/public/page-loader.gif b/public/page-loader.gif new file mode 100644 index 0000000000..8be8ba338d Binary files /dev/null and b/public/page-loader.gif differ diff --git a/src/controllers/groups.js b/src/controllers/groups.js index fa0baad6bf..9ce51a44ec 100644 --- a/src/controllers/groups.js +++ b/src/controllers/groups.js @@ -16,13 +16,22 @@ var api = module.exports; ------------------------------------------------------------------------ */ +/** + * 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` + * @param req + * @param res + * @param next + */ api.getGroups = function(req, res, next) { var user = res.locals.user; var usernameFields = 'auth.local.username auth.facebook.displayName auth.facebook.givenName auth.facebook.familyName auth.facebook.name'; + var type = req.query.type && req.query.type.split(','); // First get all groups async.parallel({ party: function(cb) { + if (type && !~type.indexOf('party')) return cb(null, {}); Group .findOne({type: 'party', members: {'$in': [user._id]}}) .populate({ @@ -33,13 +42,16 @@ api.getGroups = function(req, res, next) { .exec(cb); }, guilds: function(cb) { + if (type && !~type.indexOf('guilds')) return cb(null, []); Group.find({type: 'guild', members: {'$in': [user._id]}}).populate('members', usernameFields).exec(cb); // Group.find({type: 'guild', members: {'$in': [user._id]}}, cb); }, tavern: function(cb) { + if (type && !~type.indexOf('tavern')) return cb(null, {}); Group.findOne({_id: 'habitrpg'}, cb); }, "public": function(cb) { + if (type && !~type.indexOf('public')) return cb(null, []); Group.find({privacy: 'public'}, {name:1, description:1, members:1}, cb); } }, function(err, results){ @@ -52,7 +64,15 @@ api.getGroups = function(req, res, next) { // Sort public groups by members length (not easily doable in mongoose) results.public = _.sortBy(results.public, function(group){ return -group.members.length; - }) + }); + + // If they're requesting a specific type, let's return it as an array so that $ngResource + // can utilize it properly + if (type) { + results = _.reduce(type, function(m,t){ + return m.concat(_.isArray(results[t]) ? results[t] : [results[t]]); + }, []); + } res.json(results); }) @@ -74,7 +94,7 @@ api.postChat = function(req, res, next) { uuid: user._id, contributor: user.backer && user.backer.contributor, npc: user.backer && user.backer.npc, - text: req.body.message, + text: req.query.message, // FIXME this should be body, but ngResource is funky user: helpers.username(user.auth, user.profile.name), timestamp: +(new Date) }; @@ -87,8 +107,29 @@ api.postChat = function(req, res, next) { user.save(); } - group.save(function(err, group){ + group.save(function(err, saved){ if (err) return res.json(500, {err:err}); - res.json(group.chat); + res.json(saved); + }) +} + +api.join = function(req, res, next) { + var user = res.locals.user, + group = res.locals.group; + + group.members.push(user._id); + group.save(function(err, saved){ + if (err) return res.json(500,{err:err}); + res.json(saved); + }); +} + +api.leave = function(req, res, next) { + var user = res.locals.user, + group = res.locals.group; + + Group.update({_id:group._id},{$pull:{members:user._id}}, function(err){ + if (err) return res.json(500,{err:err}); + res.send(200); }) } \ No newline at end of file diff --git a/src/routes/api.js b/src/routes/api.js index eea0f92157..e59c5ddc49 100644 --- a/src/routes/api.js +++ b/src/routes/api.js @@ -61,6 +61,9 @@ router.get('/groups', auth, groups.getGroups); //PUT /groups/:gid (edit group) //DELETE /groups/:gid +router.post('/groups/:gid/join', auth, groups.attachGroup, groups.join); +router.post('/groups/:gid/leave', auth, groups.attachGroup, groups.leave); + //GET /groups/:gid/chat router.post('/groups/:gid/chat', auth, groups.attachGroup, groups.postChat); //PUT /groups/:gid/chat/:messageId diff --git a/views/index.jade b/views/index.jade index cd232775f5..06fc9c2007 100644 --- a/views/index.jade +++ b/views/index.jade @@ -76,27 +76,32 @@ html meta(name='apple-mobile-web-app-capable', content='yes') body(ng-app="habitrpg", ng-controller="RootCtrl", ng-cloak) - include ./shared/modals/index - include ./shared/header/header + div(ng-controller='GroupsCtrl') + include ./shared/modals/index + include ./shared/header/header - #notification-area(ng-controller='NotificationCtrl') - #wrap + #notification-area(ng-controller='NotificationCtrl') + #wrap - // Errors - ul.unstyled.alert.alert-error(ng-show='_flash.error') - li(ng-repeat='error in _flash.error') {{error}} + // Errors + ul.unstyled.alert.alert-error(ng-show='_flash.error') + li(ng-repeat='error in _flash.error') {{error}} - //if they hide the header, we still need user-menu visible - div(ng-if='user.preferences.hideHeader') - include ./shared/header/menu + //if they hide the header, we still need user-menu visible + div(ng-if='user.preferences.hideHeader') + include ./shared/header/menu - .exp-chart(ng-show='page.charts.exp') + .exp-chart(ng-show='page.charts.exp') - #main(ng-view) + #main(ng-view) - .container - h3 - a(target='_blank',href='https://workflowy.com/shared/9c77c53d-a174-d181-73a4-dc611508a936/') Roadmap - include ./shared/footer + .container + h3 + a(target='_blank',href='https://workflowy.com/shared/9c77c53d-a174-d181-73a4-dc611508a936/') Roadmap + include ./shared/footer + // Modal backdrop for when loading big stuff + #loading-indicator(style='display:none;') + .modal-backdrop + h1(style='position:absolute;top:50%;left:45%;color:white;z-index:9000') Loading... diff --git a/views/options/groups/index.jade b/views/options/groups/index.jade index 83fed721ca..aa1c6c8215 100644 --- a/views/options/groups/index.jade +++ b/views/options/groups/index.jade @@ -8,7 +8,7 @@ div(ng-controller='GroupsCtrl') li.active a(data-target='#groups-party', data-toggle='tab') Party li - a(data-target='#groups-guilds', data-toggle='tab') Guilds + a(data-target='#groups-guilds', data-toggle='tab', ng-click='fetchGuilds()') Guilds .tab-content #groups-party.tab-pane.active Party diff --git a/views/options/index.jade b/views/options/index.jade index 7249141e3c..40afc3de0c 100644 --- a/views/options/index.jade +++ b/views/options/index.jade @@ -9,7 +9,7 @@ i.icon-user | Profile li - a(data-toggle='tab',data-target='#groups-tab') + a(data-toggle='tab',data-target='#groups-tab', ng-click='fetchParty()') i.icon-heart | Groups li(ng-show='user.flags.dropsEnabled') @@ -21,7 +21,7 @@ i.icon-leaf | Stable li - a(data-toggle='tab',data-target='tavern-tab') + a(data-toggle='tab',data-target='#tavern-tab',ng-click='fetchTavern()') i.icon-eye-close | Tavern li diff --git a/views/shared/header/header.jade b/views/shared/header/header.jade index 5828472d54..371e9cdc39 100644 --- a/views/shared/header/header.jade +++ b/views/shared/header/header.jade @@ -26,7 +26,7 @@ a(x-bind='click:toggleChart', ng-click='notPorted()', data-id='exp', tooltip='Progress') i.icon-signal // party - span(ng-controller='GroupsCtrl') + span(ng-controller='PartyCtrl') .herobox-wrap(ng-repeat='profile in groups.party.members') include avatar