mirror of
https://github.com/HabitRPG/habitica.git
synced 2025-12-19 15:48:04 +01:00
challenges: much better implemntation of ui-router for nested groups. No
need to specify fields or populate members at /groups - instead, provide that functionality at /groups/:gid
This commit is contained in:
@@ -16,6 +16,7 @@ window.habitrpg = angular.module('habitrpg',
|
||||
.when('/options', '/options/profile/avatar')
|
||||
.when('/options/profile', '/options/profile/avatar')
|
||||
.when('/options/groups', '/options/groups/tavern')
|
||||
.when('/options/groups/guilds', '/options/groups/guilds/public')
|
||||
.when('/options/inventory', '/options/inventory/inventory')
|
||||
|
||||
// redirect states that don't match
|
||||
@@ -64,12 +65,6 @@ window.habitrpg = angular.module('habitrpg',
|
||||
url: "/tavern",
|
||||
templateUrl: "partials/options.groups.tavern.html",
|
||||
controller: 'TavernCtrl'
|
||||
// TODO this doesn't work, seems ngResource doesn't get the .then() function
|
||||
// resolve: {
|
||||
// group: ['Groups', function(Groups){
|
||||
// //return Groups.fetchTavern();
|
||||
// }]
|
||||
// }
|
||||
})
|
||||
.state('options.groups.party', {
|
||||
url: '/party',
|
||||
@@ -81,6 +76,21 @@ window.habitrpg = angular.module('habitrpg',
|
||||
templateUrl: "partials/options.groups.guilds.html",
|
||||
controller: 'GuildsCtrl'
|
||||
})
|
||||
.state('options.groups.guilds.public', {
|
||||
url: '/public',
|
||||
templateUrl: "partials/options.groups.guilds.public.html"
|
||||
})
|
||||
.state('options.groups.guilds.create', {
|
||||
url: '/create',
|
||||
templateUrl: "partials/options.groups.guilds.create.html"
|
||||
})
|
||||
.state('options.groups.guilds.detail', {
|
||||
url: '/:gid',
|
||||
templateUrl: 'partials/options.groups.guilds.detail.html',
|
||||
controller: ['$scope', 'Groups', '$stateParams', function($scope, Groups, $stateParams){
|
||||
$scope.group = Groups.Group.get({gid:$stateParams.gid});
|
||||
}]
|
||||
})
|
||||
|
||||
// Options > Inventory
|
||||
.state('options.inventory', {
|
||||
|
||||
@@ -205,7 +205,6 @@ habitrpg.controller("GroupsCtrl", ['$scope', '$rootScope', 'Groups', '$http', 'A
|
||||
function($scope, Groups, User) {
|
||||
Groups.fetchTavern();
|
||||
$scope.group = Groups.groups.tavern;
|
||||
|
||||
$scope.rest = function(){
|
||||
User.user.flags.rest = !User.user.flags.rest;
|
||||
User.log({op:'set',data:{'flags.rest':User.user.flags.rest}});
|
||||
|
||||
@@ -10,7 +10,7 @@ angular.module('groupServices', ['ngResource']).
|
||||
var Group = $resource(API_URL + '/api/v1/groups/:gid',
|
||||
{gid:'@_id', messageId: '@_messageId'},
|
||||
{
|
||||
//'query': {method: "GET", isArray:false}
|
||||
query: {method: "GET", isArray:false},
|
||||
postChat: {method: "POST", url: API_URL + '/api/v1/groups/:gid/chat'},
|
||||
deleteChatMessage: {method: "DELETE", url: API_URL + '/api/v1/groups/:gid/chat/:messageId'},
|
||||
join: {method: "POST", url: API_URL + '/api/v1/groups/:gid/join'},
|
||||
@@ -34,32 +34,24 @@ 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', fields:'members'}, function(_groups){
|
||||
partyQ.resolve(_groups[0]);
|
||||
Members.populate(_groups[0]);
|
||||
Group.get({gid:'party'}, function(party){
|
||||
partyQ.resolve(party);
|
||||
})
|
||||
|
||||
return {
|
||||
// Note the _.once() to make sure it can never be called again
|
||||
fetchGuilds: _.once(function(){
|
||||
$('#loading-indicator').show();
|
||||
Group.query({type:'guilds'}, function(_groups){
|
||||
$('#loading-indicator').hide();
|
||||
guildsQ.resolve(_groups);
|
||||
Members.populate(_groups);
|
||||
})
|
||||
Group.query({type:'public'}, function(_groups){
|
||||
publicQ.resolve(_groups);
|
||||
Members.populate(_groups);
|
||||
Group.query(function(_groups){
|
||||
guildsQ.resolve(_groups.guilds);
|
||||
Members.populate(_groups.guilds);
|
||||
publicQ.resolve(_groups['public']);
|
||||
Members.populate(_groups['public']);
|
||||
})
|
||||
}),
|
||||
|
||||
fetchTavern: _.once(function(){
|
||||
$('#loading-indicator').show();
|
||||
Group.query({type:'tavern'}, function(_groups){
|
||||
$('#loading-indicator').hide();
|
||||
Members.populate(_groups[0]);
|
||||
tavernQ.resolve(_groups[0]);
|
||||
Group.get({gid:'habitrpg'}, function(tavern){
|
||||
tavernQ.resolve(tavern);
|
||||
})
|
||||
}),
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ var api = module.exports;
|
||||
|
||||
var itemFields = 'items.armor items.head items.shield items.weapon items.currentPet';
|
||||
var partyFields = 'profile preferences stats achievements party backer flags.rest auth.timestamps ' + itemFields;
|
||||
var nameFields = 'profile.name';
|
||||
|
||||
function removeSelf(group, user){
|
||||
group.members = _.filter(group.members, function(m){return m._id != user._id});
|
||||
@@ -31,80 +32,31 @@ api.getMember = function(req, res) {
|
||||
})
|
||||
}
|
||||
|
||||
function sanitizeMemberFields(fields, fallback) {
|
||||
if (!fields) return fallback;
|
||||
if (~fields.indexOf(',')) fields = fields.replace(/\,/g, ' ');
|
||||
var intersection = _.intersection( (partyFields + ' items').split(' '), (fields).split(' ') );
|
||||
return _.isEmpty(_.filter(intersection, function(f){return f!=''})) ? fallback : intersection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get groups. If req.query.type provided, 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`
|
||||
*
|
||||
* We can request specific fields to keep things slim. Not specified gives you full detail. Request them as:
|
||||
* - simple: ?fields=title,description,chat,challenges,memberCount
|
||||
* - sub-fields: ?fields=title,description,members&fields.members=profile.name
|
||||
* Fetch groups list. This no longer returns party or tavern, as those can be requested indivdually
|
||||
* as /groups/party or /groups/tavern
|
||||
*/
|
||||
api.getGroups = function(req, res, next) {
|
||||
api.getGroups = function(req, res) {
|
||||
var user = res.locals.user;
|
||||
var type = req.query.type && req.query.type.split(',');
|
||||
var fields = req.query.fields && req.query.fields.replace(/\,/g, ' ')
|
||||
var challengeFields = 'name description'; // public challenge fields, request specific challenge for more details
|
||||
var groupFields = 'name description memberCount';
|
||||
var sort = '-memberCount';
|
||||
|
||||
async.parallel({
|
||||
|
||||
party: function(cb) {
|
||||
if (type && !~type.indexOf('party')) return cb(null, {});
|
||||
var query = Group.findOne({type: 'party', members: {'$in': [user._id]}});
|
||||
if (fields) {
|
||||
query.select(fields);
|
||||
if (~fields.indexOf('members'))
|
||||
query.populate('members', sanitizeMemberFields(req.query['fields.members'], partyFields));
|
||||
if (~fields.indexOf('challenges'))
|
||||
query.populate('challenges', challengeFields);
|
||||
} else {
|
||||
query.populate('members', partyFields);
|
||||
}
|
||||
query.exec(function(err, group){
|
||||
if (err) return cb(err);
|
||||
// Remove self from party (match in `populate()` hangs - mongoose bug)
|
||||
removeSelf(group, user);
|
||||
cb(null, group);
|
||||
});
|
||||
// unecessary given our ui-router setup
|
||||
party: function(cb){
|
||||
return cb(null, [{}]);
|
||||
},
|
||||
|
||||
guilds: function(cb) {
|
||||
if (type && !~type.indexOf('guilds')) return cb(null, {});
|
||||
var query = Group.find({type: 'guild', members: {'$in': [user._id]}});
|
||||
if (fields) {
|
||||
query.select(fields);
|
||||
if (~fields.indexOf('members'))
|
||||
query.populate('members', sanitizeMemberFields(req.query['fields.members'], 'profile.name'));
|
||||
if (~fields.indexOf('challenges'))
|
||||
query.populate('challenges', challengeFields);
|
||||
} else {
|
||||
query.populate('members', 'profile.name');
|
||||
}
|
||||
query.exec(cb);
|
||||
Group.find({members: {'$in': [user._id]}, type:'guild'})
|
||||
.select(groupFields).sort(sort).exec(cb);
|
||||
},
|
||||
|
||||
tavern: function(cb) {
|
||||
if (type && !~type.indexOf('tavern')) return cb(null, {});
|
||||
var query = Group.findById('habitrpg');
|
||||
if (fields) {
|
||||
query.select(fields);
|
||||
if (~fields.indexOf('challenges'))
|
||||
query.populate('challenges', challengeFields);
|
||||
}
|
||||
query.exec(cb);
|
||||
},
|
||||
|
||||
"public": function(cb) {
|
||||
if (type && !~type.indexOf('public')) return cb(null, []);
|
||||
'public': function(cb) {
|
||||
Group.find({privacy: 'public'})
|
||||
.select('name description memberCount members')
|
||||
.sort('-memberCount')
|
||||
.select(groupFields + ' members')
|
||||
.sort(sort)
|
||||
.exec(function(err, groups){
|
||||
if (err) return cb(err);
|
||||
_.each(groups, function(g){
|
||||
@@ -114,41 +66,49 @@ api.getGroups = function(req, res, next) {
|
||||
});
|
||||
cb(null, groups);
|
||||
});
|
||||
},
|
||||
|
||||
// unecessary given our ui-router setup
|
||||
tavern: function(cb) {
|
||||
return cb(null, [{}]);
|
||||
}
|
||||
|
||||
}, function(err, results){
|
||||
if (err) return res.json(500, {err: err});
|
||||
|
||||
// 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);
|
||||
})
|
||||
};
|
||||
|
||||
/**
|
||||
* Get group
|
||||
* TODO: implement requesting fields ?fields=chat,members
|
||||
*/
|
||||
api.getGroup = function(req, res, next) {
|
||||
api.getGroup = function(req, res) {
|
||||
var user = res.locals.user;
|
||||
var gid = req.params.gid;
|
||||
|
||||
Group.findById(gid).populate('members', partyFields).exec(function(err, group){
|
||||
if ( (group.type == 'guild' && group.privacy == 'private') || group.type == 'party') {
|
||||
if(!_.find(group.members, {_id: user._id}))
|
||||
return res.json(401, {err: "You don't have access to this group"});
|
||||
}
|
||||
// Remove self from party (see above failing `match` directive in `populate`
|
||||
if (group.type == 'party') {
|
||||
removeSelf(group, user);
|
||||
}
|
||||
res.json(group);
|
||||
// This will be called for the header, we need extra members' details than usuals
|
||||
if (gid == 'party') {
|
||||
Group.findOne({type: 'party', members: {'$in': [user._id]}})
|
||||
.populate('members', partyFields).exec(function(err, group){
|
||||
if (err) return res.json(500,{err:err});
|
||||
removeSelf(group, user);
|
||||
res.json(group);
|
||||
});
|
||||
} else {
|
||||
Group.findById(gid).populate('members', nameFields).exec(function(err, group){
|
||||
if ( (group.type == 'guild' && group.privacy == 'private') || group.type == 'party') {
|
||||
if(!_.find(group.members, {_id: user._id}))
|
||||
return res.json(401, {err: "You don't have access to this group"});
|
||||
}
|
||||
// Remove self from party (see above failing `match` directive in `populate`
|
||||
if (group.type == 'party') {
|
||||
removeSelf(group, user);
|
||||
}
|
||||
res.json(group);
|
||||
})
|
||||
}
|
||||
|
||||
})
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -23,43 +23,51 @@ script(type='text/ng-template', id='partials/options.groups.party.html')
|
||||
{{user.id}}
|
||||
include ./create-group
|
||||
|
||||
script(type='text/ng-template', id='partials/options.groups.guilds.public.html')
|
||||
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(invitation)') Accept
|
||||
a.btn.btn-danger(ng-click='reject(invitation)') 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.memberCount}} member(s)
|
||||
li
|
||||
// join / leave
|
||||
a.btn.btn-small.btn-danger(ng-show='group_.isMember', ng-click='leave(group)')
|
||||
i.icon-ban-circle
|
||||
| Leave
|
||||
a.btn.btn-small.btn-success(ng-hide='group._isMember', ng-click='join(group)')
|
||||
i.icon-ok
|
||||
| Join
|
||||
h4 {{group.name}}
|
||||
p {{group.description}}
|
||||
|
||||
script(type='text/ng-template', id='partials/options.groups.guilds.detail.html')
|
||||
include ./group
|
||||
|
||||
script(type='text/ng-template', id='partials/options.groups.guilds.create.html')
|
||||
include ./create-group
|
||||
|
||||
script(type='text/ng-template', id='partials/options.groups.guilds.html')
|
||||
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(invitation)') Accept
|
||||
a.btn.btn-danger(ng-click='reject(invitation)') 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.memberCount}} member(s)
|
||||
li
|
||||
// join / leave
|
||||
a.btn.btn-small.btn-danger(ng-show='group_.isMember', ng-click='leave(group)')
|
||||
i.icon-ban-circle
|
||||
| Leave
|
||||
a.btn.btn-small.btn-success(ng-hide='group._isMember', 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
|
||||
li(ng-class="{ active: $state.includes('options.groups.guilds.public') }")
|
||||
a(ui-sref='options.groups.guilds.public')
|
||||
| Public Guilds
|
||||
li(ng-class="{ active: $stateParams.gid == group._id }", ng-repeat='group in groups.guilds')
|
||||
a(ui-sref="options.groups.guilds.detail({gid:group._id})")
|
||||
| {{group.name}}
|
||||
li(ng-class="{ active: $state.includes('options.groups.guilds.create') }")
|
||||
a(ui-sref='options.groups.guilds.create')
|
||||
| Create Guild
|
||||
|
||||
.tab-pane#groups-create-guild
|
||||
include ./create-group
|
||||
.tab-content
|
||||
.tab-pane.active
|
||||
div(ui-view)
|
||||
|
||||
script(type='text/ng-template', id='partials/options.groups.html')
|
||||
ul.nav.nav-tabs
|
||||
|
||||
Reference in New Issue
Block a user