mirror of
https://github.com/HabitRPG/habitica.git
synced 2025-12-17 22:57:21 +01:00
Added support in daily tasks for specifying ’every X days’ and ‘every X weeks on specific days of the week’.
- shouldDo() now takes the entire task as an input instead of just the ‘repeat’ (days of the week) dictionary. - the ‘start’ date for a task can be specified (and can also be in the future, in which case it will be greyed out until then.) ‘start’ date also affects ‘every X days’ and ‘every X weeks’. - no migration code yet.
This commit is contained in:
@@ -72,13 +72,51 @@ api.daysSince = (yesterday, options = {}) ->
|
||||
Math.abs api.startOfDay(_.defaults {now:yesterday}, o).diff(api.startOfDay(_.defaults {now:o.now}, o), 'days')
|
||||
|
||||
###
|
||||
Should the user do this taks on this date, given the task's repeat options and user.preferences.dayStart?
|
||||
Should the user do this task on this date, given the task's repeat options and user.preferences.dayStart?
|
||||
###
|
||||
api.shouldDo = (day, repeat, options={}) ->
|
||||
return false unless repeat
|
||||
api.shouldDo = (day, dailyTask, options = {}) ->
|
||||
return false unless dailyTask.type == 'daily' && dailyTask.repeat
|
||||
# MIGRATION HACK, FIXME
|
||||
if dailyTask.startDate == null
|
||||
console.log("null!!!!!")
|
||||
dailyTask.startDate = moment().toDate();
|
||||
if dailyTask.startDate instanceof String
|
||||
console.log("startDate is a string: " + dailyTask.startDate + ". Converting to date");
|
||||
dailyTask.startDate = moment(dailyTask.startDate).toDate();
|
||||
o = sanitizeOptions options
|
||||
selected = repeat[api.dayMapping[api.startOfDay(_.defaults {now:day}, o).day()]]
|
||||
return selected
|
||||
dayOfWeek = api.startOfDay(_.defaults {now:day}, o).day()
|
||||
|
||||
# check if event is in the future
|
||||
hasStartedCheck = moment(day).isAfter(dailyTask.startDate) || moment(day).isSame(dailyTask.startDate)
|
||||
|
||||
if dailyTask.frequency == 'daily'
|
||||
daysSinceTaskStart = api.numDaysApart(day, dailyTask.startDate, o)
|
||||
everyXCheck = (daysSinceTaskStart % dailyTask.everyX == 0)
|
||||
return everyXCheck && hasStartedCheck
|
||||
else if dailyTask.frequency == 'weekly'
|
||||
dayOfWeekCheck = dailyTask.repeat[api.dayMapping[dayOfWeek]];
|
||||
weeksSinceTaskStartWeek = api.numWeeksApart(day, dailyTask.startDate, o)
|
||||
everyXCheck = (weeksSinceTaskStartWeek % dailyTask.everyX == 0)
|
||||
return dayOfWeekCheck && everyXCheck && hasStartedCheck
|
||||
else
|
||||
# unexpected frequency string
|
||||
return true
|
||||
|
||||
api.numDaysApart = (day1, day2, o) ->
|
||||
startOfDay1 = api.startOfDay(_.defaults {now:day1}, o)
|
||||
startOfDay2 = api.startOfDay(_.defaults {now:day2}, o)
|
||||
numDays = Math.abs(startOfDay1.diff(startOfDay2, 'days'))
|
||||
return numDays
|
||||
|
||||
# weeks between the two days, counting Monday as the start of each week
|
||||
api.numWeeksApart = (day1, day2, o) ->
|
||||
startOfDay1 = api.startOfDay(_.defaults {now:day1}, o)
|
||||
startOfDay2 = api.startOfDay(_.defaults {now:day2}, o)
|
||||
startWeekOfDay1 = startOfDay1.startOf('week')
|
||||
startWeekOfDay2 = startOfDay2.startOf('week')
|
||||
numWeeks = Math.abs(startWeekOfDay1.diff(startWeekOfDay2, 'weeks'))
|
||||
return numWeeks
|
||||
|
||||
|
||||
###
|
||||
------------------------------------------------------
|
||||
@@ -280,7 +318,7 @@ api.taskClasses = (task, filters=[], dayStart=0, lastCron=+new Date, showComplet
|
||||
|
||||
# show as completed if completed (naturally) or not required for today
|
||||
if type in ['todo', 'daily']
|
||||
if completed or (type is 'daily' and !api.shouldDo(+new Date, task.repeat, {dayStart}))
|
||||
if completed or (type is 'daily' and !api.shouldDo(+new Date, task, {dayStart}))
|
||||
classes += " completed"
|
||||
else
|
||||
classes += " uncompleted"
|
||||
@@ -1537,7 +1575,7 @@ api.wrap = (user, main=true) ->
|
||||
scheduleMisses = 0
|
||||
_.times daysMissed, (n) ->
|
||||
thatDay = moment(now).subtract({days: n + 1})
|
||||
if api.shouldDo(thatDay, repeat, user.preferences)
|
||||
if api.shouldDo(thatDay, task, user.preferences)
|
||||
scheduleMisses++
|
||||
if user.stats.buffs.stealth
|
||||
user.stats.buffs.stealth--
|
||||
|
||||
@@ -4,6 +4,9 @@ habitrpg.controller("TasksCtrl", ['$scope', '$rootScope', '$location', 'User','N
|
||||
function($scope, $rootScope, $location, User, Notification, $http, ApiUrl, $timeout, Shared, Guide) {
|
||||
$scope.obj = User.user; // used for task-lists
|
||||
$scope.user = User.user;
|
||||
// HACK: flagDict is for storing whether the datePicker popup is open or not. This should just be a boolean flag,
|
||||
// but apparently due to angular bug need to put the bool in intermediate dict...
|
||||
$scope.flagDict = {};
|
||||
|
||||
$scope.score = function(task, direction) {
|
||||
switch (task.type) {
|
||||
@@ -127,6 +130,24 @@ habitrpg.controller("TasksCtrl", ['$scope', '$rootScope', '$location', 'User','N
|
||||
*/
|
||||
$scope._today = moment().add({days: 1});
|
||||
|
||||
/*
|
||||
------------------------
|
||||
Dailies
|
||||
------------------------
|
||||
*/
|
||||
$scope.updateTaskStartDate = function(task) {
|
||||
// TODO: Weirdness here...startDate should be a Date and _dateString
|
||||
// should be a string, but the datePicker input fields sets _dateString
|
||||
// to a Date even though input type="text"...
|
||||
task.startDate = task._dateString;
|
||||
};
|
||||
|
||||
$scope.open = function($event) {
|
||||
$event.stopPropagation();
|
||||
$scope.flagDict['opened'] = !$scope.flagDict['opened'];
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
------------------------
|
||||
Checklists
|
||||
@@ -212,7 +233,7 @@ habitrpg.controller("TasksCtrl", ['$scope', '$rootScope', '$location', 'User','N
|
||||
$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.repeat, prefs) : true;
|
||||
var shouldDo = task.type == 'daily' ? habitrpgShared.shouldDo(new Date, task, prefs) : true;
|
||||
switch (list.view) {
|
||||
case "yellowred": // Habits
|
||||
return task.value < 1;
|
||||
|
||||
@@ -45,6 +45,7 @@ habitrpg
|
||||
task._editing = !task._editing;
|
||||
task._tags = User.user.preferences.tagsCollapsed;
|
||||
task._advanced = User.user.preferences.advancedCollapsed;
|
||||
task._dateString = moment(task.startDate).format('YYYY-MM-DD');
|
||||
if($rootScope.charts[task.id]) $rootScope.charts[task.id] = false;
|
||||
};
|
||||
}],
|
||||
|
||||
@@ -50,10 +50,14 @@ var checklist = [{
|
||||
|
||||
var DailySchema = new Schema(
|
||||
_.defaults({
|
||||
type: {type:String, 'default': 'daily'},
|
||||
type: {type: String, 'default': 'daily'},
|
||||
//TODO: Cleaner to store interval as enum instead of str?
|
||||
frequency: {type: String, 'default': 'daily'}, // 'daily', 'weekly'
|
||||
everyX: {type: Number, 'default': 1}, // e.g. once every X weeks
|
||||
startDate: {type: Date},
|
||||
history: Array,
|
||||
completed: {type: Boolean, 'default': false},
|
||||
repeat: {
|
||||
repeat: { // used only for 'weekly' frequency, TODO: Rename to daysOfWeek or something with 'week'
|
||||
m: {type: Boolean, 'default': true},
|
||||
t: {type: Boolean, 'default': true},
|
||||
w: {type: Boolean, 'default': true},
|
||||
|
||||
@@ -166,6 +166,32 @@ li(bindonce='list', id='task-{{::task.id}}', ng-repeat='task in obj[list.type+"s
|
||||
// if Daily, calendar
|
||||
fieldset(ng-if='::task.type=="daily"', class="option-group")
|
||||
legend.option-title=env.t('repeat')
|
||||
|
||||
// Choose the frequency
|
||||
ul.priority-multiplier
|
||||
li
|
||||
button(ng-class='{active: task.frequency=="daily"}', type='button', ng-click='task.frequency="daily"')='Daily'
|
||||
li
|
||||
button(ng-class='{active: task.frequency=="weekly"}', type='button', ng-click='task.frequency="weekly"')='Weekly'
|
||||
|
||||
// If frequency is daily
|
||||
fieldset(ng-show='task.frequency=="daily"')
|
||||
| Once every
|
||||
select(ng-model='task.everyX')
|
||||
option(ng-repeat='x in [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]') {{x}}
|
||||
| days<br><br>
|
||||
| Start date:
|
||||
p(class='input-group')
|
||||
input(type='text' class='form-control' datepicker-popup ng-model='task._dateString' ng-change='updateTaskStartDate(task)' is-open='flagDict.opened')
|
||||
span(class='input-group-btn')
|
||||
button(type="button" ng-click="open($event)")
|
||||
span.input-group-addon.glyphicon.glyphicon-calendar
|
||||
// If frequency is weekly
|
||||
fieldset(ng-show='task.frequency=="weekly"')
|
||||
| Once every
|
||||
select(ng-model='task.everyX')
|
||||
option(ng-repeat='x in [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]') {{x}}
|
||||
| weeks on these days: <br><br>
|
||||
ul.repeat-days(bindonce)
|
||||
// note, does not use data-toggle="buttons-checkbox" - it would interfere with our own click binding
|
||||
li
|
||||
@@ -182,6 +208,14 @@ li(bindonce='list', id='task-{{::task.id}}', ng-repeat='task in obj[list.type+"s
|
||||
button(ng-class='{active: task.repeat.f}', type='button', ng-click='task.challenge.id || (task.repeat.f= !task.repeat.f)') {{::moment.weekdaysMin(5)}}
|
||||
li
|
||||
button(ng-class='{active: task.repeat.s}', type='button', ng-click='task.challenge.id || (task.repeat.s = !task.repeat.s)') {{::moment.weekdaysMin(6)}}
|
||||
| <br><br>
|
||||
| Start date:
|
||||
p(class='input-group')
|
||||
input(type='text' class='form-control' datepicker-popup ng-model='task._dateString' ng-change='updateTaskStartDate(task)' is-open='flagDict.opened')
|
||||
span(class='input-group-btn')
|
||||
button(type="button" ng-click="open($event)")
|
||||
span.input-group-addon.glyphicon.glyphicon-calendar
|
||||
|
||||
|
||||
// if Reward, pricing
|
||||
fieldset.option-group.option-short(ng-if='task.type=="reward" && !task.challenge.id')
|
||||
|
||||
Reference in New Issue
Block a user