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-07-27 23:38:28 +00:00
2012-07-28 21:02:09 +00:00
# Custom requires
content = require ( ' ./content ' )
scoring = require ( ' ./scoring ' )
schema = require ( ' ./schema ' )
2012-07-30 21:25:04 +00:00
helpers = require ( ' ./helpers ' )
helpers . viewHelpers ( view )
2012-06-20 21:30:10 +00:00
2012-07-28 01:11:53 +00:00
# ========== ROUTES ==========
2012-07-08 20:59:07 +00:00
2012-07-19 18:48:49 +00:00
get ' /:uidParam? ' , (page, model, {uidParam}) ->
model . fetch ' users ' , (err, users) ->
2012-07-17 19:55:36 +00:00
2012-07-17 22:10:17 +00:00
# Previously saved session (eg, http://localhost/{guid}) (temporary solution until authentication built)
2012-07-19 18:48:49 +00:00
if uidParam ? and users . get ( uidParam )
model . set ' _userId ' , uidParam # set for this request
model.session.userId = uidParam # and for next requests
2012-07-17 19:55:36 +00:00
# Current browser session
2012-07-17 18:47:19 +00:00
# The session middleware will assign a _userId automatically
2012-07-17 19:55:36 +00:00
userId = model . get ' _userId '
2012-07-19 18:48:49 +00:00
user = users . get ( userId )
# Else, select a new userId and initialize user
unless user ?
2012-07-28 21:02:09 +00:00
newUser = schema . userSchema
2012-07-17 19:45:05 +00:00
for task in content . defaultTasks
2012-07-21 01:45:53 +00:00
guid = task.id = require ( ' derby/node_modules/racer ' ) . uuid ( )
2012-07-17 19:45:05 +00:00
newUser . tasks [ guid ] = task
switch task . type
when ' habit ' then newUser . habitIds . push guid
when ' daily ' then newUser . dailyIds . push guid
when ' todo ' then newUser . todoIds . push guid
when ' reward ' then newUser . rewardIds . push guid
2012-07-18 12:59:29 +00:00
users . set userId , newUser
2012-07-19 18:48:49 +00:00
2012-07-21 01:39:29 +00:00
# #TODO these *Access functions aren't being called, why?
# model.store.accessControl = true
# model.store.readPathAccess 'users.*', (id, accept) ->
# console.log "model.writeAccess called with id:#{id}" # never called
# accept(id == userId)
# model.store.writeAccess '*', 'users.*', (id, accept) ->
# console.log "model.writeAccess called with id:#{id}" # never called
# accept(id == userId)
2012-07-19 18:48:49 +00:00
getHabits ( page , model , userId )
getHabits = (page, model, userId) ->
2012-07-08 20:59:07 +00:00
2012-07-19 18:48:49 +00:00
model . subscribe " users. #{ userId } " , (err, user) ->
2012-07-28 02:29:02 +00:00
console . log { userId : userId , err : err } , " app/index.coffee: model.subscribe "
# => {userId: 26c48325-2fea-4e2e-a60f-a5fa28d7b410, err: Unauthorized: No access control declared for path users.26c48325-2fea-4e2e-a60f-a5fa28d7b410 }
2012-07-08 20:59:07 +00:00
model . ref ' _user ' , user
2012-07-17 19:33:08 +00:00
# Store
2012-07-09 01:20:30 +00:00
model . set ' _items '
armor: content . items . armor [ parseInt ( user . get ( ' items.armor ' ) ) + 1 ]
weapon: content . items . weapon [ parseInt ( user . get ( ' items.weapon ' ) ) + 1 ]
potion: content . items . potion
reroll: content . items . reroll
2012-07-08 23:37:36 +00:00
2012-07-08 20:59:07 +00:00
# http://tibia.wikia.com/wiki/Formula
2012-07-21 01:39:29 +00:00
model . fn ' _user._tnl ' , ' _user.stats.lvl ' , (lvl) -> 50 * Math . pow ( lvl , 2 ) - 150 * lvl + 200
2012-07-08 20:59:07 +00:00
# Default Tasks
model . refList " _habitList " , " _user.tasks " , " _user.habitIds "
model . refList " _dailyList " , " _user.tasks " , " _user.dailyIds "
model . refList " _todoList " , " _user.tasks " , " _user.todoIds "
2012-07-27 23:38:28 +00:00
model . refList " _completedList " , " _user.tasks " , " _user.completedIds "
2012-07-08 20:59:07 +00:00
model . refList " _rewardList " , " _user.tasks " , " _user.rewardIds "
2012-07-28 03:10:20 +00:00
2012-07-08 20:59:07 +00:00
page . render ( )
2012-04-27 02:19:31 +00:00
2012-07-28 01:11:53 +00:00
# ========== CONTROLLER FUNCTIONS ==========
2012-04-27 02:19:31 +00:00
ready (model) ->
2012-07-08 03:43:35 +00:00
2012-07-17 19:55:36 +00:00
model . set ' _purl ' , window . location . origin + ' / ' + model . get ( ' _userId ' )
2012-07-11 23:27:53 +00:00
2012-07-09 01:51:25 +00:00
$ ( ' [rel=popover] ' ) . popover ( )
#TODO: this isn't very efficient, do model.on set for specific attrs for popover
model . on ' set ' , ' * ' , ->
$ ( ' [rel=popover] ' ) . popover ( )
2012-06-27 13:37:36 +00:00
2012-07-07 04:07:30 +00:00
# Make the lists draggable using jQuery UI
# Note, have to setup helper function here and call it for each type later
# due to variable binding of "type"
setupSortable = (type) ->
$ ( " ul. #{ type } s " ) . 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 '
2012-07-07 04:07:30 +00:00
to = $ ( " ul. #{ type } s " ) . children ( ) . index ( item )
2012-05-03 01:07:36 +00:00
# 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
2012-07-07 04:07:30 +00:00
model . at ( " _ #{ type } List " ) . pass ( ignore: domId ) . move { id } , to
setupSortable ( type ) for type in [ ' habit ' , ' daily ' , ' todo ' , ' reward ' ]
2012-07-07 16:31:51 +00:00
tour = new Tour ( )
2012-07-08 02:34:34 +00:00
for step in content . tourSteps
tour . addStep
element: step . element
title: step . title
content: step . content
placement: step . placement
2012-07-07 16:31:51 +00:00
tour . start ( )
2012-06-26 15:34:45 +00:00
2012-07-28 01:56:21 +00:00
model . on ' set ' , ' _user.tasks.*.completed ' , (i, completed, previous, isLocal, passed) ->
return if passed . cron ? # Don't do this stuff on cron
2012-07-28 00:47:59 +00:00
direction = () ->
return ' up ' if completed == true and previous == false
return ' down ' if completed == false and previous == true
2012-07-28 01:56:21 +00:00
throw new Error ( " Direction neither ' up ' nor ' down ' on checkbox set. " )
2012-07-28 00:47:59 +00:00
# Score the user based on todo task
task = model . at ( " _user.tasks. #{ i } " )
2012-07-28 21:02:09 +00:00
scoring . score ( { user : model . at ( ' _user ' ) , task : task , direction : direction ( ) } )
2012-07-28 00:47:59 +00:00
# Then move the todos to/from _todoList/_completedList
if task . get ( ' type ' ) == ' todo '
2012-07-28 02:29:02 +00:00
[ from , to ] = if ( direction ( ) == ' up ' ) then [ ' todo ' , ' completed ' ] else [ ' completed ' , ' todo ' ]
[ from , to ] = [ " _user. #{ from } Ids " , " _user. #{ to } Ids " ]
# Remove from source (just remove the id from id-list)
fromIds = model . get ( from )
fromIds . splice ( fromIds . indexOf ( i ) , 1 )
model . set from , fromIds
# Push to target (just the id to id-list)
toIds = model . get ( to )
toIds . push i
model . set to , toIds
2012-07-28 00:35:05 +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
2012-07-07 02:09:51 +00:00
exports.del = (e, el) ->
2012-04-27 17:04:44 +00:00
# Derby extends model.at to support creation from DOM nodes
2012-07-07 02:09:51 +00:00
task = model . at ( e . target )
#TODO bug where I have to delete from _users.tasks AND _{type}List,
# fix when query subscriptions implemented properly
2012-07-08 22:58:11 +00:00
model . del ( ' _user.tasks. ' + task . get ( ' id ' ) )
task . remove ( )
2012-06-25 01:51:13 +00:00
2012-07-28 04:01:58 +00:00
exports.clearCompleted = (e, el) ->
_ . each model . get ( ' _completedList ' ) , (task) ->
model . del ( ' _user.tasks. ' + task . id )
model . set ( ' _user.completedIds ' , [ ] )
2012-06-29 13:45:50 +00:00
exports.toggleTaskEdit = (e, el) ->
2012-07-17 20:35:26 +00:00
hideId = $ ( el ) . attr ( ' data-hide-id ' )
toggleId = $ ( el ) . attr ( ' data-toggle-id ' )
$ ( document . getElementById ( hideId ) ) . hide ( )
$ ( document . getElementById ( toggleId ) ) . toggle ( )
2012-06-29 08:33:09 +00:00
2012-07-07 02:46:22 +00:00
exports.toggleChart = (e, el) ->
2012-07-17 20:35:26 +00:00
hideSelector = $ ( el ) . attr ( ' data-hide-id ' )
chartSelector = $ ( el ) . attr ( ' data-toggle-id ' )
2012-07-07 02:46:22 +00:00
historyPath = $ ( el ) . attr ( ' data-history-path ' )
$ ( document . getElementById ( hideSelector ) ) . hide ( )
$ ( document . getElementById ( chartSelector ) ) . toggle ( )
2012-06-29 08:33:09 +00:00
matrix = [ [ ' Date ' , ' Score ' ] ]
2012-07-07 02:46:22 +00:00
for obj in model . get ( historyPath )
2012-06-29 08:51:50 +00:00
date = new Date ( obj . date )
2012-07-11 19:38:01 +00:00
readableDate = date . toISOString ( ) #use toDateString() when done debugging
2012-06-29 08:51:50 +00:00
matrix . push [ readableDate , obj . value ]
2012-06-29 08:33:09 +00:00
data = google . visualization . arrayToDataTable matrix
2012-06-29 08:44:52 +00:00
2012-06-29 08:33:09 +00:00
options = {
title: ' History '
2012-06-29 08:44:52 +00:00
#TODO use current background color: $(el).css('background-color), but convert to hex (see http://goo.gl/ql5pR)
backgroundColor: ' whiteSmoke '
2012-06-29 08:33:09 +00:00
}
2012-07-07 02:46:22 +00:00
chart = new google . visualization . LineChart ( document . getElementById ( chartSelector ) )
2012-06-29 08:33:09 +00:00
chart . draw ( data , options )
2012-06-29 13:45:50 +00:00
2012-07-08 22:58:11 +00:00
exports.buyItem = (e, el, next) ->
user = model . at ' _user '
2012-07-09 01:20:30 +00:00
#TODO: this should be working but it's not. so instead, i'm passing all needed values as data-attrs
# item = model.at(e.target)
2012-07-08 22:58:11 +00:00
money = user . get ' stats.money '
2012-07-09 01:20:30 +00:00
[ type , value , index ] = [ $ ( el ) . attr ( ' data-type ' ) , $ ( el ) . attr ( ' data-value ' ) , $ ( el ) . attr ( ' data-index ' ) ]
2012-07-08 22:58:11 +00:00
2012-07-09 01:20:30 +00:00
return if money < value
2012-07-08 22:58:11 +00:00
user . set ' stats.money ' , money - value
if type == ' armor '
2012-07-09 01:20:30 +00:00
user . set ' items.armor ' , index
model . set ' _items.armor ' , content . items . armor [ parseInt ( index ) + 1 ]
2012-07-08 22:58:11 +00:00
else if type == ' weapon '
2012-07-09 01:20:30 +00:00
user . set ' items.weapon ' , index
model . set ' _items.weapon ' , content . items . weapon [ parseInt ( index ) + 1 ]
2012-07-08 22:58:11 +00:00
else if type == ' potion '
hp = user . get ' stats.hp '
hp += 15
hp = 50 if hp > 50
user . set ' stats.hp ' , hp
else if type == ' reroll '
2012-07-09 01:35:32 +00:00
for taskId of user . get ( ' tasks ' )
task = model . at ( ' _user.tasks. ' + taskId )
task . set ( ' value ' , 0 ) unless task . get ( ' type ' ) == ' reward '
2012-07-09 02:23:57 +00:00
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-07-21 01:39:29 +00:00
user = model . at ( ' _user ' )
2012-06-20 20:40:59 +00:00
task = model . at $ ( el ) . parents ( ' li ' ) [ 0 ]
2012-07-21 01:39:29 +00:00
2012-07-28 21:02:09 +00:00
scoring . score ( { user : user , task : task , direction : direction } )
2012-06-28 22:11:13 +00:00
2012-07-28 01:11:53 +00:00
exports.revive = (e, el) ->
stats = model . at ' _user.stats '
stats . set ' hp ' , 50 ; stats . set ' lvl ' , 1 ; stats . set ' exp ' , 0 ; stats . set ' money ' , 0
model . set ' _user.items.armor ' , 0
model . set ' _user.items.weapon ' , 0
model . set ' _items.armor ' , content . items . armor [ 1 ]
model . set ' _items.weapon ' , content . items . weapon [ 1 ]
2012-07-09 02:23:57 +00:00
2012-07-28 01:11:53 +00:00
# ========== CRON ==========
2012-07-09 02:23:57 +00:00
#TODO: remove when cron implemented
2012-07-11 19:58:17 +00:00
exports.poormanscron = poormanscron = ->
2012-07-11 19:24:23 +00:00
model . setNull ( ' _user.lastCron ' , new Date ( ) )
2012-07-13 14:43:53 +00:00
lastCron = new Date ( ( new Date ( model . get ( ' _user.lastCron ' ) ) ) . toDateString ( ) ) # calculate as midnight
today = new Date ( ( new Date ) . toDateString ( ) ) # calculate as midnight
2012-07-11 19:26:27 +00:00
DAY = 1000 * 60 * 60 * 24
2012-07-11 18:37:47 +00:00
daysPassed = Math . floor ( ( today . getTime ( ) - lastCron . getTime ( ) ) / DAY )
2012-07-09 02:23:57 +00:00
if daysPassed > 0
2012-07-11 19:13:08 +00:00
model . set ( ' _user.lastCron ' , today ) # reset cron
2012-07-16 23:01:18 +00:00
for n in [ 1 . . daysPassed ]
console . log { today: today , lastCron: lastCron , daysPassed: daysPassed , n : n } , " [debug] Cron ( #{ today } , #{ n } ) "
2012-07-28 21:02:09 +00:00
scoring . tally ( model )
2012-07-11 14:17:47 +00:00
poormanscron ( ) # Run once on refresh
setInterval ( -> # Then run once every hour
poormanscron ( )
) , 3600000
2012-07-28 20:26:06 +00:00
# ========== DEBUGGING ==========
exports.endOfDayTally = (e, el) ->
2012-07-28 21:02:09 +00:00
scoring . tally ( model )
2012-07-28 20:26:06 +00:00
# Temporary solution to running updates against the schema when the code changes
exports.updateSchema = (e, el) ->
2012-07-28 21:02:09 +00:00
schema . updateSchema ( model )
2012-07-09 03:29:54 +00:00
2012-07-28 01:11:53 +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