2012-06-20 22:15:38 +00:00
|
|
|
derby = require('derby')
|
|
|
|
|
{get, view, ready} = derby.createApp module
|
|
|
|
|
derby.use require('derby-ui-boot')
|
2012-06-21 00:47:08 +00:00
|
|
|
derby.use(require('../../ui'))
|
2012-04-27 02:19:31 +00:00
|
|
|
|
|
|
|
|
## ROUTES ##
|
|
|
|
|
|
2012-06-08 19:03:29 +00:00
|
|
|
newUser = (model, userId) ->
|
2012-06-20 20:45:39 +00:00
|
|
|
# TODO this used to be model.async.incr, revisit this
|
|
|
|
|
model.incr 'configs.1.nextUserId', (err, userId) ->
|
2012-05-02 19:50:43 +00:00
|
|
|
model.set '_session.userId', userId
|
|
|
|
|
model.set "users.#{userId}",
|
|
|
|
|
name: 'User ' + userId
|
|
|
|
|
money: 0
|
|
|
|
|
exp: 0
|
|
|
|
|
lvl: 1
|
2012-06-20 20:45:39 +00:00
|
|
|
hp: 50
|
|
|
|
|
|
2012-06-08 19:03:29 +00:00
|
|
|
get '/', (page, model) ->
|
|
|
|
|
# Render page if a userId is already stored in session data
|
|
|
|
|
userId = model.get '_session.userId'
|
|
|
|
|
# Otherwise, select a new userId and initialize user
|
|
|
|
|
if !userId
|
|
|
|
|
userId = newUser(model, userId)
|
2012-06-20 21:13:47 +00:00
|
|
|
|
2012-06-26 19:51:50 +00:00
|
|
|
userQ = "users.#{userId}"
|
|
|
|
|
model.subscribe userQ,\
|
|
|
|
|
model.query("#{userQ}.tasks").where('type').equals('habit'),\
|
|
|
|
|
model.query("#{userQ}.tasks").where('type').equals('daily'),\
|
|
|
|
|
model.query("#{userQ}.tasks").where('type').equals('todo'),\
|
|
|
|
|
model.query("#{userQ}.tasks").where('type').equals('reward'),\
|
|
|
|
|
(err, user, habits, dailys, todos, rewards) ->
|
|
|
|
|
|
2012-05-02 19:50:43 +00:00
|
|
|
model.ref '_user', user
|
2012-05-09 16:26:43 +00:00
|
|
|
|
2012-06-26 19:51:50 +00:00
|
|
|
model.refList "_habitList", habits.path(), "_user.habitIds"
|
|
|
|
|
model.refList "_dailyList", dailys.path(), "_user.dailyIds"
|
|
|
|
|
model.refList "_todoList", todos.path(), "_user.todoIds"
|
|
|
|
|
model.refList "_rewardList", rewards.path(), "_user.rewardIds"
|
2012-06-26 20:51:47 +00:00
|
|
|
unless habits.get() or dailys.get() or todos.get() or rewards.get()
|
|
|
|
|
model.push '_habitList', {type: 'habit', text: 'Take the stairs', notes: 'Test Notes', value: 0, up: true, down: true}
|
|
|
|
|
model.push '_dailyList', {type: 'daily', text: 'Go to the gym', notes: '', value: 0, completed: false }
|
|
|
|
|
model.push '_todoList', {type: 'todo', text: 'Make a doctor appointment', notes: '', value: 0, completed: false }
|
|
|
|
|
model.push '_rewardList', {type: 'reward', text: '1 TV episode', notes: '', value: 20 }
|
2012-06-20 21:13:47 +00:00
|
|
|
|
|
|
|
|
# http://tibia.wikia.com/wiki/Formula
|
|
|
|
|
model.fn '_tnl', '_user.lvl', (lvl) -> 50 * Math.pow(lvl, 2) - 150 * lvl + 200
|
2012-06-20 21:43:23 +00:00
|
|
|
|
2012-04-27 17:04:44 +00:00
|
|
|
page.render()
|
|
|
|
|
|
2012-06-10 00:15:33 +00:00
|
|
|
## VIEW HELPERS ##
|
2012-06-27 13:37:36 +00:00
|
|
|
view.fn 'taskClasses', (type, completed, value, hideCompleted) ->
|
|
|
|
|
#TODO figure out how to just pass in the task model, so i can access all these properties from one object
|
2012-06-20 21:13:47 +00:00
|
|
|
classes = type
|
2012-06-26 12:45:35 +00:00
|
|
|
classes += " completed" if completed
|
2012-06-27 13:37:36 +00:00
|
|
|
if type == 'todo'
|
|
|
|
|
classes += " hide" if (hideCompleted and completed) or (!hideCompleted and !completed)
|
|
|
|
|
|
2012-06-26 12:02:16 +00:00
|
|
|
switch
|
2012-06-26 15:02:29 +00:00
|
|
|
when value<-8 then classes += ' color-worst'
|
|
|
|
|
when value>=-8 and value<-5 then classes += ' color-worse'
|
|
|
|
|
when value>=-5 and value<-1 then classes += ' color-bad'
|
|
|
|
|
when value>=-1 and value<1 then classes += ' color-neutral'
|
|
|
|
|
when value>=1 and value<5 then classes += ' color-good'
|
|
|
|
|
when value>=5 and value<10 then classes += ' color-better'
|
|
|
|
|
when value>=10 then classes += ' color-best'
|
2012-06-10 00:15:33 +00:00
|
|
|
return classes
|
2012-06-20 21:30:10 +00:00
|
|
|
|
2012-06-20 22:34:15 +00:00
|
|
|
view.fn "percent", (x, y) ->
|
|
|
|
|
x=1 if x==0
|
|
|
|
|
Math.round(x/y*100)
|
|
|
|
|
|
2012-06-20 21:30:10 +00:00
|
|
|
view.fn "round", (num) ->
|
|
|
|
|
Math.round num
|
|
|
|
|
|
|
|
|
|
view.fn "gold", (num) ->
|
2012-06-20 22:34:15 +00:00
|
|
|
num.toFixed(1).split('.')[0] if num
|
2012-06-20 21:30:10 +00:00
|
|
|
|
|
|
|
|
view.fn "silver", (num) ->
|
2012-06-20 22:34:15 +00:00
|
|
|
num.toFixed(1).split('.')[1] if num
|
2012-04-27 02:19:31 +00:00
|
|
|
|
|
|
|
|
## CONTROLLER FUNCTIONS ##
|
|
|
|
|
|
|
|
|
|
ready (model) ->
|
2012-06-20 21:13:47 +00:00
|
|
|
|
|
|
|
|
#TODO remove this!!!!! dangerous temporary debugging helper
|
|
|
|
|
window.model = model
|
2012-06-25 01:51:13 +00:00
|
|
|
|
2012-06-25 02:35:04 +00:00
|
|
|
$('.task-notes').popover()
|
2012-06-27 13:37:36 +00:00
|
|
|
|
|
|
|
|
model.set('_hideCompleted', true)
|
|
|
|
|
$('a[data-toggle="tab"]').on 'shown', (e) ->
|
|
|
|
|
#see http://twitter.github.com/bootstrap/javascript.html#tabs
|
|
|
|
|
hideCompleted = if $(e.target).attr('href') == '#tab1' then true else false
|
|
|
|
|
model.set('_hideCompleted', hideCompleted)
|
2012-06-25 02:35:04 +00:00
|
|
|
|
2012-05-03 01:07:36 +00:00
|
|
|
lists = [ 'habit', 'daily', 'todo', 'reward']
|
|
|
|
|
|
2012-05-09 16:26:43 +00:00
|
|
|
for type in lists
|
|
|
|
|
list = model.at "_#{type}List"
|
2012-06-20 21:13:47 +00:00
|
|
|
|
2012-05-03 01:07:36 +00:00
|
|
|
# Make the list draggable using jQuery UI
|
2012-06-26 16:01:57 +00:00
|
|
|
ul = $("ul.#{type}s")
|
2012-05-03 01:07:36 +00:00
|
|
|
ul.sortable
|
2012-06-26 15:44:37 +00:00
|
|
|
dropOnEmpty: false
|
|
|
|
|
cursor: "move"
|
|
|
|
|
items: "li"
|
|
|
|
|
opacity: 0.4
|
|
|
|
|
scroll: true
|
2012-05-03 01:07:36 +00:00
|
|
|
axis: 'y'
|
|
|
|
|
update: (e, ui) ->
|
|
|
|
|
item = ui.item[0]
|
|
|
|
|
domId = item.id
|
|
|
|
|
id = item.getAttribute 'data-id'
|
|
|
|
|
to = ul.children().index(item)
|
|
|
|
|
# Use the Derby ignore option to suppress the normal move event
|
|
|
|
|
# binding, since jQuery UI will move the element in the DOM.
|
|
|
|
|
# Also, note that refList index arguments can either be an index
|
|
|
|
|
# or the item's id property
|
|
|
|
|
list.pass(ignore: domId).move {id}, to
|
2012-06-26 15:34:45 +00:00
|
|
|
|
|
|
|
|
#TODO: implement this for completed tab
|
|
|
|
|
# clearCompleted: ->
|
|
|
|
|
# _.each @options.habits.doneTodos(), (todo) ->
|
|
|
|
|
# todo.destroy()
|
|
|
|
|
# @render()
|
|
|
|
|
# return false
|
2012-06-26 15:31:58 +00:00
|
|
|
|
|
|
|
|
#TODO: Implement this for cron
|
|
|
|
|
# # Note: Set 12am daily cron for this
|
|
|
|
|
# # At end of day, add value to all incomplete Daily & Todo tasks (further incentive)
|
|
|
|
|
# # For incomplete Dailys, deduct experience
|
|
|
|
|
# def self.clear_done
|
|
|
|
|
# Habit.where('habit_type in (2,3)').collect do |h|
|
|
|
|
|
# unless h.done
|
|
|
|
|
# value = (h.score < 0) ? (( -0.1 * h.score + 1 ) * -1) : (( 0.9 ** h.score ) * -1)
|
|
|
|
|
# # Deduct experience for missed Daily tasks,
|
|
|
|
|
# # but not for Todos (just increase todo's value)
|
|
|
|
|
# if (h.habit_type==2)
|
|
|
|
|
# h.user.hp += value
|
|
|
|
|
# h.user.save
|
|
|
|
|
# end
|
|
|
|
|
# h.score += value
|
|
|
|
|
# end
|
|
|
|
|
# h.done = false if (h.habit_type==2)
|
|
|
|
|
# h.save
|
|
|
|
|
# end
|
|
|
|
|
# end
|
2012-06-20 21:30:10 +00:00
|
|
|
|
2012-06-09 18:25:10 +00:00
|
|
|
exports.addTask = (e, el, next) ->
|
|
|
|
|
type = $(el).attr('data-task-type')
|
|
|
|
|
list = model.at "_#{type}List"
|
|
|
|
|
newModel = model.at('_new' + type.charAt(0).toUpperCase() + type.slice(1))
|
2012-04-27 17:04:44 +00:00
|
|
|
# Don't add a blank todo
|
2012-06-09 18:25:10 +00:00
|
|
|
return unless text = view.escapeHtml newModel.get()
|
|
|
|
|
newModel.set ''
|
|
|
|
|
switch type
|
2012-06-20 21:43:23 +00:00
|
|
|
|
2012-06-09 18:25:10 +00:00
|
|
|
when 'habit'
|
2012-06-26 15:02:29 +00:00
|
|
|
list.push {type: type, text: text, notes: '', value: 0, up: true, down: true}
|
2012-06-20 21:43:23 +00:00
|
|
|
|
2012-06-09 18:25:10 +00:00
|
|
|
when 'reward'
|
2012-06-26 15:02:29 +00:00
|
|
|
list.push {type: type, text: text, notes: '', value: 20 }
|
2012-06-20 21:43:23 +00:00
|
|
|
|
2012-06-09 18:25:10 +00:00
|
|
|
when 'daily', 'todo'
|
2012-06-26 15:02:29 +00:00
|
|
|
list.push {type: type, text: text, notes: '', value: 0, completed: false }
|
2012-06-25 22:26:12 +00:00
|
|
|
|
|
|
|
|
# list.on 'set', '*.completed', (i, completed, previous, isLocal) ->
|
|
|
|
|
# # Move the item to the bottom if it was checked off
|
|
|
|
|
# list.move i, -1 if completed && isLocal
|
2012-04-27 17:04:44 +00:00
|
|
|
|
|
|
|
|
exports.del = (e) ->
|
|
|
|
|
# Derby extends model.at to support creation from DOM nodes
|
|
|
|
|
model.at(e.target).remove()
|
2012-06-25 01:51:13 +00:00
|
|
|
|
|
|
|
|
exports.toggleEdit = (e, el) ->
|
2012-06-25 02:49:32 +00:00
|
|
|
selector = $(el).attr('data-selector')
|
|
|
|
|
if selector.charAt(0) == '$'
|
|
|
|
|
selector = '\\' + selector
|
|
|
|
|
$('#'+selector).toggle()
|
2012-06-20 20:40:59 +00:00
|
|
|
|
|
|
|
|
exports.vote = (e, el, next) ->
|
2012-06-10 00:41:21 +00:00
|
|
|
direction = $(el).attr('data-direction')
|
2012-06-26 14:55:24 +00:00
|
|
|
direction = 'up' if direction == 'true/'
|
|
|
|
|
direction = 'down' if direction == 'false/'
|
|
|
|
|
|
2012-06-20 20:40:59 +00:00
|
|
|
#TODO this should be model.at(el), shouldn't have to find parent
|
|
|
|
|
task = model.at $(el).parents('li')[0]
|
|
|
|
|
user = model.at '_user'
|
2012-06-10 00:41:21 +00:00
|
|
|
# For negative values, use a line: something like y=-.1x+1
|
|
|
|
|
# For positibe values, taper off with inverse log: y=.9^x
|
|
|
|
|
# Would love to use inverse log for the whole thing, but after 13 fails it hits infinity
|
|
|
|
|
sign = if (direction == "up") then 1 else -1
|
2012-06-26 15:02:29 +00:00
|
|
|
value = task.get('value')
|
2012-06-10 00:41:21 +00:00
|
|
|
delta = 0
|
2012-06-26 15:02:29 +00:00
|
|
|
if value < 0
|
|
|
|
|
delta = (( -0.1 * value + 1 ) * sign)
|
2012-06-10 00:41:21 +00:00
|
|
|
else
|
2012-06-26 15:02:29 +00:00
|
|
|
delta = (( Math.pow(0.9, value) ) * sign)
|
2012-06-20 21:43:23 +00:00
|
|
|
|
2012-06-26 15:02:29 +00:00
|
|
|
# Don't adjust values for rewards, or for habits that don't have both + and -
|
|
|
|
|
adjustvalue = (task.get('type') != 'reward')
|
2012-06-10 00:41:21 +00:00
|
|
|
if (task.get('type') == 'habit') and (task.get("up")==false or task.get("down")==false)
|
2012-06-26 15:02:29 +00:00
|
|
|
adjustvalue = false
|
|
|
|
|
value += delta if adjustvalue
|
2012-06-20 21:43:23 +00:00
|
|
|
|
2012-06-26 14:55:24 +00:00
|
|
|
# up/down -voting as checkbox & assigning as completed, 2 birds one stone
|
|
|
|
|
completed = task.get("completed")
|
2012-06-10 01:16:11 +00:00
|
|
|
if task.get('type') != 'habit'
|
2012-06-26 14:55:24 +00:00
|
|
|
completed = true if direction=="up"
|
|
|
|
|
completed = false if direction=="down"
|
2012-06-26 15:02:29 +00:00
|
|
|
task.set('value', value)
|
2012-06-26 14:55:24 +00:00
|
|
|
task.set('completed', completed)
|
2012-06-20 21:43:23 +00:00
|
|
|
|
2012-06-10 00:41:21 +00:00
|
|
|
# Update the user's status
|
|
|
|
|
[money, hp, exp, lvl] = [user.get('money'), user.get('hp'), user.get('exp'), user.get('lvl')]
|
|
|
|
|
|
|
|
|
|
if task.get('type') == 'reward'
|
|
|
|
|
# purchase item
|
2012-06-26 15:02:29 +00:00
|
|
|
money -= task.get('value')
|
2012-06-10 00:41:21 +00:00
|
|
|
# if too expensive, reduce health & zero money
|
|
|
|
|
if money < 0
|
|
|
|
|
hp += money # hp - money difference
|
|
|
|
|
money = 0
|
|
|
|
|
|
|
|
|
|
# If positive delta, add points to exp & money
|
|
|
|
|
# Only take away mony if it was a mistake (aka, a checkbox)
|
|
|
|
|
if delta > 0 or (task.get('type') == 'daily' or task.get('type') == 'todo')
|
|
|
|
|
exp += delta
|
|
|
|
|
money += delta
|
|
|
|
|
# Deduct from health (rewards case handled above)
|
|
|
|
|
else if task.get('type') != 'reward'
|
|
|
|
|
hp += delta
|
|
|
|
|
|
2012-06-26 15:07:20 +00:00
|
|
|
tnl = model.at('_tnl').get()
|
2012-06-10 00:41:21 +00:00
|
|
|
# level up & carry-over exp
|
2012-06-26 15:07:20 +00:00
|
|
|
if exp >= tnl
|
2012-06-10 01:16:11 +00:00
|
|
|
exp -= tnl
|
2012-06-10 00:41:21 +00:00
|
|
|
lvl += 1
|
|
|
|
|
|
|
|
|
|
# game over
|
|
|
|
|
if hp < 0
|
|
|
|
|
[hp, lvl, exp] = [50, 1, 0]
|
2012-06-20 20:40:59 +00:00
|
|
|
|
|
|
|
|
user.set('money', money)
|
|
|
|
|
user.set('hp', hp)
|
|
|
|
|
user.set('exp', exp)
|
|
|
|
|
user.set('lvl', lvl)
|
|
|
|
|
#[user.money, user.hp, user.exp, user.lvl] = [money, hp, exp, lvl]
|
|
|
|
|
|
2012-06-26 15:44:10 +00:00
|
|
|
## SHORTCUTS ##
|
2012-04-27 17:04:44 +00:00
|
|
|
|
|
|
|
|
exports.shortcuts = (e) ->
|
|
|
|
|
return unless e.metaKey || e.ctrlKey
|
|
|
|
|
code = e.which
|
|
|
|
|
return unless command = (switch code
|
|
|
|
|
when 66 then 'bold' # Bold: Ctrl/Cmd + B
|
|
|
|
|
when 73 then 'italic' # Italic: Ctrl/Cmd + I
|
|
|
|
|
when 32 then 'removeFormat' # Clear formatting: Ctrl/Cmd + Space
|
|
|
|
|
when 220 then 'removeFormat' # Clear formatting: Ctrl/Cmd + \
|
|
|
|
|
else null
|
|
|
|
|
)
|
|
|
|
|
document.execCommand command, false, null
|
|
|
|
|
e.preventDefault() if e.preventDefault
|
|
|
|
|
return false
|
|
|
|
|
|
|
|
|
|
# Tell Firefox to use elements for styles instead of CSS
|
|
|
|
|
# See: https://developer.mozilla.org/en/Rich-Text_Editing_in_Mozilla
|
|
|
|
|
document.execCommand 'useCSS', false, true
|
|
|
|
|
document.execCommand 'styleWithCSS', false, false
|