mirror of
https://github.com/HabitRPG/habitica.git
synced 2025-12-18 15:17:25 +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')
|
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={}) ->
|
api.shouldDo = (day, dailyTask, options = {}) ->
|
||||||
return false unless repeat
|
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
|
o = sanitizeOptions options
|
||||||
selected = repeat[api.dayMapping[api.startOfDay(_.defaults {now:day}, o).day()]]
|
dayOfWeek = api.startOfDay(_.defaults {now:day}, o).day()
|
||||||
return selected
|
|
||||||
|
# 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
|
# show as completed if completed (naturally) or not required for today
|
||||||
if type in ['todo', 'daily']
|
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"
|
classes += " completed"
|
||||||
else
|
else
|
||||||
classes += " uncompleted"
|
classes += " uncompleted"
|
||||||
@@ -1537,7 +1575,7 @@ api.wrap = (user, main=true) ->
|
|||||||
scheduleMisses = 0
|
scheduleMisses = 0
|
||||||
_.times daysMissed, (n) ->
|
_.times daysMissed, (n) ->
|
||||||
thatDay = moment(now).subtract({days: n + 1})
|
thatDay = moment(now).subtract({days: n + 1})
|
||||||
if api.shouldDo(thatDay, repeat, user.preferences)
|
if api.shouldDo(thatDay, task, user.preferences)
|
||||||
scheduleMisses++
|
scheduleMisses++
|
||||||
if user.stats.buffs.stealth
|
if user.stats.buffs.stealth
|
||||||
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) {
|
function($scope, $rootScope, $location, User, Notification, $http, ApiUrl, $timeout, Shared, Guide) {
|
||||||
$scope.obj = User.user; // used for task-lists
|
$scope.obj = User.user; // used for task-lists
|
||||||
$scope.user = User.user;
|
$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) {
|
$scope.score = function(task, direction) {
|
||||||
switch (task.type) {
|
switch (task.type) {
|
||||||
@@ -127,6 +130,24 @@ habitrpg.controller("TasksCtrl", ['$scope', '$rootScope', '$location', 'User','N
|
|||||||
*/
|
*/
|
||||||
$scope._today = moment().add({days: 1});
|
$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
|
Checklists
|
||||||
@@ -212,7 +233,7 @@ habitrpg.controller("TasksCtrl", ['$scope', '$rootScope', '$location', 'User','N
|
|||||||
$scope.shouldShow = function(task, list, prefs){
|
$scope.shouldShow = function(task, list, prefs){
|
||||||
if (task._editing) // never hide a task while being edited
|
if (task._editing) // never hide a task while being edited
|
||||||
return true;
|
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) {
|
switch (list.view) {
|
||||||
case "yellowred": // Habits
|
case "yellowred": // Habits
|
||||||
return task.value < 1;
|
return task.value < 1;
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ habitrpg
|
|||||||
task._editing = !task._editing;
|
task._editing = !task._editing;
|
||||||
task._tags = User.user.preferences.tagsCollapsed;
|
task._tags = User.user.preferences.tagsCollapsed;
|
||||||
task._advanced = User.user.preferences.advancedCollapsed;
|
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;
|
if($rootScope.charts[task.id]) $rootScope.charts[task.id] = false;
|
||||||
};
|
};
|
||||||
}],
|
}],
|
||||||
|
|||||||
@@ -50,10 +50,14 @@ var checklist = [{
|
|||||||
|
|
||||||
var DailySchema = new Schema(
|
var DailySchema = new Schema(
|
||||||
_.defaults({
|
_.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,
|
history: Array,
|
||||||
completed: {type: Boolean, 'default': false},
|
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},
|
m: {type: Boolean, 'default': true},
|
||||||
t: {type: Boolean, 'default': true},
|
t: {type: Boolean, 'default': true},
|
||||||
w: {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
|
// if Daily, calendar
|
||||||
fieldset(ng-if='::task.type=="daily"', class="option-group")
|
fieldset(ng-if='::task.type=="daily"', class="option-group")
|
||||||
legend.option-title=env.t('repeat')
|
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)
|
ul.repeat-days(bindonce)
|
||||||
// note, does not use data-toggle="buttons-checkbox" - it would interfere with our own click binding
|
// note, does not use data-toggle="buttons-checkbox" - it would interfere with our own click binding
|
||||||
li
|
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)}}
|
button(ng-class='{active: task.repeat.f}', type='button', ng-click='task.challenge.id || (task.repeat.f= !task.repeat.f)') {{::moment.weekdaysMin(5)}}
|
||||||
li
|
li
|
||||||
button(ng-class='{active: task.repeat.s}', type='button', ng-click='task.challenge.id || (task.repeat.s = !task.repeat.s)') {{::moment.weekdaysMin(6)}}
|
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
|
// if Reward, pricing
|
||||||
fieldset.option-group.option-short(ng-if='task.type=="reward" && !task.challenge.id')
|
fieldset.option-group.option-short(ng-if='task.type=="reward" && !task.challenge.id')
|
||||||
|
|||||||
Reference in New Issue
Block a user