Merge branch 'develop' into Sinble-bug/slow_chat
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"extends": "habitrpg/browser",
|
||||
"env": {
|
||||
"jquery": true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,342 @@
|
||||
"use strict";
|
||||
|
||||
/* Refresh page if idle > 6h */
|
||||
var REFRESH_FREQUENCY = 21600000;
|
||||
var refresh;
|
||||
var refresher = function() {
|
||||
window.location.reload(true);
|
||||
};
|
||||
|
||||
var awaitIdle = function() {
|
||||
if(refresh) clearTimeout(refresh);
|
||||
refresh = setTimeout(refresher, REFRESH_FREQUENCY);
|
||||
};
|
||||
|
||||
awaitIdle();
|
||||
$(document).on('mousemove keydown mousedown touchstart', awaitIdle);
|
||||
/* Refresh page if idle > 6h */
|
||||
|
||||
window.habitrpg = angular.module('habitrpg',
|
||||
['ui.bootstrap', 'ui.keypress', 'ui.router', 'chieffancypants.loadingBar', 'At', 'infinite-scroll', 'ui.select2', 'angular.filter', 'ngResource', 'ngSanitize'])
|
||||
|
||||
// @see https://github.com/angular-ui/ui-router/issues/110 and https://github.com/HabitRPG/habitrpg/issues/1705
|
||||
// temporary hack until they have a better solution
|
||||
|
||||
.constant("API_URL", "")
|
||||
.constant("STORAGE_USER_ID", 'habitrpg-user')
|
||||
.constant("STORAGE_SETTINGS_ID", 'habit-mobile-settings')
|
||||
.constant("MOBILE_APP", false)
|
||||
.constant("TAVERN_ID", window.habitrpgShared.TAVERN_ID)
|
||||
//.constant("STORAGE_GROUPS_ID", "") // if we decide to take groups offline
|
||||
|
||||
.config(['$stateProvider', '$urlRouterProvider', '$httpProvider', 'STORAGE_SETTINGS_ID',
|
||||
function($stateProvider, $urlRouterProvider, $httpProvider, STORAGE_SETTINGS_ID) {
|
||||
|
||||
$urlRouterProvider
|
||||
// Setup default selected tabs
|
||||
.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/groups/hall', '/options/groups/hall/heroes')
|
||||
.when('/options/inventory', '/options/inventory/drops')
|
||||
.when('/options/settings', '/options/settings/settings')
|
||||
|
||||
// redirect states that don't match
|
||||
.otherwise("/tasks");
|
||||
|
||||
$stateProvider
|
||||
|
||||
// Tasks
|
||||
.state('tasks', {
|
||||
url: "/tasks",
|
||||
templateUrl: "partials/main.html",
|
||||
title: env.t('titleTasks')
|
||||
})
|
||||
|
||||
// Options
|
||||
.state('options', {
|
||||
url: "/options",
|
||||
templateUrl: "partials/options.html",
|
||||
controller: function(){}
|
||||
})
|
||||
|
||||
// Options > Profile
|
||||
.state('options.profile', {
|
||||
url: "/profile",
|
||||
templateUrl: "partials/options.profile.html",
|
||||
controller: 'UserCtrl'
|
||||
})
|
||||
.state('options.profile.avatar', {
|
||||
url: "/avatar",
|
||||
templateUrl: "partials/options.profile.avatar.html",
|
||||
title: env.t('titleAvatar')
|
||||
})
|
||||
.state('options.profile.backgrounds', {
|
||||
url: '/backgrounds',
|
||||
templateUrl: "partials/options.profile.backgrounds.html",
|
||||
title: env.t('titleBackgrounds')
|
||||
})
|
||||
.state('options.profile.stats', {
|
||||
url: "/stats",
|
||||
templateUrl: "partials/options.profile.stats.html",
|
||||
title: env.t('titleStats')
|
||||
})
|
||||
.state('options.profile.profile', {
|
||||
url: "/profile",
|
||||
templateUrl: "partials/options.profile.profile.html",
|
||||
title: env.t('titleProfile')
|
||||
})
|
||||
|
||||
// Options > Groups
|
||||
.state('options.social', {
|
||||
url: "/groups",
|
||||
templateUrl: "partials/options.social.html"
|
||||
})
|
||||
|
||||
.state('options.social.inbox', {
|
||||
url: "/inbox",
|
||||
templateUrl: "partials/options.social.inbox.html",
|
||||
title: env.t('titleInbox')
|
||||
})
|
||||
|
||||
.state('options.social.tavern', {
|
||||
url: "/tavern",
|
||||
templateUrl: "partials/options.social.tavern.html",
|
||||
controller: 'TavernCtrl',
|
||||
title: env.t('titleTavern')
|
||||
})
|
||||
|
||||
.state('options.social.party', {
|
||||
url: '/party',
|
||||
templateUrl: "partials/options.social.party.html",
|
||||
controller: 'PartyCtrl',
|
||||
title: env.t('titleParty')
|
||||
})
|
||||
|
||||
.state('options.social.hall', {
|
||||
url: '/hall',
|
||||
templateUrl: "partials/options.social.hall.html"
|
||||
})
|
||||
.state('options.social.hall.heroes', {
|
||||
url: '/heroes',
|
||||
templateUrl: "partials/options.social.hall.heroes.html",
|
||||
controller: 'HallHeroesCtrl',
|
||||
title: env.t('titleHeroes')
|
||||
})
|
||||
.state('options.social.hall.patrons', {
|
||||
url: '/patrons',
|
||||
templateUrl: "partials/options.social.hall.patrons.html",
|
||||
controller: 'HallPatronsCtrl',
|
||||
title: env.t('titlePatrons')
|
||||
})
|
||||
|
||||
.state('options.social.guilds', {
|
||||
url: '/guilds',
|
||||
templateUrl: "partials/options.social.guilds.html",
|
||||
controller: 'GuildsCtrl',
|
||||
title: env.t('titleGuilds')
|
||||
})
|
||||
.state('options.social.guilds.public', {
|
||||
url: '/public',
|
||||
templateUrl: "partials/options.social.guilds.public.html",
|
||||
title: env.t('titleGuilds')
|
||||
})
|
||||
.state('options.social.guilds.create', {
|
||||
url: '/create',
|
||||
templateUrl: "partials/options.social.guilds.create.html",
|
||||
title: env.t('titleGuilds')
|
||||
})
|
||||
.state('options.social.guilds.detail', {
|
||||
url: '/:gid',
|
||||
templateUrl: 'partials/options.social.guilds.detail.html',
|
||||
title: env.t('titleGuilds'),
|
||||
controller: ['$scope', 'Groups', 'Chat', '$stateParams', 'Members', 'Challenges',
|
||||
function($scope, Groups, Chat, $stateParams, Members, Challenges){
|
||||
Groups.Group.get($stateParams.gid)
|
||||
.then(function (response) {
|
||||
$scope.group = response.data.data;
|
||||
Chat.markChatSeen($scope.group._id);
|
||||
Members.getGroupMembers($scope.group._id)
|
||||
.then(function (response) {
|
||||
$scope.group.members = response.data.data;
|
||||
});
|
||||
Members.getGroupInvites($scope.group._id)
|
||||
.then(function (response) {
|
||||
$scope.group.invites = response.data.data;
|
||||
});
|
||||
Challenges.getGroupChallenges($scope.group._id)
|
||||
.then(function (response) {
|
||||
$scope.group.challenges = response.data.data;
|
||||
});
|
||||
});
|
||||
}]
|
||||
})
|
||||
|
||||
// Options > Social > Challenges
|
||||
.state('options.social.challenges', {
|
||||
url: "/challenges",
|
||||
params: { groupIdFilter: null },
|
||||
controller: 'ChallengesCtrl',
|
||||
templateUrl: "partials/options.social.challenges.html",
|
||||
title: env.t('titleChallenges')
|
||||
})
|
||||
.state('options.social.challenges.detail', {
|
||||
url: '/:cid',
|
||||
templateUrl: 'partials/options.social.challenges.detail.html',
|
||||
title: env.t('titleChallenges'),
|
||||
controller: ['$scope', 'Challenges', '$stateParams', 'Tasks', 'Members',
|
||||
function ($scope, Challenges, $stateParams, Tasks, Members) {
|
||||
Challenges.getChallenge($stateParams.cid)
|
||||
.then(function (response) {
|
||||
$scope.obj = $scope.challenge = response.data.data;
|
||||
$scope.challenge._locked = true;
|
||||
return Tasks.getChallengeTasks($scope.challenge._id);
|
||||
})
|
||||
.then(function (response) {
|
||||
var tasks = response.data.data;
|
||||
tasks.forEach(function (element, index, array) {
|
||||
if (!$scope.challenge[element.type + 's']) $scope.challenge[element.type + 's'] = [];
|
||||
$scope.challenge[element.type + 's'].push(element);
|
||||
})
|
||||
|
||||
return Members.getChallengeMembers($scope.challenge._id);
|
||||
})
|
||||
.then(function (response) {
|
||||
$scope.challenge.members = response.data.data;
|
||||
});
|
||||
}]
|
||||
})
|
||||
.state('options.social.challenges.edit', {
|
||||
url: '/:cid/edit',
|
||||
templateUrl: 'partials/options.social.challenges.detail.html',
|
||||
title: env.t('titleChallenges'),
|
||||
controller: ['$scope', 'Challenges', '$stateParams', 'Tasks',
|
||||
function ($scope, Challenges, $stateParams, Tasks) {
|
||||
Challenges.getChallenge($stateParams.cid)
|
||||
.then(function (response) {
|
||||
$scope.obj = $scope.challenge = response.data.data;
|
||||
$scope.challenge._locked = false;
|
||||
return Tasks.getChallengeTasks($scope.challenge._id);
|
||||
})
|
||||
.then(function (response) {
|
||||
var tasks = response.data.data;
|
||||
tasks.forEach(function (element, index, array) {
|
||||
if (!$scope.challenge[element.type + 's']) $scope.challenge[element.type + 's'] = [];
|
||||
$scope.challenge[element.type + 's'].push(element);
|
||||
})
|
||||
});
|
||||
}]
|
||||
})
|
||||
.state('options.social.challenges.detail.member', {
|
||||
url: '/:uid',
|
||||
templateUrl: 'partials/options.social.challenges.detail.member.html',
|
||||
title: env.t('titleChallenges'),
|
||||
controller: ['$scope', 'Members', '$stateParams',
|
||||
function($scope, Members, $stateParams){
|
||||
Members.getChallengeMemberProgress($stateParams.cid, $stateParams.uid)
|
||||
.then(function(response) {
|
||||
$scope.obj = response.data.data;
|
||||
|
||||
$scope.obj.habits = [];
|
||||
$scope.obj.todos = [];
|
||||
$scope.obj.dailys = [];
|
||||
$scope.obj.rewards = [];
|
||||
$scope.obj.tasks.forEach(function (element, index, array) {
|
||||
$scope.obj[element.type + 's'].push(element)
|
||||
});
|
||||
|
||||
$scope.obj._locked = true;
|
||||
});
|
||||
}]
|
||||
})
|
||||
|
||||
// Options > Inventory
|
||||
.state('options.inventory', {
|
||||
url: '/inventory',
|
||||
templateUrl: "partials/options.inventory.html",
|
||||
controller: 'InventoryCtrl'
|
||||
})
|
||||
.state('options.inventory.drops', {
|
||||
url: '/drops',
|
||||
templateUrl: "partials/options.inventory.drops.html",
|
||||
title: env.t('titleDrops')
|
||||
})
|
||||
.state('options.inventory.quests', {
|
||||
url: '/quests',
|
||||
templateUrl: "partials/options.inventory.quests.html",
|
||||
title: env.t('titleQuests')
|
||||
})
|
||||
.state('options.inventory.pets', {
|
||||
url: '/pets',
|
||||
templateUrl: "partials/options.inventory.pets.html",
|
||||
title: env.t('titlePets')
|
||||
})
|
||||
.state('options.inventory.mounts', {
|
||||
url: '/mounts',
|
||||
templateUrl: "partials/options.inventory.mounts.html",
|
||||
title: env.t('titleMounts')
|
||||
})
|
||||
.state('options.inventory.equipment', {
|
||||
url: '/equipment',
|
||||
templateUrl: "partials/options.inventory.equipment.html",
|
||||
title: env.t('titleEquipment')
|
||||
})
|
||||
.state('options.inventory.timetravelers', {
|
||||
url: '/timetravelers',
|
||||
templateUrl: "partials/options.inventory.timetravelers.html",
|
||||
title: env.t('titleTimeTravelers')
|
||||
})
|
||||
.state('options.inventory.seasonalshop', {
|
||||
url: '/seasonalshop',
|
||||
templateUrl: "partials/options.inventory.seasonalshop.html",
|
||||
title: env.t('titleSeasonalShop')
|
||||
})
|
||||
|
||||
// Options > Settings
|
||||
.state('options.settings', {
|
||||
url: "/settings",
|
||||
controller: 'SettingsCtrl',
|
||||
templateUrl: "partials/options.settings.html",
|
||||
})
|
||||
.state('options.settings.settings', {
|
||||
url: "/settings",
|
||||
templateUrl: "partials/options.settings.settings.html",
|
||||
title: env.t('titleSettings')
|
||||
})
|
||||
.state('options.settings.api', {
|
||||
url: "/api",
|
||||
templateUrl: "partials/options.settings.api.html",
|
||||
title: env.t('titleSettings')
|
||||
})
|
||||
.state('options.settings.export', {
|
||||
url: "/export",
|
||||
templateUrl: "partials/options.settings.export.html",
|
||||
title: env.t('titleSettings')
|
||||
})
|
||||
.state('options.settings.promo', {
|
||||
url: "/promo",
|
||||
templateUrl: "partials/options.settings.promo.html",
|
||||
title: env.t('titleSettings')
|
||||
})
|
||||
.state('options.settings.subscription', {
|
||||
url: "/subscription",
|
||||
templateUrl: "partials/options.settings.subscription.html",
|
||||
title: env.t('titleSettings')
|
||||
})
|
||||
.state('options.settings.notifications', {
|
||||
url: "/notifications",
|
||||
templateUrl: "partials/options.settings.notifications.html",
|
||||
title: env.t('titleSettings')
|
||||
});
|
||||
|
||||
var settings = JSON.parse(localStorage.getItem(STORAGE_SETTINGS_ID));
|
||||
|
||||
if (settings && settings.auth) {
|
||||
$httpProvider.defaults.headers.common['Content-Type'] = 'application/json;charset=utf-8';
|
||||
$httpProvider.defaults.headers.common['x-api-user'] = settings.auth.apiId;
|
||||
$httpProvider.defaults.headers.common['x-api-key'] = settings.auth.apiToken;
|
||||
}
|
||||
|
||||
$httpProvider.defaults.headers.common['x-client'] = 'habitica-web';
|
||||
}]);
|
||||
@@ -0,0 +1,140 @@
|
||||
"use strict";
|
||||
|
||||
/*
|
||||
The authentication controller (login & facebook)
|
||||
*/
|
||||
|
||||
angular.module('habitrpg')
|
||||
.controller("AuthCtrl", ['$scope', '$rootScope', 'User', '$http', '$location', '$window','ApiUrl', '$modal', 'Analytics',
|
||||
function($scope, $rootScope, User, $http, $location, $window, ApiUrl, $modal, Analytics) {
|
||||
$scope.Analytics = Analytics;
|
||||
|
||||
$scope.logout = function() {
|
||||
localStorage.clear();
|
||||
$window.location.href = '/logout';
|
||||
};
|
||||
|
||||
var runAuth = function(id, token) {
|
||||
User.authenticate(id, token, function(err) {
|
||||
if(!err) $scope.registrationInProgress = false;
|
||||
Analytics.login();
|
||||
Analytics.updateUser();
|
||||
Analytics.track({'hitType':'event','eventCategory':'behavior','eventAction':'login'});
|
||||
$window.location.href = ('/' + window.location.hash);
|
||||
});
|
||||
};
|
||||
|
||||
function errorAlert(data, status, headers, config) {
|
||||
$scope.registrationInProgress = false;
|
||||
if (status === 0) {
|
||||
$window.alert(window.env.t('noReachServer'));
|
||||
} else if (status === 400 && data.errors && _.isArray(data.errors)) { // bad requests
|
||||
data.errors.forEach(function (err) {
|
||||
$window.alert(err.message);
|
||||
});
|
||||
} else if (!!data && !!data.error) {
|
||||
$window.alert(data.message);
|
||||
} else {
|
||||
$window.alert(window.env.t('errorUpCase') + ' ' + status);
|
||||
}
|
||||
};
|
||||
|
||||
$scope.registrationInProgress = false;
|
||||
|
||||
$scope.register = function() {
|
||||
/*TODO highlight invalid inputs
|
||||
we have this as a workaround for https://github.com/HabitRPG/habitrpg-mobile/issues/64
|
||||
*/
|
||||
var scope = angular.element(document.getElementById('registrationForm')).scope();
|
||||
if (scope.registrationForm.$invalid) return;
|
||||
|
||||
$scope.registrationInProgress = true;
|
||||
|
||||
var url = ApiUrl.get() + "/api/v3/user/auth/local/register";
|
||||
if (location.search && location.search.indexOf('Invite=') !== -1) { // matches groupInvite and partyInvite
|
||||
url += location.search;
|
||||
}
|
||||
|
||||
if($rootScope.selectedLanguage) {
|
||||
var toAppend = url.indexOf('?') !== -1 ? '&' : '?';
|
||||
url = url + toAppend + 'lang=' + $rootScope.selectedLanguage.code;
|
||||
}
|
||||
|
||||
$http.post(url, scope.registerVals).success(function(res, status, headers, config) {
|
||||
runAuth(res.data._id, res.data.apiToken);
|
||||
}).error(errorAlert);
|
||||
};
|
||||
|
||||
$scope.auth = function() {
|
||||
var data = {
|
||||
username: $scope.loginUsername || $('#loginForm input[name="username"]').val(),
|
||||
password: $scope.loginPassword || $('#loginForm input[name="password"]').val()
|
||||
};
|
||||
//@TODO: Move all the $http methods to a service
|
||||
$http.post(ApiUrl.get() + "/api/v3/user/auth/local/login", data)
|
||||
.success(function(res, status, headers, config) {
|
||||
runAuth(res.data.id, res.data.apiToken);
|
||||
}).error(errorAlert);
|
||||
};
|
||||
|
||||
$scope.playButtonClick = function() {
|
||||
Analytics.track({'hitType':'event','eventCategory':'button','eventAction':'click','eventLabel':'Play'})
|
||||
if (User.authenticated()) {
|
||||
window.location.href = ('/' + window.location.hash);
|
||||
} else {
|
||||
$modal.open({
|
||||
templateUrl: 'modals/login.html'
|
||||
// Using controller: 'AuthCtrl' it causes problems
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
$scope.passwordReset = function(email){
|
||||
if(email == null || email.length == 0) {
|
||||
alert(window.env.t('invalidEmail'));
|
||||
} else {
|
||||
$http.post(ApiUrl.get() + '/api/v3/user/reset-password', {email:email})
|
||||
.success(function(){
|
||||
alert(window.env.t('newPassSent'));
|
||||
})
|
||||
.error(function(data){
|
||||
alert(data.err);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// ------ Social ----------
|
||||
|
||||
hello.init({
|
||||
facebook : window.env.FACEBOOK_KEY
|
||||
});
|
||||
|
||||
$scope.socialLogin = function(network){
|
||||
hello(network).login({scope:'email'}).then(function(auth){
|
||||
$http.post(ApiUrl.get() + "/api/v3/user/auth/social", auth)
|
||||
.success(function(res, status, headers, config) {
|
||||
runAuth(res.data.id, res.data.apiToken);
|
||||
}).error(errorAlert);
|
||||
}, function( e ){
|
||||
alert("Signin error: " + e.message );
|
||||
});
|
||||
};
|
||||
|
||||
$scope.clearLocalStorage = function () {
|
||||
$scope.messageModal = {
|
||||
title: window.env.t('localStorageClearing'),
|
||||
body: window.env.t('localStorageClearingExplanation'),
|
||||
noFooter: true,
|
||||
};
|
||||
|
||||
$modal.open({
|
||||
templateUrl: 'modals/message-modal.html',
|
||||
scope: $scope
|
||||
});
|
||||
|
||||
var threeSecondsForUsersToReadClearLocalStorageMessage = 3000;
|
||||
|
||||
setTimeout($scope.logout, threeSecondsForUsersToReadClearLocalStorageMessage);
|
||||
};
|
||||
}
|
||||
]);
|
||||
@@ -0,0 +1,73 @@
|
||||
'use strict';
|
||||
|
||||
habitrpg.controller('AutocompleteCtrl', ['$scope', '$timeout', 'Groups', 'User', 'InputCaret', function ($scope,$timeout,Groups,User,InputCaret) {
|
||||
$scope.clearUserlist = function() {
|
||||
$scope.response = [];
|
||||
$scope.usernames = [];
|
||||
}
|
||||
|
||||
$scope.filterUser = function(msg) {
|
||||
if (!$scope.query || !msg.user) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Ignore casing when checking for username
|
||||
var user = msg.user.toLowerCase();
|
||||
var text = $scope.query.text.toLowerCase();
|
||||
|
||||
return user.indexOf(text) == 0;
|
||||
}
|
||||
|
||||
$scope.performCompletion = function(msg) {
|
||||
$scope.autoComplete(msg);
|
||||
$scope.query = null;
|
||||
}
|
||||
|
||||
$scope.addNewUser = function(user) {
|
||||
if($.inArray(user.user,$scope.usernames) == -1) {
|
||||
user.username = user.user;
|
||||
$scope.usernames.push(user.user);
|
||||
$scope.response.push(user);
|
||||
}
|
||||
}
|
||||
|
||||
$scope.clearUserlist();
|
||||
|
||||
$scope.chatChanged = function(newvalue,oldvalue){
|
||||
if($scope.group && $scope.group.chat && $scope.group.chat.length > 0){
|
||||
for(var i = 0; i < $scope.group.chat.length; i++) {
|
||||
$scope.addNewUser($scope.group.chat[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$scope.$watch('group.chat',$scope.chatChanged);
|
||||
|
||||
$scope.caretChanged = function(newCaretPos) {
|
||||
var relativeelement = $('.chat-form div:first');
|
||||
var textarea = $('.chat-form textarea');
|
||||
var userlist = $('.list-at-user');
|
||||
var offset = {
|
||||
x: textarea.offset().left - relativeelement.offset().left,
|
||||
y: textarea.offset().top - relativeelement.offset().top,
|
||||
};
|
||||
if(relativeelement) {
|
||||
var caretOffset = InputCaret.getPosition(textarea);
|
||||
userlist.css({
|
||||
left: caretOffset.left + offset.x,
|
||||
top: caretOffset.top + offset.y + 16
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
$scope.updateTimer = false;
|
||||
|
||||
$scope.$watch(function () { return $scope.caretPos; },function(newCaretPos) {
|
||||
if($scope.updateTimer){
|
||||
$timeout.cancel($scope.updateTimer)
|
||||
}
|
||||
$scope.updateTimer = $timeout(function(){
|
||||
$scope.caretChanged(newCaretPos);
|
||||
},$scope.watchDelay)
|
||||
});
|
||||
}]);
|
||||
@@ -0,0 +1,478 @@
|
||||
habitrpg.controller("ChallengesCtrl", ['$rootScope','$scope', 'Shared', 'User', 'Challenges', 'Notification', '$compile', 'Groups', '$state', '$stateParams', 'Members', 'Tasks', 'TAVERN_ID',
|
||||
function($rootScope, $scope, Shared, User, Challenges, Notification, $compile, Groups, $state, $stateParams, Members, Tasks, TAVERN_ID) {
|
||||
|
||||
// Use presence of cid to determine whether to show a list or a single
|
||||
// challenge
|
||||
$scope.cid = $state.params.cid;
|
||||
|
||||
$scope.groupIdFilter = $stateParams.groupIdFilter;
|
||||
|
||||
_getChallenges();
|
||||
|
||||
// FIXME $scope.challenges needs to be resolved first (see app.js)
|
||||
$scope.groups = [];
|
||||
Groups.Group.getGroups('party,guilds,tavern')
|
||||
.then(function (response) {
|
||||
$scope.groups = response.data.data;
|
||||
});
|
||||
|
||||
// override score() for tasks listed in challenges-editing pages, so that nothing happens
|
||||
$scope.score = function(){}
|
||||
|
||||
//------------------------------------------------------------
|
||||
// Challenge
|
||||
//------------------------------------------------------------
|
||||
|
||||
// Use this to force the top view to change, not just the nested view.
|
||||
$scope.edit = function(challenge) {
|
||||
$state.transitionTo('options.social.challenges.edit', {cid: challenge._id}, {
|
||||
reload: true, inherit: false, notify: true
|
||||
});
|
||||
};
|
||||
|
||||
$scope.isUserMemberOf = function (challenge) {
|
||||
return User.user.challenges.indexOf(challenge._id) !== -1;
|
||||
}
|
||||
|
||||
$scope.editTask = Tasks.editTask;
|
||||
|
||||
/**
|
||||
* Create
|
||||
*/
|
||||
$scope.create = function() {
|
||||
//If the user has one filter selected, assume that the user wants to default to that group
|
||||
var defaultGroup;
|
||||
//Our filters contain all groups, but we only want groups that have atleast one challenge
|
||||
var groupsWithChallenges = _.uniq(_.pluck($scope.groupsFilter, '_id'));
|
||||
var len = groupsWithChallenges.length;
|
||||
var filterCount = 0;
|
||||
|
||||
for ( var i = 0; i < len; i += 1 ) {
|
||||
if ($scope.search.group[groupsWithChallenges[i]] === true) {
|
||||
filterCount += 1;
|
||||
defaultGroup = groupsWithChallenges[i];
|
||||
}
|
||||
|
||||
if (filterCount >= 1 && defaultGroup) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(!defaultGroup) defaultGroup = TAVERN_ID;
|
||||
|
||||
$scope.obj = $scope.newChallenge = {
|
||||
name: '',
|
||||
description: '',
|
||||
habits: [],
|
||||
dailys: [],
|
||||
todos: [],
|
||||
rewards: [],
|
||||
leader: User.user._id,
|
||||
group: defaultGroup,
|
||||
timestamp: +(new Date),
|
||||
members: [],
|
||||
official: false
|
||||
};
|
||||
|
||||
_calculateMaxPrize(defaultGroup);
|
||||
};
|
||||
|
||||
/**
|
||||
* Clone
|
||||
*/
|
||||
$scope.clone = function(challenge) {
|
||||
var clonedTasks = {
|
||||
habit: [],
|
||||
daily: [],
|
||||
todo: [],
|
||||
reward: []
|
||||
};
|
||||
|
||||
_(clonedTasks).each(function(val, type) {
|
||||
if (challenge[type + 's']) {
|
||||
challenge[type + 's'].forEach(_cloneTaskAndPush);
|
||||
}
|
||||
}).value();
|
||||
|
||||
$scope.obj = $scope.newChallenge = {
|
||||
name: challenge.name,
|
||||
shortName: challenge.shortName,
|
||||
description: challenge.description,
|
||||
habits: clonedTasks.habit,
|
||||
dailys: clonedTasks.daily,
|
||||
todos: clonedTasks.todo,
|
||||
rewards: clonedTasks.reward,
|
||||
leader: User.user._id,
|
||||
group: challenge.group._id,
|
||||
official: challenge.official,
|
||||
prize: challenge.prize
|
||||
};
|
||||
|
||||
function _cloneTaskAndPush(taskToClone) {
|
||||
var task = Tasks.cloneTask(taskToClone);
|
||||
clonedTasks[task.type].push(task);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Save
|
||||
*/
|
||||
$scope.save = function(challenge) {
|
||||
if (!challenge.group) return alert(window.env.t('selectGroup'));
|
||||
|
||||
if (!challenge.shortName || challenge.shortName.length < 3) return alert(window.env.t('shortNameTooShort'));
|
||||
|
||||
var isNew = !challenge._id;
|
||||
|
||||
if(isNew && challenge.prize > $scope.maxPrize) {
|
||||
return alert(window.env.t('challengeNotEnoughGems'));
|
||||
}
|
||||
|
||||
if (isNew) {
|
||||
var _challenge;
|
||||
Challenges.createChallenge(challenge)
|
||||
.then(function (response) {
|
||||
_challenge = response.data.data;
|
||||
Notification.text(window.env.t('challengeCreated'));
|
||||
|
||||
var challengeTasks = [];
|
||||
challengeTasks = challengeTasks.concat(challenge.todos);
|
||||
challengeTasks = challengeTasks.concat(challenge.habits);
|
||||
challengeTasks = challengeTasks.concat(challenge.dailys);
|
||||
challengeTasks = challengeTasks.concat(challenge.rewards);
|
||||
|
||||
return Tasks.createChallengeTasks(_challenge._id, challengeTasks);
|
||||
})
|
||||
.then(function (response) {
|
||||
$state.transitionTo('options.social.challenges.detail', { cid: _challenge._id }, {
|
||||
reload: true, inherit: false, notify: true
|
||||
});
|
||||
User.sync();
|
||||
});
|
||||
} else {
|
||||
Challenges.updateChallenge(challenge._id, challenge)
|
||||
.then(function (response) {
|
||||
var _challenge = response.data.data;
|
||||
$state.transitionTo('options.social.challenges.detail', { cid: _challenge._id }, {
|
||||
reload: true, inherit: false, notify: true
|
||||
});
|
||||
User.sync();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Discard
|
||||
*/
|
||||
$scope.discard = function() {
|
||||
$scope.newChallenge = null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Close Challenge
|
||||
* ------------------
|
||||
*/
|
||||
$scope.cancelClosing = function(challenge) {
|
||||
$scope.popoverEl.popover('destroy');
|
||||
$scope.popoverEl = undefined;
|
||||
$scope.closingChal = undefined;
|
||||
challenge.winner = undefined;
|
||||
};
|
||||
|
||||
//@TODO: change to $scope.remove
|
||||
$scope["delete"] = function(challenge) {
|
||||
var warningMsg;
|
||||
|
||||
if(challenge.group._id == TAVERN_ID) {
|
||||
warningMsg = window.env.t('sureDelChaTavern');
|
||||
} else {
|
||||
warningMsg = window.env.t('sureDelCha');
|
||||
}
|
||||
|
||||
if (!confirm(warningMsg)) return;
|
||||
|
||||
Challenges.deleteChallenge(challenge._id)
|
||||
.then(function (response) {
|
||||
$scope.popoverEl.popover('destroy');
|
||||
_backToChallenges();
|
||||
});
|
||||
};
|
||||
|
||||
$scope.selectWinner = function(challenge) {
|
||||
if (!challenge.winner) return;
|
||||
if (!confirm(window.env.t('youSure'))) return;
|
||||
|
||||
Challenges.selectChallengeWinner(challenge._id, challenge.winner)
|
||||
.then(function (response) {
|
||||
$scope.popoverEl.popover('destroy');
|
||||
_backToChallenges();
|
||||
});
|
||||
}
|
||||
|
||||
$scope.close = function(challenge, $event) {
|
||||
$scope.closingChal = challenge;
|
||||
$scope.popoverEl = $($event.target);
|
||||
var html = $compile('<div><div ng-include="\'partials/options.social.challenges.detail.close.html\'" /></div></div>')($scope);
|
||||
$scope.popoverEl.popover('destroy').popover({
|
||||
html: true,
|
||||
placement: 'right',
|
||||
trigger: 'manual',
|
||||
title: window.env.t('closeCha'),
|
||||
content: html
|
||||
}).popover('show');
|
||||
};
|
||||
|
||||
$scope.toggle = function(id){
|
||||
if($state.includes('options.social.challenges.detail', {cid: id})){
|
||||
$state.go('options.social.challenges')
|
||||
}else{
|
||||
$state.go('options.social.challenges.detail', {cid: id});
|
||||
}
|
||||
};
|
||||
|
||||
$scope.toggleMember = function(cid, uid){
|
||||
if($state.includes('options.social.challenges.detail.member', {cid: cid, uid: uid})){
|
||||
$state.go('options.social.challenges.detail')
|
||||
}else{
|
||||
$state.go('options.social.challenges.detail.member', {cid: cid, uid: uid});
|
||||
}
|
||||
};
|
||||
|
||||
//------------------------------------------------------------
|
||||
// Tasks
|
||||
//------------------------------------------------------------
|
||||
function addTask (addTo, listDef, challenge) {
|
||||
var task = Shared.taskDefaults({text: listDef.newTask, type: listDef.type});
|
||||
//If the challenge has not been created, we bulk add tasks on save
|
||||
if (challenge._id) Tasks.createChallengeTasks(challenge._id, task);
|
||||
if (!challenge[task.type + 's']) challenge[task.type + 's'] = [];
|
||||
challenge[task.type + 's'].unshift(task);
|
||||
delete listDef.newTask;
|
||||
};
|
||||
|
||||
$scope.addTask = function(addTo, listDef, challenge) {
|
||||
if (listDef.bulk) {
|
||||
var tasks = listDef.newTask.split(/[\n\r]+/);
|
||||
//Reverse the order of tasks so the tasks will appear in the order the user entered them
|
||||
tasks.reverse();
|
||||
_.each(tasks, function(t) {
|
||||
listDef.newTask = t;
|
||||
addTask(addTo, listDef, challenge);
|
||||
});
|
||||
listDef.bulk = false;
|
||||
} else {
|
||||
addTask(addTo, listDef, challenge);
|
||||
}
|
||||
}
|
||||
|
||||
$scope.removeTask = function(task, challenge) {
|
||||
if (!confirm(window.env.t('sureDelete', {taskType: window.env.t(task.type), taskText: task.text}))) return;
|
||||
//We only pass to the api if the challenge exists, otherwise, the tasks only exist on the client
|
||||
if (challenge._id) Tasks.deleteTask(task._id);
|
||||
var index = challenge[task.type + 's'].indexOf(task);
|
||||
challenge[task.type + 's'].splice(index, 1);
|
||||
};
|
||||
|
||||
$scope.saveTask = function(task){
|
||||
task._editing = false;
|
||||
// TODO persist
|
||||
}
|
||||
|
||||
$scope.toggleBulk = function(list) {
|
||||
if (typeof list.bulk === 'undefined') {
|
||||
list.bulk = false;
|
||||
}
|
||||
list.bulk = !list.bulk;
|
||||
list.focus = true;
|
||||
};
|
||||
|
||||
/*
|
||||
--------------------------
|
||||
Subscription
|
||||
--------------------------
|
||||
*/
|
||||
|
||||
$scope.join = function (challenge) {
|
||||
Challenges.joinChallenge(challenge._id)
|
||||
.then(function (response) {
|
||||
User.user.challenges.push(challenge._id);
|
||||
_getChallenges();
|
||||
return Tasks.getUserTasks();
|
||||
})
|
||||
.then(function (response) {
|
||||
var tasks = response.data.data;
|
||||
User.syncUserTasks(tasks);
|
||||
});
|
||||
}
|
||||
|
||||
$scope.leave = function(keep, challenge) {
|
||||
if (keep == 'cancel') {
|
||||
$scope.selectedChal = undefined;
|
||||
} else {
|
||||
Challenges.leaveChallenge($scope.selectedChal._id, keep)
|
||||
.then(function (response) {
|
||||
var index = User.user.challenges.indexOf($scope.selectedChal._id);
|
||||
delete User.user.challenges[index];
|
||||
_getChallenges();
|
||||
return Tasks.getUserTasks();
|
||||
})
|
||||
.then(function (response) {
|
||||
var tasks = response.data.data;
|
||||
User.syncUserTasks(tasks);
|
||||
});
|
||||
}
|
||||
$scope.popoverEl.popover('destroy');
|
||||
}
|
||||
|
||||
/**
|
||||
* Named "clickLeave" to distinguish between "actual" leave above, since this triggers the
|
||||
* "are you sure?" dialog.
|
||||
*/
|
||||
$scope.clickLeave = function(chal, $event) {
|
||||
$scope.selectedChal = chal;
|
||||
$scope.popoverEl = $($event.target);
|
||||
var html = $compile(
|
||||
'<a ng-controller="ChallengesCtrl" ng-click="leave(\'remove-all\')">' + window.env.t('removeTasks') + '</a><br/>\n<a ng-click="leave(\'keep-all\')">' + window.env.t('keepTasks') + '</a><br/>\n<a ng-click="leave(\'cancel\')">' + window.env.t('cancel') + '</a><br/>'
|
||||
)($scope);
|
||||
$scope.popoverEl.popover('destroy').popover({
|
||||
html: true,
|
||||
placement: 'top',
|
||||
trigger: 'manual',
|
||||
title: window.env.t('leaveCha'),
|
||||
content: html
|
||||
}).popover('show');
|
||||
}
|
||||
|
||||
//------------------------------------------------------------
|
||||
// Filtering
|
||||
//------------------------------------------------------------
|
||||
|
||||
$scope.filterChallenges = function(chal){
|
||||
if (!$scope.search) return true;
|
||||
|
||||
return _shouldShowChallenge(chal);
|
||||
}
|
||||
|
||||
$scope.$watch('newChallenge.group', function(gid){
|
||||
if (!gid) return;
|
||||
|
||||
_calculateMaxPrize(gid);
|
||||
|
||||
if (gid == TAVERN_ID) {
|
||||
$scope.newChallenge.prize = 1;
|
||||
}
|
||||
})
|
||||
|
||||
$scope.selectAll = function(){
|
||||
$scope.search.group = _.transform($scope.groups, function(searchPool, group){
|
||||
searchPool[group._id] = true;
|
||||
});
|
||||
}
|
||||
|
||||
$scope.selectNone = function(){
|
||||
$scope.search.group = _.transform($scope.groups, function(searchPool, group){
|
||||
searchPool[group._id] = false;
|
||||
});
|
||||
}
|
||||
|
||||
$scope.shouldShow = function(task, list, prefs){
|
||||
return true;
|
||||
};
|
||||
|
||||
$scope.insufficientGemsForTavernChallenge = function() {
|
||||
var balance = User.user.balance || 0;
|
||||
var isForTavern = $scope.newChallenge.group == TAVERN_ID;
|
||||
|
||||
if (isForTavern) {
|
||||
return balance <= 0;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
$scope.sendMessageToChallengeParticipant = function(uid) {
|
||||
Members.selectMember(uid)
|
||||
.then(function () {
|
||||
$rootScope.openModal('private-message', {controller:'MemberModalCtrl'});
|
||||
});
|
||||
};
|
||||
|
||||
$scope.sendGiftToChallengeParticipant = function(uid) {
|
||||
Members.selectMember(uid)
|
||||
.then(function () {
|
||||
$rootScope.openModal('send-gift', {controller:'MemberModalCtrl'});
|
||||
});
|
||||
};
|
||||
|
||||
$scope.filterInitialChallenges = function() {
|
||||
$scope.groupsFilter = _.uniq(_.compact(_.pluck($scope.challenges, 'group')), function(g) {return g._id});
|
||||
|
||||
$scope.search = {
|
||||
group: _.transform($scope.groups, function(m,g) { m[g._id] = true;}),
|
||||
_isMember: "either",
|
||||
_isOwner: "either"
|
||||
};
|
||||
//If we game from a group, then override the filter to that group
|
||||
|
||||
if ($scope.groupIdFilter) {
|
||||
$scope.search.group = {};
|
||||
$scope.search.group[$scope.groupIdFilter] = true ;
|
||||
}
|
||||
}
|
||||
|
||||
function _calculateMaxPrize(gid) {
|
||||
|
||||
var userBalance = User.getBalanceInGems() || 0;
|
||||
var availableGroupBalance = _calculateAvailableGroupBalance(gid);
|
||||
|
||||
$scope.maxPrize = userBalance + availableGroupBalance;
|
||||
}
|
||||
|
||||
function _calculateAvailableGroupBalance(gid) {
|
||||
var groupBalance = 0;
|
||||
var group = _.find($scope.groups, { _id: gid });
|
||||
|
||||
if (group && group.balance && group.leader === User.user._id) {
|
||||
groupBalance = group.balance * 4;
|
||||
}
|
||||
|
||||
return groupBalance;
|
||||
}
|
||||
|
||||
function _shouldShowChallenge (chal) {
|
||||
// Have to check that the leader object exists first in the
|
||||
// case where a challenge's leader deletes their account
|
||||
var userIsOwner = (chal.leader && chal.leader._id) === User.user.id;
|
||||
|
||||
var groupSelected = $scope.search.group[chal.group ? chal.group._id : null];
|
||||
var checkOwner = $scope.search._isOwner === 'either' || (userIsOwner === $scope.search._isOwner);
|
||||
var checkMember = $scope.search._isMember === 'either' || ($scope.isUserMemberOf(chal) === $scope.search._isMember);
|
||||
|
||||
return groupSelected && checkOwner && checkMember;
|
||||
}
|
||||
|
||||
function _backToChallenges(){
|
||||
$scope.popoverEl.popover('destroy');
|
||||
$scope.cid = null;
|
||||
$state.go('options.social.challenges');
|
||||
_getChallenges();
|
||||
}
|
||||
|
||||
// Fetch single challenge if a cid is present; fetch multiple challenges
|
||||
// otherwise
|
||||
function _getChallenges() {
|
||||
if ($scope.cid) {
|
||||
Challenges.getChallenge($scope.cid)
|
||||
.then(function (response) {
|
||||
var challenge = response.data.data;
|
||||
$scope.challenges = [challenge];
|
||||
});
|
||||
} else {
|
||||
Challenges.getUserChallenges()
|
||||
.then(function(response){
|
||||
$scope.challenges = response.data.data;
|
||||
$scope.filterInitialChallenges();
|
||||
});
|
||||
}
|
||||
};
|
||||
}]);
|
||||
@@ -0,0 +1,151 @@
|
||||
'use strict';
|
||||
|
||||
habitrpg.controller('ChatCtrl', ['$scope', 'Groups', 'Chat', 'User', '$http', 'ApiUrl', 'Notification', 'Members', '$rootScope', 'Analytics',
|
||||
function($scope, Groups, Chat, User, $http, ApiUrl, Notification, Members, $rootScope, Analytics){
|
||||
$scope.message = {content:''};
|
||||
$scope._sending = false;
|
||||
|
||||
$scope.isUserMentioned = function(user, message) {
|
||||
if(message.hasOwnProperty("highlight"))
|
||||
return message.highlight;
|
||||
message.highlight = false;
|
||||
var messagetext = message.text.toLowerCase();
|
||||
var username = user.profile.name;
|
||||
var mentioned = messagetext.indexOf(username.toLowerCase());
|
||||
var pattern = username+"([^\w]|$){1}";
|
||||
if(mentioned > -1) {
|
||||
var preceedingchar = messagetext.substring(mentioned-1,mentioned);
|
||||
if(mentioned == 0 || preceedingchar.trim() == '' || preceedingchar == '@'){
|
||||
var regex = new RegExp(pattern,'i');
|
||||
message.highlight = regex.test(messagetext);
|
||||
}
|
||||
}
|
||||
return message.highlight;
|
||||
}
|
||||
|
||||
$scope.postChat = function(group, message){
|
||||
if (_.isEmpty(message) || $scope._sending) return;
|
||||
$scope._sending = true;
|
||||
var previousMsg = (group.chat && group.chat[0]) ? group.chat[0].id : false;
|
||||
Chat.postChat(group._id, message, previousMsg)
|
||||
.then(function(response) {
|
||||
var message = response.data.data.message;
|
||||
|
||||
group.chat.unshift(message);
|
||||
|
||||
$scope.message.content = '';
|
||||
$scope._sending = false;
|
||||
|
||||
if (group.type == 'party') {
|
||||
Analytics.updateUser({'partyID': group.id, 'partySize': group.memberCount});
|
||||
}
|
||||
|
||||
if (group.privacy == 'public'){
|
||||
Analytics.track({'hitType':'event','eventCategory':'behavior','eventAction':'group chat','groupType':group.type,'privacy':group.privacy,'groupName':group.name});
|
||||
} else {
|
||||
Analytics.track({'hitType':'event','eventCategory':'behavior','eventAction':'group chat','groupType':group.type,'privacy':group.privacy});
|
||||
}
|
||||
}, function(err){
|
||||
$scope._sending = false;
|
||||
});
|
||||
}
|
||||
|
||||
$scope.deleteChatMessage = function(group, message){
|
||||
if(message.uuid === User.user.id || (User.user.backer && User.user.contributor.admin)){
|
||||
var previousMsg = (group.chat && group.chat[0]) ? group.chat[0].id : false;
|
||||
if (confirm('Are you sure you want to delete this message?')) {
|
||||
Chat.deleteChat(group._id, message.id, previousMsg)
|
||||
.then(function (response) {
|
||||
var i = _.findIndex(group.chat, {id: message.id});
|
||||
if(i !== -1) group.chat.splice(i, 1);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$scope.likeChatMessage = function(group, message) {
|
||||
if (message.uuid == User.user._id)
|
||||
return Notification.text(window.env.t('foreverAlone'));
|
||||
|
||||
if (!message.likes) message.likes = {};
|
||||
|
||||
if (message.likes[User.user._id]) {
|
||||
delete message.likes[User.user._id];
|
||||
} else {
|
||||
message.likes[User.user._id] = true;
|
||||
}
|
||||
|
||||
Chat.like(group._id, message.id);
|
||||
}
|
||||
|
||||
$scope.flagChatMessage = function(groupId,message) {
|
||||
if(!message.flags) message.flags = {};
|
||||
|
||||
if (message.flags[User.user._id]) {
|
||||
Notification.text(window.env.t('abuseAlreadyReported'));
|
||||
} else {
|
||||
$scope.abuseObject = message;
|
||||
$scope.groupId = groupId;
|
||||
Members.selectMember(message.uuid)
|
||||
.then(function () {
|
||||
$rootScope.openModal('abuse-flag',{
|
||||
controller:'MemberModalCtrl',
|
||||
scope: $scope
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
$scope.copyToDo = function(message) {
|
||||
var taskNotes = env.t("messageWroteIn", {
|
||||
user: message.uuid == 'system'
|
||||
? 'system'
|
||||
: '[' + message.user + '](' + env.BASE_URL + '/static/front/#?memberId=' + message.uuid + ')',
|
||||
group: '[' + $scope.group.name + '](' + window.location.href + ')'
|
||||
});
|
||||
|
||||
var newScope = $scope.$new();
|
||||
newScope.text = message.text;
|
||||
newScope.notes = taskNotes;
|
||||
|
||||
$rootScope.openModal('copyChatToDo',{
|
||||
controller:'CopyMessageModalCtrl',
|
||||
scope: newScope
|
||||
});
|
||||
};
|
||||
|
||||
function handleGroupResponse (response) {
|
||||
$scope.group = response;
|
||||
if (!$scope.group._id) $scope.group = response.data.data;
|
||||
};
|
||||
|
||||
$scope.sync = function(group) {
|
||||
if (group.name === Groups.TAVERN_NAME) {
|
||||
Groups.tavern(true).then(handleGroupResponse);
|
||||
} else if (group._id === User.user.party._id) {
|
||||
Groups.party(true).then(handleGroupResponse);
|
||||
} else {
|
||||
Groups.Group.get(group._id).then(handleGroupResponse);
|
||||
}
|
||||
|
||||
Chat.markChatSeen(group._id);
|
||||
}
|
||||
|
||||
// List of Ordering options for the party members list
|
||||
$scope.partyOrderChoices = {
|
||||
'level': window.env.t('sortLevel'),
|
||||
'random': window.env.t('sortRandom'),
|
||||
'pets': window.env.t('sortPets'),
|
||||
'habitrpg_date_joined' : window.env.t('sortHabitrpgJoined'),
|
||||
'party_date_joined': window.env.t('sortJoined'),
|
||||
'habitrpg_last_logged_in': window.env.t('sortHabitrpgLastLoggedIn'),
|
||||
'name': window.env.t('sortName'),
|
||||
'backgrounds': window.env.t('sortBackgrounds'),
|
||||
};
|
||||
|
||||
$scope.partyOrderAscendingChoices = {
|
||||
'ascending': window.env.t('ascendingSort'),
|
||||
'descending': window.env.t('descendingSort')
|
||||
}
|
||||
|
||||
}]);
|
||||
@@ -0,0 +1,17 @@
|
||||
'use strict';
|
||||
|
||||
habitrpg.controller("CopyMessageModalCtrl", ['$scope', 'User', 'Notification',
|
||||
function($scope, User, Notification){
|
||||
$scope.saveTodo = function() {
|
||||
var newTask = {
|
||||
text: $scope.text,
|
||||
type: 'todo',
|
||||
notes: $scope.notes
|
||||
};
|
||||
|
||||
User.addTask({body:newTask});
|
||||
Notification.text(window.env.t('messageAddedAsToDo'));
|
||||
|
||||
$scope.$close();
|
||||
}
|
||||
}]);
|
||||
@@ -0,0 +1,48 @@
|
||||
"use strict";
|
||||
|
||||
habitrpg.controller("FiltersCtrl", ['$scope', '$rootScope', 'User', 'Shared',
|
||||
function($scope, $rootScope, User, Shared) {
|
||||
var user = User.user;
|
||||
$scope._editing = false;
|
||||
$scope._newTag = {name:''};
|
||||
$scope.filterQuery = '';
|
||||
|
||||
var tagsSnap; // used to compare which tags need updating
|
||||
|
||||
$scope.saveOrEdit = function(){
|
||||
if ($scope._editing) {
|
||||
_.each(User.user.tags, function(tag){
|
||||
// Send an update op for each changed tag (excluding new tags & deleted tags, this if() packs a punch)
|
||||
if (tagsSnap[tag.id] && tagsSnap[tag.id].name != tag.name)
|
||||
User.updateTag({params:{id:tag.id}, body:{name:tag.name}});
|
||||
})
|
||||
$scope._editing = false;
|
||||
} else {
|
||||
tagsSnap = angular.copy(user.tags);
|
||||
tagsSnap = _.object(_.pluck(tagsSnap,'id'), tagsSnap);
|
||||
$scope._editing = true;
|
||||
}
|
||||
};
|
||||
|
||||
$scope.toggleFilter = function(tag) {
|
||||
if (!user.filters[tag.id]) {
|
||||
user.filters[tag.id] = true;
|
||||
} else {
|
||||
delete user.filters[tag.id];
|
||||
}
|
||||
|
||||
// no longer persisting this, it was causing a lot of confusion - users thought they'd permanently lost tasks
|
||||
// Note: if we want to persist for just this computer, easy method is:
|
||||
// User.save();
|
||||
};
|
||||
|
||||
$scope.updateTaskFilter = function(){
|
||||
user.filterQuery = $scope.filterQuery;
|
||||
};
|
||||
$scope.updateTaskFilter();
|
||||
|
||||
$scope.createTag = function() {
|
||||
User.addTag({body:{name: $scope._newTag.name, id: Shared.uuid()}});
|
||||
$scope._newTag.name = '';
|
||||
};
|
||||
}]);
|
||||
@@ -0,0 +1,180 @@
|
||||
"use strict";
|
||||
|
||||
angular.module('habitrpg').controller("FooterCtrl",
|
||||
['$scope', '$rootScope', 'User', '$http', 'Notification', 'ApiUrl', 'Social',
|
||||
function($scope, $rootScope, User, $http, Notification, ApiUrl, Social) {
|
||||
|
||||
$scope.loadWidgets = Social.loadWidgets;
|
||||
|
||||
if(env.isStaticPage){
|
||||
$scope.languages = env.availableLanguages;
|
||||
$scope.selectedLanguage = _.find(env.availableLanguages, {code: env.language.code});
|
||||
|
||||
$rootScope.selectedLanguage = $scope.selectedLanguage;
|
||||
|
||||
$scope.changeLang = function(){
|
||||
window.location = '?lang='+$scope.selectedLanguage.code;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
External Scripts
|
||||
JS files not needed right away (google charts) or entirely optional (analytics)
|
||||
Each file gets loaded async via $.getScript, so it doesn't bog page-load
|
||||
*/
|
||||
|
||||
$scope.deferredScripts = function(){
|
||||
|
||||
// Amazon Payments
|
||||
var amazonPaymentsUrl = 'https://static-na.payments-amazon.com/OffAmazonPayments/us/' +
|
||||
(window.env.NODE_ENV === 'production' ? '' : 'sandbox/') + 'js/Widgets.js';
|
||||
$.getScript(amazonPaymentsUrl);
|
||||
|
||||
// Stripe
|
||||
$.getScript('//checkout.stripe.com/v2/checkout.js');
|
||||
|
||||
/* Google Content Experiments
|
||||
if (window.env.NODE_ENV === 'production') {
|
||||
$.getScript('//www.google-analytics.com/cx/api.js?experiment=boVO4eEyRfysNE5D53nCMQ', function(){
|
||||
$rootScope.variant = cxApi.chooseVariation();
|
||||
$rootScope.$apply();
|
||||
})
|
||||
} */
|
||||
|
||||
// Scripts only for desktop
|
||||
if (!window.env.IS_MOBILE) {
|
||||
// Add This
|
||||
//$.getScript("//s7.addthis.com/js/300/addthis_widget.js#pubid=ra-5016f6cc44ad68a4"); //FIXME why isn't this working when here? instead it's now in <head>
|
||||
var addthisServices = 'facebook,twitter,googleplus,tumblr,'+window.env.BASE_URL.replace('https://','').replace('http://','');
|
||||
window.addthis_config = {
|
||||
ui_click: true,
|
||||
services_custom:{
|
||||
name: "Download",
|
||||
url: window.env.BASE_URL+"/export/avatar-"+User.user._id+".png",
|
||||
icon: window.env.BASE_URL+"/favicon.ico"
|
||||
},
|
||||
services_expanded:addthisServices,
|
||||
services_compact:addthisServices
|
||||
};
|
||||
|
||||
// Google Charts
|
||||
$.getScript("//www.google.com/jsapi", function() {
|
||||
google.load("visualization", "1", {
|
||||
packages: ["corechart"],
|
||||
callback: function() {}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Debug functions. Note that the server route for gems is only available if process.env.DEBUG=true
|
||||
*/
|
||||
if (_.contains(['development','test'],window.env.NODE_ENV)) {
|
||||
|
||||
$scope.setHealthLow = function(){
|
||||
User.set({
|
||||
'stats.hp': 1
|
||||
});
|
||||
};
|
||||
|
||||
$scope.addMissedDay = function(numberOfDays){
|
||||
if (!confirm("Are you sure you want to reset the day by " + numberOfDays + " day(s)?")) return;
|
||||
|
||||
User.setCron(numberOfDays);
|
||||
};
|
||||
|
||||
$scope.addTenGems = function(){
|
||||
User.addTenGems();
|
||||
};
|
||||
|
||||
$scope.addHourglass = function(){
|
||||
User.addHourglass();
|
||||
};
|
||||
|
||||
$scope.addGold = function(){
|
||||
User.set({
|
||||
'stats.gp': User.user.stats.gp + 500,
|
||||
});
|
||||
};
|
||||
|
||||
$scope.addMana = function(){
|
||||
User.set({
|
||||
'stats.mp': User.user.stats.mp + 500,
|
||||
});
|
||||
};
|
||||
|
||||
$scope.addLevelsAndGold = function(){
|
||||
User.set({
|
||||
'stats.exp': User.user.stats.exp + 10000,
|
||||
'stats.gp': User.user.stats.gp + 10000,
|
||||
'stats.mp': User.user.stats.mp + 10000
|
||||
});
|
||||
};
|
||||
|
||||
$scope.addOneLevel = function(){
|
||||
User.set({
|
||||
'stats.exp': User.user.stats.exp + (Math.round(((Math.pow(User.user.stats.lvl, 2) * 0.25) + (10 * User.user.stats.lvl) + 139.75) / 10) * 10)
|
||||
});
|
||||
};
|
||||
|
||||
$scope.addQuestProgress = function(){
|
||||
$http({
|
||||
method: "POST",
|
||||
url: 'api/v3/debug/quest-progress'
|
||||
})
|
||||
.then(function (response) {
|
||||
Notification.text('Quest progress increased');
|
||||
User.sync();
|
||||
})
|
||||
};
|
||||
|
||||
$scope.makeAdmin = function () {
|
||||
User.makeAdmin();
|
||||
};
|
||||
|
||||
$scope.openModifyInventoryModal = function () {
|
||||
$rootScope.openModal('modify-inventory', {controller: 'FooterCtrl', scope: $scope });
|
||||
$scope.showInv = { };
|
||||
$scope.inv = {
|
||||
gear: {},
|
||||
special: {},
|
||||
pets: {},
|
||||
mounts: {},
|
||||
eggs: {},
|
||||
hatchingPotions: {},
|
||||
food: {},
|
||||
quests: {},
|
||||
};
|
||||
$scope.setAllItems = function (type, value) {
|
||||
var set = $scope.inv[type];
|
||||
|
||||
for (var item in set) {
|
||||
if (set.hasOwnProperty(item)) {
|
||||
set[item] = value;
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
$scope.modifyInventory = function () {
|
||||
$http({
|
||||
method: "POST",
|
||||
url: 'api/v3/debug/modify-inventory',
|
||||
data: {
|
||||
gear: $scope.showInv.gear ? $scope.inv.gear : null,
|
||||
special: $scope.showInv.special ? $scope.inv.special : null,
|
||||
pets: $scope.showInv.pets ? $scope.inv.pets : null,
|
||||
mounts: $scope.showInv.mounts ? $scope.inv.mounts : null,
|
||||
eggs: $scope.showInv.eggs ? $scope.inv.eggs : null,
|
||||
hatchingPotions: $scope.showInv.hatchingPotions ? $scope.inv.hatchingPotions : null,
|
||||
food: $scope.showInv.food ? $scope.inv.food : null,
|
||||
quests: $scope.showInv.quests ? $scope.inv.quests : null,
|
||||
}
|
||||
})
|
||||
.then(function (response) {
|
||||
Notification.text('Inventory updated. Refresh or sync.');
|
||||
})
|
||||
};
|
||||
}
|
||||
}])
|
||||
@@ -0,0 +1,144 @@
|
||||
"use strict";
|
||||
|
||||
habitrpg.controller("GroupsCtrl", ['$scope', '$rootScope', 'Shared', 'Groups', '$http', '$q', 'User', 'Members', '$state', 'Notification',
|
||||
function($scope, $rootScope, Shared, Groups, $http, $q, User, Members, $state, Notification) {
|
||||
$scope.isMemberOfPendingQuest = function (userid, group) {
|
||||
if (!group.quest || !group.quest.members) return false;
|
||||
if (group.quest.active) return false; // quest is started, not pending
|
||||
return userid in group.quest.members && group.quest.members[userid] != false;
|
||||
};
|
||||
|
||||
$scope.isMemberOfRunningQuest = function (userid, group) {
|
||||
if (!group.quest || !group.quest.members) return false;
|
||||
if (!group.quest.active) return false; // quest is pending, not started
|
||||
return group.quest.members[userid];
|
||||
};
|
||||
|
||||
$scope.isMemberOfGroup = function (userid, group) {
|
||||
// If the group is a guild, just check for an intersection with the
|
||||
// current user's guilds, rather than checking the members of the group.
|
||||
if(group.type === 'guild') {
|
||||
return _.detect(User.user.guilds, function(guildId) { return guildId === group._id });
|
||||
}
|
||||
|
||||
// Similarly, if we're dealing with the user's current party, return true.
|
||||
if(group.type === 'party') {
|
||||
var currentParty = group;
|
||||
if(currentParty._id && currentParty._id === group._id) return true;
|
||||
}
|
||||
|
||||
if (!group.members) return false;
|
||||
var memberIds = _.map(group.members, function(x){return x._id});
|
||||
return ~(memberIds.indexOf(userid));
|
||||
};
|
||||
|
||||
$scope.isMember = function (user, group) {
|
||||
return ~(group.members.indexOf(user._id));
|
||||
};
|
||||
|
||||
$scope.Members = Members;
|
||||
|
||||
$scope._editing = {group: false};
|
||||
$scope.groupCopy = {};
|
||||
|
||||
$scope.editGroup = function (group) {
|
||||
angular.copy(group, $scope.groupCopy);
|
||||
group._editing = true;
|
||||
};
|
||||
|
||||
$scope.saveEdit = function (group) {
|
||||
var newLeader = $scope.groupCopy._newLeader && $scope.groupCopy._newLeader._id;
|
||||
|
||||
if (newLeader) {
|
||||
$scope.groupCopy.leader = newLeader;
|
||||
}
|
||||
|
||||
angular.copy($scope.groupCopy, group);
|
||||
|
||||
Groups.Group.update(group);
|
||||
|
||||
$scope.cancelEdit(group);
|
||||
};
|
||||
|
||||
$scope.cancelEdit = function (group) {
|
||||
group._editing = false;
|
||||
$scope.groupCopy = {};
|
||||
};
|
||||
|
||||
$scope.deleteAllMessages = function() {
|
||||
if (confirm(window.env.t('confirmDeleteAllMessages'))) {
|
||||
User.clearPMs();
|
||||
}
|
||||
};
|
||||
|
||||
// ------ Modals ------
|
||||
|
||||
$scope.clickMember = function (uid, forceShow) {
|
||||
if (User.user._id == uid && !forceShow) {
|
||||
if ($state.is('tasks')) {
|
||||
$state.go('options.profile.avatar');
|
||||
} else {
|
||||
$state.go('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)
|
||||
.then(function () {
|
||||
$rootScope.openModal('member', {controller: 'MemberModalCtrl', windowClass: 'profile-modal', size: 'lg'});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
$scope.removeMember = function (group, member, isMember) {
|
||||
// TODO find a better way to do this (share data with remove member modal)
|
||||
$scope.removeMemberData = {
|
||||
group: group,
|
||||
member: member,
|
||||
isMember: isMember
|
||||
};
|
||||
$rootScope.openModal('remove-member', {scope: $scope});
|
||||
};
|
||||
|
||||
$scope.confirmRemoveMember = function (confirm) {
|
||||
if (confirm) {
|
||||
Groups.Group.removeMember(
|
||||
$scope.removeMemberData.group._id,
|
||||
$scope.removeMemberData.member._id,
|
||||
$scope.removeMemberData.message
|
||||
).then(function (response) {
|
||||
if($scope.removeMemberData.isMember){
|
||||
_.pull($scope.removeMemberData.group.members, $scope.removeMemberData.member);
|
||||
}else{
|
||||
_.pull($scope.removeMemberData.group.invites, $scope.removeMemberData.member);
|
||||
}
|
||||
|
||||
$scope.removeMemberData = undefined;
|
||||
});
|
||||
} else {
|
||||
$scope.removeMemberData = undefined;
|
||||
}
|
||||
};
|
||||
|
||||
$scope.openInviteModal = function (group) {
|
||||
if (group.type !== 'party' && group.type !== 'guild') {
|
||||
return console.log('Invalid group type.')
|
||||
}
|
||||
|
||||
$rootScope.openModal('invite-' + group.type, {
|
||||
controller:'InviteToGroupCtrl',
|
||||
resolve: {
|
||||
injectedGroup: function(){
|
||||
return group;
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
$scope.quickReply = function (uid) {
|
||||
Members.selectMember(uid)
|
||||
.then(function (response) {
|
||||
$rootScope.openModal('private-message', {controller: 'MemberModalCtrl'});
|
||||
});
|
||||
}
|
||||
}]);
|
||||
@@ -0,0 +1,127 @@
|
||||
'use strict';
|
||||
|
||||
habitrpg.controller("GuildsCtrl", ['$scope', 'Groups', 'User', 'Challenges', '$rootScope', '$state', '$location', '$compile', 'Analytics',
|
||||
function($scope, Groups, User, Challenges, $rootScope, $state, $location, $compile, Analytics) {
|
||||
$scope.groups = {
|
||||
guilds: [],
|
||||
public: [],
|
||||
};
|
||||
|
||||
Groups.myGuilds()
|
||||
.then(function (guilds) {
|
||||
$scope.groups.guilds = guilds;
|
||||
});
|
||||
|
||||
Groups.publicGuilds()
|
||||
.then(function (guilds) {
|
||||
$scope.groups.public = guilds;
|
||||
});
|
||||
|
||||
$scope.type = 'guild';
|
||||
$scope.text = window.env.t('guild');
|
||||
|
||||
var newGroup = function(){
|
||||
return {type:'guild', privacy:'private'};
|
||||
}
|
||||
$scope.newGroup = newGroup()
|
||||
|
||||
$scope.create = function(group){
|
||||
if (User.user.balance < 1) {
|
||||
return $rootScope.openModal('buyGems', {track:"Gems > Create Group"});
|
||||
}
|
||||
|
||||
if (confirm(window.env.t('confirmGuild'))) {
|
||||
Groups.Group.create(group)
|
||||
.then(function (response) {
|
||||
var createdGroup = response.data.data;
|
||||
if (createdGroup.privacy == 'public') {
|
||||
Analytics.track({'hitType':'event', 'eventCategory':'behavior', 'eventAction':'join group', 'owner':true, 'groupType':'guild', 'privacy': createdGroup.privacy, 'groupName':createdGroup.name})
|
||||
} else {
|
||||
Analytics.track({'hitType':'event', 'eventCategory':'behavior', 'eventAction':'join group', 'owner':true, 'groupType':'guild', 'privacy': createdGroup.privacy})
|
||||
}
|
||||
$rootScope.hardRedirect('/#/options/groups/guilds/' + createdGroup._id);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
$scope.join = function (group) {
|
||||
var groupId = group._id;
|
||||
|
||||
// If we don't have the _id property, we are joining from an invitation
|
||||
// which contains a id property of the group
|
||||
if (group.id && !group._id) {
|
||||
groupId = group.id;
|
||||
}
|
||||
|
||||
Groups.Group.join(groupId)
|
||||
.then(function (response) {
|
||||
var joinedGroup = response.data.data;
|
||||
|
||||
User.user.guilds.push(joinedGroup._id);
|
||||
|
||||
if (joinedGroup.privacy == 'public') {
|
||||
Analytics.track({'hitType':'event', 'eventCategory':'behavior', 'eventAction':'join group', 'owner':false, 'groupType':'guild','privacy': joinedGroup.privacy, 'groupName': joinedGroup.name})
|
||||
} else {
|
||||
Analytics.track({'hitType':'event', 'eventCategory':'behavior', 'eventAction':'join group', 'owner':false, 'groupType':'guild','privacy': joinedGroup.privacy})
|
||||
}
|
||||
|
||||
$location.path('/options/groups/guilds/' + joinedGroup._id);
|
||||
});
|
||||
}
|
||||
|
||||
$scope.reject = function(invitationToReject) {
|
||||
var index = _.findIndex(User.user.invitations.guilds, function(invite) { return invite.id === invitationToReject.id; });
|
||||
User.user.invitations.guilds = User.user.invitations.guilds.splice(0, index);
|
||||
Groups.Group.rejectInvite(invitationToReject.id);
|
||||
}
|
||||
|
||||
$scope.leave = function(keep) {
|
||||
if (keep == 'cancel') {
|
||||
$scope.selectedGroup = undefined;
|
||||
$scope.popoverEl.popover('destroy');
|
||||
} else {
|
||||
Groups.Group.leave($scope.selectedGroup._id, keep)
|
||||
.success(function (data) {
|
||||
var index = User.user.guilds.indexOf($scope.selectedGroup._id);
|
||||
delete User.user.guilds[index];
|
||||
$scope.selectedGroup = undefined;
|
||||
$location.path('/options/groups/guilds');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
$scope.clickLeave = function(group, $event){
|
||||
$scope.selectedGroup = group;
|
||||
$scope.popoverEl = $($event.target).closest('.btn');
|
||||
|
||||
var html, title;
|
||||
|
||||
Challenges.getGroupChallenges(group._id)
|
||||
.then(function(response) {
|
||||
var challenges = _.pluck(_.filter(response.data.data, function(c) {
|
||||
return c.group._id == group._id;
|
||||
}), '_id');
|
||||
|
||||
if (_.intersection(challenges, User.user.challenges).length > 0) {
|
||||
html = $compile(
|
||||
'<a ng-controller="GroupsCtrl" ng-click="leave(\'remove-all\')">' + window.env.t('removeTasks') + '</a><br/>\n<a ng-click="leave(\'keep-all\')">' + window.env.t('keepTasks') + '</a><br/>\n<a ng-click="leave(\'cancel\')">' + window.env.t('cancel') + '</a><br/>'
|
||||
)($scope);
|
||||
title = window.env.t('leaveGroupCha');
|
||||
} else {
|
||||
html = $compile(
|
||||
'<a ng-controller="GroupsCtrl" ng-click="leave(\'keep-all\')">' + window.env.t('confirm') + '</a><br/>\n<a ng-click="leave(\'cancel\')">' + window.env.t('cancel') + '</a><br/>'
|
||||
)($scope);
|
||||
title = window.env.t('leaveGroup')
|
||||
}
|
||||
|
||||
$scope.popoverEl.popover('destroy').popover({
|
||||
html: true,
|
||||
placement: 'top',
|
||||
trigger: 'manual',
|
||||
title: title,
|
||||
content: html
|
||||
}).popover('show');
|
||||
});
|
||||
}
|
||||
}
|
||||
]);
|
||||
@@ -0,0 +1,54 @@
|
||||
"use strict";
|
||||
|
||||
habitrpg.controller("HallHeroesCtrl", ['$scope', '$rootScope', 'User', 'Notification', 'ApiUrl', 'Hall',
|
||||
function($scope, $rootScope, User, Notification, ApiUrl, Hall) {
|
||||
$scope.hero = undefined;
|
||||
$scope.currentHeroIndex = undefined;
|
||||
$scope.heroes = [];
|
||||
|
||||
Hall.getHeroes()
|
||||
.then(function (response) {
|
||||
$scope.heroes = response.data.data;
|
||||
});
|
||||
|
||||
$scope.loadHero = function(uuid, heroIndex) {
|
||||
$scope.currentHeroIndex = heroIndex;
|
||||
Hall.getHero(uuid)
|
||||
.then(function (response) {
|
||||
$scope.hero = response.data.data;
|
||||
});
|
||||
}
|
||||
|
||||
$scope.saveHero = function(hero) {
|
||||
$scope.hero.contributor.admin = ($scope.hero.contributor.level > 7) ? true : false;
|
||||
Hall.updateHero($scope.hero)
|
||||
.then(function (response) {
|
||||
Notification.text("User updated");
|
||||
$scope.hero = undefined;
|
||||
$scope._heroID = undefined;
|
||||
$scope.heroes[$scope.currentHeroIndex] = response.data.data;
|
||||
$scope.currentHeroIndex = undefined;
|
||||
});
|
||||
}
|
||||
|
||||
$scope.populateContributorInput = function(id, index) {
|
||||
$scope._heroID = id;
|
||||
window.scrollTo(0, 200);
|
||||
$scope.loadHero(id, index);
|
||||
};
|
||||
}]);
|
||||
|
||||
habitrpg.controller("HallPatronsCtrl", ['$scope', '$rootScope', 'User', 'Notification', 'ApiUrl', 'Hall',
|
||||
function($scope, $rootScope, User, Notification, ApiUrl, Hall) {
|
||||
var page = 0;
|
||||
$scope.patrons = [];
|
||||
|
||||
$scope.loadMore = function() {
|
||||
Hall.getPatrons(page++)
|
||||
.then(function (response) {
|
||||
$scope.patrons = $scope.patrons.concat(response.data.data);
|
||||
});
|
||||
}
|
||||
$scope.loadMore();
|
||||
|
||||
}]);
|
||||
@@ -0,0 +1,67 @@
|
||||
"use strict";
|
||||
|
||||
habitrpg.controller("HeaderCtrl", ['$scope', 'Groups', 'User',
|
||||
function($scope, Groups, User) {
|
||||
|
||||
$scope.Math = window.Math;
|
||||
$scope.user = User.user;
|
||||
|
||||
$scope.inviteOrStartParty = Groups.inviteOrStartParty;
|
||||
|
||||
function handlePartyResponse (party) {
|
||||
$scope.party = party;
|
||||
|
||||
var triggerResort = function() {
|
||||
$scope.partyMinusSelf = resortParty();
|
||||
};
|
||||
|
||||
triggerResort();
|
||||
$scope.$watch('user.party.order', triggerResort);
|
||||
$scope.$watch('user.party.orderAscending', triggerResort);
|
||||
}
|
||||
|
||||
Groups.party().then(handlePartyResponse, handlePartyResponse);
|
||||
|
||||
function resortParty() {
|
||||
var result = _.sortBy(
|
||||
_.filter($scope.party.members, function(member){
|
||||
return member._id !== User.user._id;
|
||||
}),
|
||||
function (member) {
|
||||
switch(User.user.party.order)
|
||||
{
|
||||
case 'level':
|
||||
return member.stats.lvl;
|
||||
break;
|
||||
case 'random':
|
||||
return Math.random();
|
||||
break;
|
||||
case 'pets':
|
||||
return member.items.pets.length;
|
||||
break;
|
||||
case 'name':
|
||||
return member.profile.name;
|
||||
break;
|
||||
case 'backgrounds':
|
||||
return member.preferences.background;
|
||||
break;
|
||||
case 'habitrpg_date_joined':
|
||||
return member.auth.timestamps.created;
|
||||
break
|
||||
case 'habitrpg_last_logged_in':
|
||||
return member.auth.timestamps.loggedin;
|
||||
break
|
||||
default:
|
||||
// party date joined
|
||||
return true;
|
||||
}
|
||||
}
|
||||
)
|
||||
if (User.user.party.orderAscending == "descending") {
|
||||
result = result.reverse()
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
]);
|
||||
@@ -0,0 +1,331 @@
|
||||
habitrpg.controller("InventoryCtrl",
|
||||
['$rootScope', '$scope', 'Shared', '$window', 'User', 'Content', 'Analytics', 'Quests', 'Stats', 'Social',
|
||||
function($rootScope, $scope, Shared, $window, User, Content, Analytics, Quests, Stats, Social) {
|
||||
|
||||
var user = User.user;
|
||||
|
||||
// convenience vars since these are accessed frequently
|
||||
|
||||
$scope.selectedEgg = null; // {index: 1, name: "Tiger", value: 5}
|
||||
$scope.selectedPotion = null; // {index: 5, name: "Red", value: 3}
|
||||
|
||||
_updateDropAnimalCount(user.items);
|
||||
|
||||
// Social sharing buttons
|
||||
$scope.loadWidgets = Social.loadWidgets;
|
||||
|
||||
// Functions from Quests service
|
||||
$scope.lockQuest = Quests.lockQuest;
|
||||
|
||||
$scope.buyQuest = function(questScroll) {
|
||||
Quests.buyQuest(questScroll)
|
||||
.then(function(quest) {
|
||||
$rootScope.selectedQuest = quest;
|
||||
$rootScope.openModal('buyQuest', {controller:'InventoryCtrl'});
|
||||
});
|
||||
};
|
||||
|
||||
$scope.questPopover = Quests.questPopover;
|
||||
|
||||
$scope.showQuest = function(questScroll) {
|
||||
Quests.showQuest(questScroll)
|
||||
.then(function(quest) {
|
||||
$rootScope.selectedQuest = quest;
|
||||
$rootScope.openModal('showQuest', {controller:'InventoryCtrl'});
|
||||
});
|
||||
};
|
||||
|
||||
$scope.questInit = function() {
|
||||
var key = $rootScope.selectedQuest.key;
|
||||
|
||||
Quests.initQuest(key).then(function() {
|
||||
$rootScope.selectedQuest = undefined;
|
||||
$scope.$close();
|
||||
});
|
||||
};
|
||||
|
||||
// count egg, food, hatchingPotion stack totals
|
||||
var countStacks = function(items) { return _.reduce(items,function(m,v){return m+v;},0);}
|
||||
|
||||
$scope.$watch('user.items.eggs', function(eggs){ $scope.eggCount = countStacks(eggs); }, true);
|
||||
$scope.$watch('user.items.hatchingPotions', function(pots){ $scope.potCount = countStacks(pots); }, true);
|
||||
$scope.$watch('user.items.food', function(food){ $scope.foodCount = countStacks(food); }, true);
|
||||
$scope.$watch('user.items.quests', function(quest){ $scope.questCount = countStacks(quest); }, true);
|
||||
|
||||
$scope.$watch('user.items.gear', function(gear){
|
||||
$scope.gear = {};
|
||||
_.each(gear.owned, function(v,key){
|
||||
if (v === false) return;
|
||||
var item = Content.gear.flat[key];
|
||||
if (!$scope.gear[item.klass]) $scope.gear[item.klass] = [];
|
||||
$scope.gear[item.klass].push(item);
|
||||
})
|
||||
}, true);
|
||||
|
||||
$scope.chooseEgg = function(egg){
|
||||
if ($scope.selectedEgg && $scope.selectedEgg.key == egg) {
|
||||
return $scope.selectedEgg = null; // clicked same egg, unselect
|
||||
}
|
||||
var eggData = _.findWhere(Content.eggs, {key:egg});
|
||||
if (!$scope.selectedPotion) {
|
||||
$scope.selectedEgg = eggData;
|
||||
} else {
|
||||
$scope.hatch(eggData, $scope.selectedPotion);
|
||||
}
|
||||
$scope.selectedFood = null;
|
||||
}
|
||||
|
||||
$scope.choosePotion = function(potion){
|
||||
if ($scope.selectedPotion && $scope.selectedPotion.key == potion) {
|
||||
return $scope.selectedPotion = null; // clicked same egg, unselect
|
||||
}
|
||||
// we really didn't think through the way these things are stored and getting passed around...
|
||||
var potionData = _.findWhere(Content.hatchingPotions, {key:potion});
|
||||
if (!$scope.selectedEgg) {
|
||||
$scope.selectedPotion = potionData;
|
||||
} else {
|
||||
$scope.hatch($scope.selectedEgg, potionData);
|
||||
}
|
||||
$scope.selectedFood = null;
|
||||
}
|
||||
|
||||
$scope.chooseFood = function(food){
|
||||
if ($scope.selectedFood && $scope.selectedFood.key == food) return $scope.selectedFood = null;
|
||||
$scope.selectedFood = Content.food[food];
|
||||
$scope.selectedEgg = $scope.selectedPotion = null;
|
||||
}
|
||||
|
||||
$scope.sellInventory = function() {
|
||||
var selected = $scope.selectedEgg ? 'selectedEgg' : $scope.selectedPotion ? 'selectedPotion' : $scope.selectedFood ? 'selectedFood' : undefined;
|
||||
if (selected) {
|
||||
var type = $scope.selectedEgg ? 'eggs' : $scope.selectedPotion ? 'hatchingPotions' : $scope.selectedFood ? 'food' : undefined;
|
||||
User.sell({params:{type:type, key: $scope[selected].key}});
|
||||
if (user.items[type][$scope[selected].key] < 1) {
|
||||
$scope[selected] = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$scope.ownedItems = function(inventory){
|
||||
return _.pick(inventory, function(v,k){return v>0;});
|
||||
}
|
||||
|
||||
$scope.hatch = function(egg, potion){
|
||||
var eggName = Content.eggs[egg.key].text();
|
||||
var potName = Content.hatchingPotions[potion.key].text();
|
||||
if (!$window.confirm(window.env.t('hatchAPot', {potion: potName, egg: eggName}))) return;
|
||||
|
||||
var userHasPet = user.items.pets[egg.key + '-' + potion.key] > 0;
|
||||
var isPremiumPet = Content.hatchingPotions[potion.key].premium && !Content.dropEggs[egg.key];
|
||||
|
||||
User.hatch({params:{egg:egg.key, hatchingPotion:potion.key}});
|
||||
|
||||
if (!user.preferences.suppressModals.hatchPet && !userHasPet && !isPremiumPet) {
|
||||
$scope.hatchedPet = {
|
||||
egg: eggName,
|
||||
potion: potName,
|
||||
potionKey:potion.key,
|
||||
eggKey: egg.key,
|
||||
pet: 'Pet-' + egg.key + '-' + potion.key
|
||||
};
|
||||
|
||||
$rootScope.openModal('hatchPet', {
|
||||
scope: $scope,
|
||||
size: 'sm'
|
||||
});
|
||||
}
|
||||
|
||||
$scope.selectedEgg = null;
|
||||
$scope.selectedPotion = null;
|
||||
|
||||
_updateDropAnimalCount(user.items);
|
||||
|
||||
// Checks if beastmaster has been reached for the first time
|
||||
if(!user.achievements.beastMaster
|
||||
&& $scope.petCount >= 90) {
|
||||
User.user.achievements.beastMaster = true;
|
||||
$rootScope.openModal('achievements/beastMaster', {controller:'UserCtrl', size:'sm'});
|
||||
}
|
||||
|
||||
// Checks if Triad Bingo has been reached for the first time
|
||||
if(!user.achievements.triadBingo
|
||||
&& $scope.mountCount >= 90
|
||||
&& Shared.count.dropPetsCurrentlyOwned(User.user.items.pets) >= 90) {
|
||||
User.user.achievements.triadBingo = true;
|
||||
$rootScope.openModal('achievements/triadBingo', {controller:'UserCtrl', size:'sm'});
|
||||
}
|
||||
}
|
||||
|
||||
$scope.choosePet = function(egg, potion){
|
||||
var petDisplayName = env.t('petName', {
|
||||
potion: Content.hatchingPotions[potion] ? Content.hatchingPotions[potion].text() : potion,
|
||||
egg: Content.eggs[egg] ? Content.eggs[egg].text() : egg
|
||||
}),
|
||||
pet = egg + '-' + potion;
|
||||
|
||||
// Feeding Pet
|
||||
if ($scope.selectedFood) {
|
||||
var food = $scope.selectedFood;
|
||||
var startingMounts = $rootScope.countExists(user.items.mounts);
|
||||
if (food.key === 'Saddle') {
|
||||
if (!$window.confirm(window.env.t('useSaddle', {pet: petDisplayName}))) return;
|
||||
} else if (!$window.confirm(window.env.t('feedPet', {name: petDisplayName, article: food.article, text: food.text()}))) {
|
||||
return;
|
||||
}
|
||||
User.feed({params:{pet: pet, food: food.key}});
|
||||
$scope.selectedFood = null;
|
||||
|
||||
_updateDropAnimalCount(user.items);
|
||||
if ($rootScope.countExists(user.items.mounts) > startingMounts && !user.preferences.suppressModals.raisePet) {
|
||||
$scope.raisedPet = {
|
||||
displayName: petDisplayName,
|
||||
spriteName: pet,
|
||||
egg: egg,
|
||||
potion: potion
|
||||
}
|
||||
$rootScope.openModal('raisePet', {
|
||||
scope: $scope,
|
||||
size:'sm'
|
||||
});
|
||||
}
|
||||
|
||||
// Checks if mountmaster has been reached for the first time
|
||||
if(!user.achievements.mountMaster
|
||||
&& $scope.mountCount >= 90) {
|
||||
User.user.achievements.mountMaster = true;
|
||||
$rootScope.openModal('achievements/mountMaster', {controller:'UserCtrl', size:'sm'});
|
||||
}
|
||||
|
||||
// Selecting Pet
|
||||
} else {
|
||||
User.equip({params:{type: 'pet', key: pet}});
|
||||
}
|
||||
}
|
||||
|
||||
$scope.chooseMount = function(egg, potion) {
|
||||
User.equip({params:{type: 'mount', key: egg + '-' + potion}});
|
||||
}
|
||||
|
||||
$scope.getSeasonalShopArray = function(set){
|
||||
var flatGearArray = _.toArray(Content.gear.flat);
|
||||
|
||||
var filteredArray = _.where(flatGearArray, {index: set});
|
||||
|
||||
return filteredArray;
|
||||
};
|
||||
|
||||
$scope.getSeasonalShopQuests = function(set){
|
||||
var questArray = _.toArray(Content.quests);
|
||||
|
||||
var filteredArray = _.filter(questArray, function(q){
|
||||
return q.key === ('egg');
|
||||
});
|
||||
|
||||
return filteredArray;
|
||||
};
|
||||
|
||||
$scope.dequip = function(itemSet){
|
||||
switch (itemSet) {
|
||||
case "battleGear":
|
||||
for (item in user.items.gear.equipped){
|
||||
var itemKey = user.items.gear.equipped[item];
|
||||
if (user.items.gear.owned[itemKey]) {
|
||||
User.equip({params: {type: 'equipped', key: itemKey}});
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case "costume":
|
||||
for (item in user.items.gear.costume){
|
||||
var itemKey = user.items.gear.costume[item];
|
||||
if (user.items.gear.owned[itemKey]) {
|
||||
User.equip({params: {type:"costume", key: itemKey}});
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case "petMountBackground":
|
||||
var pet = user.items.currentPet;
|
||||
if (pet) {
|
||||
User.equip({params:{type: 'pet', key: pet}});
|
||||
}
|
||||
|
||||
var mount = user.items.currentMount;
|
||||
if (mount) {
|
||||
User.equip({params:{type: 'mount', key: mount}});
|
||||
}
|
||||
|
||||
var background = user.preferences.background;
|
||||
if (background) {
|
||||
User.unlock({query:{path:"background."+background}});
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
$scope.$on("habit:keydown", function (e, keyEvent) {
|
||||
if (keyEvent.keyCode == "27") {
|
||||
$scope.deselectItem();
|
||||
}
|
||||
});
|
||||
|
||||
$scope.deselectItem = function() {
|
||||
$scope.selectedFood = null;
|
||||
$scope.selectedPotion = null;
|
||||
$scope.selectedEgg = null;
|
||||
};
|
||||
|
||||
$scope.openCardsModal = function(type, numberOfVariations) {
|
||||
var cardsModalScope = $rootScope.$new();
|
||||
cardsModalScope.cardType = type;
|
||||
cardsModalScope.cardMessage = _generateCard(type, numberOfVariations);
|
||||
|
||||
$rootScope.openModal('cards', {
|
||||
scope: cardsModalScope
|
||||
});
|
||||
};
|
||||
|
||||
$scope.hasAllTimeTravelerItems = function() {
|
||||
return ($scope.hasAllTimeTravelerItemsOfType('mystery') &&
|
||||
$scope.hasAllTimeTravelerItemsOfType('pets') &&
|
||||
$scope.hasAllTimeTravelerItemsOfType('mounts'));
|
||||
};
|
||||
|
||||
$scope.hasAllTimeTravelerItemsOfType = function(type) {
|
||||
if (type === 'mystery') {
|
||||
var itemsLeftInTimeTravelerStore = Content.timeTravelerStore(user.items.gear.owned);
|
||||
var keys = Object.keys(itemsLeftInTimeTravelerStore);
|
||||
|
||||
return keys.length === 0;
|
||||
}
|
||||
|
||||
if (type === 'pets' || type === 'mounts') {
|
||||
for (var key in Content.timeTravelStable[type]) {
|
||||
if (!user.items[type][key]) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
else return Console.log('Time Traveler item type must be in ["pets","mounts","mystery"]');
|
||||
};
|
||||
|
||||
$scope.clickTimeTravelItem = function(type,key) {
|
||||
if (user.purchased.plan.consecutive.trinkets < 1) return User.hourglassPurchase({params:{type:type,key:key}});
|
||||
if (!window.confirm(window.env.t('hourglassBuyItemConfirm'))) return;
|
||||
User.hourglassPurchase({params:{type:type,key:key}});
|
||||
};
|
||||
|
||||
function _updateDropAnimalCount(items) {
|
||||
$scope.petCount = Shared.count.beastMasterProgress(items.pets);
|
||||
$scope.mountCount = Shared.count.mountMasterProgress(items.mounts);
|
||||
$scope.beastMasterProgress = Stats.beastMasterProgress(items.pets);
|
||||
$scope.mountMasterProgress = Stats.mountMasterProgress(items.mounts);
|
||||
}
|
||||
|
||||
function _generateCard(kind, numberOfVariations) {
|
||||
var random = Math.random() * numberOfVariations;
|
||||
var selection = Math.floor(random);
|
||||
return env.t(kind + selection);
|
||||
}
|
||||
}
|
||||
]);
|
||||
@@ -0,0 +1,85 @@
|
||||
'use strict';
|
||||
|
||||
habitrpg.controller('InviteToGroupCtrl', ['$scope', '$rootScope', 'User', 'Groups', 'injectedGroup', '$http', 'Notification',
|
||||
function($scope, $rootScope, User, Groups, injectedGroup, $http, Notification) {
|
||||
$scope.group = injectedGroup;
|
||||
|
||||
$scope.inviter = User.user.profile.name;
|
||||
_resetInvitees();
|
||||
|
||||
$scope.addUuid = function() {
|
||||
$scope.invitees.push({uuid: ''});
|
||||
};
|
||||
|
||||
$scope.addEmail = function() {
|
||||
$scope.emails.push({name: '', email: ''});
|
||||
};
|
||||
|
||||
$scope.inviteNewUsers = function(inviteMethod) {
|
||||
if (!$scope.group._id) {
|
||||
$scope.group.name = $scope.group.name || env.t('possessiveParty', {name: User.user.profile.name});
|
||||
|
||||
return Groups.Group.create($scope.group)
|
||||
.then(function(response) {
|
||||
$scope.group = response.data.data;
|
||||
User.sync();
|
||||
Groups.data.party = $scope.group;
|
||||
_inviteByMethod(inviteMethod);
|
||||
});
|
||||
}
|
||||
|
||||
_inviteByMethod(inviteMethod);
|
||||
};
|
||||
|
||||
function _inviteByMethod(inviteMethod) {
|
||||
var invitationDetails;
|
||||
|
||||
if (inviteMethod === 'email') {
|
||||
var emails = _getEmails();
|
||||
invitationDetails = {inviter: $scope.inviter, emails: emails};
|
||||
} else if (inviteMethod === 'uuid') {
|
||||
var uuids = _getOnlyUuids();
|
||||
invitationDetails = {uuids: uuids};
|
||||
} else {
|
||||
return console.log('Invalid invite method.')
|
||||
}
|
||||
|
||||
Groups.Group.invite($scope.group._id, invitationDetails)
|
||||
.then(function() {
|
||||
Notification.text(window.env.t('invitationsSent'));
|
||||
_resetInvitees();
|
||||
var redirectTo = '/#/options/groups/'
|
||||
if ($scope.group.type === 'party') {
|
||||
redirectTo += 'party';
|
||||
} else {
|
||||
redirectTo += ('guilds/' + $scope.group._id);
|
||||
}
|
||||
|
||||
$rootScope.hardRedirect(redirectTo);
|
||||
}, function(){
|
||||
_resetInvitees();
|
||||
});
|
||||
}
|
||||
|
||||
function _getOnlyUuids() {
|
||||
var uuids = _.pluck($scope.invitees, 'uuid');
|
||||
var filteredUuids = _.filter(uuids, function(id) {
|
||||
return id != '';
|
||||
});
|
||||
return filteredUuids;
|
||||
}
|
||||
|
||||
function _getEmails() {
|
||||
var emails = _.filter($scope.emails, function(obj) {
|
||||
return obj.email != '';
|
||||
});
|
||||
return emails;
|
||||
}
|
||||
|
||||
function _resetInvitees() {
|
||||
var emptyEmails = [{name:"",email:""},{name:"",email:""}];
|
||||
var emptyInvitees = [{uuid: ''}];
|
||||
$scope.emails = emptyEmails;
|
||||
$scope.invitees = emptyInvitees;
|
||||
}
|
||||
}]);
|
||||
@@ -0,0 +1,68 @@
|
||||
"use strict";
|
||||
|
||||
habitrpg
|
||||
.controller("MemberModalCtrl", ['$scope', '$rootScope', 'Members', 'Shared', '$http', 'Notification', 'Groups', 'Chat', '$controller', 'Stats',
|
||||
function($scope, $rootScope, Members, Shared, $http, Notification, Groups, Chat, $controller, Stats) {
|
||||
|
||||
$controller('RootCtrl', {$scope: $scope});
|
||||
|
||||
$scope.timestamp = function(timestamp){
|
||||
return moment(timestamp).format($rootScope.User.user.preferences.dateFormat.toUpperCase());
|
||||
}
|
||||
|
||||
$scope.statCalc = Stats;
|
||||
|
||||
// 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) {
|
||||
if(member) {
|
||||
$scope.profile = member;
|
||||
}
|
||||
});
|
||||
|
||||
$scope.sendPrivateMessage = function(uuid, message){
|
||||
if (!message) return;
|
||||
|
||||
Members.sendPrivateMessage(message, uuid)
|
||||
.then(function (response) {
|
||||
Notification.text(window.env.t('messageSentAlert'));
|
||||
$rootScope.User.sync();
|
||||
$scope.$close();
|
||||
});
|
||||
};
|
||||
|
||||
//@TODO: We don't send subscriptions so the structure has changed in the back. Update this when we update the views.
|
||||
$scope.gift = {
|
||||
type: 'gems',
|
||||
gems: {amount: 0, fromBalance: true},
|
||||
subscription: {key: ''},
|
||||
message: ''
|
||||
};
|
||||
|
||||
$scope.sendGift = function (uuid) {
|
||||
Members.transferGems($scope.gift.message, uuid, $scope.gift.gems.amount)
|
||||
.then(function (response) {
|
||||
Notification.text(window.env.t('sentGems'));
|
||||
$rootScope.User.sync();
|
||||
$scope.$close();
|
||||
});
|
||||
};
|
||||
|
||||
$scope.reportAbuse = function(reporter, message, groupId) {
|
||||
message.flags[reporter._id] = true;
|
||||
Chat.flagChatMessage(groupId, message.id)
|
||||
.then(function(data){
|
||||
Notification.text(window.env.t('abuseReported'));
|
||||
$scope.$close();
|
||||
});
|
||||
};
|
||||
|
||||
$scope.clearFlagCount = function(message, groupId) {
|
||||
Chat.clearFlagCount(groupId, message.id)
|
||||
.then(function(data){
|
||||
message.flagCount = 0;
|
||||
Notification.text("Flags cleared");
|
||||
$scope.$close();
|
||||
});
|
||||
}
|
||||
}
|
||||
]);
|
||||
@@ -0,0 +1,47 @@
|
||||
'use strict';
|
||||
|
||||
angular.module('habitrpg')
|
||||
.controller('MenuCtrl', ['$scope', '$rootScope', '$http', 'Chat',
|
||||
function($scope, $rootScope, $http, Chat) {
|
||||
|
||||
$scope.logout = function() {
|
||||
localStorage.clear();
|
||||
window.location.href = '/logout';
|
||||
};
|
||||
|
||||
function selectNotificationValue(mysteryValue, invitationValue, cardValue, unallocatedValue, messageValue, noneValue) {
|
||||
var user = $scope.user;
|
||||
if (user.purchased && user.purchased.plan && user.purchased.plan.mysteryItems && user.purchased.plan.mysteryItems.length) {
|
||||
return mysteryValue;
|
||||
} else if ((user.invitations.party && user.invitations.party.id) || (user.invitations.guilds && user.invitations.guilds.length > 0)) {
|
||||
return invitationValue;
|
||||
} else if (user.flags.cardReceived) {
|
||||
return cardValue;
|
||||
} else if (user.flags.classSelected && !(user.preferences && user.preferences.disableClasses) && user.stats.points) {
|
||||
return unallocatedValue;
|
||||
} else if (!(_.isEmpty(user.newMessages))) {
|
||||
return messageValue;
|
||||
} else {
|
||||
return noneValue;
|
||||
}
|
||||
}
|
||||
|
||||
$scope.clearMessages = Chat.markChatSeen;
|
||||
$scope.clearCards = Chat.clearCards;
|
||||
|
||||
$scope.iconClasses = function() {
|
||||
return selectNotificationValue(
|
||||
'glyphicon-gift',
|
||||
'glyphicon-user',
|
||||
'glyphicon-envelope',
|
||||
'glyphicon-plus-sign',
|
||||
'glyphicon-comment',
|
||||
'glyphicon-comment inactive'
|
||||
);
|
||||
};
|
||||
|
||||
$scope.hasNoNotifications = function() {
|
||||
return selectNotificationValue(false, false, false, false, false, true);
|
||||
}
|
||||
}
|
||||
]);
|
||||
@@ -0,0 +1,141 @@
|
||||
'use strict';
|
||||
|
||||
habitrpg.controller('NotificationCtrl',
|
||||
['$scope', '$rootScope', 'Shared', 'Content', 'User', 'Guide', 'Notification', 'Analytics',
|
||||
function ($scope, $rootScope, Shared, Content, User, Guide, Notification, Analytics) {
|
||||
|
||||
$rootScope.$watch('user.stats.hp', function (after, before) {
|
||||
if (after <= 0){
|
||||
$rootScope.playSound('Death');
|
||||
$rootScope.openModal('death', {keyboard:false, backdrop:'static'});
|
||||
} else if (after <= 30 && !User.user.flags.warnedLowHealth) {
|
||||
$rootScope.openModal('lowHealth', {keyboard:false, backdrop:'static', controller:'UserCtrl', track:'Health Warning'});
|
||||
}
|
||||
if (after == before) return;
|
||||
if (User.user.stats.lvl == 0) return;
|
||||
Notification.hp(after - before, 'hp');
|
||||
if (after < 0) $rootScope.playSound('Minus_Habit');
|
||||
});
|
||||
|
||||
$rootScope.$watch('user.stats.exp', function(after, before) {
|
||||
if (after == before) return;
|
||||
if (User.user.stats.lvl == 0) return;
|
||||
Notification.exp(after - before);
|
||||
});
|
||||
|
||||
$rootScope.$watch('user.achievements', function(){
|
||||
$rootScope.playSound('Achievement_Unlocked');
|
||||
}, true);
|
||||
|
||||
$rootScope.$watch('user.achievements.challenges.length', function(after, before) {
|
||||
if (after === before) return;
|
||||
if (after > before) {
|
||||
$rootScope.openModal('wonChallenge', {controller: 'UserCtrl', size: 'sm'});
|
||||
}
|
||||
});
|
||||
|
||||
$rootScope.$watch('user.stats.gp', function(after, before) {
|
||||
if (after == before) return;
|
||||
if (User.user.stats.lvl == 0) return;
|
||||
var money = after - before;
|
||||
var bonus;
|
||||
if (User.user._tmp) {
|
||||
bonus = User.user._tmp.streakBonus || 0;
|
||||
}
|
||||
Notification.gp(money, bonus || 0);
|
||||
|
||||
//Append Bonus
|
||||
|
||||
if ((money > 0) && !!bonus) {
|
||||
if (bonus < 0.01) bonus = 0.01;
|
||||
Notification.text("+ " + Notification.coins(bonus) + ' ' + window.env.t('streakCoins'));
|
||||
delete User.user._tmp.streakBonus;
|
||||
}
|
||||
});
|
||||
|
||||
$rootScope.$watch('user.stats.mp', function(after,before) {
|
||||
if (after == before) return;
|
||||
if (!User.user.flags.classSelected || User.user.preferences.disableClasses) return;
|
||||
var mana = after - before;
|
||||
Notification.mp(mana);
|
||||
});
|
||||
|
||||
// Levels that already display modals and should not trigger generic Level Up
|
||||
var unlockLevels = {
|
||||
'3': 'drop system',
|
||||
'10': 'class system',
|
||||
'50': 'Orb of Rebirth'
|
||||
}
|
||||
|
||||
$rootScope.$watch('user.stats.lvl', function(after, before) {
|
||||
if (after <= before) return;
|
||||
Notification.lvl();
|
||||
$rootScope.playSound('Level_Up');
|
||||
if (User.user._tmp && User.user._tmp.drop && (User.user._tmp.drop.type === 'Quest')) return;
|
||||
if (unlockLevels['' + after]) return;
|
||||
if (!User.user.preferences.suppressModals.levelUp) $rootScope.openModal('levelUp', {controller:'UserCtrl', size:'sm'});
|
||||
});
|
||||
|
||||
$rootScope.$watch('!user.flags.classSelected && user.stats.lvl >= 10', function(after, before){
|
||||
if(after){
|
||||
$rootScope.openModal('chooseClass', {controller:'UserCtrl', keyboard:false, backdrop:'static'});
|
||||
}
|
||||
});
|
||||
|
||||
$rootScope.$watch('user.achievements.streak', function(after, before){
|
||||
if(before == undefined || after <= before) return;
|
||||
Notification.streak(User.user.achievements.streak);
|
||||
$rootScope.playSound('Achievement_Unlocked');
|
||||
if (!User.user.preferences.suppressModals.streak) {
|
||||
$rootScope.openModal('achievements/streak', {controller:'UserCtrl'});
|
||||
}
|
||||
});
|
||||
|
||||
$rootScope.$watch('user.achievements.ultimateGearSets', function(after, before){
|
||||
if (_.isEqual(after,before) || !_.contains(User.user.achievements.ultimateGearSets, true)) return;
|
||||
$rootScope.openModal('achievements/ultimateGear', {controller:'UserCtrl'});
|
||||
}, true);
|
||||
|
||||
$rootScope.$watch('user.flags.armoireEmpty', function(after,before){
|
||||
if (before == undefined || after == before || after == false) return;
|
||||
$rootScope.openModal('armoireEmpty');
|
||||
});
|
||||
|
||||
$rootScope.$watch('user.achievements.rebirths', function(after, before){
|
||||
if(after === before) return;
|
||||
$rootScope.openModal('achievements/rebirth', {controller:'UserCtrl', size: 'sm'});
|
||||
});
|
||||
|
||||
$rootScope.$watch('user.contributor.level', function(after, before){
|
||||
if (after === before || after < before || after == null) return;
|
||||
$rootScope.openModal('achievements/contributor',{controller:'UserCtrl'});
|
||||
});
|
||||
|
||||
// Completed quest modal
|
||||
$scope.$watch('user.party.quest.completed', function(after, before){
|
||||
if (!after) return;
|
||||
$rootScope.openModal('questCompleted', {controller:'InventoryCtrl'});
|
||||
});
|
||||
|
||||
// Quest invitation modal
|
||||
$scope.$watch('user.party.quest.RSVPNeeded && !user.party.quest.completed', function(after, before){
|
||||
if (after != true) return;
|
||||
$rootScope.openModal('questInvitation', {controller:'PartyCtrl'});
|
||||
});
|
||||
|
||||
$rootScope.$on('responseError500', function(ev, error){
|
||||
Notification.error(error);
|
||||
});
|
||||
$rootScope.$on('responseError', function(ev, error){
|
||||
Notification.error(error, true);
|
||||
});
|
||||
|
||||
$rootScope.$on('responseText', function(ev, error){
|
||||
Notification.text(error);
|
||||
});
|
||||
|
||||
// Show new-stuff modal on load
|
||||
if (User.user.flags.newStuff)
|
||||
$rootScope.openModal('newStuff', {size:'lg'});
|
||||
}
|
||||
]);
|
||||
@@ -0,0 +1,213 @@
|
||||
'use strict';
|
||||
|
||||
habitrpg.controller("PartyCtrl", ['$rootScope','$scope','Groups','Chat','User','Challenges','$state','$compile','Analytics','Quests','Social',
|
||||
function($rootScope, $scope, Groups, Chat, User, Challenges, $state, $compile, Analytics, Quests, Social) {
|
||||
|
||||
var user = User.user;
|
||||
|
||||
$scope.type = 'party';
|
||||
$scope.text = window.env.t('party');
|
||||
|
||||
$scope.inviteOrStartParty = Groups.inviteOrStartParty;
|
||||
$scope.loadWidgets = Social.loadWidgets;
|
||||
|
||||
Groups.Group.syncParty()
|
||||
.then(function successCallback(group) {
|
||||
$rootScope.party = $scope.group = group;
|
||||
checkForNotifications();
|
||||
}, function errorCallback(response) {
|
||||
$rootScope.party = $scope.group = $scope.newGroup = { type: 'party' };
|
||||
});
|
||||
|
||||
function checkForNotifications () {
|
||||
// Checks if user's party has reached 2 players for the first time.
|
||||
if(!user.achievements.partyUp
|
||||
&& $scope.group.memberCount >= 2) {
|
||||
User.set({'achievements.partyUp':true});
|
||||
$rootScope.openModal('achievements/partyUp', {controller:'UserCtrl', size:'sm'});
|
||||
}
|
||||
|
||||
// Checks if user's party has reached 4 players for the first time.
|
||||
if(!user.achievements.partyOn
|
||||
&& $scope.group.memberCount >= 4) {
|
||||
User.set({'achievements.partyOn':true});
|
||||
$rootScope.openModal('achievements/partyOn', {controller:'UserCtrl', size:'sm'});
|
||||
}
|
||||
}
|
||||
|
||||
if ($scope.group && $scope.group._id) {
|
||||
Chat.markChatSeen($scope.group._id);
|
||||
}
|
||||
|
||||
$scope.create = function(group) {
|
||||
if (!group.name) group.name = env.t('possessiveParty', {name: User.user.profile.name});
|
||||
Groups.Group.create(group)
|
||||
.then(function(response) {
|
||||
$rootScope.party = $scope.group = response.data.data;
|
||||
User.sync();
|
||||
Groups.data.party = $scope.group;
|
||||
Analytics.track({'hitType':'event', 'eventCategory':'behavior', 'eventAction':'join group', 'owner':true, 'groupType':'party', 'privacy':'private'});
|
||||
Analytics.updateUser({'party.id': $scope.group ._id, 'partySize': 1});
|
||||
});
|
||||
};
|
||||
|
||||
$scope.join = function (party) {
|
||||
Groups.Group.join(party.id)
|
||||
.then(function (response) {
|
||||
$rootScope.party = $scope.group = response.data.data;
|
||||
User.sync();
|
||||
Analytics.track({'hitType':'event','eventCategory':'behavior','eventAction':'join group','owner':false,'groupType':'party','privacy':'private'});
|
||||
Analytics.updateUser({'partyID': party.id});
|
||||
$rootScope.hardRedirect('/#/options/groups/party');
|
||||
});
|
||||
};
|
||||
|
||||
// TODO: refactor guild and party leave into one function
|
||||
$scope.leave = function (keep) {
|
||||
if (keep == 'cancel') {
|
||||
$scope.selectedGroup = undefined;
|
||||
$scope.popoverEl.popover('destroy');
|
||||
} else {
|
||||
Groups.Group.leave($scope.selectedGroup._id, keep)
|
||||
.then(function (response) {
|
||||
Analytics.updateUser({'partySize':null,'partyID':null});
|
||||
User.sync().then(function () {
|
||||
$rootScope.hardRedirect('/#/options/groups/party');
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// TODO: refactor guild and party clickLeave into one function
|
||||
$scope.clickLeave = function(group, $event){
|
||||
Analytics.track({'hitType':'event','eventCategory':'button','eventAction':'click','eventLabel':'Leave Party'});
|
||||
$scope.selectedGroup = group;
|
||||
$scope.popoverEl = $($event.target).closest('.btn');
|
||||
var html, title;
|
||||
html = $compile('<a ng-controller="GroupsCtrl" ng-click="leave(\'remove-all\')">' + window.env.t('removeTasks') + '</a><br/>\n<a ng-click="leave(\'keep-all\')">' + window.env.t('keepTasks') + '</a><br/>\n<a ng-click="leave(\'cancel\')">' + window.env.t('cancel') + '</a><br/>')($scope);
|
||||
title = window.env.t('leavePartyCha');
|
||||
|
||||
//TODO: Move this to challenge service
|
||||
Challenges.getGroupChallenges(group._id)
|
||||
.then(function(response) {
|
||||
var challenges = _.pluck(_.filter(response.data.data, function(c) {
|
||||
return c.group._id == group._id;
|
||||
}), '_id');
|
||||
|
||||
if (_.intersection(challenges, User.user.challenges).length > 0) {
|
||||
html = $compile(
|
||||
'<a ng-controller="GroupsCtrl" ng-click="leave(\'remove-all\')">' + window.env.t('removeTasks') + '</a><br/>\n<a ng-click="leave(\'keep-all\')">' + window.env.t('keepTasks') + '</a><br/>\n<a ng-click="leave(\'cancel\')">' + window.env.t('cancel') + '</a><br/>'
|
||||
)($scope);
|
||||
title = window.env.t('leavePartyCha');
|
||||
} else {
|
||||
html = $compile(
|
||||
'<a ng-controller="GroupsCtrl" ng-click="leave(\'keep-all\')">' + window.env.t('confirm') + '</a><br/>\n<a ng-click="leave(\'cancel\')">' + window.env.t('cancel') + '</a><br/>'
|
||||
)($scope);
|
||||
title = window.env.t('leaveParty');
|
||||
}
|
||||
|
||||
$scope.popoverEl.popover('destroy').popover({
|
||||
html: true,
|
||||
placement: 'top',
|
||||
trigger: 'manual',
|
||||
title: title,
|
||||
content: html
|
||||
}).popover('show');
|
||||
});
|
||||
};
|
||||
|
||||
$scope.clickStartQuest = function () {
|
||||
Analytics.track({'hitType':'event','eventCategory':'button','eventAction':'click','eventLabel':'Start a Quest'});
|
||||
var hasQuests = _.find(User.user.items.quests, function(quest) {
|
||||
return quest > 0;
|
||||
});
|
||||
|
||||
if (hasQuests){
|
||||
$rootScope.openModal("ownedQuests", { controller:"InventoryCtrl" });
|
||||
} else {
|
||||
$rootScope.$state.go('options.inventory.quests');
|
||||
}
|
||||
};
|
||||
|
||||
$scope.leaveOldPartyAndJoinNewParty = function(newPartyId, newPartyName) {
|
||||
if (confirm('Are you sure you want to delete your party and join ' + newPartyName + '?')) {
|
||||
Groups.Group.leave(Groups.data.party._id, false)
|
||||
.then(function() {
|
||||
$rootScope.party = $scope.group = {
|
||||
loadingNewParty: true
|
||||
};
|
||||
$scope.join({ id: newPartyId, name: newPartyName });
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
$scope.reject = function(party) {
|
||||
Groups.Group.rejectInvite(party.id);
|
||||
User.set({'invitations.party':{}});
|
||||
}
|
||||
|
||||
$scope.questInit = function() {
|
||||
var key = $rootScope.selectedQuest.key;
|
||||
|
||||
Quests.initQuest(key).then(function() {
|
||||
$rootScope.selectedQuest = undefined;
|
||||
$scope.$close();
|
||||
});
|
||||
};
|
||||
|
||||
$scope.questCancel = function(){
|
||||
if (!confirm(window.env.t('sureCancel'))) return;
|
||||
|
||||
Quests.sendAction('quests/cancel')
|
||||
.then(function(quest) {
|
||||
$scope.group.quest = quest;
|
||||
});
|
||||
}
|
||||
|
||||
$scope.questAbort = function(){
|
||||
if (!confirm(window.env.t('sureAbort'))) return;
|
||||
if (!confirm(window.env.t('doubleSureAbort'))) return;
|
||||
|
||||
Quests.sendAction('quests/abort')
|
||||
.then(function(quest) {
|
||||
$scope.group.quest = quest;
|
||||
});
|
||||
}
|
||||
|
||||
$scope.questLeave = function(){
|
||||
if (!confirm(window.env.t('sureLeave'))) return;
|
||||
|
||||
Quests.sendAction('quests/leave')
|
||||
.then(function(quest) {
|
||||
$scope.group.quest = quest;
|
||||
});
|
||||
}
|
||||
|
||||
$scope.questAccept = function(){
|
||||
Quests.sendAction('quests/accept')
|
||||
.then(function(quest) {
|
||||
$scope.group.quest = quest;
|
||||
});
|
||||
};
|
||||
|
||||
$scope.questForceStart = function(){
|
||||
Quests.sendAction('quests/force-start')
|
||||
.then(function(quest) {
|
||||
$scope.group.quest = quest;
|
||||
});
|
||||
};
|
||||
|
||||
$scope.questReject = function(){
|
||||
Quests.sendAction('quests/reject')
|
||||
.then(function(quest) {
|
||||
$scope.group.quest = quest;
|
||||
});
|
||||
};
|
||||
|
||||
$scope.canEditQuest = function() {
|
||||
var isQuestLeader = $scope.group.quest && $scope.group.quest.leader === User.user._id;
|
||||
|
||||
return isQuestLeader;
|
||||
};
|
||||
}
|
||||
]);
|
||||
@@ -0,0 +1,369 @@
|
||||
"use strict";
|
||||
|
||||
/* Make user and settings available for everyone through root scope.
|
||||
*/
|
||||
|
||||
habitrpg.controller("RootCtrl", ['$scope', '$rootScope', '$location', 'User', '$http', '$state', '$stateParams', 'Notification', 'Groups', 'Shared', 'Content', '$modal', '$timeout', 'ApiUrl', 'Payments','$sce','$window','Analytics','TAVERN_ID',
|
||||
function($scope, $rootScope, $location, User, $http, $state, $stateParams, Notification, Groups, Shared, Content, $modal, $timeout, ApiUrl, Payments, $sce, $window, Analytics, TAVERN_ID) {
|
||||
var user = User.user;
|
||||
|
||||
var initSticky = _.once(function(){
|
||||
if (window.env.IS_MOBILE || User.user.preferences.stickyHeader === false) return;
|
||||
$('.header-wrap').sticky({topSpacing:0});
|
||||
})
|
||||
$rootScope.$on('userUpdated',initSticky);
|
||||
|
||||
$rootScope.$on('$stateChangeSuccess',
|
||||
function(event, toState, toParams, fromState, fromParams){
|
||||
|
||||
$rootScope.pageTitle = $state.current.title;
|
||||
|
||||
if (!!fromState.name) Analytics.track({'hitType':'pageview','eventCategory':'navigation','eventAction':'navigate','page':'/#/'+toState.name});
|
||||
// clear inbox when entering or exiting inbox tab
|
||||
if (fromState.name=='options.social.inbox' || toState.name=='options.social.inbox') {
|
||||
User.clearNewMessages();
|
||||
}
|
||||
});
|
||||
|
||||
$rootScope.TAVERN_ID = TAVERN_ID;
|
||||
$rootScope.User = User;
|
||||
$rootScope.user = user;
|
||||
$rootScope.moment = window.moment;
|
||||
$rootScope._ = window._;
|
||||
$rootScope.settings = User.settings;
|
||||
$rootScope.Shared = Shared;
|
||||
$rootScope.Content = Content;
|
||||
$rootScope.Analytics = Analytics;
|
||||
$rootScope.env = window.env;
|
||||
$rootScope.Math = Math;
|
||||
$rootScope.Groups = Groups;
|
||||
$rootScope.toJson = angular.toJson;
|
||||
$rootScope.Payments = Payments;
|
||||
|
||||
// Angular UI Router
|
||||
$rootScope.$state = $state;
|
||||
$rootScope.$stateParams = $stateParams;
|
||||
|
||||
// indexOf helper
|
||||
$scope.indexOf = function(haystack, needle){
|
||||
return haystack && ~haystack.indexOf(needle);
|
||||
}
|
||||
|
||||
// styling helpers
|
||||
$rootScope.userLevelStyle = function(user,style){
|
||||
style = style || '';
|
||||
var npc = (user && user.backer && user.backer.npc) ? user.backer.npc : '';
|
||||
var level = (user && user.contributor && user.contributor.level) ? user.contributor.level : '';
|
||||
style += $scope.userLevelStyleFromLevel(level,npc,style)
|
||||
return style;
|
||||
}
|
||||
$scope.userAdminGlyphiconStyle = function(user,style){
|
||||
style = style || '';
|
||||
if(user && user.contributor && user.contributor.level)
|
||||
style += $scope.userAdminGlyphiconStyleFromLevel(user.contributor.level,style)
|
||||
return style;
|
||||
}
|
||||
$scope.userLevelStyleFromLevel = function(level,npc,style){
|
||||
style = style || '';
|
||||
if(npc)
|
||||
style += ' label-npc';
|
||||
if(level)
|
||||
style += ' label-contributor-'+level;
|
||||
return style;
|
||||
}
|
||||
$scope.userAdminGlyphiconStyleFromLevel = function(level,style){
|
||||
style = style || '';
|
||||
if(level)
|
||||
if(level==8)
|
||||
style += ' glyphicon glyphicon-star'; // moderator
|
||||
if(level==9)
|
||||
style += ' glyphicon icon-crown'; // staff
|
||||
return style;
|
||||
}
|
||||
|
||||
$rootScope.playSound = function(id){
|
||||
if (!user.preferences.sound || user.preferences.sound == 'off') return;
|
||||
var theme = user.preferences.sound;
|
||||
var file = 'common/audio/' + theme + '/' + id;
|
||||
document.getElementById('oggSource').src = file + '.ogg';
|
||||
document.getElementById('mp3Source').src = file + '.mp3';
|
||||
document.getElementById('sound').load();
|
||||
}
|
||||
|
||||
// count pets, mounts collected totals, etc
|
||||
$rootScope.countExists = function(items) {return _.reduce(items,function(m,v){return m+(v?1:0)},0)}
|
||||
|
||||
$scope.safeApply = function(fn) {
|
||||
var phase = this.$root.$$phase;
|
||||
if(phase == '$apply' || phase == '$digest') {
|
||||
if(fn && (typeof(fn) === 'function')) {
|
||||
fn();
|
||||
}
|
||||
} else {
|
||||
this.$apply(fn);
|
||||
}
|
||||
};
|
||||
|
||||
$rootScope.set = User.set;
|
||||
$rootScope.authenticated = User.authenticated;
|
||||
|
||||
var forceLoadBailey = function(template, options) {
|
||||
$http.get('/new-stuff.html')
|
||||
.success(function(data) {
|
||||
$rootScope.latestBaileyMessage = $sce.trustAsHtml(data);
|
||||
$modal.open({
|
||||
templateUrl: 'modals/' + template + '.html',
|
||||
controller: options.controller, // optional
|
||||
scope: options.scope, // optional
|
||||
resolve: options.resolve, // optional
|
||||
keyboard: (options.keyboard === undefined ? true : options.keyboard), // optional
|
||||
backdrop: (options.backdrop === undefined ? true : options.backdrop), // optional
|
||||
size: options.size, // optional, 'sm' or 'lg'
|
||||
windowClass: options.windowClass // optional
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// Open a modal from a template expression (like ng-click,...)
|
||||
// Otherwise use the proper $modal.open
|
||||
$rootScope.openModal = function(template, options){//controller, scope, keyboard, backdrop){
|
||||
if (!options) options = {};
|
||||
if (options.track) Analytics.track({'hitType':'event','eventCategory':'button','eventAction':'click','eventLabel':options.track});
|
||||
if(template === 'newStuff') return forceLoadBailey(template, options);
|
||||
return $modal.open({
|
||||
templateUrl: 'modals/' + template + '.html',
|
||||
controller: options.controller, // optional
|
||||
scope: options.scope, // optional
|
||||
resolve: options.resolve, // optional
|
||||
keyboard: (options.keyboard === undefined ? true : options.keyboard), // optional
|
||||
backdrop: (options.backdrop === undefined ? true : options.backdrop), // optional
|
||||
size: options.size, // optional, 'sm' or 'lg'
|
||||
windowClass: options.windowClass // optional
|
||||
});
|
||||
}
|
||||
|
||||
$rootScope.dismissAlert = function() {
|
||||
$rootScope.set({'flags.newStuff':false});
|
||||
}
|
||||
|
||||
$rootScope.acceptCommunityGuidelines = function() {
|
||||
$rootScope.set({'flags.communityGuidelinesAccepted':true});
|
||||
}
|
||||
|
||||
$rootScope.notPorted = function(){
|
||||
alert(window.env.t('notPorted'));
|
||||
}
|
||||
|
||||
$rootScope.dismissErrorOrWarning = function(type, $index){
|
||||
$rootScope.flash[type].splice($index, 1);
|
||||
}
|
||||
|
||||
$scope.contribText = function(contrib, backer){
|
||||
if (!contrib && !backer) return;
|
||||
if (backer && backer.npc) return backer.npc;
|
||||
var l = contrib && contrib.level;
|
||||
if (l && l > 0) {
|
||||
var level = (l < 3) ? window.env.t('friend') : (l < 5) ? window.env.t('elite') : (l < 7) ? window.env.t('champion') : (l < 8) ? window.env.t('legendary') : (l < 9) ? window.env.t('guardian') : window.env.t('heroic');
|
||||
return level + ' ' + contrib.text;
|
||||
}
|
||||
}
|
||||
|
||||
$rootScope.charts = {};
|
||||
$rootScope.toggleChart = function(id, task) {
|
||||
var history = [], matrix, data, chart, options;
|
||||
switch (id) {
|
||||
case 'exp':
|
||||
history = User.user.history.exp;
|
||||
$rootScope.charts.exp = (history.length == 0) ? false : !$rootScope.charts.exp;
|
||||
break;
|
||||
case 'todos':
|
||||
history = User.user.history.todos;
|
||||
$rootScope.charts.todos = (history.length == 0) ? false : !$rootScope.charts.todos;
|
||||
break;
|
||||
default:
|
||||
history = task.history;
|
||||
$rootScope.charts[id] = (history.length == 0) ? false : !$rootScope.charts[id];
|
||||
if (task && task._editing) task._editing = false;
|
||||
}
|
||||
matrix = [[env.t('date'), env.t('score')]];
|
||||
_.each(history, function(obj) {
|
||||
matrix.push([moment(obj.date).format(User.user.preferences.dateFormat.toUpperCase().replace('YYYY','YY') ), obj.value]);
|
||||
});
|
||||
data = google.visualization.arrayToDataTable(matrix);
|
||||
options = {
|
||||
title: window.env.t('history'),
|
||||
backgroundColor: {
|
||||
fill: 'transparent'
|
||||
},
|
||||
hAxis: {slantedText:true, slantedTextAngle: 90},
|
||||
height:270,
|
||||
width:300
|
||||
};
|
||||
chart = new google.visualization.LineChart($("." + id + "-chart")[0]);
|
||||
chart.draw(data, options);
|
||||
};
|
||||
|
||||
$rootScope.getGearArray = function(set){
|
||||
var flatGearArray = _.toArray(Content.gear.flat);
|
||||
|
||||
var filteredArray = _.where(flatGearArray, {gearSet: set});
|
||||
|
||||
return filteredArray;
|
||||
}
|
||||
|
||||
// @TODO: Extract equip and purchase into equipment service
|
||||
$rootScope.equip = function(itemKey, equipType) {
|
||||
equipType = equipType || (user.preferences.costume ? 'costume' : 'equipped');
|
||||
var equipParams = {
|
||||
type: equipType,
|
||||
key: itemKey
|
||||
};
|
||||
|
||||
User.equip({ params: equipParams });
|
||||
}
|
||||
|
||||
$rootScope.purchase = function(type, item){
|
||||
if (type == 'special') return User.buySpecialSpell({params:{key:item.key}});
|
||||
|
||||
var gems = user.balance * 4;
|
||||
var price = item.value;
|
||||
var message = "";
|
||||
|
||||
var itemName = window.env.t(Content.itemList[type].localeKey)
|
||||
|
||||
if (Content.itemList[type].isEquipment) {
|
||||
var eligibleForPurchase = _canBuyEquipment(item.key);
|
||||
if (!eligibleForPurchase) return false;
|
||||
|
||||
// @TODO: Attach gemValue to content so we don't have to do this
|
||||
price = ((((item.specialClass == "wizard") && (item.type == "weapon")) || item.gearSet == "animal") + 1);
|
||||
type = 'gear';
|
||||
}
|
||||
|
||||
if (gems < price) return $rootScope.openModal('buyGems');
|
||||
|
||||
if (type === 'quests') {
|
||||
if (item.previous) {message = window.env.t('alreadyEarnedQuestReward', {priorQuest: Content.quests[item.previous].text()})}
|
||||
else if (item.lvl) {message = window.env.t('alreadyEarnedQuestLevel', {level: item.lvl})}
|
||||
}
|
||||
|
||||
message += window.env.t('buyThis', {text: itemName, price: price, gems: gems});
|
||||
if ($window.confirm(message))
|
||||
User.purchase({params:{type:type,key:item.key}});
|
||||
};
|
||||
|
||||
function _canBuyEquipment(itemKey) {
|
||||
if (user.items.gear.owned[itemKey]) {
|
||||
$window.alert(window.env.t('messageAlreadyOwnGear'));
|
||||
} else if (user.items.gear.owned[itemKey] === false) {
|
||||
$window.alert(window.env.t('messageAlreadyPurchasedGear'));
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
------------------------
|
||||
Spells
|
||||
------------------------
|
||||
*/
|
||||
$scope.castStart = function(spell) {
|
||||
if (User.user.stats.mp < spell.mana) return Notification.text(window.env.t('notEnoughMana'));
|
||||
|
||||
if (spell.immediateUse && User.user.stats.gp < spell.value)
|
||||
return Notification.text('Not enough gold.');
|
||||
|
||||
$rootScope.applyingAction = true;
|
||||
$scope.spell = spell;
|
||||
if (spell.target == 'self') {
|
||||
$scope.castEnd(null, 'self');
|
||||
} else if (spell.target == 'party') {
|
||||
Groups.party()
|
||||
.then(function (party) {
|
||||
party = (_.isArray(party) ? party : []).concat(User.user);
|
||||
$scope.castEnd(party, 'party');
|
||||
})
|
||||
.catch(function (party) { // not in a party, act as a solo party
|
||||
if (party && party.type === 'party') {
|
||||
party = [User.user];
|
||||
$scope.castEnd(party, 'party');
|
||||
}
|
||||
});
|
||||
} else if (spell.target == 'tasks') {
|
||||
var tasks = User.user.habits.concat(User.user.dailys).concat(User.user.rewards).concat(User.user.todos);
|
||||
// exclude challenge tasks
|
||||
tasks = tasks.filter(function (task) {
|
||||
if (!task.challenge) return true;
|
||||
return (!task.challenge.id || task.challenge.broken);
|
||||
});
|
||||
$scope.castEnd(tasks, 'tasks');
|
||||
}
|
||||
}
|
||||
|
||||
$scope.castEnd = function(target, type, $event){
|
||||
if (!$rootScope.applyingAction) return 'No applying action';
|
||||
$event && ($event.stopPropagation(),$event.preventDefault());
|
||||
|
||||
if ($scope.spell.target != type) return Notification.text(window.env.t('invalidTarget'));
|
||||
$scope.spell.cast(User.user, target);
|
||||
User.save();
|
||||
|
||||
var spell = $scope.spell;
|
||||
var targetId = target ? target._id : null;
|
||||
$scope.spell = null;
|
||||
$rootScope.applyingAction = false;
|
||||
|
||||
var spellUrl = ApiUrl.get() + '/api/v3/user/class/cast/' + spell.key;
|
||||
if (targetId) spellUrl += '?targetId=' + targetId;
|
||||
|
||||
$http.post(spellUrl)
|
||||
.success(function(){ // TODO response will always include the modified data, no need to sync!
|
||||
var msg = window.env.t('youCast', {spell: spell.text()});
|
||||
switch (type) {
|
||||
case 'task': msg = window.env.t('youCastTarget', {spell: spell.text(), target: target.text});break;
|
||||
case 'user': msg = window.env.t('youCastTarget', {spell: spell.text(), target: target.profile.name});break;
|
||||
case 'party': msg = window.env.t('youCastParty', {spell: spell.text()});break;
|
||||
}
|
||||
Notification.markdown(msg);
|
||||
User.sync();
|
||||
});
|
||||
}
|
||||
|
||||
$rootScope.castCancel = function(){
|
||||
$rootScope.applyingAction = false;
|
||||
$scope.spell = null;
|
||||
}
|
||||
|
||||
// Because our angular-ui-router uses anchors for urls (/#/options/groups/party), window.location.href=... won't
|
||||
// reload the page. Perform manually.
|
||||
$rootScope.hardRedirect = function(url){
|
||||
window.location.href = url;
|
||||
setTimeout(function() {
|
||||
window.location.reload(false);
|
||||
});
|
||||
}
|
||||
|
||||
// Universal method for sending HTTP methods
|
||||
$rootScope.http = function(method, route, data, alertMsg){
|
||||
$http[method](ApiUrl.get() + route, data).success(function(){
|
||||
if (alertMsg) Notification.text(window.env.t(alertMsg));
|
||||
User.sync();
|
||||
});
|
||||
// error will be handled via $http interceptor
|
||||
}
|
||||
|
||||
// Global Keyevents
|
||||
var ctrlKeys = [17, 224, 91];
|
||||
$scope.$on("habit:keydown", function (e, keyEvent) {
|
||||
if (ctrlKeys.indexOf(keyEvent.keyCode) !== -1) {
|
||||
$scope.ctrlPressed = true;
|
||||
}
|
||||
});
|
||||
|
||||
$scope.$on("habit:keyup", function (e, keyEvent) {
|
||||
if (ctrlKeys.indexOf(keyEvent.keyCode) !== -1) {
|
||||
$scope.ctrlPressed = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
]);
|
||||
@@ -0,0 +1,303 @@
|
||||
'use strict';
|
||||
|
||||
// Make user and settings available for everyone through root scope.
|
||||
habitrpg.controller('SettingsCtrl',
|
||||
['$scope', 'User', '$rootScope', '$http', 'ApiUrl', 'Guide', '$location', '$timeout', 'Content', 'Notification', 'Shared', '$compile',
|
||||
function($scope, User, $rootScope, $http, ApiUrl, Guide, $location, $timeout, Content, Notification, Shared, $compile) {
|
||||
var RELEASE_ANIMAL_TYPES = {
|
||||
pets: 'releasePets',
|
||||
mounts: 'releaseMounts',
|
||||
both: 'releaseBoth',
|
||||
};
|
||||
|
||||
// FIXME we have this re-declared everywhere, figure which is the canonical version and delete the rest
|
||||
// $scope.auth = function (id, token) {
|
||||
// User.authenticate(id, token, function (err) {
|
||||
// if (!err) {
|
||||
// alert('Login successful!');
|
||||
// $location.path("/habit");
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
|
||||
// A simple object to map the key stored in the db (user.preferences.emailNotification[key])
|
||||
// to its string but ONLY when the preferences' key and the string key don't match
|
||||
var mapPrefToEmailString = {
|
||||
'importantAnnouncements': 'inactivityEmails'
|
||||
};
|
||||
|
||||
// If ?unsubFrom param is passed with valid email type,
|
||||
// automatically unsubscribe users from that email and
|
||||
// show an alert
|
||||
$timeout(function(){
|
||||
var unsubFrom = $location.search().unsubFrom;
|
||||
if(unsubFrom){
|
||||
var emailPrefKey = 'preferences.emailNotifications.' + unsubFrom;
|
||||
var emailTypeString = env.t(mapPrefToEmailString[unsubFrom] || unsubFrom);
|
||||
User.set({emailPrefKey: false});
|
||||
User.user.preferences.emailNotifications[unsubFrom] = false;
|
||||
Notification.text(env.t('correctlyUnsubscribedEmailType', {emailType: emailTypeString}));
|
||||
$location.search({});
|
||||
}
|
||||
}, 1000);
|
||||
|
||||
$scope.hideHeader = function(){
|
||||
User.set({"preferences.hideHeader":!User.user.preferences.hideHeader})
|
||||
if (User.user.preferences.hideHeader && User.user.preferences.stickyHeader){
|
||||
User.set({"preferences.stickyHeader":false});
|
||||
$rootScope.$on('userSynced', function(){
|
||||
window.location.reload();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
$scope.toggleStickyHeader = function(){
|
||||
$rootScope.$on('userSynced', function(){
|
||||
window.location.reload();
|
||||
});
|
||||
User.set({"preferences.stickyHeader":!User.user.preferences.stickyHeader});
|
||||
}
|
||||
|
||||
$scope.showTour = function(){
|
||||
User.set({'flags.showTour':true});
|
||||
Guide.goto('intro', 0, true);
|
||||
}
|
||||
|
||||
$scope.showBailey = function(){
|
||||
User.set({'flags.newStuff':true});
|
||||
}
|
||||
|
||||
$scope.dayStart = User.user.preferences.dayStart;
|
||||
|
||||
$scope.openDayStartModal = function(dayStart) {
|
||||
$scope.dayStart = +dayStart;
|
||||
$scope.nextCron = _calculateNextCron();
|
||||
|
||||
$rootScope.openModal('change-day-start', { scope: $scope });
|
||||
};
|
||||
|
||||
$scope.saveDayStart = function() {
|
||||
User.setCustomDayStart(Math.floor($scope.dayStart));
|
||||
};
|
||||
|
||||
$scope.language = window.env.language;
|
||||
$scope.availableLanguages = window.env.availableLanguages;
|
||||
|
||||
$scope.changeLanguage = function(){
|
||||
$rootScope.$on('userSynced', function(){
|
||||
window.location.reload();
|
||||
});
|
||||
User.set({'preferences.language': $scope.language.code});
|
||||
}
|
||||
|
||||
$scope.availableFormats = ['MM/dd/yyyy','dd/MM/yyyy', 'yyyy/MM/dd'];
|
||||
|
||||
$scope.reroll = function(confirm){
|
||||
$scope.popoverEl.popover('destroy');
|
||||
|
||||
if (confirm) {
|
||||
User.reroll({});
|
||||
$rootScope.$state.go('tasks');
|
||||
}
|
||||
}
|
||||
|
||||
$scope.clickReroll = function($event){
|
||||
$scope.popoverEl = $($event.target);
|
||||
|
||||
var html = $compile(
|
||||
'<a ng-controller="SettingsCtrl" ng-click="$close(); reroll(true)">' + window.env.t('confirm') + '</a><br/>\n<a ng-click="reroll(false)">' + window.env.t('cancel') + '</a><br/>'
|
||||
)($scope);
|
||||
|
||||
$scope.popoverEl.popover('destroy').popover({
|
||||
html: true,
|
||||
placement: 'top',
|
||||
trigger: 'manual',
|
||||
title: window.env.t('confirmFortify'),
|
||||
content: html
|
||||
}).popover('show');
|
||||
}
|
||||
|
||||
$scope.rebirth = function(confirm){
|
||||
$scope.popoverEl.popover('destroy');
|
||||
|
||||
if (confirm) {
|
||||
User.rebirth({});
|
||||
$rootScope.$state.go('tasks');
|
||||
}
|
||||
}
|
||||
|
||||
$scope.clickRebirth = function($event){
|
||||
$scope.popoverEl = $($event.target);
|
||||
|
||||
var html = $compile(
|
||||
'<a ng-controller="SettingsCtrl" ng-click="$close(); rebirth(true)">' + window.env.t('confirm') + '</a><br/>\n<a ng-click="rebirth(false)">' + window.env.t('cancel') + '</a><br/>'
|
||||
)($scope);
|
||||
|
||||
$scope.popoverEl.popover('destroy').popover({
|
||||
html: true,
|
||||
placement: 'top',
|
||||
trigger: 'manual',
|
||||
title: window.env.t('confirmReborn'),
|
||||
content: html
|
||||
}).popover('show');
|
||||
}
|
||||
|
||||
$scope.changeUser = function(attr, updates){
|
||||
$http.put(ApiUrl.get() + '/api/v3/user/auth/update-'+attr, updates)
|
||||
.success(function(){
|
||||
alert(window.env.t(attr+'Success'));
|
||||
_.each(updates, function(v,k){updates[k]=null;});
|
||||
User.sync();
|
||||
});
|
||||
}
|
||||
|
||||
$scope.restoreValues = {};
|
||||
$rootScope.openRestoreModal = function(){
|
||||
$scope.restoreValues.stats = angular.copy(User.user.stats);
|
||||
$scope.restoreValues.achievements = {streak: User.user.achievements.streak || 0};
|
||||
$rootScope.openModal('restore', {scope:$scope});
|
||||
};
|
||||
|
||||
$scope.restore = function(){
|
||||
var stats = $scope.restoreValues.stats,
|
||||
achievements = $scope.restoreValues.achievements;
|
||||
User.set({
|
||||
"stats.hp": stats.hp,
|
||||
"stats.exp": stats.exp,
|
||||
"stats.gp": stats.gp,
|
||||
"stats.lvl": stats.lvl,
|
||||
"stats.mp": stats.mp,
|
||||
"achievements.streak": achievements.streak
|
||||
});
|
||||
}
|
||||
|
||||
$scope.reset = function(){
|
||||
User.reset({});
|
||||
User.sync();
|
||||
$rootScope.$state.go('tasks');
|
||||
}
|
||||
|
||||
$scope['delete'] = function(password) {
|
||||
$http({
|
||||
url: ApiUrl.get() + '/api/v3/user',
|
||||
method: 'DELETE',
|
||||
data: {password: password},
|
||||
})
|
||||
.then(function(res, code) {
|
||||
localStorage.clear();
|
||||
window.location.href = '/logout';
|
||||
});
|
||||
}
|
||||
|
||||
$scope.enterCoupon = function(code) {
|
||||
$http.post(ApiUrl.get() + '/api/v3/coupons/enter/' + code).success(function(res,code){
|
||||
if (code!==200) return;
|
||||
User.sync();
|
||||
Notification.text(env.t('promoCodeApplied'));
|
||||
});
|
||||
}
|
||||
|
||||
$scope.generateCodes = function(codes){
|
||||
$http.post(ApiUrl.get() + '/api/v2/coupons/generate/'+codes.event+'?count='+(codes.count || 1))
|
||||
.success(function(res,code){
|
||||
$scope._codes = {};
|
||||
if (code!==200) return;
|
||||
window.location.href = '/api/v2/coupons?limit='+codes.count+'&_id='+User.user._id+'&apiToken='+User.settings.auth.apiToken;
|
||||
})
|
||||
}
|
||||
|
||||
$scope.clickRelease = function(type, $event){
|
||||
// Close other popovers if they're open
|
||||
$(".release_popover").not($event.target).popover('destroy');
|
||||
|
||||
// Handle clicking on the gem icon
|
||||
if ($event.target.nodeName == "SPAN") {
|
||||
$scope.releasePopoverEl = $($event.target.parentNode);
|
||||
} else {
|
||||
$scope.releasePopoverEl = $($event.target);
|
||||
}
|
||||
|
||||
var html = $compile(
|
||||
'<a ng-controller="SettingsCtrl" ng-click="$close(); releaseAnimals(\'' + type + '\')">' + window.env.t('confirm') + '</a><br/>\n<a ng-click="releaseAnimals()">' + window.env.t('cancel') + '</a><br/>'
|
||||
)($scope);
|
||||
|
||||
$scope.releasePopoverEl.popover('destroy').popover({
|
||||
html: true,
|
||||
placement: 'top',
|
||||
trigger: 'manual',
|
||||
title: window.env.t('confirmPetKey'),
|
||||
content: html
|
||||
}).popover('show');
|
||||
}
|
||||
|
||||
$scope.releaseAnimals = function (type) {
|
||||
$scope.releasePopoverEl.popover('destroy');
|
||||
|
||||
var releaseFunction = RELEASE_ANIMAL_TYPES[type];
|
||||
|
||||
if (releaseFunction) {
|
||||
User[releaseFunction]({});
|
||||
$rootScope.$state.go('tasks');
|
||||
}
|
||||
}
|
||||
|
||||
// ---- Webhooks ------
|
||||
$scope._newWebhook = {url:''};
|
||||
$scope.$watch('user.preferences.webhooks',function(webhooks){
|
||||
$scope.hasWebhooks = _.size(webhooks);
|
||||
})
|
||||
$scope.addWebhook = function(url) {
|
||||
User.addWebhook({body:{url:url, id:Shared.uuid()}});
|
||||
$scope._newWebhook.url = '';
|
||||
}
|
||||
$scope.saveWebhook = function(id,webhook) {
|
||||
delete webhook._editing;
|
||||
User.updateWebhook({params:{id:id}, body:webhook});
|
||||
}
|
||||
$scope.deleteWebhook = function(id) {
|
||||
User.deleteWebhook({params:{id:id}});
|
||||
}
|
||||
|
||||
$scope.applyCoupon = function(coupon){
|
||||
$http.get(ApiUrl.get() + '/api/v3/coupons/validate/'+coupon)
|
||||
.success(function(){
|
||||
Notification.text("Coupon applied!");
|
||||
var subs = Content.subscriptionBlocks;
|
||||
subs["basic_6mo"].discount = true;
|
||||
subs["google_6mo"].discount = false;
|
||||
});
|
||||
}
|
||||
|
||||
$scope.gemGoldCap = function(subscription) {
|
||||
var baseCap = 25;
|
||||
var gemCapIncrement = 5;
|
||||
var capIncrementThreshold = 3;
|
||||
var gemCapExtra = User.user.purchased.plan.consecutive.gemCapExtra;
|
||||
var blocks = Content.subscriptionBlocks[subscription.key].months / capIncrementThreshold;
|
||||
var flooredBlocks = Math.floor(blocks);
|
||||
|
||||
var userTotalDropCap = baseCap + gemCapExtra + flooredBlocks * gemCapIncrement;
|
||||
var maxDropCap = 50;
|
||||
|
||||
return [userTotalDropCap, maxDropCap];
|
||||
};
|
||||
|
||||
$scope.numberOfMysticHourglasses = function(subscription) {
|
||||
var numberOfHourglasses = Content.subscriptionBlocks[subscription.key].months / 3;
|
||||
return Math.floor(numberOfHourglasses);
|
||||
};
|
||||
|
||||
function _calculateNextCron() {
|
||||
$scope.dayStart;
|
||||
|
||||
var nextCron = moment().hours($scope.dayStart).minutes(0).seconds(0).milliseconds(0);
|
||||
|
||||
var currentHour = moment().format('H');
|
||||
if (currentHour >= $scope.dayStart) {
|
||||
nextCron = nextCron.add(1, 'day');;
|
||||
}
|
||||
|
||||
return +nextCron.format('x');
|
||||
}
|
||||
}
|
||||
]);
|
||||
@@ -0,0 +1,20 @@
|
||||
habitrpg.controller('SortableInventoryController', ['$scope',
|
||||
function ($scope) {
|
||||
var attributeSort = {
|
||||
constitution: ['-con', '-(con+int+per+str)'],
|
||||
intelligence: ['-int', '-(con+int+per+str)'],
|
||||
perception: ['-per', '-(con+int+per+str)'],
|
||||
strength: ['-str', '-(con+int+per+str)'],
|
||||
set: 'set'
|
||||
}
|
||||
|
||||
$scope.setOrder = function (order) {
|
||||
$scope.orderChoice = order;
|
||||
if (order in attributeSort) {
|
||||
$scope.order = attributeSort[order];
|
||||
}
|
||||
};
|
||||
|
||||
$scope.orderChoice = 'set';
|
||||
$scope.setOrder($scope.orderChoice);
|
||||
}]);
|
||||
@@ -0,0 +1,338 @@
|
||||
"use strict";
|
||||
|
||||
habitrpg.controller("TasksCtrl", ['$scope', '$rootScope', '$location', 'User','Notification', '$http', 'ApiUrl', '$timeout', 'Content', 'Shared', 'Guide', 'Tasks', 'Analytics',
|
||||
function($scope, $rootScope, $location, User, Notification, $http, ApiUrl, $timeout, Content, Shared, Guide, Tasks, Analytics) {
|
||||
$scope.obj = User.user; // used for task-lists
|
||||
$scope.user = User.user;
|
||||
|
||||
var CTRL_KEYS = [17, 224, 91];
|
||||
|
||||
$scope.armoireCount = function(gear) {
|
||||
return Shared.count.remainingGearInSet(gear, 'armoire');
|
||||
};
|
||||
|
||||
$scope.score = function(task, direction) {
|
||||
switch (task.type) {
|
||||
case 'reward':
|
||||
playRewardSound(task);
|
||||
break;
|
||||
case 'daily':
|
||||
$rootScope.playSound('Daily');
|
||||
break;
|
||||
case 'todo':
|
||||
$rootScope.playSound('ToDo');
|
||||
break;
|
||||
default:
|
||||
if (direction === 'down') $rootScope.playSound('Minus_Habit');
|
||||
else if (direction === 'up') $rootScope.playSound('Plus_Habit');
|
||||
}
|
||||
User.score({params:{task: task, direction:direction}});
|
||||
Analytics.updateUser();
|
||||
Analytics.track({'hitType':'event','eventCategory':'behavior','eventAction':'score task','taskType':task.type,'direction':direction});
|
||||
};
|
||||
|
||||
function addTask(addTo, listDef, tasks) {
|
||||
tasks = _.isArray(tasks) ? tasks : [tasks];
|
||||
|
||||
User.addTask({
|
||||
body: tasks.map(function (task) {
|
||||
return {
|
||||
text: task,
|
||||
type: listDef.type,
|
||||
tags: _.keys(User.user.filters),
|
||||
}
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
$scope.addTask = function(addTo, listDef) {
|
||||
if (listDef.bulk) {
|
||||
var tasks = listDef.newTask.split(/[\n\r]+/);
|
||||
//Reverse the order of tasks so the tasks will appear in the order the user entered them
|
||||
tasks.reverse();
|
||||
addTask(addTo, listDef, tasks);
|
||||
listDef.bulk = false;
|
||||
} else {
|
||||
addTask(addTo, listDef, listDef.newTask);
|
||||
}
|
||||
delete listDef.newTask;
|
||||
delete listDef.focus;
|
||||
if (listDef.type=='daily') Guide.goto('intro', 2);
|
||||
};
|
||||
|
||||
$scope.toggleBulk = function(list) {
|
||||
if (typeof list.bulk === 'undefined') {
|
||||
list.bulk = false;
|
||||
}
|
||||
list.bulk = !list.bulk;
|
||||
list.focus = true;
|
||||
};
|
||||
|
||||
$scope.editTask = Tasks.editTask;
|
||||
|
||||
/**
|
||||
* Add the new task to the actions log
|
||||
*/
|
||||
$scope.clearDoneTodos = function() {
|
||||
Tasks.clearCompletedTodos();
|
||||
};
|
||||
|
||||
/**
|
||||
* Pushes task to top or bottom of list
|
||||
*/
|
||||
$scope.pushTask = function(task, index, location) {
|
||||
var to = (location === 'bottom' || $scope.ctrlPressed) ? -1 : 0;
|
||||
User.sortTask({params:{id: task._id, taskType: task.type}, query:{from:index, to:to}})
|
||||
};
|
||||
|
||||
/**
|
||||
* This is calculated post-change, so task.completed=true if they just checked it
|
||||
*/
|
||||
$scope.changeCheck = function(task) {
|
||||
if (task.completed) {
|
||||
$scope.score(task, "up");
|
||||
} else {
|
||||
$scope.score(task, "down");
|
||||
}
|
||||
};
|
||||
|
||||
$scope.removeTask = function(task) {
|
||||
if (!confirm(window.env.t('sureDelete', {taskType: window.env.t(task.type), taskText: task.text}))) return;
|
||||
User.deleteTask({params:{id: task._id, taskType: task.type}})
|
||||
};
|
||||
|
||||
$scope.saveTask = function(task, stayOpen, isSaveAndClose) {
|
||||
if (task.checklist) {
|
||||
task.checklist = _.filter(task.checklist, function (i) {
|
||||
return !!i.text
|
||||
});
|
||||
}
|
||||
User.updateTask(task, {body: task});
|
||||
if (!stayOpen) task._editing = false;
|
||||
|
||||
if (isSaveAndClose) {
|
||||
$("#task-" + task._id).parent().children('.popover').removeClass('in');
|
||||
}
|
||||
|
||||
if (task.type == 'habit') Guide.goto('intro', 3);
|
||||
};
|
||||
|
||||
/**
|
||||
* Reset $scope.task to $scope.originalTask
|
||||
*/
|
||||
$scope.cancel = function() {
|
||||
var key;
|
||||
for (key in $scope.task) {
|
||||
$scope.task[key] = $scope.originalTask[key];
|
||||
}
|
||||
$scope.originalTask = null;
|
||||
$scope.editedTask = null;
|
||||
$scope.editing = false;
|
||||
};
|
||||
|
||||
$scope.unlink = function(task, keep) {
|
||||
if (keep.search('-all') !== -1) { // unlink all tasks
|
||||
Tasks.unlinkAllTasks(task.challenge.id, keep)
|
||||
.success(function () {
|
||||
User.sync({});
|
||||
});
|
||||
} else { // unlink a task
|
||||
Tasks.unlinkOneTask(task._id, keep)
|
||||
.success(function () {
|
||||
User.sync({});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
------------------------
|
||||
To-Dos
|
||||
------------------------
|
||||
*/
|
||||
$scope._today = moment().add({days: 1});
|
||||
|
||||
$scope.loadedCompletedTodos = function () {
|
||||
if (Tasks.loadedCompletedTodos === true) return;
|
||||
|
||||
Tasks.getUserTasks(true)
|
||||
.then(function (response) {
|
||||
User.user.todos = User.user.todos.concat(response.data.data);
|
||||
Tasks.loadedCompletedTodos = true;
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
------------------------
|
||||
Dailies
|
||||
------------------------
|
||||
*/
|
||||
|
||||
$scope.openDatePicker = function($event, task) {
|
||||
$event.preventDefault();
|
||||
$event.stopPropagation();
|
||||
|
||||
task._isDatePickerOpen = !task._isDatePickerOpen;
|
||||
}
|
||||
|
||||
/*
|
||||
------------------------
|
||||
Checklists
|
||||
------------------------
|
||||
*/
|
||||
function focusChecklist(task,index) {
|
||||
window.setTimeout(function(){
|
||||
$('#task-'+task._id+' .checklist-form input[type="text"]')[index].focus();
|
||||
});
|
||||
}
|
||||
|
||||
$scope.addChecklist = function(task) {
|
||||
task.checklist = [{completed:false, text:""}];
|
||||
focusChecklist(task,0);
|
||||
}
|
||||
|
||||
$scope.addChecklistItem = function(task, $event, $index) {
|
||||
if (task.checklist[$index].text) {
|
||||
$scope.saveTask(task, true);
|
||||
if ($index === task.checklist.length - 1)
|
||||
task.checklist.push({ completed: false, text: '' });
|
||||
focusChecklist(task, $index + 1);
|
||||
} else {
|
||||
// TODO Provide UI feedback that this item is still blank
|
||||
}
|
||||
}
|
||||
|
||||
$scope.removeChecklistItem = function(task, $event, $index, force) {
|
||||
// Remove item if clicked on trash icon
|
||||
if (force) {
|
||||
if (task.checklist[$index].id) Tasks.removeChecklistItem(task._id, task.checklist[$index].id);
|
||||
task.checklist.splice($index, 1);
|
||||
} else if (!task.checklist[$index].text) {
|
||||
// User deleted all the text and is now wishing to delete the item
|
||||
// saveTask will prune the empty item
|
||||
if (task.checklist[$index].id) Tasks.removeChecklistItem(task._id, task.checklist[$index].id);
|
||||
// Move focus if the list is still non-empty
|
||||
if ($index > 0)
|
||||
focusChecklist(task, $index-1);
|
||||
// Don't allow the backspace key to navigate back now that the field is gone
|
||||
$event.preventDefault();
|
||||
}
|
||||
}
|
||||
|
||||
$scope.swapChecklistItems = function(task, oldIndex, newIndex) {
|
||||
var toSwap = task.checklist.splice(oldIndex, 1)[0];
|
||||
task.checklist.splice(newIndex, 0, toSwap);
|
||||
$scope.saveTask(task, true);
|
||||
}
|
||||
|
||||
$scope.navigateChecklist = function(task,$index,$event){
|
||||
focusChecklist(task, $event.keyCode == '40' ? $index+1 : $index-1);
|
||||
}
|
||||
|
||||
$scope.checklistCompletion = function(checklist){
|
||||
return _.reduce(checklist,function(m,i){return m+(i.completed ? 1 : 0);},0)
|
||||
}
|
||||
|
||||
$scope.collapseChecklist = function(task) {
|
||||
task.collapseChecklist = !task.collapseChecklist;
|
||||
$scope.saveTask(task,true);
|
||||
}
|
||||
|
||||
/*
|
||||
------------------------
|
||||
Items
|
||||
------------------------
|
||||
*/
|
||||
|
||||
$scope.$watch('user.items.gear.owned', function(){
|
||||
$scope.itemStore = Shared.updateStore(User.user);
|
||||
},true);
|
||||
|
||||
$scope.healthPotion = Content.potion;
|
||||
$scope.armoire = Content.armoire;
|
||||
|
||||
$scope.buy = function(item) {
|
||||
playRewardSound(item);
|
||||
User.buy({params:{key:item.key}});
|
||||
};
|
||||
|
||||
$scope.buyArmoire = function () {
|
||||
playRewardSound($scope.armoire);
|
||||
User.buyArmoire();
|
||||
}
|
||||
|
||||
/*
|
||||
------------------------
|
||||
Hiding Tasks
|
||||
------------------------
|
||||
*/
|
||||
|
||||
$scope.shouldShow = function(task, list, prefs){
|
||||
if (task._editing) // never hide a task while being edited
|
||||
return true;
|
||||
var shouldDo = task.type == 'daily' ? habitrpgShared.shouldDo(new Date, task, prefs) : true;
|
||||
switch (list.view) {
|
||||
case "yellowred": // Habits
|
||||
return task.value < 1;
|
||||
case "greenblue": // Habits
|
||||
return task.value >= 1;
|
||||
case "remaining": // Dailies and To-Dos
|
||||
return !task.completed && shouldDo;
|
||||
case "complete": // Dailies and To-Dos
|
||||
return task.completed || !shouldDo;
|
||||
case "dated": // To-Dos
|
||||
return !task.completed && task.date;
|
||||
case "ingamerewards": // All skills/rewards except the user's own
|
||||
return false; // Because "rewards" list includes only the user's own
|
||||
case "all":
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
function playRewardSound (task) {
|
||||
if (task.value <= User.user.stats.gp){
|
||||
$rootScope.playSound('Reward');
|
||||
}
|
||||
}
|
||||
|
||||
var isCtrlPressed = function (keyEvent) {
|
||||
if (CTRL_KEYS.indexOf(keyEvent.keyCode) > -1) {
|
||||
$scope.ctrlPressed = true;
|
||||
$scope.$apply();
|
||||
}
|
||||
}
|
||||
|
||||
var isCtrlLetGo = function (keyEvent) {
|
||||
if (CTRL_KEYS.indexOf(keyEvent.keyCode) > -1) {
|
||||
$scope.ctrlPressed = false;
|
||||
$scope.$apply();
|
||||
}
|
||||
}
|
||||
|
||||
$scope.hoverIn = function () {
|
||||
document.addEventListener('keydown', isCtrlPressed);
|
||||
document.addEventListener('keyup', isCtrlLetGo);
|
||||
}
|
||||
|
||||
$scope.hoverOut = function () {
|
||||
$scope.ctrlPressed = false;
|
||||
document.removeEventListener('keydown', isCtrlPressed);
|
||||
document.removeEventListener('keyup', isCtrlLetGo);
|
||||
}
|
||||
|
||||
/*
|
||||
------------------------
|
||||
Tags
|
||||
------------------------
|
||||
*/
|
||||
|
||||
$scope.updateTaskTags = function (tagId, task) {
|
||||
var tagIndex = task.tags.indexOf(tagId);
|
||||
if (tagIndex === -1) {
|
||||
Tasks.addTagToTask(task._id, tagId);
|
||||
task.tags.push(tagId);
|
||||
} else {
|
||||
Tasks.removeTagFromTask(task._id, tagId);
|
||||
task.tags.splice(tagIndex, 1);
|
||||
}
|
||||
}
|
||||
}]);
|
||||
@@ -0,0 +1,18 @@
|
||||
'use strict';
|
||||
|
||||
habitrpg.controller("TavernCtrl", ['$scope', 'Groups', 'User', 'Challenges',
|
||||
function($scope, Groups, User, Challenges) {
|
||||
Groups.tavern()
|
||||
.then(function (tavern) {
|
||||
$scope.group = tavern;
|
||||
Challenges.getGroupChallenges($scope.group._id)
|
||||
.then(function (response) {
|
||||
$scope.group.challenges = response.data.data;
|
||||
});
|
||||
})
|
||||
|
||||
$scope.toggleUserTier = function($event) {
|
||||
$($event.target).next().toggle();
|
||||
}
|
||||
}
|
||||
]);
|
||||
@@ -0,0 +1,87 @@
|
||||
"use strict";
|
||||
|
||||
habitrpg.controller("UserCtrl", ['$rootScope', '$scope', '$location', 'User', '$http', '$state', 'Guide', 'Shared', 'Content', 'Stats', 'Social',
|
||||
function($rootScope, $scope, $location, User, $http, $state, Guide, Shared, Content, Stats, Social) {
|
||||
$scope.profile = User.user;
|
||||
|
||||
$scope.statCalc = Stats;
|
||||
|
||||
$scope.loadWidgets = Social.loadWidgets;
|
||||
|
||||
$scope.hideUserAvatar = function() {
|
||||
$(".userAvatar").hide();
|
||||
};
|
||||
|
||||
$scope.$watch('_editing.profile', function(value){
|
||||
if(value === true) $scope.editingProfile = angular.copy(User.user.profile);
|
||||
});
|
||||
|
||||
$scope.allocate = function(stat){
|
||||
User.allocate({query:{stat:stat}});
|
||||
}
|
||||
|
||||
$scope.changeClass = function(klass){
|
||||
if (!klass) {
|
||||
if (!confirm(window.env.t('sureReset')))
|
||||
return;
|
||||
return User.changeClass({});
|
||||
}
|
||||
|
||||
User.changeClass({query:{class:klass}});
|
||||
$scope.selectedClass = undefined;
|
||||
Shared.updateStore(User.user);
|
||||
Guide.goto('classes', 0,true);
|
||||
}
|
||||
|
||||
$scope.save = function(){
|
||||
var values = {};
|
||||
_.each($scope.editingProfile, function(value, key){
|
||||
// Using toString because we need to compare two arrays (websites)
|
||||
var curVal = $scope.profile.profile[key];
|
||||
if(!curVal || $scope.editingProfile[key].toString() !== curVal.toString())
|
||||
values['profile.' + key] = value;
|
||||
});
|
||||
User.set(values);
|
||||
$scope._editing.profile = false;
|
||||
}
|
||||
|
||||
$scope.acknowledgeHealthWarning = function(){
|
||||
User.set({'flags.warnedLowHealth':true});
|
||||
}
|
||||
|
||||
/**
|
||||
* For gem-unlockable preferences, (a) if owned, select preference (b) else, purchase
|
||||
* @param path: User.preferences <-> User.purchased maps like User.preferences.skin=abc <-> User.purchased.skin.abc.
|
||||
* Pass in this paramater as "skin.abc". Alternatively, pass as an array ["skin.abc", "skin.xyz"] to unlock sets
|
||||
*/
|
||||
$scope.unlock = function(path){
|
||||
var fullSet = ~path.indexOf(',');
|
||||
var cost =
|
||||
~path.indexOf('background.') ?
|
||||
(fullSet ? 3.75 : 1.75) : // (Backgrounds) 15G per set, 7G per individual
|
||||
(fullSet ? 1.25 : 0.5); // (Hair, skin, etc) 5G per set, 2G per individual
|
||||
|
||||
|
||||
if (fullSet) {
|
||||
if (confirm(window.env.t('purchaseFor',{cost:cost*4})) !== true) return;
|
||||
if (User.user.balance < cost) return $rootScope.openModal('buyGems');
|
||||
} else if (!User.user.fns.dotGet('purchased.' + path)) {
|
||||
if (confirm(window.env.t('purchaseFor',{cost:cost*4})) !== true) return;
|
||||
if (User.user.balance < cost) return $rootScope.openModal('buyGems');
|
||||
}
|
||||
User.unlock({query:{path:path}})
|
||||
}
|
||||
|
||||
$scope.ownsSet = function(type,_set) {
|
||||
return !_.find(_set,function(v,k){
|
||||
return !User.user.purchased[type][k];
|
||||
});
|
||||
}
|
||||
$scope.setKeys = function(type,_set){
|
||||
return _.map(_set, function(v,k){
|
||||
return type+'.'+k;
|
||||
}).join(',');
|
||||
}
|
||||
|
||||
}
|
||||
]);
|
||||
@@ -0,0 +1,25 @@
|
||||
'use strict';
|
||||
|
||||
(function(){
|
||||
|
||||
angular
|
||||
.module('habitrpg')
|
||||
.directive('closeMenu', closeMenu);
|
||||
|
||||
function closeMenu() {
|
||||
return {
|
||||
restrict: 'A',
|
||||
link: function($scope, element, attrs) {
|
||||
element.on('click', function(event) {
|
||||
if ($scope.$parent._expandedMenu) {
|
||||
$scope.$parent._expandedMenu.menu = null;
|
||||
}
|
||||
if ($scope._expandedMenu) {
|
||||
$scope._expandedMenu.menu = null;
|
||||
}
|
||||
$scope.$apply()
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}());
|
||||
@@ -0,0 +1,21 @@
|
||||
'use strict';
|
||||
|
||||
(function(){
|
||||
|
||||
angular
|
||||
.module('habitrpg')
|
||||
.directive('expandMenu', expandMenu);
|
||||
|
||||
function expandMenu() {
|
||||
return {
|
||||
restrict: 'A',
|
||||
link: function($scope, element, attrs) {
|
||||
element.on('click', function(event) {
|
||||
$scope._expandedMenu = $scope._expandedMenu || {};
|
||||
$scope._expandedMenu.menu = ($scope._expandedMenu.menu === attrs.menu) ? null : attrs.menu;
|
||||
$scope.$apply()
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}());
|
||||
@@ -0,0 +1,26 @@
|
||||
'use strict';
|
||||
|
||||
(function(){
|
||||
angular
|
||||
.module('habitrpg')
|
||||
.directive('focusElement', focusElement);
|
||||
|
||||
focusElement.$inject = ['$timeout'];
|
||||
|
||||
/**
|
||||
* Directive that places focus on the element it is applied to when the
|
||||
* expression it binds to evaluates to true.
|
||||
*/
|
||||
|
||||
function focusElement($timeout) {
|
||||
return function($scope, elem, attrs) {
|
||||
$scope.$watch(attrs.focusElement, function(newVal) {
|
||||
if (newVal) {
|
||||
$timeout(function() {
|
||||
elem[0].focus();
|
||||
}, 0, false);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}());
|
||||
@@ -0,0 +1,46 @@
|
||||
'use strict';
|
||||
|
||||
(function(){
|
||||
angular
|
||||
.module('habitrpg')
|
||||
.directive('fromNow', fromNow);
|
||||
|
||||
fromNow.$inject = [
|
||||
'$interval',
|
||||
'$timeout'
|
||||
];
|
||||
|
||||
function fromNow($interval, $timeout) {
|
||||
return function($scope, element, attr){
|
||||
var interval, timeout;
|
||||
|
||||
var updateText = function(){
|
||||
element.text(moment($scope.message.timestamp).fromNow());
|
||||
};
|
||||
|
||||
var setupInterval = function() {
|
||||
if(interval) $interval.cancel(interval);
|
||||
if(timeout) $timeout.cancel(timeout);
|
||||
|
||||
var diff = moment().diff($scope.message.timestamp, 'minute');
|
||||
|
||||
if(diff < 60) {
|
||||
// Update every minute
|
||||
interval = $interval(updateText, 60000, false);
|
||||
timeout = $timeout(setupInterval, diff * 60000);
|
||||
} else {
|
||||
// Update every hour
|
||||
interval = $interval(updateText, 3600000, false);
|
||||
}
|
||||
};
|
||||
|
||||
updateText();
|
||||
setupInterval();
|
||||
|
||||
$scope.$on('$destroy', function() {
|
||||
if(interval) $interval.cancel(interval);
|
||||
if(timeout) $timeout.cancel(timeout);
|
||||
});
|
||||
}
|
||||
}
|
||||
}());
|
||||
@@ -0,0 +1,63 @@
|
||||
'use strict';
|
||||
|
||||
(function(){
|
||||
angular
|
||||
.module('habitrpg')
|
||||
.directive('habitrpgTasks', habitrpgTasks);
|
||||
|
||||
habitrpgTasks.$inject = [
|
||||
'$rootScope',
|
||||
'User'
|
||||
];
|
||||
|
||||
function habitrpgTasks($rootScope, User) {
|
||||
return {
|
||||
restrict: 'EA',
|
||||
templateUrl: 'templates/habitrpg-tasks.html',
|
||||
//transclude: true,
|
||||
//scope: {
|
||||
// main: '@', // true if it's the user's main list
|
||||
// obj: '='
|
||||
//},
|
||||
link: function($scope, element, attrs) {
|
||||
// $scope.obj needs to come from controllers, so we can pass by ref
|
||||
$scope.main = attrs.main;
|
||||
$scope.modal = attrs.modal;
|
||||
var dailiesView;
|
||||
if(User.user.preferences.dailyDueDefaultView) {
|
||||
dailiesView = "remaining";
|
||||
} else {
|
||||
dailiesView = "all";
|
||||
}
|
||||
$rootScope.lists = [
|
||||
{
|
||||
header: window.env.t('habits'),
|
||||
type: 'habit',
|
||||
placeHolder: window.env.t('newHabit'),
|
||||
placeHolderBulk: window.env.t('newHabitBulk'),
|
||||
view: "all"
|
||||
}, {
|
||||
header: window.env.t('dailies'),
|
||||
type: 'daily',
|
||||
placeHolder: window.env.t('newDaily'),
|
||||
placeHolderBulk: window.env.t('newDailyBulk'),
|
||||
view: dailiesView
|
||||
}, {
|
||||
header: window.env.t('todos'),
|
||||
type: 'todo',
|
||||
placeHolder: window.env.t('newTodo'),
|
||||
placeHolderBulk: window.env.t('newTodoBulk'),
|
||||
view: "remaining"
|
||||
}, {
|
||||
header: window.env.t('rewards'),
|
||||
type: 'reward',
|
||||
placeHolder: window.env.t('newReward'),
|
||||
placeHolderBulk: window.env.t('newRewardBulk'),
|
||||
view: "all"
|
||||
}
|
||||
];
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}());
|
||||
@@ -0,0 +1,32 @@
|
||||
'use strict';
|
||||
|
||||
(function(){
|
||||
angular
|
||||
.module('habitrpg')
|
||||
.directive('hrpgSortChecklist', hrpgSortChecklist);
|
||||
|
||||
hrpgSortChecklist.$inject = [
|
||||
'User'
|
||||
];
|
||||
|
||||
function hrpgSortChecklist(User) {
|
||||
return function($scope, element, attrs, ngModel) {
|
||||
$(element).sortable({
|
||||
axis: "y",
|
||||
distance: 5,
|
||||
start: function (event, ui) {
|
||||
ui.item.data('startIndex', ui.item.index());
|
||||
},
|
||||
stop: function (event, ui) {
|
||||
var task = angular.element(ui.item[0]).scope().task;
|
||||
var startIndex = ui.item.data('startIndex');
|
||||
$scope.swapChecklistItems(
|
||||
task,
|
||||
startIndex,
|
||||
ui.item.index()
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}());
|
||||
@@ -0,0 +1,29 @@
|
||||
'use strict';
|
||||
|
||||
(function(){
|
||||
angular
|
||||
.module('habitrpg')
|
||||
.directive('hrpgSortTags', hrpgSortTags);
|
||||
|
||||
hrpgSortTags.$inject = [
|
||||
'User'
|
||||
];
|
||||
|
||||
function hrpgSortTags(User) {
|
||||
return function($scope, element, attrs, ngModel) {
|
||||
$(element).sortable({
|
||||
start: function (event, ui) {
|
||||
ui.item.data('startIndex', ui.item.index());
|
||||
},
|
||||
stop: function (event, ui) {
|
||||
User.sortTag({
|
||||
query: {
|
||||
from: ui.item.data('startIndex'),
|
||||
to: ui.item.index()
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}());
|
||||
@@ -0,0 +1,34 @@
|
||||
'use strict';
|
||||
|
||||
(function(){
|
||||
angular
|
||||
.module('habitrpg')
|
||||
.directive('hrpgSortTasks', hrpgSortTasks);
|
||||
|
||||
hrpgSortTasks.$inject = [
|
||||
'User'
|
||||
];
|
||||
|
||||
function hrpgSortTasks(User) {
|
||||
return function($scope, element, attrs, ngModel) {
|
||||
$(element).sortable({
|
||||
axis: "y",
|
||||
distance: 5,
|
||||
start: function (event, ui) {
|
||||
ui.item.data('startIndex', ui.item.index());
|
||||
},
|
||||
stop: function (event, ui) {
|
||||
var task = angular.element(ui.item[0]).scope().task;
|
||||
var startIndex = ui.item.data('startIndex');
|
||||
User.sortTask({
|
||||
params: { id: task._id, taskType: task.type },
|
||||
query: {
|
||||
from: startIndex,
|
||||
to: ui.item.index()
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}());
|
||||
@@ -0,0 +1,47 @@
|
||||
'use strict';
|
||||
|
||||
(function(){
|
||||
angular
|
||||
.module('habitrpg')
|
||||
.directive('popoverHtmlPopup', popoverHtmlPopup)
|
||||
.run(loadPopupTemplate);
|
||||
|
||||
popoverHtmlPopup.$inject = [
|
||||
'$sce'
|
||||
];
|
||||
|
||||
function popoverHtmlPopup($sce) {
|
||||
return {
|
||||
restrict: 'EA',
|
||||
replace: true,
|
||||
scope: { title: '@', content: '@', placement: '@', animation: '&', isOpen: '&' },
|
||||
link: function($scope, element, attrs) {
|
||||
$scope.$watch('content', function(value, oldValue) {
|
||||
$scope.unsafeContent = $sce.trustAsHtml($scope.content);
|
||||
});
|
||||
},
|
||||
templateUrl: 'template/popover/popover-html.html'
|
||||
};
|
||||
}
|
||||
|
||||
/*
|
||||
* TODO: Review whether it's appropriate to be seeding this into the
|
||||
* templateCache like this. Feel like this might be an antipattern?
|
||||
*/
|
||||
|
||||
loadPopupTemplate.$inject = [
|
||||
'$templateCache'
|
||||
];
|
||||
|
||||
function loadPopupTemplate($templateCache) {
|
||||
$templateCache.put("template/popover/popover-html.html",
|
||||
"<div class=\"popover {{placement}}\" ng-class=\"{ in: isOpen(), fade: animation() }\">\n" +
|
||||
" <div class=\"arrow\"></div>\n" +
|
||||
"\n" +
|
||||
" <div class=\"popover-inner\">\n" +
|
||||
" <h3 class=\"popover-title\" ng-bind=\"title\" ng-show=\"title\"></h3>\n" +
|
||||
" <div class=\"popover-content\" ng-bind-html=\"unsafeContent\" style=\"word-wrap: break-word\"> </div>\n" +
|
||||
" </div>\n" +
|
||||
"</div>\n");
|
||||
}
|
||||
}());
|
||||
@@ -0,0 +1,19 @@
|
||||
'use strict';
|
||||
|
||||
(function(){
|
||||
angular
|
||||
.module('habitrpg')
|
||||
.directive('popoverHtml', popoverHtml);
|
||||
|
||||
popoverHtml.$inject = [
|
||||
'$compile',
|
||||
'$timeout',
|
||||
'$parse',
|
||||
'$window',
|
||||
'$tooltip'
|
||||
];
|
||||
|
||||
function popoverHtml($compile, $timeout, $parse, $window, $tooltip) {
|
||||
return $tooltip('popoverHtml', 'popover', 'click');
|
||||
}
|
||||
}());
|
||||
@@ -0,0 +1,19 @@
|
||||
'use strict';
|
||||
|
||||
(function(){
|
||||
angular
|
||||
.module('habitrpg')
|
||||
.directive('whenScrolled', whenScrolled);
|
||||
|
||||
function whenScrolled() {
|
||||
return function($scope, elm, attr) {
|
||||
var raw = elm[0];
|
||||
|
||||
elm.bind('scroll', function() {
|
||||
if (raw.scrollTop + raw.offsetHeight >= raw.scrollHeight) {
|
||||
$scope.$apply(attr.whenScrolled);
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
}());
|
||||
@@ -0,0 +1,16 @@
|
||||
"use strict";
|
||||
|
||||
window.env = window.env || {}; //FIX tests
|
||||
|
||||
// If Moment.js is loaded,
|
||||
if(window.moment && window.env.language && window.env.language.momentLang && window.env.language.momentLangCode){
|
||||
var head = document.getElementsByTagName('head')[0];
|
||||
var script = document.createElement('script');
|
||||
script.type = 'text/javascript';
|
||||
script.text = window.env.language.momentLang;
|
||||
head.appendChild(script);
|
||||
window.moment.locale(window.env.language.momentLangCode);
|
||||
}
|
||||
|
||||
window.habitrpgShared.i18n.strings = window.env.translations;
|
||||
window.env.t = window.habitrpgShared.i18n.t;
|
||||
@@ -0,0 +1,11 @@
|
||||
angular.module('habitrpg')
|
||||
.filter('gold', function () {
|
||||
return function (gp) {
|
||||
return Math.floor(gp);
|
||||
}
|
||||
})
|
||||
.filter('silver', function () {
|
||||
return function (gp) {
|
||||
return Math.floor((gp - Math.floor(gp))*100);
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,30 @@
|
||||
angular.module('habitrpg')
|
||||
.filter('roundLargeNumbers', function(){
|
||||
return function (num) {
|
||||
return _calculateRoundedNumber(num);
|
||||
}
|
||||
});
|
||||
|
||||
function _calculateRoundedNumber(num) {
|
||||
if (num > 999999999) {
|
||||
return _convertToBillion(num);
|
||||
} else if (num > 999999) {
|
||||
return _convertToMillion(num);
|
||||
} else if (num > 999) {
|
||||
return _convertToThousand(num);
|
||||
} else {
|
||||
return num;
|
||||
}
|
||||
}
|
||||
|
||||
function _convertToThousand(num) {
|
||||
return (num / Math.pow(10, 3)).toFixed(1) + "k";
|
||||
}
|
||||
|
||||
function _convertToMillion(num) {
|
||||
return (num / Math.pow(10, 6)).toFixed(1) + "m";
|
||||
}
|
||||
|
||||
function _convertToBillion(num) {
|
||||
return (num / Math.pow(10, 9)).toFixed(1) + "b";
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
angular.module('habitrpg')
|
||||
.filter('conditionalOrderBy', ['$filter', function($filter) {
|
||||
return function (array, predicate, sortPredicate, reverseOrder) {
|
||||
if (predicate) {
|
||||
return $filter('orderBy')(array, sortPredicate, reverseOrder);
|
||||
}
|
||||
return array;
|
||||
};
|
||||
}])
|
||||
.filter('filterByTaskInfo', ['$filter', function($filter) {
|
||||
return function (tasks, term) {
|
||||
if (!tasks) return;
|
||||
|
||||
if (!angular.isString(term) || term.legth === 0) {
|
||||
return tasks;
|
||||
}
|
||||
|
||||
term = new RegExp(term, 'i');
|
||||
|
||||
var result = [];
|
||||
|
||||
for (var i = 0; i < tasks.length; i++) {
|
||||
var checklist = tasks[i].checklist;
|
||||
if (term.test(tasks[i].text) || term.test(tasks[i].notes)) {
|
||||
result.push(tasks[i]);
|
||||
} else if (checklist) {
|
||||
var found = _.find(checklist, function(box) { return term.test(box.text); });
|
||||
if (found) { result.push(tasks[i]) }
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
}]);
|
||||
@@ -0,0 +1,15 @@
|
||||
angular.module('habitrpg')
|
||||
.filter('timezoneOffsetToUtc', function () {
|
||||
return function (offset) {
|
||||
var sign = offset > 0 ? '-' : '+';
|
||||
|
||||
offset = Math.abs(offset) / 60;
|
||||
|
||||
var hour = Math.floor(offset);
|
||||
|
||||
var minutes_int = (offset - hour) * 60;
|
||||
var minutes = minutes_int < 10 ? '0'+minutes_int : minutes_int;
|
||||
|
||||
return 'UTC' + sign + hour + ':' + minutes;
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,136 @@
|
||||
'use strict';
|
||||
|
||||
(function(){
|
||||
var REQUIRED_FIELDS = ['hitType','eventCategory','eventAction'];
|
||||
var ALLOWED_HIT_TYPES = ['pageview','screenview','event','transaction','item','social','exception','timing'];
|
||||
|
||||
angular
|
||||
.module('habitrpg')
|
||||
.factory('Analytics', analyticsFactory);
|
||||
|
||||
analyticsFactory.$inject = [
|
||||
'User'
|
||||
];
|
||||
|
||||
function analyticsFactory(User) {
|
||||
|
||||
var user = User.user;
|
||||
|
||||
// Amplitude
|
||||
var r = window.amplitude || {};
|
||||
r._q = [];
|
||||
function a(window) {r[window] = function() {r._q.push([window].concat(Array.prototype.slice.call(arguments, 0)));}}
|
||||
var i = ["init", "logEvent", "logRevenue", "setUserId", "setUserProperties", "setOptOut", "setVersionName", "setDomain", "setDeviceId", "setGlobalUserProperties"];
|
||||
for (var o = 0; o < i.length; o++) {a(i[o])}
|
||||
window.amplitude = r;
|
||||
amplitude.init(window.env.AMPLITUDE_KEY, user ? user._id : undefined);
|
||||
|
||||
// Google Analytics (aka Universal Analytics)
|
||||
window['GoogleAnalyticsObject'] = 'ga';
|
||||
window['ga'] = window['ga'] || function() {
|
||||
(window['ga'].q = window['ga'].q || []).push(arguments)
|
||||
}, window['ga'].l = 1 * new Date();
|
||||
ga('create', window.env.GA_ID, user ? {'userId': user._id} : undefined);
|
||||
|
||||
function loadScripts() {
|
||||
setTimeout(function() {
|
||||
// Amplitude
|
||||
var n = document.createElement("script");
|
||||
var s = document.getElementsByTagName("script")[0];
|
||||
n.type = "text/javascript";
|
||||
n.async = true;
|
||||
n.src = "https://d24n15hnbwhuhn.cloudfront.net/libs/amplitude-2.2.0-min.gz.js";
|
||||
s.parentNode.insertBefore(n, s);
|
||||
|
||||
// Google Analytics
|
||||
var a = document.createElement('script');
|
||||
var m = document.getElementsByTagName('script')[0];
|
||||
a.async = 1;
|
||||
a.src = '//www.google-analytics.com/analytics.js';
|
||||
m.parentNode.insertBefore(a, m);
|
||||
});
|
||||
}
|
||||
|
||||
function register() {
|
||||
setTimeout(function() {
|
||||
amplitude.setUserId(user._id);
|
||||
ga('set', {'userId':user._id});
|
||||
});
|
||||
}
|
||||
|
||||
function login() {
|
||||
setTimeout(function() {
|
||||
amplitude.setUserId(user._id);
|
||||
ga('set', {'userId':user._id});
|
||||
});
|
||||
}
|
||||
|
||||
function track(properties) {
|
||||
setTimeout(function() {
|
||||
if(_doesNotHaveRequiredFields(properties)) { return false; }
|
||||
if(_doesNotHaveAllowedHitType(properties)) { return false; }
|
||||
|
||||
amplitude.logEvent(properties.eventAction,properties);
|
||||
ga('send',properties);
|
||||
});
|
||||
}
|
||||
|
||||
function updateUser(properties) {
|
||||
setTimeout(function() {
|
||||
properties = properties || {};
|
||||
|
||||
_gatherUserStats(user, properties);
|
||||
|
||||
amplitude.setUserProperties(properties);
|
||||
ga('set',properties);
|
||||
});
|
||||
}
|
||||
|
||||
if (window.env.NODE_ENV === 'production') loadScripts();
|
||||
|
||||
return {
|
||||
loadScripts: loadScripts,
|
||||
register: register,
|
||||
login: login,
|
||||
track: track,
|
||||
updateUser: updateUser
|
||||
};
|
||||
}
|
||||
|
||||
function _gatherUserStats(user, properties) {
|
||||
if (user._id) properties.UUID = user._id;
|
||||
if (user.stats) {
|
||||
properties.Class = user.stats.class;
|
||||
properties.Experience = Math.floor(user.stats.exp);
|
||||
properties.Gold = Math.floor(user.stats.gp);
|
||||
properties.Health = Math.ceil(user.stats.hp);
|
||||
properties.Level = user.stats.lvl;
|
||||
properties.Mana = Math.floor(user.stats.mp);
|
||||
}
|
||||
properties.tutorialComplete = user.flags && user.flags.tour && user.flags.tour.intro === -2;
|
||||
if (user.habits && user.dailys && user.todos && user.rewards) {
|
||||
properties["Number Of Tasks"] = {
|
||||
habits: user.habits.length,
|
||||
dailys: user.dailys.length,
|
||||
todos: user.todos.length,
|
||||
rewards: user.rewards.length
|
||||
};
|
||||
}
|
||||
if (user.contributor && user.contributor.level) properties.contributorLevel = user.contributor.level;
|
||||
if (user.purchased && user.purchased.plan.planId) properties.subscription = user.purchased.plan.planId;
|
||||
}
|
||||
|
||||
function _doesNotHaveRequiredFields(properties) {
|
||||
if (!_.isEqual(_.keys(_.pick(properties, REQUIRED_FIELDS)), REQUIRED_FIELDS)) {
|
||||
console.log('Analytics tracking calls must include the following properties: ' + JSON.stringify(REQUIRED_FIELDS));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
function _doesNotHaveAllowedHitType(properties) {
|
||||
if (!_.contains(ALLOWED_HIT_TYPES, properties.hitType)) {
|
||||
console.log('Hit type of Analytics event must be one of the following: ' + JSON.stringify(ALLOWED_HIT_TYPES));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}());
|
||||
@@ -0,0 +1,99 @@
|
||||
'use strict';
|
||||
|
||||
angular.module('habitrpg')
|
||||
.factory('Challenges', ['ApiUrl', '$resource', '$http',
|
||||
function(ApiUrl, $resource, $http) {
|
||||
var apiV3Prefix = '/api/v3';
|
||||
|
||||
function createChallenge (challengeData) {
|
||||
return $http({
|
||||
method: 'POST',
|
||||
url: apiV3Prefix + '/challenges',
|
||||
data: challengeData,
|
||||
});
|
||||
}
|
||||
|
||||
function joinChallenge (challengeId) {
|
||||
return $http({
|
||||
method: 'POST',
|
||||
url: apiV3Prefix + '/challenges/' + challengeId + '/join',
|
||||
});
|
||||
}
|
||||
|
||||
function leaveChallenge (challengeId, keep) {
|
||||
return $http({
|
||||
method: 'POST',
|
||||
url: apiV3Prefix + '/challenges/' + challengeId + '/leave',
|
||||
data: {
|
||||
keep: keep,
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function getUserChallenges () {
|
||||
return $http({
|
||||
method: 'GET',
|
||||
url: apiV3Prefix + '/challenges/user',
|
||||
});
|
||||
}
|
||||
|
||||
function getGroupChallenges (groupId) {
|
||||
return $http({
|
||||
method: 'GET',
|
||||
url: apiV3Prefix + '/challenges/groups/' + groupId,
|
||||
});
|
||||
}
|
||||
|
||||
function getChallenge (challengeId) {
|
||||
return $http({
|
||||
method: 'GET',
|
||||
url: apiV3Prefix + '/challenges/' + challengeId,
|
||||
});
|
||||
}
|
||||
|
||||
function exportChallengeCsv (challengeId) {
|
||||
return $http({
|
||||
method: 'GET',
|
||||
url: apiV3Prefix + '/challenges/' + challengeId + '/export/csv',
|
||||
});
|
||||
}
|
||||
|
||||
function updateChallenge (challengeId, updateData) {
|
||||
|
||||
var challengeDataToSend = _.omit(updateData, ['tasks', 'habits', 'todos', 'rewards', 'group']);
|
||||
if (challengeDataToSend.leader && challengeDataToSend.leader._id) challengeDataToSend.leader = challengeDataToSend.leader._id;
|
||||
|
||||
return $http({
|
||||
method: 'PUT',
|
||||
url: apiV3Prefix + '/challenges/' + challengeId,
|
||||
data: challengeDataToSend,
|
||||
});
|
||||
}
|
||||
|
||||
function deleteChallenge (challengeId) {
|
||||
return $http({
|
||||
method: 'DELETE',
|
||||
url: apiV3Prefix + '/challenges/' + challengeId,
|
||||
});
|
||||
}
|
||||
|
||||
function selectChallengeWinner (challengeId, winnerId) {
|
||||
return $http({
|
||||
method: 'POST',
|
||||
url: apiV3Prefix + '/challenges/' + challengeId + '/selectWinner/' + winnerId,
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
createChallenge: createChallenge,
|
||||
joinChallenge: joinChallenge,
|
||||
leaveChallenge: leaveChallenge,
|
||||
getUserChallenges: getUserChallenges,
|
||||
getGroupChallenges: getGroupChallenges,
|
||||
getChallenge: getChallenge,
|
||||
exportChallengeCsv: exportChallengeCsv,
|
||||
updateChallenge: updateChallenge,
|
||||
deleteChallenge: deleteChallenge,
|
||||
selectChallengeWinner: selectChallengeWinner,
|
||||
}
|
||||
}]);
|
||||
@@ -0,0 +1,87 @@
|
||||
'use strict';
|
||||
|
||||
angular.module('habitrpg')
|
||||
.factory('Chat', ['$http', 'ApiUrl', 'User',
|
||||
function($http, ApiUrl, User) {
|
||||
var apiV3Prefix = '/api/v3';
|
||||
|
||||
function getChat (groupId) {
|
||||
return $http({
|
||||
method: 'GET',
|
||||
url: apiV3Prefix + '/groups/' + groupId + '/chat',
|
||||
});
|
||||
}
|
||||
|
||||
function postChat (groupId, message, previousMsg) {
|
||||
var url = apiV3Prefix + '/groups/' + groupId + '/chat';
|
||||
|
||||
if (previousMsg) {
|
||||
url += '?previousMsg=' + previousMsg;
|
||||
}
|
||||
|
||||
return $http({
|
||||
method: 'POST',
|
||||
url: url,
|
||||
data: {
|
||||
message: message,
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function deleteChat (groupId, chatId, previousMsg) {
|
||||
var url = apiV3Prefix + '/groups/' + groupId + '/chat/' + chatId;
|
||||
|
||||
if (previousMsg) {
|
||||
url += '?previousMsg=' + previousMsg;
|
||||
}
|
||||
|
||||
return $http({
|
||||
method: 'DELETE',
|
||||
url: url,
|
||||
});
|
||||
}
|
||||
|
||||
function like (groupId, chatId) {
|
||||
return $http({
|
||||
method: 'POST',
|
||||
url: apiV3Prefix + '/groups/' + groupId + '/chat/' + chatId + '/like',
|
||||
});
|
||||
}
|
||||
|
||||
function flagChatMessage (groupId, chatId) {
|
||||
return $http({
|
||||
method: 'POST',
|
||||
url: apiV3Prefix + '/groups/' + groupId + '/chat/' + chatId + '/flag',
|
||||
});
|
||||
}
|
||||
|
||||
function clearFlagCount (groupId, chatId) {
|
||||
return $http({
|
||||
method: 'POST',
|
||||
url: apiV3Prefix + '/groups/' + groupId + '/chat/' + chatId + '/clearflags',
|
||||
});
|
||||
}
|
||||
|
||||
function markChatSeen (groupId) {
|
||||
if (User.user.newMessages) delete User.user.newMessages[groupId];
|
||||
return $http({
|
||||
method: 'POST',
|
||||
url: apiV3Prefix + '/groups/' + groupId + '/chat/seen',
|
||||
});
|
||||
}
|
||||
|
||||
function clearCards () {
|
||||
User.user._wrapped && User.set({'flags.cardReceived':false});
|
||||
}
|
||||
|
||||
return {
|
||||
getChat: getChat,
|
||||
postChat: postChat,
|
||||
deleteChat: deleteChat,
|
||||
like: like,
|
||||
flagChatMessage: flagChatMessage,
|
||||
clearFlagCount: clearFlagCount,
|
||||
markChatSeen: markChatSeen,
|
||||
clearCards: clearCards,
|
||||
}
|
||||
}]);
|
||||
@@ -0,0 +1,234 @@
|
||||
'use strict';
|
||||
|
||||
angular.module('habitrpg')
|
||||
.factory('Groups', [ '$location', '$rootScope', '$http', 'Analytics', 'ApiUrl', 'Challenges', '$q', 'User', 'Members',
|
||||
function($location, $rootScope, $http, Analytics, ApiUrl, Challenges, $q, User, Members) {
|
||||
var data = {party: undefined, myGuilds: undefined, publicGuilds: undefined, tavern: undefined };
|
||||
var groupApiURLPrefix = "/api/v3/groups";
|
||||
var TAVERN_NAME = 'HabitRPG';
|
||||
|
||||
var Group = {};
|
||||
|
||||
//@TODO: Add paging
|
||||
Group.getGroups = function(type) {
|
||||
var url = groupApiURLPrefix;
|
||||
if (type) {
|
||||
url += '?type=' + type;
|
||||
}
|
||||
|
||||
return $http({
|
||||
method: 'GET',
|
||||
url: url,
|
||||
});
|
||||
};
|
||||
|
||||
Group.get = function(gid) {
|
||||
return $http({
|
||||
method: 'GET',
|
||||
url: groupApiURLPrefix + '/' + gid,
|
||||
});
|
||||
};
|
||||
|
||||
Group.syncParty = function() {
|
||||
return party();
|
||||
};
|
||||
|
||||
Group.create = function(groupDetails) {
|
||||
return $http({
|
||||
method: "POST",
|
||||
url: groupApiURLPrefix,
|
||||
data: groupDetails,
|
||||
});
|
||||
};
|
||||
|
||||
Group.update = function(groupDetails) {
|
||||
//@TODO: Check for what has changed?
|
||||
|
||||
//Remove populated fields
|
||||
var groupDetailsToSend = _.omit(groupDetails, ['chat', 'challenges', 'members', 'invites']);
|
||||
if (groupDetailsToSend.leader && groupDetailsToSend.leader._id) groupDetailsToSend.leader = groupDetailsToSend.leader._id;
|
||||
|
||||
return $http({
|
||||
method: "PUT",
|
||||
url: groupApiURLPrefix + '/' + groupDetailsToSend._id,
|
||||
data: groupDetailsToSend,
|
||||
});
|
||||
};
|
||||
|
||||
Group.join = function(gid) {
|
||||
return $http({
|
||||
method: "POST",
|
||||
url: groupApiURLPrefix + '/' + gid + '/join',
|
||||
});
|
||||
};
|
||||
|
||||
Group.rejectInvite = function(gid) {
|
||||
return $http({
|
||||
method: "POST",
|
||||
url: groupApiURLPrefix + '/' + gid + '/reject-invite',
|
||||
});
|
||||
};
|
||||
|
||||
Group.leave = function(gid, keep) {
|
||||
return $http({
|
||||
method: "POST",
|
||||
url: groupApiURLPrefix + '/' + gid + '/leave',
|
||||
data: {
|
||||
keep: keep,
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
Group.removeMember = function(gid, memberId, message) {
|
||||
return $http({
|
||||
method: "POST",
|
||||
url: groupApiURLPrefix + '/' + gid + '/removeMember/' + memberId,
|
||||
data: {
|
||||
message: message,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
Group.invite = function(gid, invitationDetails) {
|
||||
return $http({
|
||||
method: "POST",
|
||||
url: groupApiURLPrefix + '/' + gid + '/invite',
|
||||
data: {
|
||||
uuids: invitationDetails.uuids,
|
||||
emails: invitationDetails.emails,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
Group.inviteToQuest = function(gid, key) {
|
||||
return $http({
|
||||
method: "POST",
|
||||
url: groupApiURLPrefix + '/' + gid + '/quests/invite/' + key,
|
||||
});
|
||||
};
|
||||
|
||||
//On page load, multiple controller request the party.
|
||||
//So, we cache the promise until the first result is returned
|
||||
var _cachedPartyPromise;
|
||||
function party (forceUpdate) {
|
||||
if (_cachedPartyPromise && !forceUpdate) return _cachedPartyPromise.promise;
|
||||
_cachedPartyPromise = $q.defer();
|
||||
|
||||
if (!User.user.party._id) {
|
||||
data.party = { type: 'party' };
|
||||
_cachedPartyPromise.reject(data.party);
|
||||
}
|
||||
|
||||
if (!data.party || forceUpdate) {
|
||||
Group.get('party')
|
||||
.then(function (response) {
|
||||
data.party = response.data.data;
|
||||
Members.getGroupMembers(data.party._id, true)
|
||||
.then(function (response) {
|
||||
data.party.members = response.data.data;
|
||||
return Members.getGroupInvites(data.party._id);
|
||||
})
|
||||
.then(function (response) {
|
||||
data.party.invites = response.data.data;
|
||||
return Challenges.getGroupChallenges(data.party._id)
|
||||
})
|
||||
.then(function (response) {
|
||||
data.party.challenges = response.data.data;
|
||||
_cachedPartyPromise.resolve(data.party);
|
||||
});
|
||||
}, function (response) {
|
||||
data.party = { type: 'party' };
|
||||
_cachedPartyPromise.reject(data.party);
|
||||
})
|
||||
.finally(function() {
|
||||
_cachePartyPromise = null;
|
||||
});
|
||||
} else {
|
||||
_cachedPartyPromise.resolve(data.party);
|
||||
}
|
||||
|
||||
return _cachedPartyPromise.promise;
|
||||
}
|
||||
|
||||
function publicGuilds () {
|
||||
var deferred = $q.defer();
|
||||
|
||||
if (!data.publicGuilds) {
|
||||
Group.getGroups('publicGuilds')
|
||||
.then(function (response) {
|
||||
data.publicGuilds = response.data.data;
|
||||
deferred.resolve(data.publicGuilds);
|
||||
}, function (response) {
|
||||
deferred.reject(response);
|
||||
});
|
||||
} else {
|
||||
deferred.resolve(data.publicGuilds);
|
||||
}
|
||||
|
||||
return deferred.promise;
|
||||
//TODO combine these as {type:'guilds,public'} and create a $filter() to separate them
|
||||
}
|
||||
|
||||
function myGuilds () {
|
||||
var deferred = $q.defer();
|
||||
|
||||
if (!data.myGuilds) {
|
||||
Group.getGroups('guilds')
|
||||
.then(function (response) {
|
||||
data.myGuilds = response.data.data;
|
||||
deferred.resolve(data.myGuilds);
|
||||
}, function (response) {
|
||||
deferred.reject(response);
|
||||
});
|
||||
} else {
|
||||
deferred.resolve(data.myGuilds);
|
||||
}
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
function tavern (forceUpdate) {
|
||||
var deferred = $q.defer();
|
||||
|
||||
if (!data.tavern || forceUpdate) {
|
||||
Group.get('habitrpg')
|
||||
.then(function (response) {
|
||||
data.tavern = response.data.data;
|
||||
deferred.resolve(data.tavern);
|
||||
}, function (response) {
|
||||
deferred.reject(response);
|
||||
});
|
||||
} else {
|
||||
deferred.resolve(data.tavern);
|
||||
}
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
function inviteOrStartParty (group) {
|
||||
Analytics.track({'hitType':'event','eventCategory':'button','eventAction':'click','eventLabel':'Invite Friends'});
|
||||
if (group.type === "party" || $location.$$path === "/options/groups/party") {
|
||||
group.type = 'party';
|
||||
$rootScope.openModal('invite-party', {
|
||||
controller:'InviteToGroupCtrl',
|
||||
resolve: {
|
||||
injectedGroup: function(){ return group; }
|
||||
}
|
||||
});
|
||||
} else {
|
||||
$location.path("/options/groups/party");
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
TAVERN_NAME: TAVERN_NAME,
|
||||
party: party,
|
||||
publicGuilds: publicGuilds,
|
||||
myGuilds: myGuilds,
|
||||
tavern: tavern,
|
||||
inviteOrStartParty: inviteOrStartParty,
|
||||
|
||||
data: data,
|
||||
Group: Group,
|
||||
};
|
||||
}]);
|
||||
@@ -0,0 +1,310 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Services for each tour step when you unlock features
|
||||
*/
|
||||
|
||||
angular.module('habitrpg').factory('Guide',
|
||||
['$rootScope', 'User', '$timeout', '$state', 'Analytics',
|
||||
function($rootScope, User, $timeout, $state, Analytics) {
|
||||
|
||||
var chapters = {
|
||||
intro: [
|
||||
[
|
||||
{
|
||||
state: 'options.profile.avatar',
|
||||
element: '.tab-content.ng-scope',
|
||||
content: window.env.t('tourAvatar'),
|
||||
placement: "top",
|
||||
proceed: window.env.t('tourAvatarProceed'),
|
||||
backdrop: false,
|
||||
orphan: true,
|
||||
gold: 4,
|
||||
experience: 29
|
||||
},
|
||||
{
|
||||
state: 'tasks',
|
||||
element: ".task-column.todos",
|
||||
content: window.env.t('tourToDosBrief'),
|
||||
placement: "top",
|
||||
proceed: window.env.t('tourOkay'),
|
||||
gold: 4,
|
||||
experience: 29
|
||||
},
|
||||
{
|
||||
state: 'tasks',
|
||||
element: ".task-column.dailys",
|
||||
content: window.env.t('tourDailiesBrief'),
|
||||
placement: "top",
|
||||
proceed: window.env.t('tourDailiesProceed'),
|
||||
gold: 4,
|
||||
experience: 29
|
||||
},
|
||||
{
|
||||
state: 'tasks',
|
||||
element: ".task-column.habits",
|
||||
content: window.env.t('tourHabitsBrief'),
|
||||
placement: "top",
|
||||
proceed: window.env.t('tourHabitsProceed'),
|
||||
gold: 4,
|
||||
experience: 29
|
||||
},
|
||||
{
|
||||
state: 'tasks',
|
||||
element: "h2.task-column_title.reward-title",
|
||||
content: window.env.t('tourRewardsBrief'),
|
||||
placement: "left",
|
||||
proceed: window.env.t('tourRewardsProceed'),
|
||||
gold: 4,
|
||||
experience: 29,
|
||||
final: true
|
||||
}
|
||||
]
|
||||
],
|
||||
classes: [
|
||||
[
|
||||
{
|
||||
state: 'options.inventory.equipment',
|
||||
element: '.equipment-tab',
|
||||
title: window.env.t('classGear'),
|
||||
content: window.env.t('classGearText')
|
||||
}, {
|
||||
state: 'options.profile.stats',
|
||||
element: ".allocate-stats",
|
||||
title: window.env.t('stats'),
|
||||
content: window.env.t('classStats')
|
||||
}, {
|
||||
state: 'options.profile.stats',
|
||||
element: ".auto-allocate",
|
||||
title: window.env.t('autoAllocate'),
|
||||
placement: 'left',
|
||||
content: window.env.t('autoAllocateText')
|
||||
}, {
|
||||
element: ".meter.mana",
|
||||
title: window.env.t('spells'),
|
||||
content: window.env.t('spellsText') + " <a target='_blank' href='http://habitica.wikia.com/wiki/Todos'>" + window.env.t('toDo') + "</a>."
|
||||
}, {
|
||||
orphan: true,
|
||||
title: window.env.t('readMore'),
|
||||
content: window.env.t('moreClass') + " <a href='http://habitica.wikia.com/wiki/Class_System' target='_blank'>Wikia</a>.",
|
||||
final: true
|
||||
}
|
||||
]
|
||||
],
|
||||
stats: [[
|
||||
{
|
||||
orphan: true,
|
||||
content: window.env.t('tourStatsPage'),
|
||||
final: true,
|
||||
proceed: window.env.t('tourOkay'),
|
||||
hideNavigation: true
|
||||
}
|
||||
]],
|
||||
tavern: [[
|
||||
{
|
||||
orphan: true,
|
||||
content: window.env.t('tourTavernPage'),
|
||||
final: true,
|
||||
proceed: window.env.t('tourAwesome'),
|
||||
hideNavigation: true
|
||||
}
|
||||
]],
|
||||
party: [[
|
||||
{
|
||||
orphan: true,
|
||||
content: window.env.t('tourPartyPage'),
|
||||
final: true,
|
||||
proceed: window.env.t('tourSplendid'),
|
||||
hideNavigation: true
|
||||
}
|
||||
]],
|
||||
guilds: [[
|
||||
{
|
||||
orphan: true,
|
||||
content: window.env.t('tourGuildsPage'),
|
||||
final: true,
|
||||
proceed: window.env.t('tourNifty'),
|
||||
hideNavigation: true
|
||||
}
|
||||
]],
|
||||
challenges: [[
|
||||
{
|
||||
orphan: true,
|
||||
content: window.env.t('tourChallengesPage'),
|
||||
final: true,
|
||||
proceed: window.env.t('tourOkay'),
|
||||
hideNavigation: true
|
||||
}
|
||||
]],
|
||||
market: [[
|
||||
{
|
||||
orphan: true,
|
||||
content: window.env.t('tourMarketPage'),
|
||||
final: true,
|
||||
proceed: window.env.t('tourAwesome'),
|
||||
hideNavigation: true
|
||||
}
|
||||
]],
|
||||
hall: [[
|
||||
{
|
||||
orphan: true,
|
||||
content: window.env.t('tourHallPage'),
|
||||
final: true,
|
||||
proceed: window.env.t('tourSplendid'),
|
||||
hideNavigation: true
|
||||
}
|
||||
]],
|
||||
pets: [[
|
||||
{
|
||||
orphan: true,
|
||||
content: window.env.t('tourPetsPage'),
|
||||
final: true,
|
||||
proceed: window.env.t('tourNifty'),
|
||||
hideNavigation: true
|
||||
}
|
||||
]],
|
||||
mounts: [[
|
||||
{
|
||||
orphan: true,
|
||||
content: window.env.t('tourMountsPage'),
|
||||
final: true,
|
||||
proceed: window.env.t('tourOkay'),
|
||||
hideNavigation: true
|
||||
}
|
||||
]],
|
||||
equipment: [[
|
||||
{
|
||||
orphan: true,
|
||||
content: window.env.t('tourEquipmentPage'),
|
||||
final: true,
|
||||
proceed: window.env.t('tourAwesome'),
|
||||
hideNavigation: true
|
||||
}
|
||||
]]
|
||||
}
|
||||
|
||||
_.each(chapters, function(chapter, k){
|
||||
_(chapter).flattenDeep().each(function(step, i) {
|
||||
step.content = "<div><div class='" + (env.worldDmg.guide ? "npc_justin_broken" : "npc_justin") + " float-left'></div>" + step.content + "</div>";
|
||||
$(step.element).popover('destroy'); // destroy existing hover popovers so we can add our own
|
||||
step.onShow = function(){
|
||||
Analytics.track({'hitType':'event','eventCategory':'behavior','eventAction':'tutorial','eventLabel':k+'-web','eventValue':i+1,'complete':false});
|
||||
if (step.state && !$state.is(step.state)) {
|
||||
$state.go(step.state);
|
||||
return $timeout(function(){});
|
||||
}
|
||||
};
|
||||
step.onHide = function(){
|
||||
var ups={};
|
||||
if (!$rootScope.stepAwarded) $rootScope.stepAwarded = {};
|
||||
if (!$rootScope.stepAwarded[i]) {
|
||||
$rootScope.stepAwarded[i] = true;
|
||||
ups['stats.gp'] = User.user.stats.gp + (step.gold || 0);
|
||||
ups['stats.exp'] = User.user.stats.exp + (step.experience || 0);
|
||||
}
|
||||
if (step.final) { // -2 indicates complete
|
||||
ups['flags.tour.'+k] = -2;
|
||||
$rootScope.stepAwarded = null;
|
||||
Analytics.track({'hitType':'event','eventCategory':'behavior','eventAction':'tutorial','eventLabel':k+'-web','eventValue':i+1,'complete':true})
|
||||
}
|
||||
User.set(ups);
|
||||
}
|
||||
}).value();
|
||||
});
|
||||
|
||||
var tour = {};
|
||||
_.each(chapters, function(v,k){
|
||||
tour[k] = new Tour({
|
||||
name: k,
|
||||
backdrop: true,
|
||||
template: function(i,step){
|
||||
var showFinish = step.final || k == 'classes';
|
||||
var showCounter = k=='intro' && !step.final;
|
||||
|
||||
return '<div class="popover" role="tooltip">' +
|
||||
'<div class="arrow"></div>' +
|
||||
'<h3 class="popover-title"></h3>' +
|
||||
'<div class="popover-content"></div>' +
|
||||
'<div class="popover-navigation"> ' +
|
||||
(showCounter ? '<span style="float:right;">'+ (i+1 +' of '+ _.flattenDeep(chapters[k]).length) +'</span>' : '')+ // counter
|
||||
'<div class="btn-group">' +
|
||||
(step.hideNavigation ? '' : '<button class="btn btn-sm btn-default" data-role="prev">« Previous</button>') +
|
||||
(showFinish ? ('<button class="btn btn-sm btn-primary" data-role="end" style="float:none;">' + (step.proceed ? step.proceed : "Finish Tour") + '</button>') :
|
||||
(step.hideNavigation ? '' : ('<button class="btn btn-sm btn-primary" data-role="next">' + (step.proceed ? step.proceed : "Next") + ' »</button>'))) +
|
||||
'<button class="btn btn-sm btn-default" data-role="pause-resume" data-pause-text="Pause" data-resume-text="Resume">Pause</button>' +
|
||||
'</div>' +
|
||||
'</div>' +
|
||||
'</div>';
|
||||
},
|
||||
storage: false
|
||||
});
|
||||
});
|
||||
|
||||
var goto = function(chapter, page, force) {
|
||||
if (chapter == 'intro' && User.user.flags.welcomed != true) User.set({'flags.welcomed': true});
|
||||
if (page === -1) page = 0;
|
||||
var curr = User.user.flags.tour[chapter];
|
||||
if (page != curr+1 && !force) return;
|
||||
var updates = {};updates['flags.tour.'+chapter] = page;
|
||||
User.set(updates);
|
||||
var chap = tour[chapter], opts = chap._options;
|
||||
opts.steps = [];
|
||||
_.times(page, function(p){
|
||||
opts.steps = opts.steps.concat(chapters[chapter][p]);
|
||||
})
|
||||
var end = opts.steps.length;
|
||||
opts.steps = opts.steps.concat(chapters[chapter][page]);
|
||||
chap._removeState('end');
|
||||
if (chap._inited) {
|
||||
chap.goTo(end);
|
||||
} else {
|
||||
chap.setCurrentStep(end);
|
||||
chap.start();
|
||||
}
|
||||
}
|
||||
|
||||
//Init and show the welcome tour (only after user is pulled from server & wrapped).
|
||||
var watcher = $rootScope.$watch('User.user._wrapped', function(wrapped){
|
||||
if (!wrapped) return; // only run after user has been wrapped
|
||||
watcher(); // deregister watcher
|
||||
if (window.env.IS_MOBILE) return; // Don't show tour immediately on mobile devices
|
||||
if (User.user.flags.welcomed == false) {
|
||||
$rootScope.openModal('welcome', {size: 'lg', backdrop: 'static', keyboard: false});
|
||||
}
|
||||
|
||||
var alreadyShown = function(before, after) { return !(!before && after === true) };
|
||||
//$rootScope.$watch('user.flags.dropsEnabled', _.flow(alreadyShown, function(already) { //FIXME requires lodash@~3.2.0
|
||||
$rootScope.$on('$stateChangeSuccess', function(event, toState, toParams, fromState, fromParams){
|
||||
switch (toState.name) {
|
||||
// case 'options.profile.avatar': return goto('intro', 5);
|
||||
case 'options.profile.stats': return goto('stats', 0);
|
||||
case 'options.social.tavern': return goto('tavern', 0);
|
||||
case 'options.social.party': return goto('party', 0);
|
||||
case 'options.social.guilds.public': return goto('guilds', 0);
|
||||
case 'options.social.challenges': return goto('challenges', 0);
|
||||
case 'options.social.hall.heroes': return goto('hall', 0);
|
||||
case 'options.inventory.drops': return goto('market', 0);
|
||||
case 'options.inventory.pets': return goto('pets', 0);
|
||||
case 'options.inventory.mounts': return goto('mounts', 0);
|
||||
case 'options.inventory.equipment': return goto('equipment', 0);
|
||||
}
|
||||
});
|
||||
$rootScope.$watch('user.flags.dropsEnabled', function(after, before) {
|
||||
if (alreadyShown(before,after)) return;
|
||||
var eggs = User.user.items.eggs || {};
|
||||
if (!eggs) eggs['Wolf'] = 1; // This is also set on the server
|
||||
$rootScope.openModal('dropsEnabled');
|
||||
});
|
||||
$rootScope.$watch('user.flags.rebirthEnabled', function(after, before) {
|
||||
if (alreadyShown(before, after)) return;
|
||||
$rootScope.openModal('rebirthEnabled');
|
||||
});
|
||||
});
|
||||
|
||||
var Guide = {
|
||||
goto: goto
|
||||
};
|
||||
$rootScope.Guide = Guide;
|
||||
return Guide;
|
||||
|
||||
}]);
|
||||
@@ -0,0 +1,41 @@
|
||||
'use strict';
|
||||
|
||||
angular.module('habitrpg')
|
||||
.factory('Hall', [ '$rootScope', 'ApiUrl', '$http',
|
||||
function($rootScope, ApiUrl, $http) {
|
||||
var apiV3Prefix = '/api/v3';
|
||||
var Hall = {};
|
||||
|
||||
Hall.getHeroes = function () {
|
||||
return $http({
|
||||
method: 'GET',
|
||||
url: apiV3Prefix + '/hall/heroes',
|
||||
});
|
||||
}
|
||||
|
||||
Hall.getHero = function (uuid) {
|
||||
return $http({
|
||||
method: 'GET',
|
||||
url: apiV3Prefix + '/hall/heroes/' + uuid,
|
||||
});
|
||||
}
|
||||
|
||||
Hall.updateHero = function (heroDetails) {
|
||||
return $http({
|
||||
method: 'PUT',
|
||||
url: apiV3Prefix + '/hall/heroes/' + heroDetails._id,
|
||||
data: heroDetails,
|
||||
});
|
||||
}
|
||||
|
||||
Hall.getPatrons = function (page) {
|
||||
if (!page) page = 0;
|
||||
|
||||
return $http({
|
||||
method: 'GET',
|
||||
url: apiV3Prefix + '/hall/patrons?page=' + page,
|
||||
});
|
||||
}
|
||||
|
||||
return Hall;
|
||||
}]);
|
||||
@@ -0,0 +1,128 @@
|
||||
'use strict';
|
||||
|
||||
angular.module('habitrpg')
|
||||
.factory('Members', [ '$rootScope', 'Shared', 'ApiUrl', '$http', '$q',
|
||||
function($rootScope, Shared, ApiUrl, $http, $q) {
|
||||
var members = {};
|
||||
var selectedMember = {};
|
||||
var apiV3Prefix = '/api/v3';
|
||||
|
||||
function fetchMember (memberId) {
|
||||
return $http({
|
||||
method: 'GET',
|
||||
url: apiV3Prefix + '/members/' + memberId,
|
||||
});
|
||||
}
|
||||
|
||||
//@TODO: Add paging
|
||||
function getGroupMembers (groupId, includeAllPublicFields) {
|
||||
var url = apiV3Prefix + '/groups/' + groupId + '/members';
|
||||
|
||||
if (includeAllPublicFields) {
|
||||
url += '?includeAllPublicFields=true';
|
||||
}
|
||||
|
||||
return $http({
|
||||
method: 'GET',
|
||||
url: url,
|
||||
});
|
||||
}
|
||||
|
||||
function getGroupInvites (groupId) {
|
||||
return $http({
|
||||
method: 'GET',
|
||||
url: apiV3Prefix + '/groups/' + groupId + '/invites',
|
||||
});
|
||||
}
|
||||
|
||||
function getChallengeMembers (challengeId) {
|
||||
return $http({
|
||||
method: 'GET',
|
||||
url: apiV3Prefix + '/challenges/' + challengeId + '/members?includeAllMembers=true',
|
||||
});
|
||||
}
|
||||
|
||||
function getChallengeMemberProgress (challengeId, memberId) {
|
||||
return $http({
|
||||
method: 'GET',
|
||||
url: apiV3Prefix + '/challenges/' + challengeId + '/members/' + memberId,
|
||||
});
|
||||
}
|
||||
|
||||
function sendPrivateMessage (message, toUserId) {
|
||||
return $http({
|
||||
method: 'POST',
|
||||
url: apiV3Prefix + '/members/send-private-message',
|
||||
data: {
|
||||
message: message,
|
||||
toUserId: toUserId,
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function transferGems (message, toUserId, gemAmount) {
|
||||
return $http({
|
||||
method: 'POST',
|
||||
url: apiV3Prefix + '/members/transfer-gems',
|
||||
data: {
|
||||
message: message,
|
||||
toUserId: toUserId,
|
||||
gemAmount: gemAmount,
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function selectMember (uid) {
|
||||
var self = this;
|
||||
var deferred = $q.defer();
|
||||
var memberIsReady = _checkIfMemberIsReady(members[uid]);
|
||||
|
||||
if (memberIsReady) {
|
||||
_prepareMember(members[uid], self);
|
||||
deferred.resolve();
|
||||
} else {
|
||||
fetchMember(uid)
|
||||
.then(function (response) {
|
||||
var member = response.data.data;
|
||||
addToMembersList(member); // lazy load for later
|
||||
_prepareMember(member, self);
|
||||
deferred.resolve();
|
||||
});
|
||||
}
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
function addToMembersList (member) {
|
||||
if (member._id) {
|
||||
members[member._id] = member;
|
||||
}
|
||||
}
|
||||
|
||||
function _checkIfMemberIsReady (member) {
|
||||
return member && member.items && member.items.weapon;
|
||||
}
|
||||
|
||||
function _prepareMember(member, self) {
|
||||
Shared.wrap(member, false);
|
||||
self.selectedMember = members[member._id];
|
||||
}
|
||||
|
||||
$rootScope.$on('userUpdated', function(event, user){
|
||||
addToMembersList(user);
|
||||
})
|
||||
|
||||
return {
|
||||
members: members,
|
||||
addToMembersList: addToMembersList,
|
||||
selectedMember: undefined,
|
||||
selectMember: selectMember,
|
||||
fetchMember: fetchMember,
|
||||
getGroupMembers: getGroupMembers,
|
||||
getGroupInvites: getGroupInvites,
|
||||
getChallengeMembers: getChallengeMembers,
|
||||
getChallengeMemberProgress: getChallengeMemberProgress,
|
||||
sendPrivateMessage: sendPrivateMessage,
|
||||
transferGems: transferGems,
|
||||
}
|
||||
}]);
|
||||
@@ -0,0 +1,146 @@
|
||||
'use strict'
|
||||
/**
|
||||
Set up "+1 Exp", "Level Up", etc notifications
|
||||
*/
|
||||
angular.module("habitrpg").factory("Notification",
|
||||
['$filter', function($filter) {
|
||||
|
||||
/**
|
||||
Show "+ 5 {gold_coin} 3 {silver_coin}"
|
||||
*/
|
||||
function coins(money) {
|
||||
var absolute, gold, silver;
|
||||
absolute = Math.abs(money);
|
||||
gold = Math.floor(absolute);
|
||||
silver = Math.floor((absolute - gold) * 100);
|
||||
if (gold && silver > 0) {
|
||||
return "" + gold + " <span class='notification-icon shop_gold'></span> " + silver + " <span class='notification-icon shop_silver'></span>";
|
||||
} else if (gold > 0) {
|
||||
return "" + gold + " <span class='notification-icon shop_gold'></span>";
|
||||
} else if (silver > 0) {
|
||||
return "" + silver + " <span class='notification-icon shop_silver'></span>";
|
||||
}
|
||||
}
|
||||
|
||||
function crit(val) {
|
||||
_notify(window.env.t('critBonus') + Math.round(val) + "%", 'crit', 'glyphicon glyphicon-certificate');
|
||||
}
|
||||
|
||||
function drop(val, item) {
|
||||
var dropClass = "";
|
||||
if ( item !== undefined ) {
|
||||
switch ( item.type ) {
|
||||
case "Egg":
|
||||
dropClass = 'Pet_Egg_' + item.key;
|
||||
break;
|
||||
case "HatchingPotion":
|
||||
dropClass = 'Pet_HatchingPotion_' + item.key;
|
||||
break;
|
||||
case "Food":
|
||||
dropClass = 'Pet_Food_' + item.key;
|
||||
break;
|
||||
case "armor":
|
||||
case "back":
|
||||
case "body":
|
||||
case "eyewear":
|
||||
case "head":
|
||||
case "headAccessory":
|
||||
case "shield":
|
||||
case "weapon":
|
||||
dropClass = 'shop_' + item.key;
|
||||
break;
|
||||
default:
|
||||
dropClass = 'glyphicon glyphicon-gift';
|
||||
}
|
||||
}
|
||||
_notify(val, 'drop', dropClass);
|
||||
}
|
||||
|
||||
function exp(val) {
|
||||
if (val < -50) return; // don't show when they level up (resetting their exp)
|
||||
_notify(_sign(val) + " " + _round(val) + " " + window.env.t('experience'), 'xp', 'glyphicon glyphicon-star');
|
||||
}
|
||||
|
||||
function error(error, canHide){
|
||||
_notify(error, "danger", 'glyphicon glyphicon-exclamation-sign', canHide);
|
||||
}
|
||||
|
||||
function gp(val, bonus) {
|
||||
_notify(_sign(val) + " " + coins(val - bonus), 'gp');
|
||||
}
|
||||
|
||||
function hp(val) {
|
||||
// don't show notifications if user dead
|
||||
_notify(_sign(val) + " " + _round(val) + " " + window.env.t('health'), 'hp', 'glyphicon glyphicon-heart');
|
||||
}
|
||||
|
||||
function lvl(){
|
||||
_notify(window.env.t('levelUp'), 'lvl', 'glyphicon glyphicon-chevron-up');
|
||||
}
|
||||
|
||||
function markdown(val){
|
||||
if (val) {
|
||||
var parsed_markdown = $filter("markdown")(val);
|
||||
_notify(parsed_markdown, 'info');
|
||||
}
|
||||
}
|
||||
|
||||
function mp(val) {
|
||||
_notify(_sign(val) + " " + _round(val) + " " + window.env.t('mana'), 'mp', 'glyphicon glyphicon-fire');
|
||||
}
|
||||
|
||||
function streak(val) {
|
||||
_notify(window.env.t('streakName') + ': ' + val, 'streak', 'glyphicon glyphicon-repeat');
|
||||
}
|
||||
|
||||
function text(val){
|
||||
if (val) {
|
||||
_notify(val, 'info');
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------
|
||||
// Private Methods
|
||||
//--------------------------------------------------
|
||||
|
||||
function _sign(number){
|
||||
return number?number<0?'-':'+':'+';
|
||||
}
|
||||
|
||||
function _round(number){
|
||||
return Math.abs(number.toFixed(1));
|
||||
}
|
||||
|
||||
// Used to stack notifications, must be outside of _notify
|
||||
var stack_topright = {"dir1": "down", "dir2": "left", "spacing1": 15, "spacing2": 15, "firstpos1": 60};
|
||||
|
||||
function _notify(html, type, icon, canHide) {
|
||||
var notice = $.pnotify({
|
||||
type: type || 'warning', //('info', 'text', 'warning', 'success', 'gp', 'xp', 'hp', 'lvl', 'death', 'mp', 'crit')
|
||||
text: html,
|
||||
opacity: 1,
|
||||
addclass: 'alert-' + type,
|
||||
delay: 7000,
|
||||
hide: ((type == 'error' || type == 'danger') && !canHide) ? false : true,
|
||||
mouse_reset: false,
|
||||
width: "250px",
|
||||
stack: stack_topright,
|
||||
icon: icon || false
|
||||
}).click(function() { notice.pnotify_remove() });
|
||||
}
|
||||
|
||||
return {
|
||||
coins: coins,
|
||||
crit: crit,
|
||||
drop: drop,
|
||||
exp: exp,
|
||||
error: error,
|
||||
gp: gp,
|
||||
hp: hp,
|
||||
lvl: lvl,
|
||||
markdown: markdown,
|
||||
mp: mp,
|
||||
streak: streak,
|
||||
text: text
|
||||
};
|
||||
}]);
|
||||
@@ -0,0 +1,275 @@
|
||||
'use strict';
|
||||
|
||||
angular.module('habitrpg').factory('Payments',
|
||||
['$rootScope', 'User', '$http', 'Content',
|
||||
function($rootScope, User, $http, Content) {
|
||||
var Payments = {};
|
||||
var isAmazonReady = false;
|
||||
|
||||
window.onAmazonLoginReady = function(){
|
||||
isAmazonReady = true;
|
||||
amazon.Login.setClientId(window.env.AMAZON_PAYMENTS.CLIENT_ID);
|
||||
};
|
||||
|
||||
Payments.showStripe = function(data) {
|
||||
var sub =
|
||||
data.subscription ? data.subscription
|
||||
: data.gift && data.gift.type=='subscription' ? data.gift.subscription.key
|
||||
: false;
|
||||
sub = sub && Content.subscriptionBlocks[sub];
|
||||
var amount = // 500 = $5
|
||||
sub ? sub.price*100
|
||||
: data.gift && data.gift.type=='gems' ? data.gift.gems.amount/4*100
|
||||
: 500;
|
||||
StripeCheckout.open({
|
||||
key: window.env.STRIPE_PUB_KEY,
|
||||
address: false,
|
||||
amount: amount,
|
||||
name: 'Habitica',
|
||||
description: sub ? window.env.t('subscribe') : window.env.t('checkout'),
|
||||
image: "/apple-touch-icon-144-precomposed.png",
|
||||
panelLabel: sub ? window.env.t('subscribe') : window.env.t('checkout'),
|
||||
token: function(res) {
|
||||
var url = '/stripe/checkout?a=a'; // just so I can concat &x=x below
|
||||
if (data.gift) url += '&gift=' + Payments.encodeGift(data.uuid, data.gift);
|
||||
if (data.subscription) url += '&sub='+sub.key;
|
||||
if (data.coupon) url += '&coupon='+data.coupon;
|
||||
$http.post(url, res).success(function() {
|
||||
window.location.reload(true);
|
||||
}).error(function(res) {
|
||||
alert(res.message);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Payments.showStripeEdit = function(){
|
||||
StripeCheckout.open({
|
||||
key: window.env.STRIPE_PUB_KEY,
|
||||
address: false,
|
||||
name: window.env.t('subUpdateTitle'),
|
||||
description: window.env.t('subUpdateDescription'),
|
||||
panelLabel: window.env.t('subUpdateCard'),
|
||||
token: function(data) {
|
||||
var url = '/stripe/subscribe/edit';
|
||||
$http.post(url, data).success(function() {
|
||||
window.location.reload(true);
|
||||
}).error(function(data) {
|
||||
alert(data.message);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
var amazonOnError = function(error){
|
||||
console.error(error);
|
||||
console.log(error.getErrorMessage(), error.getErrorCode());
|
||||
alert(error.getErrorMessage());
|
||||
Payments.amazonPayments.reset();
|
||||
};
|
||||
|
||||
Payments.amazonPayments = {};
|
||||
|
||||
Payments.amazonPayments.reset = function(){
|
||||
Payments.amazonPayments.modal.close();
|
||||
Payments.amazonPayments.modal = null;
|
||||
Payments.amazonPayments.type = null;
|
||||
Payments.amazonPayments.loggedIn = false;
|
||||
Payments.amazonPayments.gift = null;
|
||||
Payments.amazonPayments.billingAgreementId = null;
|
||||
Payments.amazonPayments.orderReferenceId = null;
|
||||
Payments.amazonPayments.paymentSelected = false;
|
||||
Payments.amazonPayments.recurringConsent = false;
|
||||
Payments.amazonPayments.subscription = null;
|
||||
Payments.amazonPayments.coupon = null;
|
||||
};
|
||||
|
||||
// Needs to be called everytime the modal/router is accessed
|
||||
Payments.amazonPayments.init = function(data){
|
||||
if(!isAmazonReady) return;
|
||||
if(data.type !== 'single' && data.type !== 'subscription') return;
|
||||
|
||||
if(data.gift){
|
||||
if(data.gift.gems && data.gift.gems.amount && data.gift.gems.amount <= 0) return;
|
||||
data.gift.uuid = data.giftedTo;
|
||||
}
|
||||
|
||||
if(data.subscription){
|
||||
Payments.amazonPayments.subscription = data.subscription;
|
||||
Payments.amazonPayments.coupon = data.coupon;
|
||||
}
|
||||
|
||||
Payments.amazonPayments.gift = data.gift;
|
||||
Payments.amazonPayments.type = data.type;
|
||||
|
||||
var modal = Payments.amazonPayments.modal = $rootScope.openModal('amazonPayments', {
|
||||
// Allow the modal to be closed only by pressing cancel
|
||||
// because no easy method to intercept those types of closings
|
||||
// and we need to make some cleanup
|
||||
keyboard: false,
|
||||
backdrop: 'static'
|
||||
});
|
||||
|
||||
modal.rendered.then(function(){
|
||||
OffAmazonPayments.Button('AmazonPayButton', window.env.AMAZON_PAYMENTS.SELLER_ID, {
|
||||
type: 'PwA',
|
||||
color: 'Gold',
|
||||
size: 'small',
|
||||
agreementType: 'BillingAgreement',
|
||||
|
||||
onSignIn: function(contract){
|
||||
Payments.amazonPayments.billingAgreementId = contract.getAmazonBillingAgreementId();
|
||||
|
||||
if(Payments.amazonPayments.type === 'subscription'){
|
||||
Payments.amazonPayments.loggedIn = true;
|
||||
Payments.amazonPayments.initWidgets();
|
||||
}else{
|
||||
var url = '/amazon/createOrderReferenceId'
|
||||
$http.post(url, {
|
||||
billingAgreementId: Payments.amazonPayments.billingAgreementId
|
||||
}).success(function(res){
|
||||
Payments.amazonPayments.loggedIn = true;
|
||||
Payments.amazonPayments.orderReferenceId = res.data.orderReferenceId;
|
||||
Payments.amazonPayments.initWidgets();
|
||||
}).error(function(res){
|
||||
alert(res.message);
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
authorization: function(){
|
||||
amazon.Login.authorize({
|
||||
scope: 'payments:widget',
|
||||
popup: true
|
||||
}, function(response){
|
||||
if(response.error) return alert(response.error);
|
||||
|
||||
var url = '/amazon/verifyAccessToken'
|
||||
$http.post(url, response).error(function(res){
|
||||
alert(res.message);
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
onError: amazonOnError
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
Payments.amazonPayments.canCheckout = function(){
|
||||
if(Payments.amazonPayments.type === 'single'){
|
||||
return Payments.amazonPayments.paymentSelected === true;
|
||||
}else if(Payments.amazonPayments.type === 'subscription'){
|
||||
return Payments.amazonPayments.paymentSelected === true &&
|
||||
// Mah.. one is a boolean the other a string...
|
||||
Payments.amazonPayments.recurringConsent === 'true';
|
||||
}else{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Payments.amazonPayments.initWidgets = function(){
|
||||
var walletParams = {
|
||||
sellerId: window.env.AMAZON_PAYMENTS.SELLER_ID,
|
||||
design: {
|
||||
designMode: 'responsive'
|
||||
},
|
||||
|
||||
onPaymentSelect: function() {
|
||||
$rootScope.$apply(function(){
|
||||
Payments.amazonPayments.paymentSelected = true;
|
||||
});
|
||||
},
|
||||
|
||||
onError: amazonOnError
|
||||
}
|
||||
|
||||
if(Payments.amazonPayments.type === 'subscription'){
|
||||
walletParams.agreementType = 'BillingAgreement';
|
||||
console.log(Payments.amazonPayments.billingAgreementId);
|
||||
walletParams.billingAgreementId = Payments.amazonPayments.billingAgreementId;
|
||||
walletParams.onReady = function(billingAgreement){
|
||||
Payments.amazonPayments.billingAgreementId = billingAgreement.getAmazonBillingAgreementId();
|
||||
|
||||
new OffAmazonPayments.Widgets.Consent({
|
||||
sellerId: window.env.AMAZON_PAYMENTS.SELLER_ID,
|
||||
amazonBillingAgreementId: Payments.amazonPayments.billingAgreementId,
|
||||
design: {
|
||||
designMode: 'responsive'
|
||||
},
|
||||
|
||||
onReady: function(consent){
|
||||
$rootScope.$apply(function(){
|
||||
var getConsent = consent.getConsentStatus
|
||||
Payments.amazonPayments.recurringConsent = getConsent ? getConsent() : false;
|
||||
});
|
||||
},
|
||||
|
||||
onConsent: function(consent){
|
||||
$rootScope.$apply(function(){
|
||||
Payments.amazonPayments.recurringConsent = consent.getConsentStatus();
|
||||
});
|
||||
},
|
||||
|
||||
onError: amazonOnError
|
||||
}).bind('AmazonPayRecurring');
|
||||
}
|
||||
}else{
|
||||
walletParams.amazonOrderReferenceId = Payments.amazonPayments.orderReferenceId;
|
||||
}
|
||||
|
||||
new OffAmazonPayments.Widgets.Wallet(walletParams).bind('AmazonPayWallet');
|
||||
}
|
||||
|
||||
Payments.amazonPayments.checkout = function(){
|
||||
if(Payments.amazonPayments.type === 'single'){
|
||||
var url = '/amazon/checkout';
|
||||
$http.post(url, {
|
||||
orderReferenceId: Payments.amazonPayments.orderReferenceId,
|
||||
gift: Payments.amazonPayments.gift
|
||||
}).success(function(){
|
||||
Payments.amazonPayments.reset();
|
||||
window.location.reload(true);
|
||||
}).error(function(res){
|
||||
alert(res.message);
|
||||
Payments.amazonPayments.reset();
|
||||
});
|
||||
}else if(Payments.amazonPayments.type === 'subscription'){
|
||||
var url = '/amazon/subscribe';
|
||||
|
||||
$http.post(url, {
|
||||
billingAgreementId: Payments.amazonPayments.billingAgreementId,
|
||||
subscription: Payments.amazonPayments.subscription,
|
||||
coupon: Payments.amazonPayments.coupon
|
||||
}).success(function(){
|
||||
Payments.amazonPayments.reset();
|
||||
window.location.reload(true);
|
||||
}).error(function(res){
|
||||
alert(res.message);
|
||||
Payments.amazonPayments.reset();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Payments.cancelSubscription = function(){
|
||||
if (!confirm(window.env.t('sureCancelSub'))) return;
|
||||
var paymentMethod = User.user.purchased.plan.paymentMethod;
|
||||
|
||||
if(paymentMethod === 'Amazon Payments'){
|
||||
paymentMethod = 'amazon';
|
||||
}else{
|
||||
paymentMethod = paymentMethod.toLowerCase();
|
||||
}
|
||||
|
||||
window.location.href = '/' + paymentMethod + '/subscribe/cancel?_id=' + User.user._id + '&apiToken=' + User.settings.auth.apiToken;
|
||||
}
|
||||
|
||||
Payments.encodeGift = function(uuid, gift){
|
||||
gift.uuid = uuid;
|
||||
var encodedString = JSON.stringify(gift);
|
||||
return encodeURIComponent(encodedString);
|
||||
}
|
||||
|
||||
return Payments;
|
||||
}]);
|
||||
@@ -0,0 +1,138 @@
|
||||
'use strict';
|
||||
|
||||
angular.module('habitrpg')
|
||||
.factory('Quests', ['$http', '$state','$q', 'ApiUrl', 'Content', 'Groups', 'User', 'Analytics',
|
||||
function questsFactory($http, $state, $q, ApiUrl, Content, Groups, User, Analytics) {
|
||||
|
||||
var user = User.user;
|
||||
var party;
|
||||
|
||||
Groups.party()
|
||||
.then(function (partyFound) {
|
||||
party = partyFound;
|
||||
});
|
||||
|
||||
function lockQuest(quest,ignoreLevel) {
|
||||
if (!ignoreLevel){
|
||||
if (quest.lvl && user.stats.lvl < quest.lvl) return true;
|
||||
}
|
||||
if (user.achievements.quests) return (quest.previous && !user.achievements.quests[quest.previous]);
|
||||
return (quest.previous);
|
||||
}
|
||||
|
||||
function _preventQuestModal(quest) {
|
||||
if (!quest) {
|
||||
return 'No quest with that key found';
|
||||
}
|
||||
|
||||
if (quest.previous && (!user.achievements.quests || (user.achievements.quests && !user.achievements.quests[quest.previous]))){
|
||||
alert(window.env.t('unlockByQuesting', {title: Content.quests[quest.previous].text()}));
|
||||
return 'unlockByQuesting';
|
||||
}
|
||||
|
||||
if (quest.lvl > user.stats.lvl) {
|
||||
alert(window.env.t('mustLvlQuest', {level: quest.lvl}))
|
||||
return 'mustLvlQuest';
|
||||
}
|
||||
}
|
||||
|
||||
function buyQuest(quest) {
|
||||
return $q(function(resolve, reject) {
|
||||
var item = Content.quests[quest];
|
||||
|
||||
var preventQuestModal = _preventQuestModal(item);
|
||||
if (preventQuestModal) {
|
||||
return reject(preventQuestModal);
|
||||
}
|
||||
|
||||
if (item.unlockCondition && item.unlockCondition.condition === 'party invite') {
|
||||
if (!confirm(window.env.t('mustInviteFriend'))) return reject('Did not want to invite friends');
|
||||
Groups.inviteOrStartParty(party)
|
||||
return reject('Invite or start party');
|
||||
}
|
||||
|
||||
resolve(item);
|
||||
});
|
||||
}
|
||||
|
||||
function questPopover(quest) {
|
||||
// The popover gets parsed as markdown (hence the double \n for line breaks
|
||||
var text = '';
|
||||
if(quest.boss) {
|
||||
text += '**' + window.env.t('bossHP') + ':** ' + quest.boss.hp + '\n\n';
|
||||
text += '**' + window.env.t('bossStrength') + ':** ' + quest.boss.str + '\n\n';
|
||||
} else if(quest.collect) {
|
||||
var count = 0;
|
||||
for (var key in quest.collect) {
|
||||
text += '**' + window.env.t('collect') + ':** ' + quest.collect[key].count + ' ' + quest.collect[key].text() + '\n\n';
|
||||
}
|
||||
}
|
||||
text += '---\n\n';
|
||||
text += '**' + window.env.t('rewards') + ':**\n\n';
|
||||
if(quest.drop.items) {
|
||||
for (var item in quest.drop.items) {
|
||||
text += quest.drop.items[item].text() + '\n\n';
|
||||
}
|
||||
}
|
||||
if(quest.drop.exp)
|
||||
text += quest.drop.exp + ' ' + window.env.t('experience') + '\n\n';
|
||||
if(quest.drop.gp)
|
||||
text += quest.drop.gp + ' ' + window.env.t('gold') + '\n\n';
|
||||
|
||||
return text;
|
||||
}
|
||||
|
||||
function showQuest(quest) {
|
||||
return $q(function(resolve, reject) {
|
||||
var item = Content.quests[quest];
|
||||
|
||||
var preventQuestModal = _preventQuestModal(item);
|
||||
if (preventQuestModal) {
|
||||
return reject(preventQuestModal);
|
||||
}
|
||||
|
||||
resolve(item);
|
||||
});
|
||||
}
|
||||
|
||||
function initQuest(key) {
|
||||
return $q(function(resolve, reject) {
|
||||
Analytics.track({'hitType':'event', 'eventCategory':'behavior', 'eventAction':'quest', 'owner':true, 'response':'accept', 'questName': key});
|
||||
Analytics.updateUser({'partyID': party._id, 'partySize': party.memberCount});
|
||||
Groups.Group.inviteToQuest(party._id, key)
|
||||
.then(function(response) {
|
||||
party.quest = response.data.data;
|
||||
Groups.data.party = party;
|
||||
$state.go('options.social.party');
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function sendAction(action) {
|
||||
return $q(function(resolve, reject) {
|
||||
$http.post(ApiUrl.get() + '/api/v3/groups/' + party._id + '/' + action)
|
||||
.then(function(response) {
|
||||
User.sync();
|
||||
|
||||
Analytics.updateUser({
|
||||
partyID: party._id,
|
||||
partySize: party.memberCount
|
||||
});
|
||||
|
||||
var quest = response.data.quest;
|
||||
if (!quest) quest = response.data.data;
|
||||
resolve(quest);
|
||||
});;
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
lockQuest: lockQuest,
|
||||
buyQuest: buyQuest,
|
||||
questPopover: questPopover,
|
||||
sendAction: sendAction,
|
||||
showQuest: showQuest,
|
||||
initQuest: initQuest
|
||||
}
|
||||
}]);
|
||||
@@ -0,0 +1,13 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Services that expose habitrpg-shared
|
||||
*/
|
||||
|
||||
angular.module('habitrpg')
|
||||
.factory('Shared', [function () {
|
||||
return window.habitrpgShared;
|
||||
}])
|
||||
.factory('Content', ['Shared', function (Shared) {
|
||||
return Shared.content;
|
||||
}]);
|
||||
@@ -0,0 +1,41 @@
|
||||
'use strict';
|
||||
|
||||
(function(){
|
||||
angular
|
||||
.module('habitrpg')
|
||||
.factory('Social', socialFactory);
|
||||
|
||||
socialFactory.$inject = [];
|
||||
|
||||
function socialFactory() {
|
||||
|
||||
function loadWidgets() {
|
||||
// Facebook
|
||||
if (typeof FB === 'undefined') {
|
||||
(function(d, s, id) {
|
||||
var js, fjs = d.getElementsByTagName(s)[0];
|
||||
if (d.getElementById(id)) return;
|
||||
js = d.createElement(s); js.id = id;
|
||||
js.src = "//connect.facebook.net/en_US/sdk.js#xfbml=1&version=v2.5";
|
||||
fjs.parentNode.insertBefore(js, fjs);
|
||||
}(document, 'script', 'facebook-jssdk'));
|
||||
} else {
|
||||
FB.XFBML.parse(); // http://stackoverflow.com/questions/29133563/
|
||||
}
|
||||
|
||||
// Tumblr
|
||||
$.getScript('https://assets.tumblr.com/share-button.js');
|
||||
|
||||
// Twitter
|
||||
if (typeof twttr === 'undefined') {
|
||||
$.getScript('https://platform.twitter.com/widgets.js');
|
||||
} else {
|
||||
twttr.widgets.load();
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
loadWidgets: loadWidgets
|
||||
}
|
||||
}
|
||||
}());
|
||||
@@ -0,0 +1,128 @@
|
||||
'use strict';
|
||||
|
||||
(function(){
|
||||
angular
|
||||
.module('habitrpg')
|
||||
.factory('Stats', statsFactory);
|
||||
|
||||
statsFactory.$inject = [
|
||||
'Content',
|
||||
'Shared'
|
||||
];
|
||||
|
||||
function statsFactory(Content, Shared) {
|
||||
var DROP_ANIMALS = _.keys(Content.pets);
|
||||
var TOTAL_NUMBER_OF_DROP_ANIMALS = DROP_ANIMALS.length;
|
||||
|
||||
function beastMasterProgress(pets) {
|
||||
var dropPetsFound = Shared.count.beastMasterProgress(pets);
|
||||
var display = _formatOutOfTotalDisplay(dropPetsFound, TOTAL_NUMBER_OF_DROP_ANIMALS);
|
||||
|
||||
return display;
|
||||
}
|
||||
|
||||
function classBonus(user, stat) {
|
||||
var computedStats = (user.fns && user.fns.statsComputed) ? user.fns.statsComputed() : null;
|
||||
|
||||
if(computedStats) {
|
||||
var bonus = computedStats[stat]
|
||||
- user.stats.buffs[stat]
|
||||
- levelBonus(user.stats.lvl)
|
||||
- equipmentStatBonus(stat, user.items.gear.equipped)
|
||||
- user.stats[stat];
|
||||
|
||||
return bonus;
|
||||
}
|
||||
}
|
||||
|
||||
function equipmentStatBonus(stat, equipped) {
|
||||
var gear = Content.gear.flat;
|
||||
var total = 0;
|
||||
|
||||
var equipmentTypes = ['weapon', 'armor', 'head', 'shield'];
|
||||
|
||||
_(equipmentTypes).each(function(type) {
|
||||
var equippedItem = equipped[type];
|
||||
if(gear[equippedItem]) {
|
||||
var equipmentStat = gear[equippedItem][stat];
|
||||
|
||||
total += equipmentStat;
|
||||
}
|
||||
}).value();
|
||||
|
||||
return total;
|
||||
}
|
||||
|
||||
function expDisplay(user) {
|
||||
var exp = Math.floor(user.stats.exp);
|
||||
var toNextLevel = Shared.tnl(user.stats.lvl);
|
||||
var display = _formatOutOfTotalDisplay(exp, toNextLevel);
|
||||
|
||||
return display;
|
||||
}
|
||||
|
||||
function goldDisplay(gold) {
|
||||
var display = Math.floor(gold);
|
||||
return display;
|
||||
}
|
||||
|
||||
function hpDisplay(hp) {
|
||||
var remainingHP = Math.ceil(hp);
|
||||
var totalHP = Shared.maxHealth;
|
||||
var display = _formatOutOfTotalDisplay(remainingHP, totalHP);
|
||||
|
||||
return display;
|
||||
}
|
||||
|
||||
function levelBonus(level) {
|
||||
// Level bonus is derived by taking the level, subtracting one,
|
||||
// taking the smaller of it or maxLevel (100),
|
||||
// dividing that by two and then raising it to a whole number
|
||||
|
||||
var levelOrMaxLevel = Math.min((level - 1), Shared.maxLevel);
|
||||
var levelDividedByTwo = levelOrMaxLevel / 2;
|
||||
var bonus = Math.ceil(levelDividedByTwo );
|
||||
|
||||
return bonus;
|
||||
}
|
||||
|
||||
function mountMasterProgress(mounts) {
|
||||
var dropMountsFound = Shared.count.mountMasterProgress(mounts);
|
||||
var display = _formatOutOfTotalDisplay(dropMountsFound, TOTAL_NUMBER_OF_DROP_ANIMALS);
|
||||
|
||||
return display;
|
||||
}
|
||||
|
||||
function mpDisplay(user) {
|
||||
var remainingMP = Math.floor(user.stats.mp);
|
||||
var totalMP = (user.fns && user.fns.statsComputed) ? user.fns.statsComputed().maxMP : null;
|
||||
var display = _formatOutOfTotalDisplay(remainingMP, totalMP);
|
||||
|
||||
return display;
|
||||
}
|
||||
|
||||
function totalCount(objectToCount) {
|
||||
var total = _.size(objectToCount);
|
||||
|
||||
return total;
|
||||
}
|
||||
|
||||
function _formatOutOfTotalDisplay(stat, totalStat) {
|
||||
var display = stat + "/" + totalStat;
|
||||
return display;
|
||||
}
|
||||
|
||||
return {
|
||||
beastMasterProgress: beastMasterProgress,
|
||||
classBonus: classBonus,
|
||||
equipmentStatBonus: equipmentStatBonus,
|
||||
expDisplay: expDisplay,
|
||||
goldDisplay: goldDisplay,
|
||||
hpDisplay: hpDisplay,
|
||||
levelBonus: levelBonus,
|
||||
mountMasterProgress: mountMasterProgress,
|
||||
mpDisplay: mpDisplay,
|
||||
totalCount: totalCount
|
||||
}
|
||||
}
|
||||
}());
|
||||
@@ -0,0 +1,60 @@
|
||||
'use strict';
|
||||
|
||||
angular.module('habitrpg')
|
||||
.factory('Tags', ['$rootScope', '$http',
|
||||
function tagsFactory($rootScope, $http) {
|
||||
|
||||
function getTags () {
|
||||
return $http({
|
||||
method: 'GET',
|
||||
url: 'api/v3/tags',
|
||||
});
|
||||
};
|
||||
|
||||
function createTag (tagDetails) {
|
||||
return $http({
|
||||
method: 'POST',
|
||||
url: 'api/v3/tags',
|
||||
data: tagDetails,
|
||||
});
|
||||
};
|
||||
|
||||
function getTag (tagId) {
|
||||
return $http({
|
||||
method: 'GET',
|
||||
url: 'api/v3/tags/' + tagId,
|
||||
});
|
||||
};
|
||||
|
||||
function updateTag (tagId, tagDetails) {
|
||||
return $http({
|
||||
method: 'PUT',
|
||||
url: 'api/v3/tags/' + tagId,
|
||||
data: tagDetails,
|
||||
});
|
||||
};
|
||||
|
||||
function sortTag (tagId, to) {
|
||||
return $http({
|
||||
method: 'POST',
|
||||
url: 'api/v3/reorder-tags',
|
||||
data: {tagId: tagId, to: to},
|
||||
});
|
||||
};
|
||||
|
||||
function deleteTag (tagId) {
|
||||
return $http({
|
||||
method: 'DELETE',
|
||||
url: 'api/v3/tags/' + tagId,
|
||||
});
|
||||
};
|
||||
|
||||
return {
|
||||
getTags: getTags,
|
||||
createTag: createTag,
|
||||
getTag: getTag,
|
||||
updateTag: updateTag,
|
||||
sortTag: sortTag,
|
||||
deleteTag: deleteTag,
|
||||
};
|
||||
}]);
|
||||
@@ -0,0 +1,205 @@
|
||||
'use strict';
|
||||
|
||||
var TASK_KEYS_TO_REMOVE = ['_id', 'completed', 'date', 'dateCompleted', 'history', 'id', 'streak', 'createdAt', 'challenge'];
|
||||
|
||||
angular.module('habitrpg')
|
||||
.factory('Tasks', ['$rootScope', 'Shared', '$http',
|
||||
function tasksFactory($rootScope, Shared, $http) {
|
||||
|
||||
function getUserTasks (getCompletedTodos) {
|
||||
var url = '/api/v3/tasks/user';
|
||||
|
||||
if (getCompletedTodos) url += '?type=completedTodos';
|
||||
|
||||
return $http({
|
||||
method: 'GET',
|
||||
url: url,
|
||||
});
|
||||
};
|
||||
|
||||
function createUserTasks (taskDetails) {
|
||||
return $http({
|
||||
method: 'POST',
|
||||
url: '/api/v3/tasks/user',
|
||||
data: taskDetails,
|
||||
});
|
||||
};
|
||||
|
||||
function getChallengeTasks (challengeId) {
|
||||
return $http({
|
||||
method: 'GET',
|
||||
url: '/api/v3/tasks/challenge/' + challengeId,
|
||||
});
|
||||
};
|
||||
|
||||
function createChallengeTasks (challengeId, taskDetails) {
|
||||
return $http({
|
||||
method: 'POST',
|
||||
url: '/api/v3/tasks/challenge/' + challengeId,
|
||||
data: taskDetails,
|
||||
});
|
||||
};
|
||||
|
||||
function getTask (taskId) {
|
||||
return $http({
|
||||
method: 'GET',
|
||||
url: '/api/v3/tasks/' + taskId,
|
||||
});
|
||||
};
|
||||
|
||||
function updateTask (taskId, taskDetails) {
|
||||
return $http({
|
||||
method: 'PUT',
|
||||
url: '/api/v3/tasks/' + taskId,
|
||||
data: taskDetails,
|
||||
});
|
||||
};
|
||||
|
||||
function deleteTask (taskId) {
|
||||
return $http({
|
||||
method: 'DELETE',
|
||||
url: '/api/v3/tasks/' + taskId,
|
||||
});
|
||||
};
|
||||
|
||||
function scoreTask (taskId, direction) {
|
||||
return $http({
|
||||
method: 'POST',
|
||||
url: '/api/v3/tasks/' + taskId + '/score/' + direction,
|
||||
});
|
||||
};
|
||||
|
||||
function moveTask (taskId, position) {
|
||||
return $http({
|
||||
method: 'POST',
|
||||
url: '/api/v3/tasks/' + taskId + '/move/to/' + position,
|
||||
});
|
||||
};
|
||||
|
||||
function addChecklistItem (taskId, checkListItem) {
|
||||
return $http({
|
||||
method: 'POST',
|
||||
url: '/api/v3/tasks/' + taskId + '/checklist',
|
||||
data: checkListItem,
|
||||
});
|
||||
};
|
||||
|
||||
function scoreCheckListItem (taskId, itemId) {
|
||||
return $http({
|
||||
method: 'POST',
|
||||
url: '/api/v3/tasks/' + taskId + '/checklist/' + itemId + '/score',
|
||||
});
|
||||
};
|
||||
|
||||
function updateChecklistItem (taskId, itemId, itemDetails) {
|
||||
return $http({
|
||||
method: 'PUT',
|
||||
url: '/api/v3/tasks/' + taskId + '/checklist/' + itemId,
|
||||
data: itemDetails,
|
||||
});
|
||||
};
|
||||
|
||||
function removeChecklistItem (taskId, itemId) {
|
||||
return $http({
|
||||
method: 'DELETE',
|
||||
url: '/api/v3/tasks/' + taskId + '/checklist/' + itemId,
|
||||
});
|
||||
};
|
||||
|
||||
function addTagToTask (taskId, tagId) {
|
||||
return $http({
|
||||
method: 'POST',
|
||||
url: '/api/v3/tasks/' + taskId + '/tags/' + tagId,
|
||||
});
|
||||
};
|
||||
|
||||
function removeTagFromTask (taskId, tagId) {
|
||||
return $http({
|
||||
method: 'DELETE',
|
||||
url: '/api/v3/tasks/' + taskId + '/tags/' + tagId,
|
||||
});
|
||||
};
|
||||
|
||||
function unlinkOneTask (taskId, keep) { // single task
|
||||
if (!keep) {
|
||||
keep = "keep";
|
||||
}
|
||||
|
||||
return $http({
|
||||
method: 'POST',
|
||||
url: '/api/v3/tasks/unlink-one/' + taskId + '?keep=' + keep,
|
||||
});
|
||||
};
|
||||
|
||||
function unlinkAllTasks (challengeId, keep) { // all tasks
|
||||
if (!keep) {
|
||||
keep = "keep-all";
|
||||
}
|
||||
|
||||
return $http({
|
||||
method: 'POST',
|
||||
url: '/api/v3/tasks/unlink-all/' + challengeId + '?keep=' + keep,
|
||||
});
|
||||
};
|
||||
|
||||
function clearCompletedTodos () {
|
||||
return $http({
|
||||
method: 'POST',
|
||||
url: '/api/v3/tasks/clearCompletedTodos',
|
||||
});
|
||||
};
|
||||
|
||||
function editTask(task, user) {
|
||||
task._editing = !task._editing;
|
||||
task._tags = !user.preferences.tagsCollapsed;
|
||||
task._advanced = !user.preferences.advancedCollapsed;
|
||||
if($rootScope.charts[task._id]) $rootScope.charts[task.id] = false;
|
||||
}
|
||||
|
||||
function cloneTask(task) {
|
||||
var clonedTask = _.cloneDeep(task);
|
||||
clonedTask = _cleanUpTask(clonedTask);
|
||||
|
||||
return Shared.taskDefaults(clonedTask);
|
||||
}
|
||||
|
||||
function _cleanUpTask(task) {
|
||||
var cleansedTask = _.omit(task, TASK_KEYS_TO_REMOVE);
|
||||
|
||||
// Copy checklists but reset to uncomplete and assign new id
|
||||
_(cleansedTask.checklist).forEach(function(item) {
|
||||
item.completed = false;
|
||||
item.id = Shared.uuid();
|
||||
}).value();
|
||||
|
||||
if (cleansedTask.type !== 'reward') {
|
||||
delete cleansedTask.value;
|
||||
}
|
||||
|
||||
return cleansedTask;
|
||||
}
|
||||
|
||||
return {
|
||||
getUserTasks: getUserTasks,
|
||||
loadedCompletedTodos: false,
|
||||
createUserTasks: createUserTasks,
|
||||
getChallengeTasks: getChallengeTasks,
|
||||
createChallengeTasks: createChallengeTasks,
|
||||
getTask: getTask,
|
||||
updateTask: updateTask,
|
||||
deleteTask: deleteTask,
|
||||
scoreTask: scoreTask,
|
||||
moveTask: moveTask,
|
||||
addChecklistItem: addChecklistItem,
|
||||
scoreCheckListItem: scoreCheckListItem,
|
||||
updateChecklistItem: updateChecklistItem,
|
||||
removeChecklistItem: removeChecklistItem,
|
||||
addTagToTask: addTagToTask,
|
||||
removeTagFromTask: removeTagFromTask,
|
||||
unlinkOneTask: unlinkOneTask,
|
||||
unlinkAllTasks: unlinkAllTasks,
|
||||
clearCompletedTodos: clearCompletedTodos,
|
||||
editTask: editTask,
|
||||
cloneTask: cloneTask
|
||||
};
|
||||
}]);
|
||||
@@ -0,0 +1,646 @@
|
||||
'use strict';
|
||||
|
||||
angular.module('habitrpg')
|
||||
.service('ApiUrl', ['API_URL', function(currentApiUrl) {
|
||||
this.setApiUrl = function(newUrl){
|
||||
currentApiUrl = newUrl;
|
||||
};
|
||||
|
||||
this.get = function(){
|
||||
return currentApiUrl;
|
||||
};
|
||||
}])
|
||||
|
||||
/**
|
||||
* Services that persists and retrieves user from localStorage.
|
||||
*/
|
||||
.factory('User', ['$rootScope', '$http', '$location', '$window', 'STORAGE_USER_ID', 'STORAGE_SETTINGS_ID', 'Notification', 'ApiUrl', 'Tasks', 'Tags', 'Content',
|
||||
function($rootScope, $http, $location, $window, STORAGE_USER_ID, STORAGE_SETTINGS_ID, Notification, ApiUrl, Tasks, Tags, Content) {
|
||||
var authenticated = false;
|
||||
var defaultSettings = {
|
||||
auth: { apiId: '', apiToken: ''},
|
||||
sync: {
|
||||
queue: [], //here OT will be queued up, this is NOT call-back queue!
|
||||
sent: [] //here will be OT which have been sent, but we have not got reply from server yet.
|
||||
},
|
||||
fetching: false, // whether fetch() was called or no. this is to avoid race conditions
|
||||
online: false
|
||||
};
|
||||
var settings = {}; //habit mobile settings (like auth etc.) to be stored here
|
||||
var user = {}; // this is stored as a reference accessible to all controllers, that way updates propagate
|
||||
|
||||
var userNotifications = {
|
||||
// "party.order" : env.t("updatedParty"),
|
||||
// "party.orderAscending" : env.t("updatedParty")
|
||||
// party.order notifications are not currently needed because the party avatars are resorted immediately now
|
||||
}; // this is a list of notifications to send to the user when changes are made, along with the message.
|
||||
|
||||
//first we populate user with schema
|
||||
user.apiToken = user._id = ''; // we use id / apitoken to determine if registered
|
||||
|
||||
//than we try to load localStorage
|
||||
if (localStorage.getItem(STORAGE_USER_ID)) {
|
||||
_.extend(user, JSON.parse(localStorage.getItem(STORAGE_USER_ID)));
|
||||
}
|
||||
|
||||
user._wrapped = false;
|
||||
|
||||
function syncUserTasks (tasks) {
|
||||
user.habits = [];
|
||||
user.todos = [];
|
||||
user.dailys = [];
|
||||
user.rewards = [];
|
||||
|
||||
// Order tasks based on tasksOrder
|
||||
var groupedTasks = _(tasks)
|
||||
.groupBy('type')
|
||||
.forEach(function (tasksOfType, type) {
|
||||
var order = user.tasksOrder[type + 's'];
|
||||
var orderedTasks = new Array(tasksOfType.length);
|
||||
var unorderedTasks = []; // what we want to add later
|
||||
|
||||
tasksOfType.forEach(function (task, index) {
|
||||
var taskId = task._id;
|
||||
var i = order[index] === taskId ? index : order.indexOf(taskId);
|
||||
if (i === -1) {
|
||||
unorderedTasks.push(task);
|
||||
} else {
|
||||
orderedTasks[i] = task;
|
||||
}
|
||||
});
|
||||
|
||||
// Remove empty values from the array and add any unordered task
|
||||
user[type + 's'] = _.compact(orderedTasks).concat(unorderedTasks);
|
||||
}).value();
|
||||
}
|
||||
|
||||
function sync() {
|
||||
return $http({
|
||||
method: "GET",
|
||||
url: '/api/v3/user/',
|
||||
})
|
||||
.then(function (response) {
|
||||
if (response.data.message) Notification.text(response.data.message);
|
||||
|
||||
_.extend(user, response.data.data);
|
||||
|
||||
$rootScope.$emit('userUpdated', user);
|
||||
|
||||
if (!user._wrapped) {
|
||||
// This wraps user with `ops`, which are functions shared both on client and mobile. When performed on client,
|
||||
// they update the user in the browser and then send the request to the server, where the same operation is
|
||||
// replicated. We need to wrap each op to provide a callback to send that operation
|
||||
$window.habitrpgShared.wrap(user);
|
||||
_.each(user.ops, function(op,k){
|
||||
user.ops[k] = function(req){
|
||||
try {
|
||||
op(req);
|
||||
} catch (err) {
|
||||
Notification.text(err.message);
|
||||
return;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return Tasks.getUserTasks();
|
||||
})
|
||||
.then(function (response) {
|
||||
var tasks = response.data.data;
|
||||
syncUserTasks(tasks);
|
||||
save();
|
||||
$rootScope.$emit('userSynced');
|
||||
});
|
||||
}
|
||||
|
||||
var save = function () {
|
||||
localStorage.setItem(STORAGE_USER_ID, JSON.stringify(user));
|
||||
localStorage.setItem(STORAGE_SETTINGS_ID, JSON.stringify(settings));
|
||||
};
|
||||
|
||||
function callOpsFunctionAndRequest (opName, endPoint, method, paramString, opData) {
|
||||
if (!opData) opData = {};
|
||||
|
||||
var clientResponse;
|
||||
|
||||
try {
|
||||
var args = [user];
|
||||
if (opName === 'rebirth' || opName === 'reroll' || opName === 'reset') {
|
||||
args.push(user.habits.concat(user.dailys).concat(user.rewards).concat(user.todos));
|
||||
}
|
||||
|
||||
args.push(opData);
|
||||
clientResponse = $window.habitrpgShared.ops[opName].apply(null, args);
|
||||
} catch (err) {
|
||||
Notification.text(err.message);
|
||||
return;
|
||||
}
|
||||
|
||||
var clientMessage = clientResponse[1];
|
||||
|
||||
if (clientMessage) {
|
||||
Notification.text(clientMessage);
|
||||
}
|
||||
|
||||
var url = '/api/v3/user/' + endPoint;
|
||||
if (paramString) {
|
||||
url += '/' + paramString
|
||||
}
|
||||
|
||||
var body = {};
|
||||
if (opData.body) body = opData.body;
|
||||
|
||||
var queryString = '';
|
||||
if (opData.query) queryString = '?' + $.param(opData.query)
|
||||
|
||||
$http({
|
||||
method: method,
|
||||
url: url + queryString,
|
||||
body: body,
|
||||
})
|
||||
.then(function (response) {
|
||||
if (response.data.message && response.data.message !== clientMessage) {
|
||||
Notification.text(response.data.message);
|
||||
}
|
||||
if (opName === 'openMysteryItem') {
|
||||
var openedItem = clientResponse[0];
|
||||
var text = Content.gear.flat[openedItem.key].text();
|
||||
Notification.drop(env.t('messageDropMysteryItem', {dropText: text}), openedItem);
|
||||
}
|
||||
|
||||
save();
|
||||
})
|
||||
}
|
||||
|
||||
function setUser(updates) {
|
||||
for (var key in updates) {
|
||||
_.set(user, key, updates[key]);
|
||||
}
|
||||
}
|
||||
|
||||
var userServices = {
|
||||
user: user,
|
||||
|
||||
//@TODO: WE need a new way to set the user from tests
|
||||
setUser: function (userInc) {
|
||||
user = userInc;
|
||||
},
|
||||
|
||||
allocate: function (data) {
|
||||
callOpsFunctionAndRequest('allocate', 'allocate', "POST",'', data);
|
||||
},
|
||||
|
||||
allocateNow: function () {
|
||||
callOpsFunctionAndRequest('allocateNow', 'allocate-now', "POST");
|
||||
},
|
||||
|
||||
changeClass: function (data) {
|
||||
callOpsFunctionAndRequest('changeClass', 'change-class', "POST",'', data);
|
||||
},
|
||||
|
||||
disableClasses: function () {
|
||||
callOpsFunctionAndRequest('disableClasses', 'disable-classes', "POST");
|
||||
},
|
||||
|
||||
revive: function (data) {
|
||||
callOpsFunctionAndRequest('revive', 'revive', "POST");
|
||||
},
|
||||
|
||||
addTask: function (data) {
|
||||
if (_.isArray(data.body)) {
|
||||
data.body.forEach(function (task) {
|
||||
user.ops.addTask({body: task});
|
||||
});
|
||||
} else {
|
||||
user.ops.addTask(data);
|
||||
}
|
||||
save();
|
||||
Tasks.createUserTasks(data.body);
|
||||
},
|
||||
|
||||
score: function (data) {
|
||||
try {
|
||||
$window.habitrpgShared.ops.scoreTask({user: user, task: data.params.task, direction: data.params.direction}, data.params);
|
||||
} catch (err) {
|
||||
Notification.text(err.message);
|
||||
return;
|
||||
}
|
||||
save();
|
||||
|
||||
Tasks.scoreTask(data.params.task._id, data.params.direction).then(function (res) {
|
||||
var tmp = res.data.data._tmp || {}; // used to notify drops, critical hits and other bonuses
|
||||
var crit = tmp.crit;
|
||||
var drop = tmp.drop;
|
||||
|
||||
if (crit) {
|
||||
var critBonus = crit * 100 - 100;
|
||||
Notification.crit(critBonus);
|
||||
}
|
||||
if (drop) {
|
||||
var text, notes, type;
|
||||
$rootScope.playSound('Item_Drop');
|
||||
|
||||
// Note: For Mystery Item gear, drop.type will be 'head', 'armor', etc
|
||||
// so we use drop.notificationType below.
|
||||
|
||||
if (drop.type !== 'gear' && drop.type !== 'Quest' && drop.notificationType !== 'Mystery') {
|
||||
if (drop.type === 'Food') {
|
||||
type = 'food';
|
||||
} else if (drop.type === 'HatchingPotion') {
|
||||
type = 'hatchingPotions';
|
||||
} else {
|
||||
type = drop.type.toLowerCase() + 's';
|
||||
}
|
||||
if(!user.items[type][drop.key]){
|
||||
user.items[type][drop.key] = 0;
|
||||
}
|
||||
user.items[type][drop.key]++;
|
||||
}
|
||||
|
||||
if (drop.type === 'HatchingPotion'){
|
||||
text = Content.hatchingPotions[drop.key].text();
|
||||
notes = Content.hatchingPotions[drop.key].notes();
|
||||
Notification.drop(env.t('messageDropPotion', {dropText: text, dropNotes: notes}), drop);
|
||||
} else if (drop.type === 'Egg'){
|
||||
text = Content.eggs[drop.key].text();
|
||||
notes = Content.eggs[drop.key].notes();
|
||||
Notification.drop(env.t('messageDropEgg', {dropText: text, dropNotes: notes}), drop);
|
||||
} else if (drop.type === 'Food'){
|
||||
text = Content.food[drop.key].text();
|
||||
notes = Content.food[drop.key].notes();
|
||||
Notification.drop(env.t('messageDropFood', {dropArticle: drop.article, dropText: text, dropNotes: notes}), drop);
|
||||
} else if (drop.type === 'Quest') {
|
||||
$rootScope.selectedQuest = Content.quests[drop.key];
|
||||
$rootScope.openModal('questDrop', {controller:'PartyCtrl', size:'sm'});
|
||||
} else {
|
||||
// Keep support for another type of drops that might be added
|
||||
Notification.drop(drop.dialog);
|
||||
}
|
||||
|
||||
// Analytics.track({'hitType':'event','eventCategory':'behavior','eventAction':'acquire item','itemName':after.key,'acquireMethod':'Drop'});
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
sortTask: function (data) {
|
||||
user.ops.sortTask(data);
|
||||
save();
|
||||
Tasks.moveTask(data.params.id, data.query.to);
|
||||
},
|
||||
|
||||
updateTask: function (task, data) {
|
||||
$window.habitrpgShared.ops.updateTask(task, data);
|
||||
save();
|
||||
Tasks.updateTask(task._id, data.body);
|
||||
},
|
||||
|
||||
deleteTask: function (data) {
|
||||
user.ops.deleteTask(data);
|
||||
save();
|
||||
Tasks.deleteTask(data.params.id);
|
||||
},
|
||||
|
||||
clearCompleted: function () {
|
||||
user.ops.clearCompleted(user.todos);
|
||||
save();
|
||||
Tasks.clearCompletedTodos();
|
||||
},
|
||||
|
||||
addTag: function(data) {
|
||||
user.ops.addTag(data);
|
||||
save();
|
||||
Tags.createTag(data.body);
|
||||
},
|
||||
|
||||
updateTag: function(data) {
|
||||
user.ops.updateTag(data);
|
||||
save();
|
||||
Tags.updateTag(data.params.id, data.body);
|
||||
},
|
||||
|
||||
sortTag: function (data) {
|
||||
var fromId = user.tags[data.query.from].id;
|
||||
user.ops.sortTag(data);
|
||||
Tags.sortTag(fromId, data.query.to);
|
||||
},
|
||||
|
||||
deleteTag: function(data) {
|
||||
user.ops.deleteTag(data);
|
||||
save();
|
||||
Tags.deleteTag(data.params.id);
|
||||
},
|
||||
|
||||
addTenGems: function () {
|
||||
$http({
|
||||
method: "POST",
|
||||
url: 'api/v3/debug/add-ten-gems',
|
||||
})
|
||||
.then(function (response) {
|
||||
Notification.text('+10 Gems!');
|
||||
sync();
|
||||
})
|
||||
},
|
||||
|
||||
addHourglass: function () {
|
||||
$http({
|
||||
method: "POST",
|
||||
url: 'api/v3/debug/add-hourglass',
|
||||
})
|
||||
.then(function (response) {
|
||||
sync();
|
||||
})
|
||||
},
|
||||
|
||||
setCron: function (numberOfDays) {
|
||||
var date = moment(user.lastCron).subtract(numberOfDays, 'days').toDate();
|
||||
|
||||
$http({
|
||||
method: "POST",
|
||||
url: 'api/v3/debug/set-cron',
|
||||
data: {
|
||||
lastCron: date
|
||||
}
|
||||
})
|
||||
.then(function (response) {
|
||||
Notification.text('-' + numberOfDays + ' day(s), remember to refresh');
|
||||
});
|
||||
},
|
||||
|
||||
setCustomDayStart: function (dayStart) {
|
||||
$http({
|
||||
method: "POST",
|
||||
url: 'api/v3/user/custom-day-start',
|
||||
data: {
|
||||
dayStart: dayStart
|
||||
}
|
||||
})
|
||||
.then(function (response) {
|
||||
Notification.text(response.data.data.message);
|
||||
sync();
|
||||
});
|
||||
},
|
||||
|
||||
makeAdmin: function () {
|
||||
$http({
|
||||
method: "POST",
|
||||
url: 'api/v3/debug/make-admin'
|
||||
})
|
||||
.then(function (response) {
|
||||
Notification.text('You are now an admin! Go to the Hall of Heroes to change your contributor level.');
|
||||
sync()
|
||||
});
|
||||
},
|
||||
|
||||
clearNewMessages: function () {
|
||||
callOpsFunctionAndRequest('markPmsRead', 'mark-pms-read', "POST");
|
||||
},
|
||||
|
||||
clearPMs: function () {
|
||||
callOpsFunctionAndRequest('clearPMs', 'messages', "DELETE");
|
||||
},
|
||||
|
||||
deletePM: function (data) {
|
||||
callOpsFunctionAndRequest('deletePM', 'messages', "DELETE", data.params.id, data);
|
||||
},
|
||||
|
||||
buy: function (data) {
|
||||
callOpsFunctionAndRequest('buy', 'buy', "POST", data.params.key, data);
|
||||
},
|
||||
|
||||
buyArmoire: function () {
|
||||
$http({
|
||||
method: "POST",
|
||||
url: '/api/v3/user/buy-armoire',
|
||||
})
|
||||
.then(function (response) {
|
||||
Notification.text(response.data.message);
|
||||
sync();
|
||||
})
|
||||
},
|
||||
|
||||
buyQuest: function (data) {
|
||||
callOpsFunctionAndRequest('buyQuest', 'buy-quest', "POST", data.params.key, data);
|
||||
},
|
||||
|
||||
purchase: function (data) {
|
||||
var type = data.params.type;
|
||||
var key = data.params.key;
|
||||
callOpsFunctionAndRequest('purchase', 'purchase', "POST", type + '/' + key, data);
|
||||
},
|
||||
|
||||
buySpecialSpell: function (data) {
|
||||
$window.habitrpgShared.ops['buySpecialSpell'](user, data);
|
||||
var key = data.params.key;
|
||||
|
||||
$http({
|
||||
method: "POST",
|
||||
url: '/api/v3/user/' + 'buy-special-spell/' + key,
|
||||
})
|
||||
.then(function (response) {
|
||||
Notification.text(response.data.message);
|
||||
})
|
||||
},
|
||||
|
||||
buyMysterySet: function (data) {
|
||||
callOpsFunctionAndRequest('buyMysterySet', 'buy-mystery-set', "POST", data.params.key, data);
|
||||
},
|
||||
|
||||
readCard: function (data) {
|
||||
callOpsFunctionAndRequest('readCard', 'read-card', "POST", data.params.cardType, data);
|
||||
},
|
||||
|
||||
openMysteryItem: function (data) {
|
||||
callOpsFunctionAndRequest('openMysteryItem', 'open-mystery-item', "POST");
|
||||
},
|
||||
|
||||
sell: function (data) {
|
||||
var type = data.params.type;
|
||||
var key = data.params.key;
|
||||
callOpsFunctionAndRequest('sell', 'sell', "POST", type + '/' + key, data);
|
||||
},
|
||||
|
||||
hatch: function (data) {
|
||||
var egg = data.params.egg;
|
||||
var hatchingPotion = data.params.hatchingPotion;
|
||||
callOpsFunctionAndRequest('hatch', 'hatch', "POST", egg + '/' + hatchingPotion, data);
|
||||
},
|
||||
|
||||
feed: function (data) {
|
||||
var pet = data.params.pet;
|
||||
var food = data.params.food;
|
||||
callOpsFunctionAndRequest('feed', 'feed', "POST", pet + '/' + food, data);
|
||||
},
|
||||
|
||||
equip: function (data) {
|
||||
var type = data.params.type;
|
||||
var key = data.params.key;
|
||||
callOpsFunctionAndRequest('equip', 'equip', "POST", type + '/' + key, data);
|
||||
},
|
||||
|
||||
hourglassPurchase: function (data) {
|
||||
var type = data.params.type;
|
||||
var key = data.params.key;
|
||||
callOpsFunctionAndRequest('purchaseHourglass', 'purchase-hourglass', "POST", type + '/' + key, data);
|
||||
},
|
||||
|
||||
unlock: function (data) {
|
||||
callOpsFunctionAndRequest('unlock', 'unlock', "POST", '', data);
|
||||
},
|
||||
|
||||
set: function(updates) {
|
||||
setUser(updates);
|
||||
$http({
|
||||
method: "PUT",
|
||||
url: '/api/v3/user',
|
||||
data: updates,
|
||||
})
|
||||
.then(function () {
|
||||
save();
|
||||
$rootScope.$emit('userSynced');
|
||||
})
|
||||
},
|
||||
|
||||
reroll: function () {
|
||||
callOpsFunctionAndRequest('reroll', 'reroll', "POST");
|
||||
},
|
||||
|
||||
rebirth: function () {
|
||||
callOpsFunctionAndRequest('rebirth', 'rebirth', "POST");
|
||||
},
|
||||
|
||||
reset: function () {
|
||||
callOpsFunctionAndRequest('reset', 'reset', "POST");
|
||||
},
|
||||
|
||||
releaseBoth: function () {
|
||||
callOpsFunctionAndRequest('releaseBoth', 'release-both', "POST");
|
||||
},
|
||||
|
||||
releaseMounts: function () {
|
||||
callOpsFunctionAndRequest('releaseMounts', 'release-mounts', "POST");
|
||||
},
|
||||
|
||||
releasePets: function () {
|
||||
callOpsFunctionAndRequest('releasePets', 'release-pets', "POST");
|
||||
},
|
||||
|
||||
addWebhook: function (data) {
|
||||
callOpsFunctionAndRequest('addWebhook', 'webhook', "POST", '', data, data.body);
|
||||
},
|
||||
|
||||
updateWebhook: function (data) {
|
||||
callOpsFunctionAndRequest('updateWebhook', 'webhook', "PUT", data.params.id, data, data.body);
|
||||
},
|
||||
|
||||
deleteWebhook: function (data) {
|
||||
callOpsFunctionAndRequest('deleteWebhook', 'webhook', "DELETE", data.params.id, data, data.body);
|
||||
},
|
||||
|
||||
sleep: function () {
|
||||
callOpsFunctionAndRequest('sleep', 'sleep', "POST");
|
||||
},
|
||||
|
||||
blockUser: function (data) {
|
||||
callOpsFunctionAndRequest('blockUser', 'block', "POST", data.params.uuid, data);
|
||||
},
|
||||
|
||||
online: function (status) {
|
||||
if (status===true) {
|
||||
settings.online = true;
|
||||
// syncQueue();
|
||||
} else {
|
||||
settings.online = false;
|
||||
};
|
||||
},
|
||||
|
||||
authenticate: function (uuid, token, cb) {
|
||||
if (uuid && token) {
|
||||
var offset = moment().zone(); // eg, 240 - this will be converted on server as -(offset/60)
|
||||
$http.defaults.headers.common['x-api-user'] = uuid;
|
||||
$http.defaults.headers.common['x-api-key'] = token;
|
||||
$http.defaults.headers.common['x-user-timezoneOffset'] = offset;
|
||||
authenticated = true;
|
||||
settings.auth.apiId = uuid;
|
||||
settings.auth.apiToken = token;
|
||||
settings.online = true;
|
||||
save();
|
||||
sync().then(function () {
|
||||
if (user.preferences.timezoneOffset !== offset)
|
||||
userServices.set({'preferences.timezoneOffset': offset});
|
||||
if (cb) cb();
|
||||
});
|
||||
} else {
|
||||
alert('Please enter your ID and Token in settings.')
|
||||
}
|
||||
},
|
||||
|
||||
authenticated: function(){
|
||||
return this.settings.auth.apiId !== "";
|
||||
},
|
||||
|
||||
getBalanceInGems: function() {
|
||||
var balance = user.balance || 0;
|
||||
return balance * 4;
|
||||
},
|
||||
|
||||
log: function (action, cb) {
|
||||
//push by one buy one if an array passed in.
|
||||
if (_.isArray(action)) {
|
||||
action.forEach(function (a) {
|
||||
settings.sync.queue.push(a);
|
||||
});
|
||||
} else {
|
||||
settings.sync.queue.push(action);
|
||||
}
|
||||
|
||||
save();
|
||||
},
|
||||
|
||||
sync: function(){
|
||||
userServices.log({});
|
||||
return sync();
|
||||
},
|
||||
|
||||
syncUserTasks: syncUserTasks,
|
||||
|
||||
save: save,
|
||||
|
||||
settings: settings
|
||||
};
|
||||
|
||||
//load settings if we have them
|
||||
if (localStorage.getItem(STORAGE_SETTINGS_ID)) {
|
||||
//use extend here to make sure we keep object reference in other angular controllers
|
||||
_.extend(settings, JSON.parse(localStorage.getItem(STORAGE_SETTINGS_ID)));
|
||||
|
||||
//if settings were saved while fetch was in process reset the flag.
|
||||
settings.fetching = false;
|
||||
//create and load if not
|
||||
} else {
|
||||
localStorage.setItem(STORAGE_SETTINGS_ID, JSON.stringify(defaultSettings));
|
||||
_.extend(settings, defaultSettings);
|
||||
}
|
||||
|
||||
//If user does not have ApiID that forward him to settings.
|
||||
if (!settings.auth.apiId || !settings.auth.apiToken) {
|
||||
//var search = $location.search(); // FIXME this should be working, but it's returning an empty object when at a root url /?_id=...
|
||||
var search = $location.search($window.location.search.substring(1)).$$search; // so we use this fugly hack instead
|
||||
if (search.err) return alert(search.err);
|
||||
if (search._id && search.apiToken) {
|
||||
userServices.authenticate(search._id, search.apiToken, function(){
|
||||
$window.location.href = '/';
|
||||
});
|
||||
} else {
|
||||
var isStaticOrSocial = $window.location.pathname.match(/^\/(static|social)/);
|
||||
if (!isStaticOrSocial){
|
||||
localStorage.clear();
|
||||
$location.path('/logout');
|
||||
}
|
||||
}
|
||||
} else {
|
||||
userServices.authenticate(settings.auth.apiId, settings.auth.apiToken)
|
||||
}
|
||||
|
||||
return userServices;
|
||||
}
|
||||
]);
|
||||
@@ -0,0 +1,64 @@
|
||||
"use strict";
|
||||
|
||||
window.habitrpg = angular.module('habitrpg', ['chieffancypants.loadingBar', 'ui.bootstrap'])
|
||||
.constant("API_URL", "")
|
||||
.constant("STORAGE_USER_ID", 'habitrpg-user')
|
||||
.constant("STORAGE_SETTINGS_ID", 'habit-mobile-settings')
|
||||
.constant("MOBILE_APP", false)
|
||||
|
||||
.controller("RootCtrl", ['$scope', '$location', '$modal', '$http', 'Stats', 'Members',
|
||||
function($scope, $location, $modal, $http, Stats, Members) {
|
||||
var memberId = $location.search()['memberId'];
|
||||
if (memberId) {
|
||||
Members.fetchMember(memberId)
|
||||
.success(function(response) {
|
||||
$scope.profile = response.data;
|
||||
|
||||
$scope.statCalc = Stats;
|
||||
$scope.Content = window.habitrpgShared.content;
|
||||
$modal.open({
|
||||
templateUrl: 'modals/member.html',
|
||||
scope: $scope
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
$http.defaults.headers.common['x-client'] = 'habitica-web';
|
||||
}])
|
||||
|
||||
.controller("PlansCtrl", ['$rootScope','Analytics',
|
||||
function($rootScope,Analytics) {
|
||||
$rootScope.clickContact = function(){
|
||||
Analytics.track({'hitType':'event','eventCategory':'button','eventAction':'click','eventLabel':'Contact Us (Plans)'})
|
||||
}
|
||||
}
|
||||
])
|
||||
|
||||
.controller('AboutCtrl',[function(){
|
||||
$(document).ready(function(){
|
||||
$('a.gallery').colorbox({
|
||||
maxWidth: '90%',
|
||||
maxHeight: '80%',
|
||||
transition: 'none',
|
||||
scalePhotos:true
|
||||
//maxHeight: '70%'
|
||||
});
|
||||
});
|
||||
}])
|
||||
|
||||
.controller('AccordionCtrl', function() {
|
||||
function openHashAccordion() {
|
||||
if (window.location.hash) {
|
||||
var $target = $(window.location.hash.replace('/',''));
|
||||
if ($target.hasClass('collapse')) {
|
||||
$target.collapse('show');
|
||||
$('html, body').animate({
|
||||
scrollTop: $($target).offset().top - 100
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
$(document).ready(function(){
|
||||
openHashAccordion();
|
||||
});
|
||||
})
|
||||
Reference in New Issue
Block a user