Merge branch 'develop' into Sinble-bug/slow_chat

This commit is contained in:
Blade Barringer
2016-06-03 17:24:52 -05:00
4925 changed files with 80656 additions and 39007 deletions
+6
View File
@@ -0,0 +1,6 @@
{
"extends": "habitrpg/browser",
"env": {
"jquery": true
}
}
+342
View File
@@ -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';
}]);
+140
View File
@@ -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();
});
}
};
}]);
+151
View File
@@ -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 = '';
};
}]);
+180
View File
@@ -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.');
})
};
}
}])
+144
View File
@@ -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'});
});
}
}]);
+127
View File
@@ -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');
});
}
}
]);
+54
View File
@@ -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();
});
}
}
]);
+47
View File
@@ -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'});
}
]);
+213
View File
@@ -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;
};
}
]);
+369
View File
@@ -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);
}]);
+338
View File
@@ -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();
}
}
]);
+87
View File
@@ -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);
}
});
};
}
}());
+16
View File
@@ -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;
+11
View File
@@ -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";
}
+34
View File
@@ -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,
}
}]);
+234
View File
@@ -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,
};
}]);
+310
View File
@@ -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">&laquo; 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") + ' &raquo;</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;
}]);
+138
View File
@@ -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
}
}
}());
+128
View File
@@ -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,
};
}]);
+205
View File
@@ -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
};
}]);
+646
View File
@@ -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;
}
]);
+64
View File
@@ -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();
});
})