Achievements now popup when polled for by the client
This commit is contained in:
parent
aa3fedeb02
commit
d766a24e11
12 changed files with 106 additions and 13 deletions
app
models
schemas/models
styles
templates
views/kinds
server
achievements
commons
plugins
users
|
@ -3,4 +3,8 @@ CocoModel = require './CocoModel'
|
|||
module.exports = class Achievement extends CocoModel
|
||||
@className: 'Achievement'
|
||||
@schema: require 'schemas/models/achievement'
|
||||
urlRoot: '/db/achievement'
|
||||
urlRoot: '/db/achievement'
|
||||
|
||||
initialize: (id) ->
|
||||
super()
|
||||
@set('_id', id) if id?
|
|
@ -1,6 +1,10 @@
|
|||
storage = require 'lib/storage'
|
||||
deltasLib = require 'lib/deltas'
|
||||
|
||||
class NewAchievementCollection extends Backbone.Collection
|
||||
initialize: (me = require('lib/auth').me) ->
|
||||
@url = "/db/user/#{me.id}/achievements?notified=false"
|
||||
|
||||
class CocoModel extends Backbone.Model
|
||||
idAttribute: "_id"
|
||||
loaded: false
|
||||
|
@ -86,6 +90,7 @@ class CocoModel extends Backbone.Model
|
|||
success(@, res) if success
|
||||
@markToRevert() if @_revertAttributes
|
||||
@clearBackup()
|
||||
CocoModel.pollAchievements()
|
||||
options.error = (model, res) =>
|
||||
error(@, res) if error
|
||||
return unless @notyErrors
|
||||
|
@ -266,4 +271,14 @@ class CocoModel extends Backbone.Model
|
|||
getURL: ->
|
||||
return if _.isString @url then @url else @url()
|
||||
|
||||
@pollAchievements: ->
|
||||
achievements = new NewAchievementCollection
|
||||
achievements.fetch(
|
||||
success: (collection) ->
|
||||
Backbone.Mediator.publish('achievements:new', collection) unless _.isEmpty(collection.models)
|
||||
)
|
||||
|
||||
|
||||
CocoModel.pollAchievements = _.debounce CocoModel.pollAchievements, 500
|
||||
|
||||
module.exports = CocoModel
|
||||
|
|
|
@ -21,12 +21,12 @@ MongoFindQuerySchema =
|
|||
type: 'object'
|
||||
patternProperties:
|
||||
#'^[-a-zA-Z0-9_]*$':
|
||||
'^[-a-zA-Z0-9]*$':
|
||||
'^[-a-zA-Z0-9\.]*$':
|
||||
oneOf: [
|
||||
#{ $ref: '#/definitions/' + MongoQueryOperatorSchema.id},
|
||||
{ type: 'string' }
|
||||
]
|
||||
additionalProperties: true
|
||||
additionalProperties: true # TODO make Treema accept new pattern matched keys
|
||||
definitions: {}
|
||||
|
||||
MongoFindQuerySchema.definitions[MongoQueryOperatorSchema.id] = MongoQueryOperatorSchema
|
||||
|
|
19
app/styles/notify.sass
Normal file
19
app/styles/notify.sass
Normal file
|
@ -0,0 +1,19 @@
|
|||
.notifyjs-achievement-base
|
||||
opacity: 0.85
|
||||
width: 200px
|
||||
background: #F5F5F5
|
||||
padding: 5px
|
||||
border-radius: 10px
|
||||
text-align: center
|
||||
|
||||
.achievement-title
|
||||
font-weight: bold
|
||||
|
||||
.achievement-image
|
||||
// pass
|
||||
|
||||
.achievement-name
|
||||
// pass
|
||||
|
||||
.achievement-description
|
||||
// pass
|
6
app/templates/achievement_notify.jade
Normal file
6
app/templates/achievement_notify.jade
Normal file
|
@ -0,0 +1,6 @@
|
|||
div
|
||||
div.clearfix
|
||||
div.achievement-title(data-notify-html="title")
|
||||
div.achievement-image(data-notify-html="image")
|
||||
div.achievement-name(data-notify-html="name")
|
||||
div.achievement-description(data-notify-html="description")
|
|
@ -12,7 +12,7 @@ body
|
|||
|
||||
a.navbar-brand(href='/')
|
||||
img(src="/images/pages/base/logo.png", title="CodeCombat - Learn how to code by playing a game", alt="CodeCombat")
|
||||
.collapse.navbar-collapse#collapsible-navbar
|
||||
.collapse.navbar-collapse#collapsible-navbar
|
||||
ul.nav.navbar-nav
|
||||
li.play
|
||||
a.header-font(href='/play', data-i18n="nav.play") Levels
|
||||
|
|
|
@ -6,6 +6,9 @@ CocoView = require './CocoView'
|
|||
{logoutUser, me} = require('lib/auth')
|
||||
locale = require 'locale/locale'
|
||||
|
||||
AchievementNotify = require '../../templates/achievement_notify'
|
||||
Achievement = require '../../models/Achievement'
|
||||
|
||||
filterKeyboardEvents = (allowedEvents, func) ->
|
||||
return (splat...) ->
|
||||
e = splat[0]
|
||||
|
@ -19,6 +22,41 @@ module.exports = class RootView extends CocoView
|
|||
'click .toggle-fullscreen': 'toggleFullscreen'
|
||||
'click .auth-button': 'onClickAuthbutton'
|
||||
|
||||
subscriptions:
|
||||
'achievements:new': 'handleNewAchievements'
|
||||
|
||||
initialize: ->
|
||||
$ ->
|
||||
$.notify.addStyle 'achievement',
|
||||
html: $(AchievementNotify())
|
||||
|
||||
showNewAchievement: (achievement) ->
|
||||
imageURL = '/file/' + achievement.get('icon')
|
||||
data =
|
||||
title: 'Achievement Unlocked'
|
||||
name: achievement.get('name')
|
||||
image: $("<img src='#{imageURL}' />")
|
||||
description: achievement.get('description')
|
||||
|
||||
options =
|
||||
autoHideDelay: 15000
|
||||
globalPosition: 'bottom right'
|
||||
showDuration: 400
|
||||
style: 'achievement'
|
||||
autoHide: true
|
||||
clickToHide: true
|
||||
|
||||
$.notify( data, options )
|
||||
|
||||
handleNewAchievements: (earnedAchievements) ->
|
||||
# TODO performance?
|
||||
_.each(earnedAchievements.models, (earnedAchievement) =>
|
||||
achievement = new Achievement(earnedAchievement.get('achievement'))
|
||||
achievement.fetch(
|
||||
success: @showNewAchievement
|
||||
)
|
||||
)
|
||||
|
||||
logoutAccount: ->
|
||||
logoutUser($('#login-email').val())
|
||||
|
||||
|
|
|
@ -16,7 +16,7 @@ AchievementSchema.methods.objectifyQuery = () ->
|
|||
try
|
||||
@set('query', JSON.parse(@get('query'))) if typeof @get('query') == "string"
|
||||
catch error
|
||||
#log.error "Couldn't convert query string to object because of #{error}"
|
||||
log.error "Couldn't convert query string to object because of #{error}"
|
||||
@set('query', {})
|
||||
|
||||
AchievementSchema.methods.stringifyQuery = () ->
|
||||
|
|
|
@ -13,7 +13,7 @@ EarnedAchievementSchema = new mongoose.Schema({
|
|||
default: false
|
||||
}, {strict:false})
|
||||
|
||||
# Maybe consider indexing on changed: -1 as well?
|
||||
EarnedAchievementSchema.index({user: 1, achievement: 1}, {unique: true, name: 'earned achievement index'})
|
||||
EarnedAchievementSchema.index({user: 1, changed: -1}, {name: 'latest '})
|
||||
|
||||
module.exports = EarnedAchievement = mongoose.model('EarnedAchievement', EarnedAchievementSchema)
|
|
@ -167,7 +167,7 @@ module.exports = class Handler
|
|||
projection = PROJECT
|
||||
else if req.query.project
|
||||
if @modelClass.className is 'User'
|
||||
projection = PROJECTION
|
||||
projection = PROJECT
|
||||
log.warn "Whoa, we haven't yet thought about public properties for User projection yet."
|
||||
else
|
||||
projection = {}
|
||||
|
|
|
@ -18,8 +18,6 @@ loadAchievements = ->
|
|||
|
||||
loadAchievements()
|
||||
|
||||
|
||||
# TODO make a difference between '$userID' and '$userObjectID' ?
|
||||
module.exports = AchievablePlugin = (schema, options) ->
|
||||
checkForAchievement = (doc) ->
|
||||
collectionName = doc.constructor.modelName
|
||||
|
@ -43,9 +41,11 @@ module.exports = AchievablePlugin = (schema, options) ->
|
|||
for achievement in achievements[category]
|
||||
query = achievement.get('query')
|
||||
isRepeatable = achievement.get('proportionalTo')?
|
||||
console.log 'isRepeatable: ' + isRepeatable
|
||||
alreadyAchieved = if isNew then false else LocalMongo.matchesQuery originalDocObj, query
|
||||
newlyAchieved = LocalMongo.matchesQuery(docObj, query)
|
||||
console.log 'isRepeatable: ' + isRepeatable
|
||||
console.log 'alreadyAchieved: ' + alreadyAchieved
|
||||
console.log 'newlyAchieved: ' + newlyAchieved
|
||||
|
||||
userObjectID = doc.get(achievement.get('userField'))
|
||||
userID = if _.isObject userObjectID then userObjectID.toHexString() else userObjectID # Standardize! Use strings, not ObjectId's
|
||||
|
|
|
@ -21,6 +21,12 @@ candidateProperties = [
|
|||
'jobProfile', 'jobProfileApproved', 'jobProfileNotes'
|
||||
]
|
||||
|
||||
parseLiteral = (literalString) ->
|
||||
return true if literalString is 'true'
|
||||
return false if literalString is 'false'
|
||||
return number if (number = Number(literalString)) isnt NaN
|
||||
literalString
|
||||
|
||||
UserHandler = class UserHandler extends Handler
|
||||
modelClass: User
|
||||
|
||||
|
@ -238,11 +244,16 @@ UserHandler = class UserHandler extends Handler
|
|||
@sendSuccess(res, documents)
|
||||
|
||||
getEarnedAchievements: (req, res, userID) ->
|
||||
query = EarnedAchievement.find(user: userID)
|
||||
queryObject = {$query: {user: userID}, $orderby: {changed: -1}}
|
||||
queryObject.$query[key] = parseLiteral(val) for key, val of req.query
|
||||
query = EarnedAchievement.find(queryObject)
|
||||
query.exec (err, documents) =>
|
||||
return @sendDatabaseError(res, err) if err?
|
||||
documents = (@formatEntity(req, doc) for doc in documents)
|
||||
@sendSuccess(res, documents)
|
||||
cleandocs = (@formatEntity(req, doc) for doc in documents)
|
||||
for doc in documents # Maybe move this logic elsewhere
|
||||
doc.set('notified', true)
|
||||
doc.save()
|
||||
@sendSuccess(res, cleandocs)
|
||||
|
||||
agreeToEmployerAgreement: (req, res) ->
|
||||
userIsAnonymous = req.user?.get('anonymous')
|
||||
|
|
Reference in a new issue