Files
habitica/website/client-old/js/services/taskServices.js
Keith Holliday 1082359f2c Repeatables fixes (#8538)
* Prevented watch functions from being called when task._edit is removed

* Added start date support on the UI task summary

* Fixed setting of monthly and calculations

* Fixed linting issues

* Added check for existence

* Added existence check

* Ensured correct start date is used on update

* Hid repeat options from anything not a daily

* Added missing locales

* Moved repeatables out of advance options
2017-03-08 16:48:30 -07:00

521 lines
16 KiB
JavaScript

'use strict';
var TASK_KEYS_TO_REMOVE = ['_id', 'completed', 'date', 'dateCompleted', 'history', 'id', 'streak', 'createdAt', 'challenge'];
angular.module('habitrpg')
.factory('Tasks', ['$rootScope', 'Shared', '$http', '$modal',
function tasksFactory($rootScope, Shared, $http, $modal) {
function addTasks(listDef, addTaskFn) {
var tasks = listDef.newTask;
if (listDef.bulk) {
tasks = tasks.split(/[\n\r]+/);
// Reverse the order of tasks so the tasks
// will appear in the order the user entered them
tasks.reverse();
listDef.bulk = false;
} else {
tasks = [tasks];
}
addTaskFn(listDef, tasks);
delete listDef.newTask;
delete listDef.focus;
}
function toggleBulk (list) {
list.bulk = !list.bulk;
list.focus = true;
};
function removeTask (task) {
if (!confirm(window.env.t('sureDelete', {taskType: window.env.t(task.type), taskText: task.text}))) {
return false;
};
task._edit = undefined;
return true;
}
function saveTask (task, stayOpen, isSaveAndClose) {
if (task._edit) {
angular.copy(task._edit, task);
}
task._edit = undefined;
if (task.checklist) {
task.checklist = _.filter(task.checklist, function (i) {
return !!i.text
});
}
if (!stayOpen) task._editing = false;
if (isSaveAndClose) {
$("#task-" + task._id).parent().children('.popover').removeClass('in');
}
}
function getUserTasks (getCompletedTodos) {
var url = '/api/v3/tasks/user';
if (getCompletedTodos) url += '?type=completedTodos';
return $http({
method: 'GET',
url: url,
ignoreLoadingBar: $rootScope.appLoaded !== true,
});
};
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, tasks) {
return $http({
method: 'POST',
url: '/api/v3/tasks/challenge/' + challengeId,
data: tasks,
});
};
function getGroupTasks (groupId) {
return $http({
method: 'GET',
url: '/api/v3/tasks/group/' + groupId,
});
};
function createGroupTasks (groupId, taskDetails) {
return $http({
method: 'POST',
url: '/api/v3/tasks/group/' + groupId,
data: taskDetails,
});
};
function getGroupApprovals (groupId) {
return $http({
method: 'GET',
url: '/api/v3/approvals/group/' + groupId,
});
};
function approve (taskId, userId) {
return $http({
method: 'POST',
url: '/api/v3/tasks/' + taskId + '/approve/' + userId,
});
};
function getTask (taskId) {
return $http({
method: 'GET',
url: '/api/v3/tasks/' + taskId,
});
};
function updateTask (taskId, taskDetails) {
var taskDetailsToSend = _.omit(taskDetails, ['challenge', 'group', 'history', 'reminders', 'tags'])
return $http({
method: 'PUT',
url: '/api/v3/tasks/' + taskId,
data: taskDetailsToSend,
});
};
function deleteTask (taskId) {
return $http({
method: 'DELETE',
url: '/api/v3/tasks/' + taskId,
});
};
function scoreTask (taskId, direction, body) {
if (!body) body = {};
return $http({
method: 'POST',
url: '/api/v3/tasks/' + taskId + '/score/' + direction,
data: body,
});
};
function moveTask (taskId, position) {
return $http({
method: 'POST',
url: '/api/v3/tasks/' + taskId + '/move/to/' + position,
});
};
function moveGroupTask (taskId, position) {
return $http({
method: 'POST',
url: '/api/v3/group-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 assignTask (taskId, userId) {
return $http({
method: 'POST',
url: '/api/v3/tasks/' + taskId + '/assign/' + userId,
});
};
function unAssignTask (taskId, userId) {
return $http({
method: 'POST',
url: '/api/v3/tasks/' + taskId + '/unassign/' + userId,
});
};
function editTask(task, user, taskStatus, scopeInc) {
// @TODO: This should be it's own controller. And methods should be abstracted form the three task ctrls to a directive/ctrl
var modalScope = $rootScope.$new();
modalScope.task = task;
modalScope.task._editing = true;
modalScope.task._tags = !user.preferences.tagsCollapsed;
modalScope.task._advanced = !user.preferences.advancedCollapsed;
modalScope.task._edit = angular.copy(task);
if($rootScope.charts[task._id]) $rootScope.charts[task.id] = false;
modalScope.taskStatus = taskStatus;
if (scopeInc) {
modalScope.saveTask = scopeInc.saveTask;
modalScope.addChecklist = scopeInc.addChecklist;
modalScope.addChecklistItem = scopeInc.addChecklistItem;
modalScope.removeChecklistItem = scopeInc.removeChecklistItem;
modalScope.swapChecklistItems = scopeInc.swapChecklistItems;
modalScope.navigateChecklist = scopeInc.navigateChecklist;
modalScope.checklistCompletion = scopeInc.checklistCompletion;
modalScope.canEdit = scopeInc.canEdit;
modalScope.updateTaskTags = scopeInc.updateTaskTags;
modalScope.obj = scopeInc.obj;
modalScope.unlink = scopeInc.unlink;
modalScope.removeTask = scopeInc.removeTask;
}
modalScope.cancelTaskEdit = cancelTaskEdit;
modalScope.task._edit.repeatsOn = 'dayOfMonth';
if (modalScope.task.type === 'daily' && modalScope.task._edit.weeksOfMonth && modalScope.task._edit.weeksOfMonth.length > 0) {
modalScope.task._edit.repeatsOn = 'dayOfWeek';
}
$modal.open({
scope: modalScope,
templateUrl: 'modals/task-edit.html',
controller: ['$scope', function ($scope) {
$scope.$watch('task._edit', function (newValue, oldValue) {
if ($scope.task.type !== 'daily' || !task._edit) return;
$scope.summary = generateSummary($scope.task);
$scope.repeatSuffix = generateRepeatSuffix($scope.task);
if ($scope.task._edit.repeatsOn == 'dayOfMonth') {
var date = moment(task._edit.startDate).date();
$scope.task._edit.weeksOfMonth = [];
$scope.task._edit.daysOfMonth = [date]; // @TODO This can handle multiple dates later
} else if ($scope.task._edit.repeatsOn == 'dayOfWeek') {
var week = Math.ceil(moment(task._edit.startDate).date() / 7) - 1;
var dayOfWeek = moment(task._edit.startDate).day();
var shortDay = numberToShortDay[dayOfWeek];
$scope.task._edit.daysOfMonth = [];
$scope.task._edit.weeksOfMonth = [week]; // @TODO: This can handle multiple weeks
for (var key in $scope.task._edit.repeat) {
$scope.task._edit.repeat[key] = false;
}
$scope.task._edit.repeat[shortDay] = true;
}
}, true);
}],
})
.result.catch(function() {
cancelTaskEdit(task);
});
}
/*
* Summary
*/
var frequencyMap = {
'daily': 'days',
'weekly': 'weeks',
'monthly': 'months',
'yearly': 'years',
};
var shortDayToLongDayMap = {
'su': moment().day(0).format('dddd'),
's': moment().day(6).format('dddd'),
'f': moment().day(5).format('dddd'),
'th': moment().day(4).format('dddd'),
'w': moment().day(3).format('dddd'),
't': moment().day(2).format('dddd'),
'm': moment().day(1).format('dddd'),
};
var numberToShortDay = Shared.DAY_MAPPING;
function generateSummary(task) {
var frequencyPlural = frequencyMap[task._edit.frequency];
var repeatDays = '';
for (var key in task._edit.repeat) {
if (task._edit.repeat[key]) {
repeatDays += shortDayToLongDayMap[key] + ', ';
}
}
var summary = 'Repeats ' + task._edit.frequency + ' every ' + task._edit.everyX + ' ' + frequencyPlural;
if (task._edit.frequency === 'weekly') summary += ' on ' + repeatDays;
if (task._edit.frequency === 'monthly' && task._edit.repeatsOn == 'dayOfMonth') {
var date = moment(task._edit.startDate).date();
summary += ' on the ' + date;
} else if (task._edit.frequency === 'monthly' && task._edit.repeatsOn == 'dayOfWeek') {
var week = Math.ceil(moment(task._edit.startDate).date() / 7) - 1;
var dayOfWeek = moment(task._edit.startDate).day();
var shortDay = numberToShortDay[dayOfWeek];
var longDay = shortDayToLongDayMap[shortDay];
summary += ' on the ' + (week + 1) + ' ' + longDay;
}
return summary;
}
function generateRepeatSuffix (task) {
if (task._edit.frequency === 'daily') {
return task._edit.everyX == 1 ? window.env.t('day') : window.env.t('days');
} else if (task._edit.frequency === 'weekly') {
return task._edit.everyX == 1 ? window.env.t('week') : window.env.t('weeks');
} else if (task._edit.frequency === 'monthly') {
return task._edit.everyX == 1 ? window.env.t('month') : window.env.t('months');
} else if (task._edit.frequency === 'yearly') {
return task._edit.everyX == 1 ? window.env.t('year') : window.env.t('years');
}
};
function cancelTaskEdit(task) {
task._edit = undefined;
task._editing = 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();
});
if (cleansedTask.type !== 'reward') {
delete cleansedTask.value;
}
return cleansedTask;
}
/*
------------------------
Checklists
------------------------
*/
function focusChecklist(task, index) {
window.setTimeout(function(){
$('#task-' + task._id + ' .checklist-form input[type="text"]')[index].focus();
});
}
function addChecklist(task) {
task._edit.checklist = [{completed:false, text:""}];
focusChecklist(task._edit,0);
}
function addChecklistItemToUI(task, $event, $index) {
if (task._edit.checklist[$index].text) {
if ($index === task._edit.checklist.length - 1) {
task._edit.checklist.push({ completed: false, text: '' });
}
focusChecklist(task._edit, $index + 1);
} else {
// TODO Provide UI feedback that this item is still blank
}
}
function removeChecklistItemFromUI(task, $event, $index, force) {
// Remove item if clicked on trash icon
if (force) {
task._edit.checklist.splice($index, 1);
} else if (!task._edit.checklist[$index].text) {
// User deleted all the text and is now wishing to delete the item
// saveTask will prune the empty item
// Move focus if the list is still non-empty
if ($index > 0)
focusChecklist(task._edit, $index-1);
// Don't allow the backspace key to navigate back now that the field is gone
$event.preventDefault();
}
}
function swapChecklistItems(task, oldIndex, newIndex) {
var toSwap = task._edit.checklist.splice(oldIndex, 1)[0];
task._edit.checklist.splice(newIndex, 0, toSwap);
}
function navigateChecklist(task,$index,$event) {
focusChecklist(task, $event.keyCode == '40' ? $index+1 : $index-1);
}
function checklistCompletion(checklist) {
return _.reduce(checklist,function(m,i){return m+(i.completed ? 1 : 0);},0)
}
function collapseChecklist(task) {
task.collapseChecklist = !task.collapseChecklist;
saveTask(task, true);
}
return {
addTasks: addTasks,
toggleBulk: toggleBulk,
getUserTasks: getUserTasks,
removeTask: removeTask,
saveTask: saveTask,
loadedCompletedTodos: false,
createUserTasks: createUserTasks,
getChallengeTasks: getChallengeTasks,
createChallengeTasks: createChallengeTasks,
getGroupTasks: getGroupTasks,
createGroupTasks: createGroupTasks,
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,
cancelTaskEdit: cancelTaskEdit,
cloneTask: cloneTask,
assignTask: assignTask,
unAssignTask: unAssignTask,
addChecklist: addChecklist,
addChecklistItemToUI: addChecklistItemToUI,
removeChecklistItemFromUI: removeChecklistItemFromUI,
swapChecklistItems: swapChecklistItems,
navigateChecklist: navigateChecklist,
checklistCompletion: checklistCompletion,
collapseChecklist: collapseChecklist,
getGroupApprovals: getGroupApprovals,
approve: approve,
moveGroupTask: moveGroupTask,
};
}]);