From 63401025f6f7bc1a906d07fef1c58eb584875577 Mon Sep 17 00:00:00 2001 From: Ruben Vereecken Date: Fri, 4 Jul 2014 15:13:06 +0200 Subject: [PATCH 01/83] proofed the SearchView's runSearch --- app/views/kinds/SearchView.coffee | 1 + 1 file changed, 1 insertion(+) diff --git a/app/views/kinds/SearchView.coffee b/app/views/kinds/SearchView.coffee index 8f7a17420..485fba23b 100644 --- a/app/views/kinds/SearchView.coffee +++ b/app/views/kinds/SearchView.coffee @@ -46,6 +46,7 @@ module.exports = class SearchView extends View searchInput.focus() runSearch: => + return unless @$el term = @$el.find('input#search').val() return if @sameSearch(term) @removeOldSearch() From 488d49e286e47a28b2a07637f2a41bb04c2d526d Mon Sep 17 00:00:00 2001 From: Ruben Vereecken Date: Fri, 13 Jun 2014 18:04:25 +0200 Subject: [PATCH 02/83] intermediate --- achievement_fixtures.js | 12 ---------- server/achievements/Achievement.coffee | 20 ++++++++-------- server/plugins/achievements.coffee | 4 +--- .../server/functional/achievement.spec.coffee | 24 +++++++++++++++---- 4 files changed, 30 insertions(+), 30 deletions(-) delete mode 100644 achievement_fixtures.js diff --git a/achievement_fixtures.js b/achievement_fixtures.js deleted file mode 100644 index 56b6fb42f..000000000 --- a/achievement_fixtures.js +++ /dev/null @@ -1,12 +0,0 @@ -// Fixtures - -db.achievements.insert({ - query: '{"level.original": "52d97ecd32362bc86e004e87"}', - index: true, - slug: 'dungeon-arena-started', - name: 'Dungeon Arena started', - worth: 1, - collection: 'level.session', - description: 'Started playing Dungeon Arena.', - userField: 'creator' -}); diff --git a/server/achievements/Achievement.coffee b/server/achievements/Achievement.coffee index 83562018e..82c565c96 100644 --- a/server/achievements/Achievement.coffee +++ b/server/achievements/Achievement.coffee @@ -3,6 +3,7 @@ jsonschema = require('../../app/schemas/models/achievement') log = require 'winston' util = require '../../app/lib/utils' plugins = require('../plugins/plugins') +AchievablePlugin = require '../plugins/achievements' # `pre` and `post` are not called for update operations executed directly on the database, # including `Model.update`,`.findByIdAndUpdate`,`.findOneAndUpdate`, `.findOneAndRemove`,and `.findByIdAndRemove`.order @@ -23,23 +24,22 @@ AchievementSchema.methods.objectifyQuery = -> AchievementSchema.methods.stringifyQuery = -> @set('query', JSON.stringify(@get('query'))) if typeof @get('query') != "string" - getExpFunction: -> - kind = @get('function')?.kind or jsonschema.function.default.kind - parameters = @get('function')?.parameters or jsonschema.function.default.parameters - return utils.functionCreators[kind](parameters) if kind of utils.functionCreators +AchievementSchema.methods.getExpFunction: -> + kind = @get('function')?.kind or jsonschema.function.default.kind + parameters = @get('function')?.parameters or jsonschema.function.default.parameters + return utils.functionCreators[kind](parameters) if kind of utils.functionCreators -AchievementSchema.post('init', (doc) -> doc.objectifyQuery()) +AchievementSchema.post 'init', (doc) -> doc.objectifyQuery() -AchievementSchema.pre('save', (next) -> +AchievementSchema.pre 'save', (next) -> @stringifyQuery() next() -) + +# Reload achievements upon save +AchievementSchema.post 'save', -> AchievablePlugin.loadAchievements() AchievementSchema.plugin(plugins.NamedPlugin) AchievementSchema.plugin(plugins.SearchablePlugin, {searchable: ['name']}) module.exports = Achievement = mongoose.model('Achievement', AchievementSchema) -# Reload achievements upon save -AchievablePlugin = require '../plugins/achievements' -AchievementSchema.post 'save', (doc) -> AchievablePlugin.loadAchievements() diff --git a/server/plugins/achievements.coffee b/server/plugins/achievements.coffee index a3f0096af..d49bd1926 100644 --- a/server/plugins/achievements.coffee +++ b/server/plugins/achievements.coffee @@ -10,9 +10,6 @@ module.exports = AchievablePlugin = (schema, options) -> User = require '../users/User' # Avoid mutual inclusion cycles Achievement = require('../achievements/Achievement') - checkForAchievement = (doc) -> - collectionName = doc.constructor.modelName - before = {} schema.post 'init', (doc) -> @@ -84,6 +81,7 @@ module.exports = AchievablePlugin = (schema, options) -> return module.exports.loadAchievements = -> + log.debug 'Reloading all achievements' achievements = {} Achievement = require('../achievements/Achievement') query = Achievement.find({}) diff --git a/test/server/functional/achievement.spec.coffee b/test/server/functional/achievement.spec.coffee index 793248758..67d428b02 100644 --- a/test/server/functional/achievement.spec.coffee +++ b/test/server/functional/achievement.spec.coffee @@ -51,6 +51,9 @@ describe 'Achievement', -> request.post {uri: url, json: repeatable}, (err, res, body) -> expect(res.statusCode).toBe(200) repeatable._id = body._id + + Achievement.find {}, (err, docs) -> + expect(docs.length).toBe(2) done() it 'can get all for ordinary users', (done) -> @@ -93,18 +96,29 @@ describe 'Achieving Achievements', -> it 'allows users to unlock one-time Achievements', (done) -> loginJoe (joe) -> - levelSession = + session = new LevelSession( + permissions: simplePermissions creator: joe._id level: original: 'dungeon-arena' + ) - request.post {uri:getURL('/db/level.session'), json:levelSession}, (session) -> + session.save (err, doc) -> + expect(err).toBeNull() + expect(doc).toBeDefined() + expect(doc.creator).toBe(session.creator) - done() + EarnedAchievement.find {}, (err, docs) -> + expect(err).toBeNull() + console.log docs + expect(docs.length).toBe(1) + done() - - xit 'cleaning up test: deleting all Achievements and relates', (done) -> + it 'cleaning up test: deleting all Achievements and relates', (done) -> clearModels [Achievement, EarnedAchievement, LevelSession], (err) -> expect(err).toBeNull() done() + + + From faf02d8e4bcba387209a9b4b8b713eca819b78a5 Mon Sep 17 00:00:00 2001 From: Ruben Vereecken Date: Sat, 14 Jun 2014 15:34:23 +0200 Subject: [PATCH 03/83] Finally managed a setup that makes the tests succeed --- server/achievements/Achievement.coffee | 24 +++++++++++++++-- server/plugins/achievements.coffee | 25 +++++++----------- .../server/functional/achievement.spec.coffee | 26 +++++++++++++++---- 3 files changed, 52 insertions(+), 23 deletions(-) diff --git a/server/achievements/Achievement.coffee b/server/achievements/Achievement.coffee index 82c565c96..17f713a60 100644 --- a/server/achievements/Achievement.coffee +++ b/server/achievements/Achievement.coffee @@ -24,11 +24,30 @@ AchievementSchema.methods.objectifyQuery = -> AchievementSchema.methods.stringifyQuery = -> @set('query', JSON.stringify(@get('query'))) if typeof @get('query') != "string" -AchievementSchema.methods.getExpFunction: -> +AchievementSchema.methods.getExpFunction = -> kind = @get('function')?.kind or jsonschema.function.default.kind parameters = @get('function')?.parameters or jsonschema.function.default.parameters return utils.functionCreators[kind](parameters) if kind of utils.functionCreators +AchievementSchema.statics.achievements = {} + +AchievementSchema.statics.loadAchievements = (done) -> + AchievementSchema.statics.resetAchievements() + Achievement = require('../achievements/Achievement') + query = Achievement.find({}) + query.exec (err, docs) -> + _.each docs, (achievement) -> + category = achievement.get 'collection' + AchievementSchema.statics.achievements[category] = [] unless category of AchievementSchema.statics.achievements + AchievementSchema.statics.achievements[category].push achievement + done(AchievementSchema.statics.achievements) if done? + +AchievementSchema.statics.getLoadedAchievements = -> + AchievementSchema.statics.achievements + +AchievementSchema.statics.resetAchievements = -> + delete AchievementSchema.statics.achievements[category] for category of AchievementSchema.statics.achievements + AchievementSchema.post 'init', (doc) -> doc.objectifyQuery() AchievementSchema.pre 'save', (next) -> @@ -36,10 +55,11 @@ AchievementSchema.pre 'save', (next) -> next() # Reload achievements upon save -AchievementSchema.post 'save', -> AchievablePlugin.loadAchievements() +AchievementSchema.post 'save', -> @constructor.loadAchievements() AchievementSchema.plugin(plugins.NamedPlugin) AchievementSchema.plugin(plugins.SearchablePlugin, {searchable: ['name']}) module.exports = Achievement = mongoose.model('Achievement', AchievementSchema) +AchievementSchema.statics.loadAchievements() diff --git a/server/plugins/achievements.coffee b/server/plugins/achievements.coffee index d49bd1926..460d346a2 100644 --- a/server/plugins/achievements.coffee +++ b/server/plugins/achievements.coffee @@ -4,26 +4,30 @@ LocalMongo = require '../../app/lib/LocalMongo' util = require '../../app/lib/utils' log = require 'winston' -achievements = {} -module.exports = AchievablePlugin = (schema, options) -> +AchievablePlugin = (schema, options) -> User = require '../users/User' # Avoid mutual inclusion cycles Achievement = require('../achievements/Achievement') before = {} schema.post 'init', (doc) -> + #log.debug 'initd' + #log.debug doc.toObject() before[doc.id] = doc.toObject() schema.post 'save', (doc) -> + #log.debug 'waiting in init: ' + Object.keys(before).length isNew = not doc.isInit('_id') originalDocObj = before[doc.id] unless isNew category = doc.constructor.modelName + loadedAchievements = Achievement.getLoadedAchievements() + log.debug 'about to save ' + category + ', number of achievements is ' + Object.keys(loadedAchievements).length - if category of achievements + if category of loadedAchievements docObj = doc.toObject() - for achievement in achievements[category] + for achievement in loadedAchievements[category] query = achievement.get('query') isRepeatable = achievement.get('proportionalTo')? alreadyAchieved = if isNew then false else LocalMongo.matchesQuery originalDocObj, query @@ -80,15 +84,4 @@ module.exports = AchievablePlugin = (schema, options) -> delete before[doc.id] unless isNew # This assumes everything we patch has a _id return -module.exports.loadAchievements = -> - log.debug 'Reloading all achievements' - achievements = {} - Achievement = require('../achievements/Achievement') - query = Achievement.find({}) - query.exec (err, docs) -> - _.each docs, (achievement) -> - category = achievement.get 'collection' - achievements[category] = [] unless category of achievements - achievements[category].push achievement - -AchievablePlugin.loadAchievements() +module.exports = AchievablePlugin diff --git a/test/server/functional/achievement.spec.coffee b/test/server/functional/achievement.spec.coffee index 67d428b02..87188794c 100644 --- a/test/server/functional/achievement.spec.coffee +++ b/test/server/functional/achievement.spec.coffee @@ -93,6 +93,15 @@ describe 'Achievement', -> describe 'Achieving Achievements', -> + it 'wait for achievements to be loaded', (done) -> + Achievement.loadAchievements (achievements) -> + expect(Object.keys(achievements).length).toBe(2) + + loadedAchievements = Achievement.getLoadedAchievements() + expect(Object.keys(loadedAchievements).length).toBe(2) + + done() + it 'allows users to unlock one-time Achievements', (done) -> loginJoe (joe) -> @@ -106,19 +115,26 @@ describe 'Achieving Achievements', -> expect(err).toBeNull() expect(doc).toBeDefined() expect(doc.creator).toBe(session.creator) + done() - EarnedAchievement.find {}, (err, docs) -> - expect(err).toBeNull() - console.log docs - expect(docs.length).toBe(1) - done() + it 'check if the earned achievement was already saved', (done) -> + EarnedAchievement.find {}, (err, docs) -> + expect(err).toBeNull() + expect(docs.length).toBe(1) + done() it 'cleaning up test: deleting all Achievements and relates', (done) -> clearModels [Achievement, EarnedAchievement, LevelSession], (err) -> expect(err).toBeNull() + + Achievement.resetAchievements() + loadedAchievements = Achievement.getLoadedAchievements() + expect(Object.keys(loadedAchievements).length).toBe(0) + done() + From 1fe2c67ffe1a49301784893a6eaa2badad40d1b1 Mon Sep 17 00:00:00 2001 From: Ruben Vereecken Date: Sat, 14 Jun 2014 20:12:17 +0200 Subject: [PATCH 04/83] Added tests for repeatable achievements, including complicated xp Intermediate --- app/lib/LocalMongo.coffee | 1 + app/lib/utils.coffee | 3 +- app/models/Achievement.coffee | 4 +- app/models/CocoModel.coffee | 1 + app/schemas/models/achievement.coffee | 3 +- server/achievements/Achievement.coffee | 7 +- server/plugins/achievements.coffee | 21 +++-- test/app/models/CocoModel.spec.coffee | 22 ++++- .../server/functional/achievement.spec.coffee | 86 ++++++++++++++++--- 9 files changed, 120 insertions(+), 28 deletions(-) diff --git a/app/lib/LocalMongo.coffee b/app/lib/LocalMongo.coffee index 2027e1c70..558a58e5d 100644 --- a/app/lib/LocalMongo.coffee +++ b/app/lib/LocalMongo.coffee @@ -24,6 +24,7 @@ doQuerySelector = (value, operatorObj) -> matchesQuery = (target, queryObj) -> return true unless queryObj + throw new Error 'Expected an object to match a query against, instead got null' unless target for prop, query of queryObj if prop[0] == '$' switch prop diff --git a/app/lib/utils.coffee b/app/lib/utils.coffee index 8dbae8cc2..a5698c044 100644 --- a/app/lib/utils.coffee +++ b/app/lib/utils.coffee @@ -69,6 +69,7 @@ module.exports.i18n = (say, target, language=me.lang(), fallback='en') -> null module.exports.getByPath = (target, path) -> + throw new Error 'Expected an object to match a query against, instead got null' unless target pieces = path.split('.') obj = target for piece in pieces @@ -79,7 +80,7 @@ module.exports.getByPath = (target, path) -> module.exports.round = _.curry (digits, n) -> n = +n.toFixed(digits) -positify = (func) -> (x) -> if x > 0 then func(x) else 0 +positify = (func) -> (params) -> (x) -> if x > 0 then func(params)(x) else 0 # f(x) = ax + b createLinearFunc = (params) -> diff --git a/app/models/Achievement.coffee b/app/models/Achievement.coffee index 88603c6ef..b4f0ed99b 100644 --- a/app/models/Achievement.coffee +++ b/app/models/Achievement.coffee @@ -11,6 +11,6 @@ module.exports = class Achievement extends CocoModel # TODO logic is duplicated in Mongoose Achievement schema getExpFunction: -> - kind = @get('function')?.kind or @schema.function.default.kind - parameters = @get('function')?.parameters or @schema.function.default.parameters + kind = @get('function')?.kind or jsonschema.properties.function.default.kind + parameters = @get('function')?.parameters or jsonschema.properties.function.default.parameters return utils.functionCreators[kind](parameters) if kind of utils.functionCreators diff --git a/app/models/CocoModel.coffee b/app/models/CocoModel.coffee index e28e8f1ca..245eccd79 100644 --- a/app/models/CocoModel.coffee +++ b/app/models/CocoModel.coffee @@ -293,6 +293,7 @@ class CocoModel extends Backbone.Model @pollAchievements: -> achievements = new NewAchievementCollection + console.log 'ohai' achievements.fetch( success: (collection) -> me.fetch (success: -> Backbone.Mediator.publish('achievements:new', collection)) unless _.isEmpty(collection.models) diff --git a/app/schemas/models/achievement.coffee b/app/schemas/models/achievement.coffee index d02e9c8fd..e44ec2a73 100644 --- a/app/schemas/models/achievement.coffee +++ b/app/schemas/models/achievement.coffee @@ -53,13 +53,14 @@ _.extend(AchievementSchema.properties, function: type: 'object' properties: - kind: {enum: ['linear', 'logarithmic'], default: 'linear'} + kind: {enum: ['linear', 'logarithmic', 'quadratic'], default: 'linear'} parameters: type: 'object' properties: a: {type: 'number', default: 1} b: {type: 'number', default: 1} c: {type: 'number', default: 1} + additionalProperties: true default: {kind: 'linear', parameters: a: 1} required: ['kind', 'parameters'] additionalProperties: false diff --git a/server/achievements/Achievement.coffee b/server/achievements/Achievement.coffee index 17f713a60..cd0850731 100644 --- a/server/achievements/Achievement.coffee +++ b/server/achievements/Achievement.coffee @@ -1,7 +1,7 @@ mongoose = require('mongoose') jsonschema = require('../../app/schemas/models/achievement') log = require 'winston' -util = require '../../app/lib/utils' +utils = require '../../app/lib/utils' plugins = require('../plugins/plugins') AchievablePlugin = require '../plugins/achievements' @@ -25,10 +25,11 @@ AchievementSchema.methods.stringifyQuery = -> @set('query', JSON.stringify(@get('query'))) if typeof @get('query') != "string" AchievementSchema.methods.getExpFunction = -> - kind = @get('function')?.kind or jsonschema.function.default.kind - parameters = @get('function')?.parameters or jsonschema.function.default.parameters + kind = @get('function')?.kind or jsonschema.properties.function.default.kind + parameters = @get('function')?.parameters or jsonschema.properties.function.default.parameters return utils.functionCreators[kind](parameters) if kind of utils.functionCreators +AchievementSchema.statics.jsonschema = jsonschema AchievementSchema.statics.achievements = {} AchievementSchema.statics.loadAchievements = (done) -> diff --git a/server/plugins/achievements.coffee b/server/plugins/achievements.coffee index 460d346a2..dbb09a825 100644 --- a/server/plugins/achievements.coffee +++ b/server/plugins/achievements.coffee @@ -4,7 +4,10 @@ LocalMongo = require '../../app/lib/LocalMongo' util = require '../../app/lib/utils' log = require 'winston' - +# Warning: To ensure proper functioning one must always `find` documents before saving them. +# Otherwise the schema's `post init` won't be triggered and the plugin can't keep track of changes +# TODO if this is still a common scenario I could implement a database hit after all, but only +# on the condition that it's necessary and still not too frequent in occurrence AchievablePlugin = (schema, options) -> User = require '../users/User' # Avoid mutual inclusion cycles Achievement = require('../achievements/Achievement') @@ -12,18 +15,19 @@ AchievablePlugin = (schema, options) -> before = {} schema.post 'init', (doc) -> - #log.debug 'initd' - #log.debug doc.toObject() before[doc.id] = doc.toObject() + # TODO check out how many objects go unreleased schema.post 'save', (doc) -> - #log.debug 'waiting in init: ' + Object.keys(before).length - isNew = not doc.isInit('_id') + isNew = not doc.isInit('_id') or not (doc.id of before) originalDocObj = before[doc.id] unless isNew + if doc.isInit('_id') and not doc.id of before + log.warn 'document was already initialized but did not go through `init` and is therefore treated as new while it might not be' + category = doc.constructor.modelName loadedAchievements = Achievement.getLoadedAchievements() - log.debug 'about to save ' + category + ', number of achievements is ' + Object.keys(loadedAchievements).length + #log.debug 'about to save ' + category + ', number of achievements is ' + Object.keys(loadedAchievements).length if category of loadedAchievements docObj = doc.toObject() @@ -57,7 +61,7 @@ AchievablePlugin = (schema, options) -> if isRepeatable log.debug 'Upserting repeatable achievement called \'' + (achievement.get 'name') + '\' for ' + userID proportionalTo = achievement.get 'proportionalTo' - originalAmount = util.getByPath(originalDocObj, proportionalTo) or 0 + originalAmount = if originalDocObj then util.getByPath(originalDocObj, proportionalTo) or 0 else 0 newAmount = docObj[proportionalTo] if originalAmount isnt newAmount @@ -81,7 +85,6 @@ AchievablePlugin = (schema, options) -> earnedPoints = worth wrapUp() - delete before[doc.id] unless isNew # This assumes everything we patch has a _id - return + delete before[doc.id] if doc.id of before module.exports = AchievablePlugin diff --git a/test/app/models/CocoModel.spec.coffee b/test/app/models/CocoModel.spec.coffee index f7703b851..676336897 100644 --- a/test/app/models/CocoModel.spec.coffee +++ b/test/app/models/CocoModel.spec.coffee @@ -12,7 +12,7 @@ class BlandClass extends CocoModel _id: {type: 'string'} } urlRoot: '/db/bland' - + describe 'CocoModel', -> describe 'save', -> @@ -82,3 +82,23 @@ describe 'CocoModel', -> b.patch() request = jasmine.Ajax.requests.mostRecent() expect(request).toBeUndefined() + + describe 'Achievement polling', -> + + it 'achievements are polled upon saving a model', (done) -> + #spyOn(CocoModel, 'pollAchievements') + + b = new BlandClass({}) + res = b.save() + request = jasmine.Ajax.requests.mostRecent() + request.response({status: 200, responseText: {}}) + jasmine.Ajax.requests.reset() + + #expect(CocoModel.pollAchievements).toHaveBeenCalled() + console.log jasmine.Ajax.requests.mostRecent() + + request = jasmine.Ajax.requests.mostRecent() + #expect(request.url).toBe("") + + done() + diff --git a/test/server/functional/achievement.spec.coffee b/test/server/functional/achievement.spec.coffee index 87188794c..ea33c3075 100644 --- a/test/server/functional/achievement.spec.coffee +++ b/test/server/functional/achievement.spec.coffee @@ -17,6 +17,17 @@ repeatable = userField: '_id' proportionalTo: 'simulatedBy' +diminishing = + name: 'Simulated2' + worth: 1.5 + collection: 'User' + query: "{\"simulatedBy\":{\"$gt\":\"0\"}}" + userField: '_id' + proportionalTo: 'simulatedBy' + function: + kind: 'logarithmic' + parameters: {a: 1, b: .5, c: .5, d: 1} + url = getURL('/db/achievement') describe 'Achievement', -> @@ -52,15 +63,19 @@ describe 'Achievement', -> expect(res.statusCode).toBe(200) repeatable._id = body._id - Achievement.find {}, (err, docs) -> - expect(docs.length).toBe(2) - done() + request.post {uri: url, json: diminishing}, (err, res, body) -> + expect(res.statusCode).toBe(200) + diminishing._id = body._id + + Achievement.find {}, (err, docs) -> + expect(docs.length).toBe 3 + done() it 'can get all for ordinary users', (done) -> loginJoe -> request.get {uri: url, json: unlockable}, (err, res, body) -> expect(res.statusCode).toBe(200) - expect(body.length).toBe(2) + expect(body.length).toBe 3 done() it 'can be read by ordinary users', (done) -> @@ -103,8 +118,8 @@ describe 'Achieving Achievements', -> done() - it 'allows users to unlock one-time Achievements', (done) -> - loginJoe (joe) -> + it 'saving an object that should trigger an unlockable achievement', (done) -> + unittest.getNormalJoe (joe) -> session = new LevelSession( permissions: simplePermissions creator: joe._id @@ -117,16 +132,64 @@ describe 'Achieving Achievements', -> expect(doc.creator).toBe(session.creator) done() - it 'check if the earned achievement was already saved', (done) -> - EarnedAchievement.find {}, (err, docs) -> - expect(err).toBeNull() - expect(docs.length).toBe(1) - done() + + it 'verify that an unlockable achievement has been earned', (done) -> + unittest.getNormalJoe (joe) -> + EarnedAchievement.find {}, (err, docs) -> + expect(err).toBeNull() + expect(docs.length).toBe(1) + achievement = docs[0] + + expect(achievement.get 'achievement').toBe unlockable._id + expect(achievement.get 'user').toBe joe._id.toHexString() + expect(achievement.get 'notified').toBeFalsy() + expect(achievement.get 'earnedPoints').toBe unlockable.worth + expect(achievement.get 'achievedAmount').toBeUndefined() + expect(achievement.get 'previouslyAchievedAmount').toBeUndefined() + + done() + + it 'saving an object that should trigger a repeatable achievement', (done) -> + unittest.getNormalJoe (joe) -> + expect(joe.get 'simulatedBy').toBeFalsy() + joe.set('simulatedBy', 2) + joe.save (err, doc) -> + expect(err).toBeNull() + done() + + it 'verify that a repeatable achievement has been earned', (done) -> + unittest.getNormalJoe (joe) -> + EarnedAchievement.find {achievementName: repeatable.name}, (err, docs) -> + expect(err).toBeNull() + expect(docs.length).toBe(1) + achievement = docs[0] + + expect(achievement.get 'achievement').toBe repeatable._id + expect(achievement.get 'user').toBe joe._id.toHexString() + expect(achievement.get 'notified').toBeFalsy() + expect(achievement.get 'earnedPoints').toBe 2 * repeatable.worth + expect(achievement.get 'achievedAmount').toBe 2 + expect(achievement.get 'previouslyAchievedAmount').toBeFalsy() + done() + + + it 'verify that the repeatable achievement with complex exp has been earned', (done) -> + unittest.getNormalJoe (joe) -> + EarnedAchievement.find {achievementName: diminishing.name}, (err, docs) -> + expect(err).toBeNull() + expect(docs.length).toBe 1 + achievement = docs[0] + + expect(achievement.get 'achievedAmount').toBe 2 + expect(achievement.get 'earnedPoints').toBe (Math.log(.5 * (2 + .5)) + 1) * diminishing.worth + + done() it 'cleaning up test: deleting all Achievements and relates', (done) -> clearModels [Achievement, EarnedAchievement, LevelSession], (err) -> expect(err).toBeNull() + # reset achievements in memory as well Achievement.resetAchievements() loadedAchievements = Achievement.getLoadedAchievements() expect(Object.keys(loadedAchievements).length).toBe(0) @@ -138,3 +201,4 @@ describe 'Achieving Achievements', -> + From fe5b675d18abbb3071e43f8b73ecf8e474e3c3f2 Mon Sep 17 00:00:00 2001 From: Ruben Vereecken Date: Mon, 16 Jun 2014 14:27:16 +0200 Subject: [PATCH 05/83] Achievement polling now successfully tested Intermediate --- app/models/Achievement.coffee | 2 +- app/models/CocoModel.coffee | 4 ++- app/models/EarnedAchievement.coffee | 7 +++++ app/views/kinds/RootView.coffee | 4 +-- test/app/models/CocoModel.spec.coffee | 30 ++++++++++++++----- .../achievement/achievement_get.demo.coffee | 6 ++++ 6 files changed, 42 insertions(+), 11 deletions(-) create mode 100644 app/models/EarnedAchievement.coffee create mode 100644 test/demo/views/achievement/achievement_get.demo.coffee diff --git a/app/models/Achievement.coffee b/app/models/Achievement.coffee index b4f0ed99b..094f46ea5 100644 --- a/app/models/Achievement.coffee +++ b/app/models/Achievement.coffee @@ -1,5 +1,5 @@ CocoModel = require './CocoModel' -util = require '../lib/utils' +utils = require '../lib/utils' module.exports = class Achievement extends CocoModel @className: 'Achievement' diff --git a/app/models/CocoModel.coffee b/app/models/CocoModel.coffee index 245eccd79..27a1af2a7 100644 --- a/app/models/CocoModel.coffee +++ b/app/models/CocoModel.coffee @@ -293,10 +293,12 @@ class CocoModel extends Backbone.Model @pollAchievements: -> achievements = new NewAchievementCollection - console.log 'ohai' achievements.fetch( success: (collection) -> me.fetch (success: -> Backbone.Mediator.publish('achievements:new', collection)) unless _.isEmpty(collection.models) + error: (collection, res, options) -> + console.error 'Miserably failed to fetch unnotified achievements' + console.log res ) diff --git a/app/models/EarnedAchievement.coffee b/app/models/EarnedAchievement.coffee new file mode 100644 index 000000000..28588db81 --- /dev/null +++ b/app/models/EarnedAchievement.coffee @@ -0,0 +1,7 @@ +CocoModel = require './CocoModel' +util = require '../lib/utils' + +module.exports = class EarnedAchievement extends CocoModel + @className: 'EarnedAchievement' + @schema: require 'schemas/models/earned_achievement' + urlRoot: '/db/earnedachievement' diff --git a/app/views/kinds/RootView.coffee b/app/views/kinds/RootView.coffee index 899c0aaa7..503656156 100644 --- a/app/views/kinds/RootView.coffee +++ b/app/views/kinds/RootView.coffee @@ -30,7 +30,7 @@ module.exports = class RootView extends CocoView 'achievements:new': 'handleNewAchievements' - showNewAchievement: (achievement, earnedAchievement) -> + @showNewAchievement: (achievement, earnedAchievement) -> currentLevel = me.level() nextLevel = currentLevel + 1 currentLevelExp = User.expForLevel(currentLevel) @@ -83,7 +83,7 @@ module.exports = class RootView extends CocoView achievement = new Achievement(_id: earnedAchievement.get('achievement')) console.log achievement achievement.fetch( - success: (achievement) => @showNewAchievement(achievement, earnedAchievement) + success: (achievement) => RootView.showNewAchievement(achievement, earnedAchievement) ) ) diff --git a/test/app/models/CocoModel.spec.coffee b/test/app/models/CocoModel.spec.coffee index 676336897..b671bedf7 100644 --- a/test/app/models/CocoModel.spec.coffee +++ b/test/app/models/CocoModel.spec.coffee @@ -84,21 +84,37 @@ describe 'CocoModel', -> expect(request).toBeUndefined() describe 'Achievement polling', -> + NewAchievementCollection = require 'collections/NewAchievementCollection' + EarnedAchievement = require 'models/EarnedAchievement' it 'achievements are polled upon saving a model', (done) -> #spyOn(CocoModel, 'pollAchievements') + Backbone.Mediator.subscribe 'achievements:new', (collection) -> + Backbone.Mediator.unsubscribe 'achievements:new' + expect(collection.constructor.name).toBe('NewAchievementCollection') + done() b = new BlandClass({}) res = b.save() request = jasmine.Ajax.requests.mostRecent() request.response({status: 200, responseText: {}}) - jasmine.Ajax.requests.reset() - #expect(CocoModel.pollAchievements).toHaveBeenCalled() - console.log jasmine.Ajax.requests.mostRecent() + _.delay (-> + collection = [] + model = + _id: "5390f7637b4d6f2a074a7bb4" + achievement: "537ce4855c91b8d1dda7fda8" + collection.push model - request = jasmine.Ajax.requests.mostRecent() - #expect(request.url).toBe("") - - done() + request = jasmine.Ajax.requests.mostRecent() + achievementURLMatch = (/.*achievements\?notified=false$/).exec request.url + expect(achievementURLMatch).not.toBeNull() + request.response {status: 200, responseText: JSON.stringify collection} + _.delay (-> + request = jasmine.Ajax.requests.mostRecent() + userURLMatch = (/^\/db\/user\/[a-zA-Z0-9]*$/).exec request.url + expect(userURLMatch).not.toBeNull() + request.response {status:200, responseText: JSON.stringify me} + ), 1000 + ), 1000 diff --git a/test/demo/views/achievement/achievement_get.demo.coffee b/test/demo/views/achievement/achievement_get.demo.coffee new file mode 100644 index 000000000..ff5e9927b --- /dev/null +++ b/test/demo/views/achievement/achievement_get.demo.coffee @@ -0,0 +1,6 @@ +CocoModel = require 'models/CocoModel' +RootView = require 'views/kinds/RootView' + +module.exports = -> + console.log jasmine.Ajax.requests.mostRecent() + -> console.log 'herp' From a933f9737dfa832346bd4c3a9a47b2e8fa12e895 Mon Sep 17 00:00:00 2001 From: Ruben Vereecken Date: Tue, 17 Jun 2014 21:42:27 +0200 Subject: [PATCH 06/83] Creating achievement demo intermediate --- app/lib/utils.coffee | 8 ++ app/models/Achievement.coffee | 2 +- app/models/CocoModel.coffee | 1 - app/styles/notify.sass | 111 ++++++++++++------ app/templates/achievement_notify.jade | 9 +- app/views/DemoView.coffee | 4 +- app/views/kinds/RootView.coffee | 8 +- test/app/models/CocoModel.spec.coffee | 30 +++-- .../achievement/achievement_get.demo.coffee | 36 +++++- 9 files changed, 150 insertions(+), 59 deletions(-) diff --git a/app/lib/utils.coffee b/app/lib/utils.coffee index a5698c044..88cb42f1f 100644 --- a/app/lib/utils.coffee +++ b/app/lib/utils.coffee @@ -98,3 +98,11 @@ module.exports.functionCreators = linear: positify(createLinearFunc) quadratic: positify(createQuadraticFunc) logarithmic: positify(createLogFunc) + +# Call done with true to satisfy the 'until' goal and stop repeating func +module.exports.keepDoingUntil = (func, wait=100, totalWait=5000) -> + waitSoFar = 0 + (done = (success) -> + if (waitSoFar += wait) <= totalWait && not success + _.delay (-> func done), wait) false + diff --git a/app/models/Achievement.coffee b/app/models/Achievement.coffee index 094f46ea5..4546c3453 100644 --- a/app/models/Achievement.coffee +++ b/app/models/Achievement.coffee @@ -7,7 +7,7 @@ module.exports = class Achievement extends CocoModel urlRoot: '/db/achievement' isRepeatable: -> - @get('proportionalTo')? + @get('proportionalTo')?a # TODO logic is duplicated in Mongoose Achievement schema getExpFunction: -> diff --git a/app/models/CocoModel.coffee b/app/models/CocoModel.coffee index 27a1af2a7..832e83ed0 100644 --- a/app/models/CocoModel.coffee +++ b/app/models/CocoModel.coffee @@ -298,7 +298,6 @@ class CocoModel extends Backbone.Model me.fetch (success: -> Backbone.Mediator.publish('achievements:new', collection)) unless _.isEmpty(collection.models) error: (collection, res, options) -> console.error 'Miserably failed to fetch unnotified achievements' - console.log res ) diff --git a/app/styles/notify.sass b/app/styles/notify.sass index abf50bb70..989c19924 100644 --- a/app/styles/notify.sass +++ b/app/styles/notify.sass @@ -1,50 +1,91 @@ .notifyjs-achievement-base //background: url("/images/pages/base/notify_mockup.png") - background-image: url("/images/pages/base/modal_background.png") - background-size: 100% 100% - width: 500px - height: 200px - padding: 35px 35px 15px 15px - text-align: center cursor: auto + white-space: nowrap + text-overflow: ellipsis + padding: 20px 0px .achievement-body - .achievement-image - img - float: left - width: 100px - height: 100px - border-radius: 50% - margin: 20px 30px 20px 30px - -webkit-box-shadow: 0px 0px 36px 0px white - -moz-box-shadow: 0px 0px 36px 0px white - box-shadow: 0px 0px 36px 0px white + .achievement-icon + position: absolute + width: 200px + height: 200px + left: -150px + top: 0px - .achievement-title - font-family: Bangers - font-size: 28px + .achievement-image + width: 100% + height: 100% + img + position: absolute + margin: auto + top: 0 + left: 0 + right: 0 + bottom: 0 + width: 72% - .achievement-description - margin-top: 10px - font-size: 16px + .achievement-border-wood + background: url("/images/achievements/border_wood-01.png") no-repeat + //background-color: transparent + background-size: 100% 100% - .achievement-progress - padding: 15px 0px 0px 0px + .achievement-border-silver + background: url("/images/achievements/border_silver-01.png") no-repeat + //background-color: transparent + background-size: 100% 100% - .achievement-message + .achievement-content + background-image: url("/images/achievements/reward_background2.png") + background-size: 100% 100% + width: 450px + height: 160px + text-align: center + padding: 24px 30px 20px 60px + overflow: hidden + + + .achievement-title font-family: Bangers - font-size: 18px - &:empty - display: none + font-size: 28px + padding-left: -50px + overflow: hidden - .progress-wrapper - .progress-bar-wrapper - width: 100% - .earned-exp - padding-left: 5px + .achievement-description + white-space: initial + font-size: 15px + line-height: 1.3em + overflow: hidden + max-height: 2.6em + margin-top: auto + margin-bottom: 0px !important + + .achievement-progress + padding: 8px 0px 0px 0px + + .achievement-message font-family: Bangers - font-size: 16px - float: right + font-size: 18px + &:empty + display: none + .progress-wrapper + position: absolute + padding-right: 30px + bottom: 0px + + .progress-bar-wrapper + width: 100% + .progress + border-radius: 20px + .progress-bar-border + position: relative + top: -44px + /*.earned-exp + padding-left: 5px + font-family: Bangers + font-size: 16px + float: right + */ .progress-bar-white background-color: white diff --git a/app/templates/achievement_notify.jade b/app/templates/achievement_notify.jade index f7ce0eb9b..6555b0e54 100644 --- a/app/templates/achievement_notify.jade +++ b/app/templates/achievement_notify.jade @@ -1,12 +1,15 @@ div .clearfix.achievement-body - .achievement-image(data-notify-html="image") + .achievement-icon.achievement-border-silver + .achievement-image(data-notify-html="image") .achievement-content .achievement-title(data-notify-html="title") - .achievement-description(data-notify-html="description") + p.achievement-description(data-notify-html="description") .achievement-progress .achievement-message(data-notify-html="message") .progress-wrapper - .earned-exp(data-notify-html="earnedExp") + //.earned-exp(data-notify-html="earnedExp") .progress-bar-wrapper(data-notify-html="progressBar") + .progress-bar-border + img(src='/images/achievements/bar_border-01.png' width='100%') diff --git a/app/views/DemoView.coffee b/app/views/DemoView.coffee index 00def8100..05dc3ac5d 100644 --- a/app/views/DemoView.coffee +++ b/app/views/DemoView.coffee @@ -1,4 +1,4 @@ -CocoView = require 'views/kinds/CocoView' +RootView = require 'views/kinds/RootView' template = require 'templates/demo' requireUtils = require 'lib/requireUtils' @@ -23,7 +23,7 @@ DEMO_URL_PREFIX = '/demo/' ### -module.exports = DemoView = class DemoView extends CocoView +module.exports = DemoView = class DemoView extends RootView id: "demo-view" template: template diff --git a/app/views/kinds/RootView.coffee b/app/views/kinds/RootView.coffee index 503656156..cdbe0913f 100644 --- a/app/views/kinds/RootView.coffee +++ b/app/views/kinds/RootView.coffee @@ -29,8 +29,7 @@ module.exports = class RootView extends CocoView subscriptions: 'achievements:new': 'handleNewAchievements' - - @showNewAchievement: (achievement, earnedAchievement) -> + createNotifyData: (achievement, earnedAchievement) -> currentLevel = me.level() nextLevel = currentLevel + 1 currentLevelExp = User.expForLevel(currentLevel) @@ -67,7 +66,10 @@ module.exports = class RootView extends CocoView progressBar: progressBar earnedExp: "+ #{achievedExp} XP" message: message + data + showNewAchievement: (achievement, earnedAchievement) -> + data = createNotifyData achievement, earnedAchievement options = autoHideDelay: 10000 globalPosition: 'bottom right' @@ -83,7 +85,7 @@ module.exports = class RootView extends CocoView achievement = new Achievement(_id: earnedAchievement.get('achievement')) console.log achievement achievement.fetch( - success: (achievement) => RootView.showNewAchievement(achievement, earnedAchievement) + success: (achievement) => @showNewAchievement(achievement, earnedAchievement) ) ) diff --git a/test/app/models/CocoModel.spec.coffee b/test/app/models/CocoModel.spec.coffee index b671bedf7..34c2ac109 100644 --- a/test/app/models/CocoModel.spec.coffee +++ b/test/app/models/CocoModel.spec.coffee @@ -1,4 +1,5 @@ CocoModel = require 'models/CocoModel' +utils = require 'lib/utils' class BlandClass extends CocoModel @className: 'Bland' @@ -97,24 +98,29 @@ describe 'CocoModel', -> b = new BlandClass({}) res = b.save() request = jasmine.Ajax.requests.mostRecent() - request.response({status: 200, responseText: {}}) + request.response(status: 200, responseText: '{}') - _.delay (-> - collection = [] - model = - _id: "5390f7637b4d6f2a074a7bb4" - achievement: "537ce4855c91b8d1dda7fda8" - collection.push model + collection = [] + model = + _id: "5390f7637b4d6f2a074a7bb4" + achievement: "537ce4855c91b8d1dda7fda8" + collection.push model + utils.keepDoingUntil (ready) -> request = jasmine.Ajax.requests.mostRecent() achievementURLMatch = (/.*achievements\?notified=false$/).exec request.url - expect(achievementURLMatch).not.toBeNull() + if achievementURLMatch + ready true + else return ready false + request.response {status: 200, responseText: JSON.stringify collection} - _.delay (-> + utils.keepDoingUntil (ready) -> request = jasmine.Ajax.requests.mostRecent() userURLMatch = (/^\/db\/user\/[a-zA-Z0-9]*$/).exec request.url - expect(userURLMatch).not.toBeNull() + if userURLMatch + ready true + else return ready false + request.response {status:200, responseText: JSON.stringify me} - ), 1000 - ), 1000 + diff --git a/test/demo/views/achievement/achievement_get.demo.coffee b/test/demo/views/achievement/achievement_get.demo.coffee index ff5e9927b..4cbfaced2 100644 --- a/test/demo/views/achievement/achievement_get.demo.coffee +++ b/test/demo/views/achievement/achievement_get.demo.coffee @@ -1,6 +1,38 @@ CocoModel = require 'models/CocoModel' RootView = require 'views/kinds/RootView' +utils = require 'lib/utils' +Achievement = require 'models/Achievement' +EarnedAchievement = require 'models/EarnedAchievement' + +class MockServer + module.exports = -> - console.log jasmine.Ajax.requests.mostRecent() - -> console.log 'herp' + unlockableObj = + name: 'Dungeon Arena Started' + description: 'Started playing Dungeon Arena. ' + worth: 3 + collection: 'level.session' + query: "{\"level.original\":\"dungeon-arena\"}" + userField: 'creator' + + earnedUnlockableObj = + earnedPoints: 3 + notified: false + + unlockable = new Achievement unlockableObj + earnedUnlockable = new EarnedAchievement earnedUnlockableObj + + console.log currentView + data = currentView.createNotifyData unlockable, earnedUnlockable + imageURL = '/images/achievements/swords.png' + data.image = $("") + options = + autoHideDelay: 10000 + globalPosition: 'bottom right' + showDuration: 400 + style: 'achievement' + autoHide: false + clickToHide: false + + $.notify data, options From 45798aab024de43f81c40eb33bff48531da92d9e Mon Sep 17 00:00:00 2001 From: Ruben Vereecken Date: Thu, 19 Jun 2014 15:52:05 +0200 Subject: [PATCH 07/83] added images for achievements! Demo is now operational --- app/styles/notify.sass | 7 ++----- app/templates/achievement_notify.jade | 2 +- public/images/achievements/bar_border.png | Bin 0 -> 1774 bytes public/images/achievements/border_diamond.png | Bin 0 -> 29252 bytes public/images/achievements/border_gold.png | Bin 0 -> 13620 bytes public/images/achievements/border_silver.png | Bin 0 -> 12422 bytes public/images/achievements/border_stone.png | Bin 0 -> 10615 bytes public/images/achievements/border_wood.png | Bin 0 -> 14992 bytes public/images/achievements/cross-01.png | Bin 0 -> 6948 bytes public/images/achievements/cup-01.png | Bin 0 -> 5748 bytes public/images/achievements/message-01.png | Bin 0 -> 1368 bytes public/images/achievements/patch-01.png | Bin 0 -> 7102 bytes public/images/achievements/pendant-01.png | Bin 0 -> 8936 bytes .../images/achievements/reward_background2.png | Bin 0 -> 4429 bytes public/images/achievements/scroll-01.png | Bin 0 -> 8012 bytes public/images/achievements/stars-01.png | Bin 0 -> 7916 bytes public/images/achievements/swords-01.png | Bin 0 -> 7277 bytes ...t.demo.coffee => AchievementGet.demo.coffee} | 4 +++- 18 files changed, 6 insertions(+), 7 deletions(-) create mode 100644 public/images/achievements/bar_border.png create mode 100644 public/images/achievements/border_diamond.png create mode 100644 public/images/achievements/border_gold.png create mode 100644 public/images/achievements/border_silver.png create mode 100644 public/images/achievements/border_stone.png create mode 100644 public/images/achievements/border_wood.png create mode 100644 public/images/achievements/cross-01.png create mode 100644 public/images/achievements/cup-01.png create mode 100644 public/images/achievements/message-01.png create mode 100644 public/images/achievements/patch-01.png create mode 100644 public/images/achievements/pendant-01.png create mode 100644 public/images/achievements/reward_background2.png create mode 100644 public/images/achievements/scroll-01.png create mode 100644 public/images/achievements/stars-01.png create mode 100644 public/images/achievements/swords-01.png rename test/demo/views/achievement/{achievement_get.demo.coffee => AchievementGet.demo.coffee} (92%) diff --git a/app/styles/notify.sass b/app/styles/notify.sass index 989c19924..9e077e9ab 100644 --- a/app/styles/notify.sass +++ b/app/styles/notify.sass @@ -1,5 +1,4 @@ .notifyjs-achievement-base - //background: url("/images/pages/base/notify_mockup.png") cursor: auto white-space: nowrap text-overflow: ellipsis @@ -26,13 +25,11 @@ width: 72% .achievement-border-wood - background: url("/images/achievements/border_wood-01.png") no-repeat - //background-color: transparent + background: url("/images/achievements/border_wood.png") no-repeat background-size: 100% 100% .achievement-border-silver - background: url("/images/achievements/border_silver-01.png") no-repeat - //background-color: transparent + background: url("/images/achievements/border_silver.png") no-repeat background-size: 100% 100% .achievement-content diff --git a/app/templates/achievement_notify.jade b/app/templates/achievement_notify.jade index 6555b0e54..cc9779340 100644 --- a/app/templates/achievement_notify.jade +++ b/app/templates/achievement_notify.jade @@ -12,4 +12,4 @@ div //.earned-exp(data-notify-html="earnedExp") .progress-bar-wrapper(data-notify-html="progressBar") .progress-bar-border - img(src='/images/achievements/bar_border-01.png' width='100%') + img(src='/images/achievements/bar_border.png' width='100%') diff --git a/public/images/achievements/bar_border.png b/public/images/achievements/bar_border.png new file mode 100644 index 0000000000000000000000000000000000000000..9ad03ef20ba9864b4426ea6efbd8a58544cc2896 GIT binary patch literal 1774 zcmV2XQ&$`Z@NdzU0<};;)*_w7zleVj6Ci9$?9--mGrT(ItWVpEoAY%KvL!yx zEz6cAE=snCk(f=A{Y&$Xsr(lK>CW%c+uc@BSe7kmzn|oI|KBEu z#Ba{I_uOL&hr?K{{pICZYT=btRXmYGL_~?LUX?Lfwlp@jtmQ(}(?TW^EWSu7VOnwE}?4I6NysR^~UwPjz6_k z6|V}Lu@yr`1x&SCoYS{6GBQwARfVdfuc|#v2nd zDquWnUEUJV9`F~afI0j2Y0S;d!GEg_cLM?Rba!K*zn?WtM3lrNSoZAOhiuiF*`o!{ z7ToyVk1uL!jHkV?f~MLSGMCl*N!gK>mWK1y=kRB5AKE%Qv9Pd!7QcVFhdsTj^kC`z zX(A#@JkkJ>agmP=U#ToFL!R>)tTS8id|4T;eDf`SYi!gH%WKXa={7T!l$FV=%J=mn zCy&2|e0AZ#-9ZFK@1xn@j9@Sr(-gu}URfg|qQnvD?ef9!%-&)#Qn4O2ORRJbr0t(vDj#>oF1-#0!;`#>*s>AR*(`_mu3}tp$v(t>d=wf;#mhK2|po zQPPG@i?Lxc6d1;2*Y(7&og}uS7%y!}!Xaoa~qbqoJ6$&Bi2T>h-O=f!G%YWc0s5HCr=8M9EF% zzy(Amw3JWF7;{1bCVuFPgiLh&OHW^)_T^2 z5m7<~i!>~}s+`etLb#kx>{MA{OSi(6|k-LmP^ULy{N3H!0^2w8dR%A z$YhpnDi3C7XN{L=^T<3FGLfK>zPEZ~(8yGq(tVcYaA;j;rF-{kb32>WIuQ{ihml|q zZJoEZrG!dPcXzbM%b+HHN*Gi8$IK*XY*zdw4Qjc zVa4&2D~3!YXym=<4^^uMT|-jB=pO|h&a;Avh?0%8THHBM!D2$*(vy36E7_|;X6zq@ zjNYPA3+ZQzz6_vRI2fN{g%c4a3wQ2gyJSSa?@0l(s*s5UjYlo2RXNG3CL&5|*CY>A zxYV!aAyydD2mzK3${FeX&03{E3sMZyLYp5dquWvTy9`}carXQZ+CwCf7;9lvMfWY zow#;s00icxNH%;_S*I2AxO3W*?h|8RW|iC z{?4!b9d3BhCFK$386njPPEZq4zU3Cw0-HWLS#mDwzT%F3)Gue|cJKz;qW;R|T7 z9I}*`zmsw2onI($dAN-SFQC9CrJzdzcP7CmDw{!3HWLS$jon=Dyfd|9@#0@VFqo$I z`C?|~w)7irc)>Dk*a7m=&26mWscM&kE(P2f0h`D?s-Us3v7p(>me%y=KmWwPZ{I}; zFQ(DxwoRS-qVu}zz9TO?$kwg^*TEbSdD@1laUUNLAI+lt)LPwl=T%*=HY< z+S#i4!#l`g; zcu7N-0xt#J=?0q`5J^)u2Lk6_vScvv*&dPu0UP_dcAE{J{pHVL)PVk){r&ZwIq>dR z3#0uyhodsIa+7W zp5|G#>P8;6zTHdUx&Qe&6y@gV9>b?gm%+0Hybib4%oLr@D#zuQ|DE`DTgXdSl1eAX z0xtz%3b>OAG9EM@Ha%stE3i3RUw{1~_tK?5;y>`L5yRl&J8lDKS|mWZ#N`E>D8MMY zIsCJo2P@&HPd^9k9CR2=ab9=bV+wSv(y@UQHc!W!mooP}1z!rd?^VfI&;YcNNRcxm z%@Bz~I>O5K`s>pOYFg{o}SNQz{hT+8OBBl!0EJPS$}=PQjM~?)xwoW2qXOXJbSEywB!redCR3 z#BaM@FF10~jB7Qj_Ql~=sH@xs!GJIPb7y`jOz&O{Q=AsC64gWK?*IFXux`grJ?New z&~0I$b2HHSIq3MR6iN9gDFt5&xbFipF@~bCW+YpxrA8#}swo)@wukSy6)u}Jkv^~W z1>vpw7TB<9IkeRu)WD{;wCq9{GUj}^u`m;QTa5H)kG(nz<}F(u4c&|xf3!`Wyagh; zI=zY%*1QaI(CIxwD&Ud=?pQ&K1(^^7G9EM)Hj@IIg;h5K+l7f>TX4ifRqf!aFTmq! zqCdNE>;xFqvkwHw-oM$l88&X;27aFpM57scPr4i?KRhNou#`O8lbokDA;F3_YSdbawKxSk%zGRCAN?PB1^8)wc#kacil zpY+@3H?M(jmoKHS`=0efFxedRxvF*_y!}=bbk597*Y(f*W%gVNDmaB>RgQmTLG;Ig zP)Z`=c}9VEMggZ&0TRn>jAtSmIk;HBSQFAj{IkB6g9itu)z^>dXlg1x)X?D6JFe?5 zy%hSLa{(-Nc{M+G)gOea^)!FJJA(EY$csI0Em{Ens&cI?c6?-rwjIHX_qk{J{F z_Vr}5)ZWY|(I!x2sa{oHdNLSFsEMBw4II-Q|2ojKrmcwha%dy&J`k+CTStCa-S(u)&l)%@* z!ROU6N&PXDPt@`C!kc>CPDk*JXBcRpfF;B*8TBAj{V#Qz0}D~IZp06}#8_W9-rd&j z?98zh<>bJjmKF#EBYs-lhBeUh>l@qD8 z)B~<+#S-m_5d=uat@w8RM%=o?+waaG=I4y*qefN{_!blRmJs-wIq1Bg9rWtwRFDNP zs1x0k+3R>ZfoD9;VT)J&p%=P0v0yRLMN6QzHnU^?{0UM;#iicX)*?`PPNIgs5mxv2 zFYQjBV?E?6AAbU`%>7gYLCN^ZV6;ZeMm>{I4>oM=v1^PqE#PrS0r%tSm%>>ihQrRv zN?5ynCw<&iSrzwl(EKD@3Bq^j#0fLT4IH==CXAo^kp!DK*gT*=$BJLa+zdjD1p4V@ zW*W}`(DVW!yqZ}!C9|rq5tFfh$D&1-3ib74{63#4(Qy#phX6JJ&Kfy9Y8Xv8NOHk> z8sGw64+TL_<a;_tN0a3js20HeUS3Vv*(DUz37qv5Ik0~HCBDYSzR5s_o%L*@6tRZSunjvn7A3PG`nnBkHNdf^V4KSY)ub=> zCx^d<^vj(Pi22#|wKYkr8y*8Y0wHYnlJ9U??!+*FI11s@rOPRNwT+GXz9{+moAW-m z!Mx9}oHk+n^dDR@ZPns}qW`Ryp<02>9Rt2t;0dwJ%u^YUh{`SEqpMpMlIZ#N>Se6(%A&Hs_ACA9^ zr+lh8ecpu^Y~55oboK@rsu=L};Nzp4dP9Z6X6C61-6;!P0#)Oup3_q_8v~jx8PFUy z8-?t$NfW3EmP~6Wr?-xDmpVBFS`%}+zj}QZY+n&kIZJvbX4vcht5vz|A?1)Ua1it;8wK^-RzcH& zt@P&wMMZGI^vmM|F#k)a+`m6$w&s^YZvWA+chd?Gnq5%8W|ih|Z+YS=NY9C{%qoDT zV(2lJ0;11{;Jve8VO-Ti@NT^M#`SOI7yg_0cxan;!~l;kq7KA}W}b4;opQj%nM{?P zxma&PRm~K1?Vo)%(*50c*94oJ`^W1bcV00)X7Gb;l?r5)!B9_0w(%-wuvj5u>?p|Y z))NX!d&BN;KM4PB!r5m-pZ){%_fJ4W;M7()cmmYd)qv#pL(}4=3Dqr`)?Iz4Gy=F?~I3#K#Msd+iUudM?fRwgO!|@YIw7TeT8ARZYxO2Dl_lNWD28qZBPr zTbq02)mQHbR8~%mxvz;6;IhdR6M}{cw)-d`OQdr4K7GJiS^{a#Oc1?Cz|&G67rISr z*TA;Tn`z;)8gOxx4uM0JdqF#n|P z@0zk9FB1RmAP1i6>+v&3^HV{bGA8Cp4_p$9U&NaxoDTMMFIh6E{nJlxjb%P2V>&jI z)3h}hg6(LU&+mhVh6ZRjR8JvmIMfjSogNsro;@I~SI^Kzk^*2L&=Kkq?ng4GgJcZd zPujL^2X9@yF34=l&&#La<>wVZettffP3EKej;MABUV*3&9!}>M%g6iSdFKSnFTG^$ zKkexsDv-77n3`T4-;S@%JgK2Osewyk@e6)5H9KDj-I||&{s!OHtyjbghN$_bSZ3qK zU?|dfGzwN#T?JJ&RnXGX96y@5Yd3hRs$;@|(1|`5d5lrtA_yRvoMG4!K^(7T68K7r zOQ57%$x$g_Hn~cJ3?b#?eY|%UEYu|Z`Rj4lEjLyz8&viJQM&_-f7b!p36H#-Zx=jO z{JN7CxCDz|utQ?lfQUNnT$;C{qG#)@S@%jVSBc)|-+B3E;VO%+1Wg63s=5;Vet**M zlLG+?2tKOKQdj_vK?AiHEa(F<;HCiL4P6#UuQFOdGT10|Vc&55M)0?^BviLV&15nW z@VbWq>`cQb=P(`cFsk|3%vn*B9IdhufF;ty$B>m#n4ZiK~zoGq$8(cRUj1J zPqraoJ9h0zXf7ta2nK@>Y-zPpnytF|!~cvWH;S+fnR9c&+Pz!nz}>Y60&Q&($2Tpl zJ?GY27Rc>w8PMF^Q}p}uf?luVSdJOZ&H?=fP}NJ;=fhMPKPy&o9vGZxN(M*2*yOS2WlL|N;*z_h+WdkF21vZC!$&#|R|Ni$bJrfe| zme0nRCLFI4MjST=k`I3D*tvs%w*9EpOt9PcKt|?Hp`c)w(5<9Oa5y}wg15Z*;xzAu z4d-{|d39cY{Yz=*oU?=NjB=aHCRbON%JuaFpv_exx3%>HNwRkKvr+X1^czT(4vo4{7RXzK2F~1K_sbRz(3_j=a%IXpwvCb-8b-*it>woHJd9{g zB74nZ)Q%r7$X?U?e*MeMb9?o9iTHI540P=fz1r(Z3ElS&TmsniR+oONy#gbT$|k6;nr%>n%fB<16w+6`mWZY@!{`~UBs zQ6{G9*Danr^?!AsOM21C?+d!`6}V2q7M>nFP+MC_e4Sqg>+6T}pUKb1&-!Xh{mBJI zFtopJ!&+$XXix4WW@oPwN_#9J=Hh0EF}gXpbGz05>8HbjyLX)>A2~8g4)}{fjtGrl zAYWS)K`j#uv$>@_P{3iJ&23l}fk;$^YaF1_K1L0D+8v7WC6pgX||; zFsP|mp?fx&YXqlbwV}9Vt#R_?4W{!iY=AgZO%SuQue5W=g>p;F&}4i*1aIWsxMWX_!3ag@rO;b+?Owu3bmz{3lBw<$D9J6R>I4Pb07uG(Y?7 zqk7BmvQvcfq5g0DiB%2#v=ytDYpOtEHc|8lg+l;K09s)JeNo^7A=q&L^!>Phs+t0Y825odi&pD)F(+rOX~M)$%{Sh>5A^F(QT4K< z^=mJd>*~%;20W~0LGTi#6L2SEjFA)qLWdP9^K_>6!(dAG?YBQkpxeek*QNtqto83s zICS3&aB)KaCeCL|W0cK_VgKgmo_masM53AT(gXME=GHbQ8G%I8aWnDN5}J?1Y!rL+ z{8Sh)a5Vv~3Am{Pq}{vB-q&Yc96WH~QmMUd0PXmsvxk7dPA`SbKjkq5n_@Cj1v4nn zsjw+O!@t4j^zWmr$_cC>VCufMCfq8IC&9!$#K_oA0pic!mcW*Lc`aVix%m2S$=sHB_{c?w=YaPWqgQ zh=pPE&iwa~z}hvJNQWAxhZTh?T7Y(V+R;@7!bd@?P=cuVYNDcYRM8aBi2Bjjg6hNR zNSvaWA&{leBko^?Oj3RZ5Pk&3C#3(0q5V*`Q}?gJPrw%)j@8EAy+5%%_Q%ilpu_7W ztyz7Jw0F-ni4+fWc;m;6PpEdhiHY@?kI$T)40JBgF0bs?@#(^~m!~aoI)%u3(MW5u zuwkeem5mRMV4*RZkcq$ z_+!hDfAXHOtlY<3GwXl@s|Sj1cVSg^>-Zy$Z6hQ}iW@B%Iea7ytRylq5ju4kP^^3% z2VE-%T?gk^2fr`qzAxbT3KG>vV9T^r*a|uW8wN#|Ch*rV0=aI(I$Dku_vECfZx%<5 zep@IgtYSd(1b6Q;`u_c|9|S8aFO$5UV)T`8peJj}(as(=;UXPf*6Z*pY)nKFSThqK z^1FCV{d`r$Sis`#JJ$Zg@ENH8NCP*DPbr#BE<;K8H!MH>*?Y#pL;PIY#wOR-b(hwz zxgk+X0c$kJjUAsbk~xOS+2On@7);TFPVwnj=-Qcl9UHymr*1eQ(48>gV!*~PR+z@Y zR@fQXaOlj^Kw=Z}yJg?SFULYZZAkfSalpV0oLS=WJo<;L{X2I4MD}?dlF!csB?2Ke z!=MNbqhUI{+?fuq^IQX39EH<7SHWZV&Hr5-=y>=dW~Lh0QA|yclhClf6wRHV!?*0mS|uo`fDp!uZXW|WkY6(0|%+9MlX*4!}q)UKAU%U z@JP!jYB~n|5k(__Ns^*wZ1gcvXOCf0CJRE+pV`^J)B9`&eASl>o10g7EkSxiDF_fjR%Ep4$X~;SY)zP-jUgKE%*Hb;BaH1MfRiMLa z8AEo?*VcRQdz_e=0p=Rph}z{#%a`6LH#LoowR;O zSA0ET{+!{_M;|@Wew&F4lCZh9^p=-j{(*nTj!XE^|ICLTJSx~$5LMe5s}52WJz}4} z?--_>HAk>mn$!#8eeK@^ysyptYw+-)2{>|E_W3~a`+$u?0&7aL(Z^%y=v@I}SKmN& zlYWus0_z{D=K>!+o?d~)i+>>P-Lum)dFmduQ|=wD-% z4^H=BFc@?X3u&n#yXr}Pou82bIT`6uLwvSx-;wjv{(VX|WaX@f=*1zU2CK5|Z3%=M z#Zw5lB>XnD@;|I@uJ_+R$Fp?lkJ-<`r1lHH`4trB<{lMnF(Z*yYmH&l=zoZV%T_XX zzP65g?!GIqZta6oM~71)pdoBj-Dqi4AgpR9478J_#R3nZi%Lg;nv$?cH8s-$3%=+r z_U-$vm|x&lO*f&q+d;u@-znGEmy`2p(}S+It`>Tf_DBl4L8aa4@GBNNAGtB^*}kLO z&0|M|!Et$^aL~Z(}EB)r9}uOO}+kfBNZf$jR3%n1Ra1C$*0iHVpS0 z&OY}Ap`c)&0-KBYYQ^rS|MYB-IQG8{9nJXG$u0EY(}$8TdN`w55tTVH$aesWzNtU*M9lFdF3YH|j_IOuA*X6*)FeUXWNz@>Tv-$bwe;zz^sEqv_topz8 zl4+XH#;||IF~BDF?e_-p*WLrB{HNnLzr4%8efuwEkJl_Iz8cmY(x3=mDUt#$K6t#D z38Ctj5P-@JtJO8e|HbEh3$k0>WI04ntrtt>y4-Gcl6d!7x}*IyL! z)x1Cx*-Xe^=0g1`(2fE;)+$iT&3wMzf#u65h<3*!L%)8lYG)UF^xOd+cbnYQG>rfF zR#z(xq{N#!3`_bjC^O23$lKD>cO?j*95>UlOM#1FN*ZZFEWl8aY*gpLUFe$Vp_W7 z=BD9#ffPTvRz-!e$H1a#iL-?D%gTVBMTM|n)f!C@#aCN9u-~MKtLs977;j0e<6>HN z8E^@rlDb97%}+o5DDSsDf6v|YM5zs@L-@=Gao79AY3i1(SQ<(~^I`vC!(SlV5*0T8 z2Xnf)|MB-XsK4e@KnsKhY{oSqQ=lD_e_s5;)5vY&kghYMjae9cp+n2N+-&+6pzR0W}xGjj0#tk z;Ec$8jU-O^!_Gm1=D{~f6e2HN^p$S87IDV0a%3$iNl)KhJo?RH3LLVCRr z^4t6&6H+lFqr&|u*gDs^b0@QMNNKUk<3TS zMDOFVVyHR6)P!I9Lj49u|6|bv+dlkae)iS7w%-Dg+Fct&7U?uWWGa1;>CgOVm+ud_ z1Y_LzNr32F|NQjhcvAS;wXi<2DM4RfxM-n1Y-mOf8@^b9t&PA|?EUD2m!ysk2f4@~ zsrfaf6KG&#J5CC=qlb>I>*SzQN2G_q^MHTz=0A4)J_@=8=zHs5_pj*9Hs+0(Z1a_0 zKXS)y)Ky{If_L9Pe}n*~3~=h$K2;!$JaApnx?16Qb5d_ENgFP#!M+Jk2A&i?NRlZ0 zk7L>Ns{Uz!gz?!_*n%{f?WK2Eb@~A{IFd3UPmtQFK`3Q@6?JG6UKfAG<|hes7eZu6 zjSB-^7+vN+7H_hPmwp#_*LbtWn>OZg`J4Ifsw%=cGpmnnG8m5xar^6YyM{-jpe!X~U!NpYvFJgeRGNz@G z5e~7dRIV!(F4p?m;em;P+S(l7=FL~}rX)Z8crpV>ztg$9(WLg&v)@xMl>eKB>E8c* z_%iX?BCrJm+DK%|g#2Dj%uw}C=$eF;ZtIrcdS?E+I}4rR!f7vJg%JO-Sd^Z~pHur2 zZ(3p%%G!6|o6${xTn0Kb41^h&SvCB2y${ttEeR-@I$LanRBwKhiNRO`LdiiOO}A!s+>LnswXS=gk|* z54pjpByUP4nqf;_4ob6ggTX7Fcfp^48S~`+^HYD4T3rJ)K3fX5lN&mT38eVEPT#z_ z|1Yq4lR<&5U2r-N8OBe10m$`cKMwu)M5ZO0m1tg~`%=U$xBS&7^G1#kpgUtyS{T7l zP0KD(xuXUy!D5O0NN%fp@#1UvK*{wwrX(aVB{5JE^(DrPeMT9}MlVeJ&wu(m;wO%l z{JuyuQVOPEJK3RA11W(JbV0YfTgRXN_*aMw2yPP!3lE6>2fTJPrlmUL_QOBYT2=K; zO@c6I&aXH>&cc|LU8Ztfp>VO(Ej)3kYtEd>1OUxS@R%QA$G0V8O5z;<{A|cVX^#(t zp1roKu(=<9^!#9b-SkiqvJxO+qme1tPJZYxbr1+mVIj4)j&O~X^FYsvqosE#}+SLRx+Teu|Mdi2n=22%N-SPolrOGySbMxy@h>0MvK@P zlG~FoC2{f}J}N2N9eWKECw;2I=6mnm5`V>}d#S3aQy~OXu$?l{g~!x|M$}W_$QG=t zy`|%^M$~?~{+SAhLcyyEuO0^K>ywPUYey#Ia$@DJM6Fj!4m{+>&M8 zh|2YY$lz!*#7=~t7#}W9-c3*4ES~l2f5597TfT*Nn?$oCW6mvVX5{_@W}*|Vo_x>W zU${+jxdu?b%^zSKaW1Kyg6*V-Pg9X11;dd`&l|7f4OU$buATUE2ccWZT=wl4y~b6K z;x!z~6AGYQ7L_JYR4$eucg*Ep$D(kt@^0aDKr-sKItg{-BT%Ny%4#Nw*BI#KW3ooATGrx28Uxm`%ySdE0M}= zBi%!^rA4I)P~`?THTGo`j*V6t^`JWuz{RR6(acwpNZs%*lW00}Ufm2vV?dfTq%p|QH)RZLV zMkpc~3{PK|^4m_sAd0fEr1#4&9$K7Ph^n^{zsb{}$v{>3ZqjJEg93Qdlv3AXM9B_QJ@~DC*Ndu=Nn|!b& zJm$^2d2v*X!k?uW=aeQpe-S<*9V zS(U?5z+_bJCtNVEp{ZFMynNZE5M9j722MI{R*FXf8=vUbqyqxAwb{YCx{+FSyDTa~ zlu+HcsmcArhQFf17TmerZr@7?=t z_9H41)L5!2mrg{1AHLJJY16r!Ny(=O*qBDw0hb_HqFH#oWB&XJ?1%u20G%~rxaP|x zQa3aMs;Vo)uha4iwlQn8dAu*Z_(R$2b*kZFS&~wws>biBGY<*cxAK2R2uf>-cf3!a--u%7nlrk$Vd(oO{WVaeT0Z9q+a^33bC@ z2|M1+TUPeEdLoIsnNi)sX#jSqC`n3n%rk*KW=afvq$=lX8|V4gOTE>8LT}8ar62NE zQ=*2!JU%B^T~}vK-?^g?XHv$@$0af=JF9Ra2OPU(u-U(R_f+-`tWAs~0xFYWqN*n1 zyIIpSYKiYwqnHX|b%S6avam*KR^&6zw4{x_{-i(nzWerOM&;0#+hotkY|^uuVl}kB z0@*oH)9U4I+H@J?HpNN}bZJ^3o;hh$%*k&2Jd>@PSW|Pk ztjznLQn!@Kg;fscx@>N~+V|lHc?xjYUp(&I+)pAm@~VVpCANc-u30%W357d$;5d2I zQ@F4XN6bmhik5hnz&Rw?=wzaXCq&J?nJm^0aoF&c>RrqI*dx;GAn&swiJUlHE>kvfzBvgtVv$!K?L^i zAI-kuZ04YGu99SeC7KhGo4bTD5xjwYd#1~3A!{(DZYh;xR4y0-FL>w>7R&qDeq+eX z3(qhVHN-1M3uHx`mAxzIY;&T2)5eV3_d!^=H7M(lRJs*36c8s z|5`N<{9nz_lUrLyt5qd*>7$gorBqH;Ib4wk`-Rujt^E(bo2o!ZmCoCz@4Uzjy)vm? z?2)vBL1Pfr)dN90YJgX`q|yUo66T~{bds%~L~~LzK?EP2tV~jb8X7_gN=sJuZlSQS zRyk=t|7Y_q2`!Eoq{|)Y=G{{53}i2om!r#Ik^T|bz3VEq-{6M*$dQvrI~L}46HPRP z4F^Pz<$&A1V`na-aC&848nZH%$|X`b0rZpICT27HcI?oYlWd7hvgRbF1JntE{5jdn zn3LwmV2PwIXs_mCQhB$O$}#;wQv5lo)pbE&?HVhqavfP2Uq-B``lNQ@=+-?d2`+s6 zUsO*%A7_A04A~JoscuGy9&BnRw(~%Z|2DGo=43r{B7^~h)~K-g-g={#{tp{pQJgdF3Mrb%-SJ1ok!G@gw|` z_MxgA=}#@z?3`-WZAsM74H`92Tg>chZZ3uB;X0;xO2G0um!qG|IluVhEDYKFVk47wsS?LO!l>xOc1+sIN z&^}4rmSpmAG#1fcGo%v^95|TCFu+cuXO0&{VWAV_z!qynKzP1Mvf2E5_YPtYs(<(H z8s{_FrbZ}*?%lVlu=&6IB2V&q6?0OKELWR?EY)I~l`=Iez5n@ew9=2L(#Zn`EaR=I zL`}-diSpw*zWQn?M3!D-r_<9bY>qN7#W=9(Ey|%Tb`V}UGT7MIgFPTthU=2#0s|)4 z3^rT6P*7N}o+SS_3#USOx&$3_G6i0$vCxH{(dWVa`_ZfnDt#$H%u1Kp?w}p5z9G4M zT)mE2nNwXoih+$^;)++gsQjFm05;7mTpVm^E&u%IRRp-6>;cs^Hi4_7qf`D{dq-R7 zj^a$;&fJxP(xHY?ku?EQkfmBD{JHivETmW0>_+f{VpcX43*h9IJ_38WX_A`fK>XcH9E>^?65LeDUXj+S+pdK>zje zC-C?6M;IzkP4KfS|`{G1iSe@`MAhD z{fc%fd!%3a@^V)|)xk0Ga&ZXSs(GKl_7%%@e;@P*jIFP~{u|O4pS6x3zeVY*lClqV zV_QJ^8L}lA6DXU7Q8a|j5vZ%nZ+`aKKM=>XTdae|S&u&aY9RpG29BQqV=tYa6nH}K zK0DRB5-}$Wi7oC-4Ju8wg#AVk=mz*GilIlc1-DcThUr(VQO!DWz`)(o`gLI!rZb>v zVGdYZ-+1Fb->zNnq~Cn==PJ+~bUc6vrf5uHMyitOps>}}<~Kk0+{5u;;~Rbf+~R?U z$w&eHH;1Dmd?#7Ca#X4-rCJ=yqF)OkcZtGh5J9f39YcPwjtv5u?2eB1_O`Unf>u=A z4QgwuHPE@2EV&))%F=JS-_C)z3ZzgVe{7u-3!~5TS8}s9>8ClTt?DHXaU>O*#p)SA26M)OFzX1$nR|}TMgzhsA zFg0icY|td0vN_$~eOKms?>$}E{&LSf@b6aboOgJDJ3LDvs1`3;C3T>jr0`iuMQlV^!FL?NH z=8>5*ul4WQGd$MES<*A1aLhR9HThiK-!C}gf%|T|6*AJ&;Dd!+ao+|SWWWs1NXv59WpfdW5@m-g~ed+)f3FlbS#U} zRlLJhRp6-bc6OAUK zY6LQ~84Sb}G}vr(gY2B`tzXWc+V;T*w+9;=OJaeRGkgSO4lRcsBgd1EGwAL%K)PJ5 zKL~e^9|^g{GF!4?qxSa)4-OSGGPavaOItW|GHg;Ja88{7i8D}JTi7~l)^GU*IS{s& z2y83-BCKgooX;>5#cOJ+2TMs2q!R!=O-*$D=BRLMb926D*|I5t0|$(zj&=d|?U|&m zU>aU}N-12{0GIOX&P;PsQ8&5~OGt$cRZS$IiB>Cki7)5czGFY>vrsn{8ExkzO7p)D9|=Wrx9~-%BncwiG#={q{_Qez>!BWtZv58X10XYtxYh3KR|EL z20hShrQN|T85iM$V0$|R-ENRPUcePjL75^i zJiS5+zBAXHG@&VJqKam~&29&S!wE-phJ_($){<^uBYhd4W6SH_8!~h9;Yjsv+J`-^ zrl|LsYz|1nKAc}l`!TjYRwMj0-$9>0_UbH{w~Sj7+u^9py7%6Pjm5M&TQaRlM(}K)TA_p%t^8tY*z3D zwSB`mq-9dm zveaUv<3D+&lYPDn-={pdZnqY`wq_85USiCe@is-jyeTV_g6BS{ zopZuwF%#edp*6ex0a^tf1zoD0xxmpVwn$+Uj3MAq#7U3BS)r`#g$=x3vix(+gpQ(?-F=B4@0-Y-(+d z^mmUZ*SBR$rfu@%bqa6+HTPyaGIQp|{(bu{(kKkEg;m(Fy;a`=2UmR&mfeD)BDikK zIZ&LL4GrY;8X!7i+2(S=-km#O+vd&mfYm#T!9%{RH9K+wjmBtm)IgLEq2x+-WIkAu z%Ez5q{u>8gIDn!&V(cJZL)b)LtLA6D`}c>TqeerPGaW{P3AzCh0%q*!A=0<^5IFz4 zdO2*_x`jfwW5wq%$9^?6Es+s)xqkZebFiCHI%KMC?%ZXL%P(Jwtf9j7@vdLryM5lg z-=V|D!uBun0mZqw^fj82pD+6gI-2XiWH!U~mtO_H7%?8otPaR02iFUXSX2`QotfNq z3aI+^h33Y?(B7u{c`^Zyn2W}UQ|c!EoECU1k!JMU{J0)i#CKz(m8o_{0vC=@Vz302 zPBkfQR_f2Wy;>J1tyj;sYy@upu+Zj-~~JX-8gpmCi(ci8yBeQgh=~sde zb|&b{2wfSm+JeNVtE=1vbDJZv3S{T35t^AJAy8kBGS zm;wkY0h03vl!2awj6W+$LhR;JhhAJh zfkIT$KHOJNv_{Cr{G+(Oz$ebj(H2@M@e){8%s(U(S$} zd6{ad_I)>>FFsKFh}{+sI+qmN^xRn)y^evHp57+nI5hT$FMs?A)E;gOzrHp*jfOqd zR@HbY?m;4S<7jDS?6!v~hml27>dL3u_Y!#7zIju^KS$_X5FMODk8R2B9U<$^XofPI ziEb3cjt+R_->+$RYq1=%Pn)(=Oq)9OKkP3$+-`W})mh=!Sw;~q$go0Yx4!iCTZ_Je zU4Bp8gX~FE?jo>g!m5!kxf$BqToK?*ws`ta?eUP0n~F%LT3lN()K3!h71>}3Kd-N? zg*`iW#Qju^z&myN@z-sQG(CVVuc!xHo)ziGe|={TG&E@|R;**kzJj3+G41^GcbIzg z_>BGKhMhamnn_ayhA7z9EMFG+@JDJLA@*c^O9tz_s zEh-OZHiG2w(9s35FSN`sFiQ=mfID-|!Ehc9(?+y-*%TUlYBGapHi6rxjWUMwaDrOa z6$)_RVvYy)?4gw;>6w`{7ss|1(tkV1hW&d_&<8Ip`v$)FW+4?Gt1}xWk2?>3m>Y^o ztR=3}1Al)}d&owkJL{f%9wPo54%z9z@(!=Dq@>Al#T9>d&6)EUn~!^Z=4|?iD?(a{ zA2*{g6K0N?7Opn=cKK4eVZ}+0?xj%HJv2q-A-@;u4jrOZCV2N?O-S`i2Q8WL)eV$2 zz!b1#eiGQ!H1*Bqx$q@zb9Wjg$zQga)pQI#X_5d)F3uI&|@>4s?nEbGG3 zM6)>*i`Z0KLq(&hu`ygNg@w#T<&)tDc_EG3d9V`h{l9-kE9Au&KV$6H4TBkEEs*dzdN40By{`p&c zTen`Jq19%CS01>RDjnwLUaF72D|lL?hv`^|or}X65VE}+SAx$&)QSLviiT4w#2O2S zCo`Sc*#W9g*Rv8ZP3h9|{C%)$d(){#3% zE41z17g4kr>vLQ4^R|&MlP7tJ3F%gXCINqVss%YUG$#cd6Fdq3jGCFDK#GMZm?g~y zlF`&A1p-ltVN^O_!y&NQoutECp}C<7oLPBbb!JBOVZKdqTA;re(1nc-JiYtwzlcs} zlLDElZlN{D32+vGs5Rk)w5e0q>p}O~N&;PZ8H~)&h8>;&xPtLJKEk$l;|d7WH9+(F z4d5e=XDrZ6J$tUnxb3zVtR_=saQCiBlE+P9!%^O7PKM_-VS!75cV?TFny95fO+#vC z67USf#7rw6^0C~z@2cRTLp^~j(8oA{yS^U0)wPh8o(1imHps{?jytajnv&P$q`|Xq zybUY1Y}LZ%bXH~Eci*GNqM~|6)i5&SQGlaq0TTyY_-hg9HtIn)Z{;c&(5(p0>QMqK z+F}PyI*v5Mo^QT{=C$jft-6v%kYdSu#xMTo|2Zzd{5wNl-XVGKZgX(!mh(bE5-*OP z4UhUxjrLA8eq5ZO%(xty(pXRiHixEIH1zDfz;^4c%k0ypEti^_AXrz|gY+Q{Q4;Bw z?KRcVaquARUpU(wrWy3wkMkVx!tB?ha%~v?&wlX12RPW=jJnZ9Y7jVfN!D9;dBQR<^16fFEsw~ zj}WM=oWTD4r8)1zux&eF$H7Y78J3v?19NUpI5Nby3|DJka+F^w#}kxALk&^0qR3+D zlEPRDvQ%RwK}m*V|Dcb-@LZ;4KVo0BlAC_VzWp?1z&D(@ zc>lLw(JebW8=AQKNCmbhZIdUfu(id34g0k$Dk>Iae&JO%Hxp&S4{{-N*3qNq@CUYW z^QM@v(K!Hn_JFJj+ z37AaVfSEB1`&Ns~r5PKB{So^m_D{6DVznsPtkx#y4LAIWz_yi(MzXM}i;ITmr4&Is z54sASkAqI7w=Lt&JO4}Mb9SQhfnP)g4eum+lLV=4e9xY6$>2d6Z5Q(kc4$s+3bs^> z16{}#WJ&>X&K!q*8*uBk-7>UXEXm2$?XN6&@%pRia5UsR^w49rNt3oSu&K)C;b050 zLSkfK)AiptQ!yD|5eoHAiO8zL)q_nj8m~&RB{H$UzszJy!;tX1bsRa=P_` zn|tIzhRKjSQ;ICnYP$5YwZ3Ib=?-N9o7L&h0Ui28@}Dhz@-NrpPvgaCuJQs zZl{=?{io(p{sH`;#*Mt$&Ub5NpdUX%`eJ`sTK>i zP?S;xG0nD86$iD?_LSt2xWHReKw>N{6BPj55W42NDri3v@z(+_Z3!a*?x5msv7mVw z6EYABwy?q_0UfJuCSaB&He_a21sLGElRvFv-q@>WZzvyJ4tBd8s#`mt*&UrISMzv- z1i)Ec7ON-7P+GbsSX*;Tcu~fbS($3_lugOO(Z`I86)bGHbu$QbVSwgO?m)6dJPcbsC7%g}vOfHs=1xO$y`_3Cg!Gv&*rny&fCj2uvB zVK$8%^)*D+tTzo8pddpOEVaI1!jH9Q=FlylsNOPerBv(4krj-e=I3Bz12hs38z`y; zEOeYtCqX1-;AeKC((N_%>$ipdj2(OT!|bcBfEh!}HL!UCLGT6j?=0#aCtDEmiUm4i zfu1De(4lT2-T9TkOa?3HO$eK6XE=9}Wrra&8?(#yi(l=8$ok%ZS`^9O#0vpfhA;?PTANb353{IdCxN#O3wJ{c`GhbAv%1tDYo0VWtZ% z{8n9(NZ_YDPpMq0X$GQ(%DD_ZdM#wkNi(sUGWwdtBKF3fOl#V^Tl3mjT)dStAp@MM z$&g@rxkOVX$5J^KI=`W)XdC;cMVmI$#{x0pyTvjm)rP47Y&}F_0G=QfIP)c!HHZ$! zs_=v{yjhuQDRWX$Hr1T8|LHH^sQm{YuLOA_-_H`YfX5B3mHRb6vy_!pFnyo*+cc)6 zp1Q>aE(y~z?AKXGjatRNcm387e}xJYEuO^lp0Kv{RUxx7e8P;SrTo3cb@z0Y9m_*FUz1m$`txdKmQ+7jiePov^9B?1NLKjvz+vLf+ z(4l1C^zHf$kT1yKC(4${e^Ub|YVc$b$m3YE(tP7B--%|kE6S`)1y9a+braNJi4eLO zo5qa$FAE#7mNNSo#S*G+oiHcsD>i6e8*+2Eaj*qC16pT*15Pq^O-n;z;X3vWpRQO1 zPEi1pKlv}I+SiI%nW>&M z0B4ylNoRM~oW$0+bGzoXdBA{GtjGXY6rCtok`!&tGpWDr$z<6(v$vo0Pm+$-+j>N^(Fgcwp=s{2po+$nG|qI=HzHTPC2k}TV1h*PLw%7Og=iqo|sEO{sEpi^+cvU`ov>a{C0uW<%t#+>8> zCQ@h2$<6{7&xZ?Fp77DicfMW(J)CxMB)POOYgS^r8RYAj6KS#k<(YY+)mlwgN=d2Q zneDr2)h+D15p~NRZ4^hVe~xQmB-32=wcxJR)=w(YNgwEs3h0zMnKW>5%*p_7R;HbE zj%Ka7gOycuMb$FdeN<*;e}gcbs9ZM{I925Y9K31>%{>TAadS%L&H!~&eK)PTS=H^D zVDN{kx^)J)eM=T<_h+>>JFmTVIcP_5^ROjtP9CkoK@8~VJSa2?ZnZYDZ~Du7@57XW zT=034gAPxShlwZ=mAhU!nP^&CQRM`?eI->nLyWv!3cAxv-Ix&p^d^fpTeF$>-E2hN za;BKX@Iv)#Nnc7PI*EO=W%n-4&rH2~;n*B@%$uKstyAXY(Etaap9E)%nZ4GLBQ?ty zui9JzHgd8`-AzgFguN>%Dp#Tchns2axEDitIb+zShg6N_X&oq0t4i=DOVzD+pC=e~ zvnjsYjYp$yyBB>)SJ=@6OU^s*YlsX*J7#mTQ@|x*R;se=O}p^IcHCNbS@sY5i9K*(4R)(sG^g{0$i+UrH@rP z`z4pm)7(Yc+TiW47r~XKMf8r+S+f$`l_1{&p|$LAvl@pE_Zxfm{J*#XlvF#lVPnz& zH2;b>*PPs$_JdEmfBI_l_wfY0Qz_nwicK23m zQ~GVUeFc%3x%i1)6V6+5Jb+`NV@D5Yf+s}f4)QAZ+Nbm2{2o1^y>@?it?$mLoJ9cR z?*#dOSm4Z8UEOFLFyN`MX_@lpP9MLGH6?TNX4)TqY@_1G)9a5n-z&}HRh?Bg4BpjN ztkd(|J_KgPQXSYd!II9HlSc)dj#(+|sGK*J%6Zy5;FWow!R3AXLe-8TKuNOd zR<&azwC|1{)t!FptqUM}CS~4#laBUl9&0^Rykcf=ER~!0-4d#DL05B90TfiZwOjyY zx&S{Wcegz9)UIr{wEG@-Mzq_vi6*0_-Y(_OojR~-{5AzPEZ-t-!_Og-Z&OXl>FVS_ zYMa~aPN;6JjrGv9V{4SU*(ObTJ%+jkxZDSz(N0#9JaxQWD!B-hbY zKJwO%licRDn{kftUvkL0n^Z7 z528O8N{SmIk;{}ncWMMlRKLw^0z+ZZ-yDB=_B#bO^*Y;s!kLm(-C7bCRbXFh+qXZe zs>C*R>U$7bphGVma8!xzE(0!(%4q^8>K%jJJ?8Qg4Lvk-7W{P7C@S%|CF9HSGcTQG z5Uw~!fPpM@_CG$g)YPl@lc;i`(ytVBr(BSPTsxvVAEeKE<7HLV)W_cjgTLu+5;d#B zpv#e{sstnBE!79WQ&p{1xBUF|_De2V2inCsd@%rmDQ@Hk9c;7~&*LmG_ zvp|3KQZ@|$#}X{*65!%QEHy)R)Gw2M>#g(oS(@j}UjSG4?nP|t030B;bus}IJ_?mC z3bv;N`BA(e%0@3x`kU`OY{cHNA-Op_)D7{86G+vBqI zbfHl+^MSNEjUDlWZMeuH*?3bDTiSW&&E#OyOLQMo-tBlQ9K(N z=gS3fO`kr>9ce4Ix#Kll{NocHyB&ilKNn=Iw{)uD8HbGs+8=&20v$i3M-)>Fgq&z) zQ=`X*h_YEOxabD!O}8|vkSW*D`BX6Q=X|4>#((Ubrue2L_92W)dMc~HclfZ@lkVpJqCdBH(~5-oIZP9* z)ycbDY!GI|gwC11GVIf_(aY4TJSQwTqAHshN@{~4&A!#~;6pd2{pQ~NjCDe{Kg-g? zR5F{Z*om+}5bKQiRz zMWUDNoi=4+9{1pgdcMQ3*&SQce*cG?i5VGWl#Qwzf$h;Ulh}vDv;hXPfP%0=J3Eqrq5Qyu-V0-B}<>CiRcE@7@EKj2R7k$jS1$+rdxF zgY?{j*tR->Z3i(g#yc#UpBsstI8uc1RvBCyL8xJm$8zP>-v!sM*)KIWUw}sX2!c8JY*>8ZscG~C$K%D2OD1Jmm1o@6^zXRtt4qa z1m3zjaPN;U-^%&JAO1$3y3nBFfb&9(5#3#-Ze0!>HU>BrIw2Nx#{7J@)X~xG-@A8Q z*d`?kx94CbOc*{CYTDcB-6|1uco8saRhDLog04&yMr6sdcQptH*kdwZbn$j*J@SRL zfB&Td$QF89!<9xtP5lK`v*GBCqZcK1zJ@CNqiAB|A!V^6`M}xB$ngobILJ!LyW=)%mt< z%OYwMzN`7YUT8nk3bw*R@CSnwI&6;2oY;{|e^3`XZ2d%`+!+X3)(YYtMqvfhn6ZbA z{Rg}mSiO2ENWl^nG8ym8oJ^X{{#rKy3Lce$ ze;I5s=IQO`)kE!Y&>Kt$8`el3S-YNKqXpA3efrbZkt269uyru7`9OdAa@wD}^mNIx zd|a->rGao?6_w2Z%(`8`R1hdWR{G%wAABO%+}vN|ixn1tW6;3x>yiOOA*-My?!HIc z&2Vi_n(ptm2jz)oBqz!)uISa60GP?)&ZU&Ex54Z5yj@-Gfq(M-1>-fl;*R z?FxOic%uMR*zkI_uHP8tw^>GvcsuiFKf{$#Ta~SyQ8*sB<6=r4H-$@JT7u5cRJ9S! zWn~*ZD^^T~K)@D`IJLEd!Da=c!$DuGMb1a7bh)CCKTeWHZxVz}u8;{4Ry)h(SFD1{ z%4Jf0-8gb5N>@9PM(xy&#haO3sd)Sj^;XH|`wkY;nMX>i#u(_fwa z67kmz+)dVbu0L?j|60WRGYq2XSYV@W1u-K7t=jypv7}^W_PzJMs@!0CzfEtv+i`>K zxB?f$w2T)>5r};qAln|_)~yrSAd2sBqXxS6BTWf?x_HoKi$Xeq?ktxe?Aaf>2&w`L zrpZ%kje`b#5Ui*$g2y{N6i`v3Dryh_Ahv&Zv>iNIK=gp>%zL^TG@V3tC<Lf;*w322 zVqsCha^=;FQSIbI4TA_&c@fY;@X=1ziOva~{advoGm$>cYkt%DeJs3Cc~Sg(jSPn%lAlp8j;HMxcg+Y?9{82)^Et6127*EA)5*b*e@A^g#YvYXDS6*2 zmLH^a>R3UEgrCp&nX1pJ>w6}9TzbbAUhdbRsT>jW3;t%i>lbgBCrtEm*Ot~so*N8~ zn<2tog(ib3R*DCsku^U300}0w)jV4)*aV}&=e+Lvhiy}*RwzDO8*@#)C)#iOK7nI% za7JZggD4v4j=cQx4?OGEU7rX#t34fh4;vR3x?-~dewOb5V!r79{zy>vzbe5C-w@wU z54ym*wdRf&{&tsi=+JE#RH5K0!4wJ}o<}vf!gTaxl_VT+Q0DUhHUJ@GCr?6|RIt?j zD>nmrT#3Nz|OS|hU$x5tDH z%eAnABq40q&v?W(WlBW?Q!;QOP014qTny;g$fb$D6RJ*i;~)R{>p*4Y#6-{;$j^EY z9S>GVdR*wP%T9wLvmxf+EcFG~y>2kfCTe$>hYmO2M<3*QKKk(I!KS9G$?Y~ac*%Bf zZfb#WXQrLo5Hep4hKSD?N8x1kkp(S?$0CK`OZK=plT-tmXb7)ZL<1vfX)8=4M!aBu z`0@41&C|-mHYyMtdbR~q6Vx0!8AVFn|Rg8><$f5e$KqA;4C9rK~f+TzjKyR$u z34!f|0v7{1LyUBRgR%M333R{Wp`($?K?A^2Sg3)nsL$ZIX#*R%yP|nnW{a(csCIJ# z@|$xF;&)6`%foEmDDV(AG&R`{uG6{I^R4O7Y8ahS4Ix}=hCWiLqpN|N=%BCi`Lbft z%8JserZ1*Aq+o0%W`jVSs)5KX2S$s%6-9G6<`e&H76Y1vgN<4&4+H}Dk1-nt$5J(H zSb+XsoN_Jn)z}h0J=mzFGk*Nb8Mog)544peJZ!qf$)_c7I{uu%KxbwyTpDLyI+KNt zhW@+tq~OJCQ=ArhjA>4&QzOI*j-bDWgGbr%k+ER2 zO_=bn^xJOxRDq4nv#~*v<6gRT(g7C-IwPZW>|~$zM9|s$_5o{mt*W74*Q0C{n60+B zCg!cVcE~cu4oi_vA+PoaE8a93UnOQ{BSea?)e9LOxY+iaUtJ!ou0CIKwO$Yc4?DIT zQXJOQ3y0Do%7HTrtFY1GLrQ_KI*f=_zv%F&K=%Pb)wB(SKqJ6Oejmtw0-WC;`nf+q zfs_;zGgf32aA37Tnb5~-+iEB&nQ6KC)? zaS(AVe+^AYoEAa#*YNweHgAcVA_2`v+gWG-!+Gts-!QQ8%H}&6U^^Lr(~DdhnTyCn z=V*HR={x;<_MFduCSQ@FnwTX6hS5l7JQMSBqI9F|=D5e~C3fPPfVAdy(eM@no|glU znl0XU-^ufSv+z8rzW!?2;~p%lCfxsL?@XYZy3RcQX_qW-vW<-w;*H(lu$RqAI*nsU z07Fij_B3S~+Vq?xoxtSGbY{pjv?Sq7rb#F1mdRl{4NQkJNlyqhi35o#BrHv0$HW*Y zHW=`3jIpsKTf63d&wA2*SNA>1Bt4Oh?m0TbiXuPB_ox52-R}#)N5$6#b8)1Vw@yqH zlY2de)(Lp}5$HtaLlxxuasH9lMvzclQPB`G7sBQu_wK+w{_KXJ3i|_J06}#dxt>5S ztzDIxdf2e?{@p~)dMIC8&x4JCHtRv~f49`A&4~2($DTAwO@C0)@DQ@-uVJ|s!p2sR zP(EAZSb*(I1CHa*sW|BZJqJ1=aXPqXPX(JYz=c5+45kPz6sQ$V>HvK@1W!rrIN;p? zY2#2jK3TVK)?jh4B#c8IYAXto1iJF;@9m^ zL6e~GNPbrW*LK%Q8#C9O~RA^aDjqM$C&&<*aDz! zjs`TLB?3`3xr3u)- zkTknQ62Zi!Bc|n~sik06C<#NgD^D@P@+3pVYZpz;!&>d$(|+g#=|M)(XP~2E!XJKU z=d%mFms=M41_u{|%e6@G(a0Bp(4HNY&1fL%W`vs#_lVi|IhRSVBOjm_ybgrTLEv+b zn3TFrTtIq@TP*2F*S*RK0SzhtRB@VRRk&{z}FKzy_Z7OCN@(SU{v8$TUa7_xzo zVXH(h1r{E$VH^JhlG?qAm6b1=?z-zNgqLi^ENovoa0=-HH3vE)ldDT4z|FMo-Mh_C z)n1Be1M`&PuwY8GYKOiZs$G#m8`XPgaC&+^QK_rmQmaoeD#wniWo!0SuK(LN!{6RE z6CA^Ns^Q^zYOOZOWgjZA4%)t$KQK|Qv2yt?)waWg%=3oteg7kB6Y|V@ zJp-Hm4zDlgKCid(D?0Texx_WQaPkpGAW_}qa%*Tl(yE4PhKEOa!I3}($u@0vb`PU$ zF%Mfz02cu|HaTvjluiI|`1tV>+goov>a$w&cz&IE%H%K;6MehP{3(!;Tc~(lSfPT7 z7sPt$hEKfYK zj|%vR*TMovf8L_P87iHwwTm?BWmEN<45je?Xv{}k*a;b%v(l=DCM32+VEr}0Wys6> zDE&u2dXZ5zwisC!wm5LnnV0N0u4IA=!sqGg${cv*mA@mKbxyPT zxOzN3M}x=L_@P?!Hi7Rlgj!9|IUp6K=E8d$_|>o0IU5>Q(69B=A3O?)2?sn#|08kTyE?QJ*>OKkE>(Qnh;iRJ}SURilpjjU#AiJ}T8g;>08z z;^JmHZDV&WZ1}_aYr>c3$Pt@2L3*r;*DX0_VT&o?B0#6%M9J9HL1O>QFF))$cdnA_ z%M|28LSX^-_oA7Jc{`;P!mWabYryOCHoJXom;BK1C%yJar(f7KKI(l6Unt(bzSO>_ zpZ*uJ;f%r0Wa$#v{@qQZ`h8v3`=Iilwy_(whHTh_Agv=5uR%56yhkSFF9t`<53G8@eEl8!-W+pXlh0S9wyF}O2m{_x5wvd_4v98c&*K9 zRnrF=4Gulm-rTjT+)-OwMnCt!^E)9sBYo7r<0rp)4c7401cKnAXEe#V} zp4BeTvV8CtWvNt?3iRsa(Wn^fqt)765kW)Kk=Ni?N!2tK%hl8^Tb{wTiohHehVX=T z%)=Iwz{x;IE1iZDIb%)C3B<(w0nfw~)y_02FU+?aEqKL7tpH!799}f5@utoB*tVzi z4u?afq;7VX6AqlIhclfco7ZeGz>Ub>IvL2Oen5Y`tQHzQ~ z_9d=c_p?ANJ6h3L*l3@auKJHz*kT$u1?bcq=-4P`5IkpNV}bQ|zk8JJl_D-(YAPfU z@Z`))K}Ay1AS16(v0^0%De>=S5%^H~qJ_^oGz67D+y=JcP!NGtx0j9{hMK04K__MN z7Qo%pO29k*vb@M z&JJ%TCAAZR3KO%0pb9FV+`{BJsvh1VsUEF-XnNwkTp&V8-Oxw+?WtpMx@n~C+v5*D z1S>LfB+vbr?7{y%`aZNYHGx*Aho3+AFic8GkyW`PzkDg8xrpKXNJ=J}jpD!zx6FJ$ zG#iHrXmaMGkjP%Q?j;=bLYa>Au_z~m7a+5zwPPN(F#+5t{5saelvJ-Hc*M8MAimw> za=u-(;E9nf zy(?g2(e&^QP4?KEWMA&zyBF*>D4QRxN_UNWeK4jVY_q!>xQ3P$60gZ*tN12f@7GQ(G zCLI$CKgdb4`?l#Px-3rp9 zPhrL2z;8nyvysSgp>VfgV$u~V2FBDB6mM|Po^nTB-8!YJ4g?R6{wVo)tiOlwC8ne+ zo<0}X@qgP69%sm>DI)&azQ-SdSFhQi$vOm0HG74}H?FFLujj~D#-PHX(updUk~$@8aIegh`XS)d$^v<-soW}g6=l9JIrnxn0QTO~q;`rAxpWxLG} zJn$){WOAUnm@m&UXF`ryg=4?TD76!lxAX_^2k9_$4@l?L3C?%&h7AW3%F9n$Uw?g# zyQ%3uo_{AQ9tKwo*;(BBOi9?DY)o-;VhNdf=ku5Yhe1-pCgg8JC5 zwFV!bm6ap=JDbQ`a|hmj@wBPXP*(@m9`gUr20J~mDxnPL!&1f_Znky=P!3?h#fpf{2kG#Ohm@*;9JaAE(m~>7i z%(SGSa}qPtY1y)65Ap8~THknMwfn+_avpeS9^uX2d`VC}L-xdos)rkvHa$fJG!gI+ zzLq10B+s9C=X^M;Gedu1gsuP$Mn2QiAzh|cxR<=edI*j5vZRlsIeZ?MU|OO-hsDDs z3#7m~ZuY~~+7qC+SYVid7L9^Qf+g3lgR`qAY|vp?K*VUY8s^UZee$MFhbR@J{WG=( z(#uhMK;00GibIN}4l5j({)260GrF?>^ z+9eG_?&1V z6N4cHv})p`)r0)0c-BYb&D0(@sy42GQ!p`A)ICH2p12_l0h7l_e7q`hf7o7KJ;za3 zcel5zYaU%#OfL)+;0=`zOUBUW(@#hj?%55E&toHad5g=Uey<1_54K1vwV()$DHg$; z6xYz@E0Wd}g6Z5p0G@%HicvOtOVm6-Qn2JgVae0wP-*^UXTmr{2F@a-dvVNV00Z&XPI3ZNMBHW0%ITTR6L(ay= z+~E@^7P*_7%aq`w0R(d6)ez<_Uat6_Y*rgwBKtItT&jn>1+&8VJ)6y{_*xm$OF;W> zRWLe=y-gggZP9O-%*+eA;^Jco6%~IbpGUt8T(rsPxfRgHBW&XmI0fHMR6F)(8az+n zGr3$JDPe;cLr79L~a6xYnAmA;GFk++;^J-?N8?*)k zxi}RpBA1X!F8Lz}FJf-LawQZIO$$$9Xt6wP@>J+O+Yq(6sL9BXgWrFQH&JS_`h0KOLSXX5jfx|*9yyq%rX<%*_*kwljh zF4VjO(<|15RjKGqq2SBVjT->jp`UXh{P>#7<$`X?q%FD%O+rV4iw=J9qK$FTZa3hf1dsOd#Bwp()YL$Du{7OtMa#e#?CEH(sKB5? zeIN*9vQ`H5rqWJ1tK>`7=#sW*RWwA&K6QGy4I+R?2Z)EofiAfy|pVt&AxxO(+*qIeUX4Gk;BYw~&AV6ATu8eC%S zqS&TAtzafxzj#`B&MWU%!_;qXfNYbri#VQzt37ox1QCH0fukF%y07(u?Q#oiJ~~o1 zZ~i%f4a26q)&-G#F}Z4xIB0Sv`&cVp{%b6zAQ}L;M}gG9FP3Iu)W|@`9*|AE@!WHd zx?5WpOTGb<5+QlcouD-uh12Gi%v`v9@{hqJIIiz3yBjjIbKs1_20dM!aQ@7hpeczf zYx*KEq-Md@b9F*_8Ly!#fo(YH;fJ3$EnSMIT5S+sM9i9tw2HAMbZnA34q6% zpvncH`>_&qLJ-B00SN{T*n2yIh0S6#6HLj_-+L|im;!A7O~J;ho7~XCIAn3)Vh%o# zQ9pLls}4AG60mzTf$k?PbQvIJa6q4p|(y zv1E4AJvG^Wnr6nlIBap?#tnGUKu3>PHupXA%vMi(`!e=5wPrKu zCS-uKqZ52?cQ|ZIm;P7s#*NifV8qVACKvvX!xjf_+(V}Y{)_}c@djUeZN>1ZQ{Rv3 z4~i8e-}=@wrn0hg5E_C(n~=O&YGa}=e;l~@`l6v@OTdDoxNKEb^M~Gj_lI(=+J(T1 z+1#4EY18wD($cFS?afb{kZcu69JV-cw;gnH$#D@p;oj5Hkv{P2UvKqZzrK)tU0r_u zJ89dt{U?Ol(D6Wv!xjfF4jqJ#SEl_s5kB$XdfS_C&U2hTUG5(q&QhnOw3t_}+;5ye rza6B_ka(Sp=|bcRcA3vL9PbODc=!`_HVT3k0RSfd=2r4@Rudp(({H7I!P1YQ-m#(;y_SUplo|9Z zp&;n!K|w&}Q1f|!q;9>lYAZ``_19LXNdJDkE3f=r!HyN`n}bY zy;bEYM)zOz00+Y|$>-?;;M3*;c;*7{B-cz*8>YF=Y-J?PHP{nL~NF`!23dXkzXsLKq0n1^^;d z4ovwvfRa^&0k*fPI ztEo(F$aCHOGY&HQNj_7;3V_dO5C0kkgd>XpC0+YTg8P995H)jt3ow;i0BY+7aRfll z(T0^?ktH%lO#Xh<%mq(UKy+Xbh*D>%!{wPD@YQy=`na?K!y#?pNOBngX@f?>+Q)8# z(o^|$Y%rLx!9AS1{2;P|EC}iFHbOjHSiUnslI%Ru{^bz9C<2pT$pSY$@=vfk-GXn( z*}Fx!CbG@)=P>w^*@3=8#(h0w(emZwnYU07bd2QBw%HwvL&3l#EB8<7kq*PA&Vl~K zuN|Oe1H^s~k0B{z~$^e4SE4*t5@FS~uo zi<>M}pble6Qo96!)#S%7dCCprco{>rbwahs;c( z;aMJRi9TQxt?3`s|Gd`@T$Ky>5Cc!YS^=fyb>CCwFaoeAtN}~_1y-XF6!sCC_rZxc z1jF{XUod0`2^fCVo@7$H0D%?FkH&Ax4fn&?*|(bOd`x`bDqu+BM=Cc~;xEfs$T?VN zTzhu4!F{*Xy&MFAJ+(#vg;wHoH4fn#xVq=~Vvi3#*y1&30V}~~2qjwHm8}a1WCyJo zX=Q~h>*il>Af91pg<4d zUaC2xU0R$6>z{uRzT5sj=zY%PuDb2?)j$2?{I=>B+9U`F08sb;&Petcj{ICcFf}W} zqdp%yn^&G?g@nh%uO!!1=julBlF|@-wMU+~S7o_j(r8y~psL{Dg*8<&7UH)%KWAOCVp#iTa!p_3IyWlzlvN&=o#K? zmi|dU`LVJ?MtvdqV~C)SfwMVwA?fkrtu*f4*?qtVr_a{S6S!TBzy^42HQxES7WN#f zn-{R#8JIrF4Gz1t=Sb+n5kLe5P;|`<#$K61Frar(RG6@|`aWR>Jp(;q?j4omu3U6u z`jD|7P!KfZ=#Mj}bMAjmXl2k%{@N9W)g8UW7cR%`mL3xb@=#I9K{>g6e>7ltUjzgi zXT8WRlqYVd1QxoJ`*%uY2_OJcQ*74Nj*AzV0>RL8NFX*aII;*ctWbgBlUE;w?QcJ? zIU9|vaK)0x|21Ud6*o~3G*0qkDQBJ5=x?g~>yiA$ha8p?Ka%k0OF5{h;)I`Tz0Lbs zA}|mXiR6-+9RAq~AiDMsg8^A0CNRXUlvS0`FTC~mZMqWa^2IBPW-MBMWovKaZq*U~UllscIBtuc@-Iun+# z0t$B`2Yp8c1VA7-EX$<06*A9&Gtg+BwO9cwNLSzY%+l_C2K|MCAa(#?<>&sGMt`jK zpysbxVY#AnVz1M1)W?L!%PDdR2HREbme$OE@;%MbpYg8;rh2KR8b2m~?oJn!p0w$X_$Q0& zm-t@Z0bb4t3`scFSfAS%4s5`IpSMG@ts3kQf_T;o3FKmD zLiGYfzrGIP=SBeon*m(HDw$u{zV>-||An7vR_$iq_FJ57p3vH=N39A3V)X|BpqjnG z9-nLO|C9FUUzKgC;$xKjsPXd(f>2l-w5;th;j1_gl#>L_{>p-w{5p9>}u5I9>Eiy{!Z*ef8_ zRw4Xs3M4xN{rcDiKEg+gd_baES3W-T2z0YH(!^3*GYg z+xN8w!`$K^&;tNge^CFcCcc9Efvucts{OIdiKD-AVhc-|hmQJ9lOGelgpBs5f+^6} z0|C*)UIyLmrKSvlU>H2ep~(z50XJk;t$CXUQMFwBs%SYCC)m~!)2yr>;UIVOV{hHl z(m-fY5D)-hjQ?)DJ+Z_;?ndBgQoh-y#mUkPFn<@fQI?ydoFENKxlRl z=t&?=0HFS_$TF<{VC853e?78JP8|I)`K3*4H2E6_hKNK$W>5yUVGNn&HI_#>r;xZ# z@CimhxbKx6&F&GJ6$E-FSPKA*Z*^Gz8vsB{{^01p`+#3l`b0E;VIWobnT9%W5--Df zM%Odo9K)CqYF5A&jNwnl8%yP4VAZ7qy{of$4(e2Pix9qkIiqPraT(h&!H-##=;0Vv;1847+(dcj@TqhD)@DE;t_b^&vh=m&&!5S9fvf;P zDuECHU+va5z$kxc-M@L`LgEv&N~v1AVpsw~;yR%VRcohjtQbA`#=ZefvGX0<1OUi@ z@RCS5l9#_OZ-&Fot{MRVNX@%z2T%aCKp^Yl;G+zbMF$S~>m`jaFR);OvRU@{oP)M^ z{&2@PZN(F;Q&)mslea@hW`Ygk_)eE80I>Xt8a1+di$?PY4nDB}0q1x0sh}*J7hJy3 zK#A%>8ef0#T9;4PJo#3u27$OOip&V(iZI!N7``0X>TJWAVEmvWYXI~B5H`RAC;(as z5b(QD8fGmZ-~=bOxf=ojd&NkeXc^_r|M=s)A9j5zh0BBVyXtZ4DeJ6TA!0%Z%NPgga5w0ai6(M8)s&d%+=CdD@j7V;EAkgvwh-4W6 zY9BG&0|-l@2mV{v3xppY1wi`%LawBpCA{XtW4OJIusO2#c|k>>nvfMic5aCKY|WD| zw<-{5$spVoMY=15upZcCD-;AkWF?qZ8I%H`?E(SkbfohL*!62_l(BmtuoY(B{;;rZ zOH;`rwh;i3p_D;5A0V3$6W0VA1@8Y7nG3*CLpkvhF~Jl7Z3773`TD8{Ib`<_p+VF1Nbg_N==XYbZ7o7Oz_WQ&48oD`d8D1)#m&SWo8 z)Q1>CH|PO%D+Dcrw%w{xj=!gTKog4{N`GJ|i|`By-)c=+^yPam|DvHLnQZ_7@E@;K zR)b_RNZb?DD30%NMOTGkJG8PJ6h{*ZfVLaBfC<}Kq=ChbLlU;f?;q!d%>02Tu3S}Z zEIV_8efP;b-)a#Mkied287!|^Dql5ewlNmcVD8spO^6?gvJ!+J9-Rqp`+@E3%7F|Q zKS>K8I4^`tWp#t(5$03)VD+hTrLg(k%Xe>lYk0GQAaYQi^fsL4mYxP_MFt(+#Oo~Xq2V?z_eO~$L9Lit2`%~Kn9K~DERct4;swOg7>)*7K7g(TZhwJmJWdjiAPw9JP+AVhrL%^C zvWN_@Xp)sJ6(^7EUDaGnARFR^gYxD{P>H9*7;Tb82L0ooyTDt+3NlDJA%;GHE{JKr zkr(JD%=t*;@Xw$-~QkSh1UxNa7MXqVg8A|#wP{|c~-Mj7Pd@)R39X2?7;>hZz zVuEaCMM%j4y4$6fgw1eqzC&OE+#OnKhH>=Ajj?ESELnuiFAeM~ikM`~lY$+D4SYsV zvJc`M>s4& zAM&ZpaG^pU(&#v$>@BLP4WA<+ek6 z%jN}?EKo}>Mffg2f@(+f9AIhS?$Fc>cih0v0A+7cUSS|wGR3?R0teGmD1kJd#Jf_p zmsLs8@uYt|a?9*S0fA_U=O_(v6afD9bu8c$NB^@T6GUDkL3HUkn{X_uk_BjQ5Dnba zOBz@*jm#fm-eCR+;TM5Wsjd{IWt6EF=2x66{#9c!LALZ4Y-N=#oUd6KMkyx$Y5C|M ziK2XmrWeRpym=ZOGtdtz*&rvs&KndQbI4_OshDq)bixT*B^wY)G^t5IAOk=kADG-N zVZCOsuM$#PMCVZ!pkaj#s;lb;&9lWC{X||lDGUH&O#-5p{Eq>5tIdJn6W|8>0ne&V;-qZvB(Oi!csiW@t(>cd#lSA zHk1kGN`FDkCEY}eDOqLu=8E&21koYYya1gTqoE9?GB{VlMb{)p4YF4F!&00%L)buZ z=zH*U_0)dtklKFwMb>?%X7G(R+GNCN>-5=xY_Lvb)*SrQS6o|#@OyQ!-5 zOq{q)Xv3IbXpB+_S)lgB8dc5A-3CC0RrZy(ia4Mz8}TP1Tq$oqKc{7V(E=k zmD*KHCrg5f^c$vODU_0k45-2~#L5zjlIVg2Ifzc>UCr1r|Z))ENB`2lS|;Lj(QJaaZG^}xx~aw$~K#nW$= zMoghxa8q8van-dvv88Tdpf!xzeULeFf_!l1xN%mQKt>tT_Eak@z*V}SrrZuCigg3Z zH$;PmSttk2bLT_2QUwqQfm6gYaK>wp2Pk|&FyPaaLv{%RtblNvgshOCHc;}oN^=k( z3lu1Ng(wJU)Rzg;B`rf|N`$`1;eZ-D88P z(|K~?RA7j(Ko|rZzS8J_uL{xg1!&YSsyVj|1eH~xn(}H}oO1AG2oTsrpP2lNaBU^9 zY^rIo(=rD^QO*%H7zo%tA^%|8<}es8pIv8uxvILRxYOrCvR6PG=4jM6l(}=hjKh`e zsv7B$JudicPY`l)^q2PJB|w1R_oKCXqozf-LXfRxSv^KW&-?>|1J znmZto5n+NPc}0laF|-2a-?9>B&Blah;hV42^ip~r;H`hKu;IT9Ts18KLk2jY=uC(L zfJT#npt73pj3rV`{!hR3!Jb2Y-N$0`qgoj|fd{_lFMzit3EdCJ_iob!fjYV46F&KjgI&ASBYkN(4{P49fquR1(6^_2R+!Eb(`ZO&RNK(UJ#pO978 zE`dPkT`u%)q!5FC#|`mtoS^d49X?$!(DEpah6WFeZt_nW?S#oB`SEAQ83KEcN}*h; z%;89Q6x+CCQAouE?nK4%$7J#Zik=}`k{7sYr_jXIzmFa6AeTeO0`T80UMQ=Ozknc6 zPe7w#!D+O(jUM8JtEan#Ja7+u^XT?2{c2I+v1)V3L(a`X64)a8g(_jHqFpjK706`QC8V^qN(Qk-GE1&^h zn+eRZ12!Ajt$_fSW@_BiMnOQM6@+dcV$Rx(J%KwO5-fp$XOcC|v4B7d1R5<(1W7vQ z0yxFoHHhj8D6<3tCX}ZQ0?`}LpwW_{nS)}!GI}Sf#^ect`@_dE;ranP{lICok%(05 z{p%VI0z3i4@ZKhiouxe=G-%W(+-$)34q7=V<|>0A;L96kjF||M@K_wb`k#j?UG!U` z(W0T5gLr}sr^w-Y8RLi)!q5Oy5H~^M82T;HXju@HdW3~t^5lfNL%>ZW5o<{bm4oa^ zR)GWv`ZxmS{96Sl~?MQ1DEGdO#NQ)<_buW^zb)uwR=6u#;38yT9|pQ?FF2 z?w4Tm(r=MQgTlecJCrx=@Cj}FrIKb%VTrF#QMtiCml#VTmm3o7-Fid98Oa_+-pVq$ z6gsc{!Xos8$&ann%I;AbG@3GSStVYun{>t{E=cLbhPP%^Hqq6J7bgLMYax^t=gI!; ziRVM?f~E50Ejo6fP70N3m|-z+9l_t;u7-Cv)EWXHiKW#c8Vw6K6EFnB3u~&#K|tQ)WvqH%EF7cHlAP|ePRF+bY*F2cQs``+D)~SD9}CBi>A#rtUN#sy$O#{A zuGL$!r4le;caLzw(kR(uDoY{H z4yk;Zaw?c9TNN%bVe;pSJ9-_~FNNX`69fA@h{*{1Y`dM2ZODsngtk=EXbcc|JdBWQ z8GZ45?4ULn$QZowBL=o_wZYVxJWQQRz~CZzU5p?YEW7Uydzqa=_A?Ar@H|9MMJPS; zF60~|h}ytFQJ(eWkD@h^f%B!jMAP}93MdF@)CUB$DkcyrMs@`lh(`@5c0%{o0DQ8Z zL;rO1w&!QR2yTgFt8Q~ea!Nc5OFIfKTb1P(Kk&kIaK*(_ zAkb)35DXY|DL46!w_#8p8zdzd_pBi^bWJtgAlKIttGF^ z$w2=!8%!MLfC~m^k{5g#1p$o)g>s^G;MmLU5U7Ceomm*3X@{|c?a-?W3wE7cKVPbv z#QmCFR*QL#*V%ZZT|x`1$Q54DC}ZzLbgM2rI#W-J;S1`X!pe}zpZx+dM?DPRZGS&{ zTdJ)B@}L|2W@%UooFr2YoSCDufG^)rXL51i?PU`#r_Xb6nk0RBRhZ(se!kciFK}rw#PQYCV8_=L(CA!!v6n-EpoNZ+X4IdX_ zhm7v5>I79Z!Sx0~X@w1@EIR=gW-T@~BvDCJ{w)lfI!AU}s=bnayEH6Cs!g6N7hSqQ zknVg)H&-QI9}K| zAdo$1|Hd`KwPhd#%84X8sFFmz93}KyrlA|i0Nw3n+ER$93oC)g1o-?iQ(Ug+2h2bq zn?O5rB3D?I!=zmqgCzPwC5f;~gbj5xXy{2HX+)$@B%iFrk^G=#7BB<>K~5gI%2!Gv z=rKDcUx1QGdHPL;^9=pAX=p~eA~(iemCeQi0F3XB1q=uDSeQWd;GO?@5!i%2z{K^n z27;mw0cZH8%)4LZ{HZ~vp$wcHQ!b^PI{#;EG?y$y@!1emRr8Gsf>m0QDA+v~HuwUD z5;}8B&Wb_+^ib$N8Z?xCF@5kTZ%WS$hy?_=>4PHH6ID}IZxDz*MKVcr;Ip*=eHT~< zLDB!flaNdtzS1C{qu)A>XrND2`hf}8-Wy(tRdd4f{J_z?U`#Avg$Yz_u=UMn!PR9L zgxuC75ho&e*6j}~-gJPlK`H(AX=wUHY?Fu-RXB?*@cACU)*6Dw2SH`&dC1wj6>7T8 zv-a+=RGR-5Agx_|#jS=Wh(^a{0cBbAio2eQZg0pRec96Ud~;A$bzE&Gg)D(UtP;tl zoWETAM~EAA69g@KTaHK~$_6y*!vZ+hWJL-U6o=r~V1duRQW@fWqY1K@K_IR*knKpidG+tX*6&8^Y>@w^ zCL16Cded3t4jbtXeHOs-x)ncwYYB1+REBt^)z`qr@OouW`o+Irg6<=3gwx($*4Q8~ z3<6DWKt2dMi`?-7rLc10jEn_@d8TsH=h?Q)Cu^H#7-8?kRZRf`*^^>t>5g}w115ce zB{o1^hXxtrnx%h|%^P4`;5tdOq5}mg0F=VA!Eo>>ue8mEJM@(4^OTfxa9A-z%=7S` z6+;{|2t@O2=NWe$ z3w&hB5O4m*54B!ZS#(7#uZ4~AwacEos-g?a4U}!x$OhCT zcMLPEoE;uB^SV&xsA+Wr=CPMdpC|uRNZ9C25v#=kyJe*SWFu`UjjW({**KM!9W&}4y?vA!m@9`8fN24Aj! z4Lk#Hgp+5@59$!!py&+{`BQGVPxg0|M0D<;{l)-iv8qY##ufi`phVUhDD~oa5eMI^ zyG6H(tv*c)0xdT9bk!fA=eT>J;B-hps8w%(F;hbpp<$xcqV^X!z$xbh29KW}kS@H! z0n&^G&(gR^?E1Veww~`t#s&d@5VpPb909`J0z$2L1Mb*gqb6BP77cY5P=C;l z0vA&$r7kX*S@7sP4!r?VE3gvzU2YI^)eGrk_5uw8ffgHVe{U5e4Za!5eTl;4g(Wr! zR77Rb%-bGTeP~FXG}@03P!4LhJ@bKGE`y#ntq2`C5rS_I2UOds#99$*3?{%>3W{0e ze=MI1eI_CsSdc}5a^Utai~fq$BIOD+q)zG&+I}DdD0i2h{Np9Ov>JqMHJnL{1j?Y? z!XOkDt61QX`m#WSKp;*=$eeglZXT3+yMaBiuYg!nHpqWdrvH}w`7_N)G`f#z+cv_= z`JeP5BmHw8d{MT6RR{+}gV|vmR57Ox9FluQadvoRLtbM;m;l6m9c7Dd-g){S==Y;% zggqg#j%x~pUH3+15mICJlkchSGt^Z;&pvB|fh?|^4?rJq`*WWlRaw8dptvG@VA`GB$)IaHE4j)MRp56bQkFf4vUL{iXXa;lpscs<$AEaPGj&D5E68TCp^+Vx$KI>JeI(2hak*=*#AWl$9W1 zvCNmZ7(U;DoFJ4{a!iB+{iB5F%A0x(=b`0vE|^b^huB088RVy+N2CI8b6+#ObqgP_Zn0 zNOEjtW50*SW`f9uT@OfatEu+EM{D1OgdW3XNH_EcTd%PU1ne`S>y(=}@N>V=Rj67c zYzP2igd=OswL&;|j9f}eLvUEJPJjTQnB(B4rg$&HiS|tqKbvGnk09H2ZqV?daABYG z5cHM{M?K-uPw^i8lW&;hi@-MZeE^62?Ao=D-3GfpdPDso29x2Kzb6EF8_m)t4D9g5 zfk#Vg5VFYCX4$A=XJEo;SM+S; zh5(`H=ze(Vp6TGNR-5B}%?bf@m_ug0cxMi&nF#-Ss89><` zifs|kd>)+K)bEdN>VF`%RVR!N zM-8n0jPV@P1i+*@HwS;Q_ISMjfXQzJ0Gkbtkn#Dd=6H`y&IA%hSV69SXT%7CdG2Eq z4rbPU!Et?oy<`nANyAis`{dO}VdHC$YCf(k6fdxXmv_?4wh-99j-$VFJ+L5uoKF9Z z55MLZHhsP(bz1>IWWdv5fGU!f7d2K}M5_UT2!vJSYM~hr@I-PvkXbhzNno4yz9tZG z$LzJg&xX>|Co~_LALs^!fmAAy3@SgFu2Z_~nLhX;-%Y>&k1KL`hBCPQ4+#yCS`qFe z3r0JOwE)1fP|I;}NmCl(76m~pKuAqu1y-;dEdqjAfPhAM zNQMpEPJP47rY`{Y;ukglhODsux%>IzoP(NA;N=~V@9);3t4NUzk$c%obej?{95c~( z?QhoTj{ZV39L@w2-=-DVA%GGn(n;K!0MKF}FapAeAx`MtwQl%FCO}3&W-zQAO=nAB5UvU{S`&ax zlh`vRkcBu8pwSM|VgP7S5aDhVY*9$|;} zYM>)qAR1N7(D-J}yW2i%$us&R4|akcddjl?*P91uX#li12#kP`(Z>!$2FVi<<{(y=Qz5oQD?3740{~hD2t*)YXW;$H+ff!FD>&?oOdj>^Zi8N3 z*?O}=EMUMuAe00X+Y}&#(;)4YLgJgXk&ux;Fzvd<%+!UC+KrN582!n}ueU)KCVc^s zKe!w+5-5H4YqUZz3{;cUo7~a>Xf+@ZfsjqEURU1UDS;J$AT`VwNePVL6^u-?zzQRt z(De!tFra5Ru=$;k-&<>%;m8mr1Ppjx#kpIZ)Iph(M6Rv$PsJxDR$nsrcF&ZXm$Qb+ z58{F+9Q}3H0if)`>W@#do*b7&Dp!RGjAMf%p%9F3=q?fM4g?|)a4iDLqa>wa9x}+G z@(IWaX~Zj}_b~Pf?7$_!4p^cqks_YqyKU=X>p%YxJe7CMZsqkNE0l5ZP))!P0Y!5$ z1&;KX=m|D2B(c>o?aef3ElXK+4SnyzpU?t!(DxcO?IaZoz7Su z?WKkn56dQrZ?xL>?iP&e8h2o>!29 z0XtKTWRgJJ=?j2KyV9_m<|rW2yCtA7>I4s7o?j@N?ITpT+!MU%K~FIKFqA zj7=U`y&KGG`mzxp&xo^-_*mh=5g(J^r?Gb|N*}EIYZ+h3=>MCh*b;?;Kpza6a|)5@ zOp+YUvk@3jDn-Cx#tvcUnIr&#={h-lZE<1GvUiWwOTOSl-V@giklltS`rv^kx}!ZN z{W(1S@pIF&zapc4Nd>RFsxPAZaUUS9vZu(x8_C~pXzkj+-GM;j6;K}CuXi6SV8B2S zARtnc7*p>c`v3(nTqJa|*77OvhdXbSu>kIhp*)i<`dI&Z0N*NHJ4%L90uuX3;9c3| z4|TX!ZZCs}m@NBu4tQLw(7Pk-%_Lsy_3~(GePNFuB5CwCO16^x#Q2^j#&^EBgtpB_ zn+AbZi4*|?8ffLLvmPLl;~DD=A;vo&*TNpjNhmklnPlms)zTmO78(aWpCBR-D&MmvO~=Fcq%Kw)u6H~GcJw`FZn`m|dR zNWhRKGJ`G{&@*K8#TE>kW;IDmG9@LjkVHU{OuU31C`v0hc>0w}g>?BP%vy9OFjyiF zY|&4AEm;${0(jx=5H@&fwVVxH4E$9 z=i$ydfv-~hUpo(MLJ*jQknnSKbgl+?>~!{rO= z%x0+2O*A`^VnGJ@Agcy|^h*I*NTac?ah&k zQQ*kWFz|XP1W&cc`k8j|>-Q!e1hC6-`&_`rT) zwVVwW5jK!?NE@||)Hm_NY{!%YLp(0rUfG^xPlpD9!ayS!P%b5zCIj&dJqRGsRHI}< zfLLLHc}oB;9%qB#_wmBWU+*u6FoG?k7T7L+K^}eZiVZfuFMoa0C%HAf4QhOx(88qG z>gmyWzq^Uvf3dYTx=|1`jc=H1g&m|Uf$Y$k0HPBy$p{Q33nX`91=HFT;q~$G6fxg; zEoFn=X?CHB4HF*|UT-$nVATJIIO^wh#9ZjGAdr9|S!4#hjA-cdU3a+$#tdz`Mf#ua z@WQ4Yz6L!9mN*G_tRqJE?Y7e2LqX8Iaw#iThR|XGtk7WhrS$mPYA@F8d7-i-4XiFj zCg`x*WYqPl#RgWwW`ncD;QovJxUQ{FZcz|4e|0HKyqL1YWfR?yMhvytaa2~rC4eI; zTOeLs(!6=67k)S%G#%|(mie4${13&%&!gWc1wmuTrja5bMluXcAs=05>3wjU81xKE z10CmKHBGgZs!n9*?nnfK;T;^5lUl?aA0hYui2R6)SN65$MJyBq?FSSqKxfrk3&kUi zdA4sy3YLBZ^^Vq7Kc{ksf}q)B6}fC}C9eg~hFeF!5-NvUN*19{$PoPkh@L?i__w6` zJb&MwHm}XbxXITPtgPlvmQ`?tAr5wlOq56Uf5YSd3ormwXg0FZ)D~<20000R+P( literal 0 HcmV?d00001 diff --git a/public/images/achievements/border_silver.png b/public/images/achievements/border_silver.png new file mode 100644 index 0000000000000000000000000000000000000000..016d48b0004709314691b14d80c0e1cb6694d5ff GIT binary patch literal 12422 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 Rgi8Pb002ovPDHLkV1n}=?4tkx literal 0 HcmV?d00001 diff --git a/public/images/achievements/border_wood.png b/public/images/achievements/border_wood.png new file mode 100644 index 0000000000000000000000000000000000000000..368878c20cd0b6cdeda1aad3d4d0184a17741b09 GIT binary patch literal 14992 zcmV;BI&Z~^P)b+atYVBJCk`N%VNMHj-#ttTcZE)}sJ2rB1CK=nA*ohs- zlXx7GGbgJvAsLfoGMNF66C1D-;{`H~aS#D)j4(n5ge1_utM~5e+TJ$zdrQ@O_q|t3 zt9sS_b?Q`iRd=hZ-uM0g_b>Ne$+9dG);PO%ip7W0PDg0!Ts*<5paQeKkR zrKV=p>Zs>$ElOL3biz8K8I)h!X`adJ^eX$?Z@z7%_WZIxD;^P__W7G62ry-!Z)YIC zxka_E%<9tRBYA0A!H^O&_5ZKwFi4dtb8(k3l#uD=6>Y{#Z(pT-hY{mtadiX%8t&EE z-Im48=7%&>+QPuzH&`@#;G_}M~4fV z0IsLsky5NrGX36Lp9VMSEz1nLyvL-z7KGD5hZ*OHipyzEk zNA%}Eu_*64`Oj=kGVRawcTS_xqG1C)r%O`?`1zVgkd%UZi;_y2FpVMuN~=G&y7>JY zR%riup%G!h5ug^}qf)bNiB7lo7fY3zN_LV(fG_{V3do?N8H3&QIr$lQq)4llEvc%> zsu!3{+IcK#li@-m!h#_Hqz=1#klj{S(zhR2Utl2C?b2i=gNzv|5KAc8@3l(w%2|tk zc*yykhwex-X=Bo!VQtQR4ue@HzgRe0-Yx|Q0TQ&jgiz)9BpOlS@k7aq`2vXWu}%30 z`a6x^n(sNnyeEKLzrT;&ZmM*_0Q^lodR5R*vLAVU5M@T9&Ss0&b(Mke!-E#>8?@+? zS0(ADl?wgnT!D0R?tLaF$Jy%~K#pQjds3pF7P1M^)9gHt=+MR-K&u*0hMkNEkM2)Z zlt;)BK7486FR%EY`7a0p%slW>&b*i1K7GEx2k74%m1ysP10H~W{bEN~^Y+LqEIDT0 z@sDmw(OH%ljKv852^KxZ3znS(4^H|XE7tyb1H5f+54xS*EA=NU?&Ep}&i0ZF~JhKrZzQmlU77Zkcw+e3C`;iU4jc z|G_x9gNv}^vdX|mney2aL@zTmH9GrG;avN!2cOn z@^nOn;g4UIr|qlalhT14E*{Z~ryMB&Ta=dQhSkjTbyW8F9#|lcipJDlKY9Y1Sznhc zAscoV`g{qo4qm-mij;JaWspaR4t(V5Ws&9m8zj28i<68D{Er_@(l_>}Vv7wQ+f2Sj;nJ(#_num-hi)w@1Zh*O9$77AK zl%l%{tV0Tr!+WeA*|ecovU8j$x#W=n(G6dFws{GQ2J48Hq=R_^T%+biv;dC3ik`Wj?l!j}qn&4imj}+A4KpVK=N)XhWYB=?xuESR3d8 zbbz~HC{$!$8yajC})+T$@@3Sw7$!t zHg4Gu`Uv+kB1Fj|T%TXuSoqo<4`n|h2v8sR?~AMOyq_hw@cNNn*mR`MQ-_1VzkIPw z*Q|hC3VXfR2b}WeL?Qn5=nUX_Jz)dBV5+v4pNod*kdk$q5KXE^s$^D`NgEjn;N|(K zfD-AsF`2&ohQYvX*%9P}m&^7@#^NR`&|rfsy8oqgWHSvCm66~$iwJLR){|2z==LTl!^M(HZ<%B(g7@ELVV{ zJbhM6+Q6?D^p>Qso63NmVmJ3|_p$T4NIJV2&b`U*%|hw1ddUDHG$)D`BLm8!+m=fu zA^_l!E*C~y9Ll(+c&wp`?t=4g@VIhLe7d9eKYw=I)t z^HPbDsuif{JbNTs>OjY5^(0L&;Md~qAz@58O@@-Tzn+sx9g|Ipkz-`Y)g+-171|gP zn$==#$rTCnjjNqL$@-22ElSBY3BLS7(e6-hSw^&^)rl6JEE04ltx~lJYZ3nJg|H-Y zOV_V$U!~nJo2AlOCjbLKsyz4bk7Go6$L0Budos;PJgwv6=wdG2M)-&yz;(ity zA0<7$b`RiZv=n6+_^Tze0J@>HFfZS9-cn}fFRsp_L!J?0vMLz>{#HhU){iam7qH!o@Q;u(WB_c_lYk;bgi;tVS;XR=%+ zdhnGd+Ic)2IX}3eaO&>$h3jXdLNqG`h^zEe>gWzL&>tO_=;#Ql4>C)LRg_9-4|~F! z$b{V4<$8VI0Q_-ws|0+LNgG3LWils43)zq>D0+qwB3F?J?TiEr#2~Y|Ae{g*^RN{o>*)u57 zu2Ys>ufKb}O7Gv0ph*^f{oseUf8u}{s^uf89WJCRXne_JnqD>%?F+`+*Ao>KZJbG@ zJo}VQDK1CoPURxI&gdb41m4JaXHusAc9o8fTJ&|6Bp)4jh;Yp^i4mkMi7-!uQt4bE z!Hg21Uhuzkmgp%aWlk{9f}I#Xbyd=yz&y*e(EhRV1%bzhDyk?0)pS&u;5@IO;en=iHQsGAzs>vNgX(m0glyfi2wmdaE|E*!WJCib%T0PW1r52La+F54`?JAst)GhsM_v9T%L`nTj-0 zwXxV70dlB|$s#@)@RA78z$=@=9zUef{t?3-wMNeXhRV?IKi3>0!5#g@v-ez<-!R=I zm=OX*l{(|VKUk3HfjuU3MiTwrTT=Ey4Ica$-F@b0u)GPW1Mo+AeN=VyWVgJ~liTS? zmet~LWYq}K03?WNq(Kfr7J0U)Nw$fgvWK2OZP3^EYIOZdqPH${rc2{UFcT%xX(vDw z_#t=xeEVbw{O9r#J#*Zm3?spRy*A}~b)uiKWY;Tm0{q_M^iqY00Ur&mNV%i7lhFB% z$Pc1h=xCrJN#+f6MTiETAsTGzOi)MCX`=n{^F>K*Q|b=fW#^K5}Vc_b0zK zan-bue44?(g9kqnUTFR(@Sn^O74>-VL+|^+56N>j*BS|aOHLTYR3DQ(?E?Jg`=*TQ|~O`IY*zVlK!u>JyFc;MdBdWYhT_3L$-EBqepG2wOZ5n? zKLfczqBs;ec!#{8MJ-k(0h(v)I~)>x=b%QiY|@rqr(qT~LT9;753i0{=M6p`JU6tu+!h_#u1tj%u;sA1^jVf*;|z*wiP$KLdu(xR_cTDT#1x zaE$_mjV#e)d(FTCC4V{lJ^cw%7!;p8=Exz8y<-;4Z|+u(PoDaGvU5%ozy*Jx+=*F! ze!Kyr4FEp?o{%KEafRwud$fS&_k$nFF6#Rs$)hwo8da1xXD&BppEW*neD=KI8Vz$! zsu~G?er{FFiSST z(Ki3gHBJ(X{Wgd4W-R!ll3i$hJ=rY~IZKAmJ}TeBwTdHw-#~)b5b@ci{*9}XG|q7G zm%A)`XKqO5X0BNB&HbtV$3EKpcXN&auI~R!|6?e3etui9tB76L&NAuaEl0$7gV*8p zHk-w0Lv`>=hMw%Q6JBAnYj)8giM>-~s}>Uc@L&+saC)frfds#EZIUuYiT;X_U^MR( zO+pgm)uvzj{>PhdoHGP)b$@8<3%=Zm1?vDmy0<@LAirvfZ11*=iqF9b2mGk>`{#P% zz|SNuTA7^U)I`jTuy;tspcX*~o@Xys1Sr>F(fI>vF zmuew_9pwk2NwA__rcW?zynNPK`G>V3INZ-~KRTIc{ocXkS7)67?zZHg=5;^6palzA zu#c?0b20M(06*CNGZ*|wjxgKfjW2q*gFI9Z}gKenuM|=KW zNq~QTlVFm&uHWJ+L$H%s-Bt#_w29%MAluHf#hoAs{5?&gNA{&2nav0gl0K;S`*lBn zkBX0{?=uBQ`k>0smC{i?m|2b?JS zNGPfM?d5Z7^Zj?VJv`$CaH~E6=>whS-@e3Kz-}kNhU5JK{tSDEUd0C?aMwwPFc@!$ zYI13*9s&M&N&+ppDD?PPu_>@oFq|L1jMa+*m>W7XLG=77r-*_kS$OS8;)9QVxaEqO zjsW-Z(g!-tZ>acqTZr#DZPBHRWV*ChE>(OAKK5g#XE5I!n(aY(O>}qXui@G`B~ssG z5!agE!~juXDDOmp+gTL&0gD1Jo^{SW!|s55Rp`Ko@|_tYfIH!IZ8hkvoCL#dF9XfdL9|%c>vh)GadzvJBUHf1!(M=vTGZv3_%Kc`2eay*u&Ky1;&adBY5ox|%+H^yrcHi@O45JQC=U272FX45+7;d`&l49(>$K?aSfOd8BfcG(>{sNbGDk@&=PM1Xt!-@yC(#~tj%*XR8o-)GXbE2L7= zi|BA*M50q;&KnyJH@i%s`!B3~Ak-u)mN5Z<`)7p^1wa6Amk{hn3RR8KdK`)Cx8b~` z{2wB*Cd$vBnw03xL9zcfKPmR?++$=i7(cD4on7rc_1gz z5Yztf{@6Vr!_9-BS;B`zVGG}I&H3w&F$r&n2^>DY`%E|r_+^l0INS z;&~Gws`A5of4}tc8xooEqH|*7p&`%v^IH~5I^QW2cHtOy>hni_gJA<0@fT9YU0;hz zA`UzB?2x-9<|vB-8yAJ6z{_XV&o&|g+(}&BpZET`nC<4XEGnGIN|e(a?~f#hpYk-+ zHdUjN8M711c(A?iunnE{1OG2+t&r7z@agv^Uv>SyWxBZ z5J-Me_pj#ty$w=XGUDj}G1vR^4KHk2m}nO$xZtBeyM36T-zI!mImpxl9H}j(S(3T2 z$AK-%rQRqtqQEsv^iW^mjS=PVr%V8Mu}fgw*!1isJv@Pa5XlC^9pePmps`gd>CeQA1)$vL2Wb z%IT7H^D6DjbrT?}^!fIc!MeX!`N0+zCo+;9rcB>_(nQ`WX@B1P>xoW9Ty|j`CIa8* z^fGQdN9sfeqBUJ|snFsX9IiZ+1DH!$lwpjFrKjb|1#GH=E8EUKW{=%Y3pf-o1fY%rB6QGIt)FxL{ygo=J z^?~`IzE&fY46CyBg_;TA&iw=yyu97#{m-~NJPc$iZ>&3>w*oa?uks_bzp#qA09O(6 zX2vE8mR(Z!tjisX7|vRy{1d7Kxd1;Z{rZsl-g+WHK>mPD{*mV+6V89E8I=iu@RaAh zzo`5OtFt1+6oln9PyJ4}+a&<1L{KG&04SC29g*m2E)N(hNX_?L*LCBR2;g6Sh7E1U zT^3FhD!&mVFkIKyFk~_EAgl@jfDwOp*pwtsM*C%u7XiG2i)YA3HOmTZuG^C~KT(wc zZuKWt{+yhYO5rG7Wo(}>eHuzyEOZf8-55p;Ik?4o)<_v1?!0Oc@Yj}d@R;L{Dm$8y zLR+jK997;`O9ZGWf6lnuoQ!0hbDyHDgS~dkmOxzNOKlSuS6FRPfWt~bmlZD=J~vVJ z_Py>DMNYFg=Sy_i;-Cx~$jaSrep`_M+bhYRock>jkM8E^A-J4Ifpwl<@+($)39HqB z6+Rg*7*2-^rFG91w>cQpBdU=W`^Q&gVae|jWYManL z!pkgl0^T39Ki=dgFMreu{(K~#;KP|A zMxKS$ErS?lc-hQkkgaLPeN~^PM7$zd+ZC(?p&aVZ19%v333&fSW%hWZ&5j0RtZRy9 z-4J^rc_XOZOgSw&zrv;#1?13XTK0OwQxkp}6#x5Ya}-)Av#Q;^yQT=>mp^BH+kTEz zhya+;4vjvD<&MIpng?*$$?8sGkjX=7IFEV7n5aY;>s8jY2MJ)9bX$l3Zv8)?|NZ69 zp@I}hexq17Uch=_-q}lrsQw6>vJ3)txZV*OtVP|TfvmG2rpetf%ijzu=M6;bxI7@M zOP5APfGs@uJ^erI%dou+Je>$iAhe;+*$b5wK9LdfN7#AFprE(aEHw|j*~e~WumG#_ z@3*HcE=QI6q6iR>KWo~}koWiWze4~%@pWEMR#PgzcwxfMmjF3KwZ#vj03rY!FDjC8 zlgWA03~i+Au67I%V2%F={0)ThVo?7(nYR@1{+=Eynt}krrk4Ok!>Ny&W?6bClxPVA z+kF~yK_PamsYn2S0zAy=e?9_u-hZ+nhYq+9`d?T>@&Nz$7B!(CfCx}ZY)J~8HRjnU z!{^G@;CS@9P9qcn0t0Z1-3Wl%zZY7DO9`+Pl0SA5!mSZz%+ zGDV6c0S4$2YA`ws=j3Zj1mFjR{1V8&>%&0CxmT57<;PnGoGmImb4B+{*tA3e%~A>l z%})RcV-9qDJWK*~jx)vJgOWr&{s<5tfWL9zCBUe$f=NyUDF5t`uRo9}2Fq2V`zx$r z1mLig*OeUqGi_<9R2lM|vM1YBIl2-QNq|7ctq0|g^T#M8k8F|{QeNnPVKWf{3`;KZ zKjRf<3T`BD?En&hMC4oE)R>W=r%EkHmvc(cc zAVTI5V7vdp#l9YZCviOe4@1*oBzQ6njpaiNVDjR!37gI+U{H05ong4emi^0GnQ}>o z7_dDu0`xYQN1%Cw{zrHj$ya5pSO6ewTBCq0RsMNTB3;}gQIe6MHEG30fJpOC*Lk)7 z7Iu#WgAYHtDTym2Y(~u_X|y7KUCF<$JAPWUVihQ>mqFc?E=!^)?KR1lKt%;eSfe8V zhpD1bIRIj=3h6vEt>g{4UMM?qusHZk3JpzaiR@fYg2a2*UHb1a} zQhmHQ1p*A`9q_|O3(mp=RTdFI*c{0TEXr5Y|K}yDj!y)?Z%&8^AZ&ixP~JIX?7XrH zubd=I*u04njZCXEMe%_On;+&4#u;uRFqCTm0nh`eRa*%YHrKh@xJ7A%e|TOBXdGVU3LdR%n)|sggYBo3>Ho z5RnonE(h>ROHp}1NCH_#Qd}}&jU_-3V~bnL-`7%{842eAJ3Ru7*75?pd5V0|nkr+3 z32T)14`MTC+FPSUS=LoFQBQjWI1&{BRQbs5v!kixTIspW8neO@IT#&R=ThR6Itb`&y*fUO;b)$-QWi2McQy0fJas-)*G) z-ak(Ml$?54{~s+$ATWV{7QFzT02qZxR+t8DO&TO5kg%DMKsdhw)19bDX7U!Ls;t0` zShIe_p98@5MCAd!-04O=EzW&Z6iBJT2q2pUapiqyv>^@a?X7v2~;#A2@r|^ zk-dOWH`x7;%!tI|L`I@bi^NETu<1>JFX=M06B(zdfziLn1*JrMzfO-BUSxX5TO7!pmo@~2t?tST}iL686}@7-CE0H-ItN>CW+ zy)E(onZG+{{|821WWvH4N&uW*G>|Xr|5PGOfV@`5ZG`+eTKd~QMjY`!0efvw4M`xB zLR(W(Y;ARAyTRoFmLj5nu=4`HRR&u^rXu#OI^kQdo~*D3Gn!)&CD5HA0z^fD*9OCt zAe2Ivw#Lt1fSG(AAR9SxC526m033EUE^@T@P|gZ~ypoNF3Gi|p0e1So*?m@Z6XBo? zx~N+sC1#Ks$;q~*8viE5>KI`SA;66*G`j(IdV*@w|2U+po(K>iz?-9P1mO00#t}(z z|1Fvmn|eYGFTh?x&-ontSl)`P1|1xe>~n^mun(GlnnaULkYU5!};3NV7=~v*^^NO zU@sIU(L4H!p=4N;M1{o}u)!z4@43hs=J7D4vk-s@(73Jo(@BX=Pb8|!pGpxR6fT}R zmY^oa!!^Dr;7x{lW2te;FdqdJZCqSwVbKKOuyNfot>Df6d#VChtJDFOB!vpPQdRy` zBS3&L8BL}aPOG%qR}M}2B-U8gjF&;HG(HN5#+b00y#K9hwUpnXdO2sM!y1o{gtY&j zDEU*70Im%Bc7PFYBxz$e(XlayeLc6e560#jRbErz7vEsanqYD)L1yXfFweZj$>AvAywDG&I-DhjB@F-!CXgaHGpHWussp&}3gh9=7Ef1{Q_!ut>BLf-!&hU3wxMt~4| zo<5$Wj+De0YtkE|61}5e4!i(93V1Ib_Ul*j>jF$k^aX_37~mY?XSe2j-e0nD7p8Rko(=n8uq>+ZNbAMOqpM+6csRPWDSdvtw&b#uPt#`g|0=`$I`R8zzV z#F??k=GTMO{{LE!n7P;gO;(rb@q;WE=!536Xg>>4w_PHa`U2UADDeDQhqWyX{Cq-; z$v;I%p$jkZW!Ida5dPrCyxQV(kk6f#Xe3ki{#c2~C%{OI2eP5LpX1DVH--RiNi+}z z9^IFsn^rNfv&8o>V;?ZTZH4o~qSgt3$zBw=y}uY*Fr#YYmXJahmh@3M>4OC?@4cwJ zg#$2ia?A{r*B7^kA$xWVPB}z?FV*ro>N#XA6b1g}K$5O!`hRrNkwTl7NVKdyzBBAa z0obRm%!d_;yx&n~0m`{ePm_lHBhcO>~WG$pK|6pJSoIig^&*w1U6t1N*n*k_~Q8_oK(OXCNqj2F6VqW#Bis zgW}n5eX-uxHI)be5+D}{%n058QWM?E65s*@{J$S|-oWLH%Wo!>3!IVc4URlRgDkw4 zP*#jhF95?e zrCDC`_yNm)QyY35JvTIAhEf7V0Dn~o5#aONauMNkM4U4)56zLW;oi;{-;z!7(H_$0 z%$VaS(N9{{5tKgmtj`#>MQLIYA+#FiinQ(QOyW_QK@H_L{W5 zN3xGq0twEJhDiV^biy|Uff3kGT^S}pqA+BM^630AT&t);#;CzaHkmMQ9*+F0e-vO$yI#bcS*r|=jz~5El>eZA18a>$J70g zJ$~tf=kn~b^U{Z*3WDjE&MQit@0)cO%><;-tAk1EbEVKemJcj%BkJ##N^C|X@UI&H z_`iE3nCVAVh6D-bFZi!prWwDuu@Jb(xbDaU2PPaW+{iqCIza*HgL*%(0?*G&ANE_n z7j?w%j1nMD3Vm~bnr>d>yx?b#TC}Eac9$CD(>Njo0^`2S_ENyo>D?%pKR<$c^6 zb`#-m9+V?VaND|K*z@R_$QHY>88G&l`99=Ee=u%VekcF< zuCvW0Hsg)HaLlBqj!CqvL!w_`(V!(+t`NZsphAF1P~aBUphWuH+p;X81et4BA4GCI zqscB0enq0(Z(ftN;=up6yQu_z80!6)9)|SsYW=vM)RR$T&Y4E({%MT>zDR(i_<;c1 zB#V}$C+vF|JbfP{z*T+NF@osv{buR(zeQ~_wKhri5v_OkhaeC5lQdI>m75R69jNcfQ03}wUD5bfqnC0%WjPP$5G23r)Wz{ z)ZHrEhxEOBjTtz!+cPZC|Hl4kpc`r{>1w-(2Iu4T_3_wUJ8thU=sPYeL~d2}>A|IJ3!`)88?o#dQ5wz(P!;PIPN5?#_|Q8&Z$6HF3K6v>VP zcn%Q&ksyi$IL;N4D5p!|U6UC2^+X4gMlAx+b!vTk%fg5X8}{${oolm+&5QNOO*$Xh z$D;X(^7lx1v5R156VZ=ONpx!5sqm=m$6Hw$Cyssb<&~~ zOPUumsf1{8-4ctg>x+KvILF{CFU3RyH%SH&EJ+n;4}d|pfghFR!Z64GU#`j~-?}{7 z>!ZK(;Hwr5XMkWu+BLj=gAr+~A2r$=KH;h)^5wVQ*Vm%>DA^g>;nHhhd`gi8n1O8ey;h z@;Qk{3*{1bW$@Dz&+Z?*edgZ#3=+U4!4@|Xgh;TcDNCwU)|&u-Z-;E32a6HML;yqr zkil;q%@TFCIQYG!)1o&AEjm7C+2ru{iM(mKMOXI3zZZ0n84-+;yc~PD4sruiZC>!n zGLlun>n(G% zBeR~28g83W*)!_|s78XWRF2Y0u*--j;7K1O$dYc-PfuB;q#1_8ydjC&<5dqmelST7 z?@O6^y+U1-J!MA)MudeD6>M+sEl#g6H-+lX-Rtw}`z|R~tQ)_0mgpOM9mxZ)j~xLq z%kO!8bo-(Ay%SwvnC*#J%sy-2pH%`>A_1&DQD8t0t_p)B>S}cWj|6#1+N7TxXM|zV zA)RtafP4T6a;0d1`~VT*+ixZ-Mg$ldY9=Fsp`_;>831}`=k1Be1{Uw}-+NJ^`CV%@ zIZo%7B+p(Zc}_VyH(-nz8=?8VJ{GZHk0?*_Khz0vJF3 zM|5Pw@%mW0h}Bzoe2+WE%zJ%L?~gIr6Ortq5c-MP2LGHQfJ=fMZZzoN^8!es)dxk+Ov~pTub-JMdj(5&vnRDI|z?T<*1UE%| zh>=wyPZnW#F3G+a$e8x`$QM9_=9Cm50wThtUDc%#9~pjnR@L_os>XOxnv?SsNMIR3(`SBIWb)9t&hJ`BbnCk4%BH8ujlO{)URwOdsDA&CaJKPAGcZ!i*yhvcArzs(Y&M`8dJ0^`+g`V zx1O(zX;Lg>RlBKlHkpa5`;4Z}ltt^AluN4$socoZ$w`Cu4_S0<+@hmn7M+~H&q*|# z4@LoOM*Gq^rsq#v_F@%y`whKhuhI2@e{ht&{!BFRq4nYMGx1)3&PkqmM*yEBx;Lsy zRRr{|Epewz>=LzxumDHeQCZK}m;sm{Pql02EgAeu#Qj*u-;3OY_*!9gWV z!gNctb*ap>yF$PBbkY61*mJ##EfWFkEllEU>~&7}ft{Ej+RNn5nQ@aQ^H$_B4{;S9 z*X!e$#4k+e9RCGH03Q*ylXLd#>PY>o7MYYO1m=lC9==0j9+l+*?#|V5T6l1hY|lBt zg3ClifI%f>g-ZsT7_DUg-RU$WrzaUW_YYh4{h?6@?7a-^T`XcDa;;$z?BZ@tuH~H5 z)?YhI^v00$*)%iB_m&lo%<&Ase`SCe@Gbki=jhKyjoYok@AdCK-yPco0V+p>&&1^h zc0~wXWOj?n4`2xK{R||1tt=Oi9jWBmqNX@PSayjO#29CC;Z)37err1&8MVCCAsRqA zRf9+0$D|BAJl0B&6`ZkyB`p&5u)n>kgGGu(;opO%$4+}ECY?R^y^RG%CQYW}%b&@| zw#c><7|fBqN!6Q!X5{Ifp3VpGBaS5Uy#Ac2?JsBoaJj*KQI#SuKWJqUp|t`LN|D1? zDdLydW9|qEa!1m#qlQ0OMr0UZfC4E%j){Up65b$3K!#63F=>n}J0_=vE>%@0<`0=$YeiXsDs@6-w<(nV?SZv)Jht*bj3 zK@#kD343T~z@|cqxSn)M9t&6bnfJbc`3Q>~O%2Bo{vezyr{@6$}RQd>Qlr zd%>vvvl@0ae=P=2j|4Z)PoRjWB!wm0ZXIFqRU zK0>W#lOy3tE9Y}o?dpkTM;b&9Ji3DACD0I=qNDR|J)V&o^0`3Eqgz|hqR(rKN^Ub+ zGFIce9v|J@Uv1etX`0Z{x$=Bo`F86tC6$$F&h8Ag$Vo$f&dZ8@{YKw5l@DctWy&s z1M-DhlIOW<(DLx`Id-e&>3#42pBeDKGFQhJ1Od*E2y5Jku%nth^5+a7LP{~_JRv;5 z<3PEBsaDkV5sB>|u`eH-kL653fT@uIyIA9PPkr*nvd1q{?3Q4p03rAVJ2PPbI_mVA zDVKm>*TpBovayGlzW-nIxs)jg(3o5SMA*)5cd=XhRQ?Vkg({oQy=0Y$;#sZbS9ooI z8PWqbfNqlWT&LZ!?Pa| z9$pY&wuwP*v>=xpoy;q%%quTpK<%$Xw%CUaGOdoIf!+kT*=^JX`ltZ9Ab>Do=WYLA afB^uhN|JSaR9ALBy{ok&)LM`L2?R)N5@2M!A_hZjybL&Yg_()ROePa&s#0ZA`H>$< zl{G&m|5AhhP0Hmm%v7dk#^VH+V^2KPl!dVk24f@^v!e|VXzkY0i+amFx8HmHmiyLz z4Z_i(UOn;pb@%K0y>rfY&OP@$M>?H`d6|3C`IFWT?oWPS-uYI~e(^c(`C+V~FMnV)klk($l<#tfd&u!tyS`%{Jo~k)g9B|T*l_r zjqDB*4Q`tU&wlO7mjuc8?IZgGWGkE3y!>dFC0V$z92^e9N=l2t<8i~q%YE`w_Vb|4 zo6Y;EU5$;4p}A!__*#u(fg2zV0BnEg$PPreKjm2`<|YQWV@s|2eoYbQ;$lH zB@+pl7#)LnJZ|N{V3Fvc<5du7>W#GBP^BJb(9|ozz6~f~Inu z0GB43HyH3n!l~A^z0@CqmC@(Ld+Eqe`g__*HbyxC#uh&COr5HGBDkp-L zf&12iCt`$_z!J*~FYklZt?iIX#$aaTYu)pwCZ`p^?Rjx0F_{&T<4&g&Dr>4Kh%-~u zFg-b``k7^A#SjWje>=hBz-r^b+G_Uw1qFVpUD0R+270eZj;~zZmN9YDgN8oKOSB8k zBQJGz)tcuch{GFZgTMAB3 z5d|!rN{V><<`W>Eup3yPTM zC1)oIID>Q_nivx(f79|tDDaoib8w)|3IgT!mJzTDh3`p_O|HQ#twV{&6L9?GIi{~8 z5+YmooD0%L=CF+e?V#Dr#`b6Sz~;wxXr6&kVtAct}^95D?*gvU3qAH=00q1~8`xtSGrDQt=r>*BTaORCwthxC5upT-1B( z>ts9NzE5D4i@^G>&P6I%VQu|VY7YPOKYx(0L(0xJJwon&r9VS0GfFx#;1wuLAk{3g z@t;Xe?o^+qc4yC# zNK9iqGE4{8G{N9}6$)=YQL_-NM08x$+!__kt|Svl$+5!10!mcST#k$eVQ6RsZr>SY zna{)bi?H-r0k3SqLJ4qa`ueZ;NOOl()%3g!aKjJ{UAKIZ4o7Ba(#ctfPW4j2jDcPR z9$y6^Cn3$7COo5VT5(EfQEesNMvfepgBK!Nckq6J*TpiGeB2eNu7m%4`%Ok%wjv80 zq3*~K3%t_HZ){wG!_H~RL~hjHQ^c9as4j5StQJ&&n}EjqN_R4#nvub*iWX z=Nr`&b%qe!k5T1l9I+T{SBW$pF&s{~8I6BXR_bor$tm;Lu26iv>P0^v%bVv+F;3_=}9aSg4@?mTPbgV{sDch%a_^glxrQ< zZg{y_Wbt$f`YV2RdFjf{9NEakM@X}MWzDeh;U~f8NkKtb6J>T7zr`l|RKdgdd%nB? zjrWehNRz~5Xr(dza2=q4`f(<3Tcv0XkjuTy@YwXH=L zIGU9wTRNQrFJXIXu@j}-5*N1(Au1%rM1>)*)zoI!r6J%L{ni%mA|l;pE94=6eD)jnK5b7JMZQ9JO{*V8mdF zr&e(08S{)TUwP*IxBtx&aLIHU{5$r7qe!a7PPd=ihd+hNKmdIHGKIgugfh~7F>6FRkA6>b@qGi9 zE;Lb4pa71K%MiHa>=ZGjCB|A5+Oz)Jucsa2Q>JtHY5QIwJj8yqJSZDR4HXJBDj<*m`}~q49L+$ z?){tBp=Qw%>Ko9f%!cm@jveDH-dw6>Kjh|;9`8P5KK4o_Arzm5!ZmBv&zbBM%r$0o z;^YSNmo3-S!syHxgvQ1tF&zSjT87g*7NuXyWZI*7Z}oH%?ZZFcV7>m>wp|psbSehn zu?vbMEl<%|DJ|4Ey!%;!xtoLqW+akKKq!$3BiyUkut5l2Q_Qv_kTauaX|kFpR&pQh z@pzzgK{>5}<^gP4zF3hp2@(xNjl_MJXsC`xc4}#zorGnd`~W7Ccklf%o$A3K7#{yd zSqs922$|q|QgR$MQZ4p!jE;iSMCL*Y+-glzDIB}Tc zW`21=h2##Wb6`Z%#Nh%{KK#%Ua8#Qc8l}Jy{}j8@4T5@ zA){JnJ9rot+_>~5^j*6^7Z{4yscm_32Yl!Gr@-w@)7fdYsU2PLjvO%*%grPckS}pC zNEUyiUb=Xl&P`5DnVM7V=aSEzX5Qq^!DM1liKK)Nmq@+;-h0Zd%8qAY%hqQt4_5lb z32>bL3LH0XfM;|}p9myo?M2I9pkT>-T1+fHua{19#bR+79JmcL;fRq>e^cI;>RxD` zp)cIEdoQei=m~{KQZb z8lp4hSau-JSUBE#3y8e|H(_V4WNrhGS{Jd|WIFeBfTLrRkT}4Z4FnF^Co|ZOL}-%$ zxm>)|%WBcV%a6Mp|6YN!`IbUWla@VE2C9js5W=k(%cAJ+SjS2%--Q#QQpm_Y7n`6Wefb}5B{En z0!~HNg7+6b&P_!{M~QCnXpGwFs_RvleS~$-n0??6>9_&$%hGQnun$AdPPT= zCS%lOzHi!`n+8uMS^DxTuL+D2O{7}+om-Up^8D$DZG()Vfjl6$Lkjo4A_>GXOh|Fd zlFgV*v5br|hwyOT*(R6El=!=!{?pHhp9=`Usg>Vh&XT9u7;HP^_L(`md>tmPoq;O4 zc{aC0ve+R>@!|Drt@A^&q9>!H%tbI=c?XXpD-v=d22SgTNlfOEBenu(Hk;yF+CTsb z&_uPXp@D*hdkGNud&Izb^fN1lO4*gk=pK1N?L7f+WlLsa6Bixotvq#zN*^2p1Ga%T ztwU50B=6R3bnmRHQk~CJ04MuBd_|jl?h*YQuKdASbiJx*jP)4tUY8{+VbWycttpxQ zS=+Wbvp9c7e>tu=L~z8S_e|@2yPhT6bH<1dAzO6qUs)4#2A?md$C3xsG6c+ssnqdz z`3Fy~b<}>}y$$ETI3}l481LV!i~O{)re^cgOH|NA=1b>;g}>tp3xKJr3`3|?6&Z6t4?n2Y7_^!*U`Yxu zqXHZ>gIKDLuW5mcH8{mpo5|m7?TXLFb%}~f53&4$M}X@CjiTBOn;(a*JD<B={Fk|Z=%hkgIY`<1cLWG@Ae6&qOw@2K>~#CR@jB9ZNAs%L7F znQP={%W)Z4Vpf{=yLIa>e0J=V8vWOkL!eU;ZM)_*uiYs9am19+L^PMYHF`%IN@4Z( zDYVX1@>!&Hmf}bZ$T@;S7oSW^V>(>r`&WCPgP~h@pug`X1u-}|ZFFrym(>u#GI+~Y zv8o3RA$w8wA`4oG}m)I?Dc1?~q==LTGONVqxiW^_2?;4dmPltqzCP^o$Sc33C38Iy@^ zd{Z&39cqR zU)_^K#d3Pj9*E!qkFQQcVsSkb6c*C?He8O3xk}8MSm`mAX`Fgf()d*RnzE^?I51yO zxe!L~4#OA6dmv7jFaOjn+OCA`u^T%pEoNtkU`g{(ur?7vJ9+XfT)LEP;?~~2UO@^R zE^lt6<>_Yt$mRumMcxLBw-iQh2FzoYS1f>sHnzb39yy-vR3!3g{Bo{x^{1ZPd(Fev zh-e;rx-Y`4D3`a0P-gMtS?mlKR4?3HmUDMMLE}ueyTWV=Y8#gE9sB$YGcBglg34C# zoMto+>+i9lX`+uokdsASjNJ=l+S%XD#IhbDZ>#Xw~_w5)2d^6}mGLi2PxJfJp; zTT*4U&SC3dqLyWzO%S5X!>lq{r1>}lz|Y00YGW!*+vX#~EF^vwV>@~s+zdJ|SV)U( za)`Z1k|m*BgL7T*M2~{j6tUK2%$aqQNlWUuHE)@HRA{7Qm+)<*oK6Q_L#HMPE|xc7 zXpjjH9K_d^Gnpp(zdv`KGMd-yw(GaJ&5#wA*703t>4*6i2x}0qrwaaGYl(7fsz1{< zDiOBS+CCQv1w-KT`!y#~u}J{7TSPhjEc=;9Yzxv1kg;Xdq$|& z#AY~{7DJF+P$H>oL?AJhh9Kfp4BwO{DQiz9HfwmtV@fLDf(=Z0(~0W~c+gma)E~*o z+4I*Zi^T2XWQ3zs;YGFMy*5WmTe`@i#$=QIM|%9b`s-fy_~3poCn^ht_*e3v4` zgSD&+U-xs9ph!?yVU}IU5dw!$smEe}(X{KfZVeE6Gsn zA`_Q%2?a0MQ79gtrAc8uGD4SEkr)nJ1EYyFn?RmXsSm{tMfm7Xo@JtWWOOZ3lLL?^ zaWni~gFHAdDk&oleG-0m@J+aNGc)?VGFAA5Be#VME(0sI6IOxNCYY(sTYQ1QwRl!XlGh{0`nT@Z=^F zi;ZseUxjyn{Y%>I0YyB6AWrNGzKdvIGB!*A3n}6^1d_Ld!T;4&E1vny z`@6{02nCM%4*~|Jm@tD8=X?-0n7MkjABIOVRaj=U28n>0x#Fq$dJ;%m>2Im>v@9MB zy@z}BaG^k#n3&-5?|f>VA%sawC0sr_j&TvISCu1L9RjY zMs!RT0*G-LK0J^GAbv03?~)b>aW$4Y`AAG_{X+g#oOa=MSPh_j1E&U_>P$jWk)P%= zc_xR<4)d3MEA4O2org20PY45ofvY%PlGEr+Iryle2PNC*=+{Q_56W#>)kr=&k_ouE zQap`^=jtVq-{jaguU*|RN5m~-njKmvuWo6CJGX92p8XbBvW&a4V{rP^m&D9QB`Kw> z=1i#2@Igc1G5j6h@4r zA-6KLjyIVfA3mgNF7eqo8qdecDd8b!bJ%o^TSwTz0o!M#J;s9$o|JkL#>lEgL>ah# z9xj|$ZziCT|LfOdwmPQn;Dbmo@&+E)Q*6I7u2U}e<@-n?Q?w}jAirbKAAM^~UA{C= z@njOzI;8ssn|_)Ck27<-Me)=;NG63gOI2%ribTag7Q(2x;{ob^&{*AE*Svke$l+PY zqR#_$pTM&!M|mGM57a$2n{WK>dJN2aO={NRd9db&=Cv6Bt4pL^!TDjEyWsIt9*5__ qn)~3P**qlDtiJhSc`)Sv1Q-B9A2bunG#pa^0000Dg|00009a7bBm000XU z000XU0RWnu7ytkO8FWQhbW?9;ba!ELWdK2BZ(?O2No`?gWm08fWO;GPWjp`?76C~_ zK~#9!>|J?m9Ord^yR#Q}xgQrit8gR~V?a%_>bMIELUEhSPC@4fGt>Gx)4 zcV}n6IriR__<+Iga+h;_zj?oReD8Z>;&~qGL4JH_^(OMNN&47Lwid-tdJXPsNb%Vt zZ78GPeSg*qbt0x(0*?UHEdkUm0|;N6D|{-G5}>1G^Gbkw2(X@I@G_dzD{TnSt~$8c zeG;Gu<`4mM=%E3R0ER&IN!!Lk_hWOpVbzu$>Cf&pozS#qeeMsF7XjGu^v773gLzLp z{n}8#mz&ulF%`7{3l7{t5n_h#rEAwUii5YN;#coJv>WIJv|lR9=j1)V$h6VP^$ z_xsb|aq$V@lYWSKuLBqJL)1G3(fQlLccGa(5WG7{-RGK)2&`yRAAFPD3TCpU-#f^A z31~UssHtQFl0lmy!E>XOpQn3)+Z6YT?`}R zl#5PF4jut+P)pk$JMbcGKlsN2V5n8$>EFQI*!x8RMH6RZ5;klNmVCevK&F{&E8Gwa zGXjvK?*_Cq5}ll00^n#(gGYcvu0KlqZdczc!sc$XfN$bWnEB*ARmv-sJhrR6AscF? zrPU4AwJYHI|Ha|sw|#0LmIc6)fk%KlL0(SL9FGG(I1MY>qj2xHr(tH~LdCqX&D#QC zZ(u8OEYwKr`ZoCVe-i?TDt55Dlt};qJefyWjGRyg(@2pAzWs*S5kjLfTH)PqIf`!}w19I?m9gKVh9g;u!$vbfOH-A-fduBM? zAllxxCR*|1GK5O6Pq^X2TV~ zsp7}AZ(S<{`KvZr%XJ80M#Vy-O!q1aZ0~UuwfBxaVkXSq_5yLS*1_#x|3g(MG#X)q zn6F|U=f*X_G>X}o$9fu}qsvj$J~1h9eK@52b8iZ~mYjj_vcnTE7O9N3jH7@WxVi5v zboz&2=F-1cG+c;9%MV^EOu*MHyN?P`q~gay91Xlps+#A75JbmAIRUAV|1+9eYPtYS z$1CI?8#8WnQ!jKL?1hf!?m=Yc1DN^vzss(TsCnfOOr2R)HIpE{Q5YMga0w2A(|%y1 zNlckSLxYBcBJU2MeFi5CFvlfz3v=b44*K=%hP@ zv_Gy~RlJ+)SV3r$zYFn^KL^gX4IGcZnge1ANVJQ%we1Gj3}S*BXA$E5DG1)VqB(m| z>Tn@7+mK31qvN5ZLoLvjuFpX-T-wAydnX6?r_9E{vr(qMMlM=m^rA7^?0zx<`wvHf zv9rR)^H+eo`lrBh&A@DU1(?;}A{@F+-G&ew>BNmh8ZD2>zTktvMs7fK_B9Aij{_Ua z++?u;3-JIw6~;Ll!D{1jsb4nrBY?%^ucdKc6nr-qA#^X8)-<`i`(ODvelMRePN5Zv zVoi1)7!v^!76In|8QAI9faBa?UegEWwj*G2?jr-a*1(P$fpK(ztMwMyoOzrGbN(>!Nfv@Z9zvn=95d)Q z6Yvb(5TjJ^;xXFavJ5U7ph3k?)N`ZmSBcF%4$ycOAURCV@SF%_1U+dUEDcF20tw;s5_8S*)t<)#;u!t#f+KmwM~dy*sXqrkJ`tE%=D zJ`qDpOrzU>Jl`E*Zj%b$0@1P!TQ$-5q%zwwHC6{N^dSL`0O9j$R~W(i|V90<7)KzOKoeRIMA%8Y+p4eM-AEan+*bac-2> z(@{@Oa9R>FBw`;LA0gX=Ve`V-;L+~hez~Kf^yt65O3k61q21?a^r&-VZr0_58hEJg zS=1T!7^6)C+E-1Rmrr)Zzhus_Q!>g#N9Cvrs|h#1inBrmZ;%F_oO97>Q5@uW%Ij1v zra*@$%j1ly%*Qc1bN{BlBCBd4gHeYC1N3AzMkRcbdk>0&ON0v0l<3bsDgH)zChqOrx|(3D7+Xtviu1M_d+VqQa<9=4q{A zI<5RQ?nJJAc#!sN!+Kn1`#ka!z*&K;a6!%E%+uxxC(dv+t92C01Vus`$2nc$wfo70 zrou?#Q?B<@f;XsiTu?sxbvBPF4~G9I(dVc#xXc1DY7jgF;ey#(dGPYFcY$p1#$;O8 zFSm6{t;2CMqIEgoN!gauw6H<$yx_qe>B_p2UDE%JDg(?c>Wml!&q#v0D&QHX>V;V* z$61jWDr%3t0ou|qGbMWWDnfvC+;{$$_VkQ8#wA~SA*M{E z9#?7}8)NlXIQ;T;LvNAJAcS*Dg|EXVV*YZ*Gb(B&gPsHxhpOW zTeDJTPxPqYj;Sn78bCbP*s!q?Fi>4n$#B8wG!V+nl!eA-&GK=3`uTX)xT?@)PDo+Y zDetGzCmK`vfgUBc@2k(NUm4y}$(Zb7d7#?`=o=zz86om~!d}H4hWiY3dXTjbx0>dkfaS%4(jm-%Z`3 z^L^KD9G_?!xCvqWRDl~)Q=GhV8B$>QvYJgh`yY<9hlG@H^jK6lQMsd&L!C=+0&uq_ zJE##p)JU2vmPaEbrM<(O8E*1 zQ*JO)XEI{ROc7zpWcd3MADydtyBCY`c~d5i1D5^c&72Ct@g~Qyx;mojjZGQ_PB0t! zwDOR$1Fx%nk9QdYCr@5ug7xZ%6<+*y1Z<7*bUh}lGvHC80^im73Arg^etTKLo1f>g zCRbOkUbey1?=7%nZ&C;YQWS|AhBvncXep}f&8O8z-hEgIfPGRLO?CAp8fHwPrS3R9 zdAtyGFr01SjQ?e1{pMW>L>0jdPGq5>tg+p~g0V5Wb;AnV9#2B+%8bbnZ(fTUMWzIu zfc|`Yv=r1tN6Lzy0i7P1sEJ4&I1iY*3++uYvAo?PX`im&4Pt4$hJ=$hvj7^}dL7VG;lMy4=T=k)B%}RX5ICe#1aafGBs8pyh^Z~SL-kfbp?5l` z{gddDBRdUO1O9MIB4q|tBwbr2d? zMU81+NV0ueKbdir^g56#kuvqXOVm0eng-$uAx|mKvD}?*adx7g=2xN(JaWu0Ixj{% z&ncjbG&N33ex3;_Zw(AOrOL$6=@b;sm^MseIBKL4ZUlJrdAHtYz>2Hk&M)84J+){ z1`it-K#3aLhC;k%q0wiT>}tv90kyfea!5rwt4XwxW27+#`rw=c0y7O_fzmu^r^+na zX|htxG9YMLaoxn{HkhHW9~iDAa1{ej0`N4bOz{5dKN}#lU?+x4is{sBn@$$Y+K?!# z)?rvMamA|Ul=MS|rP4hO@dByK$#D}1 zYAOnzY=SqZ3JFt5=iYJ&6v|^t>8_Mr$;bO*_@b!O=c8+#4sa(c^$?7zc$*aO69Ifj zEdz7lXcSg$1d7oah9n`;HnCq9)PJp9zk@rB!m_oMkZdv^UmBQ z)jERtOZjX$PVG#5ZdEQ6sVU&94xWrc2i0;csD)Tof%Ah{@_gb^0v`;E)f}m+=Tg?Dh6W~bzVl^=ueboi(I5&tT_Nf_ap;MBT6BeA{`gf$ttkQY| z5+M$e-_IKb4drhu9m~}`coKjuk_H~t`W*tdZUZk=t95QJJT&n6OmJ zRqbYYWV;x>V?)&RrkLXkhb+XKG(jxFz~VdyH$K(^We!q+yojAP-F;Z zd-mJFQb2m$Gr_{WDQbN~MIK!Ez=Nj%aY*UVyma#Gz$B+&;l>vbn9DC6gH>T87vm%x z$ocBoTGlqXSnA;go!^eDk=*?JgkEB^>qJ_W+MzvT2ELgfBc;&qz9f7cGNfCUmE zCPS%$cgho6$qLU}xHi@R!GsCwRV;OMt~n0F`BSIh(8;I4#555O!Dk$N72M9t!gtNv zGMX5_ZG*&2c8W{6SX9oa%ixt>a%SUietHcC|7;%|I`JH|cdaFXd-k|vriEQ%AUU78 zOkChGe%T63^YjHCwe}$Uy0>{U+lMuds)fGon_1|7ngh3+gCy}Pli^Ize*H5u+?oKI zjjKz0S8tk!(hp)`PZRCS`?nal(_)9FmfTFs%-sY5PV+|nVLbtdVy>r?m8y$-fHI+5 zj5Ih+|3>zGqTU=9bG@Cg1R&-@Ptv~c+!BV>>qCM!iJQ%?ITL|e{OosrG4q%8=xx@O z&7;`BAy5?ee)pd;3mb6p$>Nj?JlC3Gd?W$>+|u=D>y0Yw+2-lX8b}u^nWlKK)OSzn z2a7iKgq63$x4{D~4;*-W6doEe!?G-cw=B!xp|^R`AdVUZ4$A}080IB2v}v}8;JMspusaM- zyrOoGNZl@KR%$jqMg|VG?R#3ku{n6y(iMY_EeT*Q85&v=X=ml;=t}U;W|kjg z`Geof!Qvbz6e6+u)q`(HTDV+2W)QeR(5M`9w{Woka1gdV9ZNs1R4E*5g2nqzn7YwS z08-aD1n}M+mX`OFOWv)e9!l`EG|t^jKs!P}JNP6(>$k*b&PLu8*Yjz3mkW2DurTd} zz>@mvOw>lKC<_Hu$D?M-+-nj%t)*X^x5we|-}QmH{Z(SfHpo=$HgQhA1K=GN>zXxa zFdkuGX0nlh=~C53jgml=lcnmR@<4$n720}evD@=QmAm62HdmXv_RLfkA&(lO<|?U zbvtC%uFul5ER2PN)tdw0a&iz3TEMqt6JAq5Vih(HjS7Hxp1sSOmmSO9QvQAa(GcuC z{sVx9SG9K#drPEFr364+O{*fxg_sCllu#>{#G{N~Ho@<|pweqiOybr`oR?ES(zt?y zz268Eo2F5&f{n%l2kF2Gu|$3+frYv5<^;60M}@8DNQC+vSK9eL%(R))413>^ka%mw z-V~dNGqQ?5mU!-o54D?%8xUNqska0g`-NV}edfW@W$?1oQd!cuAtAc|IBvpIP|lV7 zplm{anHR1D{CL=!)AST`t<|Q^noW-_Of7 zPGt=)8L*%Kv>A4MB@TO@2|-g^(pY0v1A@R|^8wZG^&5Misf9tSCGaryl(P=u%I|E# z_UMj;kc56@XSAR$N5WH8q71}CHq}-iNRb8_d#BnmF2{K?2O#J9a#ZghA4$EQgSIs+ zbZm)3+u9^JTq;J97hCtEM@jQ4@ey$gb0*;xpUz^S6MO1uBcsdU(Svo}mj5(BTUYVY zE2nA0f_}5~n)jkg3^@C0X@a&^r-TNiK)AU^P6-=(4=hg!83(WhGCis{R^b$U5(^B} z>VnI9X(-SqWnp^D3}gzhq2+E6<#TMhB_Pp8_7t%@j4wx}ZK&1@j_Mvf4T?<_(Bi9F mG47D`>Zzx-=W6o*0t^6)h%wum-*m+Q0000+M3mP} z2_;QYxOVb7-CLrUEgNQe-AcGOobLLgbM9aFkLU9|=X;*>{qy;q5cC^F-&7v}00_?C zhop99XIS78 zBV!mOMr72fls*O)0JP;epM7N3`?<0}Jaiwt=Yrp5`&e5#$xx3kwD!BC8{l-_w4GJC z10|5?77rXD(B`_VEJ(=x!`cdV-3>{Mz-~T`r}~b% zX4MRiQxqu}GzYwBYO2;c#PD`rjt;qg#9TaIf|hR^-UP;H>-S!{7_TidG(g7ToV_2n z=9r}Gl;@0Cde9$Ax>;?t+=TUE%ifj(nazQnv2O4Wm%O;Rto63@sIJ`JTa05u*6B*3 zK0YlpR{e{EhNf}jx}X|Fg$LRIfz7Je?TVGt`-6JL2stJo5+oTp~|B3+)rv^f8?LkQ~k(#>VnpP z_`hX-f(o+*&pY^>9_1ALjU&%qsm4nvxBBMoq%TtV*;G*z7S^2fx_7|89JWI~m&UrIKE z_TDIN8TfW)-fB83^T3pVXNe7qkat2lGLX&dn-@M4lGs9oiaf_DR%BAZTOF+Og{@d5 zJi|UICWU;!y5s{JO*0LnzPro13H&oT*>Y|DH`OZvPV#df}hhnPDyGn+d}yDD9kdUEcMdZ zq`M6$^lf7FY_-|DzVr&TX*pz&u=&eF@~7%a&fOC?HkhBPV`FI|Yq{?Ajg8bAw5Pk{ z%7vl5oaIH4JZlss6N=X$tIO~lY8~8R<#jx%Wsb41;)o%3)m!~Vx+7ZHBaMs7d?)uD z&@O?UPux$_D)$2*h2=`4`@3IhJ{SU0zB0`sxTW33o@INhHIyB(+jqn*R7*A;7IXv5 zm^Cms+T$89^T|u&3O)j3Vr^G34Kh;I1H9`x(XDX*(we6U{#vwrK#*s{MLlvE)nW|+ z-r>gHm4(G!ijUOoQdg~TGR&pljdv6rl}lq(WgYZ1_Aady`?zn}A)WTf@8}CeN}ca% z-KiLl*d`5WG(*~cT?^Bb7G53fg@qWcwkPve(@+vfT~z_Gu;PW}BksFxCt%lOW_hxR RU8r6~0OuRz)8G}6{V%OXa-RSI literal 0 HcmV?d00001 diff --git a/public/images/achievements/patch-01.png b/public/images/achievements/patch-01.png new file mode 100644 index 0000000000000000000000000000000000000000..dba898f4031f770d163f1901803f3761da8d0c9c GIT binary patch literal 7102 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 literal 0 HcmV?d00001 diff --git a/public/images/achievements/pendant-01.png b/public/images/achievements/pendant-01.png new file mode 100644 index 0000000000000000000000000000000000000000..0d0b8fc008356990a1d9d4196390ea0edd60daba GIT binary patch literal 8936 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 literal 0 HcmV?d00001 diff --git a/public/images/achievements/scroll-01.png b/public/images/achievements/scroll-01.png new file mode 100644 index 0000000000000000000000000000000000000000..a7ab56721c09959eef69fe7a8d4b4249f5c0e07f GIT binary patch literal 8012 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 Hu0mjfr#yN? literal 0 HcmV?d00001 diff --git a/test/demo/views/achievement/achievement_get.demo.coffee b/test/demo/views/achievement/AchievementGet.demo.coffee similarity index 92% rename from test/demo/views/achievement/achievement_get.demo.coffee rename to test/demo/views/achievement/AchievementGet.demo.coffee index 4cbfaced2..be46199fc 100644 --- a/test/demo/views/achievement/achievement_get.demo.coffee +++ b/test/demo/views/achievement/AchievementGet.demo.coffee @@ -8,6 +8,8 @@ class MockServer module.exports = -> + me.set('points', 48) + unlockableObj = name: 'Dungeon Arena Started' description: 'Started playing Dungeon Arena. ' @@ -25,7 +27,7 @@ module.exports = -> console.log currentView data = currentView.createNotifyData unlockable, earnedUnlockable - imageURL = '/images/achievements/swords.png' + imageURL = '/images/achievements/swords-01.png' data.image = $("") options = autoHideDelay: 10000 From 39fb2cb1b4873cc61391349db0ee7b07e9eb33a6 Mon Sep 17 00:00:00 2001 From: Ruben Vereecken Date: Tue, 24 Jun 2014 13:49:54 +0200 Subject: [PATCH 08/83] Added achievement preview, exp test, stuff --- app/models/User.coffee | 7 ++--- app/styles/editor/achievement/edit.sass | 8 +++++ app/styles/notify.sass | 14 ++++++++- app/templates/achievement_notify.jade | 5 ++-- app/templates/editor/achievement/edit.jade | 3 +- app/views/editor/achievement/edit.coffee | 29 +++++++++++++++++-- app/views/kinds/RootView.coffee | 27 +++++++++++++++-- server/achievements/Achievement.coffee | 3 ++ server/plugins/achievements.coffee | 2 +- test/app/models/User.spec.coffee | 16 ++++++++++ .../achievement/AchievementGet.demo.coffee | 2 +- .../server/functional/achievement.spec.coffee | 6 ++-- 12 files changed, 104 insertions(+), 18 deletions(-) create mode 100644 test/app/models/User.spec.coffee diff --git a/app/models/User.coffee b/app/models/User.coffee index f224c85c9..755c8e097 100644 --- a/app/models/User.coffee +++ b/app/models/User.coffee @@ -7,6 +7,9 @@ module.exports = class User extends CocoModel @schema: require 'schemas/models/user' urlRoot: "/db/user" + defaults: + points: 0 + initialize: -> super() @migrateEmails() @@ -88,7 +91,3 @@ module.exports = class User extends CocoModel level: -> User.levelFromExp(@get('points')) - - levelFromExp: (xp) -> User.levelFromExp(xp) - - expForLevel: (level) -> User.expForLevel(level) diff --git a/app/styles/editor/achievement/edit.sass b/app/styles/editor/achievement/edit.sass index 7177978d3..68208ae3f 100644 --- a/app/styles/editor/achievement/edit.sass +++ b/app/styles/editor/achievement/edit.sass @@ -1,4 +1,6 @@ #editor-achievement-edit-view + height: 100% + .treema-root margin: 28px 0px 20px @@ -10,3 +12,9 @@ textarea width: 92% height: 300px + + #achievement-view + min-height: 200px + + .notifyjs-container + clear: both diff --git a/app/styles/notify.sass b/app/styles/notify.sass index 9e077e9ab..2d888b351 100644 --- a/app/styles/notify.sass +++ b/app/styles/notify.sass @@ -9,7 +9,7 @@ position: absolute width: 200px height: 200px - left: -150px + left: -140px top: 0px .achievement-image @@ -66,18 +66,30 @@ &:empty display: none + + .progress-wrapper position: absolute padding-right: 30px bottom: 0px + .achievement-level + float: left + font-size: 15px + .progress-bar-wrapper + padding-left: 30px width: 100% .progress border-radius: 20px + .progress-bar-border + padding-left: 30px position: relative top: -44px + + .progress-bar-border img + width: 100% /*.earned-exp padding-left: 5px font-family: Bangers diff --git a/app/templates/achievement_notify.jade b/app/templates/achievement_notify.jade index cc9779340..a384e4eed 100644 --- a/app/templates/achievement_notify.jade +++ b/app/templates/achievement_notify.jade @@ -10,6 +10,7 @@ div .achievement-message(data-notify-html="message") .progress-wrapper //.earned-exp(data-notify-html="earnedExp") + span.achievement-level.badge(data-notify-html="level") .progress-bar-wrapper(data-notify-html="progressBar") - .progress-bar-border - img(src='/images/achievements/bar_border.png' width='100%') + .progress-bar-border(data-notify-html="barBorder") + //img(src='/images/achievements/bar_border.png' width='100%') diff --git a/app/templates/editor/achievement/edit.jade b/app/templates/editor/achievement/edit.jade index 42b59de17..aac62cbf0 100644 --- a/app/templates/editor/achievement/edit.jade +++ b/app/templates/editor/achievement/edit.jade @@ -21,10 +21,11 @@ block content #achievement-treema #achievement-view + #achievement-view-inner hr - div#error-view + #error-view else .alert.alert-danger diff --git a/app/views/editor/achievement/edit.coffee b/app/views/editor/achievement/edit.coffee index 76988a586..6d34ed757 100644 --- a/app/views/editor/achievement/edit.coffee +++ b/app/views/editor/achievement/edit.coffee @@ -49,15 +49,38 @@ module.exports = class AchievementEditView extends View @treema.build() - pushChangesToPreview: => - 'TODO' # TODO might want some intrinsic preview thing - getRenderData: (context={}) -> context = super(context) context.achievement = @achievement context.authorized = me.isAdmin() context + afterRender: -> + super(arguments...) + @pushChangesToPreview() + + pushChangesToPreview: => + $('.notifyjs-wrapper').trigger 'notify-hide' + + if @treema? + for key, value of @treema.data + @achievement.set key, value + + earned = + earnedPoints: @achievement.get 'worth' + + data = @createNotifyData @achievement, earned + options = + style: 'achievement' + autoHide: false + clickToHide: false + arrowShow: false + elementPosition: 'bottom center' + hideDuration: 0 + showDuration: 0 +l + $('#achievement-view-inner').notify data, options + openSaveModal: -> 'Maybe later' # TODO diff --git a/app/views/kinds/RootView.coffee b/app/views/kinds/RootView.coffee index cdbe0913f..ea01e6303 100644 --- a/app/views/kinds/RootView.coffee +++ b/app/views/kinds/RootView.coffee @@ -51,12 +51,32 @@ module.exports = class RootView extends CocoView newlyAchievedBar = $("

") emptyBar = $("
") progressBar = $('
').append(alreadyAchievedBar).append(newlyAchievedBar).append(emptyBar) - message = if (currentLevel isnt 1) and leveledUp then "Reached level #{currentLevel}!" else null + #message = if (currentLevel isnt 1) and leveledUp then "Reached level #{currentLevel}!" else null alreadyAchievedBar.tooltip(title: "#{currentExp} XP in total") newlyAchievedBar.tooltip(title: "#{achievedExp} XP earned") emptyBar.tooltip(title: "#{nextLevelExp - currentExp} XP until level #{nextLevel}") + barBorder = $('') + + barBorder.hover (e) -> + #console.debug e + x = e.pageX + y = e.pageY + $actualHover = _.find [$('.progress-bar-warning'), $('.progress-bar-success'), $('.progress-bar-white')], (el) -> + offset = el.offset() + l = offset.left + t = offset.top + h = el.height() + 10 + w = el.width() + 10 + + maxx = l + w + maxy = t + h + + return (y <= maxy && y >= t) && (x <= maxx && x >= l) ? true : null + #console.debug $actualHover + $actualHover.trigger e if $actualHover + # TODO a default should be linked here imageURL = '/file/' + achievement.get('icon') data = @@ -65,7 +85,10 @@ module.exports = class RootView extends CocoView description: achievement.get('description') progressBar: progressBar earnedExp: "+ #{achievedExp} XP" - message: message + #message: message + level: currentLevel + barBorder: barBorder + data showNewAchievement: (achievement, earnedAchievement) -> diff --git a/server/achievements/Achievement.coffee b/server/achievements/Achievement.coffee index cd0850731..9c9fae51f 100644 --- a/server/achievements/Achievement.coffee +++ b/server/achievements/Achievement.coffee @@ -32,6 +32,8 @@ AchievementSchema.methods.getExpFunction = -> AchievementSchema.statics.jsonschema = jsonschema AchievementSchema.statics.achievements = {} +# Reloads all achievements into memory. +# TODO might want to tweak this to only load new achievements AchievementSchema.statics.loadAchievements = (done) -> AchievementSchema.statics.resetAchievements() Achievement = require('../achievements/Achievement') @@ -49,6 +51,7 @@ AchievementSchema.statics.getLoadedAchievements = -> AchievementSchema.statics.resetAchievements = -> delete AchievementSchema.statics.achievements[category] for category of AchievementSchema.statics.achievements +# Queries are stored as JSON strings, objectify them upon loading AchievementSchema.post 'init', (doc) -> doc.objectifyQuery() AchievementSchema.pre 'save', (next) -> diff --git a/server/plugins/achievements.coffee b/server/plugins/achievements.coffee index dbb09a825..ca1248de1 100644 --- a/server/plugins/achievements.coffee +++ b/server/plugins/achievements.coffee @@ -25,7 +25,7 @@ AchievablePlugin = (schema, options) -> if doc.isInit('_id') and not doc.id of before log.warn 'document was already initialized but did not go through `init` and is therefore treated as new while it might not be' - category = doc.constructor.modelName + category = doc.constructor.collection.name loadedAchievements = Achievement.getLoadedAchievements() #log.debug 'about to save ' + category + ', number of achievements is ' + Object.keys(loadedAchievements).length diff --git a/test/app/models/User.spec.coffee b/test/app/models/User.spec.coffee new file mode 100644 index 000000000..b751690ed --- /dev/null +++ b/test/app/models/User.spec.coffee @@ -0,0 +1,16 @@ +User = require 'models/User' + +describe 'UserModel', -> + it 'experience functions are correct', -> + expect(User.expForLevel(User.levelFromExp 0)).toBe 0 + expect(User.expForLevel(User.levelFromExp 50)).toBe 50 + expect(User.expForLevel 1).toBe 0 + expect(User.expForLevel 2).toBeGreaterThan User.expForLevel 1 + + it 'level is calculated correctly', -> + me.set 'points', 0 + expect(me.level()).toBe 1 + + me.set 'points', 50 + expect(me.level()).toBe User.levelFromExp 50 + diff --git a/test/demo/views/achievement/AchievementGet.demo.coffee b/test/demo/views/achievement/AchievementGet.demo.coffee index be46199fc..9fcdd9539 100644 --- a/test/demo/views/achievement/AchievementGet.demo.coffee +++ b/test/demo/views/achievement/AchievementGet.demo.coffee @@ -8,7 +8,7 @@ class MockServer module.exports = -> - me.set('points', 48) + me.set 'points', 48 unlockableObj = name: 'Dungeon Arena Started' diff --git a/test/server/functional/achievement.spec.coffee b/test/server/functional/achievement.spec.coffee index ea33c3075..1e815f137 100644 --- a/test/server/functional/achievement.spec.coffee +++ b/test/server/functional/achievement.spec.coffee @@ -4,7 +4,7 @@ unlockable = name: 'Dungeon Arena Started' description: 'Started playing Dungeon Arena.' worth: 3 - collection: 'level.session' + collection: 'level.sessions' query: "{\"level.original\":\"dungeon-arena\"}" userField: 'creator' @@ -12,7 +12,7 @@ repeatable = name: 'Simulated' description: 'Simulated Games.' worth: 1 - collection: 'User' + collection: 'users' query: "{\"simulatedBy\":{\"$gt\":\"0\"}}" userField: '_id' proportionalTo: 'simulatedBy' @@ -20,7 +20,7 @@ repeatable = diminishing = name: 'Simulated2' worth: 1.5 - collection: 'User' + collection: 'users' query: "{\"simulatedBy\":{\"$gt\":\"0\"}}" userField: '_id' proportionalTo: 'simulatedBy' From 7a4c6daec874372ae16fade611e7235643465903 Mon Sep 17 00:00:00 2001 From: Ruben Vereecken Date: Tue, 24 Jun 2014 15:28:18 +0200 Subject: [PATCH 09/83] stats.gamesCompleted is now tracked in users --- app/views/editor/achievement/edit.coffee | 2 +- server/levels/sessions/LevelSession.coffee | 21 ++++++++++++++++++- server/plugins/achievements.coffee | 2 +- test/server/functional/user.spec.coffee | 24 ++++++++++++++++++++++ 4 files changed, 46 insertions(+), 3 deletions(-) diff --git a/app/views/editor/achievement/edit.coffee b/app/views/editor/achievement/edit.coffee index 6d34ed757..f0e9d8265 100644 --- a/app/views/editor/achievement/edit.coffee +++ b/app/views/editor/achievement/edit.coffee @@ -78,7 +78,7 @@ module.exports = class AchievementEditView extends View elementPosition: 'bottom center' hideDuration: 0 showDuration: 0 -l + $('#achievement-view-inner').notify data, options openSaveModal: -> diff --git a/server/levels/sessions/LevelSession.coffee b/server/levels/sessions/LevelSession.coffee index 122789183..a21c7a7db 100644 --- a/server/levels/sessions/LevelSession.coffee +++ b/server/levels/sessions/LevelSession.coffee @@ -4,6 +4,7 @@ mongoose = require('mongoose') plugins = require('../../plugins/plugins') AchievablePlugin = require '../../plugins/achievements' jsonschema = require('../../../app/schemas/models/level_session') +log = require 'winston' LevelSessionSchema = new mongoose.Schema({ created: @@ -20,8 +21,26 @@ LevelSessionSchema.pre 'init', (next) -> @set(prop, _.cloneDeep(sch.default)) if sch.default? next() +previous = {} + +LevelSessionSchema.post 'init', (doc) -> + previous[doc.get 'id'] = + 'state.completed': doc.get 'state.completed' + LevelSessionSchema.pre 'save', (next) -> @set('changed', new Date()) + + id = @get('id') + initd = id of previous + + # newly completed level + if not (initd and previous[id]['state.completed']) and @get('state.completed') + User = require '../../users/User' # Avoid mutual inclusion cycles + User.update {_id: @get 'creator'}, {$inc: {'stats.gamesCompleted': 1}}, {}, (err, count) -> + log.error err if err? + + delete previous[id] if initd + next() -module.exports = LevelSession = mongoose.model('level.session', LevelSessionSchema) \ No newline at end of file +module.exports = LevelSession = mongoose.model('level.session', LevelSessionSchema) diff --git a/server/plugins/achievements.coffee b/server/plugins/achievements.coffee index ca1248de1..9e4f85a34 100644 --- a/server/plugins/achievements.coffee +++ b/server/plugins/achievements.coffee @@ -55,7 +55,7 @@ AchievablePlugin = (schema, options) -> wrapUp = -> # Update user's experience points User.update({_id: userID}, {$inc: {points: earnedPoints}}, {}, (err, count) -> - console.error err if err? + log.error err if err? ) if isRepeatable diff --git a/test/server/functional/user.spec.coffee b/test/server/functional/user.spec.coffee index 03a88242d..89db41e21 100644 --- a/test/server/functional/user.spec.coffee +++ b/test/server/functional/user.spec.coffee @@ -268,3 +268,27 @@ describe 'GET /db/user', -> expect(response.statusCode).toBe(422) done() ) + +describe 'statistics', -> + it 'games completed', (done) -> + LevelSession = require '../../../server/levels/sessions/LevelSession' + User = require '../../../server/users/User' + + session = new LevelSession + name: 'Beat Gandalf' + permissions: simplePermissions + state: completed: true + + unittest.getNormalJoe (joe) -> + expect(joe.get 'stats.gamesCompleted').toBeUndefined() + + session.set 'creator', joe.get 'id' + session.save (err) -> + expect(err).toBeNull() + + User.findOne {_id: joe.get 'id'}, (err, guy) -> + expect(err).toBeNull() + expect(guy.get 'id').toBe joe.get 'id' + expect(guy.get 'stats.gamesCompleted').toBe 1 + + done() From 7a07e1feb0c4bc04e4ed711b252899c1d4853849 Mon Sep 17 00:00:00 2001 From: Ruben Vereecken Date: Tue, 24 Jun 2014 18:14:26 +0200 Subject: [PATCH 10/83] Whoop whoop test for recalculation of gamesCompleted --- .../earned_achievement_handler.coffee | 2 +- server/commons/Handler.coffee | 13 ++++++++ server/routes/admin.coffee | 2 +- server/users/user_handler.coffee | 23 ++++++++++++++ test/server/functional/admin.spec.coffee | 23 ++++++++++++++ test/server/functional/user.spec.coffee | 30 +++++++++++++++++-- 6 files changed, 88 insertions(+), 5 deletions(-) create mode 100644 test/server/functional/admin.spec.coffee diff --git a/server/achievements/earned_achievement_handler.coffee b/server/achievements/earned_achievement_handler.coffee index 24615054d..399106473 100644 --- a/server/achievements/earned_achievement_handler.coffee +++ b/server/achievements/earned_achievement_handler.coffee @@ -21,7 +21,7 @@ class EarnedAchievementHandler extends Handler EarnedAchievementHandler.recalculate achievementSlugsOrIDs, onSuccess else EarnedAchievementHandler.recalculate onSuccess - @sendSuccess res, {} + @sendAccepted res, {} # Returns success: boolean # TODO call onFinished diff --git a/server/commons/Handler.coffee b/server/commons/Handler.coffee index ee4c312de..e2d1c4330 100644 --- a/server/commons/Handler.coffee +++ b/server/commons/Handler.coffee @@ -47,6 +47,7 @@ module.exports = class Handler # sending functions sendUnauthorizedError: (res) -> errors.forbidden(res) #TODO: rename sendUnauthorizedError to sendForbiddenError + sendForbiddenError: (res) -> errors.forbidden(res) sendNotFoundError: (res) -> errors.notFound(res) sendMethodNotAllowed: (res) -> errors.badMethod(res) sendBadInputError: (res, message) -> errors.badInput(res, message) @@ -62,6 +63,18 @@ module.exports = class Handler res.send(message) res.end() + sendCreated: (res, message) -> + res.send 201, message + res.end() + + sendAccepted: (res, message) -> + res.send 202, message + res.end() + + sendNoContent: (res, message) -> + res.send 204, message + res.end() + # generic handlers get: (req, res) -> @sendUnauthorizedError(res) if not @hasAccess(req) diff --git a/server/routes/admin.coffee b/server/routes/admin.coffee index c2ae7612a..0cf1c8c7a 100644 --- a/server/routes/admin.coffee +++ b/server/routes/admin.coffee @@ -13,7 +13,7 @@ module.exports.setup = (app) -> parts = module.split('/') module = parts[0] - return errors.unauthorized(res, 'Must be admin to access this area.') unless req.user?.isAdmin() + return errors.forbidden(res, 'Admins only') unless req.user?.isAdmin() try moduleName = module.replace '.', '_' diff --git a/server/users/user_handler.coffee b/server/users/user_handler.coffee index 0ed4f6a51..e6c0bda80 100644 --- a/server/users/user_handler.coffee +++ b/server/users/user_handler.coffee @@ -375,5 +375,28 @@ UserHandler = class UserHandler extends Handler return @sendNotFoundError res unless remark? @sendSuccess res, remark + statHandlers: + gamesCompleted: (done) -> + LevelSession = require '../levels/sessions/LevelSession' + + User.find {}, (err, users) -> + async.eachSeries users, ((user, doneWithUser) -> + userID = user.get('_id').toHexString() + + LevelSession.find {creator: userID, 'state.completed': true}, (err, sessions) -> + completedCount = sessions.length + User.findOneAndUpdate {_id: user.get '_id'}, {$set: 'stats.gamesCompleted': completedCount}, (err) -> + log.error err if err? + doneWithUser() + ), done + + recalculate: (req, res, statName) -> + return @sendForbiddenError(res) unless req.user.isAdmin() + + if statName of @statHandlers + @statHandlers[statName] -> log.debug "Finished recalculating stats" + return @sendAccepted res, {} + else return @sendNotFoundError(res) + module.exports = new UserHandler() diff --git a/test/server/functional/admin.spec.coffee b/test/server/functional/admin.spec.coffee new file mode 100644 index 000000000..d3a34baa4 --- /dev/null +++ b/test/server/functional/admin.spec.coffee @@ -0,0 +1,23 @@ +common = require '../common' + +describe 'recalculate statistics', -> + url = getURL '/admin/user/recalculate/' + + it 'does not allow regular users', (done) -> + loginJoe -> + request.post {uri:url + 'gamesCompleted'}, (err, res, body) -> + expect(res.statusCode).toBe 403 + done() + + it 'responds with a 202 Accepted', (done) -> + loginAdmin -> + request.post {uri:url + 'gamesCompleted'}, (err, res, body) -> + expect(res.statusCode).toBe 202 + done() + + it 'responds with a 404 when not found', (done) -> + loginAdmin -> + request.post {uri:url + 'ballsKicked'}, (err, res, body) -> + expect(res.statusCode).toBe 404 + done() + diff --git a/test/server/functional/user.spec.coffee b/test/server/functional/user.spec.coffee index 89db41e21..81cc765a6 100644 --- a/test/server/functional/user.spec.coffee +++ b/test/server/functional/user.spec.coffee @@ -270,9 +270,11 @@ describe 'GET /db/user', -> ) describe 'statistics', -> + LevelSession = require '../../../server/levels/sessions/LevelSession' + User = require '../../../server/users/User' + UserHandler = require '../../../server/users/user_handler' + it 'games completed', (done) -> - LevelSession = require '../../../server/levels/sessions/LevelSession' - User = require '../../../server/users/User' session = new LevelSession name: 'Beat Gandalf' @@ -286,9 +288,31 @@ describe 'statistics', -> session.save (err) -> expect(err).toBeNull() - User.findOne {_id: joe.get 'id'}, (err, guy) -> + User.findById joe.get('id'), (err, guy) -> expect(err).toBeNull() expect(guy.get 'id').toBe joe.get 'id' expect(guy.get 'stats.gamesCompleted').toBe 1 done() + + it 'recalculates games completed', (done) -> + unittest.getNormalJoe (joe) -> + loginAdmin -> + User.findByIdAndUpdate joe.get('id'), {$set:'stats.gamesCompleted':0}, (err, guy) -> + expect(err).toBeNull() + expect(guy.get 'id').toBe joe.get 'id' + expect(guy.get 'stats.gamesCompleted').toBe 0 + + UserHandler.statHandlers.gamesCompleted -> + User.findById joe.get('id'), (err, guy) -> + expect(err).toBeNull() + expect(guy.get 'id').toBe joe.get 'id' + expect(guy.get 'stats.gamesCompleted').toBe 1 + done() + + + xit 'cleans up', (done) -> + clearModels [LevelSession], (err) -> + expect(err).toBeNull() + + done() From 6ae505e8a19089204e12bc4dbb1a9d3799523b09 Mon Sep 17 00:00:00 2001 From: Ruben Vereecken Date: Tue, 24 Jun 2014 19:59:36 +0200 Subject: [PATCH 11/83] stats.articleEdits are now tracked and tested --- server/articles/Article.coffee | 13 ++++++++ server/levels/sessions/LevelSession.coffee | 3 +- server/users/user_handler.coffee | 2 +- test/server/functional/user.spec.coffee | 39 +++++++++++++++++++--- 4 files changed, 49 insertions(+), 8 deletions(-) diff --git a/server/articles/Article.coffee b/server/articles/Article.coffee index 626fc779c..a15f8f8e9 100644 --- a/server/articles/Article.coffee +++ b/server/articles/Article.coffee @@ -8,4 +8,17 @@ ArticleSchema.plugin(plugins.VersionedPlugin) ArticleSchema.plugin(plugins.SearchablePlugin, {searchable: ['body', 'name']}) ArticleSchema.plugin(plugins.PatchablePlugin) + +# Assumes every article save is a new version +ArticleSchema.pre 'save', (next) -> + return next() unless @get('creator') + User = require '../users/User' # Avoid mutual inclusion cycles + + userID = @get('creator').toHexString() + User.update {_id: userID}, {$inc: 'stats.articleEdits': 1}, {}, (err, docs) -> + log.error err if err? + + next() + + module.exports = mongoose.model('article', ArticleSchema) diff --git a/server/levels/sessions/LevelSession.coffee b/server/levels/sessions/LevelSession.coffee index a21c7a7db..0ed8784c9 100644 --- a/server/levels/sessions/LevelSession.coffee +++ b/server/levels/sessions/LevelSession.coffee @@ -36,11 +36,10 @@ LevelSessionSchema.pre 'save', (next) -> # newly completed level if not (initd and previous[id]['state.completed']) and @get('state.completed') User = require '../../users/User' # Avoid mutual inclusion cycles - User.update {_id: @get 'creator'}, {$inc: {'stats.gamesCompleted': 1}}, {}, (err, count) -> + User.update {_id: @get 'creator'}, {$inc: 'stats.gamesCompleted': 1}, {}, (err, count) -> log.error err if err? delete previous[id] if initd - next() module.exports = LevelSession = mongoose.model('level.session', LevelSessionSchema) diff --git a/server/users/user_handler.coffee b/server/users/user_handler.coffee index e6c0bda80..c6e2042e4 100644 --- a/server/users/user_handler.coffee +++ b/server/users/user_handler.coffee @@ -394,7 +394,7 @@ UserHandler = class UserHandler extends Handler return @sendForbiddenError(res) unless req.user.isAdmin() if statName of @statHandlers - @statHandlers[statName] -> log.debug "Finished recalculating stats" + @statHandlers[statName]() return @sendAccepted res, {} else return @sendNotFoundError(res) diff --git a/test/server/functional/user.spec.coffee b/test/server/functional/user.spec.coffee index 81cc765a6..0cb6a1bab 100644 --- a/test/server/functional/user.spec.coffee +++ b/test/server/functional/user.spec.coffee @@ -269,13 +269,13 @@ describe 'GET /db/user', -> done() ) -describe 'statistics', -> +describe 'Statistics', -> LevelSession = require '../../../server/levels/sessions/LevelSession' + Article = require '../../../server/articles/Article' User = require '../../../server/users/User' UserHandler = require '../../../server/users/user_handler' - it 'games completed', (done) -> - + it 'keeps track of games completed', (done) -> session = new LevelSession name: 'Beat Gandalf' permissions: simplePermissions @@ -310,9 +310,38 @@ describe 'statistics', -> expect(guy.get 'stats.gamesCompleted').toBe 1 done() + it 'keeps track of article edits', (done) -> + article = new Article + name: 'My very first' + body: 'I don\'t have much to say I\'m afraid' - xit 'cleans up', (done) -> - clearModels [LevelSession], (err) -> + unittest.getAdmin (carl) -> + expect(carl.get 'stats.articleEdits').toBeUndefined() + + article.set 'creator', carl.get 'id' + article.save (err) -> # Creates a new article, version 1.0 + expect(err).toBeNull() + + User.findById carl.get('id'), (err, guy) -> + expect(err).toBeNull() + expect(guy.get 'id').toBe carl.get 'id' + expect(guy.get 'stats.articleEdits').toBe 1 + + article.set 'version', {major: 1} + article.set 'body', 'I thought of something!' + article.save (err) -> # Creates a new minor, version 1.1 + expect(err).toBeNull() + + User.findById carl.get('id'), (err, guy) -> + expect(err).toBeNull() + expect(guy.get 'id').toBe carl.get 'id' + expect(guy.get 'stats.articleEdits').toBe 2 + + done() + + + it 'cleans up', (done) -> + clearModels [LevelSession, Article], (err) -> expect(err).toBeNull() done() From 838012a2cf5c6509b40a2364fa00d4bbde8543a0 Mon Sep 17 00:00:00 2001 From: Ruben Vereecken Date: Tue, 24 Jun 2014 20:27:22 +0200 Subject: [PATCH 12/83] Wrote and tested articleEdits recalculation --- server/users/user_handler.coffee | 20 +++++++++++--- test/server/functional/user.spec.coffee | 36 ++++++++++++++++--------- 2 files changed, 40 insertions(+), 16 deletions(-) diff --git a/server/users/user_handler.coffee b/server/users/user_handler.coffee index c6e2042e4..fab48c5f1 100644 --- a/server/users/user_handler.coffee +++ b/server/users/user_handler.coffee @@ -383,9 +383,23 @@ UserHandler = class UserHandler extends Handler async.eachSeries users, ((user, doneWithUser) -> userID = user.get('_id').toHexString() - LevelSession.find {creator: userID, 'state.completed': true}, (err, sessions) -> - completedCount = sessions.length - User.findOneAndUpdate {_id: user.get '_id'}, {$set: 'stats.gamesCompleted': completedCount}, (err) -> + LevelSession.count {creator: userID, 'state.completed': true}, (err, count) -> + update = if count then {$set: 'stats.gamesCompleted': count} else {$unset: 'stats.gamesCompleted': ''} + User.findByIdAndUpdate user.get('_id'), update, (err) -> + log.error err if err? + doneWithUser() + ), done + + articleEdits: (done) -> + Article = require '../articles/Article' + + User.find {}, (err, users) -> + async.eachSeries users, ((user, doneWithUser) -> + userID = user.get('_id').toHexString() + + Article.count {creator: userID}, (err, count) -> + update = if count then {$set: 'stats.articleEdits': count} else {$unset: 'stats.articleEdits': ''} + User.findByIdAndUpdate user.get('_id'), update, (err) -> log.error err if err? doneWithUser() ), done diff --git a/test/server/functional/user.spec.coffee b/test/server/functional/user.spec.coffee index 0cb6a1bab..36b86fc4c 100644 --- a/test/server/functional/user.spec.coffee +++ b/test/server/functional/user.spec.coffee @@ -298,47 +298,57 @@ describe 'Statistics', -> it 'recalculates games completed', (done) -> unittest.getNormalJoe (joe) -> loginAdmin -> - User.findByIdAndUpdate joe.get('id'), {$set:'stats.gamesCompleted':0}, (err, guy) -> + User.findByIdAndUpdate joe.get('id'), {$unset:'stats.gamesCompleted': ''}, (err, guy) -> expect(err).toBeNull() - expect(guy.get 'id').toBe joe.get 'id' - expect(guy.get 'stats.gamesCompleted').toBe 0 + expect(guy.get 'stats.gamesCompleted').toBeUndefined() UserHandler.statHandlers.gamesCompleted -> User.findById joe.get('id'), (err, guy) -> expect(err).toBeNull() - expect(guy.get 'id').toBe joe.get 'id' expect(guy.get 'stats.gamesCompleted').toBe 1 done() it 'keeps track of article edits', (done) -> - article = new Article + article = name: 'My very first' body: 'I don\'t have much to say I\'m afraid' + url = getURL('/db/article') - unittest.getAdmin (carl) -> + loginAdmin (carl) -> expect(carl.get 'stats.articleEdits').toBeUndefined() + article.creator = carl.get 'id' - article.set 'creator', carl.get 'id' - article.save (err) -> # Creates a new article, version 1.0 + # Create major version 1.0 + request.post {uri:url, json: article}, (err, res, body) -> expect(err).toBeNull() + expect(res.statusCode).toBe 200 + article = body User.findById carl.get('id'), (err, guy) -> expect(err).toBeNull() - expect(guy.get 'id').toBe carl.get 'id' expect(guy.get 'stats.articleEdits').toBe 1 - article.set 'version', {major: 1} - article.set 'body', 'I thought of something!' - article.save (err) -> # Creates a new minor, version 1.1 + # Create minor version 1.1 + request.post {uri:url, json: article}, (err, res, body) -> expect(err).toBeNull() User.findById carl.get('id'), (err, guy) -> expect(err).toBeNull() - expect(guy.get 'id').toBe carl.get 'id' expect(guy.get 'stats.articleEdits').toBe 2 done() + it 'recalculates article edits', (done) -> + loginAdmin (carl) -> + User.findByIdAndUpdate carl.get('id'), {$unset:'stats.articleEdits': ''}, (err, guy) -> + expect(err).toBeNull() + expect(guy.get 'stats.articleEdits').toBeUndefined() + + UserHandler.statHandlers.articleEdits -> + User.findById carl.get('id'), (err, guy) -> + expect(err).toBeNull() + expect(guy.get 'stats.articleEdits').toBe 2 + done() it 'cleans up', (done) -> clearModels [LevelSession, Article], (err) -> From b95120568136999d296f83967c477126df037557 Mon Sep 17 00:00:00 2001 From: Ruben Vereecken Date: Wed, 25 Jun 2014 20:04:39 +0200 Subject: [PATCH 13/83] intermediate --- server/articles/Article.coffee | 1 - server/levels/Level.coffee | 11 +++++++++ server/plugins/plugins.coffee | 10 ++++++++ server/users/user_handler.coffee | 42 +++++++++++++++++++++++++------- 4 files changed, 54 insertions(+), 10 deletions(-) diff --git a/server/articles/Article.coffee b/server/articles/Article.coffee index a15f8f8e9..ee9b7604e 100644 --- a/server/articles/Article.coffee +++ b/server/articles/Article.coffee @@ -8,7 +8,6 @@ ArticleSchema.plugin(plugins.VersionedPlugin) ArticleSchema.plugin(plugins.SearchablePlugin, {searchable: ['body', 'name']}) ArticleSchema.plugin(plugins.PatchablePlugin) - # Assumes every article save is a new version ArticleSchema.pre 'save', (next) -> return next() unless @get('creator') diff --git a/server/levels/Level.coffee b/server/levels/Level.coffee index 9cadeac7b..033ce4ebd 100644 --- a/server/levels/Level.coffee +++ b/server/levels/Level.coffee @@ -21,5 +21,16 @@ LevelSchema.pre 'init', (next) -> LevelSchema.post 'init', (doc) -> if _.isString(doc.get('nextLevel')) doc.set('nextLevel', undefined) + +# Assumes every level save is a new level +LevelSchema.pre 'save', (next) -> + return next() unless @get('creator') + User = require '../users/User' # Avoid mutual inclusion cycles + + userID = @get('creator').toHexString() + User.update {_id: userID}, {$inc: 'stats.levelEdits': 1}, {}, (err, docs) -> + log.error err if err? + + next() module.exports = Level = mongoose.model('level', LevelSchema) diff --git a/server/plugins/plugins.coffee b/server/plugins/plugins.coffee index 25674f688..13cf684ab 100644 --- a/server/plugins/plugins.coffee +++ b/server/plugins/plugins.coffee @@ -256,6 +256,16 @@ module.exports.VersionedPlugin = (schema) -> ) ) + schema.pre 'save', (next) -> + return next() unless @get('creator') + User = require '../users/User' # Avoid mutual inclusion cycles + + userID = @get('creator').toHexString() + User.update {_id: userID}, {$inc: 'stats.levelEdits': 1}, {}, (err, docs) -> + log.error err if err? + + next() + module.exports.SearchablePlugin = (schema, options) -> # this plugin must be added only after the others (specifically Versioned and Permissions) diff --git a/server/users/user_handler.coffee b/server/users/user_handler.coffee index fab48c5f1..065ffdcc9 100644 --- a/server/users/user_handler.coffee +++ b/server/users/user_handler.coffee @@ -375,6 +375,23 @@ UserHandler = class UserHandler extends Handler return @sendNotFoundError res unless remark? @sendSuccess res, remark + countEdits = (model, statKey, done) -> + User.find {}, (err, users) -> + async.eachSeries users, ((user, doneWithUser) -> + userID = user.get('_id').toHexString() + + model.count {creator: userID}, (err, count) -> + if count + update = $set: {} + update.$set[statKey] = count + else + update = $unset: {} + update.$unset[statKey] = '' + User.findByIdAndUpdate user.get('_id'), update, (err) -> + log.error err if err? + doneWithUser() + ), done + statHandlers: gamesCompleted: (done) -> LevelSession = require '../levels/sessions/LevelSession' @@ -392,17 +409,24 @@ UserHandler = class UserHandler extends Handler articleEdits: (done) -> Article = require '../articles/Article' + countEdits Article, 'stats.articleEdits', done - User.find {}, (err, users) -> - async.eachSeries users, ((user, doneWithUser) -> - userID = user.get('_id').toHexString() + levelEdits: (done) -> + Level = require '../levels/Level' + countEdits Level, 'stats.levelEdits', done + + levelComponentEdits: (done) -> + LevelComponent = require '../levels/components/LevelComponent' + countEdits LevelComponent, 'stats.levelComponentEdits', done + + levelSystemEdits: (done) -> + LevelSystem = require '../levels/systems/LevelSystem' + countEdits LevelSystem, 'stats.levelSystemEdits', done + + thangTypeEdits: (done) -> + ThangType = require '../levels/thangs/ThangType' + countEdits ThangType, 'stats.thangTypeEdits', done - Article.count {creator: userID}, (err, count) -> - update = if count then {$set: 'stats.articleEdits': count} else {$unset: 'stats.articleEdits': ''} - User.findByIdAndUpdate user.get('_id'), update, (err) -> - log.error err if err? - doneWithUser() - ), done recalculate: (req, res, statName) -> return @sendForbiddenError(res) unless req.user.isAdmin() From fce9f0031ba6219323b441344633c47b698370b6 Mon Sep 17 00:00:00 2001 From: Ruben Vereecken Date: Fri, 27 Jun 2014 21:30:31 +0200 Subject: [PATCH 14/83] All edits are now tracked intermediate intermediate --- app/lib/SystemNameLoader.coffee | 2 +- app/lib/deltas.coffee | 7 +--- app/schemas/models/patch.coffee | 2 + app/schemas/models/user.coffee | 8 +++- server/articles/Article.coffee | 12 ------ server/levels/Level.coffee | 11 ------ server/patches/Patch.coffee | 30 +++++++++++++- server/patches/patch_handler.coffee | 15 +++++-- server/plugins/plugins.coffee | 10 +++-- server/users/User.coffee | 13 +++++++ server/users/user_handler.coffee | 14 ++++--- test/server/functional/user.spec.coffee | 52 ++++++++++++++++++++++--- test/server/unit/patch.spec.coffee | 21 ++++++++++ 13 files changed, 146 insertions(+), 51 deletions(-) create mode 100644 test/server/unit/patch.spec.coffee diff --git a/app/lib/SystemNameLoader.coffee b/app/lib/SystemNameLoader.coffee index f23dbe491..66924682b 100644 --- a/app/lib/SystemNameLoader.coffee +++ b/app/lib/SystemNameLoader.coffee @@ -1,4 +1,4 @@ -CocoClass = require 'lib/CocoClass' +CocoClass = require './CocoClass' namesCache = {} diff --git a/app/lib/deltas.coffee b/app/lib/deltas.coffee index 8ff4d5dd2..7c3fa3884 100644 --- a/app/lib/deltas.coffee +++ b/app/lib/deltas.coffee @@ -1,4 +1,4 @@ -SystemNameLoader = require 'lib/SystemNameLoader' +SystemNameLoader = require './SystemNameLoader' ### Good-to-knows: dataPath: an array of keys that walks you up a JSON object that's being patched @@ -10,7 +10,6 @@ SystemNameLoader = require 'lib/SystemNameLoader' module.exports.expandDelta = (delta, left, schema) -> flattenedDeltas = flattenDelta(delta) (expandFlattenedDelta(fd, left, schema) for fd in flattenedDeltas) - flattenDelta = (delta, dataPath=null, deltaPath=null) -> # takes a single jsondiffpatch delta and returns an array of objects with @@ -27,9 +26,7 @@ flattenDelta = (delta, dataPath=null, deltaPath=null) -> results = results.concat flattenDelta( childDelta, dataPath.concat([dataIndex]), deltaPath.concat([deltaIndex])) results - - -expandFlattenedDelta = (delta, left, schema) -> + pandFlattenedDelta = (delta, left, schema) -> # takes a single flattened delta and converts into an object that can be # easily formatted into something human readable. diff --git a/app/schemas/models/patch.coffee b/app/schemas/models/patch.coffee index e14423371..2cbb8ad2f 100644 --- a/app/schemas/models/patch.coffee +++ b/app/schemas/models/patch.coffee @@ -20,6 +20,8 @@ PatchSchema = c.object({title:'Patch', required:['target', 'delta', 'commitMessa major: { type: 'number', minimum: 0 } minor: { type: 'number', minimum: 0 } }) + + _wasPending: type: 'boolean' }) c.extendBasicProperties(PatchSchema, 'patch') diff --git a/app/schemas/models/user.coffee b/app/schemas/models/user.coffee index 74e9c7dd3..0c09e64d5 100644 --- a/app/schemas/models/user.coffee +++ b/app/schemas/models/user.coffee @@ -160,7 +160,13 @@ UserSchema = c.object {}, data: c.object {description: "Cached LinkedIn data slurped from profile.", additionalProperties: true} points: {type:'number'} activity: {type: 'object', description: 'Summary statistics about user activity', additionalProperties: c.activity} - + stats: c.object {additionalProperties: true}, # TODO set to false after dev + gamesCompleted: type: 'integer' + articleEdits: type: 'integer' + levelEdits: type: 'integer' + levelSystemEdits: type: 'integer' + levelComponentEdits: type: 'integer' + thangTypeEdits: type: 'integer' c.extendBasicProperties UserSchema, 'user' diff --git a/server/articles/Article.coffee b/server/articles/Article.coffee index ee9b7604e..626fc779c 100644 --- a/server/articles/Article.coffee +++ b/server/articles/Article.coffee @@ -8,16 +8,4 @@ ArticleSchema.plugin(plugins.VersionedPlugin) ArticleSchema.plugin(plugins.SearchablePlugin, {searchable: ['body', 'name']}) ArticleSchema.plugin(plugins.PatchablePlugin) -# Assumes every article save is a new version -ArticleSchema.pre 'save', (next) -> - return next() unless @get('creator') - User = require '../users/User' # Avoid mutual inclusion cycles - - userID = @get('creator').toHexString() - User.update {_id: userID}, {$inc: 'stats.articleEdits': 1}, {}, (err, docs) -> - log.error err if err? - - next() - - module.exports = mongoose.model('article', ArticleSchema) diff --git a/server/levels/Level.coffee b/server/levels/Level.coffee index 033ce4ebd..9cadeac7b 100644 --- a/server/levels/Level.coffee +++ b/server/levels/Level.coffee @@ -21,16 +21,5 @@ LevelSchema.pre 'init', (next) -> LevelSchema.post 'init', (doc) -> if _.isString(doc.get('nextLevel')) doc.set('nextLevel', undefined) - -# Assumes every level save is a new level -LevelSchema.pre 'save', (next) -> - return next() unless @get('creator') - User = require '../users/User' # Avoid mutual inclusion cycles - - userID = @get('creator').toHexString() - User.update {_id: userID}, {$inc: 'stats.levelEdits': 1}, {}, (err, docs) -> - log.error err if err? - - next() module.exports = Level = mongoose.model('level', LevelSchema) diff --git a/server/patches/Patch.coffee b/server/patches/Patch.coffee index 3e12638cf..252d68743 100644 --- a/server/patches/Patch.coffee +++ b/server/patches/Patch.coffee @@ -1,7 +1,8 @@ mongoose = require('mongoose') +deltas = require '../../app/lib/deltas' {handlers} = require '../commons/mapping' -PatchSchema = new mongoose.Schema({}, {strict: false}) +PatchSchema = new mongoose.Schema({status: String}, {strict: false}) PatchSchema.pre 'save', (next) -> return next() unless @isNew # patch can't be altered after creation, so only need to check data once @@ -45,4 +46,31 @@ PatchSchema.pre 'save', (next) -> @targetLoaded = document document.save (err) -> next(err) +PatchSchema.methods.isTranslationPatch = -> + console.log @get 'delta' + expanded = deltas.expandDelta @get('delta') + console.log 'expanded' + _.some expanded, (delta) -> 'i18n' in expanded.dataPath + +PatchSchema.methods.isMiscPatch = -> + expanded = deltas.expandDelta @get 'delta' + _.some expanded, (delta) -> 'i18n' not in expanded.dataPath + +# Keep track of when a patch is pending. Accepted patches can be rejected still. +PatchSchema.path('status').set (newVal) -> + @set '_wasPending', @status is 'pending' and newVal isnt 'pending' + newVal + +PatchSchema.pre 'save', (next) -> + User = require '../users/User' + userID = @get('creator').toHexString() + + if @get('status') is 'accepted' + User.incrementStat userID, 'stats.patchesContributed' # accepted patches + else if @get('status') is 'pending' + User.incrementStat userID, 'stats.patchesSubmitted' # submitted patches + + next() + + module.exports = mongoose.model('patch', PatchSchema) diff --git a/server/patches/patch_handler.coffee b/server/patches/patch_handler.coffee index 7d30ce353..981a32af2 100644 --- a/server/patches/patch_handler.coffee +++ b/server/patches/patch_handler.coffee @@ -48,11 +48,18 @@ PatchHandler = class PatchHandler extends Handler if newStatus is 'withdrawn' return @sendUnauthorizedError(res) unless req.user.get('_id').equals patch.get('creator') - + + # newly accepted + if newStatus is 'accepted' and patch.get '_wasPending' + accepter = req.user.get 'id' + User.incrementStat accepter, 'stats.' + # these require callbacks - patch.update {$set:{status:newStatus}}, {}, -> - target.update {$pull:{patches:patch.get('_id')}}, {}, -> - @sendSuccess(res, null) + patch.set 'status', newStatus + patch.save (err) => + log.error err if err? + target.update {$pull:{patches:patch.get('_id')}}, {}, -> + @sendSuccess(res, null) onPostSuccess: (req, doc) -> log.error "Error sending patch created: could not find the loaded target on the patch object." unless doc.targetLoaded diff --git a/server/plugins/plugins.coffee b/server/plugins/plugins.coffee index 13cf684ab..ea3a4801c 100644 --- a/server/plugins/plugins.coffee +++ b/server/plugins/plugins.coffee @@ -1,5 +1,6 @@ mongoose = require('mongoose') textSearch = require('mongoose-text-search') +log = require 'winston' module.exports.MigrationPlugin = (schema, migrations) -> # Property name migrations made EZ @@ -256,13 +257,14 @@ module.exports.VersionedPlugin = (schema) -> ) ) + # Assume ever save is a new version, hence an edit schema.pre 'save', (next) -> - return next() unless @get('creator') User = require '../users/User' # Avoid mutual inclusion cycles + userID = @get('creator')?.toHexString() + return next() unless userID? - userID = @get('creator').toHexString() - User.update {_id: userID}, {$inc: 'stats.levelEdits': 1}, {}, (err, docs) -> - log.error err if err? + statName = User.statsMapping.edits[@constructor.modelName] + User.incrementStat userID, statName next() diff --git a/server/users/User.coffee b/server/users/User.coffee index 40d662913..2cdbed86f 100644 --- a/server/users/User.coffee +++ b/server/users/User.coffee @@ -108,6 +108,19 @@ UserSchema.statics.updateMailChimp = (doc, callback) -> mc?.lists.subscribe params, onSuccess, onFailure +UserSchema.statics.statsMapping = + edits: + article: 'stats.articleEdits' + level: 'stats.levelEdits' + 'level.component': 'stats.levelComponentEdits' + 'level.system': 'stats.levelSystemEdits' + 'thang.type': 'stats.thangTypeEdits' + +UserSchema.statics.incrementStat = (id, statName, done, inc=1) -> + update = $inc: {} + update.$inc[statName] = inc + @update {_id:id}, update, {}, (err) -> + done err if done? UserSchema.pre('save', (next) -> @set('emailLower', @get('email')?.toLowerCase()) diff --git a/server/users/user_handler.coffee b/server/users/user_handler.coffee index 065ffdcc9..137270ff6 100644 --- a/server/users/user_handler.coffee +++ b/server/users/user_handler.coffee @@ -375,7 +375,9 @@ UserHandler = class UserHandler extends Handler return @sendNotFoundError res unless remark? @sendSuccess res, remark - countEdits = (model, statKey, done) -> + countEdits = (model, done) -> + statKey = User.statsMapping.edits[model.modelName] + return done(new Error 'Could not resolve statKey for model') unless statKey? User.find {}, (err, users) -> async.eachSeries users, ((user, doneWithUser) -> userID = user.get('_id').toHexString() @@ -409,23 +411,23 @@ UserHandler = class UserHandler extends Handler articleEdits: (done) -> Article = require '../articles/Article' - countEdits Article, 'stats.articleEdits', done + countEdits Article, done levelEdits: (done) -> Level = require '../levels/Level' - countEdits Level, 'stats.levelEdits', done + countEdits Level, done levelComponentEdits: (done) -> LevelComponent = require '../levels/components/LevelComponent' - countEdits LevelComponent, 'stats.levelComponentEdits', done + countEdits LevelComponent, done levelSystemEdits: (done) -> LevelSystem = require '../levels/systems/LevelSystem' - countEdits LevelSystem, 'stats.levelSystemEdits', done + countEdits LevelSystem, done thangTypeEdits: (done) -> ThangType = require '../levels/thangs/ThangType' - countEdits ThangType, 'stats.thangTypeEdits', done + countEdits ThangType, done recalculate: (req, res, statName) -> diff --git a/test/server/functional/user.spec.coffee b/test/server/functional/user.spec.coffee index 36b86fc4c..9477c98af 100644 --- a/test/server/functional/user.spec.coffee +++ b/test/server/functional/user.spec.coffee @@ -272,6 +272,10 @@ describe 'GET /db/user', -> describe 'Statistics', -> LevelSession = require '../../../server/levels/sessions/LevelSession' Article = require '../../../server/articles/Article' + Level = require '../../../server/levels/Level' + LevelSystem = require '../../../server/levels/systems/LevelSystem' + LevelComponent = require '../../../server/levels/components/LevelComponent' + ThangType = require '../../../server/levels/thangs/ThangType' User = require '../../../server/users/User' UserHandler = require '../../../server/users/user_handler' @@ -315,7 +319,7 @@ describe 'Statistics', -> url = getURL('/db/article') loginAdmin (carl) -> - expect(carl.get 'stats.articleEdits').toBeUndefined() + expect(carl.get User.statsMapping.edits.article).toBeUndefined() article.creator = carl.get 'id' # Create major version 1.0 @@ -326,7 +330,7 @@ describe 'Statistics', -> User.findById carl.get('id'), (err, guy) -> expect(err).toBeNull() - expect(guy.get 'stats.articleEdits').toBe 1 + expect(guy.get User.statsMapping.edits.article).toBe 1 # Create minor version 1.1 request.post {uri:url, json: article}, (err, res, body) -> @@ -334,7 +338,7 @@ describe 'Statistics', -> User.findById carl.get('id'), (err, guy) -> expect(err).toBeNull() - expect(guy.get 'stats.articleEdits').toBe 2 + expect(guy.get User.statsMapping.edits.article).toBe 2 done() @@ -342,16 +346,52 @@ describe 'Statistics', -> loginAdmin (carl) -> User.findByIdAndUpdate carl.get('id'), {$unset:'stats.articleEdits': ''}, (err, guy) -> expect(err).toBeNull() - expect(guy.get 'stats.articleEdits').toBeUndefined() + expect(guy.get User.statsMapping.edits.article).toBeUndefined() UserHandler.statHandlers.articleEdits -> User.findById carl.get('id'), (err, guy) -> expect(err).toBeNull() - expect(guy.get 'stats.articleEdits').toBe 2 + expect(guy.get User.statsMapping.edits.article).toBe 2 + done() + + it 'keeps track of level edits', (done) -> + level = new Level + name: "King's Peak 3" + description: 'Climb a mountain.' + permissions: simplePermissions + scripts: [] + thangs: [] + + loginAdmin (carl) -> + expect(carl.get User.statsMapping.edits.level).toBeUndefined() + level.creator = carl.get 'id' + level.save (err) -> + expect(err).toBeNull() + + User.findById carl.get('id'), (err, guy) -> + expect(err).toBeNull() + expect(guy.get 'id').toBe carl.get 'id' + expect(guy.get User.statsMapping.edits.level).toBe 1 + + done() + + it 'recalculates level edits', (done) -> + unittest.getAdmin (jose) -> + User.findByIdAndUpdate jose.get('id'), {$unset:'stats.levelEdits':''}, (err, guy) -> + expect(err).toBeNull() + expect(guy.get User.statsMapping.edits.level).toBeUndefined() + + UserHandler.statHandlers.levelEdits -> + User.findById jose.get('id'), (err, guy) -> + expect(err).toBeNull() + expect(guy.get User.statsMapping.edits.level).toBe 1 done() it 'cleans up', (done) -> - clearModels [LevelSession, Article], (err) -> + clearModels [LevelSession, Article, Level, LevelSystem, LevelComponent, ThangType], (err) -> expect(err).toBeNull() done() + + + diff --git a/test/server/unit/patch.spec.coffee b/test/server/unit/patch.spec.coffee new file mode 100644 index 000000000..04a6cae01 --- /dev/null +++ b/test/server/unit/patch.spec.coffee @@ -0,0 +1,21 @@ +require '../common' + +describe 'schema methods', -> + patch = new Patch + delta: + scripts: 0: i18n: 'aaahw yeahh' + _t: 'a' + + it 'is translation patch', -> + expect(patch.isTranslationPatch()).toBeTruthy() + patch.set 'delta.i18n', undefined + expect(patch.isTranslationPatch()).toBeFalsy() + + it 'is miscellaneous patch', -> + expect(patch.isMiscPatch()).toBeTruthy() + patch.set 'delta.thangs', undefined + expect(patch.isMiscPatch()).toBeFalsy() + + + + From bb1c07570de7a1888dc3766d2c86dfb92ff72d89 Mon Sep 17 00:00:00 2001 From: Ruben Vereecken Date: Sat, 28 Jun 2014 17:04:13 +0200 Subject: [PATCH 15/83] added isTranslationPatch method to patches --- app/lib/deltas.coffee | 6 ++++-- app/views/editor/delta.coffee | 3 ++- server/patches/Patch.coffee | 10 ++++------ test/server/unit/patch.spec.coffee | 30 +++++++++++++++++++++++++++--- 4 files changed, 37 insertions(+), 12 deletions(-) diff --git a/app/lib/deltas.coffee b/app/lib/deltas.coffee index 7c3fa3884..d83191432 100644 --- a/app/lib/deltas.coffee +++ b/app/lib/deltas.coffee @@ -11,7 +11,8 @@ module.exports.expandDelta = (delta, left, schema) -> flattenedDeltas = flattenDelta(delta) (expandFlattenedDelta(fd, left, schema) for fd in flattenedDeltas) -flattenDelta = (delta, dataPath=null, deltaPath=null) -> + +module.exports.flattenDelta = flattenDelta = (delta, dataPath=null, deltaPath=null) -> # takes a single jsondiffpatch delta and returns an array of objects with return [] unless delta dataPath ?= [] @@ -26,7 +27,8 @@ flattenDelta = (delta, dataPath=null, deltaPath=null) -> results = results.concat flattenDelta( childDelta, dataPath.concat([dataIndex]), deltaPath.concat([deltaIndex])) results - pandFlattenedDelta = (delta, left, schema) -> + +expandFlattenedDelta = (delta, left, schema) -> # takes a single flattened delta and converts into an object that can be # easily formatted into something human readable. diff --git a/app/views/editor/delta.coffee b/app/views/editor/delta.coffee index 91a8f6dba..36e663a68 100644 --- a/app/views/editor/delta.coffee +++ b/app/views/editor/delta.coffee @@ -1,6 +1,7 @@ CocoView = require 'views/kinds/CocoView' template = require 'templates/editor/delta' deltasLib = require 'lib/deltas' +window.delta = deltasLib TEXTDIFF_OPTIONS = baseTextName: "Old" @@ -112,4 +113,4 @@ module.exports = class DeltaView extends CocoView delta = @model.getDelta() delta = deltasLib.pruneConflictsFromDelta delta, @conflicts if @conflicts delta = deltasLib.pruneExpandedDeltasFromDelta delta, @skippedDeltas if @skippedDeltas - delta \ No newline at end of file + delta diff --git a/server/patches/Patch.coffee b/server/patches/Patch.coffee index 252d68743..c813e1e69 100644 --- a/server/patches/Patch.coffee +++ b/server/patches/Patch.coffee @@ -47,14 +47,12 @@ PatchSchema.pre 'save', (next) -> document.save (err) -> next(err) PatchSchema.methods.isTranslationPatch = -> - console.log @get 'delta' - expanded = deltas.expandDelta @get('delta') - console.log 'expanded' - _.some expanded, (delta) -> 'i18n' in expanded.dataPath + expanded = deltas.flattenDelta @get('delta') + _.some expanded, (delta) -> 'i18n' in delta.dataPath PatchSchema.methods.isMiscPatch = -> - expanded = deltas.expandDelta @get 'delta' - _.some expanded, (delta) -> 'i18n' not in expanded.dataPath + expanded = deltas.flattenDelta @get('delta') + _.some expanded, (delta) -> 'i18n' not in delta.dataPath # Keep track of when a patch is pending. Accepted patches can be rejected still. PatchSchema.path('status').set (newVal) -> diff --git a/test/server/unit/patch.spec.coffee b/test/server/unit/patch.spec.coffee index 04a6cae01..e7ce13ddd 100644 --- a/test/server/unit/patch.spec.coffee +++ b/test/server/unit/patch.spec.coffee @@ -2,13 +2,37 @@ require '../common' describe 'schema methods', -> patch = new Patch + commitMessage: 'Accept this patch!' + editPath: '/who/knows/yes' + target: + id:null + collection: 'article' delta: - scripts: 0: i18n: 'aaahw yeahh' - _t: 'a' + "scripts": + "0": + "noteChain": + "1": + "sprites": + "0": + "say": + "i18n": + "nl-BE": [ "text": "aaahw yeahh" ] + "_t": "a" + "_t": "a" + "_t": "a" + "thangs": + "111": [ + "components": [ + "config": { + "stateless": true + } + ] + ] + "_t": "a" it 'is translation patch', -> expect(patch.isTranslationPatch()).toBeTruthy() - patch.set 'delta.i18n', undefined + patch.set 'delta.scripts', undefined expect(patch.isTranslationPatch()).toBeFalsy() it 'is miscellaneous patch', -> From a367082cc474318a3acace639968f7996ed177ce Mon Sep 17 00:00:00 2001 From: Ruben Vereecken Date: Sat, 28 Jun 2014 17:44:07 +0200 Subject: [PATCH 16/83] Corrected statistic logic, wrote test case --- app/schemas/models/patch.coffee | 1 + app/views/editor/delta.coffee | 1 - server/patches/Patch.coffee | 6 ++++- server/patches/patch_handler.coffee | 18 +++++++++++---- server/users/User.coffee | 13 +++++++++++ test/server/functional/patch.spec.coffee | 28 ++++++++++++++++++------ 6 files changed, 54 insertions(+), 13 deletions(-) diff --git a/app/schemas/models/patch.coffee b/app/schemas/models/patch.coffee index 2cbb8ad2f..be0d5a35f 100644 --- a/app/schemas/models/patch.coffee +++ b/app/schemas/models/patch.coffee @@ -22,6 +22,7 @@ PatchSchema = c.object({title:'Patch', required:['target', 'delta', 'commitMessa }) _wasPending: type: 'boolean' + _newlyAccepted: type: 'boolean' }) c.extendBasicProperties(PatchSchema, 'patch') diff --git a/app/views/editor/delta.coffee b/app/views/editor/delta.coffee index 36e663a68..c4b9203b4 100644 --- a/app/views/editor/delta.coffee +++ b/app/views/editor/delta.coffee @@ -1,7 +1,6 @@ CocoView = require 'views/kinds/CocoView' template = require 'templates/editor/delta' deltasLib = require 'lib/deltas' -window.delta = deltasLib TEXTDIFF_OPTIONS = baseTextName: "Old" diff --git a/server/patches/Patch.coffee b/server/patches/Patch.coffee index c813e1e69..b20c5d15c 100644 --- a/server/patches/Patch.coffee +++ b/server/patches/Patch.coffee @@ -54,11 +54,15 @@ PatchSchema.methods.isMiscPatch = -> expanded = deltas.flattenDelta @get('delta') _.some expanded, (delta) -> 'i18n' not in delta.dataPath -# Keep track of when a patch is pending. Accepted patches can be rejected still. +# Keep track of when a patch is pending and newly approved. PatchSchema.path('status').set (newVal) -> @set '_wasPending', @status is 'pending' and newVal isnt 'pending' + @set '_newlyAccepted', newVal is 'accepted' and not @get('_newlyAccepted') # Only true on the first accept newVal +PatchSchema.methods.isNewlyAccepted = -> @get('_newlyAccepted') +PatchSchema.methods.wasPending = -> @get '_wasPending' + PatchSchema.pre 'save', (next) -> User = require '../users/User' userID = @get('creator').toHexString() diff --git a/server/patches/patch_handler.coffee b/server/patches/patch_handler.coffee index 981a32af2..b46bcba76 100644 --- a/server/patches/patch_handler.coffee +++ b/server/patches/patch_handler.coffee @@ -49,13 +49,23 @@ PatchHandler = class PatchHandler extends Handler if newStatus is 'withdrawn' return @sendUnauthorizedError(res) unless req.user.get('_id').equals patch.get('creator') - # newly accepted - if newStatus is 'accepted' and patch.get '_wasPending' + patch.set 'status', newStatus + + # Only increment statistics upon very first accept + if patch.isNewlyAccepted() accepter = req.user.get 'id' - User.incrementStat accepter, 'stats.' + submitter = patch.get 'creator' + User.incrementStat accepter, 'stats.patchesAccepted' + # TODO maybe merge these increments together + if patch.isTranslationPatch() + User.incrementStat submitter, 'stats.totalTranslationPatches' + User.incrementStat submitter, User.statsMapping.translations[targetModel.modelName] + if patch.isMiscPatch() + User.incrementStat submitter, 'stats.totalMiscPatches' + User.incrementStat submitter, User.statsMapping.misc[targetModel.modelName] + # these require callbacks - patch.set 'status', newStatus patch.save (err) => log.error err if err? target.update {$pull:{patches:patch.get('_id')}}, {}, -> diff --git a/server/users/User.coffee b/server/users/User.coffee index 2cdbed86f..30a9377e6 100644 --- a/server/users/User.coffee +++ b/server/users/User.coffee @@ -115,6 +115,19 @@ UserSchema.statics.statsMapping = 'level.component': 'stats.levelComponentEdits' 'level.system': 'stats.levelSystemEdits' 'thang.type': 'stats.thangTypeEdits' + translations: + article: 'stats.articleTranslationPatches' + level: 'stats.levelTranslationPatches' + 'level.component': 'stats.levelComponentTranslationPatches' + 'level.system': 'stats.levelSystemTranslationPatches' + 'thang.type': 'stats.thangTypeTranslationPatches' + misc: + article: 'stats.articleMiscPatches' + level: 'stats.levelMiscPatches' + 'level.component': 'stats.levelComponentMiscPatches' + 'level.system': 'stats.levelSystemMiscPatches' + 'thang.type': 'stats.thangTypeMiscPatches' + UserSchema.statics.incrementStat = (id, statName, done, inc=1) -> update = $inc: {} diff --git a/test/server/functional/patch.spec.coffee b/test/server/functional/patch.spec.coffee index 9d0f4265d..beac9f96a 100644 --- a/test/server/functional/patch.spec.coffee +++ b/test/server/functional/patch.spec.coffee @@ -40,7 +40,7 @@ describe '/db/patch', -> expect(body.creator).toBe(joe.id) patches[0] = body done() - + it 'adds a patch to the target document', (done) -> Article.findOne({}).exec (err, article) -> expect(article.toObject().patches[0]).toBeDefined() @@ -110,10 +110,24 @@ describe '/db/patch', -> expect(article.get('status')).toBe 'accepted' done() + it 'keeps track of amount of submitted and accepted patches', (done) -> + loginJoe (joe) -> + User.findById joe.get('id'), (err, guy) -> + expect(err).toBeNull() + expect(guy.get 'stats.patchesSubmitted').toBe 1 + expect(guy.get 'stats.patchesContributed').toBe 1 + expect(guy.get 'stats.totalMiscPatches').toBe 1 + expect(guy.get 'stats.articleMiscPatches').toBe 1 + expect(guy.get 'stats.totalTranslationPatches').toBeUndefined() + done() + it 'does not allow the recipient to withdraw the pull request', (done) -> - statusURL = getURL("/db/patch/#{patches[0]._id}/status") - request.put {uri: statusURL, json: {status:'withdrawn'}}, (err, res, body) -> - expect(res.statusCode).toBe(403) - Patch.findOne({}).exec (err, article) -> - expect(article.get('status')).toBe 'accepted' - done() \ No newline at end of file + loginAdmin -> + statusURL = getURL("/db/patch/#{patches[0]._id}/status") + request.put {uri: statusURL, json: {status:'withdrawn'}}, (err, res, body) -> + expect(res.statusCode).toBe(403) + Patch.findOne({}).exec (err, article) -> + expect(article.get('status')).toBe 'accepted' + done() + + From e904a0a8f777aa1b441465292e00dfabcd1128fa Mon Sep 17 00:00:00 2001 From: Ruben Vereecken Date: Thu, 3 Jul 2014 21:20:06 +0200 Subject: [PATCH 17/83] Achievement recalculation is now covered with tests --- server/achievements/Achievement.coffee | 2 +- .../earned_achievement_handler.coffee | 115 +++++++++--------- server/commons/mapping.coffee | 9 ++ server/levels/sessions/LevelSession.coffee | 2 +- server/plugins/achievements.coffee | 16 +-- .../server/functional/achievement.spec.coffee | 59 ++++++++- 6 files changed, 132 insertions(+), 71 deletions(-) diff --git a/server/achievements/Achievement.coffee b/server/achievements/Achievement.coffee index 9c9fae51f..13712139d 100644 --- a/server/achievements/Achievement.coffee +++ b/server/achievements/Achievement.coffee @@ -64,6 +64,6 @@ AchievementSchema.post 'save', -> @constructor.loadAchievements() AchievementSchema.plugin(plugins.NamedPlugin) AchievementSchema.plugin(plugins.SearchablePlugin, {searchable: ['name']}) -module.exports = Achievement = mongoose.model('Achievement', AchievementSchema) +module.exports = Achievement = mongoose.model('Achievement', AchievementSchema, 'achievements') AchievementSchema.statics.loadAchievements() diff --git a/server/achievements/earned_achievement_handler.coffee b/server/achievements/earned_achievement_handler.coffee index 399106473..740270437 100644 --- a/server/achievements/earned_achievement_handler.coffee +++ b/server/achievements/earned_achievement_handler.coffee @@ -23,14 +23,13 @@ class EarnedAchievementHandler extends Handler EarnedAchievementHandler.recalculate onSuccess @sendAccepted res, {} - # Returns success: boolean - # TODO call onFinished - @recalculate: (callbackOrSlugsOrIDs, onFinished) -> - if _.isArray callbackOrSlugsOrIDs + @recalculate: (callbackOrSlugsOrIDs, callback) -> + if _.isArray callbackOrSlugsOrIDs # slugs or ids achievementSlugs = (thing for thing in callbackOrSlugsOrIDs when not Handler.isID(thing)) achievementIDs = (thing for thing in callbackOrSlugsOrIDs when Handler.isID(thing)) - else - onFinished = callbackOrSlugsOrIDs + else # just a callback + callback = callbackOrSlugsOrIDs + onFinished = -> callback arguments... if callback? filter = {} filter.$or = [ @@ -40,11 +39,11 @@ class EarnedAchievementHandler extends Handler # Fetch all relevant achievements Achievement.find filter, (err, achievements) -> - return log.error err if err? + log.error err if err? # Fetch every single user User.find {}, (err, users) -> - _.each users, (user) -> + async.each users, ((user, doneWithUser) -> # Keep track of a user's already achieved in order to set the notified values correctly userID = user.get('_id').toHexString() @@ -52,64 +51,70 @@ class EarnedAchievementHandler extends Handler EarnedAchievement.find {user: userID}, (err, alreadyEarned) -> alreadyEarnedIDs = [] previousPoints = 0 - _.each alreadyEarned, (earned) -> - if (_.find achievements, (single) -> earned.get('achievement') is single.get('_id').toHexString()) + async.each alreadyEarned, ((earned, doneWithEarned) -> + if (_.find achievements, (single) -> earned.get('achievement') is single.get('_id').toHexString()) # if already earned alreadyEarnedIDs.push earned.get('achievement') previousPoints += earned.get 'earnedPoints' + doneWithEarned() + ), -> # After checking already achieved + # TODO maybe also delete earned? Make sure you don't delete too many - # TODO maybe also delete earned? Make sure you don't delete too many + newTotalPoints = 0 - newTotalPoints = 0 + async.each achievements, ((achievement, doneWithAchievement) -> + isRepeatable = achievement.get('proportionalTo')? + model = mongoose.modelNameByCollection(achievement.get('collection')) + if not model? + log.error "Model with collection '#{achievement.get 'collection'}' doesn't exist." + return doneWithAchievement() - earnedAchievementSaverGenerator = (achievement) -> (callback) -> - isRepeatable = achievement.get('proportionalTo')? - model = mongoose.model(achievement.get('collection')) - if not model? - log.error "Model #{achievement.get 'collection'} doesn't even exist." - return callback() + finalQuery = _.clone achievement.get 'query' + finalQuery.$or = [{}, {}] # Allow both ObjectIDs or hexa string IDs + finalQuery.$or[0][achievement.userField] = userID + finalQuery.$or[1][achievement.userField] = ObjectId userID - model.findOne achievement.query, (err, something) -> - return callback() unless something + model.findOne finalQuery, (err, something) -> + return doneWithAchievement() if _.isEmpty something - log.debug "Matched an achievement: #{achievement.get 'name'}" + log.debug "Matched an achievement: #{achievement.get 'name'} for #{user.get 'name'}" - earned = - user: userID - achievement: achievement._id.toHexString() - achievementName: achievement.get 'name' - notified: achievement._id in alreadyEarnedIDs + earned = + user: userID + achievement: achievement._id.toHexString() + achievementName: achievement.get 'name' + notified: achievement._id in alreadyEarnedIDs - if isRepeatable - earned.achievedAmount = something.get(achievement.get 'proportionalTo') - earned.previouslyAchievedAmount = 0 + if isRepeatable + earned.achievedAmount = something.get(achievement.get 'proportionalTo') + earned.previouslyAchievedAmount = 0 - expFunction = achievement.getExpFunction() - newPoints = expFunction(earned.achievedAmount) * achievement.get('worth') + expFunction = achievement.getExpFunction() + newPoints = expFunction(earned.achievedAmount) * achievement.get('worth') + else + newPoints = achievement.get 'worth' + + earned.earnedPoints = newPoints + newTotalPoints += newPoints + + EarnedAchievement.update {achievement:earned.achievement, user:earned.user}, earned, {upsert: true}, (err) -> + log.error err if err? + doneWithAchievement() + ), saveUserPoints = -> + # In principle it is enough to deduct the old amount of points and add the new amount, + # but just to be entirely safe let's start from 0 in case we're updating all of a user's achievements + return doneWithUser() unless newTotalPoints + log.debug "Matched a total of #{newTotalPoints} new points" + if _.isEmpty filter # Completely clean + log.debug "Setting this user's score to #{newTotalPoints}" + User.update {_id: userID}, {$set: points: newTotalPoints}, {}, (err) -> + log.error err if err? + doneWithUser() else - newPoints = achievement.get 'worth' - - earned.earnedPoints = newPoints - newTotalPoints += newPoints - - EarnedAchievement.update {achievement:earned.achievement, user:earned.user}, earned, {upsert: true}, (err) -> - log.error err if err? - callback() - - saveUserPoints = (callback) -> - # In principle it is enough to deduct the old amount of points and add the new amount, - # but just to be entirely safe let's start from 0 in case we're updating all of a user's achievements - log.debug "Matched a total of #{newTotalPoints} new points" - if _.isEmpty filter # Completely clean - User.update {_id: userID}, {$set: points: newTotalPoints}, {}, (err) -> log.error err if err? - else - log.debug "Incrementing score for these achievements with #{newTotalPoints - previousPoints}" - User.update {_id: userID}, {$inc: points: newTotalPoints - previousPoints}, {}, (err) -> log.error err if err? - - earnedAchievementSavers = (earnedAchievementSaverGenerator(achievement) for achievement in achievements) - earnedAchievementSavers.push saveUserPoints - - # We need to have all these database updates chained so we know the final score - async.series earnedAchievementSavers + log.debug "Incrementing score for these achievements with #{newTotalPoints - previousPoints}" + User.update {_id: userID}, {$inc: points: newTotalPoints - previousPoints}, {}, (err) -> + log.error err if err? + doneWithUser() + ), onFinished module.exports = new EarnedAchievementHandler() diff --git a/server/commons/mapping.coffee b/server/commons/mapping.coffee index 802b33790..c7d3c3bf8 100644 --- a/server/commons/mapping.coffee +++ b/server/commons/mapping.coffee @@ -26,3 +26,12 @@ module.exports.routes = 'routes/queue' 'routes/stacklead' ] + +mongoose = require 'mongoose' +module.exports.modules = modules = # by collection name + 'achievements': 'Achievement' + 'level.sessions': 'level.session' + 'users': 'User' + +mongoose.modelNameByCollection = (collection) -> + mongoose.model modules[collection] if collection of modules diff --git a/server/levels/sessions/LevelSession.coffee b/server/levels/sessions/LevelSession.coffee index 0ed8784c9..1268b4e1c 100644 --- a/server/levels/sessions/LevelSession.coffee +++ b/server/levels/sessions/LevelSession.coffee @@ -42,4 +42,4 @@ LevelSessionSchema.pre 'save', (next) -> delete previous[id] if initd next() -module.exports = LevelSession = mongoose.model('level.session', LevelSessionSchema) +module.exports = LevelSession = mongoose.model('level.session', LevelSessionSchema, 'level.sessions') diff --git a/server/plugins/achievements.coffee b/server/plugins/achievements.coffee index 9e4f85a34..f4e3b2d9f 100644 --- a/server/plugins/achievements.coffee +++ b/server/plugins/achievements.coffee @@ -14,10 +14,12 @@ AchievablePlugin = (schema, options) -> before = {} + # Keep track the document before it's saved schema.post 'init', (doc) -> before[doc.id] = doc.toObject() # TODO check out how many objects go unreleased + # Check if an achievement has been earned schema.post 'save', (doc) -> isNew = not doc.isInit('_id') or not (doc.id of before) originalDocObj = before[doc.id] unless isNew @@ -36,27 +38,25 @@ AchievablePlugin = (schema, options) -> isRepeatable = achievement.get('proportionalTo')? alreadyAchieved = if isNew then false else LocalMongo.matchesQuery originalDocObj, query newlyAchieved = LocalMongo.matchesQuery(docObj, query) - log.debug 'isRepeatable: ' + isRepeatable - log.debug 'alreadyAchieved: ' + alreadyAchieved - log.debug 'newlyAchieved: ' + newlyAchieved + #log.debug 'isRepeatable: ' + isRepeatable + #log.debug 'alreadyAchieved: ' + alreadyAchieved + #log.debug 'newlyAchieved: ' + newlyAchieved userObjectID = doc.get(achievement.get('userField')) userID = if _.isObject userObjectID then userObjectID.toHexString() else userObjectID # Standardize! Use strings, not ObjectId's if newlyAchieved and (not alreadyAchieved or isRepeatable) - earned = { + earned = user: userID achievement: achievement._id.toHexString() achievementName: achievement.get 'name' - } worth = achievement.get('worth') earnedPoints = 0 wrapUp = -> # Update user's experience points - User.update({_id: userID}, {$inc: {points: earnedPoints}}, {}, (err, count) -> + User.update {_id: userID}, {$inc: {points: earnedPoints}}, {}, (err, count) -> log.error err if err? - ) if isRepeatable log.debug 'Upserting repeatable achievement called \'' + (achievement.get 'name') + '\' for ' + userID @@ -81,7 +81,7 @@ AchievablePlugin = (schema, options) -> log.debug 'Creating a new earned achievement called \'' + (achievement.get 'name') + '\' for ' + userID earned.earnedPoints = worth (new EarnedAchievement(earned)).save (err, doc) -> - return log.debug err if err? + return log.error err if err? earnedPoints = worth wrapUp() diff --git a/test/server/functional/achievement.spec.coffee b/test/server/functional/achievement.spec.coffee index 1e815f137..4015085f2 100644 --- a/test/server/functional/achievement.spec.coffee +++ b/test/server/functional/achievement.spec.coffee @@ -13,7 +13,7 @@ repeatable = description: 'Simulated Games.' worth: 1 collection: 'users' - query: "{\"simulatedBy\":{\"$gt\":\"0\"}}" + query: "{\"simulatedBy\":{\"$gt\":0}}" userField: '_id' proportionalTo: 'simulatedBy' @@ -21,7 +21,7 @@ diminishing = name: 'Simulated2' worth: 1.5 collection: 'users' - query: "{\"simulatedBy\":{\"$gt\":\"0\"}}" + query: "{\"simulatedBy\":{\"$gt\":0}}" userField: '_id' proportionalTo: 'simulatedBy' function: @@ -106,7 +106,6 @@ describe 'Achievement', -> expect(body.type).toBeDefined() done() - describe 'Achieving Achievements', -> it 'wait for achievements to be loaded', (done) -> Achievement.loadAchievements (achievements) -> @@ -120,11 +119,10 @@ describe 'Achieving Achievements', -> it 'saving an object that should trigger an unlockable achievement', (done) -> unittest.getNormalJoe (joe) -> - session = new LevelSession( + session = new LevelSession permissions: simplePermissions creator: joe._id level: original: 'dungeon-arena' - ) session.save (err, doc) -> expect(err).toBeNull() @@ -139,6 +137,7 @@ describe 'Achieving Achievements', -> expect(err).toBeNull() expect(docs.length).toBe(1) achievement = docs[0] + expect(achievement).toBeDefined() expect(achievement.get 'achievement').toBe unlockable._id expect(achievement.get 'user').toBe joe._id.toHexString() @@ -185,7 +184,55 @@ describe 'Achieving Achievements', -> done() - it 'cleaning up test: deleting all Achievements and relates', (done) -> +describe 'Recalculate Achievements', -> + EarnedAchievementHandler = require '../../../server/achievements/earned_achievement_handler' + + it 'remove earned achievements', (done) -> + clearModels [EarnedAchievement], (err) -> + expect(err).toBeNull() + EarnedAchievement.find {}, (err, earned) -> + expect(earned.length).toBe 0 + + User.update {}, {$set: {points: 0}}, {multi:true}, (err) -> + expect(err).toBeNull() + done() + + it 'can not be accessed by regular users', (done) -> + loginJoe -> request.post {uri:getURL '/admin/earned_achievement/recalculate'}, (err, res, body) -> + expect(res.statusCode).toBe 403 + done() + + it 'can recalculate a selection of achievements', (done) -> + loginAdmin -> + EarnedAchievementHandler.constructor.recalculate ['dungeon-arena-started'], -> + EarnedAchievement.find {}, (err, earnedAchievements) -> + expect(earnedAchievements.length).toBe 1 + + # Recalculate again, doesn't change a thing + EarnedAchievementHandler.constructor.recalculate ['dungeon-arena-started'], -> + EarnedAchievement.find {}, (err, earnedAchievements) -> + expect(earnedAchievements.length).toBe 1 + + unittest.getNormalJoe (joe) -> + User.findById joe.get('id'), (err, guy) -> + expect(err).toBeNull() + expect(guy.get 'points').toBe unlockable.worth + done() + + it 'can recalculate all achievements', (done) -> + loginAdmin -> + Achievement.count {}, (err, count) -> + expect(count).toBe 3 + EarnedAchievementHandler.constructor.recalculate -> + EarnedAchievement.find {}, (err, earnedAchievements) -> + expect(earnedAchievements.length).toBe 3 + unittest.getNormalJoe (joe) -> + User.findById joe.get('id'), (err, guy) -> + expect(err).toBeNull() + expect(guy.get 'points').toBe unlockable.worth + 2 * repeatable.worth + (Math.log(.5 * (2 + .5)) + 1) * diminishing.worth + done() + + it 'cleaning up test: deleting all Achievements and related', (done) -> clearModels [Achievement, EarnedAchievement, LevelSession], (err) -> expect(err).toBeNull() From 246c39b5588ad4ec78c62ef706aa0713c0e1637b Mon Sep 17 00:00:00 2001 From: Ruben Vereecken Date: Sat, 5 Jul 2014 17:02:48 +0200 Subject: [PATCH 18/83] User stats now go through Mongoose middleware --- server/users/User.coffee | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/server/users/User.coffee b/server/users/User.coffee index 206ca68a3..cc9c3e8fe 100644 --- a/server/users/User.coffee +++ b/server/users/User.coffee @@ -128,12 +128,13 @@ UserSchema.statics.statsMapping = 'level.system': 'stats.levelSystemMiscPatches' 'thang.type': 'stats.thangTypeMiscPatches' -# TODO Ruben make this not use update in order to go through the middleware UserSchema.statics.incrementStat = (id, statName, done, inc=1) -> - update = $inc: {} - update.$inc[statName] = inc - @update {_id:id}, update, {}, (err) -> - done err if done? + @findById id, (err, User) -> + User.incrementStat statName, done, inc=1 + +UserSchema.methods.incrementStat = (statName, done, inc=1) -> + @set statName, (@get(statName) or 0) + inc + @save (err) -> done err if done? UserSchema.pre('save', (next) -> @set('emailLower', @get('email')?.toLowerCase()) From e33bb44ffcab0219b6f0370ba54744071ee00c52 Mon Sep 17 00:00:00 2001 From: Ruben Vereecken Date: Sun, 6 Jul 2014 20:45:27 +0200 Subject: [PATCH 19/83] Start of new user routing and views are there --- app/lib/Router.coffee | 12 ++++++++++++ app/templates/kinds/user.jade | 3 +++ app/templates/user/achievements.jade | 1 + app/views/kinds/UserView.coffee | 19 +++++++++++++++++++ app/views/user/achievements.coffee | 11 +++++++++++ 5 files changed, 46 insertions(+) create mode 100644 app/templates/kinds/user.jade create mode 100644 app/templates/user/achievements.jade create mode 100644 app/views/kinds/UserView.coffee create mode 100644 app/views/user/achievements.coffee diff --git a/app/lib/Router.coffee b/app/lib/Router.coffee index 02d7432b9..d30375c5c 100644 --- a/app/lib/Router.coffee +++ b/app/lib/Router.coffee @@ -16,6 +16,9 @@ module.exports = class CocoRouter extends Backbone.Router # editor views tend to have the same general structure 'editor/:model(/:slug_or_id)(/:subview)': 'editorModelView' + # user views + 'user/:nameOrID(/:subview)': 'userView' + # Direct links 'test/*subpath': go('TestView') 'demo/*subpath': go('DemoView') @@ -46,6 +49,15 @@ module.exports = class CocoRouter extends Backbone.Router view.render() @openView if view then view else @notFoundView() + userView: (nameOrID, subview) -> + modulePrefix = 'views/user/' + suffix = subview or 'home' + ViewClass = @tryToLoadModule modulePrefix + suffix + if ViewClass + view = new ViewClass {}, nameOrID + view.render() + @openView if view then view else @notFoundView() + cache: {} openRoute: (route) -> route = route.split('?')[0] diff --git a/app/templates/kinds/user.jade b/app/templates/kinds/user.jade new file mode 100644 index 000000000..76a3d885e --- /dev/null +++ b/app/templates/kinds/user.jade @@ -0,0 +1,3 @@ +extends /templates/base + +// User pages might have some user page specific header, if not remove this diff --git a/app/templates/user/achievements.jade b/app/templates/user/achievements.jade new file mode 100644 index 000000000..d5adbd6c1 --- /dev/null +++ b/app/templates/user/achievements.jade @@ -0,0 +1 @@ +extends /templates/base diff --git a/app/views/kinds/UserView.coffee b/app/views/kinds/UserView.coffee new file mode 100644 index 000000000..d0a28de37 --- /dev/null +++ b/app/views/kinds/UserView.coffee @@ -0,0 +1,19 @@ +RootView = require 'views/kinds/RootView' +template = require 'templates/kinds/user' +User = require 'models/User' + +module.exports = class UserView extends RootView + template: template + className: 'user-view' + + constructor: (options, nameOrID) -> + # TODO Ruben Assume ID for now + user = new User nameOrID + user.fetch + success: -> + console.log 'helabaaa' + error: (model, response, options) -> + console.log response + console.log options + + super options diff --git a/app/views/user/achievements.coffee b/app/views/user/achievements.coffee new file mode 100644 index 000000000..8eb188de1 --- /dev/null +++ b/app/views/user/achievements.coffee @@ -0,0 +1,11 @@ +UserView = require 'views/kinds/UserView' +template = require 'templates/user/achievements' +{me} = require 'lib/auth' + +module.exports = class UserAchievementsViewe extends UserView + id: 'user-achievements-view' + template: template + + constructor: (options, @nameOrID) -> + super options, @nameOrID + From 10735867a429f76da9e77424cfb9b3fdc7586b57 Mon Sep 17 00:00:00 2001 From: Ruben Vereecken Date: Sun, 6 Jul 2014 21:10:28 +0200 Subject: [PATCH 20/83] Got some basic hierarchy set up for user view and template inheritance --- app/templates/kinds/user.jade | 9 +++++++++ app/templates/user/achievements.jade | 2 +- app/views/kinds/UserView.coffee | 25 +++++++++++++++---------- 3 files changed, 25 insertions(+), 11 deletions(-) diff --git a/app/templates/kinds/user.jade b/app/templates/kinds/user.jade index 76a3d885e..0ce93689e 100644 --- a/app/templates/kinds/user.jade +++ b/app/templates/kinds/user.jade @@ -1,3 +1,12 @@ extends /templates/base // User pages might have some user page specific header, if not remove this +block content + div + ol.breadcrumb + li + - var userName = user.get('name'); + a(href="/user/#{user.id}") #{userName} + li.active + | #{currentUserView} + diff --git a/app/templates/user/achievements.jade b/app/templates/user/achievements.jade index d5adbd6c1..93f106d1d 100644 --- a/app/templates/user/achievements.jade +++ b/app/templates/user/achievements.jade @@ -1 +1 @@ -extends /templates/base +extends /templates/kinds/user diff --git a/app/views/kinds/UserView.coffee b/app/views/kinds/UserView.coffee index d0a28de37..495d08d30 100644 --- a/app/views/kinds/UserView.coffee +++ b/app/views/kinds/UserView.coffee @@ -6,14 +6,19 @@ module.exports = class UserView extends RootView template: template className: 'user-view' - constructor: (options, nameOrID) -> - # TODO Ruben Assume ID for now - user = new User nameOrID - user.fetch - success: -> - console.log 'helabaaa' - error: (model, response, options) -> - console.log response - console.log options - + constructor: (options, @nameOrID) -> super options + + # TODO Ruben Assume ID for now + @user = @supermodel.loadModel(new User(_id: nameOrID), 'user').model + + onLoaded: -> + @render() + + getRenderData: -> + context = super() + context.currentUserView = 'Achievements' + context.user = @user + context + + isMe: -> @nameOrID is me.id From 1b9c8b0066f7d4bbb3abbff3dbcebb694e3187f6 Mon Sep 17 00:00:00 2001 From: Ruben Vereecken Date: Sun, 6 Jul 2014 22:48:33 +0200 Subject: [PATCH 21/83] Got users and achievements to load in a neat way --- app/collections/AchievementCollection.coffee | 7 +++++++ app/models/SuperModel.coffee | 3 --- app/views/kinds/UserView.coffee | 14 +++++++++----- app/views/user/achievements.coffee | 11 +++++++++-- 4 files changed, 25 insertions(+), 10 deletions(-) create mode 100644 app/collections/AchievementCollection.coffee diff --git a/app/collections/AchievementCollection.coffee b/app/collections/AchievementCollection.coffee new file mode 100644 index 000000000..511f5c16e --- /dev/null +++ b/app/collections/AchievementCollection.coffee @@ -0,0 +1,7 @@ +CocoCollection = require 'collections/CocoCollection' + +module.exports = class AchievementCollection extends CocoCollection + + initialize: (me = require('lib/auth').me) -> + @url = "/db/user/#{me.id}/achievements" + diff --git a/app/models/SuperModel.coffee b/app/models/SuperModel.coffee index c366d8285..44449f3bf 100644 --- a/app/models/SuperModel.coffee +++ b/app/models/SuperModel.coffee @@ -58,9 +58,6 @@ module.exports = class SuperModel extends Backbone.Model return res else @addCollection collection - @listenToOnce collection, 'sync', (c) -> - console.debug 'Registering collection', url - @registerCollection c res = @addModelResource(collection, name, fetchOptions, value) res.load() if not (res.isLoading or res.isLoaded) return res diff --git a/app/views/kinds/UserView.coffee b/app/views/kinds/UserView.coffee index 495d08d30..9a22e20e1 100644 --- a/app/views/kinds/UserView.coffee +++ b/app/views/kinds/UserView.coffee @@ -9,11 +9,13 @@ module.exports = class UserView extends RootView constructor: (options, @nameOrID) -> super options - # TODO Ruben Assume ID for now - @user = @supermodel.loadModel(new User(_id: nameOrID), 'user').model + @listenTo @, 'userLoaded', @onUserLoaded - onLoaded: -> - @render() + # TODO Ruben Assume ID for now + @user = User.getByID(@nameOrID, {}, true) # Force fetching a user isn't really the clean way to go + @user.fetch + success: => + @trigger 'userLoaded', @user getRenderData: -> context = super() @@ -21,4 +23,6 @@ module.exports = class UserView extends RootView context.user = @user context - isMe: -> @nameOrID is me.id + isMe: -> @nameOrID is me.id + + onUserLoaded: -> diff --git a/app/views/user/achievements.coffee b/app/views/user/achievements.coffee index 8eb188de1..eef3ab6a0 100644 --- a/app/views/user/achievements.coffee +++ b/app/views/user/achievements.coffee @@ -1,11 +1,18 @@ UserView = require 'views/kinds/UserView' template = require 'templates/user/achievements' {me} = require 'lib/auth' +Achievement = require 'models/Achievement' +AchievementCollection = require 'collections/AchievementCollection' module.exports = class UserAchievementsViewe extends UserView id: 'user-achievements-view' template: template - constructor: (options, @nameOrID) -> - super options, @nameOrID + events: + 'userLoaded': 'onUserLoaded' + constructor: (options, nameOrID) -> + super options, nameOrID + + onUserLoaded: (user) -> + @achievements = @supermodel.loadCollection(new AchievementCollection(@user), 'achievements').model From ec78f95da1f20261b62cd663239de035f7e0cfb1 Mon Sep 17 00:00:00 2001 From: Ruben Vereecken Date: Sun, 6 Jul 2014 23:16:00 +0200 Subject: [PATCH 22/83] Prepared things for Scott --- app/models/SuperModel.coffee | 5 +++++ app/views/user/achievements.coffee | 2 +- test/app/fixtures/achievements.coffee | 8 ++++++++ test/demo/views/achievement/AchievementGet.demo.coffee | 10 +--------- .../demo/views/achievement/UserAchievement.demo.coffee | 7 +++++++ 5 files changed, 22 insertions(+), 10 deletions(-) create mode 100644 test/app/fixtures/achievements.coffee create mode 100644 test/demo/views/achievement/UserAchievement.demo.coffee diff --git a/app/models/SuperModel.coffee b/app/models/SuperModel.coffee index 44449f3bf..ed2e01689 100644 --- a/app/models/SuperModel.coffee +++ b/app/models/SuperModel.coffee @@ -58,6 +58,11 @@ module.exports = class SuperModel extends Backbone.Model return res else @addCollection collection + @listenTo collection, 'sync', (c) -> + console.debug 'Registering collection', url + console.debug c + console.debug collection + @registerCollection c res = @addModelResource(collection, name, fetchOptions, value) res.load() if not (res.isLoading or res.isLoaded) return res diff --git a/app/views/user/achievements.coffee b/app/views/user/achievements.coffee index eef3ab6a0..cbda4d29f 100644 --- a/app/views/user/achievements.coffee +++ b/app/views/user/achievements.coffee @@ -4,7 +4,7 @@ template = require 'templates/user/achievements' Achievement = require 'models/Achievement' AchievementCollection = require 'collections/AchievementCollection' -module.exports = class UserAchievementsViewe extends UserView +module.exports = class UserAchievementsView extends UserView id: 'user-achievements-view' template: template diff --git a/test/app/fixtures/achievements.coffee b/test/app/fixtures/achievements.coffee new file mode 100644 index 000000000..c02b3b664 --- /dev/null +++ b/test/app/fixtures/achievements.coffee @@ -0,0 +1,8 @@ +module.exports.DungeonArenaStarted = + name: 'Dungeon Arena Started' + description: 'Started playing Dungeon Arena. ' + worth: 3 + collection: 'level.session' + query: "{\"level.original\":\"dungeon-arena\"}" + userField: 'creator' + diff --git a/test/demo/views/achievement/AchievementGet.demo.coffee b/test/demo/views/achievement/AchievementGet.demo.coffee index 9fcdd9539..7b22fdf77 100644 --- a/test/demo/views/achievement/AchievementGet.demo.coffee +++ b/test/demo/views/achievement/AchievementGet.demo.coffee @@ -4,19 +4,11 @@ utils = require 'lib/utils' Achievement = require 'models/Achievement' EarnedAchievement = require 'models/EarnedAchievement' -class MockServer - module.exports = -> me.set 'points', 48 - unlockableObj = - name: 'Dungeon Arena Started' - description: 'Started playing Dungeon Arena. ' - worth: 3 - collection: 'level.session' - query: "{\"level.original\":\"dungeon-arena\"}" - userField: 'creator' + unlockableObj = fixtures.DungeonArenaStarted earnedUnlockableObj = earnedPoints: 3 diff --git a/test/demo/views/achievement/UserAchievement.demo.coffee b/test/demo/views/achievement/UserAchievement.demo.coffee new file mode 100644 index 000000000..f9c19b15e --- /dev/null +++ b/test/demo/views/achievement/UserAchievement.demo.coffee @@ -0,0 +1,7 @@ +Achievement = require 'models/Achievement' +Achievements = require 'collections/AchievementCollection' +EarnedAchievement = require 'models/EarnedAchievement' +EarnedAchievementCollection = require 'collections/EarnedAchievementCollection' + +fixtures = require 'test/app/fixtures/achievements' + From 75b738f1263d971cf4413666c9aa06c77ea74f31 Mon Sep 17 00:00:00 2001 From: Ruben Vereecken Date: Mon, 7 Jul 2014 12:03:48 +0200 Subject: [PATCH 23/83] User loading now have async callbacks --- app/models/User.coffee | 10 +++++++--- app/views/kinds/UserView.coffee | 3 +-- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/app/models/User.coffee b/app/models/User.coffee index a0df41078..700379d82 100644 --- a/app/models/User.coffee +++ b/app/models/User.coffee @@ -33,9 +33,12 @@ module.exports = class User extends CocoModel return "/file/#{photoURL}#{prefix}s=#{size}" return "/db/user/#{@id}/avatar?s=#{size}" - @getByID = (id, properties, force) -> + # Callbacks can be either 'success' or 'error' + @getByID = (id, properties, force, callbacks={}) -> {me} = require 'lib/auth' - return me if me.id is id + if me.id is id + callbacks.success me if callbacks.success? + return me user = cache[id] or new module.exports({_id: id}) if force or not cache[id] user.loading = true @@ -43,7 +46,8 @@ module.exports = class User extends CocoModel success: -> user.loading = false Backbone.Mediator.publish('user:fetched') - #user.trigger 'sync' # needed? + callbacks.success arguments... if callbacks.success? + error: -> callbacks.error arguments... if callbacks.error? ) cache[id] = user user diff --git a/app/views/kinds/UserView.coffee b/app/views/kinds/UserView.coffee index 9a22e20e1..f89ce5750 100644 --- a/app/views/kinds/UserView.coffee +++ b/app/views/kinds/UserView.coffee @@ -12,8 +12,7 @@ module.exports = class UserView extends RootView @listenTo @, 'userLoaded', @onUserLoaded # TODO Ruben Assume ID for now - @user = User.getByID(@nameOrID, {}, true) # Force fetching a user isn't really the clean way to go - @user.fetch + @user = User.getByID @nameOrID, {}, true, success: => @trigger 'userLoaded', @user From b5702bc3141badc1955a11330cf32f27734020bc Mon Sep 17 00:00:00 2001 From: Ruben Vereecken Date: Sun, 6 Jul 2014 23:38:57 +0200 Subject: [PATCH 24/83] Added simple demo page --- app/collections/AchievementCollection.coffee | 5 +---- app/collections/EarnedAchievementCollection.coffee | 7 +++++++ app/views/user/achievements.coffee | 2 ++ test/{app => demo}/fixtures/achievements.coffee | 0 test/demo/views/achievement/AchievementGet.demo.coffee | 2 +- ...chievement.demo.coffee => UserAchievements.demo.coffee} | 7 ++++++- 6 files changed, 17 insertions(+), 6 deletions(-) create mode 100644 app/collections/EarnedAchievementCollection.coffee rename test/{app => demo}/fixtures/achievements.coffee (100%) rename test/demo/views/achievement/{UserAchievement.demo.coffee => UserAchievements.demo.coffee} (55%) diff --git a/app/collections/AchievementCollection.coffee b/app/collections/AchievementCollection.coffee index 511f5c16e..30056c482 100644 --- a/app/collections/AchievementCollection.coffee +++ b/app/collections/AchievementCollection.coffee @@ -1,7 +1,4 @@ CocoCollection = require 'collections/CocoCollection' module.exports = class AchievementCollection extends CocoCollection - - initialize: (me = require('lib/auth').me) -> - @url = "/db/user/#{me.id}/achievements" - + url: '/db/achievement' diff --git a/app/collections/EarnedAchievementCollection.coffee b/app/collections/EarnedAchievementCollection.coffee new file mode 100644 index 000000000..a6e050eb4 --- /dev/null +++ b/app/collections/EarnedAchievementCollection.coffee @@ -0,0 +1,7 @@ +CocoCollection = require 'collections/CocoCollection' + +module.exports = class EarnedAchievementCollection extends CocoCollection + + initialize: (me = require('lib/auth').me) -> + @url = "/db/user/#{me.id}/achievements" + diff --git a/app/views/user/achievements.coffee b/app/views/user/achievements.coffee index cbda4d29f..351cf9e93 100644 --- a/app/views/user/achievements.coffee +++ b/app/views/user/achievements.coffee @@ -2,7 +2,9 @@ UserView = require 'views/kinds/UserView' template = require 'templates/user/achievements' {me} = require 'lib/auth' Achievement = require 'models/Achievement' +EarnedAchievement = require 'models/EarnedAchievement' AchievementCollection = require 'collections/AchievementCollection' +EarnedAchievementCollection = require 'collections/EarnedAchievementCollection' module.exports = class UserAchievementsView extends UserView id: 'user-achievements-view' diff --git a/test/app/fixtures/achievements.coffee b/test/demo/fixtures/achievements.coffee similarity index 100% rename from test/app/fixtures/achievements.coffee rename to test/demo/fixtures/achievements.coffee diff --git a/test/demo/views/achievement/AchievementGet.demo.coffee b/test/demo/views/achievement/AchievementGet.demo.coffee index 7b22fdf77..c09a42fe2 100644 --- a/test/demo/views/achievement/AchievementGet.demo.coffee +++ b/test/demo/views/achievement/AchievementGet.demo.coffee @@ -3,7 +3,7 @@ RootView = require 'views/kinds/RootView' utils = require 'lib/utils' Achievement = require 'models/Achievement' EarnedAchievement = require 'models/EarnedAchievement' - +fixtures = require '../../fixtures/achievements' module.exports = -> me.set 'points', 48 diff --git a/test/demo/views/achievement/UserAchievement.demo.coffee b/test/demo/views/achievement/UserAchievements.demo.coffee similarity index 55% rename from test/demo/views/achievement/UserAchievement.demo.coffee rename to test/demo/views/achievement/UserAchievements.demo.coffee index f9c19b15e..f3b5dcfd7 100644 --- a/test/demo/views/achievement/UserAchievement.demo.coffee +++ b/test/demo/views/achievement/UserAchievements.demo.coffee @@ -1,7 +1,12 @@ Achievement = require 'models/Achievement' Achievements = require 'collections/AchievementCollection' +UserAchievementsView = require 'views/user/achievements' EarnedAchievement = require 'models/EarnedAchievement' EarnedAchievementCollection = require 'collections/EarnedAchievementCollection' -fixtures = require 'test/app/fixtures/achievements' +fixtures = require '../../fixtures/achievements' + +module.exports = -> + view = new UserAchievementsView {}, me.get '_id' + view.render() From 9055612b241e720ff38c9757f12bc29d8c81f871 Mon Sep 17 00:00:00 2001 From: Ruben Vereecken Date: Mon, 7 Jul 2014 12:44:44 +0200 Subject: [PATCH 25/83] Fixtures set up, demo start --- app/collections/AchievementCollection.coffee | 2 + .../EarnedAchievementCollection.coffee | 2 + app/models/SuperModel.coffee | 2 - app/views/editor/achievement/edit.coffee | 2 +- app/views/kinds/UserView.coffee | 5 +++ app/views/user/achievements.coffee | 4 +- test/demo/fixtures/achievements.coffee | 43 ++++++++++++++++++- .../achievement/UserAchievements.demo.coffee | 6 +++ 8 files changed, 61 insertions(+), 5 deletions(-) diff --git a/app/collections/AchievementCollection.coffee b/app/collections/AchievementCollection.coffee index 30056c482..d3bbe0343 100644 --- a/app/collections/AchievementCollection.coffee +++ b/app/collections/AchievementCollection.coffee @@ -1,4 +1,6 @@ CocoCollection = require 'collections/CocoCollection' +Achievement = require 'models/Achievement' module.exports = class AchievementCollection extends CocoCollection url: '/db/achievement' + model: Achievement diff --git a/app/collections/EarnedAchievementCollection.coffee b/app/collections/EarnedAchievementCollection.coffee index a6e050eb4..527f1459e 100644 --- a/app/collections/EarnedAchievementCollection.coffee +++ b/app/collections/EarnedAchievementCollection.coffee @@ -1,6 +1,8 @@ CocoCollection = require 'collections/CocoCollection' +EarnedAchievement = require 'models/EarnedAchievement' module.exports = class EarnedAchievementCollection extends CocoCollection + model: EarnedAchievement initialize: (me = require('lib/auth').me) -> @url = "/db/user/#{me.id}/achievements" diff --git a/app/models/SuperModel.coffee b/app/models/SuperModel.coffee index ed2e01689..3eb491b5c 100644 --- a/app/models/SuperModel.coffee +++ b/app/models/SuperModel.coffee @@ -60,8 +60,6 @@ module.exports = class SuperModel extends Backbone.Model @addCollection collection @listenTo collection, 'sync', (c) -> console.debug 'Registering collection', url - console.debug c - console.debug collection @registerCollection c res = @addModelResource(collection, name, fetchOptions, value) res.load() if not (res.isLoading or res.isLoaded) diff --git a/app/views/editor/achievement/edit.coffee b/app/views/editor/achievement/edit.coffee index 3e6a3d10e..d31f8e026 100644 --- a/app/views/editor/achievement/edit.coffee +++ b/app/views/editor/achievement/edit.coffee @@ -111,7 +111,7 @@ module.exports = class AchievementEditView extends View recalculateAchievement: => $.ajax - data: JSON.stringify(achievements: [@achievement.get('slug') or @achievement.get('_id')]) + data: JSON.stringify(earnedAchievements: [@achievement.get('slug') or @achievement.get('_id')]) success: (data, status, jqXHR) -> noty timeout: 5000 diff --git a/app/views/kinds/UserView.coffee b/app/views/kinds/UserView.coffee index f89ce5750..ada1657f2 100644 --- a/app/views/kinds/UserView.coffee +++ b/app/views/kinds/UserView.coffee @@ -25,3 +25,8 @@ module.exports = class UserView extends RootView isMe: -> @nameOrID is me.id onUserLoaded: -> + console.log 'onUserLoaded' + + onLoaded: -> + console.log 'onLoaded' + super() diff --git a/app/views/user/achievements.coffee b/app/views/user/achievements.coffee index 351cf9e93..f456f9651 100644 --- a/app/views/user/achievements.coffee +++ b/app/views/user/achievements.coffee @@ -17,4 +17,6 @@ module.exports = class UserAchievementsView extends UserView super options, nameOrID onUserLoaded: (user) -> - @achievements = @supermodel.loadCollection(new AchievementCollection(@user), 'achievements').model + super user + @earnedAchievements = @supermodel.loadCollection(new EarnedAchievementCollection(@user), 'achievements').model + diff --git a/test/demo/fixtures/achievements.coffee b/test/demo/fixtures/achievements.coffee index c02b3b664..198792668 100644 --- a/test/demo/fixtures/achievements.coffee +++ b/test/demo/fixtures/achievements.coffee @@ -1,4 +1,8 @@ -module.exports.DungeonArenaStarted = +now = new Date() +oneDayBefore = (new Date now).setDate(now.getDate() - 1) + +module.exports.DungeonArenaStarted = DungeonArenaStarted = + _id: '53ba76249259823746b6b481' name: 'Dungeon Arena Started' description: 'Started playing Dungeon Arena. ' worth: 3 @@ -6,3 +10,40 @@ module.exports.DungeonArenaStarted = query: "{\"level.original\":\"dungeon-arena\"}" userField: 'creator' +module.exports.Simulated = Simulated = + _id: '53ba76249259823746b6b482' + name: 'Simulated' + description: 'Simulated Games.' + worth: 1 + collection: 'users' + query: "{\"simulatedBy\":{\"$gt\":0}}" + userField: '_id' + proportionalTo: 'simulatedBy' + +module.exports.DungeonArenaStartedEarned = DungeonArenaStartedEarned = + user: '' + achievement: DungeonArenaStarted._id + collection: DungeonArenaStarted.collection + achievementName: DungeonArenaStarted.name + created: now + changed: now + achievedAmount: 1 + earnedPoints: 3 + previouslyAchievedAmount: 0 + notified: true + +module.exports.SimulatedEarned = SimulatedEarned = + user: '' + achievement: Simulated._id + collection: Simulated.collection + achievementName: Simulated.name + created: now + changed: now + achievedAmount: 6 + earnedPoints: 6 + previouslyAchievedAmount: 5 + notified: true + + +module.exports.achievements = [DungeonArenaStarted, Simulated] +module.exports.earnedAchievements = [DungeonArenaStartedEarned, SimulatedEarned] diff --git a/test/demo/views/achievement/UserAchievements.demo.coffee b/test/demo/views/achievement/UserAchievements.demo.coffee index f3b5dcfd7..c42463965 100644 --- a/test/demo/views/achievement/UserAchievements.demo.coffee +++ b/test/demo/views/achievement/UserAchievements.demo.coffee @@ -9,4 +9,10 @@ fixtures = require '../../fixtures/achievements' module.exports = -> view = new UserAchievementsView {}, me.get '_id' + + request = jasmine.Ajax.requests.mostRecent() + request.response + status: 200 + responseText: JSON.stringify fixtures.earnedAchievements + view.render() From ea3d1fee744eca460cd94de8f7a30838a8236a51 Mon Sep 17 00:00:00 2001 From: Ruben Vereecken Date: Mon, 7 Jul 2014 15:03:28 +0200 Subject: [PATCH 26/83] Starting on achievement overview style --- app/application.coffee | 2 +- app/models/SuperModel.coffee | 1 + app/styles/achievements.sass | 100 ++++++++++++++++++ app/styles/notify.sass | 100 ------------------ app/templates/achievement_notify.jade | 23 ++-- app/templates/user/achievements.jade | 9 ++ app/views/user/achievements.coffee | 8 +- server/achievements/Achievement.coffee | 12 +-- .../earned_achievement_handler.coffee | 2 +- test/demo/fixtures/achievements.coffee | 2 + .../achievement/UserAchievements.demo.coffee | 19 +++- 11 files changed, 155 insertions(+), 123 deletions(-) create mode 100644 app/styles/achievements.sass delete mode 100644 app/styles/notify.sass diff --git a/app/application.coffee b/app/application.coffee index 8d71a3249..fcfac2a90 100644 --- a/app/application.coffee +++ b/app/application.coffee @@ -40,7 +40,7 @@ Application = initialize: -> @facebookHandler = new FacebookHandler() @gplusHandler = new GPlusHandler() $(document).bind 'keydown', preventBackspace - $.notify.addStyle 'achievement', html: $(AchievementNotify()) + $.notify.addStyle 'achievement', html: $(AchievementNotify popup:true) @linkedinHandler = new LinkedInHandler() preload(COMMON_FILES) $.i18n.init { diff --git a/app/models/SuperModel.coffee b/app/models/SuperModel.coffee index 3eb491b5c..8c1f68e4d 100644 --- a/app/models/SuperModel.coffee +++ b/app/models/SuperModel.coffee @@ -57,6 +57,7 @@ module.exports = class SuperModel extends Backbone.Model res.markLoading() return res else + console.debug 'adding collection', collection @addCollection collection @listenTo collection, 'sync', (c) -> console.debug 'Registering collection', url diff --git a/app/styles/achievements.sass b/app/styles/achievements.sass new file mode 100644 index 000000000..0d9123634 --- /dev/null +++ b/app/styles/achievements.sass @@ -0,0 +1,100 @@ +.notifyjs-achievement-base + cursor: auto + white-space: nowrap + text-overflow: ellipsis + padding: 20px 0px + +.achievement-body + .achievement-icon + position: absolute + width: 200px + height: 200px + left: -140px + top: 0px + + .achievement-image + width: 100% + height: 100% + img + position: absolute + margin: auto + top: 0 + left: 0 + right: 0 + bottom: 0 + + .achievement-border-wood + background: url("/images/achievements/border_wood.png") no-repeat + background-size: 100% 100% + + .achievement-border-silver + background: url("/images/achievements/border_silver.png") no-repeat + background-size: 100% 100% + + .achievement-content + background-image: url("/images/achievements/reward_background2.png") + background-size: 100% 100% + width: 450px + height: 160px + text-align: center + padding: 24px 30px 20px 60px + overflow: hidden + + + .achievement-title + font-family: Bangers + font-size: 28px + padding-left: -50px + overflow: hidden + + .achievement-description + white-space: initial + font-size: 15px + line-height: 1.3em + overflow: hidden + max-height: 2.6em + margin-top: auto + margin-bottom: 0px !important + + .achievement-progress + padding: 8px 0px 0px 0px + + .achievement-message + font-family: Bangers + font-size: 18px + &:empty + display: none + + + + .progress-wrapper + position: absolute + padding-right: 30px + bottom: 0px + + .achievement-level + float: left + font-size: 15px + + .progress-bar-wrapper + padding-left: 30px + width: 100% + .progress + border-radius: 20px + + .progress-bar-border + padding-left: 30px + position: relative + top: -44px + + .progress-bar-border img + width: 100% + /*.earned-exp + padding-left: 5px + font-family: Bangers + font-size: 16px + float: right + */ + +.progress-bar-white + background-color: white diff --git a/app/styles/notify.sass b/app/styles/notify.sass deleted file mode 100644 index 2d888b351..000000000 --- a/app/styles/notify.sass +++ /dev/null @@ -1,100 +0,0 @@ -.notifyjs-achievement-base - cursor: auto - white-space: nowrap - text-overflow: ellipsis - padding: 20px 0px - - .achievement-body - .achievement-icon - position: absolute - width: 200px - height: 200px - left: -140px - top: 0px - - .achievement-image - width: 100% - height: 100% - img - position: absolute - margin: auto - top: 0 - left: 0 - right: 0 - bottom: 0 - width: 72% - - .achievement-border-wood - background: url("/images/achievements/border_wood.png") no-repeat - background-size: 100% 100% - - .achievement-border-silver - background: url("/images/achievements/border_silver.png") no-repeat - background-size: 100% 100% - - .achievement-content - background-image: url("/images/achievements/reward_background2.png") - background-size: 100% 100% - width: 450px - height: 160px - text-align: center - padding: 24px 30px 20px 60px - overflow: hidden - - - .achievement-title - font-family: Bangers - font-size: 28px - padding-left: -50px - overflow: hidden - - .achievement-description - white-space: initial - font-size: 15px - line-height: 1.3em - overflow: hidden - max-height: 2.6em - margin-top: auto - margin-bottom: 0px !important - - .achievement-progress - padding: 8px 0px 0px 0px - - .achievement-message - font-family: Bangers - font-size: 18px - &:empty - display: none - - - - .progress-wrapper - position: absolute - padding-right: 30px - bottom: 0px - - .achievement-level - float: left - font-size: 15px - - .progress-bar-wrapper - padding-left: 30px - width: 100% - .progress - border-radius: 20px - - .progress-bar-border - padding-left: 30px - position: relative - top: -44px - - .progress-bar-border img - width: 100% - /*.earned-exp - padding-left: 5px - font-family: Bangers - font-size: 16px - float: right - */ -.progress-bar-white - background-color: white diff --git a/app/templates/achievement_notify.jade b/app/templates/achievement_notify.jade index a384e4eed..02c86ad78 100644 --- a/app/templates/achievement_notify.jade +++ b/app/templates/achievement_notify.jade @@ -2,15 +2,18 @@ div .clearfix.achievement-body .achievement-icon.achievement-border-silver .achievement-image(data-notify-html="image") + if imgURL + img(src=imgURL) .achievement-content - .achievement-title(data-notify-html="title") - p.achievement-description(data-notify-html="description") + .achievement-title(data-notify-html="title") #{title} + p.achievement-description(data-notify-html="description") #{description} - .achievement-progress - .achievement-message(data-notify-html="message") - .progress-wrapper - //.earned-exp(data-notify-html="earnedExp") - span.achievement-level.badge(data-notify-html="level") - .progress-bar-wrapper(data-notify-html="progressBar") - .progress-bar-border(data-notify-html="barBorder") - //img(src='/images/achievements/bar_border.png' width='100%') + if popup + .achievement-progress + .achievement-message(data-notify-html="message") + .progress-wrapper + //.earned-exp(data-notify-html="earnedExp") + span.achievement-level.badge(data-notify-html="level") + .progress-bar-wrapper(data-notify-html="progressBar") + .progress-bar-border(data-notify-html="barBorder") + //img(src='/images/achievements/bar_border.png' width='100%') diff --git a/app/templates/user/achievements.jade b/app/templates/user/achievements.jade index 93f106d1d..ac312a0e5 100644 --- a/app/templates/user/achievements.jade +++ b/app/templates/user/achievements.jade @@ -1 +1,10 @@ extends /templates/kinds/user + +block append content + .row + each achievement, index in achievements + - var title = achievement.get('name'); + - var description = achievement.get('description'); + - var imgURL = achievement.get('icon'); + .col-sm-6.col-xs-12 + include ../achievement_notify diff --git a/app/views/user/achievements.coffee b/app/views/user/achievements.coffee index f456f9651..6d05fccca 100644 --- a/app/views/user/achievements.coffee +++ b/app/views/user/achievements.coffee @@ -18,5 +18,11 @@ module.exports = class UserAchievementsView extends UserView onUserLoaded: (user) -> super user - @earnedAchievements = @supermodel.loadCollection(new EarnedAchievementCollection(@user), 'achievements').model + @achievements = @supermodel.loadCollection(new AchievementCollection, 'achievements').model + @earnedAchievements = @supermodel.loadCollection(new EarnedAchievementCollection(@user), 'earnedAchievements').model + getRenderData: -> + context = super() + context.achievements = @achievements.models + context.earnedAchievements = @earnedAchievements.models + context diff --git a/server/achievements/Achievement.coffee b/server/achievements/Achievement.coffee index 845587405..ab248d90b 100644 --- a/server/achievements/Achievement.coffee +++ b/server/achievements/Achievement.coffee @@ -30,7 +30,7 @@ AchievementSchema.methods.getExpFunction = -> return utils.functionCreators[kind](parameters) if kind of utils.functionCreators AchievementSchema.statics.jsonschema = jsonschema -AchievementSchema.statics.achievements = {} +AchievementSchema.statics.earnedAchievements = {} # Reloads all achievements into memory. # TODO might want to tweak this to only load new achievements @@ -41,15 +41,15 @@ AchievementSchema.statics.loadAchievements = (done) -> query.exec (err, docs) -> _.each docs, (achievement) -> category = achievement.get 'collection' - AchievementSchema.statics.achievements[category] = [] unless category of AchievementSchema.statics.achievements - AchievementSchema.statics.achievements[category].push achievement - done(AchievementSchema.statics.achievements) if done? + AchievementSchema.statics.earnedAchievements[category] = [] unless category of AchievementSchema.statics.earnedAchievements + AchievementSchema.statics.earnedAchievements[category].push achievement + done(AchievementSchema.statics.earnedAchievements) if done? AchievementSchema.statics.getLoadedAchievements = -> - AchievementSchema.statics.achievements + AchievementSchema.statics.earnedAchievements AchievementSchema.statics.resetAchievements = -> - delete AchievementSchema.statics.achievements[category] for category of AchievementSchema.statics.achievements + delete AchievementSchema.statics.earnedAchievements[category] for category of AchievementSchema.statics.earnedAchievements # Queries are stored as JSON strings, objectify them upon loading AchievementSchema.post 'init', (doc) -> doc.objectifyQuery() diff --git a/server/achievements/earned_achievement_handler.coffee b/server/achievements/earned_achievement_handler.coffee index ac862a11a..5c7a41aeb 100644 --- a/server/achievements/earned_achievement_handler.coffee +++ b/server/achievements/earned_achievement_handler.coffee @@ -17,7 +17,7 @@ class EarnedAchievementHandler extends Handler recalculate: (req, res) -> onSuccess = (data) => log.debug 'Finished recalculating achievements' if 'achievements' of req.body # Support both slugs and IDs separated by commas - achievementSlugsOrIDs = req.body.achievements + achievementSlugsOrIDs = req.body.earnedAchievements EarnedAchievementHandler.recalculate achievementSlugsOrIDs, onSuccess else EarnedAchievementHandler.recalculate onSuccess diff --git a/test/demo/fixtures/achievements.coffee b/test/demo/fixtures/achievements.coffee index 198792668..68660c63c 100644 --- a/test/demo/fixtures/achievements.coffee +++ b/test/demo/fixtures/achievements.coffee @@ -5,6 +5,7 @@ module.exports.DungeonArenaStarted = DungeonArenaStarted = _id: '53ba76249259823746b6b481' name: 'Dungeon Arena Started' description: 'Started playing Dungeon Arena. ' + icon: '/images/achievements/swords-01.png' worth: 3 collection: 'level.session' query: "{\"level.original\":\"dungeon-arena\"}" @@ -14,6 +15,7 @@ module.exports.Simulated = Simulated = _id: '53ba76249259823746b6b482' name: 'Simulated' description: 'Simulated Games.' + icon: '/images/achievements/cup-02.png' worth: 1 collection: 'users' query: "{\"simulatedBy\":{\"$gt\":0}}" diff --git a/test/demo/views/achievement/UserAchievements.demo.coffee b/test/demo/views/achievement/UserAchievements.demo.coffee index c42463965..959dc1a3d 100644 --- a/test/demo/views/achievement/UserAchievements.demo.coffee +++ b/test/demo/views/achievement/UserAchievements.demo.coffee @@ -10,9 +10,20 @@ fixtures = require '../../fixtures/achievements' module.exports = -> view = new UserAchievementsView {}, me.get '_id' - request = jasmine.Ajax.requests.mostRecent() - request.response - status: 200 - responseText: JSON.stringify fixtures.earnedAchievements + respond = (request) -> + return unless request + if request.url.match /db\/achievement/ + request.response + status: 200 + responseText: JSON.stringify fixtures.achievements + else if request.url.match /db\/user\/[a-z0-9]*\/achievements/ + request.response + status: 200 + responseText: JSON.stringify fixtures.earnedAchievements + else + request.response + status: 404 + + _.each jasmine.Ajax.requests.all(), (request) -> respond request view.render() From b065a524c13c967e9a78697ca0bb6eb1badb87cd Mon Sep 17 00:00:00 2001 From: Ruben Vereecken Date: Mon, 7 Jul 2014 19:56:30 +0200 Subject: [PATCH 27/83] Got down the basic layout for achievement overview Achievement style is now compatible between overview and popup --- app/lib/utils.coffee | 2 +- app/styles/achievements.sass | 172 ++++++++++++++++--------- app/templates/achievement_notify.jade | 2 +- app/templates/user/achievements.jade | 3 +- app/views/kinds/UserView.coffee | 1 - app/views/user/achievements.coffee | 11 ++ test/demo/fixtures/achievements.coffee | 18 ++- 7 files changed, 144 insertions(+), 65 deletions(-) diff --git a/app/lib/utils.coffee b/app/lib/utils.coffee index 9347c1185..8e3a095dc 100644 --- a/app/lib/utils.coffee +++ b/app/lib/utils.coffee @@ -103,6 +103,6 @@ module.exports.functionCreators = module.exports.keepDoingUntil = (func, wait=100, totalWait=5000) -> waitSoFar = 0 (done = (success) -> - if (waitSoFar += wait) <= totalWait && not success + if (waitSoFar += wait) <= totalWait and not success _.delay (-> func done), wait) false diff --git a/app/styles/achievements.sass b/app/styles/achievements.sass index 0d9123634..6bc064c4e 100644 --- a/app/styles/achievements.sass +++ b/app/styles/achievements.sass @@ -1,15 +1,11 @@ -.notifyjs-achievement-base - cursor: auto - white-space: nowrap - text-overflow: ellipsis - padding: 20px 0px +.locked + filter: grayscale(100%) + -moz-filter: grayscale(100%) + -webkit-filter: grayscale(100%) .achievement-body .achievement-icon position: absolute - width: 200px - height: 200px - left: -140px top: 0px .achievement-image @@ -23,6 +19,25 @@ right: 0 bottom: 0 + .achievement-content + background-image: url("/images/achievements/reward_background2.png") + background-size: 100% 100% + text-align: center + overflow: hidden + + .achievement-title + font-family: Bangers + overflow: hidden + + .achievement-description + white-space: initial + font-size: 12px + line-height: 1.3em + overflow: hidden + max-height: 2.6em + margin-top: auto + margin-bottom: 0px !important + .achievement-border-wood background: url("/images/achievements/border_wood.png") no-repeat background-size: 100% 100% @@ -31,70 +46,109 @@ background: url("/images/achievements/border_silver.png") no-repeat background-size: 100% 100% - .achievement-content - background-image: url("/images/achievements/reward_background2.png") - background-size: 100% 100% - width: 450px - height: 160px - text-align: center - padding: 24px 30px 20px 60px - overflow: hidden +#user-achievements-view + .row + //.col-lg-4, .col-xs-12 + padding-right: 0px !important + .achievement-body + width: 335px + height: 120px + margin: 10px 0px - .achievement-title - font-family: Bangers - font-size: 28px - padding-left: -50px - overflow: hidden + .achievement-icon + width: 120px + height: 120px + top: 0px - .achievement-description - white-space: initial - font-size: 15px - line-height: 1.3em - overflow: hidden - max-height: 2.6em - margin-top: auto - margin-bottom: 0px !important + .achievement-image + img + -moz-transform: scale(0.6) + -webkit-transform: scale(0.6) + transform: scale(0.6) - .achievement-progress - padding: 8px 0px 0px 0px + .achievement-content + margin-left: 60px + margin-right: 5px + width: 260px + height: 100px + padding: 20px 10px 20px 60px - .achievement-message - font-family: Bangers - font-size: 18px - &:empty - display: none + .achievement-title + font-size: 20px + padding-left: -50px + .achievement-description + font-size: 12px + line-height: 1.3em + max-height: 2.6em +.notifyjs-achievement-base + padding: 20px 0px - .progress-wrapper - position: absolute - padding-right: 30px - bottom: 0px + .achievement-body + .achievement-icon + width: 200px + height: 200px + left: -140px + top: 0px - .achievement-level - float: left - font-size: 15px + .achievement-image + img + position: absolute + margin: auto + top: 0 + left: 0 + right: 0 + bottom: 0 - .progress-bar-wrapper - padding-left: 30px - width: 100% - .progress - border-radius: 20px + .achievement-content + width: 450px + height: 160px + padding: 24px 30px 20px 60px - .progress-bar-border - padding-left: 30px - position: relative - top: -44px + .achievement-title + font-size: 28px + padding-left: -50px - .progress-bar-border img - width: 100% - /*.earned-exp - padding-left: 5px + .achievement-description + font-size: 15px + line-height: 1.3em + max-height: 2.6em + margin-top: auto + margin-bottom: 0px !important + + .achievement-progress + padding: 8px 0px 0px 0px + + .achievement-message font-family: Bangers - font-size: 16px - float: right - */ + font-size: 18px + &:empty + display: none + + .progress-wrapper + position: absolute + padding-right: 30px + bottom: 0px + + .achievement-level + float: left + font-size: 15px + + .progress-bar-wrapper + padding-left: 30px + width: 100% + .progress + border-radius: 20px + + .progress-bar-border + padding-left: 30px + position: relative + top: -44px + + .progress-bar-border img + width: 100% .progress-bar-white background-color: white diff --git a/app/templates/achievement_notify.jade b/app/templates/achievement_notify.jade index 02c86ad78..1ea25416d 100644 --- a/app/templates/achievement_notify.jade +++ b/app/templates/achievement_notify.jade @@ -1,6 +1,6 @@ div .clearfix.achievement-body - .achievement-icon.achievement-border-silver + .achievement-icon.achievement-border-silver(class=locked === true ? "locked" : "") .achievement-image(data-notify-html="image") if imgURL img(src=imgURL) diff --git a/app/templates/user/achievements.jade b/app/templates/user/achievements.jade index ac312a0e5..db572e75d 100644 --- a/app/templates/user/achievements.jade +++ b/app/templates/user/achievements.jade @@ -6,5 +6,6 @@ block append content - var title = achievement.get('name'); - var description = achievement.get('description'); - var imgURL = achievement.get('icon'); - .col-sm-6.col-xs-12 + - var locked = ! achievement.get('unlocked'); + .col-lg-4.col-xs-12 include ../achievement_notify diff --git a/app/views/kinds/UserView.coffee b/app/views/kinds/UserView.coffee index ada1657f2..f930e174f 100644 --- a/app/views/kinds/UserView.coffee +++ b/app/views/kinds/UserView.coffee @@ -28,5 +28,4 @@ module.exports = class UserView extends RootView console.log 'onUserLoaded' onLoaded: -> - console.log 'onLoaded' super() diff --git a/app/views/user/achievements.coffee b/app/views/user/achievements.coffee index 6d05fccca..cd11c7571 100644 --- a/app/views/user/achievements.coffee +++ b/app/views/user/achievements.coffee @@ -21,6 +21,17 @@ module.exports = class UserAchievementsView extends UserView @achievements = @supermodel.loadCollection(new AchievementCollection, 'achievements').model @earnedAchievements = @supermodel.loadCollection(new EarnedAchievementCollection(@user), 'earnedAchievements').model + onLoaded: -> + console.log @earnedAchievements + console.log 'onLoaded' + _.each @earnedAchievements.models, (earned) => + console.log earned + return unless relatedAchievement = _.find @achievements.models, (achievement) -> + achievement.get('_id') is earned.get 'achievement' + relatedAchievement.set 'unlocked', true + earned.set 'achievement', relatedAchievement + super() + getRenderData: -> context = super() context.achievements = @achievements.models diff --git a/test/demo/fixtures/achievements.coffee b/test/demo/fixtures/achievements.coffee index 68660c63c..9d8ec0be7 100644 --- a/test/demo/fixtures/achievements.coffee +++ b/test/demo/fixtures/achievements.coffee @@ -4,7 +4,7 @@ oneDayBefore = (new Date now).setDate(now.getDate() - 1) module.exports.DungeonArenaStarted = DungeonArenaStarted = _id: '53ba76249259823746b6b481' name: 'Dungeon Arena Started' - description: 'Started playing Dungeon Arena. ' + description: 'Started playing Dungeon Arena. It was a really really hard game. So hard in fact, that this line should already be spanning' icon: '/images/achievements/swords-01.png' worth: 3 collection: 'level.session' @@ -22,6 +22,20 @@ module.exports.Simulated = Simulated = userField: '_id' proportionalTo: 'simulatedBy' +module.exports.Simulated2 = Simulated2 = + _id: '53ba76249259823746b6b483' + name: 'Simulated2' + description: 'Simulated games for real.' + icon: '/images/achievements/cup-02.png' + worth: 1.5 + collection: 'users' + query: "{\"simulatedBy\":{\"$gt\":0}}" + userField: '_id' + proportionalTo: 'simulatedBy' + function: + kind: 'logarithmic' + parameters: {a: 1, b: .5, c: .5, d: 1} + module.exports.DungeonArenaStartedEarned = DungeonArenaStartedEarned = user: '' achievement: DungeonArenaStarted._id @@ -47,5 +61,5 @@ module.exports.SimulatedEarned = SimulatedEarned = notified: true -module.exports.achievements = [DungeonArenaStarted, Simulated] +module.exports.achievements = [DungeonArenaStarted, Simulated, Simulated2] module.exports.earnedAchievements = [DungeonArenaStartedEarned, SimulatedEarned] From 5dc54c784d9428ff30a6bbab3392f7b3da4882e1 Mon Sep 17 00:00:00 2001 From: Ruben Vereecken Date: Wed, 9 Jul 2014 18:12:31 +0200 Subject: [PATCH 28/83] Added case and demo for user not found. Should be made prettier as soon as users can be queried by name --- app/models/User.coffee | 3 +++ app/templates/kinds/user.jade | 16 ++++++++++------ app/templates/user/achievements.jade | 17 +++++++++-------- app/views/kinds/UserView.coffee | 18 +++++++++++++----- app/views/user/achievements.coffee | 5 +++-- public/images/achievements/cup-02.png | Bin 0 -> 8472 bytes test/demo/views/user/UserNotFound.demo.coffee | 9 +++++++++ 7 files changed, 47 insertions(+), 21 deletions(-) create mode 100644 public/images/achievements/cup-02.png create mode 100644 test/demo/views/user/UserNotFound.demo.coffee diff --git a/app/models/User.coffee b/app/models/User.coffee index 700379d82..eadc64b12 100644 --- a/app/models/User.coffee +++ b/app/models/User.coffee @@ -18,6 +18,9 @@ module.exports = class User extends CocoModel permissions = @attributes['permissions'] or [] return 'admin' in permissions + isAnonymous: -> + @get 'anonymous' + displayName: -> @get('name') or 'Anoner' diff --git a/app/templates/kinds/user.jade b/app/templates/kinds/user.jade index 0ce93689e..780c17509 100644 --- a/app/templates/kinds/user.jade +++ b/app/templates/kinds/user.jade @@ -3,10 +3,14 @@ extends /templates/base // User pages might have some user page specific header, if not remove this block content div - ol.breadcrumb - li - - var userName = user.get('name'); - a(href="/user/#{user.id}") #{userName} - li.active - | #{currentUserView} + if user + ol.breadcrumb + li + - var userName = user.get('name'); + a(href="/user/#{user.id}") #{userName} + li.active + | #{currentUserView} + else + // TODO Ruben make this all fancy as soon as we can query users by name + | User not found. diff --git a/app/templates/user/achievements.jade b/app/templates/user/achievements.jade index db572e75d..ecb2dc673 100644 --- a/app/templates/user/achievements.jade +++ b/app/templates/user/achievements.jade @@ -1,11 +1,12 @@ extends /templates/kinds/user block append content - .row - each achievement, index in achievements - - var title = achievement.get('name'); - - var description = achievement.get('description'); - - var imgURL = achievement.get('icon'); - - var locked = ! achievement.get('unlocked'); - .col-lg-4.col-xs-12 - include ../achievement_notify + if achievements + .row + each achievement, index in achievements + - var title = achievement.get('name'); + - var description = achievement.get('description'); + - var imgURL = achievement.get('icon'); + - var locked = ! achievement.get('unlocked'); + .col-lg-4.col-xs-12 + include ../achievement_notify diff --git a/app/views/kinds/UserView.coffee b/app/views/kinds/UserView.coffee index f930e174f..54014aa87 100644 --- a/app/views/kinds/UserView.coffee +++ b/app/views/kinds/UserView.coffee @@ -10,22 +10,30 @@ module.exports = class UserView extends RootView super options @listenTo @, 'userLoaded', @onUserLoaded + @listenTo @, 'userNotFound', @ifUserNotFound # TODO Ruben Assume ID for now @user = User.getByID @nameOrID, {}, true, - success: => - @trigger 'userLoaded', @user + success: (user) => + @trigger 'userNotFound' unless user + @trigger 'userLoaded', user + error: => + console.debug 'Error while fetching user' + @trigger 'userNotFound' getRenderData: -> context = super() context.currentUserView = 'Achievements' - context.user = @user + context.user = @user unless @user?.isAnonymous() context isMe: -> @nameOrID is me.id - onUserLoaded: -> - console.log 'onUserLoaded' + onUserLoaded: (user) -> + console.log 'onUserLoaded', user + + ifUserNotFound: -> + console.warn 'user not found' onLoaded: -> super() diff --git a/app/views/user/achievements.coffee b/app/views/user/achievements.coffee index cd11c7571..251415859 100644 --- a/app/views/user/achievements.coffee +++ b/app/views/user/achievements.coffee @@ -34,6 +34,7 @@ module.exports = class UserAchievementsView extends UserView getRenderData: -> context = super() - context.achievements = @achievements.models - context.earnedAchievements = @earnedAchievements.models + if @user and not @user.isAnonymous() + context.achievements = @achievements.models + context.earnedAchievements = @earnedAchievements.models context diff --git a/public/images/achievements/cup-02.png b/public/images/achievements/cup-02.png new file mode 100644 index 0000000000000000000000000000000000000000..0eba69463684541caf284e4b20307beca47f6171 GIT binary patch literal 8472 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 + view = new UserAchievementsView {}, 'thisiddoesntexist' + + userRequest = jasmine.Ajax.requests.mostRecent() + userRequest.response status: 404 + + view.render() From 8e82702348061d33496b35315b69a5cd95e830c3 Mon Sep 17 00:00:00 2001 From: Ruben Vereecken Date: Sun, 13 Jul 2014 21:32:07 +0200 Subject: [PATCH 29/83] Fixed an async case that failed a test only sometimes --- server/plugins/plugins.coffee | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/server/plugins/plugins.coffee b/server/plugins/plugins.coffee index e609de90a..3aa5bcecf 100644 --- a/server/plugins/plugins.coffee +++ b/server/plugins/plugins.coffee @@ -258,16 +258,14 @@ module.exports.VersionedPlugin = (schema) -> ) ) - # Assume ever save is a new version, hence an edit + # Assume every save is a new version, hence an edit schema.pre 'save', (next) -> User = require '../users/User' # Avoid mutual inclusion cycles userID = @get('creator')?.toHexString() return next() unless userID? statName = User.statsMapping.edits[@constructor.modelName] - User.incrementStat userID, statName - - next() + User.incrementStat userID, statName, next module.exports.SearchablePlugin = (schema, options) -> # this plugin must be added only after the others (specifically Versioned and Permissions) From 9db84befa6eb53fe90a8860f0a89c1656b0c70d2 Mon Sep 17 00:00:00 2001 From: Ruben Vereecken Date: Sun, 13 Jul 2014 22:34:32 +0200 Subject: [PATCH 30/83] Set up a 301 Redirect for account/profile instead of a backbone redirect --- app/lib/Router.coffee | 6 ++++++ server_setup.coffee | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/app/lib/Router.coffee b/app/lib/Router.coffee index d30375c5c..f46b6a56f 100644 --- a/app/lib/Router.coffee +++ b/app/lib/Router.coffee @@ -19,6 +19,9 @@ module.exports = class CocoRouter extends Backbone.Router # user views 'user/:nameOrID(/:subview)': 'userView' + # account views + 'account(/:subview)(/*rest)': 'accountView' + # Direct links 'test/*subpath': go('TestView') 'demo/*subpath': go('DemoView') @@ -58,6 +61,9 @@ module.exports = class CocoRouter extends Backbone.Router view.render() @openView if view then view else @notFoundView() + accountView: (nameOrID, subview) -> + + cache: {} openRoute: (route) -> route = route.split('?')[0] diff --git a/server_setup.coffee b/server_setup.coffee index 7f2b11ce6..49789a9a2 100644 --- a/server_setup.coffee +++ b/server_setup.coffee @@ -70,11 +70,17 @@ setupMiddlewareToSendOldBrowserWarningWhenPlayersViewLevelDirectly = (app) -> return next() if req.query['try-old-browser-anyway'] or not isOldBrowser req res.sendfile(path.join(__dirname, 'public', 'index_old_browser.html')) +setupRedirectMiddleware = (app) -> + app.all '/account/profile/*', (req, res, next) -> + nameOrID = req.path.split('/')[3] + res.redirect 301, "/user/#{nameOrID}/profile" + exports.setupMiddleware = (app) -> setupMiddlewareToSendOldBrowserWarningWhenPlayersViewLevelDirectly app setupExpressMiddleware app setupPassportMiddleware app setupOneSecondDelayMiddleware app + setupRedirectMiddleware app ###Routing function implementations### From d261f888d7268ae4bc6f8bdf670942f75a0b1303 Mon Sep 17 00:00:00 2001 From: Ruben Vereecken Date: Mon, 14 Jul 2014 12:14:43 +0200 Subject: [PATCH 31/83] Refactored profile view to be a userView. Would still like to test remarks though --- app/lib/Router.coffee | 5 +-- app/views/kinds/UserView.coffee | 24 ++++++++----- app/views/user/achievements.coffee | 4 +-- .../profile.coffee} | 34 +++++++------------ 4 files changed, 31 insertions(+), 36 deletions(-) rename app/views/{account/profile_view.coffee => user/profile.coffee} (97%) diff --git a/app/lib/Router.coffee b/app/lib/Router.coffee index f46b6a56f..c26838b9e 100644 --- a/app/lib/Router.coffee +++ b/app/lib/Router.coffee @@ -20,7 +20,7 @@ module.exports = class CocoRouter extends Backbone.Router 'user/:nameOrID(/:subview)': 'userView' # account views - 'account(/:subview)(/*rest)': 'accountView' + # 'account(/:subview)(/*rest)': 'accountView' # Direct links 'test/*subpath': go('TestView') @@ -61,9 +61,6 @@ module.exports = class CocoRouter extends Backbone.Router view.render() @openView if view then view else @notFoundView() - accountView: (nameOrID, subview) -> - - cache: {} openRoute: (route) -> route = route.split('?')[0] diff --git a/app/views/kinds/UserView.coffee b/app/views/kinds/UserView.coffee index 54014aa87..82ed54ea1 100644 --- a/app/views/kinds/UserView.coffee +++ b/app/views/kinds/UserView.coffee @@ -6,17 +6,21 @@ module.exports = class UserView extends RootView template: template className: 'user-view' - constructor: (options, @nameOrID) -> + constructor: (options, @userID) -> super options @listenTo @, 'userLoaded', @onUserLoaded @listenTo @, 'userNotFound', @ifUserNotFound - # TODO Ruben Assume ID for now - @user = User.getByID @nameOrID, {}, true, - success: (user) => - @trigger 'userNotFound' unless user - @trigger 'userLoaded', user + @userID ?= me.id + @fetchUser @userID + + # TODO Ruben make this use the new getByNameOrID as soon as that is merged in + fetchUser: (id) -> + User.getByID id, {}, true, + success: (@user) => + @trigger 'userNotFound' unless @user + @trigger 'userLoaded', @user error: => console.debug 'Error while fetching user' @trigger 'userNotFound' @@ -27,13 +31,15 @@ module.exports = class UserView extends RootView context.user = @user unless @user?.isAnonymous() context - isMe: -> @nameOrID is me.id + isMe: -> @userID is me.id - onUserLoaded: (user) -> - console.log 'onUserLoaded', user + onUserLoaded: -> + console.log 'onUserLoaded', @user + @render() ifUserNotFound: -> console.warn 'user not found' + @render() onLoaded: -> super() diff --git a/app/views/user/achievements.coffee b/app/views/user/achievements.coffee index 251415859..02fdffad0 100644 --- a/app/views/user/achievements.coffee +++ b/app/views/user/achievements.coffee @@ -13,8 +13,8 @@ module.exports = class UserAchievementsView extends UserView events: 'userLoaded': 'onUserLoaded' - constructor: (options, nameOrID) -> - super options, nameOrID + constructor: (options, userID) -> + super options, userID onUserLoaded: (user) -> super user diff --git a/app/views/account/profile_view.coffee b/app/views/user/profile.coffee similarity index 97% rename from app/views/account/profile_view.coffee rename to app/views/user/profile.coffee index 8c88b2a39..ac87944c8 100644 --- a/app/views/account/profile_view.coffee +++ b/app/views/user/profile.coffee @@ -1,4 +1,4 @@ -View = require 'views/kinds/RootView' +UserView = require 'views/kinds/UserView' template = require 'templates/account/profile' User = require 'models/User' LevelSession = require 'models/LevelSession' @@ -25,7 +25,7 @@ adminContacts = [ {id: '52a57252a89409700d0000d9', name: 'Ignore'} ] -module.exports = class ProfileView extends View +module.exports = class ProfileView extends UserView id: 'profile-view' template: template subscriptions: @@ -50,8 +50,7 @@ module.exports = class ProfileView extends View 'click .editable-profile a': 'onClickLinkWhileEditing' 'change #admin-contact': 'onAdminContactChanged' - constructor: (options, @userID) -> - @userID ?= me.id + constructor: (options, userID) -> @onJobProfileNotesChanged = _.debounce @onJobProfileNotesChanged, 1000 @onRemarkChanged = _.debounce @onRemarkChanged, 1000 @authorizedWithLinkedIn = IN?.User?.isAuthorized() @@ -60,31 +59,23 @@ module.exports = class ProfileView extends View window.contractCallback = => @authorizedWithLinkedIn = IN?.User?.isAuthorized() @render() - super options - if User.isObjectID @userID - @finishInit() - else - $.ajax "/db/user/#{@userID}/nameToID", success: (@userID) => - @finishInit() unless @destroyed - @render() + super options, userID + + onUserLoaded: -> + @finishInit() unless @destroyed + super() finishInit: -> + console.debug 'finishing that init' return unless @userID @uploadFilePath = "db/user/#{@userID}" @highlightedContainers = [] - if @userID is me.id - @user = me - else if me.isAdmin() or 'employer' in me.get('permissions') - @user = User.getByID(@userID) - @user.fetch() - @listenTo @user, 'sync', => - @render() + if me.isAdmin() or 'employer' in me.get('permissions') $.post "/db/user/#{me.id}/track/view_candidate" $.post "/db/user/#{@userID}/track/viewed_by_employer" unless me.isAdmin() - else - @user = User.getByID(@userID) @sessions = @supermodel.loadCollection(new LevelSessionsCollection(@userID), 'candidate_sessions').model if me.isAdmin() + console.debug 'fetching that remark' # Mimicking how the VictoryModal fetches LevelFeedback @remark = new UserRemark() @remark.setURL "/db/user/#{@userID}/remark" @@ -282,7 +273,8 @@ module.exports = class ProfileView extends View _.delay -> justSavedSection.removeClass 'just-saved', duration: 1500, easing: 'easeOutQuad' , 500 - if me.isAdmin() + console.debug @user + if me.isAdmin() and @user visibleSettings = ['history', 'tasks'] data = _.pick (@remark.attributes), (value, key) -> key in visibleSettings data.history ?= [] From 5f2c7665ea6aabefc32e78191171ed3e6ba4ed9d Mon Sep 17 00:00:00 2001 From: Ruben Vereecken Date: Mon, 14 Jul 2014 14:20:13 +0200 Subject: [PATCH 32/83] Navbar refactored, starting to look good already. --- app/styles/common/top_nav.sass | 24 ++++++++++++++++++++++++ app/templates/base.jade | 30 +++++++++++++++++++++++++----- 2 files changed, 49 insertions(+), 5 deletions(-) diff --git a/app/styles/common/top_nav.sass b/app/styles/common/top_nav.sass index 3619a3f9b..970f044a8 100644 --- a/app/styles/common/top_nav.sass +++ b/app/styles/common/top_nav.sass @@ -22,6 +22,30 @@ .glyphicon-user font-size: 16px + margin-right: 5px + + //#9F915B + //#E4CF8C + .dropdown + .dropdown-menu + right: 0 + left: auto + width: 280px + padding: 0px + + li.user-dropdown-header + background: #E4CF8C + height: 175px + padding: 10px + text-align: center + + img + border: #e3be7a 8px solid + + li.user-dropdown-body + // placeholder + li.user-dropdown-footer + // placeholder .nav.navbar-link-text, .nav.navbar-link-text > li > a font-weight: normal diff --git a/app/templates/base.jade b/app/templates/base.jade index 01a67687c..252e59571 100644 --- a/app/templates/base.jade +++ b/app/templates/base.jade @@ -27,17 +27,16 @@ body select.language-dropdown - if me.get('anonymous') === false - button.btn.btn-primary.navbuttontext.header-font#logout-button(data-i18n="login.log_out") Log Out - a.btn.btn-primary.navbuttontext.header-font(href=me.get('jobProfile') ? "/account/profile/#{me.id}" : "/account/settings") + //button.btn.btn-primary.navbuttontext.header-font#logout-button(data-i18n="login.log_out") Log Out + //a.btn.btn-primary.navbuttontext.header-font(href=me.get('jobProfile') ? "/account/profile/#{me.id}" : "/account/settings") div.navbuttontext-account(data-i18n="nav.account") Account if me.get('photoURL') img.account-settings-image(src=me.getPhotoURL(18), alt="") else span.glyphicon.glyphicon-user - else - button.btn.btn-primary.navbuttontext.header-font.auth-button + //else + button.btn.btn-primary.navbuttontext.header-font.auth-button span(data-i18n="login.log_in") Log In span.spr.spl / span(data-i18n="login.sign_up") Create Account @@ -47,6 +46,27 @@ body a.header-font(href='/play', data-i18n="nav.play") Levels li a.header-font(href='/community', data-i18n="nav.community") Community + if me.get('anonymous') === false + li.dropdown + button.btn.btn-primary.navbuttontext.header-font.dropdown-toggle(href="#", data-toggle="dropdown") + if me.get('photoURL') + img.account-settings-image(src=me.getPhotoURL(18), alt="") + else + i.glyphicon.glyphicon-user + .navbuttontext-account(data-i18n="nav.account") Account + span.caret + ul.dropdown-menu(role="menu") + li.user-dropdown-header + img.img-circle(src="#{me.getPhotoURL()}" alt="") + p Lorem Ipsum + li.user-dropdown-body + .col-xs-4.text-center + Profile + .col-xs-4.text-center + Statistics + .col-xs-4.text-center + Code + li.user-dropdown-footer block outer_content #outer-content-wrapper From 1aa566e4f73a6e7933f5d6f6fca493365d411cb4 Mon Sep 17 00:00:00 2001 From: Ruben Vereecken Date: Mon, 14 Jul 2014 14:56:44 +0200 Subject: [PATCH 33/83] Layout of navbar user dropdown about finished --- app/styles/common/top_nav.sass | 31 ++++++++++++++++++++++++++++--- app/templates/base.jade | 12 ++++++++---- 2 files changed, 36 insertions(+), 7 deletions(-) diff --git a/app/styles/common/top_nav.sass b/app/styles/common/top_nav.sass index 970f044a8..7bb29374f 100644 --- a/app/styles/common/top_nav.sass +++ b/app/styles/common/top_nav.sass @@ -38,14 +38,39 @@ height: 175px padding: 10px text-align: center + color: black + border: #32281E 1px solid img border: #e3be7a 8px solid + p + margin-top: 10px + text-shadow: 2px 2px 3px white li.user-dropdown-body - // placeholder + color: black + padding: 15px + + &:after + display: table + content: " " + clear: both + li.user-dropdown-footer - // placeholder + padding: 10px + margin-left: 0px + + &:after + display: table + content: " " + clear: both + + .btn + margin: 0px + + .btn-flat + border: #ddd 1px solid + border-radius: 0px .nav.navbar-link-text, .nav.navbar-link-text > li > a font-weight: normal @@ -55,7 +80,7 @@ &:hover color: #f8e413 - .navbar-link-text a:hover + .navbar-link-text > a:hover background: darken($body-bg, 3%) .btn, .btn-group, .fancy-select diff --git a/app/templates/base.jade b/app/templates/base.jade index 252e59571..1a3e71734 100644 --- a/app/templates/base.jade +++ b/app/templates/base.jade @@ -58,15 +58,19 @@ body ul.dropdown-menu(role="menu") li.user-dropdown-header img.img-circle(src="#{me.getPhotoURL()}" alt="") - p Lorem Ipsum + p=me.get('name') || 'Anoner' li.user-dropdown-body .col-xs-4.text-center - Profile + a(href="#") Profile .col-xs-4.text-center - Statistics + a(href="#") Stats .col-xs-4.text-center - Code + a.disabled(href="#") Code li.user-dropdown-footer + .pull-left + a.btn.btn-default.btn-flat(href="") Account + .pull-right + a.btn.btn-default.btn-flat(href="") Log Out block outer_content #outer-content-wrapper From 7a52c195eb4e4e67fb0a6a4fbcc26d252f451f87 Mon Sep 17 00:00:00 2001 From: Ruben Vereecken Date: Mon, 14 Jul 2014 15:32:02 +0200 Subject: [PATCH 34/83] More user navbar styling! Also need to investigate browser reflow --- app/styles/common/top_nav.sass | 47 +++++++++++++++++++++++++++++++--- app/templates/base.jade | 2 +- 2 files changed, 44 insertions(+), 5 deletions(-) diff --git a/app/styles/common/top_nav.sass b/app/styles/common/top_nav.sass index 7bb29374f..6f4c2b95e 100644 --- a/app/styles/common/top_nav.sass +++ b/app/styles/common/top_nav.sass @@ -1,5 +1,39 @@ @import "../bootstrap/variables" +// This is still very blocky. Browser reflows? Investigate why. +//.open > .dropdown-menu + animation-name: fadeAnimation + animation-duration: .7s + animation-iteration-count: 1 + animation-timing-function: ease + animation-fill-mode: forwards + -webkit-animation-name: fadeAnimation + -webkit-animation-duration: 2s + -webkit-animation-iteration-count: 1 + -webkit-animation-timing-function: ease + -webkit-animation-fill-mode: backwards + -moz-animation-name: fadeAnimation + -moz-animation-duration: .7s + -moz-animation-iteration-count: 1 + -moz-animation-timing-function: ease + -moz-animation-fill-mode: forwards + +@keyframes fadeAnimation + from + opacity: 0 + top: 120% + to + opacity: 1 + top: 100% + +@-webkit-keyframes fadeAnimation + from + opacity: 0 + top: 120% + to + opacity: 1 + top: 100% + #top-nav a.navbar-brand padding: 4px 20px 0px 20px @@ -32,24 +66,28 @@ left: auto width: 280px padding: 0px + border-radius: 0px + font-family: Bangers li.user-dropdown-header background: #E4CF8C - height: 175px + height: 160px padding: 10px text-align: center color: black - border: #32281E 1px solid - + border-bottom: #32281E 1px solid img border: #e3be7a 8px solid - p + height: 98px // Includes the border + h3 margin-top: 10px text-shadow: 2px 2px 3px white + color: #31281E li.user-dropdown-body color: black padding: 15px + font: 15px 'Helvetica Neue', Helvetica, Arial, sans-serif &:after display: table @@ -59,6 +97,7 @@ li.user-dropdown-footer padding: 10px margin-left: 0px + font-size: 14px &:after display: table diff --git a/app/templates/base.jade b/app/templates/base.jade index 1a3e71734..f8c7dff31 100644 --- a/app/templates/base.jade +++ b/app/templates/base.jade @@ -58,7 +58,7 @@ body ul.dropdown-menu(role="menu") li.user-dropdown-header img.img-circle(src="#{me.getPhotoURL()}" alt="") - p=me.get('name') || 'Anoner' + h3=me.get('name') || 'Anoner' li.user-dropdown-body .col-xs-4.text-center a(href="#") Profile From 5aa6db76969fa60b2c27cbaf0f6e8bb26f5283ec Mon Sep 17 00:00:00 2001 From: Ruben Vereecken Date: Mon, 14 Jul 2014 18:40:23 +0200 Subject: [PATCH 35/83] Made the sass more snappy --- app/styles/common/top_nav.sass | 22 ++++++---------------- 1 file changed, 6 insertions(+), 16 deletions(-) diff --git a/app/styles/common/top_nav.sass b/app/styles/common/top_nav.sass index 6f4c2b95e..dee79b574 100644 --- a/app/styles/common/top_nav.sass +++ b/app/styles/common/top_nav.sass @@ -1,14 +1,14 @@ @import "../bootstrap/variables" // This is still very blocky. Browser reflows? Investigate why. -//.open > .dropdown-menu +.open > .dropdown-menu animation-name: fadeAnimation animation-duration: .7s animation-iteration-count: 1 animation-timing-function: ease animation-fill-mode: forwards -webkit-animation-name: fadeAnimation - -webkit-animation-duration: 2s + -webkit-animation-duration: .7s -webkit-animation-iteration-count: 1 -webkit-animation-timing-function: ease -webkit-animation-fill-mode: backwards @@ -58,11 +58,8 @@ font-size: 16px margin-right: 5px - //#9F915B - //#E4CF8C .dropdown .dropdown-menu - right: 0 left: auto width: 280px padding: 0px @@ -75,7 +72,7 @@ padding: 10px text-align: center color: black - border-bottom: #32281E 1px solid + border-bottom: #32281e 1px solid img border: #e3be7a 8px solid height: 98px // Includes the border @@ -87,8 +84,8 @@ li.user-dropdown-body color: black padding: 15px + letter-spacing: 1px font: 15px 'Helvetica Neue', Helvetica, Arial, sans-serif - &:after display: table content: " " @@ -98,20 +95,16 @@ padding: 10px margin-left: 0px font-size: 14px - &:after display: table content: " " clear: both - - .btn - margin: 0px - .btn-flat border: #ddd 1px solid border-radius: 0px + margin: 0px - .nav.navbar-link-text, .nav.navbar-link-text > li > a + .nav.navbar-link-text > li > a font-weight: normal font-size: 25px letter-spacing: 2px @@ -155,9 +148,6 @@ top: 13px max-width: 140px - .nav - margin-bottom: 0 - div.fancy-select text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25) div.trigger From 40ba28f49bc10fd31a93bc507b9a4dcd0ca54afc Mon Sep 17 00:00:00 2001 From: Ruben Vereecken Date: Tue, 15 Jul 2014 16:15:21 +0200 Subject: [PATCH 36/83] Basics for account and user home pages --- app/lib/Router.coffee | 14 +++++++++++- app/styles/account/home.sass | 5 +++++ app/styles/user/home.sass | 5 +++++ app/templates/account/home.jade | 35 ++++++++++++++++++++++++++++++ app/templates/kinds/user.jade | 7 +++--- app/templates/user/home.jade | 3 +++ app/views/account/home.coffee | 17 +++++++++++++++ app/views/kinds/UserView.coffee | 3 ++- app/views/user/achievements.coffee | 3 --- app/views/user/home.coffee | 15 +++++++++++++ server_setup.coffee | 4 +--- 11 files changed, 100 insertions(+), 11 deletions(-) create mode 100644 app/styles/account/home.sass create mode 100644 app/styles/user/home.sass create mode 100644 app/templates/account/home.jade create mode 100644 app/templates/user/home.jade create mode 100644 app/views/account/home.coffee create mode 100644 app/views/user/home.coffee diff --git a/app/lib/Router.coffee b/app/lib/Router.coffee index e9613f21d..88dbb991a 100644 --- a/app/lib/Router.coffee +++ b/app/lib/Router.coffee @@ -20,7 +20,7 @@ module.exports = class CocoRouter extends Backbone.Router 'user/:nameOrID(/:subview)': 'userView' # account views - # 'account(/:subview)(/*rest)': 'accountView' + 'account(/:subview)': 'accountView' # Direct links 'test': go('TestView') @@ -63,6 +63,18 @@ module.exports = class CocoRouter extends Backbone.Router view.render() @openView if view then view else @notFoundView() + # TODO There should be a uniform way to define these routes. This is backwards compatible + accountView: (subview) -> + modulePrefix = 'views/account/' + suffix = subview or 'home' + suffix2 = suffix + '_view' + ViewClass = @tryToLoadModule modulePrefix + suffix + ViewClass = @tryToLoadModule modulePrefix + suffix2 unless ViewClass + if ViewClass + view = new ViewClass + view.render() + @openView if view then view else @notFoundView() + cache: {} openRoute: (route) -> route = route.split('?')[0] diff --git a/app/styles/account/home.sass b/app/styles/account/home.sass new file mode 100644 index 000000000..1f329ac05 --- /dev/null +++ b/app/styles/account/home.sass @@ -0,0 +1,5 @@ +#user-home-view + #avatar + width: 150px + + content: "blub" diff --git a/app/styles/user/home.sass b/app/styles/user/home.sass new file mode 100644 index 000000000..70b1a9ad6 --- /dev/null +++ b/app/styles/user/home.sass @@ -0,0 +1,5 @@ +#account-home-view + .main-content-area + padding: 20px 20px + img#avatar + width: 150px diff --git a/app/templates/account/home.jade b/app/templates/account/home.jade new file mode 100644 index 000000000..a79a1aae5 --- /dev/null +++ b/app/templates/account/home.jade @@ -0,0 +1,35 @@ +extends /templates/base + +block content + h2 Account + div + .col-sm-3.text-center + img#avatar(src="#{me.getPhotoURL(100)}") + h3=me.get('name') || 'Anoner' + .col-sm-6 + dl.dl-horizontal + if me.get('firstName') || me.get('lastName') + dt Full name + dd=me.get('firstName') + ' ' + me.get('lastName') + dt Email + dd + span.spr=me.get('email') + span (subscriptions) + + .col-sm-3 + h3 Account + ul + li + a Settings + li + a Payment + h3 Public + ul + li + a Public profile + li + a Job Profile + li + a Statistics + li + a Code diff --git a/app/templates/kinds/user.jade b/app/templates/kinds/user.jade index 780c17509..a6671506a 100644 --- a/app/templates/kinds/user.jade +++ b/app/templates/kinds/user.jade @@ -3,14 +3,15 @@ extends /templates/base // User pages might have some user page specific header, if not remove this block content div - if user + if user && viewName ol.breadcrumb li - var userName = user.get('name'); a(href="/user/#{user.id}") #{userName} li.active - | #{currentUserView} - else + | #{viewName} + else if !user // TODO Ruben make this all fancy as soon as we can query users by name | User not found. + diff --git a/app/templates/user/home.jade b/app/templates/user/home.jade new file mode 100644 index 000000000..f6c8b9fa0 --- /dev/null +++ b/app/templates/user/home.jade @@ -0,0 +1,3 @@ +extends /templates/kinds/user + +block append content diff --git a/app/views/account/home.coffee b/app/views/account/home.coffee new file mode 100644 index 000000000..bf96c50d5 --- /dev/null +++ b/app/views/account/home.coffee @@ -0,0 +1,17 @@ +View = require 'views/kinds/RootView' +template = require 'templates/account/home' +{me} = require 'lib/auth' +User = require 'models/User' +AuthModalView = require 'views/modal/auth_modal' + +module.exports = class AccountHomeView extends View + id: 'account-home-view' + template: template + + constructor: (options) -> + super options + return unless me + + afterRender: -> + super() + @openModelView new AuthModalView if me.isAnonymous() diff --git a/app/views/kinds/UserView.coffee b/app/views/kinds/UserView.coffee index 82ed54ea1..2383c86cf 100644 --- a/app/views/kinds/UserView.coffee +++ b/app/views/kinds/UserView.coffee @@ -5,6 +5,7 @@ User = require 'models/User' module.exports = class UserView extends RootView template: template className: 'user-view' + viewName: null # Used for the breadcrumbs constructor: (options, @userID) -> super options @@ -27,7 +28,7 @@ module.exports = class UserView extends RootView getRenderData: -> context = super() - context.currentUserView = 'Achievements' + context.viewName = @viewName context.user = @user unless @user?.isAnonymous() context diff --git a/app/views/user/achievements.coffee b/app/views/user/achievements.coffee index 02fdffad0..2a70501e4 100644 --- a/app/views/user/achievements.coffee +++ b/app/views/user/achievements.coffee @@ -10,9 +10,6 @@ module.exports = class UserAchievementsView extends UserView id: 'user-achievements-view' template: template - events: - 'userLoaded': 'onUserLoaded' - constructor: (options, userID) -> super options, userID diff --git a/app/views/user/home.coffee b/app/views/user/home.coffee new file mode 100644 index 000000000..9b9b105f7 --- /dev/null +++ b/app/views/user/home.coffee @@ -0,0 +1,15 @@ +UserView = require 'views/kinds/UserView' +template = require 'templates/user/home' +{me} = require 'lib/auth' + +module.exports = class UserHomeView extends UserView + id: 'user-home-view' + template: template + + constructor: (options) -> + super options + + getRenderData: -> + context = super() + context + diff --git a/server_setup.coffee b/server_setup.coffee index 6750d789d..bb72554c4 100644 --- a/server_setup.coffee +++ b/server_setup.coffee @@ -70,17 +70,15 @@ setupMiddlewareToSendOldBrowserWarningWhenPlayersViewLevelDirectly = (app) -> return next() if req.query['try-old-browser-anyway'] or not isOldBrowser req res.sendfile(path.join(__dirname, 'public', 'index_old_browser.html')) -<<<<<<< HEAD setupRedirectMiddleware = (app) -> app.all '/account/profile/*', (req, res, next) -> nameOrID = req.path.split('/')[3] res.redirect 301, "/user/#{nameOrID}/profile" -======= + setupTrailingSlashRemovingMiddleware = (app) -> app.use (req, res, next) -> return res.redirect 301, req.url[...-1] if req.url.length > 1 and req.url.slice(-1) is '/' next() ->>>>>>> master exports.setupMiddleware = (app) -> setupMiddlewareToSendOldBrowserWarningWhenPlayersViewLevelDirectly app From 023a7adc8161e917719946f64eccd9f0b4bf56e6 Mon Sep 17 00:00:00 2001 From: Ruben Vereecken Date: Wed, 16 Jul 2014 12:39:18 +0200 Subject: [PATCH 37/83] Half the account page is there --- app/styles/account/home.sass | 15 ++- app/styles/common/top_nav.sass | 18 ++-- app/styles/user/home.sass | 5 - app/templates/account/home.jade | 111 ++++++++++++++++++++- app/templates/base.jade | 20 ++-- app/views/account/home.coffee | 6 ++ server/levels/sessions/LevelSession.coffee | 2 + 7 files changed, 146 insertions(+), 31 deletions(-) diff --git a/app/styles/account/home.sass b/app/styles/account/home.sass index 1f329ac05..d8aa424cb 100644 --- a/app/styles/account/home.sass +++ b/app/styles/account/home.sass @@ -1,5 +1,12 @@ -#user-home-view - #avatar - width: 150px +#account-home-view + dl + margin-bottom: 0px - content: "blub" + img#picture + max-width: 50% + + .panel + margin-bottom: 10px + + h2 + margin-bottom: 0px diff --git a/app/styles/common/top_nav.sass b/app/styles/common/top_nav.sass index dee79b574..8215c2a2a 100644 --- a/app/styles/common/top_nav.sass +++ b/app/styles/common/top_nav.sass @@ -1,4 +1,5 @@ @import "../bootstrap/variables" +@import "../bootstrap/mixins" // This is still very blocky. Browser reflows? Investigate why. .open > .dropdown-menu @@ -34,6 +35,9 @@ opacity: 1 top: 100% +a.disabled + color: #5b5855 + #top-nav a.navbar-brand padding: 4px 20px 0px 20px @@ -53,6 +57,7 @@ .account-settings-image width: 18px height: 18px + margin-right: 5px .glyphicon-user font-size: 16px @@ -86,19 +91,14 @@ padding: 15px letter-spacing: 1px font: 15px 'Helvetica Neue', Helvetica, Arial, sans-serif - &:after - display: table - content: " " - clear: both + +clearfix() li.user-dropdown-footer padding: 10px margin-left: 0px font-size: 14px - &:after - display: table - content: " " - clear: both + +clearfix() + .btn-flat border: #ddd 1px solid border-radius: 0px @@ -112,7 +112,7 @@ &:hover color: #f8e413 - .navbar-link-text > a:hover + .navbar-link-text > li > a:hover background: darken($body-bg, 3%) .btn, .btn-group, .fancy-select diff --git a/app/styles/user/home.sass b/app/styles/user/home.sass index 70b1a9ad6..e69de29bb 100644 --- a/app/styles/user/home.sass +++ b/app/styles/user/home.sass @@ -1,5 +0,0 @@ -#account-home-view - .main-content-area - padding: 20px 20px - img#avatar - width: 150px diff --git a/app/templates/account/home.jade b/app/templates/account/home.jade index a79a1aae5..57ea5799f 100644 --- a/app/templates/account/home.jade +++ b/app/templates/account/home.jade @@ -1,8 +1,100 @@ extends /templates/base block content + .clearfix + .col-sm-6.clearfix + h2 Account Settings + hr + + .panel.panel-default + .panel-heading + h3.panel-title + a(href="account/settings#me") Me + .panel-body + dl + dt Name + dd=me.get('name') + dt Email + dd abe@lincoln + .panel.panel-default + .panel-heading + h3.panel-title + a(href="account/settings#picture") Picture + .panel-body.text-center + img#picture(src="#{me.getPhotoURL(150)}" alt="") + .panel.panel-default + .panel-heading + h3.panel-title + a(href="account/settings#wizard") Wizard + //.panel-body + | Lorem Ipsum + .panel.panel-default + .panel-heading + h3.panel-title + a(href="account/settings#emails") Emails + .panel-body + .form + .form-group.checkbox + label.control-label(for="email_archmageNews") + span General + input#email_archmageNews(name="email_archmageNews", type="checkbox", checked=subs.generalNews, disabled="true") + .form-group.checkbox + label.control-label(for="email_archmageNews") + span.spr(data-i18n="classes.archmage_title") + | Archmage + span(data-i18n="classes.archmage_title_description") + | (Coder) + input#email_archmageNews(name="email_archmageNews", type="checkbox", checked=subs.archmageNews, disabled="true") + .form-group.checkbox + label.control-label(for="email_artisanNews") + span.spr(data-i18n="classes.artisan_title") + | Artisan + span(data-i18n="classes.artisan_title_description") + | (Level Builder) + input#email_artisanNews(name="email_artisanNews", type="checkbox", checked=subs.artisanNews, disabled="true") + .form-group.checkbox + label.control-label(for="email_adventurerNews") + span.spr(data-i18n="classes.adventurer_title") + | Adventurer + span(data-i18n="classes.adventurer_title_description") + | (Level Playtester) + input#email_adventurerNews(name="email_adventurerNews", type="checkbox", checked=subs.adventurerNews, disabled="true") + .form-group.checkbox + label.control-label(for="email_scribeNews") + span.spr(data-i18n="classes.scribe_title") + | Scribe + span(data-i18n="classes.scribe_title_description") + | (Article Editor) + input#email_scribeNews(name="email_scribeNews", type="checkbox", checked=subs.scribeNews, disabled="true") + .form-group.checkbox + label.control-label(for="email_diplomatNews") + span.spr(data-i18n="classes.diplomat_title") + | Diplomat + span(data-i18n="classes.diplomat_title_description") + | (Translator) + input#email_diplomatNews(name="email_diplomatNews", type="checkbox", checked=subs.diplomatNews, disabled="true") + .form-group.checkbox + label.control-label(for="email_ambassadorNews") + span.spr(data-i18n="classes.ambassador_title") + | Ambassador + span(data-i18n="classes.ambassador_title_description") + | (Support) + input#email_ambassadorNews(name="email_ambassadorNews", type="checkbox", checked=subs.ambassadorNews, disabled="true") + + .panel.panel-default + .panel-heading + h3.panel-title + a(href="account/settings#password") Password + .panel.panel-default + .panel-heading + h3.panel-title + a(href="account/settings#job-profile") Job Profile + .col-sm-6 + h2 Recently Played + hr + +//block content h2 Account - div .col-sm-3.text-center img#avatar(src="#{me.getPhotoURL(100)}") h3=me.get('name') || 'Anoner' @@ -10,11 +102,22 @@ block content dl.dl-horizontal if me.get('firstName') || me.get('lastName') dt Full name - dd=me.get('firstName') + ' ' + me.get('lastName') + dd=me.get('firstName') || '' + ' ' + me.get('lastName') || '' dt Email dd span.spr=me.get('email') - span (subscriptions) + //span (subscriptions) + hr + - var dateCreated = me.get('dateCreated'); + - var signedCLA = me.get('signedCLA'); + - console.log(moment) + dt Member since + dd= moment(dateCreated).format('MMMM Do YYYY') + if signedCLA + dt Signed CLA + dd= moment(signedCLA).format('MMMM Do YYYY') + + // TODO Have social network icons here for easy linking .col-sm-3 h3 Account @@ -33,3 +136,5 @@ block content a Statistics li a Code + + diff --git a/app/templates/base.jade b/app/templates/base.jade index f8c7dff31..06482a750 100644 --- a/app/templates/base.jade +++ b/app/templates/base.jade @@ -35,12 +35,6 @@ body else span.glyphicon.glyphicon-user - //else - button.btn.btn-primary.navbuttontext.header-font.auth-button - span(data-i18n="login.log_in") Log In - span.spr.spl / - span(data-i18n="login.sign_up") Create Account - ul(class='navbar-link-text').nav.navbar-nav.pull-right li.play a.header-font(href='/play', data-i18n="nav.play") Levels @@ -53,7 +47,7 @@ body img.account-settings-image(src=me.getPhotoURL(18), alt="") else i.glyphicon.glyphicon-user - .navbuttontext-account(data-i18n="nav.account") Account + .navbuttontext-account(data-i18n="nav.account" href="/account") Account span.caret ul.dropdown-menu(role="menu") li.user-dropdown-header @@ -65,12 +59,18 @@ body .col-xs-4.text-center a(href="#") Stats .col-xs-4.text-center - a.disabled(href="#") Code + a.disabled() Code li.user-dropdown-footer .pull-left - a.btn.btn-default.btn-flat(href="") Account + a.btn.btn-default.btn-flat(href="/account") Account .pull-right - a.btn.btn-default.btn-flat(href="") Log Out + button#logout-button.btn.btn-default.btn-flat(data-i18n="login.log_out") Log Out + else + li + button.btn.btn-primary.navbuttontext.header-font.auth-button + span(data-i18n="login.log_in") Log In + span.spr.spl / + span(data-i18n="login.sign_up") Create Account block outer_content #outer-content-wrapper diff --git a/app/views/account/home.coffee b/app/views/account/home.coffee index bf96c50d5..91368c9bf 100644 --- a/app/views/account/home.coffee +++ b/app/views/account/home.coffee @@ -12,6 +12,12 @@ module.exports = class AccountHomeView extends View super options return unless me + getRenderData: -> + c = super() + c.subs = {} + c.subs[sub] = 1 for sub in c.me.getEnabledEmails() + c + afterRender: -> super() @openModelView new AuthModalView if me.isAnonymous() diff --git a/server/levels/sessions/LevelSession.coffee b/server/levels/sessions/LevelSession.coffee index 228d0b1e8..6a3f17b45 100644 --- a/server/levels/sessions/LevelSession.coffee +++ b/server/levels/sessions/LevelSession.coffee @@ -42,4 +42,6 @@ LevelSessionSchema.pre 'save', (next) -> delete previous[id] if initd next() +LevelSessionSchema.index {user: 1, changed: -1}, {sparse: true, name: 'last played index'} + module.exports = LevelSession = mongoose.model('level.session', LevelSessionSchema, 'level.sessions') From 0d87209fa810ee436cdef77bf524d437bdaec3d8 Mon Sep 17 00:00:00 2001 From: Ruben Vereecken Date: Mon, 21 Jul 2014 19:06:22 +0200 Subject: [PATCH 38/83] Recently played is now shown on account page --- .../RecentlyPlayedCollection.coffee | 2 +- app/templates/account/home.jade | 19 +++++++++++++++++++ app/views/account/home.coffee | 3 +++ app/views/user/achievements.coffee | 2 +- bower.json | 1 + 5 files changed, 25 insertions(+), 2 deletions(-) diff --git a/app/collections/RecentlyPlayedCollection.coffee b/app/collections/RecentlyPlayedCollection.coffee index 1325781df..ff76aaf5b 100644 --- a/app/collections/RecentlyPlayedCollection.coffee +++ b/app/collections/RecentlyPlayedCollection.coffee @@ -1,4 +1,4 @@ -CocoCollection = require './CocoCollections' +CocoCollection = require './CocoCollection' LevelSession = require 'models/LevelSession' module.exports = class RecentlyPlayedCollection extends CocoCollection diff --git a/app/templates/account/home.jade b/app/templates/account/home.jade index 57ea5799f..20413b226 100644 --- a/app/templates/account/home.jade +++ b/app/templates/account/home.jade @@ -92,6 +92,25 @@ block content .col-sm-6 h2 Recently Played hr + if !recentlyPlayed + div Loading... + else if recentlyPlayed.length + table.table + tr + th Level + th Last Played + th Status + each session in recentlyPlayed + tr + td + a(href="/play/level/#{session.get('levelID')}")= session.get('levelName') + td= moment(session.get('changed')).fromNow() + if session.get('state').complete === true + td Completed + else + .panel.panel-default + .panel-body + div No games played during the past two weeks. //block content h2 Account diff --git a/app/views/account/home.coffee b/app/views/account/home.coffee index 91368c9bf..41953afaf 100644 --- a/app/views/account/home.coffee +++ b/app/views/account/home.coffee @@ -3,6 +3,7 @@ template = require 'templates/account/home' {me} = require 'lib/auth' User = require 'models/User' AuthModalView = require 'views/modal/auth_modal' +RecentlyPlayedCollection = require 'collections/RecentlyPlayedCollection' module.exports = class AccountHomeView extends View id: 'account-home-view' @@ -11,11 +12,13 @@ module.exports = class AccountHomeView extends View constructor: (options) -> super options return unless me + @recentlyPlayed = @supermodel.loadCollection(new RecentlyPlayedCollection(me.get('_id')), 'recentlyPlayed').model getRenderData: -> c = super() c.subs = {} c.subs[sub] = 1 for sub in c.me.getEnabledEmails() + c.recentlyPlayed = @recentlyPlayed.models c afterRender: -> diff --git a/app/views/user/achievements.coffee b/app/views/user/achievements.coffee index 2a70501e4..1a8d4dc52 100644 --- a/app/views/user/achievements.coffee +++ b/app/views/user/achievements.coffee @@ -14,9 +14,9 @@ module.exports = class UserAchievementsView extends UserView super options, userID onUserLoaded: (user) -> - super user @achievements = @supermodel.loadCollection(new AchievementCollection, 'achievements').model @earnedAchievements = @supermodel.loadCollection(new EarnedAchievementCollection(@user), 'earnedAchievements').model + super user onLoaded: -> console.log @earnedAchievements diff --git a/bower.json b/bower.json index 91a04f9cc..e979e2f49 100644 --- a/bower.json +++ b/bower.json @@ -31,6 +31,7 @@ "i18next": "git://github.com/nwinter/i18next.git", "firepad": "~0.1.2", "marked": "~0.3.0", + "moment": "~2.5.0", "aether": "~0.2.22", "underscore.string": "~2.3.3", "firebase": "~1.0.2", From 972d231ff556c4f98a4e968708b4ae72d0d2d68b Mon Sep 17 00:00:00 2001 From: Ruben Vereecken Date: Mon, 21 Jul 2014 19:49:16 +0200 Subject: [PATCH 39/83] Refactored router and views to anticipate the renameViews feature --- app/lib/Router.coffee | 32 ++++--------------- ...view.coffee => AccountSettingsView.coffee} | 0 .../{home.coffee => MainAccountView.coffee} | 2 +- app/views/kinds/UserView.coffee | 2 +- ...vements.coffee => AchievementsView.coffee} | 4 +-- .../{profile.coffee => JobProfileView.coffee} | 2 +- .../user/{home.coffee => MainUserView.coffee} | 4 +-- 7 files changed, 14 insertions(+), 32 deletions(-) rename app/views/account/{settings_view.coffee => AccountSettingsView.coffee} (100%) rename app/views/account/{home.coffee => MainAccountView.coffee} (93%) rename app/views/user/{achievements.coffee => AchievementsView.coffee} (93%) rename app/views/user/{profile.coffee => JobProfileView.coffee} (99%) rename app/views/user/{home.coffee => MainUserView.coffee} (72%) diff --git a/app/lib/Router.coffee b/app/lib/Router.coffee index 665892c86..42690cb62 100644 --- a/app/lib/Router.coffee +++ b/app/lib/Router.coffee @@ -47,11 +47,14 @@ module.exports = class CocoRouter extends Backbone.Router # editor views tend to have the same general structure 'editor/:model(/:slug_or_id)(/:subview)': 'editorModelView' - # user views - 'user/:nameOrID(/:subview)': 'userView' + 'user/:slugOrID': go('user/MainUserView') + 'user/:slugOrID/stats': go('user/AchievementsView') + 'user/:slugOrID/profile': go('user/JobProfileView') + #'user/:slugOrID/code': go('user/CodeView') - # account views - 'account(/:subview)': 'accountView' + 'account': go('account/MainAccountView') + 'account/settings': go('account/AccountSettingsView') + #'account/payment' # Direct links @@ -83,27 +86,6 @@ module.exports = class CocoRouter extends Backbone.Router view.render() @openView if view then view else @notFoundView() - userView: (nameOrID, subview) -> - modulePrefix = 'views/user/' - suffix = subview or 'home' - ViewClass = @tryToLoadModule modulePrefix + suffix - if ViewClass - view = new ViewClass {}, nameOrID - view.render() - @openView if view then view else @notFoundView() - - # TODO Ruben There should be a uniform way to define these routes. This is backwards compatible - accountView: (subview) -> - modulePrefix = 'views/account/' - suffix = subview or 'home' - suffix2 = suffix + '_view' - ViewClass = @tryToLoadModule modulePrefix + suffix - ViewClass = @tryToLoadModule modulePrefix + suffix2 unless ViewClass - if ViewClass - view = new ViewClass - view.render() - @openView if view then view else @notFoundView() - cache: {} openRoute: (route) -> route = route.split('?')[0] diff --git a/app/views/account/settings_view.coffee b/app/views/account/AccountSettingsView.coffee similarity index 100% rename from app/views/account/settings_view.coffee rename to app/views/account/AccountSettingsView.coffee diff --git a/app/views/account/home.coffee b/app/views/account/MainAccountView.coffee similarity index 93% rename from app/views/account/home.coffee rename to app/views/account/MainAccountView.coffee index 41953afaf..425ff29c5 100644 --- a/app/views/account/home.coffee +++ b/app/views/account/MainAccountView.coffee @@ -5,7 +5,7 @@ User = require 'models/User' AuthModalView = require 'views/modal/auth_modal' RecentlyPlayedCollection = require 'collections/RecentlyPlayedCollection' -module.exports = class AccountHomeView extends View +module.exports = class MainAccountView extends View id: 'account-home-view' template: template diff --git a/app/views/kinds/UserView.coffee b/app/views/kinds/UserView.coffee index 2383c86cf..6b146f4f8 100644 --- a/app/views/kinds/UserView.coffee +++ b/app/views/kinds/UserView.coffee @@ -7,7 +7,7 @@ module.exports = class UserView extends RootView className: 'user-view' viewName: null # Used for the breadcrumbs - constructor: (options, @userID) -> + constructor: (@userID, options) -> super options @listenTo @, 'userLoaded', @onUserLoaded diff --git a/app/views/user/achievements.coffee b/app/views/user/AchievementsView.coffee similarity index 93% rename from app/views/user/achievements.coffee rename to app/views/user/AchievementsView.coffee index 1a8d4dc52..cad94f181 100644 --- a/app/views/user/achievements.coffee +++ b/app/views/user/AchievementsView.coffee @@ -6,11 +6,11 @@ EarnedAchievement = require 'models/EarnedAchievement' AchievementCollection = require 'collections/AchievementCollection' EarnedAchievementCollection = require 'collections/EarnedAchievementCollection' -module.exports = class UserAchievementsView extends UserView +module.exports = class AchievementsView extends UserView id: 'user-achievements-view' template: template - constructor: (options, userID) -> + constructor: (userID, options) -> super options, userID onUserLoaded: (user) -> diff --git a/app/views/user/profile.coffee b/app/views/user/JobProfileView.coffee similarity index 99% rename from app/views/user/profile.coffee rename to app/views/user/JobProfileView.coffee index 725231bc5..480d9e7d4 100644 --- a/app/views/user/profile.coffee +++ b/app/views/user/JobProfileView.coffee @@ -54,7 +54,7 @@ module.exports = class ProfileView extends UserView 'change #admin-contact': 'onAdminContactChanged' 'click .session-link': 'onSessionLinkPressed' - constructor: (options, userID) -> + constructor: (userID, options) -> @onJobProfileNotesChanged = _.debounce @onJobProfileNotesChanged, 1000 @onRemarkChanged = _.debounce @onRemarkChanged, 1000 @authorizedWithLinkedIn = IN?.User?.isAuthorized() diff --git a/app/views/user/home.coffee b/app/views/user/MainUserView.coffee similarity index 72% rename from app/views/user/home.coffee rename to app/views/user/MainUserView.coffee index 9b9b105f7..60752797e 100644 --- a/app/views/user/home.coffee +++ b/app/views/user/MainUserView.coffee @@ -2,11 +2,11 @@ UserView = require 'views/kinds/UserView' template = require 'templates/user/home' {me} = require 'lib/auth' -module.exports = class UserHomeView extends UserView +module.exports = class MainUserView extends UserView id: 'user-home-view' template: template - constructor: (options) -> + constructor: (userID, options) -> super options getRenderData: -> From ebdbc0f89197465fcda56c7d3596375a74bdaf06 Mon Sep 17 00:00:00 2001 From: Ruben Vereecken Date: Mon, 21 Jul 2014 22:02:08 +0200 Subject: [PATCH 40/83] Basis for user view is finished --- app/styles/common/top_nav.sass | 2 ++ app/styles/user/home.sass | 29 +++++++++++++++++++ app/templates/account/home.jade | 1 - app/templates/kinds/user.jade | 20 ++++++++----- app/templates/user/home.jade | 18 ++++++++++++ app/views/kinds/UserView.coffee | 4 ++- .../JobProfileCodeModal.coffee | 0 app/views/user/JobProfileView.coffee | 2 +- 8 files changed, 65 insertions(+), 11 deletions(-) rename app/views/{account => user}/JobProfileCodeModal.coffee (100%) diff --git a/app/styles/common/top_nav.sass b/app/styles/common/top_nav.sass index 8215c2a2a..f4adc3a4e 100644 --- a/app/styles/common/top_nav.sass +++ b/app/styles/common/top_nav.sass @@ -37,6 +37,8 @@ a.disabled color: #5b5855 + text-decoration: none + cursor: default #top-nav a.navbar-brand diff --git a/app/styles/user/home.sass b/app/styles/user/home.sass index e69de29bb..0e51f28b4 100644 --- a/app/styles/user/home.sass +++ b/app/styles/user/home.sass @@ -0,0 +1,29 @@ +@import "../bootstrap/variables" +@import "../bootstrap/mixins" + +#user-home-view + margin-top: 20px + + .left-column + +make-sm-column(4) + + .right-column + +make-sm-column(8) + + .picture-wrapper + text-align: center + outline: 1px solid darkgrey + max-width: 80% + +center-block() + + > .picture + max-width: 100% + border: 4px solid white + + > .name + margin: 0px auto + padding: 10px inherit + background: white + color: white + text-shadow: 2px 0 0 #000, -2px 0 0 #000, 0 2px 0 #000, 0 -2px 0 #000, 1px 1px #000, -1px -1px 0 #000, 1px -1px 0 #000, -1px 1px 0 #000 + diff --git a/app/templates/account/home.jade b/app/templates/account/home.jade index 20413b226..a3afdb879 100644 --- a/app/templates/account/home.jade +++ b/app/templates/account/home.jade @@ -5,7 +5,6 @@ block content .col-sm-6.clearfix h2 Account Settings hr - .panel.panel-default .panel-heading h3.panel-title diff --git a/app/templates/kinds/user.jade b/app/templates/kinds/user.jade index a6671506a..6cb134ce2 100644 --- a/app/templates/kinds/user.jade +++ b/app/templates/kinds/user.jade @@ -2,16 +2,20 @@ extends /templates/base // User pages might have some user page specific header, if not remove this block content - div - if user && viewName - ol.breadcrumb - li - - var userName = user.get('name'); - a(href="/user/#{user.id}") #{userName} - li.active - | #{viewName} + .clearfix + //- + if user && viewName + ol.breadcrumb + li + - var userName = user.get('name'); + //_a(href="/user/#{user.id}") #{userName} + li.active + //-| #{viewName} + if !userLoaded + | LOADING else if !user // TODO Ruben make this all fancy as soon as we can query users by name | User not found. + diff --git a/app/templates/user/home.jade b/app/templates/user/home.jade index f6c8b9fa0..f84b82442 100644 --- a/app/templates/user/home.jade +++ b/app/templates/user/home.jade @@ -1,3 +1,21 @@ extends /templates/kinds/user block append content + if user + .left-column + .picture-wrapper + img.picture(src="#{me.getPhotoURL(150)}" alt="") + h3.name= user.get('name') + .right-column + .panel.panel-default + .panel-heading + h3.panel-title Achievements + .panel-body + .panel.panel-default + .panel-heading + h3.panel-title S. Levels + .panel-body + .panel.panel-default + .panel-heading + h3.panel-title M. Levels + .panel-body diff --git a/app/views/kinds/UserView.coffee b/app/views/kinds/UserView.coffee index 6b146f4f8..a1e39f0a0 100644 --- a/app/views/kinds/UserView.coffee +++ b/app/views/kinds/UserView.coffee @@ -20,16 +20,18 @@ module.exports = class UserView extends RootView fetchUser: (id) -> User.getByID id, {}, true, success: (@user) => + @userLoaded = true @trigger 'userNotFound' unless @user @trigger 'userLoaded', @user error: => - console.debug 'Error while fetching user' + @userLoaded = true @trigger 'userNotFound' getRenderData: -> context = super() context.viewName = @viewName context.user = @user unless @user?.isAnonymous() + context.userLoaded = @userLoaded context isMe: -> @userID is me.id diff --git a/app/views/account/JobProfileCodeModal.coffee b/app/views/user/JobProfileCodeModal.coffee similarity index 100% rename from app/views/account/JobProfileCodeModal.coffee rename to app/views/user/JobProfileCodeModal.coffee diff --git a/app/views/user/JobProfileView.coffee b/app/views/user/JobProfileView.coffee index 480d9e7d4..488e8c397 100644 --- a/app/views/user/JobProfileView.coffee +++ b/app/views/user/JobProfileView.coffee @@ -236,7 +236,7 @@ module.exports = class ProfileView extends UserView jobProfile.name ?= (@user.get('firstName') + ' ' + @user.get('lastName')).trim() if @user?.get('firstName') context.profile = jobProfile context.user = @user - context.myProfile = @user?.id is context.me.id + context.myProfile = @isMe() context.allowedToViewJobProfile = @user and (me.isAdmin() or 'employer' in me.get('permissions') or (context.myProfile && !me.get('anonymous'))) context.allowedToEditJobProfile = @user and (me.isAdmin() or (context.myProfile && !me.get('anonymous'))) context.profileApproved = @user?.get 'jobProfileApproved' From 411bb879856208c21455fa5cdbc367bc6f50ef9b Mon Sep 17 00:00:00 2001 From: Ruben Vereecken Date: Tue, 22 Jul 2014 12:14:21 +0200 Subject: [PATCH 41/83] Added contributor categories to user profile page --- app/models/SuperModel.coffee | 3 +- app/styles/account/home.sass | 6 +++ app/styles/base.sass | 3 +- app/styles/common/top_nav.sass | 12 +++-- app/styles/user/home.sass | 25 +++++++++++ app/templates/account/home.jade | 6 +++ app/templates/base.jade | 13 ++---- app/templates/user/home.jade | 78 ++++++++++++++++++++++++++------- 8 files changed, 112 insertions(+), 34 deletions(-) diff --git a/app/models/SuperModel.coffee b/app/models/SuperModel.coffee index 8c1f68e4d..c366d8285 100644 --- a/app/models/SuperModel.coffee +++ b/app/models/SuperModel.coffee @@ -57,9 +57,8 @@ module.exports = class SuperModel extends Backbone.Model res.markLoading() return res else - console.debug 'adding collection', collection @addCollection collection - @listenTo collection, 'sync', (c) -> + @listenToOnce collection, 'sync', (c) -> console.debug 'Registering collection', url @registerCollection c res = @addModelResource(collection, name, fetchOptions, value) diff --git a/app/styles/account/home.sass b/app/styles/account/home.sass index d8aa424cb..453b52e94 100644 --- a/app/styles/account/home.sass +++ b/app/styles/account/home.sass @@ -1,3 +1,6 @@ +@import "../bootstrap/variables" +@import "../bootstrap/mixins" + #account-home-view dl margin-bottom: 0px @@ -10,3 +13,6 @@ h2 margin-bottom: 0px + + .panel-title > a + margin-left: 5px diff --git a/app/styles/base.sass b/app/styles/base.sass index c873d3d0f..cdf458557 100644 --- a/app/styles/base.sass +++ b/app/styles/base.sass @@ -21,7 +21,8 @@ h1 h2 h3 h4 margin: 56px auto 0 min-height: 600px padding: 14px 12px 5px 12px - @include box-sizing(border-box) + +box-sizing(border-box) + +clearfix() #outer-content-wrapper background: #B4B4B4 diff --git a/app/styles/common/top_nav.sass b/app/styles/common/top_nav.sass index f4adc3a4e..7e0feece9 100644 --- a/app/styles/common/top_nav.sass +++ b/app/styles/common/top_nav.sass @@ -73,29 +73,33 @@ a.disabled border-radius: 0px font-family: Bangers - li.user-dropdown-header + .user-dropdown-header background: #E4CF8C height: 160px padding: 10px text-align: center color: black border-bottom: #32281e 1px solid + a:hover + background-color: transparent img border: #e3be7a 8px solid height: 98px // Includes the border - h3 + &:hover + box-shadow: 0 0 20px #e3be7a + > h3 margin-top: 10px text-shadow: 2px 2px 3px white color: #31281E - li.user-dropdown-body + .user-dropdown-body color: black padding: 15px letter-spacing: 1px font: 15px 'Helvetica Neue', Helvetica, Arial, sans-serif +clearfix() - li.user-dropdown-footer + .user-dropdown-footer padding: 10px margin-left: 0px font-size: 14px diff --git a/app/styles/user/home.sass b/app/styles/user/home.sass index 0e51f28b4..4da3c19a3 100644 --- a/app/styles/user/home.sass +++ b/app/styles/user/home.sass @@ -27,3 +27,28 @@ color: white text-shadow: 2px 0 0 #000, -2px 0 0 #000, 0 2px 0 #000, 0 -2px 0 #000, 1px 1px #000, -1px -1px 0 #000, 1px -1px 0 #000, -1px 1px 0 #000 + .list-group-item > a + color: #555555 + margin-left: 5px + +.contributor-categories + list-style: none + padding: 0px + + > .contributor-category + outline: 1px solid black + margin-bottom: 15px + + > .contributor-image + border: none + width: 100% + border-bottom: 1px solid black + + > .contributor-title + text-align: center + padding: 5px 0px + margin: 0px + background: white + +.vertical-buffer + padding: 10px 0px diff --git a/app/templates/account/home.jade b/app/templates/account/home.jade index a3afdb879..0645e0aa1 100644 --- a/app/templates/account/home.jade +++ b/app/templates/account/home.jade @@ -7,6 +7,7 @@ block content hr .panel.panel-default .panel-heading + i.glyphicon.glyphicon-user.pull-left h3.panel-title a(href="account/settings#me") Me .panel-body @@ -17,18 +18,21 @@ block content dd abe@lincoln .panel.panel-default .panel-heading + i.glyphicon.glyphicon-picture.pull-left h3.panel-title a(href="account/settings#picture") Picture .panel-body.text-center img#picture(src="#{me.getPhotoURL(150)}" alt="") .panel.panel-default .panel-heading + i.glyphicon.glyphicon-user.pull-left h3.panel-title a(href="account/settings#wizard") Wizard //.panel-body | Lorem Ipsum .panel.panel-default .panel-heading + i.glyphicon.glyphicon-envelope.pull-left h3.panel-title a(href="account/settings#emails") Emails .panel-body @@ -82,10 +86,12 @@ block content .panel.panel-default .panel-heading + i.glyphicon.glyphicon-wrench.pull-left h3.panel-title a(href="account/settings#password") Password .panel.panel-default .panel-heading + i.glyphicon.glyphicon-briefcase.pull-left h3.panel-title a(href="account/settings#job-profile") Job Profile .col-sm-6 diff --git a/app/templates/base.jade b/app/templates/base.jade index c23bd7315..f3edd46a0 100644 --- a/app/templates/base.jade +++ b/app/templates/base.jade @@ -27,14 +27,6 @@ body select.language-dropdown - //button.btn.btn-primary.navbuttontext.header-font#logout-button(data-i18n="login.log_out") Log Out - //a.btn.btn-primary.navbuttontext.header-font(href=me.get('jobProfile') ? "/account/profile/#{me.id}" : "/account/settings") - div.navbuttontext-account(data-i18n="nav.account") Account - if me.get('photoURL') - img.account-settings-image(src=me.getPhotoURL(18), alt="") - else - span.glyphicon.glyphicon-user - ul(class='navbar-link-text').nav.navbar-nav.pull-right li.play a.header-font(href='/play', data-i18n="nav.play") Levels @@ -51,11 +43,12 @@ body span.caret ul.dropdown-menu(role="menu") li.user-dropdown-header - img.img-circle(src="#{me.getPhotoURL()}" alt="") + a(href="/user/#{me.get('slug') || me.get('_id')}") + img.img-circle(src="#{me.getPhotoURL()}" alt="") h3=me.get('name') || 'Anoner' li.user-dropdown-body .col-xs-4.text-center - a(href="#") Profile + a(href="/user/#{me.get('slug') || me.get('_id')}") Profile .col-xs-4.text-center a(href="#") Stats .col-xs-4.text-center diff --git a/app/templates/user/home.jade b/app/templates/user/home.jade index f84b82442..9541450aa 100644 --- a/app/templates/user/home.jade +++ b/app/templates/user/home.jade @@ -2,20 +2,64 @@ extends /templates/kinds/user block append content if user - .left-column - .picture-wrapper - img.picture(src="#{me.getPhotoURL(150)}" alt="") - h3.name= user.get('name') - .right-column - .panel.panel-default - .panel-heading - h3.panel-title Achievements - .panel-body - .panel.panel-default - .panel-heading - h3.panel-title S. Levels - .panel-body - .panel.panel-default - .panel-heading - h3.panel-title M. Levels - .panel-body + .vertical-buffer + .row + .left-column + .picture-wrapper + img.picture(src="#{me.getPhotoURL(150)}" alt="") + h3.name= user.get('name') + hr + ul.list-group + li.list-group-item + i.glyphicon.glyphicon-briefcase + a(href="profile") Job Profile + li.list-group-item + i.glyphicon.glyphicon-pencil + a(href="") Code + - var emails = user.get('emails') + if emails + hr + ul.contributor-categories + //li.contributor-category + img.contributor-image(src="/images/pages/user/general.png") + h4.contributor-title CodeCombateer + if emails.adventurerNews + li.contributor-category + img.contributor-image(src="/images/pages/user/adventurer.png") + h4.contributor-title + a(href="/contribute#adventurer") Adventurer + if emails.ambassadorNews + li.contributor-category + img.contributor-image(src="/images/pages/user/ambassador.png") + h4.contributor-title + a(href="/contribute#ambassador") Ambassador + if emails.archmageNews + li.contributor-category + img.contributor-image(src="/images/pages/user/archmage.png") + h4.contributor-title + a(href="/contribute#archmage") Archmage + if emails.artisanNews + li.contributor-category + img.contributor-image(src="/images/pages/user/artisan.png") + h4.contributor-title + a(href="/contribute#artisan") Artisan + if emails.scribeNews + li.contributor-category + img.contributor-image(src="/images/pages/user/scribe.png") + h4.contributor-title + a(href="/contribute#scribe") Scribe + + + .right-column + .panel.panel-default + .panel-heading + h3.panel-title Achievements + .panel-body + .panel.panel-default + .panel-heading + h3.panel-title S. Levels + .panel-body + .panel.panel-default + .panel-heading + h3.panel-title M. Levels + .panel-body From 8969311d82a95ea16ad6e4825cf54ba5f1d89f2a Mon Sep 17 00:00:00 2001 From: Ruben Vereecken Date: Tue, 22 Jul 2014 21:45:36 +0200 Subject: [PATCH 42/83] Butons on profile page now nicer --- app/styles/user/home.sass | 24 ++++++++++++++++++------ app/templates/user/home.jade | 18 ++++++++---------- 2 files changed, 26 insertions(+), 16 deletions(-) diff --git a/app/styles/user/home.sass b/app/styles/user/home.sass index 4da3c19a3..052434f3f 100644 --- a/app/styles/user/home.sass +++ b/app/styles/user/home.sass @@ -10,14 +10,15 @@ .right-column +make-sm-column(8) - .picture-wrapper + .profile-wrapper text-align: center outline: 1px solid darkgrey - max-width: 80% + max-width: 100% +center-block() > .picture - max-width: 100% + width: 100% + background-color: #dadada border: 4px solid white > .name @@ -27,13 +28,24 @@ color: white text-shadow: 2px 0 0 #000, -2px 0 0 #000, 0 2px 0 #000, 0 -2px 0 #000, 1px 1px #000, -1px -1px 0 #000, 1px -1px 0 #000, -1px 1px 0 #000 - .list-group-item > a - color: #555555 - margin-left: 5px + .profile-menu + padding-left: 0px + width: 100% + > a + border-radius: 0 + border-width: 1px 0px + border-color: darkgrey + &:hover + border-color: #888 + > span + color: #555555 + font-size: 15px + margin-left: 5px .contributor-categories list-style: none padding: 0px + margin-top: 15px > .contributor-category outline: 1px solid black diff --git a/app/templates/user/home.jade b/app/templates/user/home.jade index 9541450aa..aa502a999 100644 --- a/app/templates/user/home.jade +++ b/app/templates/user/home.jade @@ -5,20 +5,18 @@ block append content .vertical-buffer .row .left-column - .picture-wrapper + .profile-wrapper img.picture(src="#{me.getPhotoURL(150)}" alt="") h3.name= user.get('name') - hr - ul.list-group - li.list-group-item - i.glyphicon.glyphicon-briefcase - a(href="profile") Job Profile - li.list-group-item - i.glyphicon.glyphicon-pencil - a(href="") Code + .btn-group-vertical.profile-menu + a.btn.btn-default(href="/user/#{user.get('slug') || user.get('_id')}/profile") + i.glyphicon.glyphicon-briefcase + span Job Profile + a.btn.btn-default(href="profile") + i.glyphicon.glyphicon-pencil + span Code - var emails = user.get('emails') if emails - hr ul.contributor-categories //li.contributor-category img.contributor-image(src="/images/pages/user/general.png") From ebfe1fc1c0d9799882fec1b9ee5e60dff4b782b5 Mon Sep 17 00:00:00 2001 From: Ruben Vereecken Date: Wed, 23 Jul 2014 13:06:51 +0200 Subject: [PATCH 43/83] Proofed admin endpoint some more, no more dangling connections --- server/routes/admin.coffee | 2 ++ test/server/functional/admin.spec.coffee | 15 +++++++++++++-- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/server/routes/admin.coffee b/server/routes/admin.coffee index cfd9a50eb..2d89dfb72 100644 --- a/server/routes/admin.coffee +++ b/server/routes/admin.coffee @@ -20,7 +20,9 @@ module.exports.setup = (app) -> name = handlers[moduleName] handler = require('../' + name) + return errors.notFound res, 'Handler not found for ' + moduleName unless handler return handler[parts[1]](req, res, parts[2..]...) if parts[1] of handler + return errors.notFound res, 'Method not found for handler ' + name catch error log.error("Error trying db method '#{req.route.method}' route '#{parts}' from #{name}: #{error}") diff --git a/test/server/functional/admin.spec.coffee b/test/server/functional/admin.spec.coffee index d3a34baa4..87a390de5 100644 --- a/test/server/functional/admin.spec.coffee +++ b/test/server/functional/admin.spec.coffee @@ -15,9 +15,20 @@ describe 'recalculate statistics', -> expect(res.statusCode).toBe 202 done() - it 'responds with a 404 when not found', (done) -> + xit 'responds with a 404 if handler not found', (done) -> + loginAdmin -> + request.post {uri:getURL '/admin/blobfish/swim'}, (err, res, body) -> + expect(res.statusCode).toBe 404 + done() + + it 'responds with a 404 if handler method not found', (done) -> + loginAdmin -> + request.post {uri:getURL '/admin/user/hammertime'}, (err, res, body) -> + expect(res.statusCode).toBe 404 + done() + + it 'responds with a 404 if recalculate method not found', (done) -> loginAdmin -> request.post {uri:url + 'ballsKicked'}, (err, res, body) -> expect(res.statusCode).toBe 404 done() - From 2394bd81297742e2c1635e811e7953109f760728 Mon Sep 17 00:00:00 2001 From: Ruben Vereecken Date: Wed, 23 Jul 2014 15:22:53 +0200 Subject: [PATCH 44/83] Added the recalculation script for a couple of statistics --- scripts/recalculateStatistics.coffee | 35 ++++++++++++++++++++++++ server/users/user_handler.coffee | 15 +++++----- test/server/functional/admin.spec.coffee | 7 ++++- test/server/functional/user.spec.coffee | 6 ++-- 4 files changed, 52 insertions(+), 11 deletions(-) create mode 100644 scripts/recalculateStatistics.coffee diff --git a/scripts/recalculateStatistics.coffee b/scripts/recalculateStatistics.coffee new file mode 100644 index 000000000..144d3bea0 --- /dev/null +++ b/scripts/recalculateStatistics.coffee @@ -0,0 +1,35 @@ +database = require '../server/commons/database' +mongoose = require 'mongoose' +log = require 'winston' +async = require 'async' + +### SET UP ### +do (setupLodash = this) -> + GLOBAL._ = require 'lodash' + _.str = require 'underscore.string' + _.mixin _.str.exports() + +database.connect() + +### USER STATS ### +UserHandler = require '../server/users/user_handler' + +report = (func, name, done) -> + log.info 'Started ' + name + '...' + func name, (err) -> + log.warn err if err? + log.info 'Finished ' + name + done err if done? + +whenAllFinished = -> + log.info 'All recalculations finished.' + process.exit() + +async.series [ + (c) -> report UserHandler.recalculateAsync, 'gamesCompleted', c + (c) -> report UserHandler.recalculateAsync, 'articleEdits', c + (c) -> report UserHandler.recalculateAsync, 'levelEdits', c + (c) -> report UserHandler.recalculateAsync, 'levelComponentEdits', c + (c) -> report UserHandler.recalculateAsync, 'levelSystemEdits', c + (c) -> report UserHandler.recalculateAsync, 'thangTypeEdits', c +], whenAllFinished diff --git a/server/users/user_handler.coffee b/server/users/user_handler.coffee index f8acab746..e2e2d8b93 100644 --- a/server/users/user_handler.coffee +++ b/server/users/user_handler.coffee @@ -407,7 +407,7 @@ UserHandler = class UserHandler extends Handler doneWithUser() ), done - statHandlers: + statRecalculators: gamesCompleted: (done) -> LevelSession = require '../levels/sessions/LevelSession' @@ -442,14 +442,15 @@ UserHandler = class UserHandler extends Handler ThangType = require '../levels/thangs/ThangType' countEdits ThangType, done + recalculateAsync: (statName, done) => + return new Error 'Recalculation handler not found' unless statName of @statRecalculators + @statRecalculators[statName] done recalculate: (req, res, statName) -> return @sendForbiddenError(res) unless req.user.isAdmin() - - if statName of @statHandlers - @statHandlers[statName]() - return @sendAccepted res, {} - else return @sendNotFoundError(res) - + log.debug 'recalculate' + return @sendNotFoundError(res) unless statName of @statRecalculators + @recalculateAsync statName + @sendAccepted res, {} module.exports = new UserHandler() diff --git a/test/server/functional/admin.spec.coffee b/test/server/functional/admin.spec.coffee index 87a390de5..96be3ef77 100644 --- a/test/server/functional/admin.spec.coffee +++ b/test/server/functional/admin.spec.coffee @@ -15,7 +15,7 @@ describe 'recalculate statistics', -> expect(res.statusCode).toBe 202 done() - xit 'responds with a 404 if handler not found', (done) -> + it 'responds with a 404 if handler not found', (done) -> loginAdmin -> request.post {uri:getURL '/admin/blobfish/swim'}, (err, res, body) -> expect(res.statusCode).toBe 404 @@ -32,3 +32,8 @@ describe 'recalculate statistics', -> request.post {uri:url + 'ballsKicked'}, (err, res, body) -> expect(res.statusCode).toBe 404 done() + + + + + diff --git a/test/server/functional/user.spec.coffee b/test/server/functional/user.spec.coffee index ec6cc84a0..f711e5b50 100644 --- a/test/server/functional/user.spec.coffee +++ b/test/server/functional/user.spec.coffee @@ -364,7 +364,7 @@ describe 'Statistics', -> expect(err).toBeNull() expect(guy.get 'stats.gamesCompleted').toBeUndefined() - UserHandler.statHandlers.gamesCompleted -> + UserHandler.statRecalculators.gamesCompleted -> User.findById joe.get('id'), (err, guy) -> expect(err).toBeNull() expect(guy.get 'stats.gamesCompleted').toBe 1 @@ -406,7 +406,7 @@ describe 'Statistics', -> expect(err).toBeNull() expect(guy.get User.statsMapping.edits.article).toBeUndefined() - UserHandler.statHandlers.articleEdits -> + UserHandler.statRecalculators.articleEdits -> User.findById carl.get('id'), (err, guy) -> expect(err).toBeNull() expect(guy.get User.statsMapping.edits.article).toBe 2 @@ -439,7 +439,7 @@ describe 'Statistics', -> expect(err).toBeNull() expect(guy.get User.statsMapping.edits.level).toBeUndefined() - UserHandler.statHandlers.levelEdits -> + UserHandler.statRecalculators.levelEdits -> User.findById jose.get('id'), (err, guy) -> expect(err).toBeNull() expect(guy.get User.statsMapping.edits.level).toBe 1 From cde87e4fe524c7f8fbb3fa2abec0bcf0c13405f3 Mon Sep 17 00:00:00 2001 From: Ruben Vereecken Date: Wed, 23 Jul 2014 20:00:28 +0200 Subject: [PATCH 45/83] Covered general patches with tests --- app/schemas/models/user.coffee | 34 +++++++++++++++----- app/schemas/schemas.coffee | 1 + scripts/recalculateStatistics.coffee | 14 +++++---- server/users/user_handler.coffee | 40 +++++++++++++++++++++--- test/server/functional/patch.spec.coffee | 29 ++++++++++++++++- 5 files changed, 100 insertions(+), 18 deletions(-) diff --git a/app/schemas/models/user.coffee b/app/schemas/models/user.coffee index 03aec34b7..11c9a32bc 100644 --- a/app/schemas/models/user.coffee +++ b/app/schemas/models/user.coffee @@ -165,13 +165,33 @@ _.extend UserSchema.properties, data: c.object {description: 'Cached LinkedIn data slurped from profile.', additionalProperties: true} points: {type: 'number'} activity: {type: 'object', description: 'Summary statistics about user activity', additionalProperties: c.activity} - stats: c.object {additionalProperties: true}, # TODO set to false after dev - gamesCompleted: type: 'integer' - articleEdits: type: 'integer' - levelEdits: type: 'integer' - levelSystemEdits: type: 'integer' - levelComponentEdits: type: 'integer' - thangTypeEdits: type: 'integer' + stats: c.object {additionalProperties: false}, + gamesCompleted: c.int() + articleEdits: c.int() + levelEdits: c.int() + levelSystemEdits: c.int() + levelComponentEdits: c.int() + thangTypeEdits: c.int() + 'stats.patchesSubmitted': c.int + description: 'Amount of patches submitted, not necessarily accepted' + 'stats.patchesContributed': c.int + description: 'Amount of patches submitted and accepted' + 'stats.patchesAccepted': c.int + description: 'Amount of patches accepted by the user as owner' + # The below patches only apply to those that actually got accepted + 'stats.totalTranslationPatches': c.int() + 'stats.totalMiscPatches': c.int() + 'stats.articleTranslationPatches': c.int() + 'stats.articleMiscPatches': c.int() + 'stats.levelTranslationPatches': c.int() + 'stats.levelMiscPatches': c.int() + 'stats.levelComponentTranslationPatches': c.int() + 'stats.levelComponentMiscPatches': c.int() + 'stats.levelSystemTranslationPatches': c.int() + 'stats.levelSystemMiscPatches': c.int() + 'stats.thangTypeTranslationPatches': c.int() + 'stats.thangTypeMiscPatches': c.int() + c.extendBasicProperties UserSchema, 'user' diff --git a/app/schemas/schemas.coffee b/app/schemas/schemas.coffee index 65045a2e7..889e7c0c7 100644 --- a/app/schemas/schemas.coffee +++ b/app/schemas/schemas.coffee @@ -19,6 +19,7 @@ me.date = (ext) -> combine({type: ['object', 'string'], format: 'date-time'}, ex # should just be string (Mongo ID), but sometimes mongoose turns them into objects representing those, so we are lenient me.objectId = (ext) -> schema = combine({type: ['object', 'string']}, ext) me.url = (ext) -> combine({type: 'string', format: 'url', pattern: urlPattern}, ext) +me.int = (ext) -> if ext then combine {type: 'integer'}, ext else {type: 'integer'} PointSchema = me.object {title: 'Point', description: 'An {x, y} coordinate point.', format: 'point2d', required: ['x', 'y']}, x: {title: 'x', description: 'The x coordinate.', type: 'number', 'default': 15} diff --git a/scripts/recalculateStatistics.coffee b/scripts/recalculateStatistics.coffee index 144d3bea0..52f051335 100644 --- a/scripts/recalculateStatistics.coffee +++ b/scripts/recalculateStatistics.coffee @@ -26,10 +26,12 @@ whenAllFinished = -> process.exit() async.series [ - (c) -> report UserHandler.recalculateAsync, 'gamesCompleted', c - (c) -> report UserHandler.recalculateAsync, 'articleEdits', c - (c) -> report UserHandler.recalculateAsync, 'levelEdits', c - (c) -> report UserHandler.recalculateAsync, 'levelComponentEdits', c - (c) -> report UserHandler.recalculateAsync, 'levelSystemEdits', c - (c) -> report UserHandler.recalculateAsync, 'thangTypeEdits', c + # Misc + (c) -> report UserHandler.recalculateStats, 'gamesCompleted', c + # Edits + (c) -> report UserHandler.recalculateStats, 'articleEdits', c + (c) -> report UserHandler.recalculateStats, 'levelEdits', c + (c) -> report UserHandler.recalculateStats, 'levelComponentEdits', c + (c) -> report UserHandler.recalculateStats, 'levelSystemEdits', c + (c) -> report UserHandler.recalculateStats, 'thangTypeEdits', c ], whenAllFinished diff --git a/server/users/user_handler.coffee b/server/users/user_handler.coffee index e2e2d8b93..edf31dc80 100644 --- a/server/users/user_handler.coffee +++ b/server/users/user_handler.coffee @@ -393,9 +393,10 @@ UserHandler = class UserHandler extends Handler return done(new Error 'Could not resolve statKey for model') unless statKey? User.find {}, (err, users) -> async.eachSeries users, ((user, doneWithUser) -> - userID = user.get('_id').toHexString() + userObjectID = user.get('_id') + userStringID = userObjectID.toHexString() - model.count {creator: userID}, (err, count) -> + model.count {$or: [creator: userObjectID, creator: userStringID]}, (err, count) -> if count update = $set: {} update.$set[statKey] = count @@ -442,7 +443,38 @@ UserHandler = class UserHandler extends Handler ThangType = require '../levels/thangs/ThangType' countEdits ThangType, done - recalculateAsync: (statName, done) => + patchesContributed: (done) -> + Patch = require '../patches/Patch' + + User.find {}, (err, users) -> + async.eachSeries users, ((user, doneWithUser) -> + userObjectID = user.get('_id') + userStringID = userObjectID.toHexString() + + Patch.count {$or: [{creator: userObjectID}, {creator: userStringID}], 'status': 'accepted'}, (err, count) -> + update = if count then {$set: 'stats.patchesContributed': count} else {$unset: 'stats.patchesContributed': ''} + User.findByIdAndUpdate user.get('_id'), update, (err) -> + log.error err if err? + doneWithUser() + ), done + + patchesSubmitted: (done) -> + Patch = require '../patches/Patch' + + User.find {}, (err, users) -> + async.eachSeries users, ((user, doneWithUser) -> + userObjectID = user.get('_id') + userStringID = userObjectID.toHexString() + + Patch.count {$or: [{creator: userObjectID}, {creator: userStringID}]}, (err, count) -> + update = if count then {$set: 'stats.patchesSubmitted': count} else {$unset: 'stats.patchesSubmitted': ''} + User.findByIdAndUpdate user.get('_id'), update, (err) -> + log.error err if err? + doneWithUser() + ), done + + + recalculateStats: (statName, done) => return new Error 'Recalculation handler not found' unless statName of @statRecalculators @statRecalculators[statName] done @@ -450,7 +482,7 @@ UserHandler = class UserHandler extends Handler return @sendForbiddenError(res) unless req.user.isAdmin() log.debug 'recalculate' return @sendNotFoundError(res) unless statName of @statRecalculators - @recalculateAsync statName + @recalculateStats statName @sendAccepted res, {} module.exports = new UserHandler() diff --git a/test/server/functional/patch.spec.coffee b/test/server/functional/patch.spec.coffee index faa9a2f2a..36715ad14 100644 --- a/test/server/functional/patch.spec.coffee +++ b/test/server/functional/patch.spec.coffee @@ -2,6 +2,9 @@ require '../common' describe '/db/patch', -> request = require 'request' + async = require 'async' + UserHandler = require '../../../server/users/user_handler' + it 'clears the db first', (done) -> clearModels [User, Article, Patch], (err) -> throw err if err @@ -112,7 +115,7 @@ describe '/db/patch', -> it 'keeps track of amount of submitted and accepted patches', (done) -> loginJoe (joe) -> - User.findById joe.get('id'), (err, guy) -> + User.findById joe.get('_id'), (err, guy) -> expect(err).toBeNull() expect(guy.get 'stats.patchesSubmitted').toBe 1 expect(guy.get 'stats.patchesContributed').toBe 1 @@ -121,6 +124,25 @@ describe '/db/patch', -> expect(guy.get 'stats.totalTranslationPatches').toBeUndefined() done() + it 'recalculates amount of submitted and accepted patches', (done) -> + loginJoe (joe) -> + console.log joe + User.findById joe.get('_id'), (err, joe) -> + expect(joe.get 'stats.patchesSubmitted').toBe 1 + joe.update {$unset: stats: ''}, (err) -> + UserHandler.modelClass.findById joe.get('_id'), (err, joe) -> + expect(err).toBeNull() + expect(joe.get 'stats').toBeUndefined() + async.parallel [ + (done) -> UserHandler.recalculateStats 'patchesContributed', done + (done) -> UserHandler.recalculateStats 'patchesSubmitted', done + ], (err) -> + expect(err).toBeNull() + UserHandler.modelClass.findById joe.get('_id'), (err, joe) -> + expect(joe.get 'stats.patchesContributed').toBe 1 + expect(joe.get 'stats.patchesSubmitted').toBe 1 + done() + it 'does not allow the recipient to withdraw the pull request', (done) -> loginAdmin -> statusURL = getURL("/db/patch/#{patches[0]._id}/status") @@ -129,3 +151,8 @@ describe '/db/patch', -> Patch.findOne({}).exec (err, article) -> expect(article.get('status')).toBe 'accepted' done() + + + + + From 3191c87cf104a030353ad83a5648062986ebaa7f Mon Sep 17 00:00:00 2001 From: Ruben Vereecken Date: Thu, 24 Jul 2014 14:41:06 +0200 Subject: [PATCH 46/83] Added recalculation for patches. Struggled with translations. Won't recalculate those probably --- scripts/recalculateStatistics.coffee | 3 + server/routes/admin.coffee | 4 +- server/users/User.coffee | 8 ++- server/users/user_handler.coffee | 76 ++++++++++++++++-------- test/server/functional/patch.spec.coffee | 11 +++- 5 files changed, 71 insertions(+), 31 deletions(-) diff --git a/scripts/recalculateStatistics.coffee b/scripts/recalculateStatistics.coffee index 52f051335..f1c145b90 100644 --- a/scripts/recalculateStatistics.coffee +++ b/scripts/recalculateStatistics.coffee @@ -34,4 +34,7 @@ async.series [ (c) -> report UserHandler.recalculateStats, 'levelComponentEdits', c (c) -> report UserHandler.recalculateStats, 'levelSystemEdits', c (c) -> report UserHandler.recalculateStats, 'thangTypeEdits', c + # Patches + (c) -> report UserHandler.recalculateStats, 'patchesContributed', c + (c) -> report UserHandler.recalculateStats, 'patchesSubmitted', c ], whenAllFinished diff --git a/server/routes/admin.coffee b/server/routes/admin.coffee index 2d89dfb72..c27f154e3 100644 --- a/server/routes/admin.coffee +++ b/server/routes/admin.coffee @@ -18,9 +18,9 @@ module.exports.setup = (app) -> try moduleName = module.replace '.', '_' name = handlers[moduleName] - handler = require('../' + name) + return errors.notFound res, 'Handler not found for ' + moduleName unless name? - return errors.notFound res, 'Handler not found for ' + moduleName unless handler + handler = require('../' + name) return handler[parts[1]](req, res, parts[2..]...) if parts[1] of handler return errors.notFound res, 'Method not found for handler ' + name diff --git a/server/users/User.coffee b/server/users/User.coffee index 501f6286c..844fef3ee 100644 --- a/server/users/User.coffee +++ b/server/users/User.coffee @@ -133,8 +133,12 @@ UserSchema.statics.statsMapping = 'thang.type': 'stats.thangTypeMiscPatches' UserSchema.statics.incrementStat = (id, statName, done, inc=1) -> - @findById id, (err, User) -> - User.incrementStat statName, done, inc=1 + id = mongoose.Types.ObjectId id if _.isString id + @findById id, (err, user) -> + log.error err if err? + err = new Error "Could't find user with id '#{id}'" unless user or err + return done err if err? + user.incrementStat statName, done, inc=1 UserSchema.methods.incrementStat = (statName, done, inc=1) -> @set statName, (@get(statName) or 0) + inc diff --git a/server/users/user_handler.coffee b/server/users/user_handler.coffee index edf31dc80..3a4d24194 100644 --- a/server/users/user_handler.coffee +++ b/server/users/user_handler.coffee @@ -408,6 +408,43 @@ UserHandler = class UserHandler extends Handler doneWithUser() ), done + # I don't like leaking big variables, could remove this for readability + # Meant for passing into MongoDB + {isMiscPatch, isTranslationPatch} = do -> + deltas = require '../../app/lib/deltas' + flattenDelta = _.clone deltas.flattenDelta + some = _.clone _.some + + isMiscPatch: -> + expanded = flattenDelta @delta + some expanded, (delta) -> 'i18n' not in delta.dataPath + isTranslationPatch: -> + expanded = flattenDelta @delta + some expanded, (delta) -> 'i18n' in delta.dataPath + + countPatchesByUsers = (query, statName, done) -> + Patch = require '../patches/Patch' + + User.find {}, (err, users) -> + async.eachSeries users, ((user, doneWithUser) -> + #log.debug user + userObjectID = user.get '_id' + userStringID = userObjectID.toHexString() + # Extend query with a patch ownership test + _.extend query, {$or: [{creator: userObjectID}, {creator: userStringID}]} + log.debug JSON.stringify query + + Patch.count query, (err, count) -> + method = if count then '$set' else '$unset' + update = {} + update[method] = {} + update[method][statName] = count or '' + log.debug JSON.stringify update + User.findByIdAndUpdate user.get('_id'), update, (err) -> + log.error err if err? + doneWithUser() + ), done + statRecalculators: gamesCompleted: (done) -> LevelSession = require '../levels/sessions/LevelSession' @@ -444,38 +481,27 @@ UserHandler = class UserHandler extends Handler countEdits ThangType, done patchesContributed: (done) -> - Patch = require '../patches/Patch' - - User.find {}, (err, users) -> - async.eachSeries users, ((user, doneWithUser) -> - userObjectID = user.get('_id') - userStringID = userObjectID.toHexString() - - Patch.count {$or: [{creator: userObjectID}, {creator: userStringID}], 'status': 'accepted'}, (err, count) -> - update = if count then {$set: 'stats.patchesContributed': count} else {$unset: 'stats.patchesContributed': ''} - User.findByIdAndUpdate user.get('_id'), update, (err) -> - log.error err if err? - doneWithUser() - ), done + countPatchesByUsers {'status': 'accepted'}, 'stats.patchesContributed', done patchesSubmitted: (done) -> - Patch = require '../patches/Patch' + countPatchesByUsers {}, 'stats.patchesSubmitted', done - User.find {}, (err, users) -> - async.eachSeries users, ((user, doneWithUser) -> - userObjectID = user.get('_id') - userStringID = userObjectID.toHexString() + # The below don't work + totalTranslationPatches: (done) -> + countPatchesByUsers {$where: isTranslationPatch}, 'stats.totalTranslationPatches', done - Patch.count {$or: [{creator: userObjectID}, {creator: userStringID}]}, (err, count) -> - update = if count then {$set: 'stats.patchesSubmitted': count} else {$unset: 'stats.patchesSubmitted': ''} - User.findByIdAndUpdate user.get('_id'), update, (err) -> - log.error err if err? - doneWithUser() - ), done + totalMiscPatches: (done) -> + log.debug isMiscPatch + countPatchesByUsers {$where: isMiscPatch}, 'stats.totalMiscPatches', done + articleTranslationPatches: (done) -> + countPatchesByUsers {$where: isTranslationPatch}, User.statsMapping.translations.article, done + + articleMiscPatches: (done) -> + countPatchesByUsers {$where: isMiscPatch}, User.statsMapping.translations.article, done recalculateStats: (statName, done) => - return new Error 'Recalculation handler not found' unless statName of @statRecalculators + done new Error 'Recalculation handler not found' unless statName of @statRecalculators @statRecalculators[statName] done recalculate: (req, res, statName) -> diff --git a/test/server/functional/patch.spec.coffee b/test/server/functional/patch.spec.coffee index 36715ad14..d663fafce 100644 --- a/test/server/functional/patch.spec.coffee +++ b/test/server/functional/patch.spec.coffee @@ -126,7 +126,6 @@ describe '/db/patch', -> it 'recalculates amount of submitted and accepted patches', (done) -> loginJoe (joe) -> - console.log joe User.findById joe.get('_id'), (err, joe) -> expect(joe.get 'stats.patchesSubmitted').toBe 1 joe.update {$unset: stats: ''}, (err) -> @@ -136,11 +135,18 @@ describe '/db/patch', -> async.parallel [ (done) -> UserHandler.recalculateStats 'patchesContributed', done (done) -> UserHandler.recalculateStats 'patchesSubmitted', done + (done) -> UserHandler.recalculateStats 'totalMiscPatches', done + (done) -> UserHandler.recalculateStats 'totalTranslationPatches', done + (done) -> UserHandler.recalculateStats 'articleMiscPatches', done ], (err) -> expect(err).toBeNull() UserHandler.modelClass.findById joe.get('_id'), (err, joe) -> - expect(joe.get 'stats.patchesContributed').toBe 1 expect(joe.get 'stats.patchesSubmitted').toBe 1 + expect(joe.get 'stats.patchesContributed').toBe 1 + # Recalculation of these stats doesn't work, alas + #expect(joe.get 'stats.totalMiscPatches').toBe 1 + #expect(joe.get 'stats.articleMiscPatches').toBe 1 + #expect(joe.get 'stats.totalTranslationPatches').toBeUndefined() done() it 'does not allow the recipient to withdraw the pull request', (done) -> @@ -156,3 +162,4 @@ describe '/db/patch', -> + From b2c11fa087082e70ab157d4ab60423f46029bd50 Mon Sep 17 00:00:00 2001 From: Ruben Vereecken Date: Thu, 24 Jul 2014 15:05:26 +0200 Subject: [PATCH 47/83] Added achievement recalculation script --- scripts/recalculateAchievements.coffee | 19 +++++++++++++++++ .../earned_achievement_handler.coffee | 21 ++++++++++--------- 2 files changed, 30 insertions(+), 10 deletions(-) create mode 100644 scripts/recalculateAchievements.coffee diff --git a/scripts/recalculateAchievements.coffee b/scripts/recalculateAchievements.coffee new file mode 100644 index 000000000..2ec5e02c0 --- /dev/null +++ b/scripts/recalculateAchievements.coffee @@ -0,0 +1,19 @@ +database = require '../server/commons/database' +mongoose = require 'mongoose' +log = require 'winston' +async = require 'async' + +### SET UP ### +do (setupLodash = this) -> + GLOBAL._ = require 'lodash' + _.str = require 'underscore.string' + _.mixin _.str.exports() + +database.connect() + +EarnedAchievementHandler = require '../server/achievements/earned_achievement_handler' +log.info 'Starting earned achievement recalculation...' +EarnedAchievementHandler.constructor.recalculate (err) -> + log.error err if err? + log.info 'Finished recalculating all earned achievements.' + process.exit() diff --git a/server/achievements/earned_achievement_handler.coffee b/server/achievements/earned_achievement_handler.coffee index 5c7a41aeb..313da4612 100644 --- a/server/achievements/earned_achievement_handler.coffee +++ b/server/achievements/earned_achievement_handler.coffee @@ -29,7 +29,8 @@ class EarnedAchievementHandler extends Handler achievementIDs = (thing for thing in callbackOrSlugsOrIDs when Handler.isID(thing)) else # just a callback callback = callbackOrSlugsOrIDs - onFinished = -> callback arguments... if callback? + callback = if callback then callback else -> # Make a dummy just for ease of coding + onFinished = -> callback arguments... filter = {} filter.$or = [ @@ -39,10 +40,15 @@ class EarnedAchievementHandler extends Handler # Fetch all relevant achievements Achievement.find filter, (err, achievements) -> - log.error err if err? + callback err if err? + callback new Error 'No achievements to recalculate' unless achievements.length + log.info "Recalculating a total of #{achievements.length} achievements..." # Fetch every single user User.find {}, (err, users) -> + callback err if err? + log.info "... for a total of #{users.length} users." + async.each users, ((user, doneWithUser) -> # Keep track of a user's already achieved in order to set the notified values correctly userID = user.get('_id').toHexString() @@ -97,8 +103,7 @@ class EarnedAchievementHandler extends Handler newTotalPoints += newPoints EarnedAchievement.update {achievement:earned.achievement, user:earned.user}, earned, {upsert: true}, (err) -> - log.error err if err? - doneWithAchievement() + doneWithAchievement err ), saveUserPoints = -> # In principle it is enough to deduct the old amount of points and add the new amount, # but just to be entirely safe let's start from 0 in case we're updating all of a user's achievements @@ -106,14 +111,10 @@ class EarnedAchievementHandler extends Handler log.debug "Matched a total of #{newTotalPoints} new points" if _.isEmpty filter # Completely clean log.debug "Setting this user's score to #{newTotalPoints}" - User.update {_id: userID}, {$set: points: newTotalPoints}, {}, (err) -> - log.error err if err? - doneWithUser() + User.update {_id: userID}, {$set: points: newTotalPoints}, {}, doneWithUser else log.debug "Incrementing score for these achievements with #{newTotalPoints - previousPoints}" - User.update {_id: userID}, {$inc: points: newTotalPoints - previousPoints}, {}, (err) -> - log.error err if err? - doneWithUser() + User.update {_id: userID}, {$inc: points: newTotalPoints - previousPoints}, {}, doneWithUser ), onFinished module.exports = new EarnedAchievementHandler() From a03e3aedd1c6dad7fe6f56764b9e4bbc3fce8689 Mon Sep 17 00:00:00 2001 From: Ruben Vereecken Date: Thu, 24 Jul 2014 19:42:43 +0200 Subject: [PATCH 48/83] Managed to recalculate translation/misc patches after all --- scripts/recalculateStatistics.coffee | 16 +++- server/patches/Patch.coffee | 4 +- server/users/user_handler.coffee | 93 +++++++++++++++++------- test/server/functional/patch.spec.coffee | 7 +- 4 files changed, 88 insertions(+), 32 deletions(-) diff --git a/scripts/recalculateStatistics.coffee b/scripts/recalculateStatistics.coffee index f1c145b90..7177acf9d 100644 --- a/scripts/recalculateStatistics.coffee +++ b/scripts/recalculateStatistics.coffee @@ -25,7 +25,7 @@ whenAllFinished = -> log.info 'All recalculations finished.' process.exit() -async.series [ +async.parallel [ # Misc (c) -> report UserHandler.recalculateStats, 'gamesCompleted', c # Edits @@ -37,4 +37,18 @@ async.series [ # Patches (c) -> report UserHandler.recalculateStats, 'patchesContributed', c (c) -> report UserHandler.recalculateStats, 'patchesSubmitted', c + (c) -> report UserHandler.recalculateStats, 'totalTranslationPatches', c + (c) -> report UserHandler.recalculateStats, 'totalMiscPatches', c + + (c) -> report UserHandler.recalculateStats, 'articleMiscPatches', c + (c) -> report UserHandler.recalculateStats, 'levelMiscPatches', c + (c) -> report UserHandler.recalculateStats, 'levelComponentMiscPatches', c + (c) -> report UserHandler.recalculateStats, 'levelSystemMiscPatches', c + (c) -> report UserHandler.recalculateStats, 'thangTypeMiscPatches', c + + (c) -> report UserHandler.recalculateStats, 'articleTranslationPatches', c + (c) -> report UserHandler.recalculateStats, 'levelTranslationPatches', c + (c) -> report UserHandler.recalculateStats, 'levelComponentTranslationPatches', c + (c) -> report UserHandler.recalculateStats, 'levelSystemTranslationPatches', c + (c) -> report UserHandler.recalculateStats, 'thangTypeTranslationPatches', c ], whenAllFinished diff --git a/server/patches/Patch.coffee b/server/patches/Patch.coffee index bba72389b..9080849fc 100644 --- a/server/patches/Patch.coffee +++ b/server/patches/Patch.coffee @@ -1,5 +1,6 @@ mongoose = require('mongoose') deltas = require '../../app/lib/deltas' +log = require 'winston' {handlers} = require '../commons/mapping' PatchSchema = new mongoose.Schema({status: String}, {strict: false}) @@ -46,7 +47,7 @@ PatchSchema.pre 'save', (next) -> @targetLoaded = document document.save (err) -> next(err) -PatchSchema.methods.isTranslationPatch = -> +PatchSchema.methods.isTranslationPatch = -> # Don't ever fat arrow bind this one expanded = deltas.flattenDelta @get('delta') _.some expanded, (delta) -> 'i18n' in delta.dataPath @@ -74,5 +75,4 @@ PatchSchema.pre 'save', (next) -> next() - module.exports = mongoose.model('patch', PatchSchema) diff --git a/server/users/user_handler.coffee b/server/users/user_handler.coffee index 3a4d24194..b8f58f9c1 100644 --- a/server/users/user_handler.coffee +++ b/server/users/user_handler.coffee @@ -412,37 +412,58 @@ UserHandler = class UserHandler extends Handler # Meant for passing into MongoDB {isMiscPatch, isTranslationPatch} = do -> deltas = require '../../app/lib/deltas' - flattenDelta = _.clone deltas.flattenDelta - some = _.clone _.some - isMiscPatch: -> - expanded = flattenDelta @delta - some expanded, (delta) -> 'i18n' not in delta.dataPath - isTranslationPatch: -> - expanded = flattenDelta @delta - some expanded, (delta) -> 'i18n' in delta.dataPath + isMiscPatch: (obj) -> + expanded = deltas.flattenDelta obj.get 'delta' + _.some expanded, (delta) -> 'i18n' not in delta.dataPath + isTranslationPatch: (obj) -> + expanded = deltas.flattenDelta obj.get 'delta' + _.some expanded, (delta) -> 'i18n' in delta.dataPath + + Patch = require '../patches/Patch' + # filter is passed a mongoose document and should return a boolean, + # determining whether the patch should be counted + countPatchesByUsersInMemory = (query, filter, statName, done) -> + updateUser = (user, count, doneUpdatingUser) -> + method = if count then '$set' else '$unset' + update = {} + update[method] = {} + update[method][statName] = count or '' + User.findByIdAndUpdate user.get('_id'), update, doneUpdatingUser + + User.find {}, (err, users) -> + async.eachSeries users, ((user, doneWithUser) -> + userObjectID = user.get '_id' + userStringID = userObjectID.toHexString() + # Extend query with a patch ownership test + _.extend query, {$or: [{creator: userObjectID}, {creator: userStringID}]} + + count = 0 + stream = Patch.where(query).stream() + stream.on 'data', (doc) -> ++count if filter doc + stream.on 'error', (err) -> + updateUser user, count, doneWithUser + log.error "Recalculating #{statName} for user #{user} stopped prematurely because of error" + stream.on 'close', -> + updateUser user, count, doneWithUser + ), done countPatchesByUsers = (query, statName, done) -> Patch = require '../patches/Patch' User.find {}, (err, users) -> async.eachSeries users, ((user, doneWithUser) -> - #log.debug user userObjectID = user.get '_id' userStringID = userObjectID.toHexString() # Extend query with a patch ownership test _.extend query, {$or: [{creator: userObjectID}, {creator: userStringID}]} - log.debug JSON.stringify query Patch.count query, (err, count) -> method = if count then '$set' else '$unset' update = {} update[method] = {} update[method][statName] = count or '' - log.debug JSON.stringify update - User.findByIdAndUpdate user.get('_id'), update, (err) -> - log.error err if err? - doneWithUser() + User.findByIdAndUpdate user.get('_id'), update, doneWithUser ), done statRecalculators: @@ -455,9 +476,7 @@ UserHandler = class UserHandler extends Handler LevelSession.count {creator: userID, 'state.completed': true}, (err, count) -> update = if count then {$set: 'stats.gamesCompleted': count} else {$unset: 'stats.gamesCompleted': ''} - User.findByIdAndUpdate user.get('_id'), update, (err) -> - log.error err if err? - doneWithUser() + User.findByIdAndUpdate user.get('_id'), update, doneWithUser ), done articleEdits: (done) -> @@ -486,20 +505,44 @@ UserHandler = class UserHandler extends Handler patchesSubmitted: (done) -> countPatchesByUsers {}, 'stats.patchesSubmitted', done - # The below don't work + # The below need functions for filtering and are thus checked in memory totalTranslationPatches: (done) -> - countPatchesByUsers {$where: isTranslationPatch}, 'stats.totalTranslationPatches', done + countPatchesByUsersInMemory {}, isTranslationPatch, 'stats.totalTranslationPatches', done totalMiscPatches: (done) -> - log.debug isMiscPatch - countPatchesByUsers {$where: isMiscPatch}, 'stats.totalMiscPatches', done - - articleTranslationPatches: (done) -> - countPatchesByUsers {$where: isTranslationPatch}, User.statsMapping.translations.article, done + countPatchesByUsersInMemory {}, isMiscPatch, 'stats.totalMiscPatches', done articleMiscPatches: (done) -> - countPatchesByUsers {$where: isMiscPatch}, User.statsMapping.translations.article, done + countPatchesByUsersInMemory {'target.collection': 'article'}, isMiscPatch, User.statsMapping.misc.article, done + levelMiscPatches: (done) -> + countPatchesByUsersInMemory {'target.collection': 'level'}, isMiscPatch, User.statsMapping.misc.level, done + + levelComponentMiscPatches: (done) -> + countPatchesByUsersInMemory {'target.collection': 'level_component'}, isMiscPatch, User.statsMapping.misc['level.component'], done + + levelSystemMiscPatches: (done) -> + countPatchesByUsersInMemory {'target.collection': 'level_system'}, isMiscPatch, User.statsMapping.misc['level.system'], done + + thangTypeMiscPatches: (done) -> + countPatchesByUsersInMemory {'target.collection': 'thang_type'}, isMiscPatch, User.statsMapping.misc['thang.type'], done + + articleTranslationPatches: (done) -> + countPatchesByUsersInMemory {'target.collection': 'article'}, isTranslationPatch, User.statsMapping.translations.article, done + + levelTranslationPatches: (done) -> + countPatchesByUsersInMemory {'target.collection': 'level'}, isTranslationPatch, User.statsMapping.translations.level, done + + levelComponentTranslationPatches: (done) -> + countPatchesByUsersInMemory {'target.collection': 'level_component'}, isTranslationPatch, User.statsMapping.translations['level.component'], done + + levelSystemTranslationPatches: (done) -> + countPatchesByUsersInMemory {'target.collection': 'level_system'}, isTranslationPatch, User.statsMapping.translations['level.system'], done + + thangTypeTranslationPatches: (done) -> + countPatchesByUsersInMemory {'target.collection': 'thang_type'}, isTranslationPatch, User.statsMapping.translations['thang.type'], done + + recalculateStats: (statName, done) => done new Error 'Recalculation handler not found' unless statName of @statRecalculators @statRecalculators[statName] done diff --git a/test/server/functional/patch.spec.coffee b/test/server/functional/patch.spec.coffee index d663fafce..798032e3c 100644 --- a/test/server/functional/patch.spec.coffee +++ b/test/server/functional/patch.spec.coffee @@ -143,10 +143,9 @@ describe '/db/patch', -> UserHandler.modelClass.findById joe.get('_id'), (err, joe) -> expect(joe.get 'stats.patchesSubmitted').toBe 1 expect(joe.get 'stats.patchesContributed').toBe 1 - # Recalculation of these stats doesn't work, alas - #expect(joe.get 'stats.totalMiscPatches').toBe 1 - #expect(joe.get 'stats.articleMiscPatches').toBe 1 - #expect(joe.get 'stats.totalTranslationPatches').toBeUndefined() + expect(joe.get 'stats.totalMiscPatches').toBe 1 + expect(joe.get 'stats.articleMiscPatches').toBe 1 + expect(joe.get 'stats.totalTranslationPatches').toBeUndefined() done() it 'does not allow the recipient to withdraw the pull request', (done) -> From 15c19396577058dd5b7cf19089a91f0005c9c378 Mon Sep 17 00:00:00 2001 From: Ruben Vereecken Date: Mon, 28 Jul 2014 14:13:38 +0200 Subject: [PATCH 49/83] Started on achievement setup script --- app/schemas/models/achievement.coffee | 15 ++++-- scripts/setupAchievements.coffee | 68 +++++++++++++++++++++++++++ 2 files changed, 80 insertions(+), 3 deletions(-) create mode 100644 scripts/setupAchievements.coffee diff --git a/app/schemas/models/achievement.coffee b/app/schemas/models/achievement.coffee index 417fda0f4..bd14cd251 100644 --- a/app/schemas/models/achievement.coffee +++ b/app/schemas/models/achievement.coffee @@ -34,10 +34,10 @@ MongoFindQuerySchema.definitions[MongoQueryOperatorSchema.id] = MongoQueryOperat AchievementSchema = c.object() c.extendNamedProperties AchievementSchema -c.extendBasicProperties AchievementSchema, 'article' +c.extendBasicProperties AchievementSchema, 'achievement' # TODO What's this about? c.extendSearchableProperties AchievementSchema -_.extend(AchievementSchema.properties, +_.extend AchievementSchema.properties, query: #type:'object' $ref: '#/definitions/' + MongoFindQuerySchema.id @@ -47,11 +47,17 @@ _.extend(AchievementSchema.properties, userField: {type: 'string'} related: c.objectId(description: 'Related entity') icon: {type: 'string', format: 'image-file', title: 'Icon'} + category: + type: 'string' + description: "E.g. 'level', 'ladder', 'contributor'..." # TODO might make this enum? + difficulty: c.int + description: 'The higher the more difficult' proportionalTo: type: 'string' description: 'For repeatables only. Denotes the field a repeatable achievement needs for its calculations' function: type: 'object' + description: 'Function that gives total experience for X amount achieved' properties: kind: {enum: ['linear', 'logarithmic', 'quadratic'], default: 'linear'} parameters: @@ -64,7 +70,10 @@ _.extend(AchievementSchema.properties, default: {kind: 'linear', parameters: a: 1} required: ['kind', 'parameters'] additionalProperties: false -) + +_.extend AchievementSchema, # Let's have these on the bottom + required: ['query', 'worth', 'collection', 'userField', 'category', 'difficulty'] + additionalProperties: false AchievementSchema.definitions = {} AchievementSchema.definitions[MongoFindQuerySchema.id] = MongoFindQuerySchema diff --git a/scripts/setupAchievements.coffee b/scripts/setupAchievements.coffee new file mode 100644 index 000000000..a3463cd4b --- /dev/null +++ b/scripts/setupAchievements.coffee @@ -0,0 +1,68 @@ +database = require '../server/commons/database' +mongoose = require 'mongoose' +log = require 'winston' +async = require 'async' + +### SET UP ### +do (setupLodash = this) -> + GLOBAL._ = require 'lodash' + _.str = require 'underscore.string' + _.mixin _.str.exports() + +database.connect() + +achievements = + signup: + name: 'Signed up' + description: 'Signed up to the most awesome coding game around.' + query: 'anonymous': false + worth: 10 + collection: 'users' + userField: '_id' + category: 'Miscellaneous' + difficulty: 1 + + completedFirstLevel: + name: 'Completed one Level' + description: 'Completed your very first level.' + query: 'stats.gamesCompleted': $gte: 1 + worth: 50 + collection: 'users' + userField: '_id' + category: 'Levels' + difficulty: 1 + + simulatedBy: + name: 'Simulated ladder game' + description: 'Simulated a ladder game.' + query: 'simulatedBy': $gte: 1 + worth: 1 + collection: 'users' + userField: '_id' + category: 'Miscellaneous' + difficulty: 1 + proportionalTo: 'simulatedBy' + function: + kind: 'logarithmic' + parameters: # TODO tweak + a: 5 + b: 1 + c: 0 + +Achievement = require '../server/achievements/Achievement' + +Achievement.remove {}, (err) -> + log.error err if err? + log.info 'Removed all achievements.' + + async.each Object.keys(achievements), (key, callback) -> + achievement = achievements[key] + log.info "Setting up '#{achievement.name}'..." + achievement = new Achievement achievement + achievement.save (err) -> + log.error err if err? + callback() + , (err) -> + log.error err if err? + log.info 'Finished setting up achievements.' + process.exit() From a8df3df0956be7b73561316f8cdf4d32c0fc97b0 Mon Sep 17 00:00:00 2001 From: Ruben Vereecken Date: Mon, 28 Jul 2014 15:14:11 +0200 Subject: [PATCH 50/83] Default image for achievements. Redid border logic --- app/application.coffee | 7 +++- app/models/Achievement.coffee | 12 ++++++ app/schemas/models/achievement.coffee | 5 ++- app/styles/achievements.sass | 36 +++++++++++++----- app/templates/achievement_notify.jade | 2 +- app/views/editor/achievement/edit.coffee | 2 +- app/views/kinds/RootView.coffee | 5 +-- .../achievements/{stars-01.png => stars.png} | Bin 8 files changed, 52 insertions(+), 17 deletions(-) rename public/images/achievements/{stars-01.png => stars.png} (100%) diff --git a/app/application.coffee b/app/application.coffee index fcfac2a90..9a4bb1936 100644 --- a/app/application.coffee +++ b/app/application.coffee @@ -40,7 +40,12 @@ Application = initialize: -> @facebookHandler = new FacebookHandler() @gplusHandler = new GPlusHandler() $(document).bind 'keydown', preventBackspace - $.notify.addStyle 'achievement', html: $(AchievementNotify popup:true) + #$.notify.addStyle 'achievement', html: $(AchievementNotify popup:true) + $.notify.addStyle 'achievement-wood', html: $(AchievementNotify popup:true) + $.notify.addStyle 'achievement-stone', html: $(AchievementNotify popup:true) + $.notify.addStyle 'achievement-silver', html: $(AchievementNotify popup:true) + $.notify.addStyle 'achievement-gold', html: $(AchievementNotify popup:true) + $.notify.addStyle 'achievement-diamong', html: $(AchievementNotify popup:true) @linkedinHandler = new LinkedInHandler() preload(COMMON_FILES) $.i18n.init { diff --git a/app/models/Achievement.coffee b/app/models/Achievement.coffee index 4546c3453..27712d52b 100644 --- a/app/models/Achievement.coffee +++ b/app/models/Achievement.coffee @@ -14,3 +14,15 @@ module.exports = class Achievement extends CocoModel kind = @get('function')?.kind or jsonschema.properties.function.default.kind parameters = @get('function')?.parameters or jsonschema.properties.function.default.parameters return utils.functionCreators[kind](parameters) if kind of utils.functionCreators + + @styleMapping: + 1: 'achievement-wood' + 2: 'achievement-stone' + 3: 'achievement-silver' + 4: 'achievement-gold' + 5: 'achievement-diamond' + + getNotifyStyle: -> Achievement.styleMapping[@get 'difficulty'] + + getImageURL: -> + if @get 'icon' then '/file/' + @get('icon') else '/images/achievements/stars.png' diff --git a/app/schemas/models/achievement.coffee b/app/schemas/models/achievement.coffee index bd14cd251..a41627861 100644 --- a/app/schemas/models/achievement.coffee +++ b/app/schemas/models/achievement.coffee @@ -24,8 +24,9 @@ MongoFindQuerySchema = '^[-a-zA-Z0-9\.]*$': oneOf: [ #{$ref: '#/definitions/' + MongoQueryOperatorSchema.id}, - {type: 'string'}, + {type: 'string'} {type: 'object'} + {type: 'boolean'} ] additionalProperties: true # TODO make Treema accept new pattern matched keys definitions: {} @@ -72,7 +73,7 @@ _.extend AchievementSchema.properties, additionalProperties: false _.extend AchievementSchema, # Let's have these on the bottom - required: ['query', 'worth', 'collection', 'userField', 'category', 'difficulty'] + required: ['name', 'description', 'query', 'worth', 'collection', 'userField', 'category', 'difficulty'] additionalProperties: false AchievementSchema.definitions = {} diff --git a/app/styles/achievements.sass b/app/styles/achievements.sass index 6bc064c4e..e99e3766b 100644 --- a/app/styles/achievements.sass +++ b/app/styles/achievements.sass @@ -38,14 +38,6 @@ margin-top: auto margin-bottom: 0px !important - .achievement-border-wood - background: url("/images/achievements/border_wood.png") no-repeat - background-size: 100% 100% - - .achievement-border-silver - background: url("/images/achievements/border_silver.png") no-repeat - background-size: 100% 100% - #user-achievements-view .row //.col-lg-4, .col-xs-12 @@ -83,7 +75,8 @@ line-height: 1.3em max-height: 2.6em -.notifyjs-achievement-base +.notifyjs-achievement-wood-base, .notifyjs-achievement-stone-base, .notifyjs-achievement-silver-base, +.notifyjs-achievement-gold-base, .notifyjs-achievement-diamond-base padding: 20px 0px .achievement-body @@ -150,5 +143,30 @@ .progress-bar-border img width: 100% +.notifyjs-achievement-wood-base + .achievement-icon + background: url("/images/achievements/border_wood.png") no-repeat + background-size: 100% 100% + +.notifyjs-achievement-stone-base + .achievement-icon + background: url("/images/achievements/border_stone.png") no-repeat + background-size: 100% 100% + +.notifyjs-achievement-silver-base + .achievement-icon + background: url("/images/achievements/border_silver.png") no-repeat + background-size: 100% 100% + +.notifyjs-achievement-gold-base + .achievement-icon + background: url("/images/achievements/border_gold.png") no-repeat + background-size: 100% 100% + +.notifyjs-achievement-diamond-base + .achievement-icon + background: url("/images/achievements/border_diamond.png") no-repeat + background-size: 100% 100% + .progress-bar-white background-color: white diff --git a/app/templates/achievement_notify.jade b/app/templates/achievement_notify.jade index 1ea25416d..2e9e64650 100644 --- a/app/templates/achievement_notify.jade +++ b/app/templates/achievement_notify.jade @@ -1,6 +1,6 @@ div .clearfix.achievement-body - .achievement-icon.achievement-border-silver(class=locked === true ? "locked" : "") + .achievement-icon(class=locked === true ? "locked" : "", class=border) .achievement-image(data-notify-html="image") if imgURL img(src=imgURL) diff --git a/app/views/editor/achievement/edit.coffee b/app/views/editor/achievement/edit.coffee index a2d200b7d..f21800a25 100644 --- a/app/views/editor/achievement/edit.coffee +++ b/app/views/editor/achievement/edit.coffee @@ -71,7 +71,7 @@ module.exports = class AchievementEditView extends RootView data = @createNotifyData @achievement, earned options = - style: 'achievement' + style: @achievement.getNotifyStyle() autoHide: false clickToHide: false arrowShow: false diff --git a/app/views/kinds/RootView.coffee b/app/views/kinds/RootView.coffee index 2f80775e4..54202dab2 100644 --- a/app/views/kinds/RootView.coffee +++ b/app/views/kinds/RootView.coffee @@ -80,10 +80,9 @@ module.exports = class RootView extends CocoView $actualHover.trigger e if $actualHover # TODO a default should be linked here - imageURL = '/file/' + achievement.get('icon') data = title: achievement.get('name') - image: $("") + image: $("") description: achievement.get('description') progressBar: progressBar earnedExp: "+ #{achievedExp} XP" @@ -99,7 +98,7 @@ module.exports = class RootView extends CocoView autoHideDelay: 10000 globalPosition: 'bottom right' showDuration: 400 - style: 'achievement' + style: achievement.getNotifyStyle() autoHide: true clickToHide: true diff --git a/public/images/achievements/stars-01.png b/public/images/achievements/stars.png similarity index 100% rename from public/images/achievements/stars-01.png rename to public/images/achievements/stars.png From 194463c1132ce33f7998dba3a9f0c0150ecdb34a Mon Sep 17 00:00:00 2001 From: Ruben Vereecken Date: Mon, 28 Jul 2014 17:40:20 +0200 Subject: [PATCH 51/83] Greatly redid achievement styling for various scenarios --- app/application.coffee | 3 +- app/styles/achievements.sass | 71 +++++++++++-------- app/templates/achievement_notify.jade | 15 ++-- app/views/kinds/RootView.coffee | 9 +-- test/demo/fixtures/achievements.coffee | 2 +- .../achievement/AchievementGet.demo.coffee | 7 +- 6 files changed, 59 insertions(+), 48 deletions(-) diff --git a/app/application.coffee b/app/application.coffee index 9a4bb1936..b0f4c5ba0 100644 --- a/app/application.coffee +++ b/app/application.coffee @@ -40,12 +40,11 @@ Application = initialize: -> @facebookHandler = new FacebookHandler() @gplusHandler = new GPlusHandler() $(document).bind 'keydown', preventBackspace - #$.notify.addStyle 'achievement', html: $(AchievementNotify popup:true) $.notify.addStyle 'achievement-wood', html: $(AchievementNotify popup:true) $.notify.addStyle 'achievement-stone', html: $(AchievementNotify popup:true) $.notify.addStyle 'achievement-silver', html: $(AchievementNotify popup:true) $.notify.addStyle 'achievement-gold', html: $(AchievementNotify popup:true) - $.notify.addStyle 'achievement-diamong', html: $(AchievementNotify popup:true) + $.notify.addStyle 'achievement-diamond', html: $(AchievementNotify popup:true) @linkedinHandler = new LinkedInHandler() preload(COMMON_FILES) $.i18n.init { diff --git a/app/styles/achievements.sass b/app/styles/achievements.sass index e99e3766b..01d60f552 100644 --- a/app/styles/achievements.sass +++ b/app/styles/achievements.sass @@ -25,11 +25,11 @@ text-align: center overflow: hidden - .achievement-title + > .achievement-title font-family: Bangers overflow: hidden - .achievement-description + > .achievement-description white-space: initial font-size: 12px line-height: 1.3em @@ -37,6 +37,7 @@ max-height: 2.6em margin-top: auto margin-bottom: 0px !important + padding-left: 8px #user-achievements-view .row @@ -78,9 +79,11 @@ .notifyjs-achievement-wood-base, .notifyjs-achievement-stone-base, .notifyjs-achievement-silver-base, .notifyjs-achievement-gold-base, .notifyjs-achievement-diamond-base padding: 20px 0px + cursor: default .achievement-body .achievement-icon + z-index: 1000 width: 200px height: 200px left: -140px @@ -96,6 +99,7 @@ bottom: 0 .achievement-content + position: relative width: 450px height: 160px padding: 24px 30px 20px 60px @@ -111,37 +115,38 @@ margin-top: auto margin-bottom: 0px !important - .achievement-progress - padding: 8px 0px 0px 0px + .progress-wrapper + margin-left: 20px + position: absolute + bottom: 48px - .achievement-message - font-family: Bangers - font-size: 18px - &:empty - display: none - - .progress-wrapper + .achievement-level + font-size: 20px + z-index: 1000 position: absolute - padding-right: 30px - bottom: 0px + left: -15px + margin-top: -3px + box-shadow: 0 0 0 2px black, 0 0 0 3px lightgrey, 0 0 0 5px black - .achievement-level - float: left - font-size: 15px + > .progress-bar-wrapper + position: absolute + width: 331px + height: 20px + z-index: 2 - .progress-bar-wrapper - padding-left: 30px - width: 100% - .progress - border-radius: 20px + > .progress + margin-top: 5px + border-radius: 50px + height: 14px - .progress-bar-border - padding-left: 30px - position: relative - top: -44px - - .progress-bar-border img - width: 100% + > .progress-bar-border + position: absolute + width: 340px + height: 30px + margin-top: -2px + background: url("/images/achievements/bar_border.png") no-repeat + background-size: 100% 100% + z-index: 1 .notifyjs-achievement-wood-base .achievement-icon @@ -168,5 +173,11 @@ background: url("/images/achievements/border_diamond.png") no-repeat background-size: 100% 100% -.progress-bar-white - background-color: white +.exp-bar-accumulated + background-color: #680080 + +.exp-bar-new + background-color: #0096ff + +.exp-bar-left + background-color: #fffbfd diff --git a/app/templates/achievement_notify.jade b/app/templates/achievement_notify.jade index 2e9e64650..8348d5bf0 100644 --- a/app/templates/achievement_notify.jade +++ b/app/templates/achievement_notify.jade @@ -9,11 +9,10 @@ div p.achievement-description(data-notify-html="description") #{description} if popup - .achievement-progress - .achievement-message(data-notify-html="message") - .progress-wrapper - //.earned-exp(data-notify-html="earnedExp") - span.achievement-level.badge(data-notify-html="level") - .progress-bar-wrapper(data-notify-html="progressBar") - .progress-bar-border(data-notify-html="barBorder") - //img(src='/images/achievements/bar_border.png' width='100%') + .progress-wrapper + //.earned-exp(data-notify-html="earnedExp") + span.achievement-level.badge(data-notify-html="level") + .progress-bar-wrapper(data-notify-html="progressBar") + .progress-bar-border + //(data-notify-html="barBorder") + //img(src='/images/achievements/bar_border.png' width='100%') diff --git a/app/views/kinds/RootView.coffee b/app/views/kinds/RootView.coffee index 54202dab2..52f9e9190 100644 --- a/app/views/kinds/RootView.coffee +++ b/app/views/kinds/RootView.coffee @@ -49,9 +49,9 @@ module.exports = class RootView extends CocoView console.debug "Current level is #{currentLevel} (#{currentLevelExp} xp), next level is #{nextLevel} (#{nextLevelExp} xp)." console.debug "Need a total of #{nextLevelExp - currentLevelExp}, already had #{previousExp} and just now earned #{achievedExp} totalling on #{currentExp}" - alreadyAchievedBar = $("
") - newlyAchievedBar = $("
") - emptyBar = $("
") + alreadyAchievedBar = $("
") + newlyAchievedBar = $("
") + emptyBar = $("
") progressBar = $('
').append(alreadyAchievedBar).append(newlyAchievedBar).append(emptyBar) #message = if (currentLevel isnt 1) and leveledUp then "Reached level #{currentLevel}!" else null @@ -61,6 +61,7 @@ module.exports = class RootView extends CocoView barBorder = $('') + ### barBorder.hover (e) -> #console.debug e x = e.pageX @@ -79,7 +80,7 @@ module.exports = class RootView extends CocoView #console.debug $actualHover $actualHover.trigger e if $actualHover - # TODO a default should be linked here + ### data = title: achievement.get('name') image: $("") diff --git a/test/demo/fixtures/achievements.coffee b/test/demo/fixtures/achievements.coffee index 9d8ec0be7..3dceb1d39 100644 --- a/test/demo/fixtures/achievements.coffee +++ b/test/demo/fixtures/achievements.coffee @@ -5,7 +5,7 @@ module.exports.DungeonArenaStarted = DungeonArenaStarted = _id: '53ba76249259823746b6b481' name: 'Dungeon Arena Started' description: 'Started playing Dungeon Arena. It was a really really hard game. So hard in fact, that this line should already be spanning' - icon: '/images/achievements/swords-01.png' + #icon: '/images/achievements/swords-01.png' worth: 3 collection: 'level.session' query: "{\"level.original\":\"dungeon-arena\"}" diff --git a/test/demo/views/achievement/AchievementGet.demo.coffee b/test/demo/views/achievement/AchievementGet.demo.coffee index c09a42fe2..0b8fb2e04 100644 --- a/test/demo/views/achievement/AchievementGet.demo.coffee +++ b/test/demo/views/achievement/AchievementGet.demo.coffee @@ -19,14 +19,15 @@ module.exports = -> console.log currentView data = currentView.createNotifyData unlockable, earnedUnlockable - imageURL = '/images/achievements/swords-01.png' - data.image = $("") options = autoHideDelay: 10000 globalPosition: 'bottom right' showDuration: 400 - style: 'achievement' + style: 'achievement-silver' autoHide: false clickToHide: false $.notify data, options + + view = new RootView + view.render() From 7987e1d05b0c2143c4547ca1eeac554c5b840c93 Mon Sep 17 00:00:00 2001 From: Ruben Vereecken Date: Mon, 28 Jul 2014 20:04:44 +0200 Subject: [PATCH 52/83] Did a bunch related to achievements and user profile page --- app/models/Achievement.coffee | 2 +- app/styles/achievements.sass | 10 +++++++--- app/styles/common/top_nav.sass | 15 +++++++++++++-- app/styles/user/home.sass | 2 +- app/templates/achievement_notify.jade | 5 +---- app/templates/base.jade | 1 + app/templates/user/home.jade | 2 +- app/views/kinds/RootView.coffee | 2 -- public/images/achievements/default.png | Bin 0 -> 7916 bytes 9 files changed, 25 insertions(+), 14 deletions(-) create mode 100644 public/images/achievements/default.png diff --git a/app/models/Achievement.coffee b/app/models/Achievement.coffee index 27712d52b..494c29091 100644 --- a/app/models/Achievement.coffee +++ b/app/models/Achievement.coffee @@ -25,4 +25,4 @@ module.exports = class Achievement extends CocoModel getNotifyStyle: -> Achievement.styleMapping[@get 'difficulty'] getImageURL: -> - if @get 'icon' then '/file/' + @get('icon') else '/images/achievements/stars.png' + if @get 'icon' then '/file/' + @get('icon') else '/images/achievements/default.png' diff --git a/app/styles/achievements.sass b/app/styles/achievements.sass index 01d60f552..c91cb9237 100644 --- a/app/styles/achievements.sass +++ b/app/styles/achievements.sass @@ -120,13 +120,12 @@ position: absolute bottom: 48px - .achievement-level + .user-level font-size: 20px - z-index: 1000 position: absolute left: -15px margin-top: -3px - box-shadow: 0 0 0 2px black, 0 0 0 3px lightgrey, 0 0 0 5px black + box-shadow: 0 0 0 2px black, 0 0 0 4px lightgrey, 0 0 0 6px black > .progress-bar-wrapper position: absolute @@ -181,3 +180,8 @@ .exp-bar-left background-color: #fffbfd + +.user-level + z-index: 1000 + box-shadow: 0 0 0 1px black, 0 0 0 3px lightgrey, 0 0 0 4px black + font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif diff --git a/app/styles/common/top_nav.sass b/app/styles/common/top_nav.sass index 7e0feece9..129070977 100644 --- a/app/styles/common/top_nav.sass +++ b/app/styles/common/top_nav.sass @@ -73,14 +73,15 @@ a.disabled border-radius: 0px font-family: Bangers - .user-dropdown-header + > .user-dropdown-header + position: relative background: #E4CF8C height: 160px padding: 10px text-align: center color: black border-bottom: #32281e 1px solid - a:hover + > a:hover background-color: transparent img border: #e3be7a 8px solid @@ -91,6 +92,16 @@ a.disabled margin-top: 10px text-shadow: 2px 2px 3px white color: #31281E + .user-level + position: absolute + top: 85px + right: 100px + font-size: 20px + border-radius: 50% + background-color: #FFE4BC + box-shadow: 0 0 0 1px black // disable for double border + color: gold + text-shadow: 1px 1px black, -1px -1px 0 black, 1px -1px 0 black, -1px 1px 0 black .user-dropdown-body color: black diff --git a/app/styles/user/home.sass b/app/styles/user/home.sass index 052434f3f..bbb58a696 100644 --- a/app/styles/user/home.sass +++ b/app/styles/user/home.sass @@ -18,7 +18,7 @@ > .picture width: 100% - background-color: #dadada + background-color: #ffe4bc border: 4px solid white > .name diff --git a/app/templates/achievement_notify.jade b/app/templates/achievement_notify.jade index 8348d5bf0..a01b832c4 100644 --- a/app/templates/achievement_notify.jade +++ b/app/templates/achievement_notify.jade @@ -10,9 +10,6 @@ div if popup .progress-wrapper - //.earned-exp(data-notify-html="earnedExp") - span.achievement-level.badge(data-notify-html="level") + span.user-level.badge(data-notify-html="level") .progress-bar-wrapper(data-notify-html="progressBar") .progress-bar-border - //(data-notify-html="barBorder") - //img(src='/images/achievements/bar_border.png' width='100%') diff --git a/app/templates/base.jade b/app/templates/base.jade index f3edd46a0..d810526c9 100644 --- a/app/templates/base.jade +++ b/app/templates/base.jade @@ -43,6 +43,7 @@ body span.caret ul.dropdown-menu(role="menu") li.user-dropdown-header + span.user-level.badge= me.level() a(href="/user/#{me.get('slug') || me.get('_id')}") img.img-circle(src="#{me.getPhotoURL()}" alt="") h3=me.get('name') || 'Anoner' diff --git a/app/templates/user/home.jade b/app/templates/user/home.jade index aa502a999..40fb2311c 100644 --- a/app/templates/user/home.jade +++ b/app/templates/user/home.jade @@ -12,7 +12,7 @@ block append content a.btn.btn-default(href="/user/#{user.get('slug') || user.get('_id')}/profile") i.glyphicon.glyphicon-briefcase span Job Profile - a.btn.btn-default(href="profile") + a.btn.btn-default.disabled(href="#") i.glyphicon.glyphicon-pencil span Code - var emails = user.get('emails') diff --git a/app/views/kinds/RootView.coffee b/app/views/kinds/RootView.coffee index 52f9e9190..17d313399 100644 --- a/app/views/kinds/RootView.coffee +++ b/app/views/kinds/RootView.coffee @@ -53,7 +53,6 @@ module.exports = class RootView extends CocoView newlyAchievedBar = $("
") emptyBar = $("
") progressBar = $('
').append(alreadyAchievedBar).append(newlyAchievedBar).append(emptyBar) - #message = if (currentLevel isnt 1) and leveledUp then "Reached level #{currentLevel}!" else null alreadyAchievedBar.tooltip(title: "#{currentExp} XP in total") newlyAchievedBar.tooltip(title: "#{achievedExp} XP earned") @@ -87,7 +86,6 @@ module.exports = class RootView extends CocoView description: achievement.get('description') progressBar: progressBar earnedExp: "+ #{achievedExp} XP" - #message: message level: currentLevel barBorder: barBorder diff --git a/public/images/achievements/default.png b/public/images/achievements/default.png new file mode 100644 index 0000000000000000000000000000000000000000..690d8e2cfd34288918e766eceaefb346fcd1943c GIT binary patch literal 7916 zcmV|F_XRMovdGjnGqlaPe$kdTmtB~dT|*)akl1++o|pHQ({+uHWEuYKRsF0cLG zt6OcWt-iX{_nuuWeOA<3ML|RXiL!)!$wC4l`$G18oAti)zcY6@_uO;t%p?;sV9)oR zZ%DZF!-mC>P8}U~*Pqk7NzO#F(16QY~LIb%xuL#9F95M~23mNc~!09~T z=_%k$1aM)*s|DV_^Y~xyZz`|f%sWMLPU>45f422C4_L!gwGzOx02D(Fz)%EFM}a30 zj58B}$)JV9``3T?hetXa+L!q{P1uC6+6_P3`X6zr2_5+35LIOs(hPJd*cT!I>O+)u zI)Zh20z6VOW#GcM|LBiD@CP_3!~Mfk-+kisr||C+JjbvQEH-)0ArR-*6W60dAyo7z zw2F?ZdWOAIFmLb6yVi8nx7`}ZNrwA}(ms0e{f9i@TZm)IvP{}pJ<(1&PKW!Pf~RI3 zA{vaxI*EBTXDX*&DlB_|b=Zu>YtWY8eS&ho^iAezJL+0*IrM+~^9lCJ$Cb}RvX5k* z?jOtxoOKG|pdR|mz6YHSr-eRH{J;o7N9oju0P7GXMWnqGSXq_gy=OMkhfA5gL}Hhsi2aI>0PMp1 zeyF+>=m@9YJDNvz5+f&U(Emd=*^Y{y6CQZkcfO!#8k=<#C?bx*9miBB1A7SaN|*A_ ziq=r9Gx~5^#@%rV74XOgF;J{i09gFdskQV0;xlhVDK{-ue(5Mg$iAO>LXcOL5Hxk> z))iH!XfTf_pPw`4se3939E*8UgW?9B*jCfBn8hX~>xSIG0sCAHOy`BgzQXMV8yUdS zVS|ntHhd15M@uHp-bwY?-#%MT9{?N>m92B0#lQ`^-v_|H&W83p=pdBsqnT%5vThut zQy*fI$&2wM_BEcbTS^}wk7gYd+fF5rjFP!{t;+L+xbMuaV+%Bx$I7;Rj=W92WU`zU zuXL6b6lD$d4o*=A$}d?m>0q-jm%&=-AZ4ExPh+sp!0K~W%2sYrzZvlzBb5%(EaG98zL z*m1E9^eP;e!+$-naOu_+1sd|{^jxz@+>1D3?{frC=7lk0l)tvlFXnY$?#xy-`jR{N z;Q_0->fJG)eu~O*$y~A)mG6C1@qI%{&CU4butvNGRURIpBY+eYAda>TH9!@{5F}qV zjs+U+WeRmg)swLkk>XP_$?!l+I!*U5*ms8tJc#)S*`%K`I0*I`K70kh6W%qL4Hoi4j@uml2j#i(qgesIgHM#g*kJAot1 z21Zq+=kye4bnvO?fMoCk2-bVn&6ACw_$QMjDh}k2V zGg$KGjq9|6a$k-MBjy7+rlr$!4Q4_v$+%8Vo;#3oRJEuflcGj-F+P*WCEq&*4p6jY z>Npv7JqK+1J#Sba$yRdoa#&AGcg$eVK(e}lJ?Sj_PVKl(t3b>LjMc@d!VkJTz%?|M z)g`Coq%{!sOiuyF0*`0cv_NBmM0|D+m!t37m~`V4NBbh`+!& zDaJW@RiK;@`jz#b{yoc9@+6iX>R_P2lWm8kxuS6f*UPlM834<0+YaJ(k4jdWczDvl zi21;#(%?0oubquwP7~KIAoXk_8Uz;O5JMhmS#&Z$a}cj~`8~_C5jftYf#JaDIZlqw zhrQGx^#fNkDWAQq-6>4)fHCmQ&fj7iz|Df&!rF(sMl|&VLLKvgbRK{0*OcAsO!r(D z$vQhFnNAK+XatNSL30qG*@=#$6DaqTRgD~A^$Zu*z_EoayT}};0wA_rYD(&E?n+aG zmxC|=dH;cvL6boYi&j79qrihOM3rr^JIt>={K{96C>56|dH|3<$^eOCWI}X<(QF)0 zT!QEC6JjIVCr*xON1o3a^JdhSx08R=3C%Uf4Cje-QQ0F>z z7L`*}UARZ_UH9eAR9~kH=H61D|7L%o9u!~H|?)&XlA>zP}yN_<8fcRUK$*2%1s%W#hp zJUM6=lmpVHV0os&8_ObtE{{jW1XvHRCX1=V5*<-HX;oH0fu|Y+I!?tlWQ&8^ z)7&-p!lBZCj!B6tSa^5-wrPv9Yh>`uRB$Y13Qqj}@Z3wsFRbWp>bQA4z@ke+x+O5y z*#`KalHf67K9=1Jh0@j7v3$?-JCp;(l@29t=9`)g>Fv0yj z(s8{ZNb!*oA3t~P{6_%1g#cQ6ZENDkFTMXAf9G~ z1=nUv3;t{#yI$b1V(?nDe*=D66p_3E)`kCK+!*Bsx=sQX-otBKRf#6^Cr*ml2jJyb z6qaQa{PCm5tpmg9{+tXXB_VT`Bri~56jnU`?0rBAahMBN=Y_yxvY@m?lUp`@6`Flv zk6>3|Mo7sdfLONo4XM&V>J9nIfCI3=eTH!(GH6O0(||!biS5b7z&yfW z7qDmk00(8L%bhZlvo4NPAqSo`Ej#Py((C?bzfXyWVL+%jQkF8*!|K&;5&m-d^ON6$XmSh&wU=}G8G_P$rz;hH5od%>Z#*i9QRv0 z_{u5N*QU08ne%7tV-7$vWAHSA3zZ^=1j8fu3MKh87;!a)-0hVes6*uib*k^95+|Nq zyk+@WePY%mRhR8+Z9t7hhkbZ#spH2o0BoSm?VOATI9Wb{wQ$@JDvhP>X#Y#6)Cw&a zhVJ~qCRp0SDNI|K-E1@)+w4|*0{NqTzxfv0^73K+1bzLCv1OFY>26qPRQWT!r?V!( zC~(ZaW-K|8*og^Ocb1WyQ-o`luN_y>M}|meaAzyA0<<^j(Feae&ivV|RdfC)E;XSW zL|+;eb609Lv@lp(}}LR;>0aHj3sxl5x=(Jd&7=} z3l zJa}*_JxHCJo7vpg(c9Nv(=wL@U|VG|nmX?`B$&dK?6X(lhHn@_oYm`Y3?dVAX#0CqQA zLUkuULDQFQPy!e>4)JHXW8s>4{(D7a8}u2iAK~IlZ<)O>v#YTcD13B54K4R43ACpc%E45 z46*pIg>1>O;Xrt(*$o)fM#uTg9g_)#tu|~zzum`<@K<=-sS|v zm?V}+z^A8-Jsd~+#mu_;mHU^q=qPHeIxc1 z1InD7aG{-la3ZI}Z^jK0jFZ8$19;&SaNa4pGJu^8?K!j;3Z=U6L&=eWVFQwkf_!$r zj!^$4Pw(|y9SNGjf>{T|d{kM(JWG$Fk=>cqjv~eVG zxH#fxZA9sb9>wWC`g>-P0E39J+M0 zEZ1SRN3rbS#ZTXrY+yq6T6|b6KSM<3MZjzm!b3+Q0XWyy&F1WT!--0dY6AC59Ba1| zY|{eH3!XrOMZ9`P|5_p~wv3ynoov3?7&q8Gkjk=yAfr+^vHS@n3>V;2w7LK27<4mF zFN_~>;57ADd+gd(%(&sEfBQuTs=L6JWgNd1t^etk-+F)>WP}9tW*zc?To-_6pxDNn z$tmIp-!|*^IcNJj`@jxnvt$&)iHd@EQ9|x_P?WIu%D>yX-ErL2c2p$LYTd8s>h5C! z9_8*oJEd1vNywOV^vlnD{e`edOAjL)kbxsN>5xZJyJY6E0xYfRNMgp#k{v9^ES%1g zQ5bJg{>dI>bxcN+rVJp#Cf2ROjl zIKWBs8vr~yiO8BLLC>vga%V+~rMxnG+_o0fjB1%Znp{Q8TDIik;-krq4Aj>Ej zV2=}MzCr;gH?GXvw)*?)cM_w5zGZ-qX^Rg;^-@SlEk|EhS54q0`M4s5;N=FP5Cjatn+FmPrZi zfsl9(C_5%iRqW0aJ~86*KRx-t(bvXSj82*eaZ^9T zK_OFK4mup*0SC)i(XuHrvMj#lRMQTQ7?n72XwDZG9_eZAc6MLxNN2GSjCpmbh#IQ} zl#)FGnTvW*pd;>C@?bjT5W{VD0m56At zk9JH1>*CF~?#O@O_Ctg>$qK&77>=CZF8Tt_4c=pqVWC^>u1XO7bzEqnS4eMUe6%fh z&Aj4)?tT#m_9>#vrC?!XLL6GS^4gS4_fr73f{;aT&^y=t`#tY@KpLW6X~xkjO3?Gw z5eqnk=#_4je`GD@LQixjH_shrWCrBF+g5yI&7s&S@%8l0XD;1@uH}+BOOMFs((Y8D z19lmkj`#}$(5pPS5PQKKE^un_*Z{JYnjlY1&Y8ARUXzXNS#8zL)9BOXEMI(0gJ-t5 z0i;VsVObsn6ndEs`g^vTaTZKvN5}AEK8WN6kS;B2*$MC8PP}t?CVd)d5xBHObgh@1 z`RZousB6vcZSA%o|CaGUTvMd6(yexCgD<13HA9>XK)p*DVy7j8KD@LDoVDzl2QMW{ zha%%t$t84l_lenvKMYRf#M8(h;CzF}2apchok)9;GgnLHWz}+2(jsuqZgf4A%-jgo zcbATqFGK9zPl7Ti)xV@7pMkt^LD6FxN^9e6gVq?;4Z}<%d*^ilUheWR)pwAjXeg;k zKKi=y1t(0bW#`fZ|it`PtF~=jI~c>$stj065Zg zJ`YIdTGH(`t*R+SQ{=TyLS?+>Xp3VAJ1KqfR2hwgUCVHIop3*)P^EDemH9vG&nCNYiNnCST;~_mAGd3=!neEhwUA6Uj*-lh4#wO8vZ`o zBDe!}mUMcyOy$` zZF|SvLQP)XmMfXw9y>PUIALtbwx=BEgIAr>0HHB0<`|#Sd1lwK`G~z@uznhYWk@NAXtS7Sub+Y-i3x(B_AsJhgJLA0Z)64%%O2Z;ugGW(Y@Gg9@lw(90= z4dD181IO+KE6!7Sn&%6OGU!ECVOcE22EC$ZK~rONP}>s5PQo-Qa!?SwhY?<5!oo21 z%-)=(>JgBhJ^5Ix=Z`2=rXK-dh>=mDGX{p{i;d}O)-B=Ki?E=i$pDmh|J#Ai9pp1C z=qv@IzBBNnJmhCg3erqJGF-03n*2l4}xSe9ha`Aj;ZuNCS=W4eqpa9Xc;qV z`}W3j4rwT!uR^jAMu3e2)eVTf&4DgZyXPA`w%*d=f@-sj{*K-> zpUiU&D5WO|_AQVCECj%{n5tPKvoyWsOorFlT+uX#!8$VDL;mJD^&?~)E3=|t-(dH^ z6qS@@&eG^crhvUpkV%-dNZU8EOka|7c*(uX-+|$HJQjkD1Q2;sv6w}gqOBd~u$&gp z^7}k5lMWd)85nGb#l11cxvC_Vv!Ymp&c#@LJz|RAwhtN_>>F=x0D!gVo?ExC{@(gM zbMIPo=Dz11`b}I~V!1Eb1t2vY8o0iw8Ggw;gba~!CRVm3gW)T%_FoTrsKDB0Q~_XH zO-q8x-qEaMJqnT+9`I2>=0GNSOo6!p>wmiW*?Ak5oWLJpNNjIpe01xVpZ?naF4%O- zpJBF+*0I5%0V!rWCqAT-<+!Nq-dV5R(~rj66h^^1#9*EDxwWcU<*caIrREjr(U4|> z`E_8w7Z9H-OM(mhp!b*xN_7)gKN7$+LgpeiI$M@j@Tk_s1I4*J0zxq+vK``Au&x|JrKU8e1MKN)8`KvaP|u4sQ6* zd$*n0b?nsXoyRt__FLmF07xaGdvUd4>6K+1u?P6|B5j+t}5xbP&d#XFyV zYg0o>?MmLxE#-whsOoGn%3GC)a#vvx!~F*_J~f=@v}kP-$@l#I-sIl@U|mrhR(nt$ z75HMCWJ@z99bNssb+1jF91FwH$YJ*Mj2m*}MRMCW+g(4tXM0^y^=AjS?YoaPWJ8kD zA>4`Hzl_Qbcc2C9lbIP5X37%-4Z%A6zaPE>3Vvcb03*unftj-cV%x#(_2}JaF7jnk zFbrWY^5xCH`0C3TxK@v2BG-O&WiA}+m8{bLq}`NJz4h6Lp21f!J@%O_w_|dkSYz)SXe$_Zm+mJPWq&BY#8+QOsi*2yq+=_?R{vJ0t zOkAOtcA}TQa8a_UCz8W9nSC-_afdjGYssGHcidThvf?hDl*%B2qzA%8Z=;Fcg{yh9 zt%B-pk`pg^Q@mVm@NB;C1QwbjN^iTnF+eK0X++GnjCs>{uKCw5?Lx|FQ{?PY-d?V} zouchXy*s2GpX1%Grp|%`zyI*=rtFL)-4 zsf`8?HZ>w+J=m@4ICe&Sf#zY``vlu)(5%S6$@r|tSLDPPBU10tDuIaQxeC|cm3>qAuR{&9$%KhHFI1$@etdXvWc-&eKkSp15WKY448gwTYPZrMNzR`u(Ff z6@7gAOY%0MzC31?=os{axohW@BhJc3{@TIOFpr)z=)LKvq=`iX-TjKew)8Gknn@Py z?cKOC@AaiyR}?Z>W@WIA$2b?&;ph=>@^1_jkPgBgnkc8nI(OZo&pT0!A~9 zC6n9*XE@FmsT>Bxo@eMl%2~cLm{zO^!?-?Qd$#JWb03~q-dxdmi!WzQFdJL%e&Q>C zV#K3Pzoe6t5K;8QL%s4WNzVy%y_?f}YWSbN{o)I}t=%}FeVqTm?H|M>$8{4fKglxk zW^GjC(uUuK_*2ewh(G33HHZqaw;?wn_O|3g$^Nq$Wd%hmdYZd(hWm%os7z3C-W>~G zx_SMg5{CV@QTIiwhsx#!3Z9ONl?+I|pbmf+)>_q^@ZM8z{MI@+oWYkzQ^%B9>3bI4 zbL%IuQ{sCmR#_P=qb1UTlvAz!wHaE^C0UCH83=%k3_$5YMbdHGYg@xF9yy Date: Tue, 29 Jul 2014 16:50:07 +0200 Subject: [PATCH 53/83] Added some support to fetch level sessions for the user profile page --- app/models/LevelSession.coffee | 2 ++ app/templates/user/home.jade | 17 +++++++++++++-- app/views/kinds/UserView.coffee | 2 +- app/views/user/MainUserView.coffee | 35 ++++++++++++++++++++++++++++++ server/users/user_handler.coffee | 4 +++- 5 files changed, 56 insertions(+), 4 deletions(-) diff --git a/app/models/LevelSession.coffee b/app/models/LevelSession.coffee index b98cac66e..3964b5032 100644 --- a/app/models/LevelSession.coffee +++ b/app/models/LevelSession.coffee @@ -36,3 +36,5 @@ module.exports = class LevelSession extends CocoModel spell = item[1] return true if c1[thang][spell] isnt c2[thang]?[spell] false + + isMultiPlayer: -> @get('team')? # Only multiplayer level sessions have teams defined diff --git a/app/templates/user/home.jade b/app/templates/user/home.jade index 40fb2311c..4380619de 100644 --- a/app/templates/user/home.jade +++ b/app/templates/user/home.jade @@ -8,6 +8,9 @@ block append content .profile-wrapper img.picture(src="#{me.getPhotoURL(150)}" alt="") h3.name= user.get('name') + if favoriteLanguage + div.profile-info Favorite language is + span= favoriteLanguage .btn-group-vertical.profile-menu a.btn.btn-default(href="/user/#{user.get('slug') || user.get('_id')}/profile") i.glyphicon.glyphicon-briefcase @@ -47,9 +50,8 @@ block append content h4.contributor-title a(href="/contribute#scribe") Scribe - .right-column - .panel.panel-default + //.panel.panel-default .panel-heading h3.panel-title Achievements .panel-body @@ -57,6 +59,17 @@ block append content .panel-heading h3.panel-title S. Levels .panel-body + if (!singlePlayerSessions) + p Loading... + else if (singlePlayerSessions.length) + table.table + tr + th Level + th Last Played + th Status + + //each session in singlePlayerSessions + .panel.panel-default .panel-heading h3.panel-title M. Levels diff --git a/app/views/kinds/UserView.coffee b/app/views/kinds/UserView.coffee index a1e39f0a0..52b60b445 100644 --- a/app/views/kinds/UserView.coffee +++ b/app/views/kinds/UserView.coffee @@ -13,7 +13,7 @@ module.exports = class UserView extends RootView @listenTo @, 'userLoaded', @onUserLoaded @listenTo @, 'userNotFound', @ifUserNotFound - @userID ?= me.id + @userID ?= me.id # TODO Ruben really? @fetchUser @userID # TODO Ruben make this use the new getByNameOrID as soon as that is merged in diff --git a/app/views/user/MainUserView.coffee b/app/views/user/MainUserView.coffee index 60752797e..64b018c0c 100644 --- a/app/views/user/MainUserView.coffee +++ b/app/views/user/MainUserView.coffee @@ -1,7 +1,16 @@ UserView = require 'views/kinds/UserView' +CocoCollection = require 'collections/CocoCollection' +LevelSession = require 'models/LevelSession' template = require 'templates/user/home' {me} = require 'lib/auth' +class LevelSessionsCollection extends CocoCollection + model: LevelSession + + constructor: (userID) -> + @url = "/db/user/#{userID}/level.sessions?project=state.complete,levelID,levelName,changed,submittedCodeLanguage&order=-1" + super() + module.exports = class MainUserView extends UserView id: 'user-home-view' template: template @@ -11,5 +20,31 @@ module.exports = class MainUserView extends UserView getRenderData: -> context = super() + if @user + singlePlayerSessions = [] + multiPlayerSessions = [] + languageCounts = {} + for levelSession in @levelSessions.models + if levelSession.isMultiPlayer() + multiPlayerSessions.push levelSession + else + singlePlayerSessions.push levelSession + languageCounts[levelSession.get 'submittedCodeLanguage'] = (languageCounts[levelSession.get 'submittedCodeLanguage'] or 0) + 1 + mostUsedCount = 0 + favoriteLanguage = null + for language, count of languageCounts + if count > mostUsedCount + mostUsedCount = count + language = favoriteLanguage + context.favoriteLanguage = favoriteLanguage + context.singlePlayerSessions = singlePlayerSessions + context.multiPlayerSessions = multiPlayerSessions + console.debug context context + onUserLoaded: (user) -> + @levelSessions = @supermodel.loadCollection(new LevelSessionsCollection(@userID), 'levelSessions').model + super user + + onLoaded: -> + super() diff --git a/server/users/user_handler.coffee b/server/users/user_handler.coffee index b8f58f9c1..dfc1de92f 100644 --- a/server/users/user_handler.coffee +++ b/server/users/user_handler.coffee @@ -249,8 +249,10 @@ UserHandler = class UserHandler extends Handler projection[field] = 1 for field in req.query.project.split(',') when isAuthorized or not (field in LevelSessionHandler.privateProperties) else unless isAuthorized projection[field] = 0 for field in LevelSessionHandler.privateProperties + sort = {} + sort.changed = req.query.order if req.query.order - LevelSession.find(query).select(projection).exec (err, documents) => + LevelSession.find(query).select(projection).sort(sort).exec (err, documents) => return @sendDatabaseError(res, err) if err documents = (LevelSessionHandler.formatEntity(req, doc) for doc in documents) @sendSuccess(res, documents) From d4043ac3db0592d4eeaa8866eace01718816f635 Mon Sep 17 00:00:00 2001 From: Ruben Vereecken Date: Tue, 29 Jul 2014 20:11:45 +0200 Subject: [PATCH 54/83] Implemented singleplayer and multiplayer games played on profile --- app/models/LevelSession.coffee | 6 ++- app/styles/user/home.sass | 11 ++++- app/templates/account/home.jade | 3 +- app/templates/user/home.jade | 64 ++++++++++++++++++++++-------- app/views/user/MainUserView.coffee | 7 ++-- 5 files changed, 66 insertions(+), 25 deletions(-) diff --git a/app/models/LevelSession.coffee b/app/models/LevelSession.coffee index 3964b5032..ae7ac0b9c 100644 --- a/app/models/LevelSession.coffee +++ b/app/models/LevelSession.coffee @@ -37,4 +37,8 @@ module.exports = class LevelSession extends CocoModel return true if c1[thang][spell] isnt c2[thang]?[spell] false - isMultiPlayer: -> @get('team')? # Only multiplayer level sessions have teams defined + isMultiPlayer: -> + console.log @get 'levelName' + console.log @ + console.log @get 'team' + @get('team')? # Only multiplayer level sessions have teams defined diff --git a/app/styles/user/home.sass b/app/styles/user/home.sass index bbb58a696..c7b546508 100644 --- a/app/styles/user/home.sass +++ b/app/styles/user/home.sass @@ -21,10 +21,17 @@ background-color: #ffe4bc border: 4px solid white - > .name + > .profile-info + background: white + + .extra-info + padding-bottom: 3px + &:empty + display: none + + .name margin: 0px auto padding: 10px inherit - background: white color: white text-shadow: 2px 0 0 #000, -2px 0 0 #000, 0 2px 0 #000, 0 -2px 0 #000, 1px 1px #000, -1px -1px 0 #000, 1px -1px 0 #000, -1px 1px 0 #000 diff --git a/app/templates/account/home.jade b/app/templates/account/home.jade index 0645e0aa1..bfe4aca26 100644 --- a/app/templates/account/home.jade +++ b/app/templates/account/home.jade @@ -110,8 +110,9 @@ block content td a(href="/play/level/#{session.get('levelID')}")= session.get('levelName') td= moment(session.get('changed')).fromNow() + td if session.get('state').complete === true - td Completed + | Completed else .panel.panel-default .panel-body diff --git a/app/templates/user/home.jade b/app/templates/user/home.jade index 4380619de..4c8c94246 100644 --- a/app/templates/user/home.jade +++ b/app/templates/user/home.jade @@ -7,10 +7,11 @@ block append content .left-column .profile-wrapper img.picture(src="#{me.getPhotoURL(150)}" alt="") - h3.name= user.get('name') - if favoriteLanguage - div.profile-info Favorite language is - span= favoriteLanguage + div.profile-info + h3.name= user.get('name') + if favoriteLanguage + div.extra-info Favorite language is + strong.spl.spr= favoriteLanguage .btn-group-vertical.profile-menu a.btn.btn-default(href="/user/#{user.get('slug') || user.get('_id')}/profile") i.glyphicon.glyphicon-briefcase @@ -57,20 +58,49 @@ block append content .panel-body .panel.panel-default .panel-heading - h3.panel-title S. Levels - .panel-body - if (!singlePlayerSessions) + h3.panel-title Singleplayer Levels + if (!singlePlayerSessions) + .panel-body p Loading... - else if (singlePlayerSessions.length) - table.table + else if (singlePlayerSessions.length) + table.table + tr + th Level + th Last Played + th Status + each session in singlePlayerSessions tr - th Level - th Last Played - th Status - - //each session in singlePlayerSessions - + td + if session.get('levelName') + a(href="/play/level/#{session.get('levelID')}")= session.get('levelName') + td= moment(session.get('changed')).fromNow() + if session.get('state').complete === true + td Completed + else + td Unfinished + else + .panel-body + p No Singleplayer games played yet. .panel.panel-default .panel-heading - h3.panel-title M. Levels - .panel-body + h3.panel-title Multiplayer Levels + if (!multiPlayerSessions) + .panel-body + p Loading... + else if (multiPlayerSessions.length) + table.table + tr + th Level + th Team + th Last Played + each session in multiPlayerSessions + tr + td + a(href="/play/level/#{session.get('levelID')}")= session.get('levelName') + td= session.get('team') + td= moment(session.get('changed')).fromNow() + if session.get('state').complete === true + td Completed + else + .panel-body + p No Multiplayer games played yet. diff --git a/app/views/user/MainUserView.coffee b/app/views/user/MainUserView.coffee index 64b018c0c..167a6ee38 100644 --- a/app/views/user/MainUserView.coffee +++ b/app/views/user/MainUserView.coffee @@ -8,7 +8,7 @@ class LevelSessionsCollection extends CocoCollection model: LevelSession constructor: (userID) -> - @url = "/db/user/#{userID}/level.sessions?project=state.complete,levelID,levelName,changed,submittedCodeLanguage&order=-1" + @url = "/db/user/#{userID}/level.sessions?project=state.complete,levelID,levelName,changed,team,submittedCodeLanguage&order=-1" super() module.exports = class MainUserView extends UserView @@ -35,11 +35,10 @@ module.exports = class MainUserView extends UserView for language, count of languageCounts if count > mostUsedCount mostUsedCount = count - language = favoriteLanguage - context.favoriteLanguage = favoriteLanguage + favoriteLanguage = language context.singlePlayerSessions = singlePlayerSessions context.multiPlayerSessions = multiPlayerSessions - console.debug context + context.favoriteLanguage = favoriteLanguage context onUserLoaded: (user) -> From 871149b2bcf0543f6a10cb90c5c971194c01ea5d Mon Sep 17 00:00:00 2001 From: Ruben Vereecken Date: Wed, 30 Jul 2014 22:23:43 +0200 Subject: [PATCH 55/83] Rechecked and added cool stuff for achievements --- .../NewAchievementCollection.coffee | 3 + app/models/Achievement.coffee | 2 +- app/models/CocoModel.coffee | 8 +- app/models/SuperModel.coffee | 12 ++- app/models/User.coffee | 15 ++-- app/schemas/models/user.coffee | 30 +++---- app/styles/achievements.sass | 21 +++-- app/templates/achievement_notify.jade | 6 +- app/templates/base.jade | 2 +- app/templates/kinds/user.jade | 14 ++-- app/templates/user/achievements.jade | 52 +++++++++--- app/views/kinds/RootView.coffee | 26 +++--- app/views/user/AchievementsView.coffee | 19 ++++- scripts/setupAchievements.coffee | 16 +++- .../earned_achievement_handler.coffee | 4 +- test/app/lib/utils.spec.coffee | 79 ++++++++++--------- test/app/models/User.spec.coffee | 4 +- 17 files changed, 201 insertions(+), 112 deletions(-) diff --git a/app/collections/NewAchievementCollection.coffee b/app/collections/NewAchievementCollection.coffee index 7b33d7d75..b9e4326b5 100644 --- a/app/collections/NewAchievementCollection.coffee +++ b/app/collections/NewAchievementCollection.coffee @@ -1,6 +1,9 @@ CocoCollection = require 'collections/CocoCollection' +Achievement = require 'models/Achievement' class NewAchievementCollection extends CocoCollection + model: Achievement + initialize: (me = require('lib/auth').me) -> @url = "/db/user/#{me.id}/achievements?notified=false" diff --git a/app/models/Achievement.coffee b/app/models/Achievement.coffee index 494c29091..1010d2eaf 100644 --- a/app/models/Achievement.coffee +++ b/app/models/Achievement.coffee @@ -7,7 +7,7 @@ module.exports = class Achievement extends CocoModel urlRoot: '/db/achievement' isRepeatable: -> - @get('proportionalTo')?a + @get('proportionalTo')? # TODO logic is duplicated in Mongoose Achievement schema getExpFunction: -> diff --git a/app/models/CocoModel.coffee b/app/models/CocoModel.coffee index a50edd924..b27f64ba1 100644 --- a/app/models/CocoModel.coffee +++ b/app/models/CocoModel.coffee @@ -1,8 +1,6 @@ storage = require 'lib/storage' deltasLib = require 'lib/deltas' -NewAchievementCollection = require '../collections/NewAchievementCollection' - class CocoModel extends Backbone.Model idAttribute: '_id' loaded: false @@ -298,13 +296,15 @@ class CocoModel extends Backbone.Model return if _.isString @url then @url else @url() @pollAchievements: -> + NewAchievementCollection = require '../collections/NewAchievementCollection' # Nasty mutual inclusion if put on top + console.debug 'Polling for new achievements' achievements = new NewAchievementCollection - achievements.fetch( + achievements.fetch success: (collection) -> + console.debug 'Polling for achievements success', collection me.fetch (success: -> Backbone.Mediator.publish('achievements:new', collection)) unless _.isEmpty(collection.models) error: (collection, res, options) -> console.error 'Miserably failed to fetch unnotified achievements' - ) CocoModel.pollAchievements = _.debounce CocoModel.pollAchievements, 500 diff --git a/app/models/SuperModel.coffee b/app/models/SuperModel.coffee index c366d8285..6bf27d963 100644 --- a/app/models/SuperModel.coffee +++ b/app/models/SuperModel.coffee @@ -58,9 +58,15 @@ module.exports = class SuperModel extends Backbone.Model return res else @addCollection collection - @listenToOnce collection, 'sync', (c) -> - console.debug 'Registering collection', url - @registerCollection c + onCollectionSynced = (c) -> + if collection.url is c.url + console.debug 'Registering collection', url, c + @registerCollection c + else + console.warn 'Sync triggered for collection', c + console.warn 'Yet got other object', c + @listenToOnce collection, 'sync', onCollectionSynced + @listenToOnce collection, 'sync', onCollectionSynced res = @addModelResource(collection, name, fetchOptions, value) res.load() if not (res.isLoading or res.isLoaded) return res diff --git a/app/models/User.coffee b/app/models/User.coffee index fec03efec..810212065 100644 --- a/app/models/User.coffee +++ b/app/models/User.coffee @@ -16,6 +16,10 @@ module.exports = class User extends CocoModel super() @migrateEmails() + onLoaded: -> + CocoModel.pollAchievements() # Check for achievements on login + super arguments... + isAdmin: -> permissions = @attributes['permissions'] or [] return 'admin' in permissions @@ -121,15 +125,16 @@ module.exports = class User extends CocoModel isEmailSubscriptionEnabled: (name) -> (@get('emails') or {})[name]?.enabled a = 5 - b = 40 + b = 100 + c = b - # y = a * ln(1/b * (x + b)) + 1 + # y = a * ln(1/b * (x + c)) + 1 @levelFromExp: (xp) -> - if xp > 0 then Math.floor(a * Math.log((1/b) * (xp + b))) + 1 else 1 + if xp > 0 then Math.floor(a * Math.log((1/b) * (xp + c))) + 1 else 1 - # x = (e^((y-1)/a) - 1) * b + # x = b * e^((y-1)/a) - c @expForLevel: (level) -> - Math.ceil((Math.exp((level - 1)/ a) - 1) * b) + if level > 1 then Math.ceil Math.exp((level - 1)/ a) * b - c else 0 level: -> User.levelFromExp(@get('points')) diff --git a/app/schemas/models/user.coffee b/app/schemas/models/user.coffee index 4f2d3beed..be4478820 100644 --- a/app/schemas/models/user.coffee +++ b/app/schemas/models/user.coffee @@ -229,25 +229,25 @@ _.extend UserSchema.properties, levelSystemEdits: c.int() levelComponentEdits: c.int() thangTypeEdits: c.int() - 'stats.patchesSubmitted': c.int + patchesSubmitted: c.int description: 'Amount of patches submitted, not necessarily accepted' - 'stats.patchesContributed': c.int + patchesContributed: c.int description: 'Amount of patches submitted and accepted' - 'stats.patchesAccepted': c.int + patchesAccepted: c.int description: 'Amount of patches accepted by the user as owner' # The below patches only apply to those that actually got accepted - 'stats.totalTranslationPatches': c.int() - 'stats.totalMiscPatches': c.int() - 'stats.articleTranslationPatches': c.int() - 'stats.articleMiscPatches': c.int() - 'stats.levelTranslationPatches': c.int() - 'stats.levelMiscPatches': c.int() - 'stats.levelComponentTranslationPatches': c.int() - 'stats.levelComponentMiscPatches': c.int() - 'stats.levelSystemTranslationPatches': c.int() - 'stats.levelSystemMiscPatches': c.int() - 'stats.thangTypeTranslationPatches': c.int() - 'stats.thangTypeMiscPatches': c.int() + totalTranslationPatches: c.int() + totalMiscPatches: c.int() + articleTranslationPatches: c.int() + articleMiscPatches: c.int() + levelTranslationPatches: c.int() + levelMiscPatches: c.int() + levelComponentTranslationPatches: c.int() + levelComponentMiscPatches: c.int() + levelSystemTranslationPatches: c.int() + levelSystemMiscPatches: c.int() + thangTypeTranslationPatches: c.int() + thangTypeMiscPatches: c.int() c.extendBasicProperties UserSchema, 'user' diff --git a/app/styles/achievements.sass b/app/styles/achievements.sass index c91cb9237..688f8640e 100644 --- a/app/styles/achievements.sass +++ b/app/styles/achievements.sass @@ -129,7 +129,8 @@ > .progress-bar-wrapper position: absolute - width: 331px + margin-left: 12px + width: 319px height: 20px z-index: 2 @@ -147,27 +148,27 @@ background-size: 100% 100% z-index: 1 -.notifyjs-achievement-wood-base +.notifyjs-achievement-wood-base, .achievement-wood .achievement-icon background: url("/images/achievements/border_wood.png") no-repeat background-size: 100% 100% -.notifyjs-achievement-stone-base +.notifyjs-achievement-stone-base, .achievement-stone .achievement-icon background: url("/images/achievements/border_stone.png") no-repeat background-size: 100% 100% -.notifyjs-achievement-silver-base +.notifyjs-achievement-silver-base, .achievement-silver .achievement-icon background: url("/images/achievements/border_silver.png") no-repeat background-size: 100% 100% -.notifyjs-achievement-gold-base +.notifyjs-achievement-gold-base, .achievement-gold .achievement-icon background: url("/images/achievements/border_gold.png") no-repeat background-size: 100% 100% -.notifyjs-achievement-diamond-base +.notifyjs-achievement-diamond-base, .achievement-diamond .achievement-icon background: url("/images/achievements/border_diamond.png") no-repeat background-size: 100% 100% @@ -185,3 +186,11 @@ z-index: 1000 box-shadow: 0 0 0 1px black, 0 0 0 3px lightgrey, 0 0 0 4px black font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif + +// Achievements page +h2.achievements-category + margin-left: 20px + +.table-layout + #no-achievements + margin-top: 40px diff --git a/app/templates/achievement_notify.jade b/app/templates/achievement_notify.jade index a01b832c4..505097224 100644 --- a/app/templates/achievement_notify.jade +++ b/app/templates/achievement_notify.jade @@ -1,6 +1,6 @@ -div - .clearfix.achievement-body - .achievement-icon(class=locked === true ? "locked" : "", class=border) +div(class=notifyClass) + .clearfix.achievement-body(class=locked === true ? "locked" : "") + .achievement-icon .achievement-image(data-notify-html="image") if imgURL img(src=imgURL) diff --git a/app/templates/base.jade b/app/templates/base.jade index e47222734..7eca794e8 100644 --- a/app/templates/base.jade +++ b/app/templates/base.jade @@ -51,7 +51,7 @@ body .col-xs-4.text-center a(href="/user/#{me.get('slug') || me.get('_id')}") Profile .col-xs-4.text-center - a(href="#") Stats + a(href="/user/#{me.get('slug') || me.get('_id')}/stats") Stats .col-xs-4.text-center a.disabled() Code li.user-dropdown-footer diff --git a/app/templates/kinds/user.jade b/app/templates/kinds/user.jade index 6cb134ce2..a40153635 100644 --- a/app/templates/kinds/user.jade +++ b/app/templates/kinds/user.jade @@ -3,14 +3,12 @@ extends /templates/base // User pages might have some user page specific header, if not remove this block content .clearfix - //- - if user && viewName - ol.breadcrumb - li - - var userName = user.get('name'); - //_a(href="/user/#{user.id}") #{userName} - li.active - //-| #{viewName} + if user && viewName + ol.breadcrumb + li + a(href="/user/#{user.id}") #{user.displayName()} + li.active + | #{viewName} if !userLoaded | LOADING else if !user diff --git a/app/templates/user/achievements.jade b/app/templates/user/achievements.jade index ecb2dc673..1bdf4bc85 100644 --- a/app/templates/user/achievements.jade +++ b/app/templates/user/achievements.jade @@ -1,12 +1,46 @@ extends /templates/kinds/user block append content - if achievements - .row - each achievement, index in achievements - - var title = achievement.get('name'); - - var description = achievement.get('description'); - - var imgURL = achievement.get('icon'); - - var locked = ! achievement.get('unlocked'); - .col-lg-4.col-xs-12 - include ../achievement_notify + .btn-group.pull-right + button#grid-layout-button.btn.btn-default(data-layout='grid', class=activeLayout==='grid' ? 'active' : '') Grid + button#table-layout-button.btn.btn-default(data-layout='table', class=activeLayout==='table' ? 'active' : '') Table + if achievementsByCategory + if activeLayout === 'grid' + .grid-layout + each achievements, category in achievementsByCategory + .row + h2.achievements-category=category + each achievement, index in achievements + - var title = achievement.get('name'); + - var description = achievement.get('description'); + - var imgURL = achievement.getImageURL(); + - var locked = ! achievement.get('unlocked'); + - var notifyClass = achievement.getNotifyStyle() + .col-lg-4.col-xs-12 + include ../achievement_notify + else if activeLayout === 'table' + .table-layout + if earnedAchievements.length + table.table + tr + th Name + th Description + th Date + th Amount + th XP + each earnedAchievement in earnedAchievements + - var achievement = earnedAchievement.get('achievement'); + tr + td= achievement.get('name') + td= achievement.get('description') + td= moment().format("MMMM Do YY", earnedAchievement.get('changed')) + if achievement.isRepeatable() + td= earnedAchievement.get('achievedAmount') + else + td + td= earnedAchievement.get('earnedPoints') + else + .panel#no-achievements + .panel-body No achievements earned yet. + else + div How did you even do that? diff --git a/app/views/kinds/RootView.coffee b/app/views/kinds/RootView.coffee index 3ce1add4e..0f1a046aa 100644 --- a/app/views/kinds/RootView.coffee +++ b/app/views/kinds/RootView.coffee @@ -8,6 +8,8 @@ locale = require 'locale/locale' Achievement = require '../../models/Achievement' User = require '../../models/User' +utils = require 'lib/utils' + # TODO remove filterKeyboardEvents = (allowedEvents, func) -> @@ -39,11 +41,15 @@ module.exports = class RootView extends CocoView totalExpNeeded = nextLevelExp - currentLevelExp expFunction = achievement.getExpFunction() currentExp = me.get('points') - previousExp = currentExp - achievement.get('worth') - previousExp = expFunction(earnedAchievement.get('previouslyAchievedAmount')) * achievement.get('worth') if achievement.isRepeatable() - achievedExp = currentExp - previousExp + if achievement.isRepeatable() + achievedExp = expFunction(earnedAchievement.get('previouslyAchievedAmount')) * achievement.get('worth') if achievement.isRepeatable() + else + achievedExp = achievement.get 'worth' + previousExp = currentExp - achievedExp leveledUp = currentExp - achievedExp < currentLevelExp + console.debug 'Leveled up' if leveledUp alreadyAchievedPercentage = 100 * (previousExp - currentLevelExp) / totalExpNeeded + alreadyAchievedPercentage = 0 if alreadyAchievedPercentage < 0 # In case of level up newlyAchievedPercentage = if leveledUp then 100 * (currentExp - currentLevelExp) / totalExpNeeded else 100 * achievedExp / totalExpNeeded console.debug "Current level is #{currentLevel} (#{currentLevelExp} xp), next level is #{nextLevel} (#{nextLevelExp} xp)." @@ -92,25 +98,23 @@ module.exports = class RootView extends CocoView data showNewAchievement: (achievement, earnedAchievement) -> - data = createNotifyData achievement, earnedAchievement + data = @createNotifyData achievement, earnedAchievement options = - autoHideDelay: 10000 + autoHideDelay: 1000000 globalPosition: 'bottom right' showDuration: 400 style: achievement.getNotifyStyle() - autoHide: true + autoHide: false clickToHide: true + console.debug 'showing achievement', achievement.get 'name' $.notify( data, options ) handleNewAchievements: (earnedAchievements) -> - _.each(earnedAchievements.models, (earnedAchievement) => + _.each earnedAchievements.models, (earnedAchievement) => achievement = new Achievement(_id: earnedAchievement.get('achievement')) - console.log achievement - achievement.fetch( + achievement.fetch success: (achievement) => @showNewAchievement(achievement, earnedAchievement) - ) - ) logoutAccount: -> logoutUser($('#login-email').val()) diff --git a/app/views/user/AchievementsView.coffee b/app/views/user/AchievementsView.coffee index cad94f181..03e7a851e 100644 --- a/app/views/user/AchievementsView.coffee +++ b/app/views/user/AchievementsView.coffee @@ -9,6 +9,12 @@ EarnedAchievementCollection = require 'collections/EarnedAchievementCollection' module.exports = class AchievementsView extends UserView id: 'user-achievements-view' template: template + viewName: 'Stats' + activeLayout: 'grid' + + events: + 'click #grid-layout-button': 'layoutChanged' + 'click #table-layout-button': 'layoutChanged' constructor: (userID, options) -> super options, userID @@ -29,9 +35,20 @@ module.exports = class AchievementsView extends UserView earned.set 'achievement', relatedAchievement super() + layoutChanged: (e) -> + @activeLayout = $(e.currentTarget).data 'layout' + @render() + getRenderData: -> context = super() + context.activeLayout = @activeLayout + + # After user is loaded if @user and not @user.isAnonymous() - context.achievements = @achievements.models context.earnedAchievements = @earnedAchievements.models + context.achievements = @achievements.models + context.achievementsByCategory = {} + for achievement in @achievements.models + context.achievementsByCategory[achievement.get('category')] ?= [] + context.achievementsByCategory[achievement.get('category')].push achievement context diff --git a/scripts/setupAchievements.coffee b/scripts/setupAchievements.coffee index a3463cd4b..a06c5ebce 100644 --- a/scripts/setupAchievements.coffee +++ b/scripts/setupAchievements.coffee @@ -19,7 +19,7 @@ achievements = worth: 10 collection: 'users' userField: '_id' - category: 'Miscellaneous' + category: 'miscellaneous' difficulty: 1 completedFirstLevel: @@ -29,7 +29,17 @@ achievements = worth: 50 collection: 'users' userField: '_id' - category: 'Levels' + category: 'levels' + difficulty: 1 + + completedFiveLevels: + name: 'Completed one Level' + description: 'Completed your very first level.' + query: 'stats.gamesCompleted': $gte: 1 + worth: 50 + collection: 'users' + userField: '_id' + category: 'levels' difficulty: 1 simulatedBy: @@ -39,7 +49,7 @@ achievements = worth: 1 collection: 'users' userField: '_id' - category: 'Miscellaneous' + category: 'miscellaneous' difficulty: 1 proportionalTo: 'simulatedBy' function: diff --git a/server/achievements/earned_achievement_handler.coffee b/server/achievements/earned_achievement_handler.coffee index 313da4612..4002de462 100644 --- a/server/achievements/earned_achievement_handler.coffee +++ b/server/achievements/earned_achievement_handler.coffee @@ -75,9 +75,9 @@ class EarnedAchievementHandler extends Handler return doneWithAchievement() finalQuery = _.clone achievement.get 'query' - finalQuery.$or = [{}, {}] # Allow both ObjectIDs or hexa string IDs + finalQuery.$or = [{}, {}] # Allow both ObjectIDs or hex string IDs finalQuery.$or[0][achievement.userField] = userID - finalQuery.$or[1][achievement.userField] = ObjectId userID + finalQuery.$or[1][achievement.userField] = mongoose.Types.ObjectId userID model.findOne finalQuery, (err, something) -> return doneWithAchievement() if _.isEmpty something diff --git a/test/app/lib/utils.spec.coffee b/test/app/lib/utils.spec.coffee index 061a9aa71..bb1ab7233 100644 --- a/test/app/lib/utils.spec.coffee +++ b/test/app/lib/utils.spec.coffee @@ -1,45 +1,48 @@ -describe 'utils library', -> +describe 'Utility library', -> util = require 'lib/utils' - beforeEach -> - this.fixture1 = - 'text': 'G\'day, Wizard! Come to practice? Well, let\'s get started...' - 'blurb': 'G\'day' - 'i18n': - 'es-419': - 'text': '¡Buenas, Hechicero! ¿Vienes a practicar? Bueno, empecemos...' - 'es-ES': - 'text': '¡Buenas Mago! ¿Vienes a practicar? Bien, empecemos...' - 'es': - 'text': '¡Buenas Mago! ¿Vienes a practicar? Muy bien, empecemos...' - 'fr': - 'text': 'S\'lut, Magicien! Venu pratiquer? Ok, bien débutons...' - 'pt-BR': - 'text': 'Bom dia, feiticeiro! Veio praticar? Então vamos começar...' - 'en': - 'text': 'Ohai Magician!' - 'de': - 'text': '\'N Tach auch, Zauberer! Kommst Du zum Üben? Dann lass uns anfangen...' - 'sv': - 'text': 'Godagens, trollkarl! Kommit för att öva? Nå, låt oss börja...' + describe 'i18n', -> + beforeEach -> + this.fixture1 = + 'text': 'G\'day, Wizard! Come to practice? Well, let\'s get started...' + 'blurb': 'G\'day' + 'i18n': + 'es-419': + 'text': '¡Buenas, Hechicero! ¿Vienes a practicar? Bueno, empecemos...' + 'es-ES': + 'text': '¡Buenas Mago! ¿Vienes a practicar? Bien, empecemos...' + 'es': + 'text': '¡Buenas Mago! ¿Vienes a practicar? Muy bien, empecemos...' + 'fr': + 'text': 'S\'lut, Magicien! Venu pratiquer? Ok, bien débutons...' + 'pt-BR': + 'text': 'Bom dia, feiticeiro! Veio praticar? Então vamos começar...' + 'en': + 'text': 'Ohai Magician!' + 'de': + 'text': '\'N Tach auch, Zauberer! Kommst Du zum Üben? Dann lass uns anfangen...' + 'sv': + 'text': 'Godagens, trollkarl! Kommit för att öva? Nå, låt oss börja...' - it 'i18n should find a valid target string', -> - expect(util.i18n(this.fixture1, 'text', 'sv')).toEqual(this.fixture1.i18n['sv'].text) - expect(util.i18n(this.fixture1, 'text', 'es-ES')).toEqual(this.fixture1.i18n['es-ES'].text) + it 'i18n should find a valid target string', -> + expect(util.i18n(this.fixture1, 'text', 'sv')).toEqual(this.fixture1.i18n['sv'].text) + expect(util.i18n(this.fixture1, 'text', 'es-ES')).toEqual(this.fixture1.i18n['es-ES'].text) - it 'i18n picks the correct fallback for a specific language', -> - expect(util.i18n(this.fixture1, 'text', 'fr-be')).toEqual(this.fixture1.i18n['fr'].text) + it 'i18n picks the correct fallback for a specific language', -> + expect(util.i18n(this.fixture1, 'text', 'fr-be')).toEqual(this.fixture1.i18n['fr'].text) - it 'i18n picks the correct fallback', -> - expect(util.i18n(this.fixture1, 'text', 'nl')).toEqual(this.fixture1.i18n['en'].text) - expect(util.i18n(this.fixture1, 'text', 'nl', 'de')).toEqual(this.fixture1.i18n['de'].text) + it 'i18n picks the correct fallback', -> + expect(util.i18n(this.fixture1, 'text', 'nl')).toEqual(this.fixture1.i18n['en'].text) + expect(util.i18n(this.fixture1, 'text', 'nl', 'de')).toEqual(this.fixture1.i18n['de'].text) - it 'i18n falls back to the default text, even for other targets (like blurb)', -> - delete this.fixture1.i18n['en'] - expect(util.i18n(this.fixture1, 'text', 'en')).toEqual(this.fixture1.text) - expect(util.i18n(this.fixture1, 'blurb', 'en')).toEqual(this.fixture1.blurb) - delete this.fixture1.blurb - expect(util.i18n(this.fixture1, 'blurb', 'en')).toEqual(null) + it 'i18n falls back to the default text, even for other targets (like blurb)', -> + delete this.fixture1.i18n['en'] + expect(util.i18n(this.fixture1, 'text', 'en')).toEqual(this.fixture1.text) + expect(util.i18n(this.fixture1, 'blurb', 'en')).toEqual(this.fixture1.blurb) + delete this.fixture1.blurb + expect(util.i18n(this.fixture1, 'blurb', 'en')).toEqual(null) - it 'i18n can fall forward if a general language is not found', -> - expect(util.i18n(this.fixture1, 'text', 'pt')).toEqual(this.fixture1.i18n['pt-BR'].text) + it 'i18n can fall forward if a general language is not found', -> + expect(util.i18n(this.fixture1, 'text', 'pt')).toEqual(this.fixture1.i18n['pt-BR'].text) + + describe 'Miscellaneous utility', -> diff --git a/test/app/models/User.spec.coffee b/test/app/models/User.spec.coffee index b751690ed..35b87f450 100644 --- a/test/app/models/User.spec.coffee +++ b/test/app/models/User.spec.coffee @@ -3,7 +3,8 @@ User = require 'models/User' describe 'UserModel', -> it 'experience functions are correct', -> expect(User.expForLevel(User.levelFromExp 0)).toBe 0 - expect(User.expForLevel(User.levelFromExp 50)).toBe 50 + expect(User.levelFromExp User.expForLevel 1).toBe 1 + expect(User.levelFromExp User.expForLevel 10).toBe 10 expect(User.expForLevel 1).toBe 0 expect(User.expForLevel 2).toBeGreaterThan User.expForLevel 1 @@ -13,4 +14,3 @@ describe 'UserModel', -> me.set 'points', 50 expect(me.level()).toBe User.levelFromExp 50 - From 26085f9f3ecb579c59a34805400a69de77767a1c Mon Sep 17 00:00:00 2001 From: Ruben Vereecken Date: Sun, 3 Aug 2014 23:58:51 +0200 Subject: [PATCH 56/83] Added a bunch of achievements to the script. Restyled big parts. --- app/Router.coffee | 2 +- app/models/LevelSession.coffee | 2 +- app/schemas/models/achievement.coffee | 3 + app/styles/achievements.sass | 12 +- app/styles/base.sass | 6 + app/templates/account/home.jade | 21 +- app/templates/base.jade | 1 + app/templates/user/home.jade | 16 +- app/views/kinds/RootView.coffee | 35 ++-- app/views/user/AchievementsView.coffee | 3 +- app/views/user/MainUserView.coffee | 2 +- scripts/setupAchievements.coffee | 194 ++++++++++++++++-- server/achievements/Achievement.coffee | 2 + .../earned_achievement_handler.coffee | 18 +- server/plugins/achievements.coffee | 6 +- 15 files changed, 243 insertions(+), 80 deletions(-) diff --git a/app/Router.coffee b/app/Router.coffee index 0b0755653..a7d4eeb49 100644 --- a/app/Router.coffee +++ b/app/Router.coffee @@ -51,7 +51,7 @@ module.exports = class CocoRouter extends Backbone.Router 'editor': go('editor/MainEditorView') 'editor/achievement': go('editor/achievement/AchievementSearchView') - 'editor/achievement': go('editor/achievement/AchievementEditView') + 'editor/achievement/:articleID': go('editor/achievement/AchievementEditView') 'editor/article': go('editor/article/ArticleSearchView') 'editor/article/preview': go('editor/article/ArticlePreviewView') 'editor/article/:articleID': go('editor/article/ArticleEditView') diff --git a/app/models/LevelSession.coffee b/app/models/LevelSession.coffee index ae7ac0b9c..c33662824 100644 --- a/app/models/LevelSession.coffee +++ b/app/models/LevelSession.coffee @@ -37,7 +37,7 @@ module.exports = class LevelSession extends CocoModel return true if c1[thang][spell] isnt c2[thang]?[spell] false - isMultiPlayer: -> + isMultiplayer: -> console.log @get 'levelName' console.log @ console.log @get 'team' diff --git a/app/schemas/models/achievement.coffee b/app/schemas/models/achievement.coffee index a41627861..1740b845b 100644 --- a/app/schemas/models/achievement.coffee +++ b/app/schemas/models/achievement.coffee @@ -56,6 +56,9 @@ _.extend AchievementSchema.properties, proportionalTo: type: 'string' description: 'For repeatables only. Denotes the field a repeatable achievement needs for its calculations' + recalculable: + type: 'boolean' + description: 'Needs to be set to true before it is elligible for recalculation.' function: type: 'object' description: 'Function that gives total experience for X amount achieved' diff --git a/app/styles/achievements.sass b/app/styles/achievements.sass index 688f8640e..141d24e76 100644 --- a/app/styles/achievements.sass +++ b/app/styles/achievements.sass @@ -1,7 +1,7 @@ .locked - filter: grayscale(100%) - -moz-filter: grayscale(100%) - -webkit-filter: grayscale(100%) + filter: desaturate(gray, 50) + -moz-filter: desaturate(gray, 50) + -webkit-filter: desaturate(gray, 50) .achievement-body .achievement-icon @@ -37,8 +37,10 @@ max-height: 2.6em margin-top: auto margin-bottom: 0px !important - padding-left: 8px + padding-left: 5px + text-overflow: ellipsis +// Specific to the user stats page #user-achievements-view .row //.col-lg-4, .col-xs-12 @@ -65,7 +67,7 @@ margin-right: 5px width: 260px height: 100px - padding: 20px 10px 20px 60px + padding: 15px 10px 20px 60px .achievement-title font-size: 20px diff --git a/app/styles/base.sass b/app/styles/base.sass index cdf458557..deed44d93 100644 --- a/app/styles/base.sass +++ b/app/styles/base.sass @@ -288,3 +288,9 @@ body[lang='ja'] a[data-toggle="coco-modal"] cursor: pointer + +.achievement-corner + position: fixed + bottom: 0px + right: 0px + z-index: 1001 diff --git a/app/templates/account/home.jade b/app/templates/account/home.jade index bfe4aca26..129961760 100644 --- a/app/templates/account/home.jade +++ b/app/templates/account/home.jade @@ -106,13 +106,20 @@ block content th Last Played th Status each session in recentlyPlayed - tr - td - a(href="/play/level/#{session.get('levelID')}")= session.get('levelName') - td= moment(session.get('changed')).fromNow() - td - if session.get('state').complete === true - | Completed + if session.get('levelName') + tr + td + - var posturl = '' + - if (session.get('team')) posturl = '?team=' + session.get('team') + a(href="/play/level/#{session.get('levelID') + posturl}")= session.get('levelName') + (session.get('team') ? ' (' + session.get('team') + ')' : '') + td= moment(session.get('changed')).fromNow() + if session.get('state').complete === true + td Completed + else if ! session.isMultiplayer() + td Unfinished + else + td + else .panel.panel-default .panel-body diff --git a/app/templates/base.jade b/app/templates/base.jade index 7eca794e8..fe9158497 100644 --- a/app/templates/base.jade +++ b/app/templates/base.jade @@ -73,6 +73,7 @@ body .main-content-area block content p If this is showing, you dun goofed + .achievement-corner block footer .footer.clearfix diff --git a/app/templates/user/home.jade b/app/templates/user/home.jade index 4c8c94246..b235a77f3 100644 --- a/app/templates/user/home.jade +++ b/app/templates/user/home.jade @@ -69,15 +69,15 @@ block append content th Last Played th Status each session in singlePlayerSessions - tr - td - if session.get('levelName') + if session.get('levelName') + tr + td a(href="/play/level/#{session.get('levelID')}")= session.get('levelName') - td= moment(session.get('changed')).fromNow() - if session.get('state').complete === true - td Completed - else - td Unfinished + td= moment(session.get('changed')).fromNow() + if session.get('state').complete === true + td Completed + else + td Unfinished else .panel-body p No Singleplayer games played yet. diff --git a/app/views/kinds/RootView.coffee b/app/views/kinds/RootView.coffee index 0f1a046aa..7ecffe5cd 100644 --- a/app/views/kinds/RootView.coffee +++ b/app/views/kinds/RootView.coffee @@ -66,26 +66,6 @@ module.exports = class RootView extends CocoView barBorder = $('') - ### - barBorder.hover (e) -> - #console.debug e - x = e.pageX - y = e.pageY - $actualHover = _.find [$('.progress-bar-warning'), $('.progress-bar-success'), $('.progress-bar-white')], (el) -> - offset = el.offset() - l = offset.left - t = offset.top - h = el.height() + 10 - w = el.width() + 10 - - maxx = l + w - maxy = t + h - - return (y <= maxy && y >= t) && (x <= maxx && x >= l) ? true : null - #console.debug $actualHover - $actualHover.trigger e if $actualHover - - ### data = title: achievement.get('name') image: $("") @@ -99,16 +79,25 @@ module.exports = class RootView extends CocoView showNewAchievement: (achievement, earnedAchievement) -> data = @createNotifyData achievement, earnedAchievement + console.debug data options = - autoHideDelay: 1000000 + autoHideDelay: 5000 + elementPosition: 'top left' globalPosition: 'bottom right' - showDuration: 400 + showDuration: 0 style: achievement.getNotifyStyle() autoHide: false clickToHide: true console.debug 'showing achievement', achievement.get 'name' - $.notify( data, options ) + unless @timeout? + $.notify data, options + @timeout = 2000 + else + setTimeout -> + $.notify data, options + @timeout += 2000 + , @timeout handleNewAchievements: (earnedAchievements) -> _.each earnedAchievements.models, (earnedAchievement) => diff --git a/app/views/user/AchievementsView.coffee b/app/views/user/AchievementsView.coffee index 03e7a851e..7ca4dfb72 100644 --- a/app/views/user/AchievementsView.coffee +++ b/app/views/user/AchievementsView.coffee @@ -26,9 +26,8 @@ module.exports = class AchievementsView extends UserView onLoaded: -> console.log @earnedAchievements - console.log 'onLoaded' + console.log @achievements _.each @earnedAchievements.models, (earned) => - console.log earned return unless relatedAchievement = _.find @achievements.models, (achievement) -> achievement.get('_id') is earned.get 'achievement' relatedAchievement.set 'unlocked', true diff --git a/app/views/user/MainUserView.coffee b/app/views/user/MainUserView.coffee index 167a6ee38..82e3c7cbe 100644 --- a/app/views/user/MainUserView.coffee +++ b/app/views/user/MainUserView.coffee @@ -25,7 +25,7 @@ module.exports = class MainUserView extends UserView multiPlayerSessions = [] languageCounts = {} for levelSession in @levelSessions.models - if levelSession.isMultiPlayer() + if levelSession.isMultiplayer() multiPlayerSessions.push levelSession else singlePlayerSessions.push levelSession diff --git a/scripts/setupAchievements.coffee b/scripts/setupAchievements.coffee index a06c5ebce..42cfb4e9f 100644 --- a/scripts/setupAchievements.coffee +++ b/scripts/setupAchievements.coffee @@ -11,9 +11,21 @@ do (setupLodash = this) -> database.connect() -achievements = + +## Util + +## Types +contributor = (obj) -> + _.extend obj, # This way we get the name etc on top + collection: 'users' + userField: '_id' + category: 'contributor' + +### UNLOCKABLES ### +# Generally ordered according to user.stats schema +unlockableAchievements = signup: - name: 'Signed up' + name: 'Signed Up' description: 'Signed up to the most awesome coding game around.' query: 'anonymous': false worth: 10 @@ -21,27 +33,155 @@ achievements = userField: '_id' category: 'miscellaneous' difficulty: 1 + recalculable: true completedFirstLevel: - name: 'Completed one Level' + name: 'Completed 1 Level' description: 'Completed your very first level.' query: 'stats.gamesCompleted': $gte: 1 - worth: 50 + worth: 20 collection: 'users' userField: '_id' category: 'levels' difficulty: 1 + recalculable: true completedFiveLevels: - name: 'Completed one Level' - description: 'Completed your very first level.' - query: 'stats.gamesCompleted': $gte: 1 + name: 'Completed 5 Levels' + description: 'Completed 5 Levels.' + query: 'stats.gamesCompleted': $gte: 5 worth: 50 collection: 'users' userField: '_id' category: 'levels' + difficulty: 2 + recalculable: true + + completedTwentyLevels: + name: 'Completed 20 Levels' + description: 'Completed 20 Levels.' + query: 'stats.gamesCompleted': $gte: 20 + worth: 500 + collection: 'users' + userField: '_id' + category: 'levels' + difficulty: 3 + recalculable: true + + editedOneArticle: contributor + name: 'Edited an Article' + description: 'Edited your first Article.' + query: 'stats.articleEdits': $gte: 1 + worth: 50 difficulty: 1 + editedOneLevel: contributor + name: 'Edited a Level' + description: 'Edited your first Level.' + query: 'stats.levelEdits': $gte: 1 + worth: 50 + difficulty: 1 + recalculable: true + + editedOneLevelSystem: contributor + name: 'Edited a Level System' + description: 'Edited your first Level System.' + query: 'stats.levelSystemEdits': $gte: 1 + worth: 50 + difficulty: 1 + recalculable: true + + editedOneLevelComponent: contributor + name: 'Edited a Level Component' + description: 'Edited your first Level Component.' + query: 'stats.levelComponentEdits': $gte: 1 + worth: 50 + difficulty: 1 + recalculable: true + + editedOneThangType: contributor + name: 'Edited a Thang Type' + description: 'Edited your first Thang Type.' + query: 'stats.thangTypeEdits': $gte: 1 + worth: 50 + difficulty: 1 + recalculable: true + + submittedOnePatch: contributor + name: 'Submitted a Patch' + description: 'Submitted your very first patch.' + query: 'stats.patchesSubmitted': $gte: 1 + worth: 50 + difficulty: 1 + recalculable: true + + contributedOnePatch: contributor + name: 'Contributed a Patch' + description: 'Got your very first accepted Patch.' + query: 'stats.patchesContributed': $gte: 1 + worth: 50 + difficulty: 1 + recalculable: true + + acceptedOnePatch: contributor + name: 'Accepted a Patch' + description: 'Accepted your very first patch.' + query: 'stats.patchesAccepted': $gte: 1 + worth: 50 + difficulty: 1 + recalculable: false + + oneTranslationPatch: contributor + name: 'First Translation' + description: 'Did your very first translation.' + query: 'stats.totalTranslationPatches': $gte: 1 + worth: 50 + difficulty: 1 + recalculable: true + + oneMiscPatch: contributor + name: 'First Miscellaneous Patch' + description: 'Did your first miscellaneous patch.' + query: 'stats.totalMiscPatches': $gte: 1 + worth: 50 + difficulty: 1 + recalculable: true + + oneArticleTranslationPatch: contributor + name: 'First Article Translation' + description: 'Did your very first Article translation.' + query: 'stats.articleTranslationPatches': $gte: 1 + worth: 50 + difficulty: 1 + recalculable: true + + oneArticleMiscPatch: contributor + name: 'First Misc Article Patch' + description: 'Did your first miscellaneous Article patch.' + query: 'stats.totalMiscPatches': $gte: 1 + worth: 50 + difficulty: 1 + recalculable: true + + oneLevelTranslationPatch: contributor + name: 'First Level Translation' + description: 'Did your very first Level translation.' + query: 'stats.levelTranslationPatches': $gte: 1 + worth: 50 + difficulty: 1 + recalculable: true + + oneLevelMiscPatch: contributor + name: 'First Misc Level Patch' + description: 'Did your first misc Level patch.' + query: 'stats.levelMiscPatches': $gte: 1 + worth: 50 + difficulty: 1 + recalculable: true + + +### REPEATABLES ### +repeatableAchievements = simulatedBy: name: 'Simulated ladder game' description: 'Simulated a ladder game.' @@ -60,19 +200,33 @@ achievements = c: 0 Achievement = require '../server/achievements/Achievement' +EarnedAchievement = require '../server/achievements/EarnedAchievement' -Achievement.remove {}, (err) -> - log.error err if err? - log.info 'Removed all achievements.' +Achievement.find {}, (err, achievements) -> + achievementIDs = (achievement.get('_id') + '' for achievement in achievements) + EarnedAchievement.remove {achievement: $in: achievementIDs}, (err, count) -> + return log.error err if err? + log.info "Removed #{count} earned achievements that were related" - async.each Object.keys(achievements), (key, callback) -> - achievement = achievements[key] - log.info "Setting up '#{achievement.name}'..." - achievement = new Achievement achievement - achievement.save (err) -> + Achievement.remove {}, (err) -> log.error err if err? - callback() - , (err) -> - log.error err if err? - log.info 'Finished setting up achievements.' - process.exit() + log.info 'Removed all achievements.' + + log.info "Got #{Object.keys(unlockableAchievements).length} unlockable achievements" + log.info "and #{Object.keys(repeatableAchievements).length} repeatable achievements" + achievements = _.extend unlockableAchievements, repeatableAchievements + + async.each Object.keys(achievements), (key, callback) -> + achievement = achievements[key] + log.info "Setting up '#{achievement.name}'..." + achievementM = new Achievement achievement + # What the actual * Mongoose? It automatically converts 'stats.edits' to a nested object + achievementM.set 'query', achievement.query + log.debug JSON.stringify achievementM.get 'query' + achievementM.save (err) -> + log.error err if err? + callback() + , (err) -> + log.error err if err? + log.info 'Finished setting up achievements.' + process.exit() diff --git a/server/achievements/Achievement.coffee b/server/achievements/Achievement.coffee index ab248d90b..9039370a3 100644 --- a/server/achievements/Achievement.coffee +++ b/server/achievements/Achievement.coffee @@ -29,6 +29,8 @@ AchievementSchema.methods.getExpFunction = -> parameters = @get('function')?.parameters or jsonschema.properties.function.default.parameters return utils.functionCreators[kind](parameters) if kind of utils.functionCreators +AchievementSchema.methods.isRecalculable = -> @get('recalculable') is true + AchievementSchema.statics.jsonschema = jsonschema AchievementSchema.statics.earnedAchievements = {} diff --git a/server/achievements/earned_achievement_handler.coffee b/server/achievements/earned_achievement_handler.coffee index 4002de462..20da7f159 100644 --- a/server/achievements/earned_achievement_handler.coffee +++ b/server/achievements/earned_achievement_handler.coffee @@ -47,7 +47,7 @@ class EarnedAchievementHandler extends Handler # Fetch every single user User.find {}, (err, users) -> callback err if err? - log.info "... for a total of #{users.length} users." + log.info "for a total of #{users.length} users." async.each users, ((user, doneWithUser) -> # Keep track of a user's already achieved in order to set the notified values correctly @@ -68,6 +68,8 @@ class EarnedAchievementHandler extends Handler newTotalPoints = 0 async.each achievements, ((achievement, doneWithAchievement) -> + return doneWithAchievement() unless achievement.isRecalculable() + isRepeatable = achievement.get('proportionalTo')? model = mongoose.modelNameByCollection(achievement.get('collection')) if not model? @@ -79,6 +81,8 @@ class EarnedAchievementHandler extends Handler finalQuery.$or[0][achievement.userField] = userID finalQuery.$or[1][achievement.userField] = mongoose.Types.ObjectId userID + log.debug JSON.stringify finalQuery + model.findOne finalQuery, (err, something) -> return doneWithAchievement() if _.isEmpty something @@ -105,16 +109,12 @@ class EarnedAchievementHandler extends Handler EarnedAchievement.update {achievement:earned.achievement, user:earned.user}, earned, {upsert: true}, (err) -> doneWithAchievement err ), saveUserPoints = -> - # In principle it is enough to deduct the old amount of points and add the new amount, - # but just to be entirely safe let's start from 0 in case we're updating all of a user's achievements + # Since some achievements cannot be recalculated it's important to deduct the old amount of exp + # and add the new amount, instead of just setting to the new amount return doneWithUser() unless newTotalPoints log.debug "Matched a total of #{newTotalPoints} new points" - if _.isEmpty filter # Completely clean - log.debug "Setting this user's score to #{newTotalPoints}" - User.update {_id: userID}, {$set: points: newTotalPoints}, {}, doneWithUser - else - log.debug "Incrementing score for these achievements with #{newTotalPoints - previousPoints}" - User.update {_id: userID}, {$inc: points: newTotalPoints - previousPoints}, {}, doneWithUser + log.debug "Incrementing score for these achievements with #{newTotalPoints - previousPoints}" + User.update {_id: userID}, {$inc: points: newTotalPoints - previousPoints}, {}, doneWithUser ), onFinished module.exports = new EarnedAchievementHandler() diff --git a/server/plugins/achievements.coffee b/server/plugins/achievements.coffee index d440ca0e1..fd1e7af7d 100644 --- a/server/plugins/achievements.coffee +++ b/server/plugins/achievements.coffee @@ -38,9 +38,9 @@ AchievablePlugin = (schema, options) -> isRepeatable = achievement.get('proportionalTo')? alreadyAchieved = if isNew then false else LocalMongo.matchesQuery originalDocObj, query newlyAchieved = LocalMongo.matchesQuery(docObj, query) - #log.debug 'isRepeatable: ' + isRepeatable - #log.debug 'alreadyAchieved: ' + alreadyAchieved - #log.debug 'newlyAchieved: ' + newlyAchieved + log.debug 'isRepeatable: ' + isRepeatable + log.debug 'alreadyAchieved: ' + alreadyAchieved + log.debug 'newlyAchieved: ' + newlyAchieved userObjectID = doc.get(achievement.get('userField')) userID = if _.isObject userObjectID then userObjectID.toHexString() else userObjectID # Standardize! Use strings, not ObjectId's From 6267352c6e340851186b05a98f1b246df8532bf4 Mon Sep 17 00:00:00 2001 From: Ruben Vereecken Date: Mon, 4 Aug 2014 15:26:21 +0200 Subject: [PATCH 57/83] Drastically changed the way achievements get grayscaled --- app/lib/utils.coffee | 9 +++ app/models/Achievement.coffee | 27 +++++++- app/styles/achievements.sass | 60 ++++++++++++------ app/templates/achievement_notify.jade | 28 ++++---- app/templates/user/achievements.jade | 7 +- app/views/user/AchievementsView.coffee | 7 +- ...ground2.png => achievement_background.png} | Bin .../achievement_background_locked.png | Bin 0 -> 4388 bytes .../achievements/border_diamond_locked.png | Bin 0 -> 27426 bytes .../achievements/border_gold_locked.png | Bin 0 -> 12558 bytes .../achievements/border_silver_locked.png | Bin 0 -> 12162 bytes .../achievements/border_stone_locked.png | Bin 0 -> 10568 bytes .../achievements/border_wood_locked.png | Bin 0 -> 13317 bytes 13 files changed, 101 insertions(+), 37 deletions(-) rename public/images/achievements/{reward_background2.png => achievement_background.png} (100%) create mode 100644 public/images/achievements/achievement_background_locked.png create mode 100644 public/images/achievements/border_diamond_locked.png create mode 100644 public/images/achievements/border_gold_locked.png create mode 100644 public/images/achievements/border_silver_locked.png create mode 100644 public/images/achievements/border_stone_locked.png create mode 100644 public/images/achievements/border_wood_locked.png diff --git a/app/lib/utils.coffee b/app/lib/utils.coffee index 663dbe959..44ef9dd1c 100644 --- a/app/lib/utils.coffee +++ b/app/lib/utils.coffee @@ -109,3 +109,12 @@ module.exports.keepDoingUntil = (func, wait=100, totalWait=5000) -> if (waitSoFar += wait) <= totalWait and not success _.delay (-> func done), wait) false +module.exports.grayscale = (imageData) -> + d = imageData.data + for i in [0..d.length] by 4 + r = d[i] + g = d[i+1] + b = d[i+2] + v = 0.2126*r + 0.7152*g + 0.0722*b + d[i] = d[i+1] = d[i+2] = v + imageData diff --git a/app/models/Achievement.coffee b/app/models/Achievement.coffee index 1010d2eaf..74d7d55d9 100644 --- a/app/models/Achievement.coffee +++ b/app/models/Achievement.coffee @@ -24,5 +24,30 @@ module.exports = class Achievement extends CocoModel getNotifyStyle: -> Achievement.styleMapping[@get 'difficulty'] + @defaultImageURL: '/images/achievements/default.png' + getImageURL: -> - if @get 'icon' then '/file/' + @get('icon') else '/images/achievements/default.png' + if @get 'icon' then '/file/' + @get('icon') else Achievement.defaultImageURL + + hasImage: -> @get('icon')? + + # TODO Could cache the default icon separately + cacheLockedImage: -> + return @lockedImageURL if @lockedImageURL + canvas = document.createElement 'canvas' + image = new Image + image.src = @getImageURL() + defer = $.Deferred() + image.onload = => + canvas.width = image.width + canvas.height = image.height + context = canvas.getContext '2d' + context.drawImage image, 0, 0 + imgData = context.getImageData 0, 0, canvas.width, canvas.height + imgData = utils.grayscale imgData + context.putImageData imgData, 0, 0 + @lockedImageURL = canvas.toDataURL() + defer.resolve @lockedImageURL + defer + + getLockedImageURL: -> @lockedImageURL diff --git a/app/styles/achievements.sass b/app/styles/achievements.sass index 141d24e76..122f81e45 100644 --- a/app/styles/achievements.sass +++ b/app/styles/achievements.sass @@ -1,7 +1,5 @@ .locked - filter: desaturate(gray, 50) - -moz-filter: desaturate(gray, 50) - -webkit-filter: desaturate(gray, 50) + // This used to be a grayscale filter but they're mad intensive to paint .achievement-body .achievement-icon @@ -19,8 +17,14 @@ right: 0 bottom: 0 + &.locked + .achievement-content + background-image: url("/images/achievements/achievement_background_locked.png") + &:not(.locked) + .achievement-content + background-image: url("/images/achievements/achievement_background.png") + .achievement-content - background-image: url("/images/achievements/reward_background2.png") background-size: 100% 100% text-align: center overflow: hidden @@ -150,30 +154,48 @@ background-size: 100% 100% z-index: 1 +.achievement-icon + background-size: 100% 100% !important + .notifyjs-achievement-wood-base, .achievement-wood - .achievement-icon - background: url("/images/achievements/border_wood.png") no-repeat - background-size: 100% 100% + &.locked + .achievement-icon + background: url("/images/achievements/border_wood_locked.png") no-repeat + &:not(.locked) + .achievement-icon + background: url("/images/achievements/border_wood.png") no-repeat .notifyjs-achievement-stone-base, .achievement-stone - .achievement-icon - background: url("/images/achievements/border_stone.png") no-repeat - background-size: 100% 100% + &.locked + .achievement-icon + background: url("/images/achievements/border_stone_locked.png") no-repeat + &:not(.locked) + .achievement-icon + background: url("/images/achievements/border_stone.png") no-repeat .notifyjs-achievement-silver-base, .achievement-silver - .achievement-icon - background: url("/images/achievements/border_silver.png") no-repeat - background-size: 100% 100% + &.locked + .achievement-icon + background: url("/images/achievements/border_silver_locked.png") no-repeat + &:not(.locked) + .achievement-icon + background: url("/images/achievements/border_silver.png") no-repeat .notifyjs-achievement-gold-base, .achievement-gold - .achievement-icon - background: url("/images/achievements/border_gold.png") no-repeat - background-size: 100% 100% + &.locked + .achievement-icon + background: url("/images/achievements/border_gold_locked.png") no-repeat + &:not(.locked) + .achievement-icon + background: url("/images/achievements/border_gold.png") no-repeat .notifyjs-achievement-diamond-base, .achievement-diamond - .achievement-icon - background: url("/images/achievements/border_diamond.png") no-repeat - background-size: 100% 100% + &.locked + .achievement-icon + background: url("/images/achievements/border_diamond_locked.png") no-repeat + &:not(.locked) + .achievement-icon + background: url("/images/achievements/border_diamond.png") no-repeat .exp-bar-accumulated background-color: #680080 diff --git a/app/templates/achievement_notify.jade b/app/templates/achievement_notify.jade index 505097224..5b07afabd 100644 --- a/app/templates/achievement_notify.jade +++ b/app/templates/achievement_notify.jade @@ -1,15 +1,15 @@ -div(class=notifyClass) - .clearfix.achievement-body(class=locked === true ? "locked" : "") - .achievement-icon - .achievement-image(data-notify-html="image") - if imgURL - img(src=imgURL) - .achievement-content - .achievement-title(data-notify-html="title") #{title} - p.achievement-description(data-notify-html="description") #{description} +- var addedClass = notifyClass + (locked === true ? ' locked' : '') +.clearfix.achievement-body(class=addedClass) + .achievement-icon + .achievement-image(data-notify-html="image") + if imgURL + img(src=imgURL) + .achievement-content + .achievement-title(data-notify-html="title") #{title} + p.achievement-description(data-notify-html="description") #{description} - if popup - .progress-wrapper - span.user-level.badge(data-notify-html="level") - .progress-bar-wrapper(data-notify-html="progressBar") - .progress-bar-border + if popup + .progress-wrapper + span.user-level.badge(data-notify-html="level") + .progress-bar-wrapper(data-notify-html="progressBar") + .progress-bar-border diff --git a/app/templates/user/achievements.jade b/app/templates/user/achievements.jade index 1bdf4bc85..cd64db67e 100644 --- a/app/templates/user/achievements.jade +++ b/app/templates/user/achievements.jade @@ -13,9 +13,14 @@ block append content each achievement, index in achievements - var title = achievement.get('name'); - var description = achievement.get('description'); - - var imgURL = achievement.getImageURL(); - var locked = ! achievement.get('unlocked'); - var notifyClass = achievement.getNotifyStyle() + - var imgURL = achievement.getImageURL(); + if locked + - var imgURL = achievement.getLockedImageURL(); + else + - var imgURL = achievement.getImageURL(); + - console.log(locked); .col-lg-4.col-xs-12 include ../achievement_notify else if activeLayout === 'table' diff --git a/app/views/user/AchievementsView.coffee b/app/views/user/AchievementsView.coffee index 7ca4dfb72..20cc05717 100644 --- a/app/views/user/AchievementsView.coffee +++ b/app/views/user/AchievementsView.coffee @@ -25,13 +25,16 @@ module.exports = class AchievementsView extends UserView super user onLoaded: -> - console.log @earnedAchievements + console.log @earnedAchievementsy console.log @achievements - _.each @earnedAchievements.models, (earned) => + for earned in @earnedAchievements.models return unless relatedAchievement = _.find @achievements.models, (achievement) -> achievement.get('_id') is earned.get 'achievement' relatedAchievement.set 'unlocked', true earned.set 'achievement', relatedAchievement + deferredImages = (achievement.cacheLockedImage() for achievement in @achievements.models when not achievement.get 'unlocked') + whenever = $.when deferredImages... + whenever.done => @render() super() layoutChanged: (e) -> diff --git a/public/images/achievements/reward_background2.png b/public/images/achievements/achievement_background.png similarity index 100% rename from public/images/achievements/reward_background2.png rename to public/images/achievements/achievement_background.png diff --git a/public/images/achievements/achievement_background_locked.png b/public/images/achievements/achievement_background_locked.png new file mode 100644 index 0000000000000000000000000000000000000000..94a300b1224b7ffacdaa3965d830f76568ea0976 GIT binary patch literal 4388 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 L?y6QO+eQ8l&MW=) literal 0 HcmV?d00001 diff --git a/public/images/achievements/border_diamond_locked.png b/public/images/achievements/border_diamond_locked.png new file mode 100644 index 0000000000000000000000000000000000000000..bc56fbc754aa4bca64976411122fb49398b93da6 GIT binary patch literal 27426 zcmXtA2RzpO+r90KBzr|qW_HNl$tKBm8(CS|8Icu9_TDRduk1~BMz-utvPlx&%m4Fv zpXY8V`rY4gu5+Dpu7XrmWN+h8;vf)++wyYKYVhOS&9_^a@I968urK_8X{;zKjkvz~ zKchJ>8a{$;FQ?;#K;RPHd_zGbrI5o1v7F_VpJFZCLce{NzfOgQ0fD%Wke8NJcl+_H z+1*)h>dpQ&!v+Pqq&?zQ1mV-5n5Syvraf^_<-=Cx!q&4ZiVa8k9F7eBj;5z>$~ASB*-$(_yJDaxw^ypPoF*wAW+98S!l9c zM4UzJYcXt4SK?4Y{X@}}u_YYVdLuc7slzTDo5NL^@y4g7uJcPucF_^y2tz^v@l3bu z(Jr?wwIQGF4c9;9Ss*AAOQsp870J=9)O zyB+eetNvkF;aGc`&%#E^gQBo@rC6d0bCVJhv^}o=otMczxGQ@3!{cnPi!NNmFCYNp z-x8L18?4&cf?tK#b7B9`;rVgto3Ixzjy&I9QO0ewb{ zZU^vgNZKSAWN|*OeT<+ppnLh^^!IPCe@E*BSQgV(*4Brv-e<1N4<9c7!_(K-|1&e} zh1t3|{I^+&G1=16@;v_U-@lTGG?7@2NNf=r3D`a&Q(g;R8)Sd3Qb+FI?r!t2afN53 zSNmWx|LS26ZJ0g#;UER$<|%)5iB$cXB0R_0V3we!VC!CChRzQSQIZZHqH* z$;)q@J!ogXEEL$3Z9~#H%=~J8MLKdM1vaF?>$oSu`_mO@&qNNTf3_^{_naSZF53$r z5O14l1_uX6EO-%!vZ-MB;>M1SQMe7)`i6$$5$Hp*60#~3_Jd;#0vU=22ZwF%2=%h=>7Zm2}wy(^n~)4E!o-VySuwwiAn^) zT?cQQ-TrRpJ>ueO!%_U3Ms{Scpz+c?i0q|42x_kd5#HV?kI)s z*l*~wtKs5dLqjSEpV?g*J3G#WcW3%_NDk?&?_ciZeYYsL9AmWk@df+sW$f3DJKleu zPz$@T-ER2(`*$mUqSE_1A+4V32LaBUCdAIj+9e_m5&J1zUB+Gh|fVam83&uM5Y;eTQ$7PrAjR<`F14tb9N zcidf;+^leIQj!p}=gqT}BFitHiN2ZjHRnsNXUwgilOm*f%OGAa<^P<+(SV&KFS2K8 zX({gN>gqaDjvk%Y1`fv7&Q9mt9P;Oxc@+h$^Uh>RXV^XA`vkWHt~pCIap5=?6g;e( zw~dL5%OzxnchWtSD!hDrZofUspmy&mO?cjEbo#&S`DOdM_65SPb?s~KupR!pdzXyz zNJ{!GYGRb)+O?JuUo!4wnc*<&oGfypvOaVMDXrH0H6|*OBYWDog4eBi;W2!E=dKGC z87b*FGTnNy<=xiSmfS-M6I@(ea!N{6gy*ic;S*Sg$?M@|`=$ELwD(gxPu<d>(@FXeL`okUD3#PqHU!bc&; zPbF{Q+`HeD8xFurPfu#wTH)P&zU14=%F5(aRGr_x;qBO=p`nqJlaHW|pjwm`70oVA zPm{B8a?)Dc*m(9BSKu|w@0qa-1*?jW5y&u;sYl>8kr{ecEM?)PZt7JugR~Y;>YN-u>;P!bIkC(vV1D9g4%zFlg6^eohs}i_;|NIyC7gx?M zCRopH7#JArg;I;_zkL7x{7Y?Zo+uw_FbaYx1i>XNj2l02Q(k=~e2ln0e*Rqa4eNb7 z@6uM^fuA4h8j8M{Nx~*_ zSr;BjuW(w118G`^>B^F;C58FuAk>GE`Z2JK9m~thCCA6dcm8;`9C?aAb8_O*)zwwi z(hA(zFwa+KY5I3G;C8+quS3Ag&kv;}*K5;Q*3VbEM=rm{t4^s*Se+`A|2-o?(&jw~xHt6{6#;fe>bjimjQc94Bt@*~p(iEHNi zMB2n`c^U`G5{v94dBMtXLIihkSLIt9n_npD!&7(h1H0ggRac^@%F$k8JZMH5N7ihU$`&F34*2p{3Sy7wd( zsiGdFP?t?auX5rIjM+b#M?HqS3NKMn(E{u&>SVegW*@e;m zcx;iOul*(XXhi(~DI~C>S5qJ48>pUl3!_rumKRR%Bamm$e)Mg_E zxW}8HpV!vbUh=#I8qoCjXVH2@|JI+GaWfNAZ${nP=2t&pL9PawW zT5jjsrVg`w|Ja8VQjMY9f46pb=O6R%9NP;}PtMEr_SUfx+**Qy{%5w;Z1`PmZLP`W z-&2C1QY2P5t#}&h2~YaDl0n(^MzURQh6ynTW#~#r0IucF-@A6+R3s!Mss-v11M37h z0fL6=yn)wc7S~+hEay*SD z%IG^rQ7%ybTvbIR?DET}&$NHX=|ksBw`K!)p}&{ZycacP;Bou%@{(;`ugd&3?D2iV zppmJmc*^m!MQ`!`{{G6A_^F+)GVjZs@yp%WTBv8sD_y|_r*R?GH8sY7Z^fI6^n}c@ z3UF5Q)eB>AllvMP8^2FX1f->Pjzuks&dmZK#KpxBE^gbj9S$ z$1hDy(X}?x%9L);d0$`ZES|x37yM;>`t)gjSy|wWjf?{loeJ+$E$>(-+i(=s zq6w)SH8b@#*jFHo%coO@!-a2ws|eUHNnGbB#;3V9&f}MpsW?r1&z@H*txl>XcCMwi z*ZEWs6&qZSQ&_1ma{Kn}>d^3T@9zDxvLQZ+-}{T{kIp539364T#m6tZG``sW=NY0Y z7$oi2&>$2OAD>lO8I?mrdokTJGSYTWO+{Hb6SnV7Yi1^jW)Vq+HXa=vo!|sJ?U)7c zh*dSbQV!gk4FFb_*5eI_$affG`G2>e-Ju44`AboLtJ1ee>0v~Xrg3dyu=@ZaJ`lH0 zWA%~p_c>eF!$h7}ElZB|HD+(i?g_hOR8{dXSs0X&spL6zMXamUkB+JllaNT3Xwne| zwe6gm2kiYjKW2-6&Jz2%sfn70hvybLs%2yA@$*{GB~}&|Kh2``e;!&|TAByI7ihfx zyuAKVZSCy^WcdZUvlmPEszUKxCC3Ik3KE}8&Htp*iPr)LaTLw;%&Xt3dM~)kxKioU z?`e~8vrwR);|~our{;?6u`PkahmFhI<(YffL*{%FD%dke4{u) z&^-l3#ph~jQ9L?P%BS$BUZu>!dEZR;7BdMsgDv~I*K|BIwdk?mtQ8$`E`S^)khPI5 z8~msGiV6{t6MqQr)S4l!rQu60v{rw?xp;Y|X*x3By;wDCnn1OX>4i0m69+RQ<<<3Bz zBAc!KRb0bA9ZJl^Z-=a^4H#F1#iudCrPOW68Z}lL?y%bnCK*QP8Tb0%s=A~6vRc>L z7f{ATWo^#1Np8jXU*e4@qaIWNQ)G>xXwBB;6lGZp6Tpd?^5|p3VqJW zg;r=wET(P!d3bu!`BSU0S9W&$?~q;U$3bJI7`#w(gS$|n06plnsG8l?Mj}lwOW~v# zSGYYbTRpoN>raOkGz2>=w(S*nq7iqZX0~Ty+s@uzch#`iz_$71#Kdx&FWN>xKmcw1 zyshx42R(X(`@-In+3US0SV+?8tsh~gqFxw?oY`FtLEy4v_1qB(IfNHy`#lTI9z5OJ zRaI3f7roqxUB>Oby}gfn-bl}pkOp^YM#}NdE-tn{Bn{4qn=6?3`NcPBU}JOhE*@qd z%KmJ_o1I8i=Iq;;@2d~p<_LnM)u)aV;iOPfQQeR)*Kc`h1**Eb{YNZK2ApZ{e(MbN zIpwGY$b4NZ&r<7hd0BJM;R^&GWOXj&w{FLMv%0W3wvR$`5ZxPR#p?=B<0;2yG5GWn%7jT#!<0Z@2Ih3@4Ol7yK~!~!f0Uf-vf>My4Kd~ zw8O(gPKWx4o5j4kybxS;a(Ab2a&mIdIeRBY`_26LlL-$3*2W1ZV5LbRr@?}Y920vA ztFK+6ug=9EX233L%;inRIulN4`;1RNVDy^?t%j`MC51 z8ek$QR!6AM07$-kiHZH%r`{G*C6%mJ8`cuT-o1@6XQf)*A?-z; z%5HL6+QRU97`2F71kVI5e&8cf(NwJzQvAS|e92>RGV3Ta#R@q>SyMZU8U^Z;cqm?e ze_Oi%fLy-~4b|wBOp5Za%q1!18g4l^RA>}wrsmDDAHC}fKzo3QQe{@RsGXgCCuV5P zMQ&5Cv(&flsIH+=!Ph@}fzh{r62z3oHM{$pwsC$B167E2w6WQXIrE~;cL0S-D=(4Q zrWL9jt)Z@NLp19n$AcxwAw2q3u?<}cNA}VzRc2>6avV6^LvMWX1D`zN=B{jCJuJq@ zeE<5;tw|(OE(SY53Q*j?^CdkYHZd`AHh1J2`|EFKatk={8v#;ZR~^0@KZ5Ve8R7k=<9M3zS7?_>&Z1u2nkEXp z>(M{NJ$~C5P;sJ2WU8J3?N8$+$1^vbp0}x;H6!CRXfkndI7~C*nx+Zv3Q=F7k6m~r z@zRTfnOSb$QL@J~@X$@<4LyotBZF2k*IudggKbteHW58NJrA#mLJizrHLH$w$H#0O z9DDfas5{3&=UoQUskCcRFUrgIJkw6;AxD z4Ut*h&rvJD{pE=I60r;#*Gff2g>Zaa+;3pxGUoW9X^JL{CGtxNBy?+$^7G_dy=F$d zrfZUyv;}AE%*;O8Y&4q8#ILNat*@)9t4|5*rt%b|G9zq($a;nMRMF8MBz&N&&n5Dg z`Y%MrgoS74^zGrl+9v78hCE;jFvyu06KCVv@RkPyn2U+B^} z&v3)`@l%7;Sz-m8w+J>h#?7nNV|}SBNG~0kJ-zdqWLaPKL@fHV`vf2x4O{XiEtcq1 zZM15Z8N9|i!^fNX_U+s6oSYod3T?K{H6&b9gGNqgp?fSWENYE}++A;gJ%_)ZZ#dmn zjOVy*=EIS|klT52d;S6+`GJH!yHfrkzgq0=8eo5LDY8VkYk#RqN z!s@yzH+Mt%0bQIQ$SY;;=H?qj24$|#_{?7VEV*5dvy)k@;Y2ENFsy1ujeO7G313#z zBJ}*x6|0c*?rrnc!QOo1;+h}6kEe^vZ-cTM@HC1&T{b!=0Sm*63wCw?$iVJ1x!t5AH zLce{$SbV!vGS8R1I{?jp%0O&ZEYs{cWBX@GZTV69SxGd7P8HhhQCR$s(w_xGWsV!6 ze{EIM)FfqW;YpOZuT2!()o*TYUc<)DUfm71dM2u-hObzsiZL%(M?)i4mm%7+CCqO~ zS#mRP<>xdO#yl1Vb+ey;(A{1$T|Ao{R^QhEvtwgpI;++80wGSDLk^n4jLMABiNNIy z)C$yj&@I}#x}H9VLfZ-3lO7`dpxaF(%!0Syaor-eFW2_Vj|hSY#D>Y2PwN*8$o=7U zbM8Pn`Xgde(yIs92L4Gkksi$ph0p-WIw?O?*VHr~h+`MzNtDTY^!U}Q3hjfn=x2^n z@gduCTb^G%?8EAlsSjx^h~nfZj;YUXDcvFc&lsN@mBGYabW5;3Wm>W489(2a+)A7GX<@_M4|O>L2KbcQPV10uk^PEA z25N`o2VQU>RcKrqo8NeOT|KMna)lc1y7^D1YQfrQ9nGX}|0gR;ono8hA%v0$RTe4iB$>4Ivy?i}ZIqUcC_J=pw=i05vkLqoczRPTAQa^A<5& zA~)E~^r@YBujVlXtsoroAqI@f+VKd+Fe1-*(i*ofmp>ZVoOtm$PI&i+qGw!P?t5SR zgZl5BWyF&_X!)2z{sl8}GbkPDk0}Q&jua0CsKfk9OG`ObR8+iJ$?+(&zB4DhSE(+e zf8@wYc`5EtpC>J&zUwS>78V+sc=GmgmL-;@E&roh0TMc%cV4ZAv_aA=bqm(OzQxix zcNQ8gcyYpe-gk6JZJoaTQeC~A>bmgKodf#a=-TS)64(n-bS0!H-l#6!;y151{3LaF z4#^1$Q4vnBlc2zhHkFqjU<;jIf8!ZO64}5KQcOvb5)-2b2L~ILmXut^%F#P=m&zI% z8k!gyE}k_T>`%(kN1?JO7tV9M`(5cPn-(lbuf{JZcwXMQOkPq{lx!45{2-D!duoRh zDkz|!1-k619q`oj;lQYNm2)$H!khT(bB>h?H{R$!N4} zTfbZTi1qr#_BAM$9_*w8Ce9L<<1e0Ne))2eWHb;HL=2P&*!;Dwn%d}-KYtcF1Mhg7 z0n8;Y+iMTD$qX0Ghh?lsXJ@tt_pGXg_Z%OBTJ-`Nlq?r)ghQwd4@0D!OtXQV;)BQt zC&^dUy3b#{h$eGBJv{~I;H4y4kTe$`pQgk0FyheUnKs%eHq$W)df-8Iv@731N?^W*Y?b%6#FLN#wE`H^v zyZB`%KpFT$u|f0S^k|N3GM}54|12y#oEBhJhC_ocUV470u!ay(KR&eXC@S#yF^NHy zx$+&J-lq?q_Zo4XxQV14!M?hejGSM3K{K3LQgR!v7?AyfRv=}liApsU#l^pznTc`M zGoASdtDBbF%4D(EF_BdM^OTXgXsor(*D7?L2-KL2|{tN!` zguZFpr>EgNh_|pCICfY$HdY zLm3|*H}%{H+_o3ME71%FkRsih{}^}6_W zc5`#{8ECzl6pf$~dahC}8a3C9ZJX*>*V(wa2~7B>@iiM<)54kZHZ^h*pvJIkI_wsA z7fHM5&At_1c(}LP-%cl7G-^c_+@noU_1yY6e&Ra3((I3Pwl7ds;i;l$ zY|Oh}10WBJ9OGFRz?JEpi5)Yp$S5o0B(mXz0z&grebBZRH;hK~FDD>cfb)OR?|rqZ zle1m${QHiPHr2XkG7lnGLH=?v_p#j1Y@jodR>Cl;?7(hEZZB9Fl@c7)*wB#nd9@}l?JbUmh6bA! z$lfA5ZLal5fj2*irwqz6zFT;fHZ}dDM@ycuDTNP?a{?=iLjgNzCGG0f)+rFXdhHgy|6lFoWg-->qn_3PKxRuESkqvYA{AfB0-t<#>q zy~IsiIY99$bux;J{!4RgA4EkSG4y6Lm`5tPh3rBlLhPb?;Pwcykc5fK&G@avCk!7~UC z3=9-uFC{d;3z7I}v1IE%ZxN6+FJGz~b>!>oE6$LftY}6z_!3GG1*^ewy?D|zuX0Okpou4+1<9jTT^P%w*r@B z3g$x9>y$35Ur-Uv+tw~vRf|bCMme7!dh$g$>dzD7Jbk9GKhOG&?6H_w>7(R9aqPfO z%W{WP{ngz|*GGv7Vg5trRhPlU>~4H|n5J_DmFpMIf4{P6Ssd$P?rG7)L1H42?V$Sf$gV=H_W z+BW*tRukcgoV{>K-+Fjl|3?^L4A% z|8)B77J4R_*!zCd$Iic|EA{~wWw<3fI)5*J2>Y}>{^ZFM@p9dg4ti&3Sggtl3fxuh z6ZrQ|fl4Q+uUI>K8w8Nci?h7bgg>3MS0kDR1_n>-V$`I#$fu4zXGD z2Tm!4(DTYy!)d(R!{*EOzJa(&FPcJU1w$ZWg%$7vAhsb2z z3hX3-_F%DR;&!_MaKm~yWQ%28)ut@AV+OJFwwyO^l6QfCib7p1ztXy`e}|b`t@`o^ zl=;SgUfy))**7+Jbt^lq)~F3%-tI4UHnxAEDtT1qMsP#6RG{Hw8f%cFsnNq~fVRho zt8RO$_3CuWzWQ%m8K6-NND(Tnm{VN|1JyQ`mNyQMj<@iepXxbMncBDa9-=OXep)ZZ zt|Q4$#`F0GPJf!G7x+g-QjP-DL)!|Yte6N1mRMaYE32z$-(9d3VnK{CWYUT_D4*A* zP~y%t!LOaYJg1Wlv%&a&ySk<23Ip~&rz);r#Mwg8mztgsM6^t|aWJb9{ zWUjzoL%fH6c!yb%tD-X2T9$1+V_o8Os6rhc@VRvPv_EaLcCF3e)Se?KY9Ea}GJIfH zOX~cM9ovA59C`f1x=d6?ViQiNAX{`FW8dbTJ9jqK>j4Pl=m}L~U$Dg9_V0+CQMgOy zypLs{s2N-SGj>glAH?V5uOmMh34=aET@p7|0f(AQ`Pnn^uIlvA@L4e-dE)MB=X#_e zINT-wWftY<|EW-A4k?vhht;&I@b~UDnrypS5&Pd|AC#xiZ(Ot>~Aw6CieE_ zkCPj^j4PTNevJIgRV(mPB(^+F7kcw^_(9}Hp%{b!s(pciIf$Tv!K5ujmE!qsSFdr&gStd#303 zWL3pZ#fbD#3E7g<$DW4>}VgMRMUl5eM~Vi zF~!>RQR{^Qd|^0U{Fwo)r?5RjJ zI|oPZU>`VueN6^-qU&nrDd7X_nx*Q?=*0G>14ezIk_M}ku8sIcCmp)#vTYhqZ|?4%I8cI25A4tvw~rBVe28TEkerYpx1+6KYc^a>6<^=MCS4+C@p@2 z^#q5d)3>zUv1#)K4q0jiY9t@F*bNdx21@V(JMa8=JT9>?hUQ1o(cOf7{J|JuTsvEm zQx$zOrza+p76BV&65;_psrfC;_f+-V(M^XeJt0BRo*jYqXJTDobpCl87Y$ z+VZo5%O7s5exaHG!d-z04rup_)e4G*Qf+DKD#uyVN4j@Tb04GLqo8=mNE~LJNH$RN zE!(l4xD+uLJ#V}c!N$f`J}2)Oq|~x!>KMR>ka!T8!l=Z?4{>57)&}1O-vVYQ+bRqe{^)5}bgV`|tD_wC?zY zurSrhOsff_iO_^|fLCIsCeTo<&(}(Uh*4PosLrBqt}?l{w#MY{8dV!x7Ylq_7)6>Q zHd-M^kuucWg#l;NrBO#18D&yWxjp;l>r7182bBnnn&N84$T=@B2#;vSH^6`6KlJzR_RWmY2oBFZ&yIo!7c`?J{+sJ(=84Nz+m@F>9AP^kGHPn#-dsb#ApjlC zltNU#h8_ny^**jmq6>QVPi52yP7V&f?`@IouUfiInX7eo3D9P@w(|d@45i$r`7Ywt zWTqy#(K2JRkU%Z_9C5eVz&oj>Zj(Dvshgyyq-SE%+Eh@qG+@wjF9*IRQduy;9IId` zQY^jf!#{~^yiHu{NVY_+0`=o>@|3i+t;qwjo4%{;FHi$0XgT7onM%J9g`s{_$WaSN za-2nyoq2XMG|NK9qK2saJA0``j$(2;(V9=FMnJHi^5Ky1zR23~xpsqf?d*$;b*k1! z;jk&4Uo{?XoW7ksr^mLN`s|#kYQa`d9eiSN-vWEy}6lPJH@SBL2cSTdvoRs%Syun-S){FW5@^=?5 zyl@>}d#N*`-Z1;yY_+xsO&J8oSPUCoO71{h7s(Rqg0|JWq1J%pXgZzL zgXG`lKM!6?lAGWtd!*dI7eL949E-c7_uHw9^XR>q5CmlFs(cSKEff$ewX^z;K(kwM zG2fHSWd8}}UuXm2(RXrEa}NAPcV8`ab>4ntYti_OHY@`f?8u| z_CB=c>dNcn?CceWotGC4Sh)rdBL5^V0?(;EeiBq0DuwCGbA=r8f7%=fI{4NYuYrXvH(caWU8?1 z-Yl~G-HzU(ABZxqBkzJ1NEv#Q!5p`G48aU0H^}|H8cJy&9VPS>GxTjY5`ZNjjX*~6 z@baFl!|wK}%O_%m@v9A5OoOHX<|1gg!cYs_Hjhct1sqnMfwaX3((}+1@J9=1Qa}NP zoE3D8+TLJhcI1g)TB4((goK8Ee`KUbPngKb$(cSt(9?xN?|<=o_XWYDWWSFyvc;N3dEo2Jt{#4buKt?-#h|=b zk`I4(fa#nZ$MlW$iPLp3GBPs1Ih=m&ls>GLh|L?_=l}Dk)lGWtK7nb!XUDF~YGxpf zpFx={WPoq4y{$)z6_0JlF^{NmS@2YW`1d+~{jxC- zwlL$S_~jyugNyqdiNph)+JZOfw?ONnq2u;wPPkyI%5xDKPDm8Gnp=&uqxDd2D{z-% z2XqkI09}We3-H@}Ae$*ah$Q%4_8&3Mb3HxWW`pp`N^VFsnfZrSKpv$+J0z5<%qD_@ zocw+>N5!D|Y?A*_SEfC|-fZnN*)AI^xWkZgI86n=xeG&2gyvZ?0lj#{)J}jLy&t4v zB<}mP8~MO5hd19tLPncShM9PU?bgnrnkC znfCxthMpKl5^{|hW=JeChYqUa%V-7J9tYF^UOKY#Ppu-QFec^rHtdWJwi>NoDKCmU(>*~y8g2|U!|s8#iI`uKR1 zkJx9svsyBY@?o^%R~is*RAQ1#^ZIKE$+eJ>5DN9BmesOJ|8}fG ztlwk!Y{HwieU zxt#DO>Jv03|7t@~_Z}foFDlka*-q~{&dchQ2 z|Ck)Fphzh4Y4dHt6G5-T(XR^&4F-d7@@qyK`b(U0oMJqu!%iL}sdH8=?6u zEEuGSdy6qNLX!E+>Ad18pcrsw1YCZR!>2Dh3w@uO!h*Z;dbUO~ zM{Ri|^NE1l!F@(1rWeB?XrCnxZj7dedu~N!RD*5ofm>HDVL*09@@=|0(na25_w~i43mYs#JY3?bnNy*txZeGOBZ z+XD^v0YIf+GgSZ`@b%o*B4ux;5qeE4o1Q_((s6mVIP55VT)-+{sGF;|V7us#t%b@E z-w;Xm^J2f17B->aqTPtgdY&j#_2cQzRNh+}-4adM-;Vv(O1x=MK?xsz^K2H)&VX2QhJE-lzhkX{vhMyj8{kWMtv*`?QF z0v?m;Jyv=fNI|lp7}=PcGj3`dw0em`!0B$NjKlen86G}R%l8TpC>w1UM-w zn|w!iK-h5mRnen%mgxMDM`!-~_sB;JpsWf$Q-bxk9(wPJ>+T^QN^Mu9SS%@pi}s^hrEkJ}_L= z^t(Bd;{Jl$MF!i{5Lk4vBn?W|+$ejD5!{L2Siz|+Zg|Prd_q{X(fQ#vm3BlV@hiVl z0N)*xt$_5V!{1(wX$WmwBfhP=HU)Y@0q-kM1%GIn)GFZ8b=~ngK$~2B`XI8opukcc zYRrLmpZK3kugUi$I2aIyNKNVy@cd&x)cpy1dJ7ASC8gm}+_b6^eY#7Bh0`^5L#;=% z+W+}h&DfCB(hmP^fWF?zeX6%t4o5rd`$t&&oU7kANe{C*Ybz@+%=?VswopH0Vfo8> z6b^GRZdd1e zCHCh$a5*BxgBv=b@$>YrA}CDB#uW*1^a(cokbFd~olVu{pIKbI10@hN-*G9kzwhBX z=4hrsy0)T}am|#$v#K;sm|22I zvzr=YG&{;cFv1_+e=0n|_2}Z_^*u+iy_r|(81^~Q1#o|5Wo50FbB8xO`=Xf~j@IM> z@iHNz8~YAiK}dW1^sKgn=lm== zFmAVlI=D-%_6tmAWY*OQtp5crF&>bv{v>ZKu^J^*m08-vgib+0p&X`&EO;pacBqA% znIE4IYJ++3Y^p@>jHC1<{|@5T|0>@{)2CSws!m?qKWjji@*M?%*2VW@cJ_rHmHDez znVLn<4Gi$>k!e5fM)YN?J-qq40CiySkEa+^rjv2Ua+uWUz_e71p1eW3TlH#Hs40h! zuN*UR?v2M>-qaML%8U-5kK;@J7%k!r9t@tStE*=e6kt<^V!{hFOsIr6FkbXoi`&7? z3Xr=MeFI6%`^`v^EWA$+`)Q?*&W{zIcOV0#G$y!Z6zczny&i# z0r&Y1u=L4@E8&MfWvI*x1+C}LeS!Q<62l>v;)t)p&s4nyiGW}-&W`;v6VRq}EE0Xb zTUC=`pn|y-qRCIgyU_a;yFM=JxiI)*iAtEh5=)_7#)N0 z#wz_q!<9UE;3fjykAKf<->ndXGH+MN>kP}&M{NV;61H@WDzog+W2}uTHSA|Lgo3mW z(D@ef1FuDB;>Ri$p%r+Mdl%y4nD77FnJbscY6q}9fZ-_mlH42^Jly{E%Tpoi`!}M( z4J-mBRIX2Pb{TIX1BqkEavPIL<$c#2S^!)o`K^;D@SBz_@TZ@neN1ZxHx z4IV6%-V}x-@>yuAy#9`*=MGlGrD%h$<0{FIT_Rr(T|&LiZc*$gcI@Z`iq&R)h6nh= zMlb{g;g`6VaOb!7_GEz3sEiaA7OHA%!)(uIS2OMz5riRR5p!U-t^-+hI*O?5X4J#N z_Mre6us?jLgMPm-Kfk<0zfn_2T->lnW0a0GxZSFH1*RcBf_J*4p{2FTBz}%4#KkdgOf#j5kR7=!;Y6iZtVv536t+@M*}%No5igYqG*1T43#>7ZrXW z9p^}bO{8W~Gq`z-QMBXYyu4pnsqu@>f6?2qqJ(SKO-@d}T6c6u?MtJm(Ehb5mj0|~ zd^GdLIdTbxn*aftj`~^2@7+^=U((!sxeuK=mSg(|(}_~@9Q%~2@38obPTt^@-OLI^ zwR$fHklFX#b8R!OXj?rr75NVy2husQ6f5PJ2-)a#CQVgUF0$~Rf%*o$qi#KgyHPu@ zx5XCa?JP?3^DVG__ym{eGF9cU?rSLKm6sE^G~RSlM%cW=|}s99D22-%nHWF!qv z1(;cUoT&6foiEBi)RH5qQ1Kz6@eqNgpWqjkIn@Y+a&dBMsx@MV{$tk)u|++%IH_ zgde*MPS8i6P#&)=`%SiMd#)MOamvsx^OpQDz@a*{L%r40}j&S8Z~4}S0Z z(F%m6)q^LYELWMH(zSd=wL6#UNl?k#Vd${~)A1AtCGN_hKzDaHm#Apy`MJBwLBje6 zkPnK3U;@qW&&`Q{o}D1B`04xjql*TSZ3 zYG=on)k6#j#?2?Q>(yjH<$OI%MYI|l6I1S0rpnAwpw7aCp%bB2Lr!Lcg-6u__=A8FH$U;4bpMeWbT7PDFBlG(gOK zm7O*K@=f#fHVA3T&|J#iCb{}AUxe#PRCG`axxvn9MyGd23nDR{6KSU_m6erha90=%6jFX>DKiY2xmpwIsYZCBw}ZZWYiJFny9tzW-P+aby> zc+nB5MI9jZ68b924f@#lcq}-#&lx-W&d8V8$?WCrdYu3g!&a=k?HoWY6h#PZ&T>;U z3m7sJV*=c5+`|N6|KrEavSQ-IIPJz2*(H@5KlZ!9F43tn zr*r>2ZuN%tos|2M+W>kR$PY@47j(-HjUbay}42x4+%oz;O@dRJk_m}0IMJ-)#K zH6Eiun{kU62gAB~035Nxb=DH&#E|+U5XP0i$)nykfc(KM@^aa2mHmA~W`2Htussul zRkd!>)XqR3u7{Y+R5nbaJc+@j$WdpHRG#NXmDOg9rOONk42;VfVB3QTbb-<{M%O6K zI!I^ARO^9e^pFE~RhgJkjWV1F6~XcHr`cu3?XDJGZXSY97Wt zq?2y|{q~4|Z5ri1Q1=F0#F2agmwC0W&KcOSRV^)E)Z06BcKIE?Z&3&vVmcC)avx=? z_8w6*HZ}syg+}Tam4o0`_P-r45(cyNW+o?HKoNAW`+ z9c3sn3imh0Hkm2Jfc^;x80@;>zKM>K;ta=iR_mt#ZP$oPruhkF}S0| z)46xtOp%R>9o2)Mj^RUc8bNnKT5Q;QWl}eqs(~so2kj8#VbIehmTybwvi>x!T-%hN z&i?lN`SS{ooHk_@mE3h|7y%zN8g9%fVeFG#*Zge1^l_bhs{>++#oN9RfZN-%wJ-TL zlw*$oF#7Fq^!1Iivl3)84N8dysmg4nGDnV=-rs2%Vx~e^0zke8RiE1=_?JS`;FiI$ zRds`5*7v#y_owpzWEx|>=*f4GcEu{KN9mTBYiJ0*Jz)ltBfv`QpacJyKtYN+i^k)} zkE;nr(eWS9ghUga^V;@&g{W${%5red~cK=V5hrr(0*ARs9GAkrdEOOQr@+k~AMM^vxY z_L9NDAr2~ZszF(qS=y6V_jqPTMpTo8XCVETic<4cVXn{mxd34k?-rlk!rW7H*3M-v zl{AqcASl+TDkYjz(6UgV^d?4Ia*wI8G z2(4T+NA{-szH}v!g(Y5A8O4tN1Ajv-YCQV~#S)Rss@T9gv>)1ydK=#JMe8LhrFq5) zM2=$7xGmkT&=whpsov1o`yD#Z{6?|M7S=WY^Jina<3HQm+f7clPX6|px^Uy|>7%eZ zG96=b1nW`c%=b?eX;r-)1U)~m@P|QgKdhJ?Gb?M6k&lUb7^_KN(fsQYTkZ{Ll>YK& z*|MSlJBZVSVS+}O86Ng&4o?2SZs&TWOM*|4fs1zQ=2BE>f9r3(I>{67N_uQP%6Dv5 zxxHSzdEzAis&ddfVlxu#-(YmYdi7pPAVgz20vE0ej!#dNDL5oM8DIobUh+-)ck zK;^DKK72J;G`Abg{NIFf+0-?y)cjiC^ln>qz8(ahC=-gb%EUet>3uJqO-0_dcdv^L z`SoG4SjXvuQW`u&)5)kJNC3Th>ujXBJ6^yB)1%s6FScCzsTFvJrZ6r=GuH+-z{t+c z{Y(Elo_7^(&#(^o_N?31t5v+LZx2*4UM|RDTEdLZ`;*Yzsb4Av;T^tDmep)?WU?H7 zIyXeGMT3r+_VZ4BIE39MFU?(yh|)?Rh`fb=`Z{QW*Yo&OT>9`+0stW7BM>sye2<$dPF;H`Tj!Bm8~X zfZ7MC7+AA&lq)2VRU>#H1Ugc9lk40M^%S^_3@=MRevXMq7&Q+C8|~$@G?Drj#0&$j zMJg=UHsI2RZ4X;YT@?mN(R!*v(Q~LfA-)CZ^6M}^IzRHYdA++5?AKpm5(M7~@g6E| zF6872t(N;Aw}zr*kiiu*&Y;eAWwAg}B~Lh~ghWJ-^A)@{$?{dEUx{|xEuF42N`Esv z7|?Z{`%8z$gNgVKAdPE(0;xl(Sh#g^$j!kkh;V{dfOR(5DU&Q*t^QVq{YykUC}Y=& zYW2MHZYbV~#k~w!@lq?4r{)V3RK|3a!Tjx778wmsbB9X;opj##aonI@0mj{T9E>q5NWYv>)BZ8 z$n-Q0Dr0(wFRt$sSmjW!T9vpCe7iyIZ`~4I*iYSdGb3Z@b$jO;UG*iq4j`mjt))Br z>(`$V=`r@Zc!o&i7gm>dWMbXQ1d|%QQ89JV6BtxM+6jw_^@@x!xEN?|t1;?j&Rc3Q zWYLZ8+dwv`f)94G4}m4p0_FNznI)za3`}f+=q9P zjK9+BJ5vJu0ets~4K~wN8@GP2GXFf#zDT^T-4f)TB7YXdnLj(4DKQE^PU}LNI@*r8fRu=E+c8x3IE0$JI%(Gt%jOA?rZX z1>&w#K~m>Z8%QpR4Y1d*d(v%3DKl&*@N{m*ggE5s(BwN7k*klIlP;XkAD20;$;0~l zy{ZMa<1%^rB`ubO$8F|Z;_zY#gTEUBDF~yPIIvdxu`OI*!L)PT1>GtYhIJoYr8ePw z_F6fbvvnQ%v}?{4ytHHh)--`<{PWY(9>K5XdW@yEwTx;hv+bu;I8-T`o0>cezy$8P z5)I=zMHuh|iblp+-MfYHgRv9#VgWp=7h-wIsQ6197(RIKE%wBRN37Z6PxfHj!r9%a z;0-D0sIsREl@-OsVXM-dMlAa3QEKhbrK{Ulzr>nAwsFfL7oIFudbRhtM}})wWm1OA1j>?#Kj+ z9wbRnyRR^X7CfzmT;{o!fq?<-=>=5jWFvDI7jn={Z6tiPig#gfyu)9XrQFsvoASK(bFw0EZoa#YFu8NUV!id4$q=J;Hp5=f?qh+ zxA_8;x>vBBo_b^@B- zboG~BR1hSkM!yG$dWi~o zPwb;l;+fC@2llT@%Y1>`BrlYo#{qMD>{ejS8ruv>-NWAmGQz z&;JR|GKh=}3=U?;#OJURe@bs{72Y|%8Ia+QdM72svbX1OiSy&xDW&!iAD)M0t^LLl$ewg zV#m&djdQYruWr_IismP`!U5n6+Ci3Xf8%ZgtMltV)#9lg7pJowAcPURAd!LykwITG zE(#+^Ey)Ljmxdj-o6=dBpvW&(&CPS|BC!_)V;~>zT|=aql+~F5qP=h~fG2P1z)i!4 z6&7lg>=%r@?G@)e_kPn3@Z4`UDEx<-kMJZV+?JV-3i9R8Qnrv~jMfC%(KSm_jvgiJ z?spOz0gMI|9=JO67l?4g;o<~7EZ!>6E47R4HO>ICO8-DFRNxE{oF{T~aZ+-!$+eCzF1{Uqy$?YK=xeDjyoEt^ z>Dkj}BY!*x-TXqFbT$D(PsbwT{-Pq^cw#S{6F%#t9MM(@IgSu@dbp$2FlU=AxEGaE~QBQ1vs!31aYJ57WLWG^Mavbzf|2*cm);KHi|sq}5-6xa>`NCHEVO1)9Y z@+fL+%jm1(!bx5WFKrl>;D!$=%-hrAVaxnEzijMjZ*NTGeatV)!KMxu?0@ycUc0?; z(%i;O%7K>$4h58NU&u?qsMO7L8<1S!JkE9QS9(OcjFPMfZ@BU4^b&DyueT^|x^()< z=;@k|)cMN1%vCEyU$UQEwddoBK9~h!+vxl}LGDlNwBb0ChX0MiKacl=xq}pjaaF`m zbvK`J^Y9P=XK0~Orv%I1q|tkt0G+yH%au02I?^_vecv_ha@))&ywJjj^W`C-&QlQY zYK*6Wef0l9|DWS3IDs7_pw|&X^I=BJ^I%E&M{@%SFM3T98dbJq@*N>i;6Py?H2n!< z)L*LXU@~4lfH`2f$G|8y@$}rHf4Ec@$c3n0fQIznwcXxqz)Zoe;8peC*dnX`DE8ji zGWH0$i(vgvM>JgWXcoRBcYF}&vbC5lj5{??29Rd4LsfGo0?+3xT&G-Ia~cC}GqRsl zHGYvmANovckInaiZ!#R8C$fAs=;E{W3-+zmOS*=>dWki?-uFaIqX`E+3L9F--n+-H z-ot2(b1e{^_}2($Msct45AhE1`TQfRWkz~UJ-WF548=z|rL9+IcGLpMytMgJ2P(6x zjyo;;B$ZP-T^{o-EkZ>_MaF^bEm{$G|vjA1X)YT zH%j>`IcozDYYR~pNRe09gO=3>4pUGcyQHc?GZ0ukK39M|)O}_NT#|~WgkGKpuiqYN zv&HVsy04|QvN1D9-tOzVF#>(q4uB}GQpGcsKDINeI(By6OjdZCV|ELc-=Cd@)@!Mo z)`ov=rbe z_3-bvI0E;4{Sy-t`75U`Bx+~aL0hy@wshrh%cT6OH>4%ZJmU7)7=UWEkl_$E+?jy6 z&I8c0>Hz^ps>5atLAQJDHZjuNuHYBC_O*}NKd(_W{&2%>?~EZ}CklPt>O>T*=WAWRLsgF%rVlC52^=X0O`=+pqD?cpL>E{X#_7X@A28?RWz8@ z47|LWWe1v?RPH?Ak-z485Vv{qO=KBY9K>pnmp#`?8h6jK$1KB^M1#rGE;~_AqZ>MK z?e8C&tVwwqoB&IfC~UvjcFfF#-D;<8)aKIr&&L1g%zZWk0*Uv2c&?0zg&!>&^tzZ? zOzeXZ%hp*S<@il1qmJvGv;WgBsLXI%f;#!N)UI}BbbOrg>W2vFQZ{Mjf+3=yMik=KcyP4P~050W(6 z&6Si!m*FV9vVpt;g|lrn0tsBb=ZQ&45wQNaXx;7TSrcxE2+;bwel33sbb?S97ndtA zMY`PeO?$%7NyPZb47t?sWz&VCfu{G&{OGb(7x* z3VX%B1bbhG@R#U_EC)Ic$nXCUb!bmrLo#S966BArTHQ+<5tFpI0JxF5M&=*%>v{(;xeOmd5T>M{g1lM)O<7-tG5QWZbw6nq<%7r(lQ(?i z=9Diu&YZJEAP2DrC-T+BVtW93eo}7Yhe5+~Ya2Ve>yq}jBa#W%ObV%_a^m30_g4|d zp7d7H)_yL=C$&z5C>-B$^4oF3l*bUbcjJCO@^#is9T>sSi)g_cexDo#`Z3MW`#aid$(#-YAt>Ak1BNrvJW38XSL=W7*`Y#G-5E?XCCu zzixkg1W1C{R&ed``S2wA{fDrzm#3Q;;pJ(pH0f`MndD^>;?8t3mRr~C`RF4$m+WB{ zDs|`@8yiawZP4}Hj*9{bBlezhIN~F?ImdvX;bcccbOO=Gk)L;X@E=$Scjr5L z+H@cJC18jECP|FHk%naeQMxz=gOr|BJGuXpXk&chPClW>lN`AA#3Q}f*>p)>n-wq3 zc#@Q~w4}Rdz~eyk(tyY^Scah2yzCaaiYx7)L^EX#$AoLqcsbMvYa z=39?3tg9+s^P*cP-QbH|QCB(D~ zAErgyx-v&caovZa0nhU&QpKR05`^xgadWQj8tbV;6*IFn!SV^Uinowk7=+;!DxUkM z=4cNoz>stWA$%i4!-viKz{ZMCO&#jFVESj>sg7nHhtJpLUE+J2Ly)^Ih=vn}~P_7<7lqNj-nV^a$=gb*vO&)L9F5;&{05 zLt8{h<@!GCBT`()+wL(cA9?aT{Y16sOPGn!^Y2gIl4?g+bMkLDS=$etn4 zKVFj?9hNbZ;=o)lBzPn8x>lC)6&pE;NNo#1F8uwKVx-4%0l#Adagb85US`j`-ovD@ z5iNkFZC6-SG#iI&-gY`P1;NsCP?WBI>1B!4=JDzo=bTgzG<1*7Ga`0)dEDf;$gf0Qz+Lc`cvuQQ>?bt z51?bOaQl<}Zc%iJmW8H{d@vNX7*VY_tl@g#Pl87JB~XB^EngIt*TW^!^2eBpiWu~Q*VlW z1lTIXfB{g}zj_8Ruhh0cH4%yJC@ImS1E;+ehmWB``!nw*R`rp^Lu&*Y?r z_-YNR2=0Y*dOCqfh7a2My6i4x>fqcSBTu0VR0 ze9!}sD^%ZVxBeH}pnV{}jojHj0A61cngKPf4<06<&pd!-mYbCY2=JnT&xKJo7*+64 zVe{g>Q5sksel7t^&j&h;6FnSGk5=;A`0hZQjw<)O5f?^J6ZZc+SJk{r4&26wFR`sfmdV zkr#7dF+_tOh0;3=OmtLWMx;@--Km+eJ?Di5fkgyV$wsgjO)9CV7{jo3R$L`?KC@n@ zq$s^!hr={LpIH^GGtq$3h;X2)mCU2So}n=EX|gbw`O@}DM1mzHbcsJxZlGd*e!kTx zA--w)UYPi3tXsW5hzJI?GnHNYj+)>fvx*Qr{KyHR70LXW%}thhk1ckTMF0WO`w@WM zf*uO9JE@rG;Gw&K+adta?j$QYlOSFx?X?qWfRM% zsMim9#fSdZtJ1F~-!C&A-9#@c=^cf@4L5K%{ZltHGi&WoejX@#qjD+6+41Q7bW1*YpY1Iw=NJuV+ppm z>g95c{xjIN5#7k9|W0# z^YilvsCXXZR-pU<98yN~yIJ`PF(WkhU?ckg|#|R}V3D2)DMJ;dZ9pDZgf81fPz7A1pB>4; z3>`oCyU6!PAL{KZ*APq7mYx%&Ce-sh_>N@xg~}&~2CpO}+(XScP9Uuhed2x(T|`z) zWVYiUtMY-@@L*JX-6|dapQrfs@*xbf;L0O5C=nxFJqeOWUFXut=O_1jEAqIF8EJlw zO(#*;u%gxBZ*Q5Cy!qTrAhLCQE(h1$1@f7hRUGP8A69mDR9XV>YjnsmU%V`%Jyj6A zPjLNKsfFYUigUpC?jIWf1Cm@09te5f1z%ae+q~FAKj5TXED`#^d5+QT0Pf5u@LoLy ztrPssx4f?*1<>J#j`aiQ_tEL%@p*Yw6@5L^vbrF3Q1^1AN~MknI0PJ`dw*5hEwtp` zs*J?4i9rkdz%CUZSx5Z#)#k(IhvV=*Ri^rfwSk z+9c{T0_OfLLn163Kw;+^?T%@~SfEdk33|&Oo}9Q1?SoJdikn#U_V)z1H9~;t2fo!3 zbht%Kg7IWB5I03W2(H|J{{72g!exk-vR-Jp52}*CM`doBHw8r)WA4*oE@Qn?-mL`_ zbbJKf&CYXhF(|0vF0dxvfuMwBo8hjhHp(<%q3NvfaGTzf5zf1g4Mq z3X^{Qj-@gHF{wq{oSs12eG_2W@H)J2`b}!->IOnl{qu(lBqO-^=;NpS^%3y+lh?*D z|LfO#xUrG|(Og4M4=VAgUwQ4khm|<1t0RQRA-myuanSuyJX4Y}8c;vrNc;}w>4VL} zQk(&i1ld?;1qz(NvbwsDy}eSXLZDZw?)YF0gk#7EM0C?x2E#aPB9>j-yBv&QYXG-E zdml4JkMxwcR3kZ5=DDmlB@1tfzHQl!WoLotHCT7JwKn$ph8U1O^rqlv>iwCk-~)-E z0~#xzZq`)ju^b#6#*0X=j{T09jhi+!fZrL(&dI~`!JLcbWd6P@km9bq-2|y&+vK$B zzj;Gizi+big({kX%=*NPNu4nI0A8gtfGiYXoyIAo z33*K}AI$bh)5y~kgP9ZlQG7)D2=*t$X-e`Ho6ycYg#bd>J`9+73`WXUEaDKd!)n8o zhep;}|Ltq?&?iHWShw4-#e<;I{$(*yjHUcoEzC(s0ZwDewjZ2N_JorFV zZ>(+gtzk(BLgH2Ke~;0^m({>1k%qQEixL0;QgZpQzrI6%jasV^O()lv zl}M@SZnn$qWWsa_<^6nc-YWp4v1RZ0Tt*1;4Z6w&-RSK}`b{%>|Dzp)1<52-my;q4 z4w$dk6SK3)ZGy(tR5Z;o%7vMk=yolqIU<~dxQSx;r~}u#IbT(1ZT*+r zW%UNrbz|C|%E~?&koC}K%4}OMyghDD`|kCpBeLP8FPnd1fT1doGE@StQp)Vb%p1n5 zL_EB_GFJ+qvx9w!MCqgp6L~s-OPPXiqLHeb zJ*4e*V(5Va`t;bX8CC4$Z}Z{m&(Exgo7^^Ed|L7x@1uC{_2LJ!v05Rwd9@Z3qN%O3 zwxZ$(P0aU)hT$3TNfxKi!k3|h!#Bb#{sWiejGr4YfJ!;w%_0e`K89o(kg}s*ZCE&! zL%JPI(1$PqoNgyIeu~3F@#Ja5GB@tDDxnU6DdVMN*Y?W$=}~7lM~V(oPx-4YDBZ;{ zl#qk)eu%~ot_?SL(4-wY@p+9gCQhYZA~-o!G*bJai0q426rjPx5#!*6N3U@OSox9w z%j>;hSxo?+YT|(N>s(i(W9Pc1-bD?6gY>r?kJmra%^G(*U$*gWeY59 zUFIiGpGMC2L!p0p7TdWbDNMMa5i`8W6sJ|3+PsmI#73{WCP2qH!NN z#Hls-9`Js1bW^>@_w$Evj?e4@F58p00pt~}oLt2;3hfi%uVEsTnm)b_d?GMT!^9#G zK1Ty^379zy3!h*XOM2jzJf*107+q`&gK{R2(dhWn1e|2&1PXQk!oMq%rkA$Wk;xHU>GF1hM zL7=>6A3O86iH9#U^c!Yv4j+DA^_$_^A7#u_!P=i zauJJ$-0EZj#n4#$015cY5{Jvtp4LV>%G8*;LeM^mLg;C=k(`HIQY_++;m83kdIe6q zIhX%(UFiIU=G*0CYh(=f97}RSaas@?;cSetOVkVQ4evyOz9wT)bxlp?DN`>8hscD& z1pnfo$x5l68P4gURoZ@Z*X$dTNpXg+nB0$kcwU?wJSZ+MHmO3I4sv0AGt+uAE4L^Pc{F^Qja`)X>A;&RDG7!~l6VzM+8Jo9U2+>w1O<;)#A(FkfmxBa-fkq_aRTGY^eyRU!d*Grh4IM_c(V-*I4A{>S|Nk--Inwg_~ vP(k#eqxeb0^UAOqkBKDms7&|6-|NyR7lz#Eaxw5}RtTxr@?s^TZ~gxdcz@0U literal 0 HcmV?d00001 diff --git a/public/images/achievements/border_gold_locked.png b/public/images/achievements/border_gold_locked.png new file mode 100644 index 0000000000000000000000000000000000000000..f1ee95e3d1ee5e5765562edccaa2fd16fb1ac0f9 GIT binary patch literal 12558 zcmX9_1zZ#C*Bv-wbTdM7!~g+l6{KTG2}mj3rSM93cY~xfBHakm-H4P(2{@IOmWJ=) z{~;6I&uvfKd+t5w9wRl>6bbQY@gNWgAxcS36a4H3k4{`{@Vla2^8@e$+w7U59OU-y zS6=(aWbh2kNlE`L1cFa~_rQQ;zK4S+aa>TU@;EkmemZt@T5=X!N~uJrd)LCy@ID(ITXzbH31xtK@$l%pF8Gq~d^Z`G z&)B^|IU(frVCBJs2jhjtFt+Y-B4tdjaCScIvmWY>ni2GYxQKv25^tM-Zy4dw@NoCl z`H^X@o`$BT0Iz@3hYwi2y}h-$#@@TaLPA0j3|cxm-?B3@Je?;C$<_kY5sfr*0gCcGT!;~D>yy9oV`7_!ONFx|9v()&6e~x zn#Q*=ov*XZw;W9suGFvp!=@aOl@5JgdgLM_)6?tMDfw(~@Avw8VO}1S+hVPG_>j`O zHVhb1_&xCby6K7470)QwQ$MiBKUm;8!Ff7uPO)8``rnH38nz&!7#o~Um)&vFc=QkT zU%Z%|AjIxPDFted>vbCpkJ)78dUPD6no!LB5Y!%sJxwa>9r^k zgLs65_dk^gQGVK3@Ga)LMG@i%f4lyrUzS(`8s1A9&6i?EP?{#@bzH(LBSueDVcgkq zGpWdUJ?xOGT%QE>id|WAGC>c-a`N%bIi6KkR@&IxM`KmAg-QBe2q`Ei49wEO39!@0 zts0t|&^@8}nTUFW%AwKGXi`=_b&1a}jE&=272{SnCJQqyN-8Sanr)|x{nKMD(qACH z+F$+t{adfqt&$fB?)vMuZ(+s7>=&cvVIUcJ3KsrJ85U2561SnF|MMnO%@ zO;*s+k)xNGYBj{*`74olu0sE4-vKwzS{w#Ypd7M;nNZx|RcY8t_3qufvLx$pl*`{ny2v^oFU{KYD{o~fhylMd}AqR=Yw=f(Hs)@WmoElsb5*& z{q4B?yJ{XU@o%x9px`4CRy=T){T$t?Yx{ep&Jthb^-{?hy{vGJnL`185kPE1@9<7;n=j8 zn4PWa<0B)3IcD{C$GC73etKDFQ0eqITW&9pE&1^1s1`)P>FDTar|2|dDuzYcoUQ0{sl|KO2n3$NZz0=;kQ+w=Y-D)FB&wKUrF|*~my%qwS zH(B6fH#RmRt{sGE6LWK`7n?n05C}w1+B))sCpv^e7#jS>*F!wv5>(Wl}WEog0h zIjsa1M7g)Oy`5Y+C6Yy`Q%*_H%k=1x{;9VqoLwQTsHmv<>1kV=_>%WtyMVyXd_4{h z5j~Sm;nC62K`-OYJ~04-Q`QtH+Wg8BVy9HznfjM8_TbXzsbSazo%pRss-^strGQDJ zfq7F=5yFuBX)k~6;NbLRW?EW!wg2^YL4~HSF16dv_k7O8j??x%SDhCxB*c~%n?DNQ z1>~IKZ*^MRla&tNCl8m0?fn{sa%b|w!^78(&PgKWC>YmIyucq<4?GkopQ&Iwh|nb^ zC%Y#0*a-Dl)iWcJFzjIICY6@cAKvm0dlOj|YrIci7w9%${%s9b7eDBm{`~oK=LbYY zE~FJAPC2o)cY5jkXN=~(cqt!h?euc(#OsBwFQ^Tzn-APnsX@?vcM$o$>!vD$U}Iwz z7Ji{SX4+^_Yu1l%LF_~kzvgG8px>r#va?)EnT z+B`tuMPeIjX%%v#3$3o!oZ#2j*G{d=EsYXye)TL`1ewC&u>;MKU=N9jiTMjo)WkHP z7BG0M2I|F3PE7@^=Xl4ZOP7_DY+MieUkwURBh}JH5D{MvN)xwkK@o6R{6xsDTLtze zEG(RPAx=+Z?&Orf=^fO~)1q*D z>a1P=#mRQ9&16A(jL$(ob)@{KYTsS^z()JIADuquhX%?)x6D$M0duHB>&t(;bu%A- z%;uuS2#ATzZEPYqJ;8^p9-X_;JoV2)tuEG_C})k0sT63N!LdtAOIxPuxOFPJ%3hS4 z8JSMxNYshEh3GXqnV#LHcJef{NzQ*{oTOcwE@tarvg>-%Mt}D_*=sBO{9io}e$(Ou z!B?r40KA1_{flWk$W++1#-7n|f4wrl(uh{F&eeA(F0V6y$KQjSGiN9U>W^XCUWs)|8V zQ&a6s$JdhQzmn^YKd$Tzg5VN5x&fF9uDJm6j@aY!@{&^5H4aTefNecq#;87^RQayg zL4;LMFc~hjeS)X0>{Yf~Sd31~Vlx44c(@UtN_Sh_wrF4nm18cYf| zEylY4@KmfHee2|;@oB`P-N?nwjmygWwGKKyjR1v0HJ^NY8HxP{+^RViIF+(8t~~!BT=u$S`Q>=$v=HKYW3i@GBnRwxVKq zL14H{K5FkE1GRc8%-v4_knCuEBFz)4aI!={0u=A>4YuTmhldoA{FL2?MTtyCm@_jo zB~NoF){|Au-hO_yvA3st4DU7}arv2Z16gnoA+JX%6q16HTBjFi3NQNj(f*SS=pz{V z`}?Eh?n21}?9Mp5;&D$=0+Ru(ijfaf>BOHO-Unbj(kNItH!@jCd2{A2R#F`wu<*S6 z#B;p3a^rSxp`FU{9{L>feMk|R3QIr^#YR_G0Ow#eyMNCbKV~4L@fYkEPx+7&iA24H z2T+mfR2t0QjvC+mw4Y=>Pfnw zCrTH6lf(0jf{zp+1kdHu^0G2s|J%!ziCm2&_5F4BMFK4&BVJZ1bMrzj46(T0lkGWq zD7Xs*853D#fTg~201VPuo%zdoabdwCR7rD*T`(b&R!Bqy0IMm;7$p>``q(_cpdQc$ zwTUL~jp`X1N<23*p$<^(u?LaG*M6+I^iay!*jVKo=yG1Qy2XAcmis-?7mOAQkzoda zps$>wj>4C~WQ=EHXXkoqt))diH9cKxF-*4PI%g>6vXQWHb8&IO_UO?gLr+gn|ICLu zOf9TCNZT!YumERr6B84rPZ#^#M95o4Tqq|7JE`(L9DoU5=jR`B#XBARl&7GeFj@TM zSa2B@9)7T3Uwxg5Zf>KI4%*0BZSS#1B9Y%ad|OLszWn}glLf*T=z*CaAmzm_1t?Mq z6+_dagdI!sE8Ysw3xR&F;F8z4!@+0sL)w>d3Uv+H5h!di|6I9-R-7T89h_kB%3kjA zI1j}FnRXs2QkQ0B*r&JUKk-KiJ1%^l=)!xxQS<`G$yylPAUMp%Z;g;TUmn zd40E8JGSrZNBu&bWEf|VbTSh!l0XXn%b_f%VQ&cNhnU+A{Lh!{ zXEDS-ytXUN=?CXO-iIkU2MCdT%f#q&rUwUYR_<+W&B4~i!yHtT%|;dp&R422^h16= z254ORN8WK;B2VhAC)lrVPCIYYY~?YN2o~xrqiwkQ`lG3Z74wfzPWmxlX;O2tb&atF zjs7N^B@OD%v&!=UiQ4e#liAUq@w(@SXk>J{Gm}(~Huj@zHB#?$iEg`nul8Ev_}mLA zydb0%FKJPZuZ0EFs9N{rGtvFW(CB`aMO!g(@fhFBfA`<>=18#z7xA6#Hm&FohJj{u zY@^+X@1a$ma6pD4bjtnPw{Psq*2IK_r2{yxo9IAc0%?>uTmbg>(htOcrx2+3RQ-Qn zBuj%`&A!JPY3(=R=gjQmIYtQK3klXMXx>{-zCU#)MF?6`8wUr(NP+(?;8gQ^!4_5o z&>HjrGfja*LPF*1rk8?5`yS%8#9Ldl<#h$ROeA^G^lkv=QRI>#%2Kw}dHhd7uT|$@ z?(QynRBn8|wTooN2>8gHL2){xr9OQ8K9%6-Hb&9(lJ)dZau!WdXhmnIvBjCJtZdne z@Ms+f3XL5TSLUYx;j8_RClyE8-$AdWJ$vl$!f~4EWQeJQVfDnz@<~sM5L^Ek8zxeN zinb$_$Ij8QA6!YK^dTUwr|O?LO2YDlgK{Yz%6n#uyyTV4m#KE3DFaw z!3J1bCuvLdwX}j`VhHpTe9rdQe(D#6H z@gr>L`CFxF-M%g_-@p6EAJBAxqLLwcwo^Yr6*oZFCm&QoeO5JtkNdJn^7>dy zwFv|OeHLNSZvmv0zYhsfxV&{6Au+K%S0J(E6$zj!=%0i?PoOXCjPh!0p9{hn7#I`~ zWYYfrojlln-Q5ppa7`oG*-&1ca{2pRDU6fNTPbDu z?_q(U<)M?aBzY%~{!O^BxOl3X!cmfB6;fm*#a*&SJg~jFnMfmkf+@;DS|rW)M%Jq> z5EQm|+`3AJ#g^pqn2FS!pidaZ-`}D72Rld~FZ_y3>CU}im3#B%&CadCxNF-os>$ra z=jor^o6TIecy3sMA~Mn?wT3ru4j(`xfJ#;N#1iVfA5w6k)8pelr2^OVsKwoZuU`{l za6weY$2M`0cw&50xJe^`3ux z8b=A)&8m}_9Nl`O0>_~Pdq3t3z6R%mu%7Qy+){toKF`nz2@Ahd!-a)F0m@~%V&p*E zz7!e-$h>jr;>#>5bpK!jy0q+g_L?VXb^nWL#`s9+BwnpUDrY=Eku9&cSIi6G9sAIP zAzF&Z-$y?@OS8Z@+~41KAKZd{!}lMWn4l({oZ2G&+1|Cfnzuu!$tT4e+7PvK;$4OoD-a|o<~2`Dz??F(3%X)4m5hyZx3WIuL0(%#DP_GmR%QWR zOhpCHbFBIGBbv_J>%ChOZ3GjFnR&E9EqPtnyF*F=CH0W@4ImRN2TBEivS)~{iv9I2 zJb{*nNQ%+N={bVBXoxV;Pi;1j(FsS+P^k@15HPB5ZSu1xcK!bE!Tkp?xq|?HZWmB` z4BtdhkZAEqk@AsZ3n9B_Qf&F|Z(-r%Qk1zr`pd6H+&s;zC z)tIcxxRZA=##m49Oz9OOqE11`D0@Y&S{E;Iz(xa8BPI+w2@x&kJyv4L|1WX@eeb{v z1RF`6vX~f(yDJ}&2j^n}%`wjLh;6E6;=6ZjE#GEm8*Nk&&DZ}H(Wx@Q!NGkwBzRHJ z2e86C%#;B&E7eT399MP@o4^%BWW&@F3*q=Mmc)n*OMNINMt^UAsEwoJ zJK>pSP&(*b5}6DbnFfFY^|IcaqS?8svcWthDS*XD$uZ1wUWthW(#zi$+#l-n`IX)$ z74eifbn}xFu+UMGI+VJ&i4bjM7IO(j0h}8KswPZ}@|-9G%8*AZ zx`2=)_X};8@hVD~v_br3F3t%s6G~Fz^aU>&_jzSft7jsRpB zvD|w}Ws~~_!M{|Br1T=5M{`_I#;80Y+&Vfu0zJ(B^Kq=6wn5mY&JfrN1 z4t#(gnV3L8xPE)@^Z@tX`#wtvKEC*pTUnrb7qCVW>!|D z^-J7Xf3WJIFJA@#-2zyEjl^5|fizY4>-W#I&icUy$(_G$>Kzg~;TmcNjF-kOIbF zsh}?XaGpPF9LOSFcQjBhRViJ-u+5`(YcH!Hv<_Yr7|gHLH$f;&8>^(E(icU^{dI0G zJT;X{UO+=#>S5wQE>JDN*#HrT?c6)qc-Nx*m|nv|FVRoF}&-3oun_QOYYS=Y(+=Ag0Z(5JE9u8zM9obyc+TD5f7-CH$+4k!kRDkjKGJWnKCNtKL-0NeFZ(Et+?743JY_gy)1B@^sE2|tC zS#;ULEfhrAyCW` zvax`(JOmQ=oznUNz|4pPpq3YuqcA^!0uuFc zc<+=INGzZ;1+6-qmNsY*&AgC$yd)0E#Wx9YKx_w`gkOabAT%pM9($IbeR9mQEp^BhbK-DhZsux zAzrmK1*dnr5eR{gO(_v!NwF zFWfOL!AcurEzpKQ{$wzfQL1=JzD>(3LVj-N659+N^g*fB@z1osCnqzgY$`A6sB`0{lSr#ne@AKrms;Vk!u1K}_Vu6dVTUAf#?7{nwTgmF{ z)39NLvJhYgt1vr!uj$5Hf|r#yJ(Y^Uu@jO=h+C)h3~u`2g%3)!lB(*gDkkJRV9tNC zit}nA@jxKDU1+oiTfVqDnn2x$q17=#uX=IewXDyQYU#YzgEa=|ek!s6Lm+f2=ud@G<1?DvE@QS3BSj&a=r(O$!kiADFBQRsO9D5 zTiOB_cjHLchBYjOI=z%M`@@A#Oa9Un-%#S7|A%Vo_PlmObz#UT8oAOfN z<+rzI|H5+XR~>oYh6CuWOmOG|1VR#j=LJAB-bO6G=`@`4Nd5QkpPmC4hRZlq71r6f zkxOMlJwaDD-ZL516RHUVdTyN_r0e6PPHrR{#xUK^N+)BvZuJ0A4YvriG`s3OkE{xK z3g!#uai%bvfN-AYgq{VLCDa};u`UaS&cpcHvC28Ihu0Bp(m8B0HScv?f~M*tGFmUS{{TH`F8OA@PX5!q*a4h+4x$9Aei4W7U9fs-z*Qc)=CKC@ z-9}07e0|ajEP}}(hD81+n3jc3+Q@l%wfuSS0XaL$z2%wm=j*%@sEttgI5+M<%w#x3 z#b-o+HmvT)f_D+eC|PB2ALtYto14x3>!vIMJV4 zh|jJ$b6_A@h_Qn`(6OScE|1sTT7|tOmtHqq@*OElvL6%zJKDb zhKT-FWyoz`r-fI=X{Er93-S9`pR502TKO)RXG5pfk4&AMNC5CBWA)MxN<8MRc7Ok) ziAU+j=eHc+D)fVu5J7+cz8+_9JzeoP7Gg62Gl{+oc^C{fGgABrLuqBQe&Q(|smS0# zbZ#aA9u)KL%+%G1ZF`Q>gTN-GwUb|Mo9V^Nko% zww^K8t}sHn=FZ#8PU?9BfIgg0IOP#!ku;Lh*w>O%kM3#h!Zd(&g!P>~09W8L0P_RD zb}|G1^Y(77&^tUF<>Mv`9KfCC#%3a21+tl)UMB1iXrvzdd0R&ShD|LjCgoDt{yM>#?nL=c8mXltaC+*&+1AmB+8&60<D58Je;?pd z=5iW~{#|uEH~Zm|8|*>-X8FBs#SrD(Rx%8r;NpBhr$YL^+&}Vauk*#k4$hB8H!HIL z^o5JXWhD@ofT=Zh*WHE&5d??*9EdkoBemNmrw+oQoGMNXwy8anoc_Srq(f-a6Me zl@1>Rj42*Jd4hs|7k>^-*H9-p!B$&JC(LDU%gN5ZpXDGy74P(Gh_vnOXLR2R42pT2 z&;|Q?i#Rv0)eE}4sJ^vhkKK(Ahk#z7sAs;TrKP0|cH%&e3gRTy!8ns^$pkETfCz&g zpA-!K_xs4zyS7g>J%yw}s94o!Y?#*9*U!%xd5?2Bghr%6AfWL@j>2gO!dj+3s;>yH z?Y$|um1?LotDg}Ityuq%XmFv8qNSvuSZzQ35j?$#X!+aMCzmNfi{7e6Jmlu$k^uv4 zI3<$pog-5vFT1aRH;}ipgAv84f z=Kd2m#D2K7lJnJ>bN~25A4ZyL2n8;GkMq(pISsBl684i>=D+_0UVr{gHLId7FOL-! z6=g9e!9Z-}f9(n;#!?6F!QFXP`4FM~>)f3QV!!?1kT<^DFZ*V5-$d%zMWD0R&ftzM zBqqvAB*dU|$FHla)5bRi_NiWgR7!2t9UTmvc{0n^sMcxvL!cIsB5RzO-SDQ^klpLC>6Uc2Dt00-qqD5HMV;G z7?OYAYy_A81DI0E2<@^zVb)TAukrkOzDHlhAi5ivA|l^e+5X{z6MR{KWHJVpSY1&? zvuWt-+j?Cc3{p0htn4W|I##~EuGYp$x5HF<1e=KfS0PP5@-8Ez)Ij}CW002*{h1RC zGU95n^P27yc!x^WPNU>l0BNX~;R4~7zE(~sESRCP1A5ExmGdv|F+~mQHou8S>zYAR zHa4w>gQPSCb~e!EUFh)`f5ZC-v&KtvQJ&R3j^z-eEjEpi2LsL`czn!;!{d$2My*U!z`=Fx=(NCSND3kx6;|TbUmOmb= z6wj>18mZrED=SC2gQ;(1{L>3g;1lSIB~-sdxg{I`T!8h-(TMulBi39+6j>5m2OhJT zeM@9g4moLdhAU+XnS2=;N$Q6I&xo)v<>uyQnUU%F;cvYTpU+#LRN@DN7ZwcK4hHD* z0W)3ponLbjqta7nM-pJmj(tC&m547WV0mL{X_=&1`|NIRMK9_1>fs4Zq}hzwF zI3?M}D~h>uEciPq#xwj*5l}Yx5ySz#J^zoM+P!9+PGry-U0GK4)3tZl7=v zF~7MwmkU-`Qv;T~?Sx{m|2I{IEFj`># zn57gn64++|4lV{juAbMFmf9|m_L$W|sU8?m0LPr_)Lr)-oI@$efF=gj8@PIwC+c_r zR!4SP3(@k0_pXH(%Rf(9Ff0SeHY)ji9zb&oK0ZDS;C*I71Bhd)JO382 zICAQ_h|$-zgwwv_O|!{$)YK%?eg2#j$;~bJn4YMwek57v8puBLQWjGql0rgqkC6;U z)oEgcGEDv*l=piDcTLk#?|^k>>Z7wckuPocE(-{Y#xjTc`Gm~OGS9z(%dZusr7^(j zBeKg3n07t9fd!G7UcPE@)M2^hg$uwi#{@O!=(N1?@$t;{dq;)DsCZx^YIfVr{Mknm zX8J{eB{7_%obaNURXPRN^Le!s-=?2%WM8jcBZ#EZzb_NW3V;L!M%J0}ryk;WvoA*y z;F0wB{Z99%Uo|itL1}4e<$nA+nKKU8Q8RA-+0@juEpP>j$l97INO7`Od!QkLw4>mF zDd^s=vzsCC*a8J;B80I5eWa|cin(ZjPb5)#qErl&4e&X&SwCVWKvVs<2?hA}mL?Ad^9J{RH*gz%(>IV3ZDuUC{bOQsX+#G}V&O?W#W>T;Pp!U5G@*xA> zi_oPdI{72f6(6l%gkx5MI@Ly%TeX0_0Qp-lbyl1+bmAYlxU!C?!QHFYa4TIgdOzD@ zbz^#FPyY_jACLfq#fJUy^ro@LXgu=%=bl)48 zfl4YXDX}ViYQBup-TXPdbfuBt^8^#5Nx+#t=W}Krt%xMoJrxy(A-a#I?Q#N*^&Lg% zz)9NOCT4~J1)bFJWwTShQr&+0Roj~VeH~s$RP%qo@j%Vy@#35ZhI6tqp-GSn+okm6%;H|S8BO7@Ulud~Ds{wvcnCE!D1}=x%dy>slARq!r1hH5>M>mSODep4t&)Txp^dshz zN%K5MoxDBai^-P|Rh&N`O%r$4c3sn63NQ$cGmP zUbsorCi*%eyK%YBh*;r}xBe-AZpuCHW$RbwfD^)j2n)71%cJD{#^5|9v2rYc2-Ov&PTDwt^isqSg4m>g$J;tk1h!p0cjSbTUr{Vq(i#9`7ggd zK)`)_-`u%p&YUyhYAUjia4B&i5Xd8WIhY3cS`R*Iu`$7Ct*)~x@CDOMQ5FWdfB2i- zlpha%g5xBo?*f6~5kGvOK+?WafFEMH$}3A_&10iKA|$ktN}qy2orcQbsP zG}lw_TjJhNSNy}nd}b$QA8bxs$tqC0L}aiN$D~h$|Gp;P*?MIsNz!Hd`%U)TCO1y9 zeRX>IyLi`QcAQOoS4p@7wW&O1N_iNLeSZ`ZAH_WA(XVO?{R+FeFAfv2{sGx8-d_f# zWxFtE8W)~g9w1#gHhl+k_KzF)uNw>XI4}{hy2{D}!jQ{<|NdQt;*sk>0{i}rW(j*8 zJze9WjP9y2>x{Tx6XoXRt-P#T&lFM?ZHrBe2o&WAJ4WeG7xr>?Q&%6k{iX)L8y;5s zGPKT4MMc$5S8Wy^U)|p4%-1x$)45oJ7&-9>_(ojm}=`G=dRy1-O zTibJPgP$97LXWP5a8J_G(wrehKMi)de=Rv7N7qTo$<6O>E`JmjN@{D9+VMSe-<$27 z!noP75^FEkuY7`!x#T2Z3Z*=}Ubv?rk(MZ%TxktLW8vab!M&O&G5oOghZWMwJ0u+P zuP<4uX#Cgu@zU0K(Zx>X&_O4;2?TO?NPAD{<>mD3g z#(5Rvv)^>ig5tLfX~q?&4@@iwG%_)X`TN&kb#?UsvG+TXed+j8&CoE?D$9@TeAeah z^|c&BFbEx@sfpUL@eq?P7MsTJMBQ$tTAA!5M>eK{f2iedyJfizix%eKz;!LqF!!Y| zncMvS?pDBK@9QFFg+a-~Qrb+_@S8zUmpdcMTEk~3$=$gUCRwhhPO|{V05*?6o{LfTgq!&vRMaya2*{Tnbyp_L+bsP znfawNl6>V?DBi)P?@J1Ny-LeRknHc@(H}i}1c$>-o9<%iWu4A;^xro7>Yp@S_=-}A z`M(Xgxj5vc4Y)Z>y54WO=YGa#rzb|Jn4E@#dvOes_#T#{E}}eE(d@SSx9Ue(*}Am- z8=eWXtCLMg;Fm81;I|v6-VRGm_Yz}f{pHUx}}YdilUSyNV*$&!g0_ys+5Zas8si~=2{alTw*c;|lP6ZYvwY3!B8TF86p?0fl z281!)XyA%Km?HHckj>4_oqOW(v9S=vWUd$N?06uNoEqlJ^*9jO%r>T`5Q7pOCto-m zJDJNUxYMj{c=!pVb^m;&EZ~;CqOvkJIl23;DJlwo-bGPCq5Q*&jNNQ4AyYgK76DaY zo9UA({-&lT&*K4pThXhHoOs)bVxAI{7XL9A#F$qdqFbPqZjRLZLb3?<46F}hYkNCB zGakVrKt)hH=b%Qyctw#v7S66)Y6|dY7=A&!GHDVJ{qraEYz-0^9E>WxzYfAVY*Q87 zeSUWK%M2-=Gt&Cc8ONCW-0juk`q~=qpeBe~hLFdv?bGGe-gwXZ+jHp>Wk*MD_x*Vz zw}ZvD_(SX#Z(&2q0fEnr0HvA;BRidQkKeDKrx>=h`RurM*F0%GN0M;}N@NjEk& z#9vatWM#1g1qD4{@-WJ#3{CLz z@-nz(WMN?_v!%TJ$@KKJ-O=xH05zYtwrrsM{41$enG#USP=gW~b#=m5yu4m9r=G$T z6riXkBqlDci``$Ai}m&QE0K_qMa*mU22O6D>Q&ijFRTru(>~=M!|!^u0YVV{q38IM zxw_=HC-n^tni2D}>t`n?txHbX4GqtXjEpw6x1|KAEa~4{S~5W?9|4HC@fW+Eg=KMI zT*=bB zpJr!g=X&4Qa?@9ce4FufEW2A4dLAx)xV&s;H(eoJ5wU#eIXpM_sAeXEXx84|9we-s zU`%K_#g(rp{-Z}o;?hsNwAjAq=1P?4Nac)xI{FCg9!S}^|CrgBnXy2g|0pe0 z!MOq{)&KR4Cgf(uu_YP}iy$nCk)56Wa4hTYWcPhvl098^%zxx207v=k|1xZ~vql6@ z+v(tYvXDp`8b0GPZtP+5(!fPW#YRUh7OB%u`E0}%X~^jx0{nMkzQB@kNDQF_1MNV0I(z)Pt3Ayix+uN6X$66me zQY34NmZAUq_pb##pR_u72Ug{@dTKK}4R`nI?9!|sAP2Q{bt`ik6aveB{8*fO4gjeY zJO<}g9fy8}#l|$KbrlsAM-UW|&X1a05gr~MIW;x9>Tw_v;Ymqkz3R$&mb=H7Qx-0b z@wxakuo1* zy4wln88ks0nyLuBe^eB{Q1kZZ#0iC+ytp{ad8>qr@#UFGd|8(@`pjjqiiw~+$Pc>wdMZ0<#Ef+ zQ6HYeBxKAUO(fHm230NT!mZ|gADO%HYhBCAH`XMNLnYxhHo+rslAFQp(_fpeY&0(^ zx1AO^PAH=>`9v{77h-Q=mUci zG7u1_(T}frc^|6lj@D;-*)OKoXS>1O+FDw}+)XE=g@#QZ_me`nFuVY-J=>dO592~Z zLj$MkzfdR%?$X8K%ErzP3Dy4ozOIEj6Xjw%igEtfJCNK6LSMjOJvY96j*~e*I&yYT z_-pa%9mwloBCz9(#$Nvt^ZWYx?iW6y8{s+^e6Iv}Ha9mXA3p%ra8SqyRc;62Pu&`G z^8dk|nPmuDKVvl-lym{C(`S#{m;+Uu?fT~7T&MD4ffyT?kpoKegyY~xadAh>?MzEa z3n`jn4f=`Jix$sAL@_PcApz%A>@oxZG%5nwQNn&9>I1DHfYlE%gq^c$=4F5TCgCpH zER)mr^^MDa85x{7J|Kac06<)gsHn+OpJ+YLXTc@sHcmKQ0jWgmcS1N+$P&Jcez1hb zO*&U)z)49-X|cTrmQ!gpB;(+)XCLaPB@r{(YZ$90NyW7aU<~jzj#Y(50TkcWz@ed` z?gUm%^ln=3jc?y`3ISZPu(8E`^n0jZj?I^r|KQ4)&t4DbVumIDP-_7FHzKIeP?L&lhK4Bv*ez8!^q?$vlk)uDm?`Tqi7#!;=rjnIdPpo zbrqmG@D#52*BumuVqsyCot+HYo0F3h1AtSN%le>*K%PX5YSrzdbG;8A zc;uelSBm zC-rSux!w5fTl|o=);lgLf_5{cZ+>y{&g#kLScA;+X_2hJ`13wgTeCW!lk z)`e!3EHYGF^wYa@Phr3+7tiXHIB3Fp?f60vEKn#E+gr}v)%CmI$%yP{Go;>K3Mw_- zqfkDF*&f5j{wPnTqSBYVkDvB*vaxp*MsZG|1&RMIcfrD{ahPM9x?SlAC&mEFo%n2y zd2(TM%S^=1@r>VLRfKCg%*x7&`MQlEG&V87)sh>*Bk= zF_I(JxSQSW5j)i_(!~*=c)=DII=kewIE6GvKl(s3*S_@9jxYWBg2Gq*w3I(-N=%?* z!md5M^qp!6xcA|=eJ4YQ!1MK5R)RImaWx2Lr8fKu+;tViSzVzx6Mz1^cE0-ybx8Zt zf5I^{yPu}*WI*+5#c>7Cdwbn-?*m}*p=@|DxDFeDBI@{pS6V%o&(4gL%o6!=&VgK6 z7~BZY9h8y~5h{WeuqfvP{j{{F2n zFE}K}fEwS+;6sx$(&Jwu&#A@qUyR9AT~X83^)<%aN8<63ksvh|hONm5gkB(+{57it zBiBo!7IMcn{y9r$`MJHV`9t2NXppa2+x+BYx8c(G#01m!Bz-LQ-(bLRB{^Xb|T^kv2%``(kQe3X2_cWJ={C+uO)BT82U_f>N5VwG0k*xT!> zciToMTZ`mw_PiJhxI5-|)P}8V>qkUJBHeclXgVw__0Q0im6bR4_py}6B6QFRqZfYA zM9aLZGMKKQUzz;(2e6x%Zh9vi(X7W=erMAo>R2L~fk$h}j?)#Py6@%rSUOHb7)X)!5AgGV%Wygp>b7QGcAP9C|oJyEj4nC%?> z2{FQ=#6%1_mZ@qIbZ4`76nLQPB9T7zI$p!E6sWGQc6ins#uc_U>x`IlNLCi-B*PPu z+;SDfND3AAw=$q`AYBW%KM#0MOB)Uwo|$2%fN5-v#|M`(r1i~3^QAvh$tNa@#0U~M zooea{XR;*yR8WtPk6-Rnqfh`i6|}JC=g*sk*szTvB6iO3k+(J}3oD1MyY4GMdDuyR zkrdiGm^ij+aqkGFe2?}*bA06p3zE!jl6lni%#ou`f4GKO#~=ab0y@F@d1F9gzm=86 z%$-Dpg|W9R3_0_>d>M#G&ULV;ngE&^AT?l+rhxAodF_T*Tns#OvOyac7rh2J248yg zoI-AXK31&UM}}-z+~n7W-7nx&1_X$4kVRTt>h~9!)Vo@{&ssiaM;td^-drAgxgB@F z_s*|J#>YDqmB9Wq8aw`;i6fqSgBvQ@c#7Tco(!501bx1H1NFk?poeEpE{Svj*#EP8 z4NMV#k7BLm0dQ!5=mWYJFYe;9vd2IaIVlQ*xe$%lEw4E$LEbZAF`XT~>oU_R){E5( z(ii<2Hs5pmt+qCWKfiOy3FroGmX+TsD>Z*M!Yilcpv)TIzkPc#QN-*}Zdo%kGB-D{ z@}s7P{9&_22F;MGpufJG;m7bcGNQf?sF`_`Gs4n9dI|dWVjcF>pVHq23LDWbba}ka1BEhN%UoSuJ;4o?%MF!7 zrjU8yiVsAx3ihNmTc z?HWj&*_F}NB_GmJoF=a#n)#(H8DE0??iIXcK^5^v{kM8Xo^=v+T@bwt&`}5jxhzfo z6ikt24JEtg3v^Y>Re3PcDKKRXk((EC_L2~M#xU~1!osRt&d7K#Q5GMuW@PkR@FK## zP7?7ZcJzmedIB3!`12>&ENqq_cd?ng2S8tJ^lI2E1U6fSVdF^blRu}sZT|~4dG|{-s%BPB@ic=+@cXJs|M~9Iun+h-KXii8SWzRhM) zsAj~>*`6#rM&*ShBq!gi;huPD5Q-qzs}joS?!Lmmli-HJF_$f<=|Oj~*K}??&y$w7 zPaa6a-F=`##B(=mS=sCG7VoBHS8usF76xli1o9v+ik7^Fftp(DbzNCmnE?l4sNw0% z@~JmaSbKk2AqbB*f!skF4tqlInGhS@!b6D=4b&g24lHr6>|__(jttVI{%Rm?c{a^| zm#T)r3fL&{yHea9gFUSn{AF2*W6AsKZBZ_fx&z*Z1tSep9Px}KbTk4+r*4^N2NVH- zF(=yqy-7)L=EhIGZ|N&0LWOp?J;-@Rgb943-cllBNuNz)dQ zDZ=UqjkwxY_XP6356Z)!RH5zf47NN}^5e0tTU!IO>(oJg$`DK)ibVx+IgZmJ8VQ%- zL5w+L#CBAezkB!10#vlODZM<-;Vk@Vya7>RaOmox=jbXbt7iB+G94?x3q>tx&;-j8 z$u*^t>mz?HmA5_{fhwofJc%}dsh*|4p$|0zgjgv<@b%&3Wd-LZQ4XJrBvNkw_;R#X zEPj-3L@qqfI$Dx@XnTdl(&%@mf9l3iY8f_CPIV0WA{`D(pkQcUDqrRd3vSESGfo|v zWo;a%2tW3&^T4y_mW56vW^uy{g;dYjgODssPI~TBiDB?!#f<*rCq3a^@Pwp)e2l~N zv0-#QxkL~;L~NW+k@gLc=)wi3iRBoW(+(y=!@>dwV|(OC2nkU#!ou{(=@5n(^;5%Q zgd?7@Q`t;tS8w(t;(KADoCuyAErrOy4I6$mwEAsJ6D0`=lzqN915!y?f>QVnVANy$ zK7)kE8>y+O78>LuKag3!Lx|I(ohA5P9)nYu6r_9D>n3PKSw{oQ`3Cfmgy0`?R&Od< zHlkom<%(>`EKI@?IqR)I1^n9~0|**FfnFBnvDJtt(PSH}`gXB(x?F4MC9^q7UOb6U z%`Jw*;6H&jmO!)-e)}g{#=Hm)v#Qop!Z>Mc6n2&llaU7QgZhnL>?k5#Sq6v<+z2dG z6p-r);;O2uPF3 zG%^sLo!&__3Yt~cJNkfgj{wsN6Jqr-qMPa^9g?h5;^8J}2hKG1NySt2Mp&NOpiy@B-DTIpu@bzKXBYR(ct`!5l z?7P1|tt0%dK#fp0F;U&Y8&RH4y1Me7vg3Q1zRC81$eAiKXt05}`C>UT!MDEeUvS?) zy0cLDlnop%*IRvY>yNzhvLWIRok(Yc8QieD6o#le|igWqChy6z%9s-(|=7oy0Y^dsk8T`Ri}D zY6w4878W&O&yJg=D%;68cKL+3yUnaSc?`(z?ry|Q-NHWkPJSZXkR(J@Kp<>w&9akt zF{JG|V9_JP!{Tuobe$u@^z4h;sQ9sJ3PoDx&s@^P%00P*6qBk>f zESyGFGB&~tfi;aC$w|i_(hY|aJU;O} z`(0Gfa=Dfcj1yg9sLxveQ@+A@TzvKA*FdAW_{SY^$-n<$OKa2xC{GP@nVuIdtC>09 zdGPnS>YSEP0a}xuf3UWs8yO>U}eYvWj@@$y}d;O~aK@4BvaJoCd`KI-9G)h_92AT5e*ptD zF^m8*Qzzzo`7fD;lQVRb@1Zk+1Ol-Rh^?V8H=+R)2zTQF>UBW+K$2@uR-QFz?!tEA zNRjCUUG#cAtpM=K!`uA;lYaJa%bt=Fe#|1^arD+}&~fOMKkqWCX+@WM^Cm-uXFg8m zSc|Sc8})(Df2*hx_#fjDHR?>txUh9LXYDvEqzQf?{{W$4G7 zJ|N#zH@5k4+=jI1r=9MVfd;Bkf6@JxZi}S3IXwrN;^+V*xb3;<{f*1n`hyO-$1ert zNsNsR(lzl*tWoY;YX2N>Ko;3a$A?=ekoK4vfhJCWjs@foUaLV0)+$V z;iRHM@bKh_D{7*Ffa{rsiq|d>AOp#7uYqUlueg8+XZ_xmf9jeu0vG=U-@LoMJ_K6K zul@5#H$|ci&@AuVZ*Omho3x-kzh4iyvjV2Zku#s=pSKEts}p>dvJ=^`RVu0*iP{MM zp)v+F266~+6W@;fwof1EV#1o%8j(qD*yV^%rcf$*vsfzM`cd7c!; zK_HLO9%ccyDakfIICdOU0eMYQM~4gu%fsX2iGRwnfR#BJX@0&phrlbx*;#pZ6c-l< zM{tX|tVP+ODvUwv>+4?vu|`Mb=FrJ_X29+TI}88b-zq#+(Mtw!PDv7}>cX*CCMzo| zadwE&kATtEcswX%Q_8)bl$r}vSBwZm$IbOpty{BOcDG1e>#v(VkCyhD=2OlGa+Z?^ zTAuaEqQb(1y@q|FDQ3tjK!T|n$1GEqu*aeo;u96XLIl=W#A}8l#v}J4I>Gpq<~9-c z%cJ+h(CFe~4q(LL6A=8)%qb`cuR(k-EW|aeanQaAB8nF2L0K%RnMWO~4mSMwpDhnR zfHxppffw-f9pz{d4+GLzx*JZm7OyJWCiPgPLuS{acM~Ll7p=II6k4_8f{H0^{(p`O z%^AAyP#idZ{FgpNgpUc@{3Z8>&O(mQ(K0LF(!EOfA9>$!879Kt&3mm=KS<=Dk((?v ziqCiqx^oPDfgnF$$-7^8+#N&>Kd0^Zrh~qjz6=B4^@0R9Tw9-m-&|kj6@r&N)>E4z zraPqN<{vi;H{d^%(jFc)c8pz?l}jOyMHi{*%VWfPbjyrO6h7(=Ci9XefgTI#wFWtu z8qiB3#1J94c4xq6FlzE91(GRX?7-uDnLbca4o)JKplevGC;{=%?o5po{?YCAx#Ac9 zQRt(}=_qg$P^WlI?p%ku6=ZTj10Km%@6&`lxO7Sk7zO-+v)y>LRdjHD zyX>fvOV{zmlmsxZZcq*sCEiQptO9!jXo{h;X2>wk@17c^I3p=wgM8Qn>zvDhLi=D@ zxo0u3PAM}H8~YwgBhT8VRTvAf#EPY2#jQYlo?a| zEQON8#Dsw>@^G4noPksq%!zjCpEEKgkhZVx#(q<*F@<6Z7)*+=SH)^wo^tx9M0VYX zz2=eG*{Eb=Z`REuKXlLcf$Qt*-Nt^W+SYqQO zt|nLt4HIi~QGNXVMZv9@HfMNwd6CZl6h@+PK-WAUaPQZ6dp6U%a6^sT7-xI8LVGW8 zc5^(W=r3MUQWDpG1xy9YN+g)oVGY z%NC7Ckem1t%H+gCvQQU+J2En9?4z6^(3G@YBL)CoTArEMlZv3*KJ*6IBUlZ<<^{gx zWA`;ZU^W#edI4oJHYKbsKM!?O?PX7vFdNNLWf?$jQL%uNA4%b|JRYnLjsc_wdL#$Kr|uVE9+Y2@|llXTgF z3ZUSOgqi*7Wyk>20cMp?g4C(-Dao_D-v!Xn36cHyzoQ_PKPJ&X)aP{$MvR1S4%+2w zqJA2bJR}@A=r2e^yYO$?n{)tnBLP3_=Chl6o@&9QqXl$!CVf7olV!-FdxP}n6Kyu_ z=VXzbw0np(91L(uwQG^KL8k1{U{V519$;t2Cnkmhw{H5kl9$(;Brc-}&`W(UTk46W z7x2IF+yOHk+hOtPJC+pD+Xn-V{=5}Hk$qHI%##ZGoSphX0IWcOn@_+?%gar}=c#-` zOB-FszCv~)uyedzju)p@q7w-wcz~7^Hizx(&ZNEAWX!FSDI5+wnC`($kw`TbD3_8q z_)`qFwce}~_w>B^Tx3LES0nHtP8E|8Dat5;;?D!Y+aP0AerOdAnLS`uW~jgm@pXoR zvdByKDuZYc^vJ!ejvI%g)n0l`;T(`*UqyV#FhgG0yh{CJzIhBr&l>1}2Bs>!Ej12K<4AW~HFBW37f^OQyQzwF-5gC2Qv|hVVnSy+gK-rS1ET}xXS~vsHrsfc9>>`q zJtW%-t}DU842m7`bp|-M@7}%me)9wT1I%*a;^DoI*IDaI74hK%`hGge6%g+iALQ|X zOkuBAz^;GyDs_tBy>}B0m^oT139AS8gXcO}$@uEKn!gK}@EiqjP~42UJ5Ct<=MaM2%j6Gp19(Y$PpZAAUhspUqcK#FZcnGbP2M zs=QoHEkdby^yLC-Z-=;vjt&!xI*qXBllxN>{=#nycV{&tQ&VBU)FLYdo4uIfG2hnO z3OVr>A)pdybr533x&mH<)4$)OpsigAXU=S;j}ZWbAgizcG$JD6;ABh{cv`;a4GW#B{zt?>VknO}zVrqDa0dv=!C%|5pgikYj|D$) zu46bhWMgPLfq{YoX}KJ5EO%OxoM$Nqb0jlCFO>zw!1&HY(%Tk)kxeqzC|M7|4B!jK zL42Bw1u|3$f@r%U?+@H3yO^OHy==S%5L)iu7tGAEJSTt{Nr90A0C5M~Cie#AYBOfY zKm-eVh=lLeW_!)0Nznks))^1Lru-&`h*M|bX4H%4a1=iMeh4& zT1v`aU>`8S+*EY8v$OMqC7jmrO2`}3`H(#86eT9G9(VD`rcTZ1hA;eY>sK+Yp~@i6o=wJJJ`9h4rOk*d z+2SLuBj5X9frBW`q~b=PM>Coo#16D7!~lLXILJK%1EkchX4xKfZ;NXNe*czqcjp5G zN%{w=@a~@nrXAFG&F;IDz#sV!v?{Hik1OR$Ip~*UiKAAaKJtZ97NZSXv(O4iIT~& zTb<5^&1UXgp5@NCb&uJXc14qAYLpu-N zy#Zd}a6HVsy$+Rp@i3goXU05#0)~cm8D8LCOjlYB5C_HLpa^1J_(9Hkr9GM6om~1l z2~csIIFAL?sV83Y0VV}D=wXjGUBLx**zO}(65z0Q3uzYFs_A@>fac>+lYY7zMZN58 zo0g|PJZ|Hr(UaP(Nx~>dEBy;`V}pQ*zK|dfKR8|{G=^G7%jAHR1eaRTc(L{fm>oKk zDVuEn=lp^w+!Txrf1@#-$Y`wyx<_BpEX32)!>6ChP^b9kIe$}Qcn5!Wj7nyBYN*MCAws+v^vvxN zv?d#_f)|d>Wu7cl2;8R}_+1Y_L(=V0B z0E=t936ggQmW)s3CD_2~kLId_Kqb3(u(qE5jKejaG11KFU`zQS=c!HRa#;4rb1)hL z=0w0;CRcLg>9$Q3CHM|}1{PAyW*Zno@N8w`Gw=ojwx%yBzy*8~&ttii<5%NP_iRB} z>?YakY{KPE+&j#W=O~StzX$u!PYxg57784&N(^DB?0GyC0MC2aFettz<_pa&7=N4u zqT1RdrIe5i;&TBzp zX5w-n1ak#W#KZAYCWAr##)XRu1f6Z7(a+{c@mJhTKL`5zv<=0e4F#I`3oWTCDhkPt zxv~f9KuT7u2XRpReCHv@kkxKSNQYyvZ=ynOc&FK8{NtZm1_lP8T{`VFLlQtNA*BL; p)`VDt7_wvb9WgTHpv|wZ?ps>QS0R~6DPUS2A}_51E0r`2{vQdJ1YrOG literal 0 HcmV?d00001 diff --git a/public/images/achievements/border_stone_locked.png b/public/images/achievements/border_stone_locked.png new file mode 100644 index 0000000000000000000000000000000000000000..66f92b2b4dc798d92b2fa28064510db5fa4f18c6 GIT binary patch literal 10568 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 literal 0 HcmV?d00001 From 3266e3b46b58cfd2e4aca0755f36cab78ca1e2c6 Mon Sep 17 00:00:00 2001 From: Ruben Vereecken Date: Mon, 4 Aug 2014 22:21:43 +0200 Subject: [PATCH 58/83] intermediate --- .../RelatedAchievementsCollection.coffee | 10 +++++++ app/templates/editor/level/edit.jade | 6 ++++- .../editor/related-achievements.jade | 5 ++++ .../editor/RelatedAchievementsView.coffee | 26 +++++++++++++++++++ app/views/editor/level/LevelEditView.coffee | 2 ++ .../achievements/achievement_handler.coffee | 11 ++++++++ 6 files changed, 59 insertions(+), 1 deletion(-) create mode 100644 app/collections/RelatedAchievementsCollection.coffee create mode 100644 app/templates/editor/related-achievements.jade create mode 100644 app/views/editor/RelatedAchievementsView.coffee diff --git a/app/collections/RelatedAchievementsCollection.coffee b/app/collections/RelatedAchievementsCollection.coffee new file mode 100644 index 000000000..e558d2891 --- /dev/null +++ b/app/collections/RelatedAchievementsCollection.coffee @@ -0,0 +1,10 @@ +CocoCollection = require 'collections/CocoCollection' +Achievement = require 'models/Achievement' + +class RelatedAchievementCollection extends CocoCollection + model: Achievement + + initialize: (relatedID) -> + @url = "/db/achievement?related=#{relatedID}" + +module.exports = RelatedAchievementCollection diff --git a/app/templates/editor/level/edit.jade b/app/templates/editor/level/edit.jade index 3f16f5559..6bcc24328 100644 --- a/app/templates/editor/level/edit.jade +++ b/app/templates/editor/level/edit.jade @@ -33,6 +33,8 @@ block header - var patches = level.get('patches') if patches && patches.length span.badge= patches.length + li + a(href="#related-achievements-view", data-toggle="tab") Achievements li a(href="#docs-components-view", data-toggle="tab", data-i18n="editor.level_tab_docs") Documentation .navbar-header @@ -121,8 +123,10 @@ block outer_content div.tab-pane#editor-level-patches .patches-view + div.tab-pane#related-achievements-view + div.tab-pane#docs-components-view div#error-view -block footer \ No newline at end of file +block footer diff --git a/app/templates/editor/related-achievements.jade b/app/templates/editor/related-achievements.jade new file mode 100644 index 000000000..5aa61fba9 --- /dev/null +++ b/app/templates/editor/related-achievements.jade @@ -0,0 +1,5 @@ +- console.debug('this is definitely rendering'); +h2 yooo + + +button Sexy diff --git a/app/views/editor/RelatedAchievementsView.coffee b/app/views/editor/RelatedAchievementsView.coffee new file mode 100644 index 000000000..8278f17ef --- /dev/null +++ b/app/views/editor/RelatedAchievementsView.coffee @@ -0,0 +1,26 @@ +CocoView = require 'views/kinds/CocoView' +template = require 'templates/editor/related-achievements' +RelatedAchievementsCollection = require 'collections/RelatedAchievementsCollection' +Achievement = require 'models/Achievement' + +module.exports = class RelatedAchievementsView extends CocoView + id: 'related-achievements-view' + template: template + className: 'tab-pane' + + constructor: (options) -> + super options + @relatedID = options.id + @achievements = new RelatedAchievementsCollection @relatedID + console.debug @achievements + @supermodel.loadCollection @achievements, 'achievements' + + onLoaded: -> + console.debug 'related achievements loaded' + super() + + getRenderData: -> + c = super() + c.achievements = @achievements + c.relatedID = @relatedID + c diff --git a/app/views/editor/level/LevelEditView.coffee b/app/views/editor/level/LevelEditView.coffee index 21b22c5f8..350ae00a5 100644 --- a/app/views/editor/level/LevelEditView.coffee +++ b/app/views/editor/level/LevelEditView.coffee @@ -15,6 +15,7 @@ SaveLevelModal = require './modals/SaveLevelModal' LevelForkView = require './modals/ForkLevelModal' SaveVersionModal = require 'views/modal/SaveVersionModal' PatchesView = require 'views/editor/PatchesView' +RelatedAchievementsView = require 'views/editor/RelatedAchievementsView' VersionHistoryView = require './modals/LevelVersionsModal' ComponentDocsView = require 'views/docs/ComponentDocumentationView' @@ -75,6 +76,7 @@ module.exports = class LevelEditView extends RootView @subViews['scriptsTab'] = @insertSubView new ScriptsTabView world: @world, supermodel: @supermodel, files: @files @subViews['componentsTab'] = @insertSubView new ComponentsTabView supermodel: @supermodel @subViews['systemsTab'] = @insertSubView new SystemsTabView supermodel: @supermodel + @subViews['achievementsTab'] = @insertSubView new RelatedAchievementsView supermodel: @supermodel, id: @level.id @subviews['componentsDocsTab'] = @insertSubView new ComponentDocsView supermodel: @supermodel Backbone.Mediator.publish 'level-loaded', level: @level diff --git a/server/achievements/achievement_handler.coffee b/server/achievements/achievement_handler.coffee index 989a8b955..433df56d6 100644 --- a/server/achievements/achievement_handler.coffee +++ b/server/achievements/achievement_handler.coffee @@ -11,4 +11,15 @@ class AchievementHandler extends Handler hasAccess: (req) -> req.method is 'GET' or req.user?.isAdmin() + get: (req, res) -> + # /db/achievement?related= + if req.query.related + return @sendUnauthorizedError(res) if not @hasAccess(req) + Achievement.find {related: req.query.related}, (err, docs) => + return @sendDatabaseError(res, err) if err + docs = (@formatEntity(req, doc) for doc in docs) + @sendSuccess res, docs + else + super req, res + module.exports = new AchievementHandler() From 7d7d5300c06d0bc69eaeaa50058bea748292b791 Mon Sep 17 00:00:00 2001 From: Ruben Vereecken Date: Tue, 5 Aug 2014 14:33:33 +0200 Subject: [PATCH 59/83] Refactored New Model Modal (from SearchView originally) --- app/schemas/models/achievement.coffee | 13 +++-- app/schemas/schemas.coffee | 3 +- app/templates/editor/achievement/edit.jade | 17 +++---- .../editor/related-achievements.jade | 5 -- .../editor/related_achievements.jade | 18 +++++++ app/templates/kinds/search.jade | 22 +-------- app/templates/modal/new_model.jade | 19 ++++++++ .../docs/ComponentDocumentationView.coffee | 2 +- .../editor/RelatedAchievementsView.coffee | 9 +++- .../achievement/AchievementEditView.coffee | 12 ++--- app/views/editor/level/LevelEditView.coffee | 2 +- app/views/kinds/SearchView.coffee | 36 ++++---------- app/views/modal/NewModelModal.coffee | 47 +++++++++++++++++++ 13 files changed, 125 insertions(+), 80 deletions(-) delete mode 100644 app/templates/editor/related-achievements.jade create mode 100644 app/templates/editor/related_achievements.jade create mode 100644 app/templates/modal/new_model.jade create mode 100644 app/views/modal/NewModelModal.coffee diff --git a/app/schemas/models/achievement.coffee b/app/schemas/models/achievement.coffee index 1740b845b..ec88bcad3 100644 --- a/app/schemas/models/achievement.coffee +++ b/app/schemas/models/achievement.coffee @@ -42,10 +42,12 @@ _.extend AchievementSchema.properties, query: #type:'object' $ref: '#/definitions/' + MongoFindQuerySchema.id - worth: {type: 'number'} + worth: c.float + default: 10 collection: {type: 'string'} - description: {type: 'string'} - userField: {type: 'string'} + description: c.shortString + default: 'Probably the coolest you\'ll ever get.' + userField: c.shortString() related: c.objectId(description: 'Related entity') icon: {type: 'string', format: 'image-file', title: 'Icon'} category: @@ -53,12 +55,14 @@ _.extend AchievementSchema.properties, description: "E.g. 'level', 'ladder', 'contributor'..." # TODO might make this enum? difficulty: c.int description: 'The higher the more difficult' + default: 1 proportionalTo: type: 'string' description: 'For repeatables only. Denotes the field a repeatable achievement needs for its calculations' recalculable: type: 'boolean' description: 'Needs to be set to true before it is elligible for recalculation.' + default: true function: type: 'object' description: 'Function that gives total experience for X amount achieved' @@ -76,7 +80,8 @@ _.extend AchievementSchema.properties, additionalProperties: false _.extend AchievementSchema, # Let's have these on the bottom - required: ['name', 'description', 'query', 'worth', 'collection', 'userField', 'category', 'difficulty'] + # TODO We really need some required properties in my opinion but this makes creating new achievements impossible as it is now + #required: ['name', 'description', 'query', 'worth', 'collection', 'userField', 'category', 'difficulty'] additionalProperties: false AchievementSchema.definitions = {} diff --git a/app/schemas/schemas.coffee b/app/schemas/schemas.coffee index 889e7c0c7..ea6881729 100644 --- a/app/schemas/schemas.coffee +++ b/app/schemas/schemas.coffee @@ -19,7 +19,8 @@ me.date = (ext) -> combine({type: ['object', 'string'], format: 'date-time'}, ex # should just be string (Mongo ID), but sometimes mongoose turns them into objects representing those, so we are lenient me.objectId = (ext) -> schema = combine({type: ['object', 'string']}, ext) me.url = (ext) -> combine({type: 'string', format: 'url', pattern: urlPattern}, ext) -me.int = (ext) -> if ext then combine {type: 'integer'}, ext else {type: 'integer'} +me.int = (ext) -> combine {type: 'integer'}, ext +me.float = (ext) -> combine {type: 'number'}, ext PointSchema = me.object {title: 'Point', description: 'An {x, y} coordinate point.', format: 'point2d', required: ['x', 'y']}, x: {title: 'x', description: 'The x coordinate.', type: 'number', 'default': 15} diff --git a/app/templates/editor/achievement/edit.jade b/app/templates/editor/achievement/edit.jade index aac62cbf0..51572899a 100644 --- a/app/templates/editor/achievement/edit.jade +++ b/app/templates/editor/achievement/edit.jade @@ -2,14 +2,13 @@ extends /templates/base block content if me.isAdmin() - div - ol.breadcrumb - li - a(href="/editor", data-i18n="editor.main_title") CodeCombat Editors - li - a(href="/editor/achievement", data-i18n="editor.achievement_title") Achievement Editor - li.active - | #{achievement.attributes.name} + ol.breadcrumb + li + a(href="/editor", data-i18n="editor.main_title") CodeCombat Editors + li + a(href="/editor/achievement", data-i18n="editor.achievement_title") Achievement Editor + li.active + | #{achievement.attributes.name} button(data-i18n="", disabled=me.isAdmin() === true ? undefined : "true").btn.btn-primary#recalculate-button Recalculate button(data-i18n="common.save", disabled=me.isAdmin() === true ? undefined : "true").btn.btn-primary#save-button Save @@ -25,8 +24,6 @@ block content hr - #error-view - else .alert.alert-danger span Admin only. Turn around. diff --git a/app/templates/editor/related-achievements.jade b/app/templates/editor/related-achievements.jade deleted file mode 100644 index 5aa61fba9..000000000 --- a/app/templates/editor/related-achievements.jade +++ /dev/null @@ -1,5 +0,0 @@ -- console.debug('this is definitely rendering'); -h2 yooo - - -button Sexy diff --git a/app/templates/editor/related_achievements.jade b/app/templates/editor/related_achievements.jade new file mode 100644 index 000000000..1ab64773e --- /dev/null +++ b/app/templates/editor/related_achievements.jade @@ -0,0 +1,18 @@ + +if achievements.loading + h2 Loading... +else if ! achievements.models.length + .panel + .panel-body + p No achievements added for this level yet. +else + table + tr + th Name + th Description + th XP + each achievement in achievements.models + tr + td= achievement.get('name') + td= achievement.get('description') + td= achievement.get('worth') diff --git a/app/templates/kinds/search.jade b/app/templates/kinds/search.jade index 0e522d272..d69074636 100644 --- a/app/templates/kinds/search.jade +++ b/app/templates/kinds/search.jade @@ -12,32 +12,12 @@ block content if me.get('anonymous') a.btn.btn-primary.open-modal-button(data-toggle="coco-modal", data-target="modal/AuthModal", role="button", data-i18n="#{currentNewSignup}") Log in to Create a New Content else - a.btn.btn-primary.open-modal-button(href='#new-model-modal', role="button", data-toggle="modal", data-i18n="#{currentNew}") Create a New Something + a.btn.btn-primary.open-modal-button#new-model-button(data-i18n="#{currentNew}") Create a New Something input#search(data-i18n="[placeholder]#{currentSearch}") hr div.results table - // TODO: make this into a ModalView subview - div.modal.fade#new-model-modal - .modal-dialog - .background-wrapper - .modal-content - .modal-header - h3(data-i18n="#{currentNew}") Create New #{modelLabel} - .modal-body - form.form - .form-group - label.control-label(for="name", data-i18n="general.name") Name - input#name.form-control(name="name", type="text") - .modal-footer - button.btn(data-dismiss="modal", data-i18n="common.cancel") Cancel - button.btn.btn-primary.new-model-submit(data-i18n="common.create") Create - .modal-body.wait.secret - h3(data-i18n="play_level.tip_reticulating") Reticulating Splines... - .progress.progress-striped.active - .progress-bar - else .alert.alert-danger span Admin only. Turn around. diff --git a/app/templates/modal/new_model.jade b/app/templates/modal/new_model.jade new file mode 100644 index 000000000..c904d1b96 --- /dev/null +++ b/app/templates/modal/new_model.jade @@ -0,0 +1,19 @@ +extends /templates/modal/modal_base + +block modal-header-content + h3(data-i18n="#{currentNew}") Create New #{modelLabel} + +block modal-body-content + form.form + .form-group + label.control-label(for="name", data-i18n="general.name") Name + input#name.form-control(name="name", type="text") + +block modal-footer + .modal-footer + button.btn(data-dismiss="modal", data-i18n="common.cancel") Cancel + button.btn.btn-primary.new-model-submit(data-i18n="common.create") Create + .modal-body.wait.secret + h3(data-i18n="play_level.tip_reticulating") Reticulating Splines... + .progress.progress-striped.active + .progress-bar diff --git a/app/views/docs/ComponentDocumentationView.coffee b/app/views/docs/ComponentDocumentationView.coffee index b20385618..cf85cd636 100644 --- a/app/views/docs/ComponentDocumentationView.coffee +++ b/app/views/docs/ComponentDocumentationView.coffee @@ -37,4 +37,4 @@ module.exports = class ComponentDocumentationView extends CocoView c.language = 'javascript' else c.language = me.get('aceConfig').language - c \ No newline at end of file + c diff --git a/app/views/editor/RelatedAchievementsView.coffee b/app/views/editor/RelatedAchievementsView.coffee index 8278f17ef..2f341c843 100644 --- a/app/views/editor/RelatedAchievementsView.coffee +++ b/app/views/editor/RelatedAchievementsView.coffee @@ -1,5 +1,5 @@ CocoView = require 'views/kinds/CocoView' -template = require 'templates/editor/related-achievements' +template = require 'templates/editor/related_achievements' RelatedAchievementsCollection = require 'collections/RelatedAchievementsCollection' Achievement = require 'models/Achievement' @@ -10,13 +10,14 @@ module.exports = class RelatedAchievementsView extends CocoView constructor: (options) -> super options - @relatedID = options.id + @relatedID = options.relatedID @achievements = new RelatedAchievementsCollection @relatedID console.debug @achievements @supermodel.loadCollection @achievements, 'achievements' onLoaded: -> console.debug 'related achievements loaded' + @achievements.loading = false super() getRenderData: -> @@ -24,3 +25,7 @@ module.exports = class RelatedAchievementsView extends CocoView c.achievements = @achievements c.relatedID = @relatedID c + + render: -> + console.debug 'rendering achievements' + super() diff --git a/app/views/editor/achievement/AchievementEditView.coffee b/app/views/editor/achievement/AchievementEditView.coffee index d72dda993..3791e3d8d 100644 --- a/app/views/editor/achievement/AchievementEditView.coffee +++ b/app/views/editor/achievement/AchievementEditView.coffee @@ -2,6 +2,7 @@ RootView = require 'views/kinds/RootView' template = require 'templates/editor/achievement/edit' Achievement = require 'models/Achievement' ConfirmModal = require 'views/modal/ConfirmModal' +errors = require 'lib/errors' module.exports = class AchievementEditView extends RootView id: 'editor-achievement-edit-view' @@ -20,13 +21,10 @@ module.exports = class AchievementEditView extends RootView @achievement = new Achievement(_id: @achievementID) @achievement.saveBackups = true - @listenToOnce(@achievement, 'error', - () => - @hideLoading() - $(@$el).find('.main-content-area').children('*').not('#error-view').remove() - - @insertSubView(new ErrorView()) - ) + @achievement.once 'error', (achievement, jqxhr) => + @hideLoading() + $(@$el).find('.main-content-area').children('*').not('.breadcrumb').remove() + errors.backboneFailure arguments... @achievement.fetch() @listenToOnce(@achievement, 'sync', @buildTreema) diff --git a/app/views/editor/level/LevelEditView.coffee b/app/views/editor/level/LevelEditView.coffee index 98b289c39..ce6eb47c2 100644 --- a/app/views/editor/level/LevelEditView.coffee +++ b/app/views/editor/level/LevelEditView.coffee @@ -75,7 +75,7 @@ module.exports = class LevelEditView extends RootView @insertSubView new ScriptsTabView world: @world, supermodel: @supermodel, files: @files @insertSubView new ComponentsTabView supermodel: @supermodel @insertSubView new SystemsTabView supermodel: @supermodel - @insertSubView new RelatedAchievementsView supermodel: @supermodel, id: @level.id + @insertSubView new RelatedAchievementsView supermodel: @supermodel, relatedID: @level.id @insertSubView new ComponentDocsView supermodel: @supermodel Backbone.Mediator.publish 'level-loaded', level: @level diff --git a/app/views/kinds/SearchView.coffee b/app/views/kinds/SearchView.coffee index 7fdd4cad6..ef79c4ab4 100644 --- a/app/views/kinds/SearchView.coffee +++ b/app/views/kinds/SearchView.coffee @@ -1,6 +1,6 @@ RootView = require 'views/kinds/RootView' +NewModelModal = require 'views/modal/NewModelModal' template = require 'templates/kinds/search' -forms = require 'lib/forms' app = require 'application' class SearchCollection extends Backbone.Collection @@ -26,9 +26,7 @@ module.exports = class SearchView extends RootView events: 'change input#search': 'runSearch' 'keydown input#search': 'runSearch' - 'click button.new-model-submit': 'makeNewModel' - 'submit form': 'makeNewModel' - 'shown.bs.modal #new-model-modal': 'focusOnName' + 'click #new-model-button': 'newModel' 'hidden.bs.modal #new-model-modal': 'onModalHidden' constructor: (options) -> @@ -79,31 +77,13 @@ module.exports = class SearchView extends RootView @collection.off() @collection = null - makeNewModel: (e) -> - e.preventDefault() - name = @$el.find('#name').val() - model = new @model() - model.set('name', name) - if @model.schema.properties.permissions - model.set 'permissions', [{access: 'owner', target: me.id}] - res = model.save() - return unless res - - modal = @$el.find('#new-model-modal') - forms.clearFormAlerts(modal) - @showLoading(modal.find('.modal-body')) - res.error => - @hideLoading() - forms.applyErrorsToForm(modal, JSON.parse(res.responseText)) - that = @ - res.success -> - that.model = model - modal.modal('hide') - - onModalHidden: -> + onNewModelSaved: (@model) -> # Can only redirect after the modal hidden event has triggered + console.debug 'new model saved' base = document.location.pathname[1..] + '/' app.router.navigate(base + (@model.get('slug') or @model.id), {trigger: true}) - focusOnName: -> - @$el.find('#name').focus() + newModel: (e) -> + modal = new NewModelModal model: @model, modelLabel: @modelLabel + modal.once 'success', @onNewModelSaved + @openModalView modal diff --git a/app/views/modal/NewModelModal.coffee b/app/views/modal/NewModelModal.coffee new file mode 100644 index 000000000..3114b2608 --- /dev/null +++ b/app/views/modal/NewModelModal.coffee @@ -0,0 +1,47 @@ +ModalView = require 'views/kinds/ModalView' +template = require 'templates/modal/new_model' +forms = require 'lib/forms' + +module.exports = class NewModelModal extends ModalView + id: 'new-model-modal' + template: template + plain: false + + events: + 'click button.new-model-submit': 'makeNewModel' + 'submit form': 'makeNewModel' + 'shown.bs.modal #new-model-modal': 'focusOnName' + + + constructor: (options) -> + super options + @model = options.model + @modelLabel = options.modelLabel + + getRenderData: -> + c = super() + c.modelLabel = @modelLabel + #c.newModelTitle = @newModelTitle + c + + makeNewModel: (e) -> + e.preventDefault() + name = @$el.find('#name').val() + model = new @model + model.set('name', name) + if @model.schema.properties.permissions + model.set 'permissions', [{access: 'owner', target: me.id}] + res = model.save() + return unless res + + forms.clearFormAlerts @$el + @showLoading(@$el.find('.modal-body')) + res.error => + @hideLoading() + forms.applyErrorsToForm(@$el, JSON.parse(res.responseText)) + #Backbone.Mediator.publish 'model-save-fail', model + res.success => + @$el.modal('hide') + @trigger 'success', model + #Backbone.Mediator.publish 'model-save-success', model + From d42922871ed90b031d5c5500a58a1afd819134f3 Mon Sep 17 00:00:00 2001 From: Ruben Vereecken Date: Tue, 5 Aug 2014 15:14:51 +0200 Subject: [PATCH 60/83] Added related achievements tab to level editor --- app/styles/achievements.sass | 3 +++ app/styles/editor/related-achievements.sass | 6 +++++ .../editor/related-achievements.jade | 26 +++++++++++++++++++ .../editor/related_achievements.jade | 18 ------------- .../editor/RelatedAchievementsView.coffee | 15 ++++++++++- app/views/kinds/SearchView.coffee | 2 -- app/views/modal/NewModelModal.coffee | 3 ++- .../achievements/achievement_handler.coffee | 2 +- 8 files changed, 52 insertions(+), 23 deletions(-) create mode 100644 app/styles/editor/related-achievements.sass create mode 100644 app/templates/editor/related-achievements.jade delete mode 100644 app/templates/editor/related_achievements.jade diff --git a/app/styles/achievements.sass b/app/styles/achievements.sass index 122f81e45..4896c5eec 100644 --- a/app/styles/achievements.sass +++ b/app/styles/achievements.sass @@ -218,3 +218,6 @@ h2.achievements-category .table-layout #no-achievements margin-top: 40px + +.achievement-icon-small + height: 18px diff --git a/app/styles/editor/related-achievements.sass b/app/styles/editor/related-achievements.sass new file mode 100644 index 000000000..46314ea65 --- /dev/null +++ b/app/styles/editor/related-achievements.sass @@ -0,0 +1,6 @@ +#related-achievements-view + #new-achievement-button + margin-bottom: 10px + + .icon-column + width: 25px diff --git a/app/templates/editor/related-achievements.jade b/app/templates/editor/related-achievements.jade new file mode 100644 index 000000000..9270c0def --- /dev/null +++ b/app/templates/editor/related-achievements.jade @@ -0,0 +1,26 @@ + +button.btn.btn-primary#new-achievement-button(disabled=me.isAdmin() === true ? undefined : "true" data-i18n="editor.new_achievement_title") Create a New Achievement + +if achievements.loading + h2 Loading... +else if ! achievements.models.length + .panel + .panel-body + p No achievements added for this level yet. +else + table.table.table-hover + thead + tr + th + th Name + th Description + th XP + tbody + each achievement in achievements.models + tr + td(style="width: 20px") + img.achievement-icon-small(src=achievement.getImageURL() alt="#{achievement.get('name') icon") + td + a(href="/editor/achievement/#{achievement.get('slug')}")= achievement.get('name') + td= achievement.get('description') + td= achievement.get('worth') diff --git a/app/templates/editor/related_achievements.jade b/app/templates/editor/related_achievements.jade deleted file mode 100644 index 1ab64773e..000000000 --- a/app/templates/editor/related_achievements.jade +++ /dev/null @@ -1,18 +0,0 @@ - -if achievements.loading - h2 Loading... -else if ! achievements.models.length - .panel - .panel-body - p No achievements added for this level yet. -else - table - tr - th Name - th Description - th XP - each achievement in achievements.models - tr - td= achievement.get('name') - td= achievement.get('description') - td= achievement.get('worth') diff --git a/app/views/editor/RelatedAchievementsView.coffee b/app/views/editor/RelatedAchievementsView.coffee index 2f341c843..774b550e7 100644 --- a/app/views/editor/RelatedAchievementsView.coffee +++ b/app/views/editor/RelatedAchievementsView.coffee @@ -1,13 +1,18 @@ CocoView = require 'views/kinds/CocoView' -template = require 'templates/editor/related_achievements' +template = require 'templates/editor/related-achievements' RelatedAchievementsCollection = require 'collections/RelatedAchievementsCollection' Achievement = require 'models/Achievement' +NewModelModal = require 'views/modal/NewModelModal' +app = require 'application' module.exports = class RelatedAchievementsView extends CocoView id: 'related-achievements-view' template: template className: 'tab-pane' + events: + 'click #new-achievement-button': 'makeNewAchievement' + constructor: (options) -> super options @relatedID = options.relatedID @@ -29,3 +34,11 @@ module.exports = class RelatedAchievementsView extends CocoView render: -> console.debug 'rendering achievements' super() + + onNewAchievementSaved: (achievement) -> + app.router.navigate('/editor/achievement/' + (achievement.get('slug') or achievement.id), {trigger: true}) + + makeNewAchievement: -> + modal = new NewModelModal model: Achievement, modelLabel: 'Achievement', properties: related: @relatedID + modal.once 'success', @onNewAchievementSaved + @openModalView modal diff --git a/app/views/kinds/SearchView.coffee b/app/views/kinds/SearchView.coffee index ef79c4ab4..1e41c8ead 100644 --- a/app/views/kinds/SearchView.coffee +++ b/app/views/kinds/SearchView.coffee @@ -78,8 +78,6 @@ module.exports = class SearchView extends RootView @collection = null onNewModelSaved: (@model) -> - # Can only redirect after the modal hidden event has triggered - console.debug 'new model saved' base = document.location.pathname[1..] + '/' app.router.navigate(base + (@model.get('slug') or @model.id), {trigger: true}) diff --git a/app/views/modal/NewModelModal.coffee b/app/views/modal/NewModelModal.coffee index 3114b2608..9e7667207 100644 --- a/app/views/modal/NewModelModal.coffee +++ b/app/views/modal/NewModelModal.coffee @@ -12,11 +12,11 @@ module.exports = class NewModelModal extends ModalView 'submit form': 'makeNewModel' 'shown.bs.modal #new-model-modal': 'focusOnName' - constructor: (options) -> super options @model = options.model @modelLabel = options.modelLabel + @properties = options.properties getRenderData: -> c = super() @@ -31,6 +31,7 @@ module.exports = class NewModelModal extends ModalView model.set('name', name) if @model.schema.properties.permissions model.set 'permissions', [{access: 'owner', target: me.id}] + model.set(key, prop) for key, prop of @properties if @properties? res = model.save() return unless res diff --git a/server/achievements/achievement_handler.coffee b/server/achievements/achievement_handler.coffee index 433df56d6..feed09f20 100644 --- a/server/achievements/achievement_handler.coffee +++ b/server/achievements/achievement_handler.coffee @@ -5,7 +5,7 @@ class AchievementHandler extends Handler modelClass: Achievement # Used to determine which properties requests may edit - editableProperties: ['name', 'query', 'worth', 'collection', 'description', 'userField', 'proportionalTo', 'icon', 'function'] + editableProperties: ['name', 'query', 'worth', 'collection', 'description', 'userField', 'proportionalTo', 'icon', 'function', 'related', 'difficulty', 'category'] jsonSchema = require '../../app/schemas/models/achievement.coffee' hasAccess: (req) -> From 06ba50f5e2f03f11cfdd6f43b2f9005f51a7a419 Mon Sep 17 00:00:00 2001 From: Ruben Vereecken Date: Tue, 5 Aug 2014 18:58:37 +0200 Subject: [PATCH 61/83] Added user images --- public/images/pages/user/adventurer.png | Bin 0 -> 73257 bytes public/images/pages/user/ambassador.png | Bin 0 -> 51718 bytes public/images/pages/user/archmage.png | Bin 0 -> 87782 bytes public/images/pages/user/artisan.png | Bin 0 -> 56476 bytes public/images/pages/user/diplomat.png | Bin 0 -> 56817 bytes public/images/pages/user/general.png | Bin 0 -> 37077 bytes public/images/pages/user/scribe.png | Bin 0 -> 85648 bytes 7 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 public/images/pages/user/adventurer.png create mode 100644 public/images/pages/user/ambassador.png create mode 100644 public/images/pages/user/archmage.png create mode 100644 public/images/pages/user/artisan.png create mode 100644 public/images/pages/user/diplomat.png create mode 100644 public/images/pages/user/general.png create mode 100644 public/images/pages/user/scribe.png diff --git a/public/images/pages/user/adventurer.png b/public/images/pages/user/adventurer.png new file mode 100644 index 0000000000000000000000000000000000000000..2729d87b8c6968215ba7d9b87543770c096721b6 GIT binary patch literal 73257 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

=-eh3YH5^kaKcCrA*LhsXKV`ZJcL*U@T%P+vS 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 literal 0 HcmV?d00001 From 9f0add22a5b3f9354f2eb107d632003eb458bf33 Mon Sep 17 00:00:00 2001 From: Ruben Vereecken Date: Thu, 7 Aug 2014 22:03:00 +0200 Subject: [PATCH 62/83] Worked in a lot of changes Scott proposed --- .../EarnedAchievementCollection.coffee | 5 +- app/models/CocoModel.coffee | 6 +- app/models/LevelSession.coffee | 6 +- app/models/User.coffee | 42 +-- app/schemas/models/achievement.coffee | 6 +- app/schemas/models/patch.coffee | 4 +- app/styles/account/home.sass | 16 +- app/styles/achievements.sass | 23 +- app/templates/account/home.jade | 298 ++++++++---------- app/templates/kinds/user.jade | 10 +- app/templates/user/achievements.jade | 4 +- app/templates/user/home.jade | 55 +++- app/views/account/MainAccountView.coffee | 14 +- .../achievement/AchievementEditView.coffee | 2 +- app/views/kinds/RootView.coffee | 8 +- app/views/kinds/UserView.coffee | 27 +- app/views/user/AchievementsView.coffee | 34 +- app/views/user/MainUserView.coffee | 22 +- .../achievement_background_light.png | Bin 0 -> 4509 bytes public/images/achievements/default.png | Bin 7916 -> 9880 bytes public/images/achievements/star.png | Bin 0 -> 9035 bytes public/images/achievements/stars.png | Bin 7916 -> 0 bytes server/achievements/Achievement.coffee | 2 +- .../earned_achievement_handler.coffee | 7 +- server/patches/Patch.coffee | 8 +- server/users/User.coffee | 2 +- 26 files changed, 287 insertions(+), 314 deletions(-) create mode 100644 public/images/achievements/achievement_background_light.png create mode 100644 public/images/achievements/star.png delete mode 100644 public/images/achievements/stars.png diff --git a/app/collections/EarnedAchievementCollection.coffee b/app/collections/EarnedAchievementCollection.coffee index 527f1459e..d091c36f6 100644 --- a/app/collections/EarnedAchievementCollection.coffee +++ b/app/collections/EarnedAchievementCollection.coffee @@ -4,6 +4,5 @@ EarnedAchievement = require 'models/EarnedAchievement' module.exports = class EarnedAchievementCollection extends CocoCollection model: EarnedAchievement - initialize: (me = require('lib/auth').me) -> - @url = "/db/user/#{me.id}/achievements" - + initialize: (userID) -> + @url = "/db/user/#{userID}/achievements" diff --git a/app/models/CocoModel.coffee b/app/models/CocoModel.coffee index b27f64ba1..ad0bc00b3 100644 --- a/app/models/CocoModel.coffee +++ b/app/models/CocoModel.coffee @@ -297,14 +297,12 @@ class CocoModel extends Backbone.Model @pollAchievements: -> NewAchievementCollection = require '../collections/NewAchievementCollection' # Nasty mutual inclusion if put on top - console.debug 'Polling for new achievements' achievements = new NewAchievementCollection achievements.fetch success: (collection) -> - console.debug 'Polling for achievements success', collection me.fetch (success: -> Backbone.Mediator.publish('achievements:new', collection)) unless _.isEmpty(collection.models) - error: (collection, res, options) -> - console.error 'Miserably failed to fetch unnotified achievements' + error: -> + console.error 'Miserably failed to fetch unnotified achievements', arguments CocoModel.pollAchievements = _.debounce CocoModel.pollAchievements, 500 diff --git a/app/models/LevelSession.coffee b/app/models/LevelSession.coffee index c33662824..34b03be6d 100644 --- a/app/models/LevelSession.coffee +++ b/app/models/LevelSession.coffee @@ -38,7 +38,7 @@ module.exports = class LevelSession extends CocoModel false isMultiplayer: -> - console.log @get 'levelName' - console.log @ - console.log @get 'team' @get('team')? # Only multiplayer level sessions have teams defined + + completed: -> + @get('state')?.complete || false diff --git a/app/models/User.coffee b/app/models/User.coffee index 810212065..7b492cdba 100644 --- a/app/models/User.coffee +++ b/app/models/User.coffee @@ -42,51 +42,13 @@ module.exports = class User extends CocoModel return "/file/#{photoURL}#{prefix}s=#{size}" return "/db/user/#{@id}/avatar?s=#{size}&employerPageAvatar=#{useEmployerPageAvatar}" - # Callbacks can be either 'success' or 'error' - @getByID = (id, properties, force, callbacks={}) -> - {me} = require 'lib/auth' - if me.id is id - callbacks.success me if callbacks.success? - return me - user = cache[id] or new module.exports({_id: id}) - if force or not cache[id] - user.loading = true - user.fetch( - success: -> - user.loading = false - Backbone.Mediator.publish('user:fetched') - callbacks.success arguments... if callbacks.success? - error: -> callbacks.error arguments... if callbacks.error? - ) - cache[id] = user - user + getSlugOrID: -> @get('slug') or @get('_id') + set: -> if arguments[0] is 'jobProfileApproved' and @get("jobProfileApproved") is false and not @get("jobProfileApprovedDate") @set "jobProfileApprovedDate", (new Date()).toISOString() super arguments... - # callbacks can be either success or error - @getByIDOrSlug: (idOrSlug, force, callbacks={}) -> - {me} = require 'lib/auth' - isID = util.isID idOrSlug - if me.id is idOrSlug or me.slug is idOrSlug - callbacks.success me if callbacks.success? - return me - cached = cache[idOrSlug] - user = cached or new @ _id: idOrSlug - if force or not cached - user.loading = true - user.fetch - success: -> - user.loading = false - Backbone.Mediator.publish 'user:fetched' - callbacks.success user if callbacks.success? - error: -> - user.loading = false - callbacks.error user if callbacks.error? - cache[idOrSlug] = user - user - @getUnconflictedName: (name, done) -> $.ajax "/auth/name/#{name}", success: (data) -> done data.name diff --git a/app/schemas/models/achievement.coffee b/app/schemas/models/achievement.coffee index ec88bcad3..eed560e74 100644 --- a/app/schemas/models/achievement.coffee +++ b/app/schemas/models/achievement.coffee @@ -35,7 +35,7 @@ MongoFindQuerySchema.definitions[MongoQueryOperatorSchema.id] = MongoQueryOperat AchievementSchema = c.object() c.extendNamedProperties AchievementSchema -c.extendBasicProperties AchievementSchema, 'achievement' # TODO What's this about? +c.extendBasicProperties AchievementSchema, 'achievement' c.extendSearchableProperties AchievementSchema _.extend AchievementSchema.properties, @@ -51,8 +51,8 @@ _.extend AchievementSchema.properties, related: c.objectId(description: 'Related entity') icon: {type: 'string', format: 'image-file', title: 'Icon'} category: - type: 'string' - description: "E.g. 'level', 'ladder', 'contributor'..." # TODO might make this enum? + enum: ['level', 'ladder', 'contributor'] + description: 'For categorizing and display purposes' difficulty: c.int description: 'The higher the more difficult' default: 1 diff --git a/app/schemas/models/patch.coffee b/app/schemas/models/patch.coffee index 66a32bb09..bd4e822fe 100644 --- a/app/schemas/models/patch.coffee +++ b/app/schemas/models/patch.coffee @@ -21,8 +21,8 @@ PatchSchema = c.object({title: 'Patch', required: ['target', 'delta', 'commitMes minor: {type: 'number', minimum: 0} }) - _wasPending: type: 'boolean' - _newlyAccepted: type: 'boolean' + wasPending: type: 'boolean' + newlyAccepted: type: 'boolean' }) c.extendBasicProperties(PatchSchema, 'patch') diff --git a/app/styles/account/home.sass b/app/styles/account/home.sass index 453b52e94..3e695687c 100644 --- a/app/styles/account/home.sass +++ b/app/styles/account/home.sass @@ -6,7 +6,7 @@ margin-bottom: 0px img#picture - max-width: 50% + max-width: 100% .panel margin-bottom: 10px @@ -14,5 +14,19 @@ h2 margin-bottom: 0px + a + font-size: 28px + margin-left: 5px + .panel-title > a margin-left: 5px + color: rgb(11, 99, 188) + + .panel-me + td + padding-left: 15px + + .panel-emails + h4 + font-family: $font-family-base + diff --git a/app/styles/achievements.sass b/app/styles/achievements.sass index 4896c5eec..63d0e650e 100644 --- a/app/styles/achievements.sass +++ b/app/styles/achievements.sass @@ -1,3 +1,5 @@ +@import 'bootstrap/variables' + .locked // This used to be a grayscale filter but they're mad intensive to paint @@ -22,7 +24,7 @@ background-image: url("/images/achievements/achievement_background_locked.png") &:not(.locked) .achievement-content - background-image: url("/images/achievements/achievement_background.png") + background-image: url("/images/achievements/achievement_background_light.png") .achievement-content background-size: 100% 100% @@ -30,18 +32,23 @@ overflow: hidden > .achievement-title - font-family: Bangers + font-family: $font-family-base + font-weight: bold + white-space: nowrap + max-height: 2em overflow: hidden + text-overflow: ellipsis + > .achievement-description white-space: initial font-size: 12px line-height: 1.3em - overflow: hidden max-height: 2.6em margin-top: auto margin-bottom: 0px !important padding-left: 5px + overflow: hidden text-overflow: ellipsis // Specific to the user stats page @@ -75,7 +82,6 @@ .achievement-title font-size: 20px - padding-left: -50px .achievement-description font-size: 12px @@ -105,12 +111,14 @@ bottom: 0 .achievement-content + background-image: url("/images/achievements/achievement_background.png") position: relative width: 450px height: 160px padding: 24px 30px 20px 60px .achievement-title + font-family: Bangers font-size: 28px padding-left: -50px @@ -209,11 +217,14 @@ .user-level z-index: 1000 box-shadow: 0 0 0 1px black, 0 0 0 3px lightgrey, 0 0 0 4px black - font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif + font-family: $font-family-base // Achievements page -h2.achievements-category +.achievement-category-title margin-left: 20px + font-family: $font-family-base + font-weight: bold + color: #5a5a5a .table-layout #no-achievements diff --git a/app/templates/account/home.jade b/app/templates/account/home.jade index 129961760..df897a358 100644 --- a/app/templates/account/home.jade +++ b/app/templates/account/home.jade @@ -1,172 +1,138 @@ extends /templates/base block content - .clearfix - .col-sm-6.clearfix - h2 Account Settings - hr - .panel.panel-default - .panel-heading - i.glyphicon.glyphicon-user.pull-left - h3.panel-title - a(href="account/settings#me") Me - .panel-body - dl - dt Name - dd=me.get('name') - dt Email - dd abe@lincoln - .panel.panel-default - .panel-heading - i.glyphicon.glyphicon-picture.pull-left - h3.panel-title - a(href="account/settings#picture") Picture - .panel-body.text-center - img#picture(src="#{me.getPhotoURL(150)}" alt="") - .panel.panel-default - .panel-heading - i.glyphicon.glyphicon-user.pull-left - h3.panel-title - a(href="account/settings#wizard") Wizard - //.panel-body - | Lorem Ipsum - .panel.panel-default - .panel-heading - i.glyphicon.glyphicon-envelope.pull-left - h3.panel-title - a(href="account/settings#emails") Emails - .panel-body - .form - .form-group.checkbox - label.control-label(for="email_archmageNews") - span General - input#email_archmageNews(name="email_archmageNews", type="checkbox", checked=subs.generalNews, disabled="true") - .form-group.checkbox - label.control-label(for="email_archmageNews") - span.spr(data-i18n="classes.archmage_title") - | Archmage - span(data-i18n="classes.archmage_title_description") - | (Coder) - input#email_archmageNews(name="email_archmageNews", type="checkbox", checked=subs.archmageNews, disabled="true") - .form-group.checkbox - label.control-label(for="email_artisanNews") - span.spr(data-i18n="classes.artisan_title") - | Artisan - span(data-i18n="classes.artisan_title_description") - | (Level Builder) - input#email_artisanNews(name="email_artisanNews", type="checkbox", checked=subs.artisanNews, disabled="true") - .form-group.checkbox - label.control-label(for="email_adventurerNews") - span.spr(data-i18n="classes.adventurer_title") - | Adventurer - span(data-i18n="classes.adventurer_title_description") - | (Level Playtester) - input#email_adventurerNews(name="email_adventurerNews", type="checkbox", checked=subs.adventurerNews, disabled="true") - .form-group.checkbox - label.control-label(for="email_scribeNews") - span.spr(data-i18n="classes.scribe_title") - | Scribe - span(data-i18n="classes.scribe_title_description") - | (Article Editor) - input#email_scribeNews(name="email_scribeNews", type="checkbox", checked=subs.scribeNews, disabled="true") - .form-group.checkbox - label.control-label(for="email_diplomatNews") - span.spr(data-i18n="classes.diplomat_title") - | Diplomat - span(data-i18n="classes.diplomat_title_description") - | (Translator) - input#email_diplomatNews(name="email_diplomatNews", type="checkbox", checked=subs.diplomatNews, disabled="true") - .form-group.checkbox - label.control-label(for="email_ambassadorNews") - span.spr(data-i18n="classes.ambassador_title") - | Ambassador - span(data-i18n="classes.ambassador_title_description") - | (Support) - input#email_ambassadorNews(name="email_ambassadorNews", type="checkbox", checked=subs.ambassadorNews, disabled="true") - - .panel.panel-default - .panel-heading - i.glyphicon.glyphicon-wrench.pull-left - h3.panel-title - a(href="account/settings#password") Password - .panel.panel-default - .panel-heading - i.glyphicon.glyphicon-briefcase.pull-left - h3.panel-title - a(href="account/settings#job-profile") Job Profile - .col-sm-6 - h2 Recently Played - hr - if !recentlyPlayed - div Loading... - else if recentlyPlayed.length - table.table - tr - th Level - th Last Played - th Status - each session in recentlyPlayed - if session.get('levelName') - tr - td - - var posturl = '' - - if (session.get('team')) posturl = '?team=' + session.get('team') - a(href="/play/level/#{session.get('levelID') + posturl}")= session.get('levelName') + (session.get('team') ? ' (' + session.get('team') + ')' : '') - td= moment(session.get('changed')).fromNow() - if session.get('state').complete === true - td Completed - else if ! session.isMultiplayer() - td Unfinished - else - td - - else - .panel.panel-default + if !me.isAnonymous() + .clearfix + .col-sm-6.clearfix + h2 Account Settings + a.spl(href="settings") + i.glyphicon.glyphicon-cog + hr + .row + .col-xs-6 + .panel.panel-default + .panel-heading + h3.panel-title + i.glyphicon.glyphicon-picture + a(href="account/settings#picture") Picture + .panel-body.text-center + img#picture(src="#{me.getPhotoURL(150)}" alt="") + .col-xs-6 + .panel.panel-default + .panel-heading + h3.panel-title + i.glyphicon.glyphicon-user + a(href="account/settings#wizard") Wizard + if (wizardSource) + .panel-body.text-center + img(src="#{wizardSource}") + .panel.panel-default.panel-me + .panel-heading + h3.panel-title + i.glyphicon.glyphicon-user + a(href="account/settings#me") Me .panel-body - div No games played during the past two weeks. - -//block content - h2 Account - .col-sm-3.text-center - img#avatar(src="#{me.getPhotoURL(100)}") - h3=me.get('name') || 'Anoner' - .col-sm-6 - dl.dl-horizontal - if me.get('firstName') || me.get('lastName') - dt Full name - dd=me.get('firstName') || '' + ' ' + me.get('lastName') || '' - dt Email - dd - span.spr=me.get('email') - //span (subscriptions) - hr - - var dateCreated = me.get('dateCreated'); - - var signedCLA = me.get('signedCLA'); - - console.log(moment) - dt Member since - dd= moment(dateCreated).format('MMMM Do YYYY') - if signedCLA - dt Signed CLA - dd= moment(signedCLA).format('MMMM Do YYYY') - - // TODO Have social network icons here for easy linking - - .col-sm-3 - h3 Account - ul - li - a Settings - li - a Payment - h3 Public - ul - li - a Public profile - li - a Job Profile - li - a Statistics - li - a Code + table + tr + th Name + td=me.get('name') || 'Anoner' + tr + th Email + td=me.get('email') + .panel.panel-default.panel-emails + .panel-heading + h3.panel-title + i.glyphicon.glyphicon-envelope + a(href="account/settings#emails") Emails + .panel-body + if !hasEmailNotes && !hasEmailNews + p No email subscriptions. + if hasEmailNotes + h4 Notifications + ul + if subs.anyNotes + li(data-i18n="account_settings.email_any_notes") Any Notifications + if subs.recruitNotes + li(data-i18n="account_settings.email_recruit_notes") Job Opportunities + if hasEmailNews + h4 News + ul + if (subs.generalNews) + li(data-i18n="account_settings.email_announcements") General + if (subs.archmageNews) + li + span(data-i18n="classes.archmage_title") + | Archmage + span(data-i18n="classes.archmage_title_description") + | (Coder) + if (subs.artisanNews) + li + span.spr(data-i18n="classes.artisan_title") + | Artisan + span(data-i18n="classes.artisan_title_description") + | (Level Builder) + if (subs.adventurerNews) + li + span.spr(data-i18n="classes.adventurer_title") + | Adventurer + span(data-i18n="classes.adventurer_title_description") + | (Level Playtester) + if (subs.scribeNews) + li + span.spr(data-i18n="classes.scribe_title") + | Scribe + span(data-i18n="classes.scribe_title_description") + | (Article Editor) + if (subs.diplomatNews) + li + span.spr(data-i18n="classes.diplomat_title") + | Diplomat + span(data-i18n="classes.diplomat_title_description") + | (Translator) + if (subs.ambassadorNews) + li + span.spr(data-i18n="classes.ambassador_title") + | Ambassador + span(data-i18n="classes.ambassador_title_description") + | (Support) + .panel.panel-default + .panel-heading + h3.panel-title + i.glyphicon.glyphicon-wrench + a(href="account/settings#password") Password + .panel.panel-default + .panel-heading + h3.panel-title + i.glyphicon.glyphicon-briefcase + a(href="account/settings#job-profile") Job Profile + .col-sm-6 + h2 Recently Played + hr + if !recentlyPlayed + div Loading... + else if recentlyPlayed.length + table.table + tr + th Level + th Last Played + th Status + each session in recentlyPlayed + if session.get('levelName') + tr + td + - var posturl = '' + - if (session.get('team')) posturl = '?team=' + session.get('team') + a(href="/play/level/#{session.get('levelID') + posturl}")= session.get('levelName') + (session.get('team') ? ' (' + session.get('team') + ')' : '') + td= moment(session.get('changed')).fromNow() + if session.get('state').complete === true + td Completed + else if ! session.isMultiplayer() + td Unfinished + else + td + else + .panel.panel-default + .panel-body + div No games played during the past two weeks. diff --git a/app/templates/kinds/user.jade b/app/templates/kinds/user.jade index a40153635..1bcd19c1c 100644 --- a/app/templates/kinds/user.jade +++ b/app/templates/kinds/user.jade @@ -6,14 +6,8 @@ block content if user && viewName ol.breadcrumb li - a(href="/user/#{user.id}") #{user.displayName()} + a(href="/user/#{user.getSlugOrID()}") #{user.displayName()} li.active | #{viewName} - if !userLoaded + if !user || user.loading | LOADING - else if !user - // TODO Ruben make this all fancy as soon as we can query users by name - | User not found. - - - diff --git a/app/templates/user/achievements.jade b/app/templates/user/achievements.jade index cd64db67e..34ccebd8a 100644 --- a/app/templates/user/achievements.jade +++ b/app/templates/user/achievements.jade @@ -9,7 +9,7 @@ block append content .grid-layout each achievements, category in achievementsByCategory .row - h2.achievements-category=category + h2.achievement-category-title=category.charAt(0).toUpperCase() + category.slice(1) each achievement, index in achievements - var title = achievement.get('name'); - var description = achievement.get('description'); @@ -33,7 +33,7 @@ block append content th Date th Amount th XP - each earnedAchievement in earnedAchievements + each earnedAchievement in earnedAchievements.models - var achievement = earnedAchievement.get('achievement'); tr td= achievement.get('name') diff --git a/app/templates/user/home.jade b/app/templates/user/home.jade index b235a77f3..e32e6dc32 100644 --- a/app/templates/user/home.jade +++ b/app/templates/user/home.jade @@ -13,9 +13,12 @@ block append content div.extra-info Favorite language is strong.spl.spr= favoriteLanguage .btn-group-vertical.profile-menu - a.btn.btn-default(href="/user/#{user.get('slug') || user.get('_id')}/profile") + a.btn.btn-default(href="/user/#{user.getSlugOrID()}/profile") i.glyphicon.glyphicon-briefcase span Job Profile + a.btn.btn-default(href="/user/#{user.getSlugOrID()}/stats") + i.glyphicon.glyphicon-certificate + span Stats a.btn.btn-default.disabled(href="#") i.glyphicon.glyphicon-pencil span Code @@ -52,10 +55,6 @@ block append content a(href="/contribute#scribe") Scribe .right-column - //.panel.panel-default - .panel-heading - h3.panel-title Achievements - .panel-body .panel.panel-default .panel-heading h3.panel-title Singleplayer Levels @@ -65,9 +64,9 @@ block append content else if (singlePlayerSessions.length) table.table tr - th Level - th Last Played - th Status + th.col-xs-4 Level + th.col-xs-4 Last Played + th.col-xs-4 Status each session in singlePlayerSessions if session.get('levelName') tr @@ -90,17 +89,43 @@ block append content else if (multiPlayerSessions.length) table.table tr - th Level - th Team - th Last Played + th.col-xs-4 Level + th.col-xs-4 Last Played + th.col-xs-4 Score each session in multiPlayerSessions tr td - a(href="/play/level/#{session.get('levelID')}")= session.get('levelName') - td= session.get('team') + - var posturl = '' + - if (session.get('team')) posturl = '?team=' + session.get('team') + a(href="/play/level/#{session.get('levelID') + posturl}")= session.get('levelName') + (session.get('team') ? ' (' + session.get('team') + ')' : '') td= moment(session.get('changed')).fromNow() - if session.get('state').complete === true - td Completed + if session.get('totalScore') + td= session.get('totalScore') * 100 + else + td Unfinished else .panel-body p No Multiplayer games played yet. + .panel.panel-default + .panel-heading + h3.panel-title Achievements + if ! earnedAchievements + .panel-body + p Loading... + else if ! earnedAchievements.length + .panel-body + p No achievements earned so far. + else + table.table + tr + th.col-xs-4 Achievement + th.col-xs-4 Last Earned + th.col-xs-4 Amount + each achievement in earnedAchievements.models + tr + td= achievement.get('achievementName') + td= moment().format("MMMM Do YY", achievement.get('changed')) + if achievement.get('achievedAmount') + td= achievement.get('achievedAmount') + else + td diff --git a/app/views/account/MainAccountView.coffee b/app/views/account/MainAccountView.coffee index 03442e381..50d7360be 100644 --- a/app/views/account/MainAccountView.coffee +++ b/app/views/account/MainAccountView.coffee @@ -4,6 +4,7 @@ template = require 'templates/account/home' User = require 'models/User' AuthModalView = require 'views/modal/AuthModal' RecentlyPlayedCollection = require 'collections/RecentlyPlayedCollection' +ThangType = require 'models/ThangType' module.exports = class MainAccountView extends View id: 'account-home-view' @@ -12,15 +13,24 @@ module.exports = class MainAccountView extends View constructor: (options) -> super options return unless me - @recentlyPlayed = @supermodel.loadCollection(new RecentlyPlayedCollection(me.get('_id')), 'recentlyPlayed').model + @wizardType = ThangType.loadUniversalWizard() + @recentlyPlayed = new RecentlyPlayedCollection me.get('_id') + @supermodel.loadModel @wizardType, 'thang' + @supermodel.loadCollection @recentlyPlayed, 'recentlyPlayed' + + onLoaded: -> + super() getRenderData: -> c = super() c.subs = {} c.subs[sub] = 1 for sub in c.me.getEnabledEmails() + c.hasEmailNotes = _.any c.me.getEnabledEmails(), (sub) -> sub.contains 'Notes' + c.hasEmailNews = _.any c.me.getEnabledEmails(), (sub) -> sub.contains 'News' + c.wizardSource = @wizardType.getPortraitSource colorConfig: me.get('wizard')?.colorConfig if @wizardType.loaded c.recentlyPlayed = @recentlyPlayed.models c afterRender: -> super() - @openModelView new AuthModalView if me.isAnonymous() + @openModalView new AuthModalView if me.isAnonymous() diff --git a/app/views/editor/achievement/AchievementEditView.coffee b/app/views/editor/achievement/AchievementEditView.coffee index 3791e3d8d..0e42a6470 100644 --- a/app/views/editor/achievement/AchievementEditView.coffee +++ b/app/views/editor/achievement/AchievementEditView.coffee @@ -80,7 +80,7 @@ module.exports = class AchievementEditView extends RootView $('#achievement-view-inner').notify data, options openSaveModal: -> - 'Maybe later' # TODO + 'Maybe later' # TODO patch patch patch saveAchievement: (e) -> @treema.endExistingEdits() diff --git a/app/views/kinds/RootView.coffee b/app/views/kinds/RootView.coffee index 7ecffe5cd..0b636c045 100644 --- a/app/views/kinds/RootView.coffee +++ b/app/views/kinds/RootView.coffee @@ -47,13 +47,13 @@ module.exports = class RootView extends CocoView achievedExp = achievement.get 'worth' previousExp = currentExp - achievedExp leveledUp = currentExp - achievedExp < currentLevelExp - console.debug 'Leveled up' if leveledUp + #console.debug 'Leveled up' if leveledUp alreadyAchievedPercentage = 100 * (previousExp - currentLevelExp) / totalExpNeeded alreadyAchievedPercentage = 0 if alreadyAchievedPercentage < 0 # In case of level up newlyAchievedPercentage = if leveledUp then 100 * (currentExp - currentLevelExp) / totalExpNeeded else 100 * achievedExp / totalExpNeeded - console.debug "Current level is #{currentLevel} (#{currentLevelExp} xp), next level is #{nextLevel} (#{nextLevelExp} xp)." - console.debug "Need a total of #{nextLevelExp - currentLevelExp}, already had #{previousExp} and just now earned #{achievedExp} totalling on #{currentExp}" + #console.debug "Current level is #{currentLevel} (#{currentLevelExp} xp), next level is #{nextLevel} (#{nextLevelExp} xp)." + #console.debug "Need a total of #{nextLevelExp - currentLevelExp}, already had #{previousExp} and just now earned #{achievedExp} totalling on #{currentExp}" alreadyAchievedBar = $("

") newlyAchievedBar = $("
") @@ -79,7 +79,6 @@ module.exports = class RootView extends CocoView showNewAchievement: (achievement, earnedAchievement) -> data = @createNotifyData achievement, earnedAchievement - console.debug data options = autoHideDelay: 5000 elementPosition: 'top left' @@ -89,7 +88,6 @@ module.exports = class RootView extends CocoView autoHide: false clickToHide: true - console.debug 'showing achievement', achievement.get 'name' unless @timeout? $.notify data, options @timeout = 2000 diff --git a/app/views/kinds/UserView.coffee b/app/views/kinds/UserView.coffee index 52b60b445..f2e997f1e 100644 --- a/app/views/kinds/UserView.coffee +++ b/app/views/kinds/UserView.coffee @@ -9,40 +9,27 @@ module.exports = class UserView extends RootView constructor: (@userID, options) -> super options - - @listenTo @, 'userLoaded', @onUserLoaded @listenTo @, 'userNotFound', @ifUserNotFound - - @userID ?= me.id # TODO Ruben really? @fetchUser @userID - # TODO Ruben make this use the new getByNameOrID as soon as that is merged in fetchUser: (id) -> - User.getByID id, {}, true, - success: (@user) => - @userLoaded = true - @trigger 'userNotFound' unless @user - @trigger 'userLoaded', @user - error: => - @userLoaded = true - @trigger 'userNotFound' + if @isMe() + @user = me + @onLoaded() + @user = new User _id: id + @supermodel.loadModel @user, 'user' getRenderData: -> context = super() context.viewName = @viewName context.user = @user unless @user?.isAnonymous() - context.userLoaded = @userLoaded context isMe: -> @userID is me.id - onUserLoaded: -> - console.log 'onUserLoaded', @user - @render() + onLoaded: -> + super() ifUserNotFound: -> console.warn 'user not found' @render() - - onLoaded: -> - super() diff --git a/app/views/user/AchievementsView.coffee b/app/views/user/AchievementsView.coffee index 20cc05717..c5a7f32bb 100644 --- a/app/views/user/AchievementsView.coffee +++ b/app/views/user/AchievementsView.coffee @@ -19,22 +19,22 @@ module.exports = class AchievementsView extends UserView constructor: (userID, options) -> super options, userID - onUserLoaded: (user) -> - @achievements = @supermodel.loadCollection(new AchievementCollection, 'achievements').model - @earnedAchievements = @supermodel.loadCollection(new EarnedAchievementCollection(@user), 'earnedAchievements').model - super user - onLoaded: -> - console.log @earnedAchievementsy - console.log @achievements - for earned in @earnedAchievements.models - return unless relatedAchievement = _.find @achievements.models, (achievement) -> - achievement.get('_id') is earned.get 'achievement' - relatedAchievement.set 'unlocked', true - earned.set 'achievement', relatedAchievement - deferredImages = (achievement.cacheLockedImage() for achievement in @achievements.models when not achievement.get 'unlocked') - whenever = $.when deferredImages... - whenever.done => @render() + unless @achievements or @earnedAchievements + @supermodel.resetProgress() + @achievements = new AchievementCollection + @earnedAchievements = new EarnedAchievementCollection @user.getSlugOrID() + @supermodel.loadCollection @achievements, 'achievements' + @supermodel.loadCollection @earnedAchievements, 'earnedAchievements' + else + for earned in @earnedAchievements.models + return unless relatedAchievement = _.find @achievements.models, (achievement) -> + achievement.get('_id') is earned.get 'achievement' + relatedAchievement.set 'unlocked', true + earned.set 'achievement', relatedAchievement + deferredImages = (achievement.cacheLockedImage() for achievement in @achievements.models when not achievement.get 'unlocked') + whenever = $.when deferredImages... + whenever.done => @render() super() layoutChanged: (e) -> @@ -47,8 +47,8 @@ module.exports = class AchievementsView extends UserView # After user is loaded if @user and not @user.isAnonymous() - context.earnedAchievements = @earnedAchievements.models - context.achievements = @achievements.models + context.earnedAchievements = @earnedAchievements + context.achievements = @achievements context.achievementsByCategory = {} for achievement in @achievements.models context.achievementsByCategory[achievement.get('category')] ?= [] diff --git a/app/views/user/MainUserView.coffee b/app/views/user/MainUserView.coffee index 82e3c7cbe..cd70bb452 100644 --- a/app/views/user/MainUserView.coffee +++ b/app/views/user/MainUserView.coffee @@ -3,12 +3,13 @@ CocoCollection = require 'collections/CocoCollection' LevelSession = require 'models/LevelSession' template = require 'templates/user/home' {me} = require 'lib/auth' +EarnedAchievementCollection = require 'collections/EarnedAchievementCollection' class LevelSessionsCollection extends CocoCollection model: LevelSession constructor: (userID) -> - @url = "/db/user/#{userID}/level.sessions?project=state.complete,levelID,levelName,changed,team,submittedCodeLanguage&order=-1" + @url = "/db/user/#{userID}/level.sessions?project=state.complete,levelID,levelName,changed,team,submittedCodeLanguage,totalScore&order=-1" super() module.exports = class MainUserView extends UserView @@ -20,7 +21,8 @@ module.exports = class MainUserView extends UserView getRenderData: -> context = super() - if @user + if @levelSessions and @levelSessions.loaded + console.debug 'yep sessions loaded' singlePlayerSessions = [] multiPlayerSessions = [] languageCounts = {} @@ -39,11 +41,19 @@ module.exports = class MainUserView extends UserView context.singlePlayerSessions = singlePlayerSessions context.multiPlayerSessions = multiPlayerSessions context.favoriteLanguage = favoriteLanguage + if @earnedAchievements and @earnedAchievements.loaded + console.debug 'earned achievements loaded' + context.earnedAchievements = @earnedAchievements context - onUserLoaded: (user) -> - @levelSessions = @supermodel.loadCollection(new LevelSessionsCollection(@userID), 'levelSessions').model - super user - onLoaded: -> + console.debug @earnedAchievements + console.debug @earnedAchievements?.loaded + if @user.loaded and not @earnedAchievements + @supermodel.resetProgress() + #@levelSessions = new LevelSessionsCollection @user.getSlugOrID() + @earnedAchievements = new EarnedAchievementCollection @user.getSlugOrID() + #@supermodel.loadCollection @levelSessions, 'levelSessions' + @supermodel.loadCollection @earnedAchievements, 'earnedAchievements' + super() diff --git a/public/images/achievements/achievement_background_light.png b/public/images/achievements/achievement_background_light.png new file mode 100644 index 0000000000000000000000000000000000000000..53e09e62d4ec7611495bbcdb282f94c7807bdb44 GIT binary patch literal 4509 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 KMNUMnLSTYT^w=B# literal 7916 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 l~TN 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 category = achievement.get 'collection' AchievementSchema.statics.earnedAchievements[category] = [] unless category of AchievementSchema.statics.earnedAchievements AchievementSchema.statics.earnedAchievements[category].push achievement - done(AchievementSchema.statics.earnedAchievements) if done? + done?(AchievementSchema.statics.earnedAchievements) AchievementSchema.statics.getLoadedAchievements = -> AchievementSchema.statics.earnedAchievements diff --git a/server/achievements/earned_achievement_handler.coffee b/server/achievements/earned_achievement_handler.coffee index 20da7f159..a0ad8618e 100644 --- a/server/achievements/earned_achievement_handler.coffee +++ b/server/achievements/earned_achievement_handler.coffee @@ -29,7 +29,6 @@ class EarnedAchievementHandler extends Handler achievementIDs = (thing for thing in callbackOrSlugsOrIDs when Handler.isID(thing)) else # just a callback callback = callbackOrSlugsOrIDs - callback = if callback then callback else -> # Make a dummy just for ease of coding onFinished = -> callback arguments... filter = {} @@ -40,13 +39,13 @@ class EarnedAchievementHandler extends Handler # Fetch all relevant achievements Achievement.find filter, (err, achievements) -> - callback err if err? - callback new Error 'No achievements to recalculate' unless achievements.length + callback?(err) if err? + callback?(new Error 'No achievements to recalculate') unless achievements.length log.info "Recalculating a total of #{achievements.length} achievements..." # Fetch every single user User.find {}, (err, users) -> - callback err if err? + callback?(err) if err? log.info "for a total of #{users.length} users." async.each users, ((user, doneWithUser) -> diff --git a/server/patches/Patch.coffee b/server/patches/Patch.coffee index 9080849fc..774e6ec37 100644 --- a/server/patches/Patch.coffee +++ b/server/patches/Patch.coffee @@ -57,12 +57,12 @@ PatchSchema.methods.isMiscPatch = -> # Keep track of when a patch is pending and newly approved. PatchSchema.path('status').set (newVal) -> - @set '_wasPending', @status is 'pending' and newVal isnt 'pending' - @set '_newlyAccepted', newVal is 'accepted' and not @get('_newlyAccepted') # Only true on the first accept + @set 'wasPending', @status is 'pending' and newVal isnt 'pending' + @set 'newlyAccepted', newVal is 'accepted' and not @get('newlyAccepted') # Only true on the first accept newVal -PatchSchema.methods.isNewlyAccepted = -> @get('_newlyAccepted') -PatchSchema.methods.wasPending = -> @get '_wasPending' +PatchSchema.methods.isNewlyAccepted = -> @get('newlyAccepted') +PatchSchema.methods.wasPending = -> @get 'wasPending' PatchSchema.pre 'save', (next) -> User = require '../users/User' diff --git a/server/users/User.coffee b/server/users/User.coffee index 8d0093fcd..966f63bc6 100644 --- a/server/users/User.coffee +++ b/server/users/User.coffee @@ -142,7 +142,7 @@ UserSchema.statics.incrementStat = (id, statName, done, inc=1) -> UserSchema.methods.incrementStat = (statName, done, inc=1) -> @set statName, (@get(statName) or 0) + inc - @save (err) -> done err if done? + @save (err) -> done?(err) UserSchema.statics.unconflictName = unconflictName = (name, done) -> User.findOne {slug: _.str.slugify(name)}, (err, otherUser) -> From 3bfd341363f11246570864f1004ef2d8ff21d865 Mon Sep 17 00:00:00 2001 From: Ruben Vereecken Date: Fri, 8 Aug 2014 13:08:13 +0200 Subject: [PATCH 63/83] Goal states are now tracked in level sessions (state.goalStates) --- app/lib/LevelBus.coffee | 10 ++++++++++ app/lib/utils.coffee | 12 ++++++++++++ app/schemas/models/level_session.coffee | 8 ++++++++ 3 files changed, 30 insertions(+) diff --git a/app/lib/LevelBus.coffee b/app/lib/LevelBus.coffee index 845020c8d..7ea0837c1 100644 --- a/app/lib/LevelBus.coffee +++ b/app/lib/LevelBus.coffee @@ -1,6 +1,7 @@ Bus = require './Bus' {me} = require 'lib/auth' LevelSession = require 'models/LevelSession' +utils = require 'lib/utils' module.exports = class LevelBus extends Bus @@ -22,6 +23,7 @@ module.exports = class LevelBus extends Bus 'tome:spell-changed': 'onSpellChanged' 'tome:spell-created': 'onSpellCreated' 'application:idle-changed': 'onIdleChanged' + 'goal-manager:new-goal-states': 'onNewGoalStates' constructor: -> super(arguments...) @@ -192,6 +194,14 @@ module.exports = class LevelBus extends Bus @changedSessionProperties.state = true @saveSession() + onNewGoalStates: ({goalStates})-> + state = @session.get 'state' + unless utils.kindaEqual state.goalStates, goalStates # Only save when goals really change + state.goalStates = goalStates + @session.set 'state', state + @changedSessionProperties.state = true + @saveSession() + onPlayerJoined: (snapshot) => super(arguments...) return unless @onPoint() diff --git a/app/lib/utils.coffee b/app/lib/utils.coffee index 44ef9dd1c..c009af43a 100644 --- a/app/lib/utils.coffee +++ b/app/lib/utils.coffee @@ -118,3 +118,15 @@ module.exports.grayscale = (imageData) -> v = 0.2126*r + 0.7152*g + 0.0722*b d[i] = d[i+1] = d[i+2] = v imageData + +# Deep compares l with r, with the exception that undefined values are considered equal to missing values +# Very practical for comparing Mongoose documents where undefined is not allowed, instead fields get deleted +module.exports.kindaEqual = compare = (l, r) -> + if _.isObject(l) and _.isObject(r) + for key in _.union Object.keys(l), Object.keys(r) + return false unless compare l[key], r[key] + return true + else if l is r + return true + else + return false diff --git a/app/schemas/models/level_session.coffee b/app/schemas/models/level_session.coffee index 92ff0f5ba..42105d3c2 100644 --- a/app/schemas/models/level_session.coffee +++ b/app/schemas/models/level_session.coffee @@ -97,6 +97,14 @@ _.extend LevelSessionSchema.properties, type: 'object' source: type: 'string' + goalStates: + type: 'object' + description: 'Maps Goal ID on a goal state object' + additionalProperties: + title: 'Goal State' + type: 'object' + properties: + status: enum: ['failure', 'incomplete', 'success'] code: type: 'object' From 47f00f9b5e0527b7d99d1f3ef47edd3f03ae4179 Mon Sep 17 00:00:00 2001 From: Ruben Vereecken Date: Fri, 8 Aug 2014 17:14:57 +0200 Subject: [PATCH 64/83] Added achievement deleting and automatic achievement filling --- app/lib/LevelBus.coffee | 1 + app/templates/editor/achievement/edit.jade | 1 + .../editor/level/modal/new-achievement.jade | 23 ++++++++ .../{ => level}/related-achievements.jade | 0 .../achievement/AchievementEditView.coffee | 40 ++++++++++++-- app/views/editor/level/LevelEditView.coffee | 4 +- .../RelatedAchievementsView.coffee | 15 +++--- .../level/modals/NewAchievementModal.coffee | 54 +++++++++++++++++++ app/views/modal/ConfirmModal.coffee | 12 ++--- app/views/modal/NewModelModal.coffee | 21 +++++--- .../achievements/achievement_handler.coffee | 13 ++++- .../earned_achievement_handler.coffee | 10 ++-- server/commons/Handler.coffee | 10 ++-- server/routes/db.coffee | 2 +- .../server/functional/achievement.spec.coffee | 26 ++++++--- 15 files changed, 182 insertions(+), 50 deletions(-) create mode 100644 app/templates/editor/level/modal/new-achievement.jade rename app/templates/editor/{ => level}/related-achievements.jade (100%) rename app/views/editor/{ => level}/RelatedAchievementsView.coffee (73%) create mode 100644 app/views/editor/level/modals/NewAchievementModal.coffee diff --git a/app/lib/LevelBus.coffee b/app/lib/LevelBus.coffee index 7ea0837c1..4f7cb583c 100644 --- a/app/lib/LevelBus.coffee +++ b/app/lib/LevelBus.coffee @@ -195,6 +195,7 @@ module.exports = class LevelBus extends Bus @saveSession() onNewGoalStates: ({goalStates})-> + console.debug arguments state = @session.get 'state' unless utils.kindaEqual state.goalStates, goalStates # Only save when goals really change state.goalStates = goalStates diff --git a/app/templates/editor/achievement/edit.jade b/app/templates/editor/achievement/edit.jade index 51572899a..f3191b9bd 100644 --- a/app/templates/editor/achievement/edit.jade +++ b/app/templates/editor/achievement/edit.jade @@ -11,6 +11,7 @@ block content | #{achievement.attributes.name} button(data-i18n="", disabled=me.isAdmin() === true ? undefined : "true").btn.btn-primary#recalculate-button Recalculate + button(data-i18n="common.delete", disabled=me.isAdmin() === true ? undefined : "true").btn.btn-primary#delete-button Delete button(data-i18n="common.save", disabled=me.isAdmin() === true ? undefined : "true").btn.btn-primary#save-button Save h3(data-i18n="achievement.edit_achievement_title") Edit Achievement diff --git a/app/templates/editor/level/modal/new-achievement.jade b/app/templates/editor/level/modal/new-achievement.jade new file mode 100644 index 000000000..f66347278 --- /dev/null +++ b/app/templates/editor/level/modal/new-achievement.jade @@ -0,0 +1,23 @@ +extends /templates/modal/new_model + +block modal-body-content + form.form + .form-group + label.control-label(for="name", data-i18n="general.name") Name + input#name.form-control(name="name", type="text") + .form-group + label.control-label(for="description") Description + input#description.form-control(name="description", type="text") + h4 Miscellaneous achievement keys + .radio + label + input(type="checkbox", name="queryOptions" id="misc-level-completion" value="misc-level-completion") + span.spl Level Completion + - var goals = level.get('goals'); + if goals && goals.length + h4 Base achievement on goals? + each goal in goals + .radio + label + input(type="checkbox", name="queryOptions" id="#{goal.id}" value="#{goal.id}") + span.spl= goal.name diff --git a/app/templates/editor/related-achievements.jade b/app/templates/editor/level/related-achievements.jade similarity index 100% rename from app/templates/editor/related-achievements.jade rename to app/templates/editor/level/related-achievements.jade diff --git a/app/views/editor/achievement/AchievementEditView.coffee b/app/views/editor/achievement/AchievementEditView.coffee index 0e42a6470..dfd58e3b8 100644 --- a/app/views/editor/achievement/AchievementEditView.coffee +++ b/app/views/editor/achievement/AchievementEditView.coffee @@ -3,6 +3,7 @@ template = require 'templates/editor/achievement/edit' Achievement = require 'models/Achievement' ConfirmModal = require 'views/modal/ConfirmModal' errors = require 'lib/errors' +app = require 'application' module.exports = class AchievementEditView extends RootView id: 'editor-achievement-edit-view' @@ -12,6 +13,7 @@ module.exports = class AchievementEditView extends RootView events: 'click #save-button': 'saveAchievement' 'click #recalculate-button': 'confirmRecalculation' + 'click #delete-button': 'confirmDeletion' subscriptions: 'save-new': 'saveAchievement' @@ -96,15 +98,26 @@ module.exports = class AchievementEditView extends RootView url = "/editor/achievement/#{@achievement.get('slug') or @achievement.id}" document.location.href = url - confirmRecalculation: (e) -> + confirmRecalculation: -> renderData = 'confirmTitle': 'Are you really sure?' 'confirmBody': 'This will trigger recalculation of the achievement for all users. Are you really sure you want to go down this path?' 'confirmDecline': 'Not really' 'confirmConfirm': 'Definitely' - confirmModal = new ConfirmModal(renderData) - confirmModal.onConfirm @recalculateAchievement + confirmModal = new ConfirmModal renderData + confirmModal.on 'confirm', @recalculateAchievement + @openModalView confirmModal + + confirmDeletion: -> + renderData = + 'confirmTitle': 'Are you really sure?' + 'confirmBody': 'This will completely delete the achievement, potentially breaking a lot of stuff you don\'t want breaking. Are you entirely sure?' + 'confirmDecline': 'Not really' + 'confirmConfirm': 'Definitely' + + confirmModal = new ConfirmModal renderData + confirmModal.on 'confirm', @deleteAchievement @openModalView confirmModal recalculateAchievement: => @@ -126,3 +139,24 @@ module.exports = class AchievementEditView extends RootView url: '/admin/earned.achievement/recalculate' type: 'POST' contentType: 'application/json' + + deleteAchievement: => + console.debug 'deleting' + $.ajax + type: 'DELETE' + success: -> + noty + timeout: 5000 + text: 'Aaaand it\'s gone.' + type: 'success' + layout: 'topCenter' + _.delay -> + app.router.navigate '/editor/achievement', trigger: true + , 500 + error: (jqXHR, status, error) -> + console.error jqXHR + timeout: 5000 + text: "Deleting achievement failed with error code #{jqXHR.status}" + type: 'error' + layout: 'topCenter' + url: "/db/achievement/#{@achievement.id}" diff --git a/app/views/editor/level/LevelEditView.coffee b/app/views/editor/level/LevelEditView.coffee index ce6eb47c2..8f3f506cb 100644 --- a/app/views/editor/level/LevelEditView.coffee +++ b/app/views/editor/level/LevelEditView.coffee @@ -15,7 +15,7 @@ SaveLevelModal = require './modals/SaveLevelModal' LevelForkView = require './modals/ForkLevelModal' SaveVersionModal = require 'views/modal/SaveVersionModal' PatchesView = require 'views/editor/PatchesView' -RelatedAchievementsView = require 'views/editor/RelatedAchievementsView' +RelatedAchievementsView = require 'views/editor/level/RelatedAchievementsView' VersionHistoryView = require './modals/LevelVersionsModal' ComponentDocsView = require 'views/docs/ComponentDocumentationView' @@ -75,7 +75,7 @@ module.exports = class LevelEditView extends RootView @insertSubView new ScriptsTabView world: @world, supermodel: @supermodel, files: @files @insertSubView new ComponentsTabView supermodel: @supermodel @insertSubView new SystemsTabView supermodel: @supermodel - @insertSubView new RelatedAchievementsView supermodel: @supermodel, relatedID: @level.id + @insertSubView new RelatedAchievementsView supermodel: @supermodel, level: @level @insertSubView new ComponentDocsView supermodel: @supermodel Backbone.Mediator.publish 'level-loaded', level: @level diff --git a/app/views/editor/RelatedAchievementsView.coffee b/app/views/editor/level/RelatedAchievementsView.coffee similarity index 73% rename from app/views/editor/RelatedAchievementsView.coffee rename to app/views/editor/level/RelatedAchievementsView.coffee index 774b550e7..598f9ddc3 100644 --- a/app/views/editor/RelatedAchievementsView.coffee +++ b/app/views/editor/level/RelatedAchievementsView.coffee @@ -1,8 +1,8 @@ CocoView = require 'views/kinds/CocoView' -template = require 'templates/editor/related-achievements' +template = require 'templates/editor/level/related-achievements' RelatedAchievementsCollection = require 'collections/RelatedAchievementsCollection' Achievement = require 'models/Achievement' -NewModelModal = require 'views/modal/NewModelModal' +NewAchievementModal = require './modals/NewAchievementModal' app = require 'application' module.exports = class RelatedAchievementsView extends CocoView @@ -15,7 +15,8 @@ module.exports = class RelatedAchievementsView extends CocoView constructor: (options) -> super options - @relatedID = options.relatedID + @level = options.level + @relatedID = @level.id @achievements = new RelatedAchievementsCollection @relatedID console.debug @achievements @supermodel.loadCollection @achievements, 'achievements' @@ -31,14 +32,10 @@ module.exports = class RelatedAchievementsView extends CocoView c.relatedID = @relatedID c - render: -> - console.debug 'rendering achievements' - super() - onNewAchievementSaved: (achievement) -> app.router.navigate('/editor/achievement/' + (achievement.get('slug') or achievement.id), {trigger: true}) makeNewAchievement: -> - modal = new NewModelModal model: Achievement, modelLabel: 'Achievement', properties: related: @relatedID - modal.once 'success', @onNewAchievementSaved + modal = new NewAchievementModal model: Achievement, modelLabel: 'Achievement', level: @level + modal.once 'model-created', @onNewAchievementSaved @openModalView modal diff --git a/app/views/editor/level/modals/NewAchievementModal.coffee b/app/views/editor/level/modals/NewAchievementModal.coffee new file mode 100644 index 000000000..a666bd239 --- /dev/null +++ b/app/views/editor/level/modals/NewAchievementModal.coffee @@ -0,0 +1,54 @@ +NewModelModal = require 'views/modal/NewModelModal' +template = require 'templates/editor/level/modal/new-achievement' +forms = require 'lib/forms' +Achievement = require 'models/Achievement' + +module.exports = class NewAchievementModal extends NewModelModal + id: 'new-achievement-modal' + template: template + plain: false + + constructor: (options) -> + super options + @level = options.level + + getRenderData: -> + c = super() + c.level = @level + console.debug 'level', c.level + c + + createQuery: -> + checked = @$el.find('[name=queryOptions]:checked') + checkedValues = ($(check).val() for check in checked) + subQueries = [] + for id in checkedValues + switch id + when 'misc-level-completion' + subQueries.push state: complete: true + else # It's a goal + q = state: goalStates: {} + q.state.goalStates[id] = {} + q.state.goalStates[id].status = 'success' + subQueries.push q + unless subQueries.length + query = {} + else if subQueries.length is 1 + query = subQueries[0] + else + query = $or: subQueries + query + + makeNewModel: -> + achievement = new Achievement + name = @$el.find('#name').val() + description = @$el.find('#description').val() + query = @createQuery() + + achievement.set 'name', name + achievement.set 'description', description + achievement.set 'query', query + achievement.set 'collection', 'level.sessions' + achievement.set 'userField', 'creator' + + achievement diff --git a/app/views/modal/ConfirmModal.coffee b/app/views/modal/ConfirmModal.coffee index 4749fc913..2ea188cf3 100644 --- a/app/views/modal/ConfirmModal.coffee +++ b/app/views/modal/ConfirmModal.coffee @@ -8,8 +8,8 @@ module.exports = class ConfirmModal extends ModalView closeOnConfirm: true events: - 'click #decline-button': 'doDecline' - 'click #confirm-button': 'doConfirm' + 'click #decline-button': 'onDecline' + 'click #confirm-button': 'onConfirm' constructor: (@renderData={}, options={}) -> super(options) @@ -21,10 +21,6 @@ module.exports = class ConfirmModal extends ModalView setRenderData: (@renderData) -> - onDecline: (@decline) -> + onDecline: -> @trigger 'decline' - onConfirm: (@confirm) -> - - doConfirm: -> @confirm() if @confirm - - doDecline: -> @decline() if @decline + onConfirm: -> @trigger 'confirm' diff --git a/app/views/modal/NewModelModal.coffee b/app/views/modal/NewModelModal.coffee index 9e7667207..9edd7c5f7 100644 --- a/app/views/modal/NewModelModal.coffee +++ b/app/views/modal/NewModelModal.coffee @@ -8,15 +8,15 @@ module.exports = class NewModelModal extends ModalView plain: false events: - 'click button.new-model-submit': 'makeNewModel' - 'submit form': 'makeNewModel' - 'shown.bs.modal #new-model-modal': 'focusOnName' + 'click button.new-model-submit': 'onModelSubmitted' + 'submit form': 'onModelSubmitted' constructor: (options) -> super options @model = options.model @modelLabel = options.modelLabel @properties = options.properties + $('#name').ready @focusOnName getRenderData: -> c = super() @@ -24,14 +24,19 @@ module.exports = class NewModelModal extends ModalView #c.newModelTitle = @newModelTitle c - makeNewModel: (e) -> - e.preventDefault() - name = @$el.find('#name').val() + makeNewModel: -> model = new @model + name = @$el.find('#name').val() model.set('name', name) if @model.schema.properties.permissions model.set 'permissions', [{access: 'owner', target: me.id}] model.set(key, prop) for key, prop of @properties if @properties? + model + + onModelSubmitted: (e) -> + console.debug 'on model submitted' + e.preventDefault() + model = @makeNewModel() res = model.save() return unless res @@ -43,6 +48,8 @@ module.exports = class NewModelModal extends ModalView #Backbone.Mediator.publish 'model-save-fail', model res.success => @$el.modal('hide') - @trigger 'success', model + @trigger 'model-created', model #Backbone.Mediator.publish 'model-save-success', model + focusOnName: (e) -> + $('#name').focus() # TODO Why isn't this working anymore.. It does get called diff --git a/server/achievements/achievement_handler.coffee b/server/achievements/achievement_handler.coffee index feed09f20..211068bd1 100644 --- a/server/achievements/achievement_handler.coffee +++ b/server/achievements/achievement_handler.coffee @@ -5,9 +5,11 @@ class AchievementHandler extends Handler modelClass: Achievement # Used to determine which properties requests may edit - editableProperties: ['name', 'query', 'worth', 'collection', 'description', 'userField', 'proportionalTo', 'icon', 'function', 'related', 'difficulty', 'category'] + editableProperties: ['name', 'query', 'worth', 'collection', 'description', 'userField', 'proportionalTo', 'icon', 'function', 'related', 'difficulty', 'category', 'recalculable'] + allowedMethods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'] jsonSchema = require '../../app/schemas/models/achievement.coffee' + hasAccess: (req) -> req.method is 'GET' or req.user?.isAdmin() @@ -22,4 +24,13 @@ class AchievementHandler extends Handler else super req, res + delete: (req, res, slugOrID) -> + return @sendUnauthorizedError res unless req.user?.isAdmin() + @getDocumentForIdOrSlug slugOrID, (err, document) => # Check first + return @sendDatabaseError(res, err) if err + return @sendNotFoundError(res) unless document? + document.remove (err, document) => + return @sendDatabaseError(res, err) if err + @sendNoContent res + module.exports = new AchievementHandler() diff --git a/server/achievements/earned_achievement_handler.coffee b/server/achievements/earned_achievement_handler.coffee index a0ad8618e..0926bcd28 100644 --- a/server/achievements/earned_achievement_handler.coffee +++ b/server/achievements/earned_achievement_handler.coffee @@ -71,21 +71,17 @@ class EarnedAchievementHandler extends Handler isRepeatable = achievement.get('proportionalTo')? model = mongoose.modelNameByCollection(achievement.get('collection')) - if not model? - log.error "Model with collection '#{achievement.get 'collection'}' doesn't exist." - return doneWithAchievement() + return doneWithAchievement new Error "Model with collection '#{achievement.get 'collection'}' doesn't exist." unless model? finalQuery = _.clone achievement.get 'query' finalQuery.$or = [{}, {}] # Allow both ObjectIDs or hex string IDs finalQuery.$or[0][achievement.userField] = userID finalQuery.$or[1][achievement.userField] = mongoose.Types.ObjectId userID - log.debug JSON.stringify finalQuery - model.findOne finalQuery, (err, something) -> return doneWithAchievement() if _.isEmpty something - log.debug "Matched an achievement: #{achievement.get 'name'} for #{user.get 'name'}" + #log.debug "Matched an achievement: #{achievement.get 'name'} for #{user.get 'name'}" earned = user: userID @@ -107,7 +103,7 @@ class EarnedAchievementHandler extends Handler EarnedAchievement.update {achievement:earned.achievement, user:earned.user}, earned, {upsert: true}, (err) -> doneWithAchievement err - ), saveUserPoints = -> + ), -> # Wrap up a user, save points # Since some achievements cannot be recalculated it's important to deduct the old amount of exp # and add the new amount, instead of just setting to the new amount return doneWithUser() unless newTotalPoints diff --git a/server/commons/Handler.coffee b/server/commons/Handler.coffee index b1dc28c48..72a83fd7a 100644 --- a/server/commons/Handler.coffee +++ b/server/commons/Handler.coffee @@ -57,7 +57,7 @@ module.exports = class Handler sendUnauthorizedError: (res) -> errors.forbidden(res) #TODO: rename sendUnauthorizedError to sendForbiddenError sendForbiddenError: (res) -> errors.forbidden(res) sendNotFoundError: (res, message) -> errors.notFound(res, message) - sendMethodNotAllowed: (res) -> errors.badMethod(res) + sendMethodNotAllowed: (res, message) -> errors.badMethod(res, @allowedMethods, message) sendBadInputError: (res, message) -> errors.badInput(res, message) sendDatabaseError: (res, err) -> return @sendError(res, err.code, err.response) if err.response and err.code @@ -79,8 +79,8 @@ module.exports = class Handler res.send 202, message res.end() - sendNoContent: (res, message) -> - res.send 204, message + sendNoContent: (res) -> + res.send 204 res.end() # generic handlers @@ -453,9 +453,9 @@ module.exports = class Handler res.send dict res.end() - delete: (req, res) -> @sendMethodNotAllowed res, @allowedMethods, 'DELETE not allowed.' + delete: (req, res) -> @sendMethodNotAllowed res, 'DELETE not allowed.' - head: (req, res) -> @sendMethodNotAllowed res, @allowedMethods, 'HEAD not allowed.' + head: (req, res) -> @sendMethodNotAllowed res, 'HEAD not allowed.' # This is not a Mongoose user projectionForUser: (req, model, ownerID) -> diff --git a/server/routes/db.coffee b/server/routes/db.coffee index 5b4ae3b17..4005684ed 100644 --- a/server/routes/db.coffee +++ b/server/routes/db.coffee @@ -38,7 +38,7 @@ module.exports.setup = (app) -> return handler.getByRelationship(req, res, parts[1..]...) if parts.length > 2 return handler.getById(req, res, parts[1]) if req.route.method is 'get' and parts[1]? return handler.patch(req, res, parts[1]) if req.route.method is 'patch' and parts[1]? - handler[req.route.method](req, res) + handler[req.route.method](req, res, parts[1..]...) catch error log.error("Error trying db method #{req.route.method} route #{parts} from #{name}: #{error}") log.error(error) diff --git a/test/server/functional/achievement.spec.coffee b/test/server/functional/achievement.spec.coffee index 9695515a3..8ac1ed9ef 100644 --- a/test/server/functional/achievement.spec.coffee +++ b/test/server/functional/achievement.spec.coffee @@ -7,6 +7,10 @@ unlockable = collection: 'level.sessions' query: "{\"level.original\":\"dungeon-arena\"}" userField: 'creator' + recalculable: true + +unlockable2 = _.clone unlockable +unlockable2.name = 'This one is obsolete' repeatable = name: 'Simulated' @@ -16,6 +20,7 @@ repeatable = query: "{\"simulatedBy\":{\"$gt\":0}}" userField: '_id' proportionalTo: 'simulatedBy' + recalculable: true diminishing = name: 'Simulated2' @@ -27,11 +32,12 @@ diminishing = function: kind: 'logarithmic' parameters: {a: 1, b: .5, c: .5, d: 1} + recalculable: true url = getURL('/db/achievement') describe 'Achievement', -> - allowHeader = 'GET, POST, PUT, PATCH' + allowHeader = 'GET, POST, PUT, PATCH, DELETE' it 'preparing test: deleting all Achievements first', (done) -> clearModels [Achievement, EarnedAchievement, LevelSession, User], (err) -> @@ -92,12 +98,18 @@ describe 'Achievement', -> expect(res.headers.allow).toBe(allowHeader) done() - it 'can\'t be requested with HTTP DEL method', (done) -> - loginJoe -> - request.del {uri: url + '/' + unlockable._id}, (err, res, body) -> - expect(res.statusCode).toBe(405) - expect(res.headers.allow).toBe(allowHeader) - done() + it 'allows admins to delete achievements using DELETE', (done) -> + loginAdmin -> + request.post {uri: url, json: unlockable2}, (err, res, body) -> + expect(res.statusCode).toBe(200) + unlockable2._id = body._id + + request.del {uri: url + '/' + unlockable2._id}, (err, res, body) -> + expect(res.statusCode).toBe(204) + + request.del {uri: url + '/' + unlockable2._id}, (err, res, body) -> + expect(res.statusCode).toBe(404) + done() it 'get schema', (done) -> request.get {uri: url + '/schema'}, (err, res, body) -> From e6569539dc51b3574c8a6891e93fe6e103a9d44e Mon Sep 17 00:00:00 2001 From: Ruben Vereecken Date: Fri, 8 Aug 2014 19:26:24 +0200 Subject: [PATCH 65/83] User page now uses the supermodel for user loading --- app/collections/EarnedAchievementCollection.coffee | 1 + app/lib/LevelBus.coffee | 1 - app/templates/user/achievements.jade | 3 +-- app/templates/user/home.jade | 2 +- app/views/modal/NewModelModal.coffee | 1 - app/views/user/MainUserView.coffee | 11 +++-------- server/plugins/achievements.coffee | 12 ++++++------ 7 files changed, 12 insertions(+), 19 deletions(-) diff --git a/app/collections/EarnedAchievementCollection.coffee b/app/collections/EarnedAchievementCollection.coffee index d091c36f6..82207afeb 100644 --- a/app/collections/EarnedAchievementCollection.coffee +++ b/app/collections/EarnedAchievementCollection.coffee @@ -6,3 +6,4 @@ module.exports = class EarnedAchievementCollection extends CocoCollection initialize: (userID) -> @url = "/db/user/#{userID}/achievements" + super() diff --git a/app/lib/LevelBus.coffee b/app/lib/LevelBus.coffee index 4f7cb583c..7ea0837c1 100644 --- a/app/lib/LevelBus.coffee +++ b/app/lib/LevelBus.coffee @@ -195,7 +195,6 @@ module.exports = class LevelBus extends Bus @saveSession() onNewGoalStates: ({goalStates})-> - console.debug arguments state = @session.get 'state' unless utils.kindaEqual state.goalStates, goalStates # Only save when goals really change state.goalStates = goalStates diff --git a/app/templates/user/achievements.jade b/app/templates/user/achievements.jade index 34ccebd8a..c5a7c2f4e 100644 --- a/app/templates/user/achievements.jade +++ b/app/templates/user/achievements.jade @@ -20,7 +20,6 @@ block append content - var imgURL = achievement.getLockedImageURL(); else - var imgURL = achievement.getImageURL(); - - console.log(locked); .col-lg-4.col-xs-12 include ../achievement_notify else if activeLayout === 'table' @@ -38,7 +37,7 @@ block append content tr td= achievement.get('name') td= achievement.get('description') - td= moment().format("MMMM Do YY", earnedAchievement.get('changed')) + td= moment().format("MMMM Do YYYY", earnedAchievement.get('changed')) if achievement.isRepeatable() td= earnedAchievement.get('achievedAmount') else diff --git a/app/templates/user/home.jade b/app/templates/user/home.jade index e32e6dc32..e9aedd463 100644 --- a/app/templates/user/home.jade +++ b/app/templates/user/home.jade @@ -124,7 +124,7 @@ block append content each achievement in earnedAchievements.models tr td= achievement.get('achievementName') - td= moment().format("MMMM Do YY", achievement.get('changed')) + td= moment().format("MMMM Do YYYY", achievement.get('changed')) if achievement.get('achievedAmount') td= achievement.get('achievedAmount') else diff --git a/app/views/modal/NewModelModal.coffee b/app/views/modal/NewModelModal.coffee index 9edd7c5f7..0f7a33a10 100644 --- a/app/views/modal/NewModelModal.coffee +++ b/app/views/modal/NewModelModal.coffee @@ -34,7 +34,6 @@ module.exports = class NewModelModal extends ModalView model onModelSubmitted: (e) -> - console.debug 'on model submitted' e.preventDefault() model = @makeNewModel() res = model.save() diff --git a/app/views/user/MainUserView.coffee b/app/views/user/MainUserView.coffee index cd70bb452..cf0913913 100644 --- a/app/views/user/MainUserView.coffee +++ b/app/views/user/MainUserView.coffee @@ -22,7 +22,6 @@ module.exports = class MainUserView extends UserView getRenderData: -> context = super() if @levelSessions and @levelSessions.loaded - console.debug 'yep sessions loaded' singlePlayerSessions = [] multiPlayerSessions = [] languageCounts = {} @@ -42,18 +41,14 @@ module.exports = class MainUserView extends UserView context.multiPlayerSessions = multiPlayerSessions context.favoriteLanguage = favoriteLanguage if @earnedAchievements and @earnedAchievements.loaded - console.debug 'earned achievements loaded' context.earnedAchievements = @earnedAchievements context onLoaded: -> - console.debug @earnedAchievements - console.debug @earnedAchievements?.loaded - if @user.loaded and not @earnedAchievements + if @user.loaded and not (@earnedAchievements or @levelSessions) @supermodel.resetProgress() - #@levelSessions = new LevelSessionsCollection @user.getSlugOrID() + @levelSessions = new LevelSessionsCollection @user.getSlugOrID() @earnedAchievements = new EarnedAchievementCollection @user.getSlugOrID() - #@supermodel.loadCollection @levelSessions, 'levelSessions' + @supermodel.loadCollection @levelSessions, 'levelSessions' @supermodel.loadCollection @earnedAchievements, 'earnedAchievements' - super() diff --git a/server/plugins/achievements.coffee b/server/plugins/achievements.coffee index fd1e7af7d..23b1aba56 100644 --- a/server/plugins/achievements.coffee +++ b/server/plugins/achievements.coffee @@ -38,9 +38,9 @@ AchievablePlugin = (schema, options) -> isRepeatable = achievement.get('proportionalTo')? alreadyAchieved = if isNew then false else LocalMongo.matchesQuery originalDocObj, query newlyAchieved = LocalMongo.matchesQuery(docObj, query) - log.debug 'isRepeatable: ' + isRepeatable - log.debug 'alreadyAchieved: ' + alreadyAchieved - log.debug 'newlyAchieved: ' + newlyAchieved + #log.debug 'isRepeatable: ' + isRepeatable + #log.debug 'alreadyAchieved: ' + alreadyAchieved + #log.debug 'newlyAchieved: ' + newlyAchieved userObjectID = doc.get(achievement.get('userField')) userID = if _.isObject userObjectID then userObjectID.toHexString() else userObjectID # Standardize! Use strings, not ObjectId's @@ -59,7 +59,7 @@ AchievablePlugin = (schema, options) -> log.error err if err? if isRepeatable - log.debug 'Upserting repeatable achievement called \'' + (achievement.get 'name') + '\' for ' + userID + #log.debug 'Upserting repeatable achievement called \'' + (achievement.get 'name') + '\' for ' + userID proportionalTo = achievement.get 'proportionalTo' originalAmount = if originalDocObj then util.getByPath(originalDocObj, proportionalTo) or 0 else 0 newAmount = docObj[proportionalTo] @@ -74,11 +74,11 @@ AchievablePlugin = (schema, options) -> return log.debug err if err? earnedPoints = earned.earnedPoints - log.debug earnedPoints + #log.debug earnedPoints wrapUp() else # not alreadyAchieved - log.debug 'Creating a new earned achievement called \'' + (achievement.get 'name') + '\' for ' + userID + #log.debug 'Creating a new earned achievement called \'' + (achievement.get 'name') + '\' for ' + userID earned.earnedPoints = worth (new EarnedAchievement(earned)).save (err, doc) -> return log.error err if err? From 672b0f54f6cd17ea51475ca04552ab1fe82633b7 Mon Sep 17 00:00:00 2001 From: Ruben Vereecken Date: Mon, 11 Aug 2014 14:11:26 +0200 Subject: [PATCH 66/83] Worked away our NotifyJS dependency - horrible lib --- app/application.coffee | 6 -- app/models/Achievement.coffee | 2 +- app/styles/achievements.sass | 33 +++---- app/styles/editor/achievement/edit.sass | 4 +- app/templates/achievement_notify.jade | 16 ---- .../achievements/achievement-popup.jade | 18 ++++ app/templates/editor/achievement/edit.jade | 2 +- app/templates/user/achievements.jade | 4 +- .../achievements/AchievementPopup.coffee | 89 +++++++++++++++++++ .../achievement/AchievementEditView.coffee | 13 +-- app/views/kinds/RootView.coffee | 66 +------------- .../achievement/AchievementGet.demo.coffee | 15 +--- vendor/scripts/notify-combined.min.js | 5 -- 13 files changed, 138 insertions(+), 135 deletions(-) delete mode 100644 app/templates/achievement_notify.jade create mode 100644 app/templates/achievements/achievement-popup.jade create mode 100644 app/views/achievements/AchievementPopup.coffee delete mode 100644 vendor/scripts/notify-combined.min.js diff --git a/app/application.coffee b/app/application.coffee index 7d471acd3..1b23abcbb 100644 --- a/app/application.coffee +++ b/app/application.coffee @@ -5,7 +5,6 @@ locale = require 'locale/locale' {me} = require 'lib/auth' Tracker = require 'lib/Tracker' CocoView = require 'views/kinds/CocoView' -AchievementNotify = require '../../templates/achievement_notify' marked.setOptions {gfm: true, sanitize: true, smartLists: true, breaks: false} @@ -40,11 +39,6 @@ Application = initialize: -> @facebookHandler = new FacebookHandler() @gplusHandler = new GPlusHandler() $(document).bind 'keydown', preventBackspace - $.notify.addStyle 'achievement-wood', html: $(AchievementNotify popup:true) - $.notify.addStyle 'achievement-stone', html: $(AchievementNotify popup:true) - $.notify.addStyle 'achievement-silver', html: $(AchievementNotify popup:true) - $.notify.addStyle 'achievement-gold', html: $(AchievementNotify popup:true) - $.notify.addStyle 'achievement-diamond', html: $(AchievementNotify popup:true) @linkedinHandler = new LinkedInHandler() preload(COMMON_FILES) $.i18n.init { diff --git a/app/models/Achievement.coffee b/app/models/Achievement.coffee index 74d7d55d9..3ae32aa84 100644 --- a/app/models/Achievement.coffee +++ b/app/models/Achievement.coffee @@ -22,7 +22,7 @@ module.exports = class Achievement extends CocoModel 4: 'achievement-gold' 5: 'achievement-diamond' - getNotifyStyle: -> Achievement.styleMapping[@get 'difficulty'] + getStyle: -> Achievement.styleMapping[@get 'difficulty'] @defaultImageURL: '/images/achievements/default.png' diff --git a/app/styles/achievements.sass b/app/styles/achievements.sass index 63d0e650e..2b539c910 100644 --- a/app/styles/achievements.sass +++ b/app/styles/achievements.sass @@ -1,12 +1,10 @@ @import 'bootstrap/variables' -.locked - // This used to be a grayscale filter but they're mad intensive to paint - .achievement-body + position: relative + .achievement-icon position: absolute - top: 0px .achievement-image width: 100% @@ -88,8 +86,7 @@ line-height: 1.3em max-height: 2.6em -.notifyjs-achievement-wood-base, .notifyjs-achievement-stone-base, .notifyjs-achievement-silver-base, -.notifyjs-achievement-gold-base, .notifyjs-achievement-diamond-base +.achievement-popup padding: 20px 0px cursor: default @@ -99,7 +96,7 @@ width: 200px height: 200px left: -140px - top: 0px + top: -20px .achievement-image img @@ -165,7 +162,7 @@ .achievement-icon background-size: 100% 100% !important -.notifyjs-achievement-wood-base, .achievement-wood +.achievement-wood &.locked .achievement-icon background: url("/images/achievements/border_wood_locked.png") no-repeat @@ -173,7 +170,7 @@ .achievement-icon background: url("/images/achievements/border_wood.png") no-repeat -.notifyjs-achievement-stone-base, .achievement-stone +.achievement-stone &.locked .achievement-icon background: url("/images/achievements/border_stone_locked.png") no-repeat @@ -181,7 +178,7 @@ .achievement-icon background: url("/images/achievements/border_stone.png") no-repeat -.notifyjs-achievement-silver-base, .achievement-silver +.achievement-silver &.locked .achievement-icon background: url("/images/achievements/border_silver_locked.png") no-repeat @@ -189,7 +186,7 @@ .achievement-icon background: url("/images/achievements/border_silver.png") no-repeat -.notifyjs-achievement-gold-base, .achievement-gold +.achievement-gold &.locked .achievement-icon background: url("/images/achievements/border_gold_locked.png") no-repeat @@ -197,7 +194,7 @@ .achievement-icon background: url("/images/achievements/border_gold.png") no-repeat -.notifyjs-achievement-diamond-base, .achievement-diamond +.achievement-diamond &.locked .achievement-icon background: url("/images/achievements/border_diamond_locked.png") no-repeat @@ -205,13 +202,13 @@ .achievement-icon background: url("/images/achievements/border_diamond.png") no-repeat -.exp-bar-accumulated +.xp-bar-old background-color: #680080 -.exp-bar-new +.xp-bar-new background-color: #0096ff -.exp-bar-left +.xp-bar-left background-color: #fffbfd .user-level @@ -232,3 +229,9 @@ .achievement-icon-small height: 18px + +// Achievement Popup +.achievement-popup-container + position: fixed + right: 0px + bottom: 0px diff --git a/app/styles/editor/achievement/edit.sass b/app/styles/editor/achievement/edit.sass index 68208ae3f..157353e9a 100644 --- a/app/styles/editor/achievement/edit.sass +++ b/app/styles/editor/achievement/edit.sass @@ -15,6 +15,6 @@ #achievement-view min-height: 200px + position: relative + padding-left: 200px - .notifyjs-container - clear: both diff --git a/app/templates/achievement_notify.jade b/app/templates/achievement_notify.jade deleted file mode 100644 index 79c5e5956..000000000 --- a/app/templates/achievement_notify.jade +++ /dev/null @@ -1,16 +0,0 @@ -div - - var addedClass = notifyClass + (locked === true ? ' locked' : '') - .clearfix.achievement-body(class=addedClass) - .achievement-icon - .achievement-image(data-notify-html="image") - if imgURL - img(src=imgURL) - .achievement-content - .achievement-title(data-notify-html="title") #{title} - p.achievement-description(data-notify-html="description") #{description} - - if popup - .progress-wrapper - span.user-level.badge(data-notify-html="level") - .progress-bar-wrapper(data-notify-html="progressBar") - .progress-bar-border diff --git a/app/templates/achievements/achievement-popup.jade b/app/templates/achievements/achievement-popup.jade new file mode 100644 index 000000000..ff889a68e --- /dev/null +++ b/app/templates/achievements/achievement-popup.jade @@ -0,0 +1,18 @@ +- var addedClass = style + (locked === true ? ' locked' : '') +.clearfix.achievement-body(class=addedClass) + .achievement-icon + .achievement-image + img(src=imgURL) + .achievement-content + .achievement-title= title + p.achievement-description= description + + if popup + .progress-wrapper + span.user-level.badge= level + .progress-bar-wrapper + .progress + .progress-bar.xp-bar-old(style="width:#{oldXPWidth}%" data-toggle="tooltip" data-placement="top" title="#{currentXP} XP in total") + .progress-bar.xp-bar-new(style="width:#{newXPWidth}%" data-toggle="tooltip" title="#{newXP} XP earned") + .progress-bar.xp-bar-left(style="width:#{leftXPWidth}%" data-toggle="tooltip" title="#{leftXP} XP until level #{nextLevel}") + .progress-bar-border diff --git a/app/templates/editor/achievement/edit.jade b/app/templates/editor/achievement/edit.jade index f3191b9bd..b3ebf8005 100644 --- a/app/templates/editor/achievement/edit.jade +++ b/app/templates/editor/achievement/edit.jade @@ -20,7 +20,7 @@ block content #achievement-treema - #achievement-view + #achievement-view.clearfix #achievement-view-inner hr diff --git a/app/templates/user/achievements.jade b/app/templates/user/achievements.jade index c5a7c2f4e..b84b63e17 100644 --- a/app/templates/user/achievements.jade +++ b/app/templates/user/achievements.jade @@ -14,14 +14,14 @@ block append content - var title = achievement.get('name'); - var description = achievement.get('description'); - var locked = ! achievement.get('unlocked'); - - var notifyClass = achievement.getNotifyStyle() + - var style = achievement.getStyle() - var imgURL = achievement.getImageURL(); if locked - var imgURL = achievement.getLockedImageURL(); else - var imgURL = achievement.getImageURL(); .col-lg-4.col-xs-12 - include ../achievement_notify + include ../achievements/achievement-popup else if activeLayout === 'table' .table-layout if earnedAchievements.length diff --git a/app/views/achievements/AchievementPopup.coffee b/app/views/achievements/AchievementPopup.coffee new file mode 100644 index 000000000..e9709936a --- /dev/null +++ b/app/views/achievements/AchievementPopup.coffee @@ -0,0 +1,89 @@ +CocoView = require 'views/kinds/CocoView' +template = require 'templates/achievements/achievement-popup' +User = require '../../models/User' +Achievement = require '../../models/Achievement' + +module.exports = class AchievementPopup extends CocoView + className: 'achievement-popup' + template: template + + constructor: (options) -> + @achievement = options.achievement + @earnedAchievement = options.earnedAchievement + @container = options.container or @getContainer() + @popup = options.container + @popup ?= true + super options + console.debug 'Created an AchievementPopup', @$el + + @render() + + calculateData: -> + currentLevel = me.level() + nextLevel = currentLevel + 1 + currentLevelExp = User.expForLevel(currentLevel) + nextLevelXP = User.expForLevel(nextLevel) + totalExpNeeded = nextLevelXP - currentLevelExp + expFunction = @achievement.getExpFunction() + currentXP = me.get 'points' + if @achievement.isRepeatable() + achievedXP = expFunction(@earnedAchievement.get('previouslyAchievedAmount')) * @achievement.get('worth') if @achievement.isRepeatable() + else + achievedXP = @achievement.get 'worth' + previousXP = currentXP - achievedXP + leveledUp = currentXP - achievedXP < currentLevelExp + #console.debug 'Leveled up' if leveledUp + alreadyAchievedPercentage = 100 * (previousXP - currentLevelExp) / totalExpNeeded + alreadyAchievedPercentage = 0 if alreadyAchievedPercentage < 0 # In case of level up + newlyAchievedPercentage = if leveledUp then 100 * (currentXP - currentLevelExp) / totalExpNeeded else 100 * achievedXP / totalExpNeeded + + #console.debug "Current level is #{currentLevel} (#{currentLevelExp} xp), next level is #{nextLevel} (#{nextLevelXP} xp)." + #console.debug "Need a total of #{nextLevelXP - currentLevelExp}, already had #{previousXP} and just now earned #{achievedXP} totalling on #{currentXP}" + + alreadyAchievedBar = $("

") + newlyAchievedBar = $("
") + emptyBar = $("
") + progressBar = $('
').append(alreadyAchievedBar).append(newlyAchievedBar).append(emptyBar) + + alreadyAchievedBar.tooltip(title: "#{currentXP} XP in total") + newlyAchievedBar.tooltip(title: "#{achievedXP} XP earned") + emptyBar.tooltip(title: "#{nextLevelXP - currentXP} XP until level #{nextLevel}") + + data = + title: @achievement.get('name') + imgURL: @achievement.getImageURL() + description: @achievement.get('description') + level: currentLevel + currentXP: currentXP + newXP: achievedXP + leftXP: nextLevelXP - currentXP + oldXPWidth: alreadyAchievedPercentage + newXPWidth: newlyAchievedPercentage + leftXPWidth: 100 - newlyAchievedPercentage - alreadyAchievedPercentage + + getRenderData: -> + c = super() + _.extend c, @calculateData() + c.style = @achievement.getStyle() + c.popup = true + c + + render: -> + console.debug 'render achievement popup' + super() + @container.append @$el + + getContainer: -> + unless @container + @container = $('.achievement-popup-container') + unless @container.length + $('body').append('
') + @container = $('.achievement-popup-container') + @container + + afterRender: -> + super() + _.delay @initializeTooltips, 1000 # TODO this could be smoother + + initializeTooltips: -> + $('.progress-bar').tooltip() diff --git a/app/views/editor/achievement/AchievementEditView.coffee b/app/views/editor/achievement/AchievementEditView.coffee index dfd58e3b8..7872daa54 100644 --- a/app/views/editor/achievement/AchievementEditView.coffee +++ b/app/views/editor/achievement/AchievementEditView.coffee @@ -1,6 +1,7 @@ RootView = require 'views/kinds/RootView' template = require 'templates/editor/achievement/edit' Achievement = require 'models/Achievement' +AchievementPopup = require 'views/achievements/AchievementPopup' ConfirmModal = require 'views/modal/ConfirmModal' errors = require 'lib/errors' app = require 'application' @@ -60,7 +61,6 @@ module.exports = class AchievementEditView extends RootView @pushChangesToPreview() pushChangesToPreview: => - $('.notifyjs-wrapper').trigger 'notify-hide' if @treema? for key, value of @treema.data @@ -69,17 +69,8 @@ module.exports = class AchievementEditView extends RootView earned = earnedPoints: @achievement.get 'worth' - data = @createNotifyData @achievement, earned - options = - style: @achievement.getNotifyStyle() - autoHide: false - clickToHide: false - arrowShow: false - elementPosition: 'bottom center' - hideDuration: 0 - showDuration: 0 + popup = new AchievementPopup achievement: @achievement, earnedAchievement:earned, popup: false, container: $('#achievement-view') - $('#achievement-view-inner').notify data, options openSaveModal: -> 'Maybe later' # TODO patch patch patch diff --git a/app/views/kinds/RootView.coffee b/app/views/kinds/RootView.coffee index 0b636c045..a5dac5e7e 100644 --- a/app/views/kinds/RootView.coffee +++ b/app/views/kinds/RootView.coffee @@ -6,8 +6,7 @@ CocoView = require './CocoView' {logoutUser, me} = require('lib/auth') locale = require 'locale/locale' -Achievement = require '../../models/Achievement' -User = require '../../models/User' +AchievementPopup = require 'views/achievements/AchievementPopup' utils = require 'lib/utils' # TODO remove @@ -33,69 +32,8 @@ module.exports = class RootView extends CocoView subscriptions: 'achievements:new': 'handleNewAchievements' - createNotifyData: (achievement, earnedAchievement) -> - currentLevel = me.level() - nextLevel = currentLevel + 1 - currentLevelExp = User.expForLevel(currentLevel) - nextLevelExp = User.expForLevel(nextLevel) - totalExpNeeded = nextLevelExp - currentLevelExp - expFunction = achievement.getExpFunction() - currentExp = me.get('points') - if achievement.isRepeatable() - achievedExp = expFunction(earnedAchievement.get('previouslyAchievedAmount')) * achievement.get('worth') if achievement.isRepeatable() - else - achievedExp = achievement.get 'worth' - previousExp = currentExp - achievedExp - leveledUp = currentExp - achievedExp < currentLevelExp - #console.debug 'Leveled up' if leveledUp - alreadyAchievedPercentage = 100 * (previousExp - currentLevelExp) / totalExpNeeded - alreadyAchievedPercentage = 0 if alreadyAchievedPercentage < 0 # In case of level up - newlyAchievedPercentage = if leveledUp then 100 * (currentExp - currentLevelExp) / totalExpNeeded else 100 * achievedExp / totalExpNeeded - - #console.debug "Current level is #{currentLevel} (#{currentLevelExp} xp), next level is #{nextLevel} (#{nextLevelExp} xp)." - #console.debug "Need a total of #{nextLevelExp - currentLevelExp}, already had #{previousExp} and just now earned #{achievedExp} totalling on #{currentExp}" - - alreadyAchievedBar = $("
") - newlyAchievedBar = $("
") - emptyBar = $("
") - progressBar = $('
').append(alreadyAchievedBar).append(newlyAchievedBar).append(emptyBar) - - alreadyAchievedBar.tooltip(title: "#{currentExp} XP in total") - newlyAchievedBar.tooltip(title: "#{achievedExp} XP earned") - emptyBar.tooltip(title: "#{nextLevelExp - currentExp} XP until level #{nextLevel}") - - barBorder = $('') - - data = - title: achievement.get('name') - image: $("") - description: achievement.get('description') - progressBar: progressBar - earnedExp: "+ #{achievedExp} XP" - level: currentLevel - barBorder: barBorder - - data - showNewAchievement: (achievement, earnedAchievement) -> - data = @createNotifyData achievement, earnedAchievement - options = - autoHideDelay: 5000 - elementPosition: 'top left' - globalPosition: 'bottom right' - showDuration: 0 - style: achievement.getNotifyStyle() - autoHide: false - clickToHide: true - - unless @timeout? - $.notify data, options - @timeout = 2000 - else - setTimeout -> - $.notify data, options - @timeout += 2000 - , @timeout + popup = new AchievementPopup achievement: achievement, earnedAchievement: earnedAchievement handleNewAchievements: (earnedAchievements) -> _.each earnedAchievements.models, (earnedAchievement) => diff --git a/test/demo/views/achievement/AchievementGet.demo.coffee b/test/demo/views/achievement/AchievementGet.demo.coffee index 0b8fb2e04..0154390c5 100644 --- a/test/demo/views/achievement/AchievementGet.demo.coffee +++ b/test/demo/views/achievement/AchievementGet.demo.coffee @@ -17,17 +17,8 @@ module.exports = -> unlockable = new Achievement unlockableObj earnedUnlockable = new EarnedAchievement earnedUnlockableObj - console.log currentView - data = currentView.createNotifyData unlockable, earnedUnlockable - options = - autoHideDelay: 10000 - globalPosition: 'bottom right' - showDuration: 400 - style: 'achievement-silver' - autoHide: false - clickToHide: false - - $.notify data, options - view = new RootView view.render() + + view.showNewAchievement unlockable, earnedUnlockable + view diff --git a/vendor/scripts/notify-combined.min.js b/vendor/scripts/notify-combined.min.js deleted file mode 100644 index bafa7af50..000000000 --- a/vendor/scripts/notify-combined.min.js +++ /dev/null @@ -1,5 +0,0 @@ -/** Notify.js - v0.3.1 - 2014/02/06 - * http://notifyjs.com/ - * Copyright (c) 2014 Jaime Pillora - MIT - */ -(function(t,i,n,e){"use strict";var o,r,s,a,l,h,c,p,u,d,f,A,m,w,g,y,b,v,x,C,S,E,M,k,H,D,F,T=[].indexOf||function(t){for(var i=0,n=this.length;n>i;i++)if(i in this&&this[i]===t)return i;return-1};S="notify",C=S+"js",s=S+"!blank",M={t:"top",m:"middle",b:"bottom",l:"left",c:"center",r:"right"},m=["l","c","r"],F=["t","m","b"],b=["t","b","l","r"],v={t:"b",m:null,b:"t",l:"r",c:null,r:"l"},x=function(t){var i;return i=[],n.each(t.split(/\W+/),function(t,n){var o;return o=n.toLowerCase().charAt(0),M[o]?i.push(o):e}),i},D={},a={name:"core",html:'
\n
\n
\n
',css:"."+C+"-corner {\n position: fixed;\n margin: 5px;\n z-index: 1050;\n}\n\n."+C+"-corner ."+C+"-wrapper,\n."+C+"-corner ."+C+"-container {\n position: relative;\n display: block;\n height: inherit;\n width: inherit;\n margin: 3px;\n}\n\n."+C+"-wrapper {\n z-index: 1;\n position: absolute;\n display: inline-block;\n height: 0;\n width: 0;\n}\n\n."+C+"-container {\n display: none;\n z-index: 1;\n position: absolute;\n cursor: pointer;\n}\n\n[data-notify-text],[data-notify-html] {\n position: relative;\n}\n\n."+C+"-arrow {\n position: absolute;\n z-index: 2;\n width: 0;\n height: 0;\n}"},H={"border-radius":["-webkit-","-moz-"]},f=function(t){return D[t]},r=function(i,e){var o,r,s,a;if(!i)throw"Missing Style name";if(!e)throw"Missing Style definition";if(!e.html)throw"Missing Style HTML";return(null!=(a=D[i])?a.cssElem:void 0)&&(t.console&&console.warn(""+S+": overwriting style '"+i+"'"),D[i].cssElem.remove()),e.name=i,D[i]=e,o="",e.classes&&n.each(e.classes,function(t,i){return o+="."+C+"-"+e.name+"-"+t+" {\n",n.each(i,function(t,i){return H[t]&&n.each(H[t],function(n,e){return o+=" "+e+t+": "+i+";\n"}),o+=" "+t+": "+i+";\n"}),o+="}\n"}),e.css&&(o+="/* styles for "+e.name+" */\n"+e.css),o&&(e.cssElem=y(o),e.cssElem.attr("id","notify-"+e.name)),s={},r=n(e.html),u("html",r,s),u("text",r,s),e.fields=s},y=function(t){var i;i=l("style"),i.attr("type","text/css"),n("head").append(i);try{i.html(t)}catch(e){i[0].styleSheet.cssText=t}return i},u=function(t,i,e){var o;return"html"!==t&&(t="text"),o="data-notify-"+t,p(i,"["+o+"]").each(function(){var i;return i=n(this).attr(o),i||(i=s),e[i]=t})},p=function(t,i){return t.is(i)?t:t.find(i)},E={clickToHide:!0,autoHide:!0,autoHideDelay:5e3,arrowShow:!0,arrowSize:5,breakNewLines:!0,elementPosition:"bottom",globalPosition:"top right",style:"bootstrap",className:"error",showAnimation:"slideDown",showDuration:400,hideAnimation:"slideUp",hideDuration:200,gap:5},g=function(t,i){var e;return e=function(){},e.prototype=t,n.extend(!0,new e,i)},h=function(t){return n.extend(E,t)},l=function(t){return n("<"+t+">")},A={},d=function(t){var i;return t.is("[type=radio]")&&(i=t.parents("form:first").find("[type=radio]").filter(function(i,e){return n(e).attr("name")===t.attr("name")}),t=i.first()),t},w=function(t,i,n){var o,r;if("string"==typeof n)n=parseInt(n,10);else if("number"!=typeof n)return;if(!isNaN(n))return o=M[v[i.charAt(0)]],r=i,t[o]!==e&&(i=M[o.charAt(0)],n=-n),t[i]===e?t[i]=n:t[i]+=n,null},k=function(t,i,n){if("l"===t||"t"===t)return 0;if("c"===t||"m"===t)return n/2-i/2;if("r"===t||"b"===t)return n-i;throw"Invalid alignment"},c=function(t){return c.e=c.e||l("div"),c.e.text(t).html()},o=function(){function t(t,i,e){"string"==typeof e&&(e={className:e}),this.options=g(E,n.isPlainObject(e)?e:{}),this.loadHTML(),this.wrapper=n(a.html),this.wrapper.data(C,this),this.arrow=this.wrapper.find("."+C+"-arrow"),this.container=this.wrapper.find("."+C+"-container"),this.container.append(this.userContainer),t&&t.length&&(this.elementType=t.attr("type"),this.originalElement=t,this.elem=d(t),this.elem.data(C,this),this.elem.before(this.wrapper)),this.container.hide(),this.run(i)}return t.prototype.loadHTML=function(){var t;return t=this.getStyle(),this.userContainer=n(t.html),this.userFields=t.fields},t.prototype.show=function(t,i){var n,o,r,s,a,l=this;if(o=function(){return t||l.elem||l.destroy(),i?i():e},a=this.container.parent().parents(":hidden").length>0,r=this.container.add(this.arrow),n=[],a&&t)s="show";else if(a&&!t)s="hide";else if(!a&&t)s=this.options.showAnimation,n.push(this.options.showDuration);else{if(a||t)return o();s=this.options.hideAnimation,n.push(this.options.hideDuration)}return n.push(o),r[s].apply(r,n)},t.prototype.setGlobalPosition=function(){var t,i,e,o,r,s,a,h;return h=this.getPosition(),a=h[0],s=h[1],r=M[a],t=M[s],o=a+"|"+s,i=A[o],i||(i=A[o]=l("div"),e={},e[r]=0,"middle"===t?e.top="45%":"center"===t?e.left="45%":e[t]=0,i.css(e).addClass(""+C+"-corner"),n("body").append(i)),i.prepend(this.wrapper)},t.prototype.setElementPosition=function(){var t,i,o,r,s,a,l,h,c,p,u,d,f,A,g,y,x,C,S,E,H,D,z,Q,B,R,N,P,U;for(z=this.getPosition(),E=z[0],C=z[1],S=z[2],u=this.elem.position(),h=this.elem.outerHeight(),d=this.elem.outerWidth(),c=this.elem.innerHeight(),p=this.elem.innerWidth(),Q=this.wrapper.position(),s=this.container.height(),a=this.container.width(),A=M[E],y=v[E],x=M[y],l={},l[x]="b"===E?h:"r"===E?d:0,w(l,"top",u.top-Q.top),w(l,"left",u.left-Q.left),U=["top","left"],B=0,N=U.length;N>B;B++)H=U[B],g=parseInt(this.elem.css("margin-"+H),10),g&&w(l,H,g);if(f=Math.max(0,this.options.gap-(this.options.arrowShow?o:0)),w(l,x,f),this.options.arrowShow){for(o=this.options.arrowSize,i=n.extend({},l),t=this.userContainer.css("border-color")||this.userContainer.css("background-color")||"white",R=0,P=b.length;P>R;R++)H=b[R],D=M[H],H!==y&&(r=D===A?t:"transparent",i["border-"+D]=""+o+"px solid "+r);w(l,M[y],o),T.call(b,C)>=0&&w(i,M[C],2*o)}else this.arrow.hide();return T.call(F,E)>=0?(w(l,"left",k(C,a,d)),i&&w(i,"left",k(C,o,p))):T.call(m,E)>=0&&(w(l,"top",k(C,s,h)),i&&w(i,"top",k(C,o,c))),this.container.is(":visible")&&(l.display="block"),this.container.removeAttr("style").css(l),i?this.arrow.removeAttr("style").css(i):e},t.prototype.getPosition=function(){var t,i,n,e,o,r,s,a;if(i=this.options.position||(this.elem?this.options.elementPosition:this.options.globalPosition),t=x(i),0===t.length&&(t[0]="b"),n=t[0],0>T.call(b,n))throw"Must be one of ["+b+"]";return(1===t.length||(e=t[0],T.call(F,e)>=0&&(o=t[1],0>T.call(m,o)))||(r=t[0],T.call(m,r)>=0&&(s=t[1],0>T.call(F,s))))&&(t[1]=(a=t[0],T.call(m,a)>=0?"m":"l")),2===t.length&&(t[2]=t[1]),t},t.prototype.getStyle=function(t){var i;if(t||(t=this.options.style),t||(t="default"),i=D[t],!i)throw"Missing style: "+t;return i},t.prototype.updateClasses=function(){var t,i;return t=["base"],n.isArray(this.options.className)?t=t.concat(this.options.className):this.options.className&&t.push(this.options.className),i=this.getStyle(),t=n.map(t,function(t){return""+C+"-"+i.name+"-"+t}).join(" "),this.userContainer.attr("class",t)},t.prototype.run=function(t,i){var o,r,a,l,h,u=this;if(n.isPlainObject(i)?n.extend(this.options,i):"string"===n.type(i)&&(this.options.className=i),this.container&&!t)return this.show(!1),e;if(this.container||t){r={},n.isPlainObject(t)?r=t:r[s]=t;for(a in r)o=r[a],l=this.userFields[a],l&&("text"===l&&(o=c(o),this.options.breakNewLines&&(o=o.replace(/\n/g,"
"))),h=a===s?"":"="+a,p(this.userContainer,"[data-notify-"+l+h+"]").html(o));return this.updateClasses(),this.elem?this.setElementPosition():this.setGlobalPosition(),this.show(!0),this.options.autoHide?(clearTimeout(this.autohideTimer),this.autohideTimer=setTimeout(function(){return u.show(!1)},this.options.autoHideDelay)):e}},t.prototype.destroy=function(){return this.wrapper.remove()},t}(),n[S]=function(t,i,e){return t&&t.nodeName||t.jquery?n(t)[S](i,e):(e=i,i=t,new o(null,i,e)),t},n.fn[S]=function(t,i){return n(this).each(function(){var e;return e=d(n(this)).data(C),e?e.run(t,i):new o(n(this),t,i)}),this},n.extend(n[S],{defaults:h,addStyle:r,pluginOptions:E,getStyle:f,insertCSS:y}),n(function(){return y(a.css).attr("id","core-notify"),n(i).on("click notify-hide","."+C+"-wrapper",function(t){var i;return i=n(this).data(C),i&&(i.options.clickToHide||"notify-hide"===t.type)?i.show(!1):e})})})(window,document,jQuery),$.notify.addStyle("bootstrap",{html:"
\n\n
",classes:{base:{"font-weight":"bold",padding:"8px 15px 8px 14px","text-shadow":"0 1px 0 rgba(255, 255, 255, 0.5)","background-color":"#fcf8e3",border:"1px solid #fbeed5","border-radius":"4px","white-space":"nowrap","padding-left":"25px","background-repeat":"no-repeat","background-position":"3px 7px"},error:{color:"#B94A48","background-color":"#F2DEDE","border-color":"#EED3D7","background-image":"url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAtRJREFUeNqkVc1u00AQHq+dOD+0poIQfkIjalW0SEGqRMuRnHos3DjwAH0ArlyQeANOOSMeAA5VjyBxKBQhgSpVUKKQNGloFdw4cWw2jtfMOna6JOUArDTazXi/b3dm55socPqQhFka++aHBsI8GsopRJERNFlY88FCEk9Yiwf8RhgRyaHFQpPHCDmZG5oX2ui2yilkcTT1AcDsbYC1NMAyOi7zTX2Agx7A9luAl88BauiiQ/cJaZQfIpAlngDcvZZMrl8vFPK5+XktrWlx3/ehZ5r9+t6e+WVnp1pxnNIjgBe4/6dAysQc8dsmHwPcW9C0h3fW1hans1ltwJhy0GxK7XZbUlMp5Ww2eyan6+ft/f2FAqXGK4CvQk5HueFz7D6GOZtIrK+srupdx1GRBBqNBtzc2AiMr7nPplRdKhb1q6q6zjFhrklEFOUutoQ50xcX86ZlqaZpQrfbBdu2R6/G19zX6XSgh6RX5ubyHCM8nqSID6ICrGiZjGYYxojEsiw4PDwMSL5VKsC8Yf4VRYFzMzMaxwjlJSlCyAQ9l0CW44PBADzXhe7xMdi9HtTrdYjFYkDQL0cn4Xdq2/EAE+InCnvADTf2eah4Sx9vExQjkqXT6aAERICMewd/UAp/IeYANM2joxt+q5VI+ieq2i0Wg3l6DNzHwTERPgo1ko7XBXj3vdlsT2F+UuhIhYkp7u7CarkcrFOCtR3H5JiwbAIeImjT/YQKKBtGjRFCU5IUgFRe7fF4cCNVIPMYo3VKqxwjyNAXNepuopyqnld602qVsfRpEkkz+GFL1wPj6ySXBpJtWVa5xlhpcyhBNwpZHmtX8AGgfIExo0ZpzkWVTBGiXCSEaHh62/PoR0p/vHaczxXGnj4bSo+G78lELU80h1uogBwWLf5YlsPmgDEd4M236xjm+8nm4IuE/9u+/PH2JXZfbwz4zw1WbO+SQPpXfwG/BBgAhCNZiSb/pOQAAAAASUVORK5CYII=)"},success:{color:"#468847","background-color":"#DFF0D8","border-color":"#D6E9C6","background-image":"url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAutJREFUeNq0lctPE0Ecx38zu/RFS1EryqtgJFA08YCiMZIAQQ4eRG8eDGdPJiYeTIwHTfwPiAcvXIwXLwoXPaDxkWgQ6islKlJLSQWLUraPLTv7Gme32zoF9KSTfLO7v53vZ3d/M7/fIth+IO6INt2jjoA7bjHCJoAlzCRw59YwHYjBnfMPqAKWQYKjGkfCJqAF0xwZjipQtA3MxeSG87VhOOYegVrUCy7UZM9S6TLIdAamySTclZdYhFhRHloGYg7mgZv1Zzztvgud7V1tbQ2twYA34LJmF4p5dXF1KTufnE+SxeJtuCZNsLDCQU0+RyKTF27Unw101l8e6hns3u0PBalORVVVkcaEKBJDgV3+cGM4tKKmI+ohlIGnygKX00rSBfszz/n2uXv81wd6+rt1orsZCHRdr1Imk2F2Kob3hutSxW8thsd8AXNaln9D7CTfA6O+0UgkMuwVvEFFUbbAcrkcTA8+AtOk8E6KiQiDmMFSDqZItAzEVQviRkdDdaFgPp8HSZKAEAL5Qh7Sq2lIJBJwv2scUqkUnKoZgNhcDKhKg5aH+1IkcouCAdFGAQsuWZYhOjwFHQ96oagWgRoUov1T9kRBEODAwxM2QtEUl+Wp+Ln9VRo6BcMw4ErHRYjH4/B26AlQoQQTRdHWwcd9AH57+UAXddvDD37DmrBBV34WfqiXPl61g+vr6xA9zsGeM9gOdsNXkgpEtTwVvwOklXLKm6+/p5ezwk4B+j6droBs2CsGa/gNs6RIxazl4Tc25mpTgw/apPR1LYlNRFAzgsOxkyXYLIM1V8NMwyAkJSctD1eGVKiq5wWjSPdjmeTkiKvVW4f2YPHWl3GAVq6ymcyCTgovM3FzyRiDe2TaKcEKsLpJvNHjZgPNqEtyi6mZIm4SRFyLMUsONSSdkPeFtY1n0mczoY3BHTLhwPRy9/lzcziCw9ACI+yql0VLzcGAZbYSM5CCSZg1/9oc/nn7+i8N9p/8An4JMADxhH+xHfuiKwAAAABJRU5ErkJggg==)"},info:{color:"#3A87AD","background-color":"#D9EDF7","border-color":"#BCE8F1","background-image":"url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3QYFAhkSsdes/QAAA8dJREFUOMvVlGtMW2UYx//POaWHXg6lLaW0ypAtw1UCgbniNOLcVOLmAjHZolOYlxmTGXVZdAnRfXQm+7SoU4mXaOaiZsEpC9FkiQs6Z6bdCnNYruM6KNBw6YWewzl9z+sHImEWv+vz7XmT95f/+3/+7wP814v+efDOV3/SoX3lHAA+6ODeUFfMfjOWMADgdk+eEKz0pF7aQdMAcOKLLjrcVMVX3xdWN29/GhYP7SvnP0cWfS8caSkfHZsPE9Fgnt02JNutQ0QYHB2dDz9/pKX8QjjuO9xUxd/66HdxTeCHZ3rojQObGQBcuNjfplkD3b19Y/6MrimSaKgSMmpGU5WevmE/swa6Oy73tQHA0Rdr2Mmv/6A1n9w9suQ7097Z9lM4FlTgTDrzZTu4StXVfpiI48rVcUDM5cmEksrFnHxfpTtU/3BFQzCQF/2bYVoNbH7zmItbSoMj40JSzmMyX5qDvriA7QdrIIpA+3cdsMpu0nXI8cV0MtKXCPZev+gCEM1S2NHPvWfP/hL+7FSr3+0p5RBEyhEN5JCKYr8XnASMT0xBNyzQGQeI8fjsGD39RMPk7se2bd5ZtTyoFYXftF6y37gx7NeUtJJOTFlAHDZLDuILU3j3+H5oOrD3yWbIztugaAzgnBKJuBLpGfQrS8wO4FZgV+c1IxaLgWVU0tMLEETCos4xMzEIv9cJXQcyagIwigDGwJgOAtHAwAhisQUjy0ORGERiELgG4iakkzo4MYAxcM5hAMi1WWG1yYCJIcMUaBkVRLdGeSU2995TLWzcUAzONJ7J6FBVBYIggMzmFbvdBV44Corg8vjhzC+EJEl8U1kJtgYrhCzgc/vvTwXKSib1paRFVRVORDAJAsw5FuTaJEhWM2SHB3mOAlhkNxwuLzeJsGwqWzf5TFNdKgtY5qHp6ZFf67Y/sAVadCaVY5YACDDb3Oi4NIjLnWMw2QthCBIsVhsUTU9tvXsjeq9+X1d75/KEs4LNOfcdf/+HthMnvwxOD0wmHaXr7ZItn2wuH2SnBzbZAbPJwpPx+VQuzcm7dgRCB57a1uBzUDRL4bfnI0RE0eaXd9W89mpjqHZnUI5Hh2l2dkZZUhOqpi2qSmpOmZ64Tuu9qlz/SEXo6MEHa3wOip46F1n7633eekV8ds8Wxjn37Wl63VVa+ej5oeEZ/82ZBETJjpJ1Rbij2D3Z/1trXUvLsblCK0XfOx0SX2kMsn9dX+d+7Kf6h8o4AIykuffjT8L20LU+w4AZd5VvEPY+XpWqLV327HR7DzXuDnD8r+ovkBehJ8i+y8YAAAAASUVORK5CYII=)"},warn:{color:"#C09853","background-color":"#FCF8E3","border-color":"#FBEED5","background-image":"url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAMAAAC6V+0/AAABJlBMVEXr6eb/2oD/wi7/xjr/0mP/ykf/tQD/vBj/3o7/uQ//vyL/twebhgD/4pzX1K3z8e349vK6tHCilCWbiQymn0jGworr6dXQza3HxcKkn1vWvV/5uRfk4dXZ1bD18+/52YebiAmyr5S9mhCzrWq5t6ufjRH54aLs0oS+qD751XqPhAybhwXsujG3sm+Zk0PTwG6Shg+PhhObhwOPgQL4zV2nlyrf27uLfgCPhRHu7OmLgAafkyiWkD3l49ibiAfTs0C+lgCniwD4sgDJxqOilzDWowWFfAH08uebig6qpFHBvH/aw26FfQTQzsvy8OyEfz20r3jAvaKbhgG9q0nc2LbZxXanoUu/u5WSggCtp1anpJKdmFz/zlX/1nGJiYmuq5Dx7+sAAADoPUZSAAAAAXRSTlMAQObYZgAAAAFiS0dEAIgFHUgAAAAJcEhZcwAACxMAAAsTAQCanBgAAAAHdElNRQfdBgUBGhh4aah5AAAAlklEQVQY02NgoBIIE8EUcwn1FkIXM1Tj5dDUQhPU502Mi7XXQxGz5uVIjGOJUUUW81HnYEyMi2HVcUOICQZzMMYmxrEyMylJwgUt5BljWRLjmJm4pI1hYp5SQLGYxDgmLnZOVxuooClIDKgXKMbN5ggV1ACLJcaBxNgcoiGCBiZwdWxOETBDrTyEFey0jYJ4eHjMGWgEAIpRFRCUt08qAAAAAElFTkSuQmCC)"}}}); \ No newline at end of file From 857d3ca02cfc3cf5921f66a89557308504c69405 Mon Sep 17 00:00:00 2001 From: Ruben Vereecken Date: Mon, 11 Aug 2014 14:51:34 +0200 Subject: [PATCH 67/83] Added some simple animations to achievement popups --- app/styles/achievements.sass | 32 ++++++++++--------- .../achievements/achievement-popup.jade | 4 +-- app/templates/editor/achievement/edit.jade | 1 - .../achievements/AchievementPopup.coffee | 22 +++++++------ .../achievement/AchievementEditView.coffee | 1 + test/demo/fixtures/achievements.coffee | 1 - .../achievement/AchievementGet.demo.coffee | 10 +++++- 7 files changed, 41 insertions(+), 30 deletions(-) diff --git a/app/styles/achievements.sass b/app/styles/achievements.sass index 2b539c910..574734048 100644 --- a/app/styles/achievements.sass +++ b/app/styles/achievements.sass @@ -51,10 +51,6 @@ // Specific to the user stats page #user-achievements-view - .row - //.col-lg-4, .col-xs-12 - padding-right: 0px !important - .achievement-body width: 335px height: 120px @@ -63,7 +59,7 @@ .achievement-icon width: 120px height: 120px - top: 0px + top: -10px .achievement-image img @@ -88,7 +84,7 @@ .achievement-popup padding: 20px 0px - cursor: default + position: relative .achievement-body .achievement-icon @@ -132,16 +128,23 @@ bottom: 48px .user-level + background-image: url("/images/achievements/level-bg.png") + color: white + width: 38px + height: 38px + line-height: 38px font-size: 20px position: absolute left: -15px - margin-top: -3px - box-shadow: 0 0 0 2px black, 0 0 0 4px lightgrey, 0 0 0 6px black + margin-top: -8px + vertical-align: middle + z-index: 1000 + font-family: $font-family-base > .progress-bar-wrapper position: absolute - margin-left: 12px - width: 319px + margin-left: 17px + width: 314px height: 20px z-index: 2 @@ -211,11 +214,6 @@ .xp-bar-left background-color: #fffbfd -.user-level - z-index: 1000 - box-shadow: 0 0 0 1px black, 0 0 0 3px lightgrey, 0 0 0 4px black - font-family: $font-family-base - // Achievements page .achievement-category-title margin-left: 20px @@ -235,3 +233,7 @@ position: fixed right: 0px bottom: 0px + +.popup + cursor: default + left: 600px diff --git a/app/templates/achievements/achievement-popup.jade b/app/templates/achievements/achievement-popup.jade index ff889a68e..f3cca98e4 100644 --- a/app/templates/achievements/achievement-popup.jade +++ b/app/templates/achievements/achievement-popup.jade @@ -9,10 +9,10 @@ if popup .progress-wrapper - span.user-level.badge= level + span.user-level= level .progress-bar-wrapper .progress .progress-bar.xp-bar-old(style="width:#{oldXPWidth}%" data-toggle="tooltip" data-placement="top" title="#{currentXP} XP in total") .progress-bar.xp-bar-new(style="width:#{newXPWidth}%" data-toggle="tooltip" title="#{newXP} XP earned") - .progress-bar.xp-bar-left(style="width:#{leftXPWidth}%" data-toggle="tooltip" title="#{leftXP} XP until level #{nextLevel}") + .progress-bar.xp-bar-left(style="width:#{leftXPWidth}%" data-toggle="tooltip" title="#{leftXP} XP until level #{level+1}") .progress-bar-border diff --git a/app/templates/editor/achievement/edit.jade b/app/templates/editor/achievement/edit.jade index b3ebf8005..17bab8045 100644 --- a/app/templates/editor/achievement/edit.jade +++ b/app/templates/editor/achievement/edit.jade @@ -21,7 +21,6 @@ block content #achievement-treema #achievement-view.clearfix - #achievement-view-inner hr diff --git a/app/views/achievements/AchievementPopup.coffee b/app/views/achievements/AchievementPopup.coffee index e9709936a..c8e68760f 100644 --- a/app/views/achievements/AchievementPopup.coffee +++ b/app/views/achievements/AchievementPopup.coffee @@ -13,6 +13,7 @@ module.exports = class AchievementPopup extends CocoView @container = options.container or @getContainer() @popup = options.container @popup ?= true + @className += ' popup' if @popup super options console.debug 'Created an AchievementPopup', @$el @@ -40,15 +41,6 @@ module.exports = class AchievementPopup extends CocoView #console.debug "Current level is #{currentLevel} (#{currentLevelExp} xp), next level is #{nextLevel} (#{nextLevelXP} xp)." #console.debug "Need a total of #{nextLevelXP - currentLevelExp}, already had #{previousXP} and just now earned #{achievedXP} totalling on #{currentXP}" - alreadyAchievedBar = $("
") - newlyAchievedBar = $("
") - emptyBar = $("
") - progressBar = $('
').append(alreadyAchievedBar).append(newlyAchievedBar).append(emptyBar) - - alreadyAchievedBar.tooltip(title: "#{currentXP} XP in total") - newlyAchievedBar.tooltip(title: "#{achievedXP} XP earned") - emptyBar.tooltip(title: "#{nextLevelXP - currentXP} XP until level #{nextLevel}") - data = title: @achievement.get('name') imgURL: @achievement.getImageURL() @@ -71,7 +63,17 @@ module.exports = class AchievementPopup extends CocoView render: -> console.debug 'render achievement popup' super() - @container.append @$el + @container.prepend @$el + if @popup + @$el.animate + left: 0 + @$el.on 'click', (e) => + @$el.animate + left: 600 + , => + @$el.remove() + @destroy() + getContainer: -> unless @container diff --git a/app/views/editor/achievement/AchievementEditView.coffee b/app/views/editor/achievement/AchievementEditView.coffee index 7872daa54..98d6a4f32 100644 --- a/app/views/editor/achievement/AchievementEditView.coffee +++ b/app/views/editor/achievement/AchievementEditView.coffee @@ -61,6 +61,7 @@ module.exports = class AchievementEditView extends RootView @pushChangesToPreview() pushChangesToPreview: => + $('#achievement-view').empty() if @treema? for key, value of @treema.data diff --git a/test/demo/fixtures/achievements.coffee b/test/demo/fixtures/achievements.coffee index 3dceb1d39..30d4c1bd3 100644 --- a/test/demo/fixtures/achievements.coffee +++ b/test/demo/fixtures/achievements.coffee @@ -15,7 +15,6 @@ module.exports.Simulated = Simulated = _id: '53ba76249259823746b6b482' name: 'Simulated' description: 'Simulated Games.' - icon: '/images/achievements/cup-02.png' worth: 1 collection: 'users' query: "{\"simulatedBy\":{\"$gt\":0}}" diff --git a/test/demo/views/achievement/AchievementGet.demo.coffee b/test/demo/views/achievement/AchievementGet.demo.coffee index 0154390c5..a1cbafa20 100644 --- a/test/demo/views/achievement/AchievementGet.demo.coffee +++ b/test/demo/views/achievement/AchievementGet.demo.coffee @@ -9,16 +9,24 @@ module.exports = -> me.set 'points', 48 unlockableObj = fixtures.DungeonArenaStarted - earnedUnlockableObj = earnedPoints: 3 notified: false + simulated = fixtures.Simulated + earnedSimulated = + achievedAmount: 1 + earnedPoints: 1 + notified: false + unlockable = new Achievement unlockableObj earnedUnlockable = new EarnedAchievement earnedUnlockableObj + simulated = new Achievement simulated + earnedSimulated = new EarnedAchievement earnedSimulated view = new RootView view.render() view.showNewAchievement unlockable, earnedUnlockable + #view.showNewAchievement simulated, earnedSimulated view From d6f5b7512d15e9ae23c569f1fbd760e0ebdb3f07 Mon Sep 17 00:00:00 2001 From: Ruben Vereecken Date: Tue, 12 Aug 2014 13:41:14 +0200 Subject: [PATCH 68/83] i18n for all new pages --- app/locale/en.coffee | 43 ++++++++++++++ .../account/{home.sass => account_home.sass} | 2 +- app/styles/achievements.sass | 15 +++-- app/styles/common/top_nav.sass | 8 +-- app/styles/user/home.sass | 2 +- .../account/{home.jade => account_home.jade} | 55 +++++++++--------- .../achievements/achievement-popup.jade | 9 ++- app/templates/base.jade | 14 ++--- .../editor/level/modal/new-achievement.jade | 8 +-- .../editor/level/related-achievements.jade | 8 +-- app/templates/user/achievements.jade | 12 ++-- .../user/{home.jade => user_home.jade} | 58 +++++++++---------- app/views/account/MainAccountView.coffee | 12 ++-- .../achievements/AchievementPopup.coffee | 1 + app/views/user/MainUserView.coffee | 4 +- 15 files changed, 151 insertions(+), 100 deletions(-) rename app/styles/account/{home.sass => account_home.sass} (95%) rename app/templates/account/{home.jade => account_home.jade} (67%) rename app/templates/user/{home.jade => user_home.jade} (65%) diff --git a/app/locale/en.coffee b/app/locale/en.coffee index 2b8549b5e..43fe6fc8c 100644 --- a/app/locale/en.coffee +++ b/app/locale/en.coffee @@ -49,6 +49,9 @@ blog: "Blog" forum: "Forum" account: "Account" + profile: "Profile" + stats: "Stats" + code: "Code" admin: "Admin" home: "Home" contribute: "Contribute" @@ -176,12 +179,14 @@ new_password: "New Password" new_password_verify: "Verify" email_subscriptions: "Email Subscriptions" + email_subscriptions_none: "No Email Subscriptions." email_announcements: "Announcements" email_announcements_description: "Get emails on the latest news and developments at CodeCombat." email_notifications: "Notifications" email_notifications_summary: "Controls for personalized, automatic email notifications related to your CodeCombat activity." email_any_notes: "Any Notifications" email_any_notes_description: "Disable to stop all activity notification emails." + email_news: "News" email_recruit_notes: "Job Opportunities" email_recruit_notes_description: "If you play really well, we may contact you about getting you a (better) job." contributor_emails: "Contributor Class Emails" @@ -555,6 +560,10 @@ level_search_title: "Search Levels Here" achievement_search_title: "Search Achievements" read_only_warning2: "Note: you can't save any edits here, because you're not logged in." + no_achievements: "No achievements have been added for this level yet." + achievement_query_misc: "Key achievement off of miscellanea" + achievement_query_goals: "Key achievement off of level goals" + level_completion: "Level Completion" article: edit_btn_preview: "Preview" @@ -563,6 +572,7 @@ general: and: "and" name: "Name" + date: "Date" body: "Body" version: "Version" commit_msg: "Commit Message" @@ -916,3 +926,36 @@ text_diff: "Text Diff" merge_conflict_with: "MERGE CONFLICT WITH" no_changes: "No Changes" + + user: + stats: "Stats" + singleplayer_title: "Singleplayer Levels" + multiplayer_title: "Multiplayer Levels" + achievements_title: "Achievements" + last_played: "Last Played" + status: "Status" + status_completed: "Completed" + status_unfinished: "Unfinished" + no_singleplayer: "No Singleplayer games played yet." + no_multiplayer: "No Multiplayer games played yet." + no_achievements: "No Achievements earned yet." + + achievements: + last_earned: "Last Earned" + amount_achieved: "Amount" + achievement: "Achievement" + category_contributor: "Contributor" + category_miscellaneous: "Miscellaneous" + category_levels: "Levels" + category_undefined: "Uncategorized" + current_xp_prefix: "" + current_xp_postfix: " in total" + new_xp_prefix: "" + new_xp_postfix: " earned" + left_xp_prefix: "" + left_xp_infix: " until level " + left_xp_postfix: "" + + account: + recently_played: "Recently Played" + no_recent_games: "No games played during the past two weeks." diff --git a/app/styles/account/home.sass b/app/styles/account/account_home.sass similarity index 95% rename from app/styles/account/home.sass rename to app/styles/account/account_home.sass index 3e695687c..800c8a8b2 100644 --- a/app/styles/account/home.sass +++ b/app/styles/account/account_home.sass @@ -1,7 +1,7 @@ @import "../bootstrap/variables" @import "../bootstrap/mixins" -#account-home-view +#account-home dl margin-bottom: 0px diff --git a/app/styles/achievements.sass b/app/styles/achievements.sass index 574734048..1ae6a7ff8 100644 --- a/app/styles/achievements.sass +++ b/app/styles/achievements.sass @@ -128,18 +128,13 @@ bottom: 48px .user-level - background-image: url("/images/achievements/level-bg.png") - color: white - width: 38px - height: 38px - line-height: 38px font-size: 20px + color: white position: absolute left: -15px margin-top: -8px vertical-align: middle z-index: 1000 - font-family: $font-family-base > .progress-bar-wrapper position: absolute @@ -237,3 +232,11 @@ .popup cursor: default left: 600px + +.user-level + background-image: url("/images/achievements/level-bg.png") + width: 38px + height: 38px + line-height: 38px + font-size: 20px + font-family: $font-family-base diff --git a/app/styles/common/top_nav.sass b/app/styles/common/top_nav.sass index 129070977..42e7fce47 100644 --- a/app/styles/common/top_nav.sass +++ b/app/styles/common/top_nav.sass @@ -94,12 +94,8 @@ a.disabled color: #31281E .user-level position: absolute - top: 85px - right: 100px - font-size: 20px - border-radius: 50% - background-color: #FFE4BC - box-shadow: 0 0 0 1px black // disable for double border + top: 73px + right: 86px color: gold text-shadow: 1px 1px black, -1px -1px 0 black, 1px -1px 0 black, -1px 1px 0 black diff --git a/app/styles/user/home.sass b/app/styles/user/home.sass index c7b546508..55eec3fb2 100644 --- a/app/styles/user/home.sass +++ b/app/styles/user/home.sass @@ -1,7 +1,7 @@ @import "../bootstrap/variables" @import "../bootstrap/mixins" -#user-home-view +#user-home margin-top: 20px .left-column diff --git a/app/templates/account/home.jade b/app/templates/account/account_home.jade similarity index 67% rename from app/templates/account/home.jade rename to app/templates/account/account_home.jade index df897a358..d59a411df 100644 --- a/app/templates/account/home.jade +++ b/app/templates/account/account_home.jade @@ -4,8 +4,9 @@ block content if !me.isAnonymous() .clearfix .col-sm-6.clearfix - h2 Account Settings - a.spl(href="settings") + h2 + span(data-i18n="account_settings.title") Account Settings + a.spl(href="/account/settings") i.glyphicon.glyphicon-cog hr .row @@ -14,15 +15,15 @@ block content .panel-heading h3.panel-title i.glyphicon.glyphicon-picture - a(href="account/settings#picture") Picture + a(href="account/settings#picture" data-i18n="account_settings.picture_tab") Picture .panel-body.text-center - img#picture(src="#{me.getPhotoURL(150)}" alt="") + img#picture(src="#{me.getPhotoURL(150)}" alt="Picture") .col-xs-6 .panel.panel-default .panel-heading h3.panel-title i.glyphicon.glyphicon-user - a(href="account/settings#wizard") Wizard + a(href="account/settings#wizard" data-i18n="account_settings.wizard_tab") Wizard if (wizardSource) .panel-body.text-center img(src="#{wizardSource}") @@ -30,35 +31,37 @@ block content .panel-heading h3.panel-title i.glyphicon.glyphicon-user - a(href="account/settings#me") Me + a(href="account/settings#me" data-i18n="account_settings.me_tab") Me .panel-body table tr - th Name - td=me.get('name') || 'Anoner' + th(data-i18n="general.name") Name + td=me.displayName() tr - th Email + th(data-i18n="general.email") Email td=me.get('email') .panel.panel-default.panel-emails .panel-heading h3.panel-title i.glyphicon.glyphicon-envelope - a(href="account/settings#emails") Emails + a(href="account/settings#emails" data-i18n="account_settings.emails_tab") Emails .panel-body - if !hasEmailNotes && !hasEmailNews - p No email subscriptions. + if !hasEmailNotes && !hasEmailNews && !hasGeneralNews + p(data-i18n="account_settings.email_subscriptions_none") No email subscriptions. + if hasGeneralNews + h4(data-i18n="account_settings.email_news") News + ul + li(data-i18n="account_settings.email_announcements") Announcements if hasEmailNotes - h4 Notifications + h4(data-i18n="account_settings.email_notifications") Notifications ul if subs.anyNotes li(data-i18n="account_settings.email_any_notes") Any Notifications if subs.recruitNotes li(data-i18n="account_settings.email_recruit_notes") Job Opportunities if hasEmailNews - h4 News + h4(data-i18n="account_settings.contributor_emails") Contributor Emails ul - if (subs.generalNews) - li(data-i18n="account_settings.email_announcements") General if (subs.archmageNews) li span(data-i18n="classes.archmage_title") @@ -100,23 +103,23 @@ block content .panel-heading h3.panel-title i.glyphicon.glyphicon-wrench - a(href="account/settings#password") Password + a(href="account/settings#password" data-i18n="general.password") Password .panel.panel-default .panel-heading h3.panel-title i.glyphicon.glyphicon-briefcase - a(href="account/settings#job-profile") Job Profile + a(href="account/settings#job-profile" data-i18n="account_settings.job_profile") Job Profile .col-sm-6 - h2 Recently Played + h2(data-i18n="user.recently_played") Recently Played hr if !recentlyPlayed - div Loading... + div(data-i18n="common.loading") Loading... else if recentlyPlayed.length table.table tr - th Level - th Last Played - th Status + th(data-i18n="resources.level") Level + th(data-i18n="user.last_played") Last Played + th(data-i18n="user.status") Status each session in recentlyPlayed if session.get('levelName') tr @@ -126,13 +129,13 @@ block content a(href="/play/level/#{session.get('levelID') + posturl}")= session.get('levelName') + (session.get('team') ? ' (' + session.get('team') + ')' : '') td= moment(session.get('changed')).fromNow() if session.get('state').complete === true - td Completed + td(data-i18n="user.status_completed") Completed else if ! session.isMultiplayer() - td Unfinished + td(data-i18n="user.status_unfinished") Unfinished else td else .panel.panel-default .panel-body - div No games played during the past two weeks. + div(data-i18n="account.no_recent_games") No games played during the past two weeks. diff --git a/app/templates/achievements/achievement-popup.jade b/app/templates/achievements/achievement-popup.jade index f3cca98e4..f3e53d4dc 100644 --- a/app/templates/achievements/achievement-popup.jade +++ b/app/templates/achievements/achievement-popup.jade @@ -12,7 +12,10 @@ span.user-level= level .progress-bar-wrapper .progress - .progress-bar.xp-bar-old(style="width:#{oldXPWidth}%" data-toggle="tooltip" data-placement="top" title="#{currentXP} XP in total") - .progress-bar.xp-bar-new(style="width:#{newXPWidth}%" data-toggle="tooltip" title="#{newXP} XP earned") - .progress-bar.xp-bar-left(style="width:#{leftXPWidth}%" data-toggle="tooltip" title="#{leftXP} XP until level #{level+1}") + - var currentTitle = $.i18n.t('achievements.current_xp_prefix') + currentXP + ' XP' + $.i18n.t('achievements.current_xp_postfix'); + - var newTitle = $.i18n.t('achievements.new_xp_prefix') + newXP + ' XP' + $.i18n.t('achievements.new_xp_postfix'); + - var leftTitle = $.i18n.t('achievements.left_xp_prefix') + newXP + ' XP' + $.i18n.t('achievements.left_xp_infix') + (level+1) + $.i18n.t('achievements.left_xp_postfix'); + .progress-bar.xp-bar-old(style="width:#{oldXPWidth}%" data-toggle="tooltip" data-placement="top" title="#{currentTitle}") + .progress-bar.xp-bar-new(style="width:#{newXPWidth}%" data-toggle="tooltip" title="#{newTitle}") + .progress-bar.xp-bar-left(style="width:#{leftXPWidth}%" data-toggle="tooltip" title="#{leftTitle}") .progress-bar-border diff --git a/app/templates/base.jade b/app/templates/base.jade index fe9158497..a5f9c29bb 100644 --- a/app/templates/base.jade +++ b/app/templates/base.jade @@ -43,20 +43,20 @@ body span.caret ul.dropdown-menu(role="menu") li.user-dropdown-header - span.user-level.badge= me.level() - a(href="/user/#{me.get('slug') || me.get('_id')}") + span.user-level= me.level() + a(href="/user/#{me.getSlugOrID()}") img.img-circle(src="#{me.getPhotoURL()}" alt="") - h3=me.get('name') || 'Anoner' + h3=me.displayName() li.user-dropdown-body .col-xs-4.text-center - a(href="/user/#{me.get('slug') || me.get('_id')}") Profile + a(href="/user/#{me.getSlugOrID()}" data-i18n="nav.profile") Profile .col-xs-4.text-center - a(href="/user/#{me.get('slug') || me.get('_id')}/stats") Stats + a(href="/user/#{me.getSlugOrID()}/stats" data-i18n="nav.stats") Stats .col-xs-4.text-center - a.disabled() Code + a.disabled(data-i18n="nav.code") Code li.user-dropdown-footer .pull-left - a.btn.btn-default.btn-flat(href="/account") Account + a.btn.btn-default.btn-flat(href="/account" data-i18n="nav.account") Account .pull-right button#logout-button.btn.btn-default.btn-flat(data-i18n="login.log_out") Log Out else diff --git a/app/templates/editor/level/modal/new-achievement.jade b/app/templates/editor/level/modal/new-achievement.jade index f66347278..dfcc39cbc 100644 --- a/app/templates/editor/level/modal/new-achievement.jade +++ b/app/templates/editor/level/modal/new-achievement.jade @@ -6,16 +6,16 @@ block modal-body-content label.control-label(for="name", data-i18n="general.name") Name input#name.form-control(name="name", type="text") .form-group - label.control-label(for="description") Description + label.control-label(for="description" data-i18n="general.description") Description input#description.form-control(name="description", type="text") - h4 Miscellaneous achievement keys + h4(data-i18n="editor.achievement_query_misc") Key achievement off of miscellanea .radio label input(type="checkbox", name="queryOptions" id="misc-level-completion" value="misc-level-completion") - span.spl Level Completion + span.spl(data-i18n="editor.level_completion") Level Completion - var goals = level.get('goals'); if goals && goals.length - h4 Base achievement on goals? + h4(data-i18n="editor.achievement_query_goals") Key achievement off of level goals each goal in goals .radio label diff --git a/app/templates/editor/level/related-achievements.jade b/app/templates/editor/level/related-achievements.jade index 9270c0def..e0312d779 100644 --- a/app/templates/editor/level/related-achievements.jade +++ b/app/templates/editor/level/related-achievements.jade @@ -2,18 +2,18 @@ button.btn.btn-primary#new-achievement-button(disabled=me.isAdmin() === true ? undefined : "true" data-i18n="editor.new_achievement_title") Create a New Achievement if achievements.loading - h2 Loading... + h2(data-i18n="common.loading") Loading... else if ! achievements.models.length .panel .panel-body - p No achievements added for this level yet. + p(data-i18n="editor.no_achievements") No achievements added for this level yet. else table.table.table-hover thead tr th - th Name - th Description + th(data-i18n="general.name") Name + th(data-i18n="general.description") Description th XP tbody each achievement in achievements.models diff --git a/app/templates/user/achievements.jade b/app/templates/user/achievements.jade index b84b63e17..37cbad2b3 100644 --- a/app/templates/user/achievements.jade +++ b/app/templates/user/achievements.jade @@ -9,7 +9,7 @@ block append content .grid-layout each achievements, category in achievementsByCategory .row - h2.achievement-category-title=category.charAt(0).toUpperCase() + category.slice(1) + h2.achievement-category-title(data-i18n="category_#{category}")=category each achievement, index in achievements - var title = achievement.get('name'); - var description = achievement.get('description'); @@ -27,10 +27,10 @@ block append content if earnedAchievements.length table.table tr - th Name - th Description - th Date - th Amount + th(data-i18n="general.name") Name + th(data-i18n="general.description") Description + th(data-i18n="general.date") Date + th(data-i18n="achievements.amount_achieved") Amount th XP each earnedAchievement in earnedAchievements.models - var achievement = earnedAchievement.get('achievement'); @@ -45,6 +45,6 @@ block append content td= earnedAchievement.get('earnedPoints') else .panel#no-achievements - .panel-body No achievements earned yet. + .panel-body(data-i18n="user.no_achievements") No achievements earned yet. else div How did you even do that? diff --git a/app/templates/user/home.jade b/app/templates/user/user_home.jade similarity index 65% rename from app/templates/user/home.jade rename to app/templates/user/user_home.jade index e9aedd463..d3e6f22db 100644 --- a/app/templates/user/home.jade +++ b/app/templates/user/user_home.jade @@ -15,13 +15,13 @@ block append content .btn-group-vertical.profile-menu a.btn.btn-default(href="/user/#{user.getSlugOrID()}/profile") i.glyphicon.glyphicon-briefcase - span Job Profile + span(data-i18n="account_settings.job_profile") Job Profile a.btn.btn-default(href="/user/#{user.getSlugOrID()}/stats") i.glyphicon.glyphicon-certificate - span Stats + span(data-i18n="user.stats") Stats a.btn.btn-default.disabled(href="#") i.glyphicon.glyphicon-pencil - span Code + span(data-i18n="general.code") Code - var emails = user.get('emails') if emails ul.contributor-categories @@ -32,41 +32,41 @@ block append content li.contributor-category img.contributor-image(src="/images/pages/user/adventurer.png") h4.contributor-title - a(href="/contribute#adventurer") Adventurer + a(href="/contribute#adventurer" data-i18n="classes.adventurer_title") Adventurer if emails.ambassadorNews li.contributor-category img.contributor-image(src="/images/pages/user/ambassador.png") h4.contributor-title - a(href="/contribute#ambassador") Ambassador + a(href="/contribute#ambassador" data-i18n="classes.ambassador_title") Ambassador if emails.archmageNews li.contributor-category img.contributor-image(src="/images/pages/user/archmage.png") h4.contributor-title - a(href="/contribute#archmage") Archmage + a(href="/contribute#archmage" data-i18n="classes.archmage_title") Archmage if emails.artisanNews li.contributor-category img.contributor-image(src="/images/pages/user/artisan.png") h4.contributor-title - a(href="/contribute#artisan") Artisan + a(href="/contribute#artisan" data-i18n="classes.artisan_title") Artisan if emails.scribeNews li.contributor-category img.contributor-image(src="/images/pages/user/scribe.png") h4.contributor-title - a(href="/contribute#scribe") Scribe + a(href="/contribute#scribe" data-i18n="classes.scribe_title") Scribe .right-column .panel.panel-default .panel-heading - h3.panel-title Singleplayer Levels + h3.panel-title(data-i18n="user.singleplayer_title") Singleplayer Levels if (!singlePlayerSessions) .panel-body - p Loading... + p(data-i18n="common.loading") Loading... else if (singlePlayerSessions.length) table.table tr - th.col-xs-4 Level - th.col-xs-4 Last Played - th.col-xs-4 Status + th.col-xs-4(data-i18n="resources.level") Level + th.col-xs-4(data-i18n="user.last_played") Last Played + th.col-xs-4(data-i18n="user.status") Status each session in singlePlayerSessions if session.get('levelName') tr @@ -74,24 +74,24 @@ block append content a(href="/play/level/#{session.get('levelID')}")= session.get('levelName') td= moment(session.get('changed')).fromNow() if session.get('state').complete === true - td Completed + td(data-i18n="user.status_completed") Completed else - td Unfinished + td(data-i18n="user.status_unfinished") Unfinished else .panel-body - p No Singleplayer games played yet. + p(data-i18n="no_singleplayer") No Singleplayer games played yet. .panel.panel-default .panel-heading - h3.panel-title Multiplayer Levels + h3.panel-title(data-i18n="no_multiplayer") Multiplayer Levels if (!multiPlayerSessions) .panel-body - p Loading... + p(data-i18n="common.loading") Loading... else if (multiPlayerSessions.length) table.table tr - th.col-xs-4 Level - th.col-xs-4 Last Played - th.col-xs-4 Score + th.col-xs-4(data-i18n="resources.level") Level + th.col-xs-4(data-i18n="user.last_played") Last Played + th.col-xs-4(data-i18n="general.score") Score each session in multiPlayerSessions tr td @@ -102,25 +102,25 @@ block append content if session.get('totalScore') td= session.get('totalScore') * 100 else - td Unfinished + td(data-i18n="user.status_unfinished") Unfinished else .panel-body - p No Multiplayer games played yet. + p(data-i18n="user.no_multiplayer") No Multiplayer games played yet. .panel.panel-default .panel-heading - h3.panel-title Achievements + h3.panel-title(data-i18n="user.achievements") Achievements if ! earnedAchievements .panel-body - p Loading... + p(data-i18n="common.loading") Loading... else if ! earnedAchievements.length .panel-body - p No achievements earned so far. + p(data-i18n="user.no_achievements") No achievements earned so far. else table.table tr - th.col-xs-4 Achievement - th.col-xs-4 Last Earned - th.col-xs-4 Amount + th.col-xs-4(data-i18n="achievements.achievement") Achievement + th.col-xs-4(data-i18n="achievements.last_earned") Last Earned + th.col-xs-4(data-i18n="achievements.amount_achieved") Amount each achievement in earnedAchievements.models tr td= achievement.get('achievementName') diff --git a/app/views/account/MainAccountView.coffee b/app/views/account/MainAccountView.coffee index 50d7360be..a14e2192a 100644 --- a/app/views/account/MainAccountView.coffee +++ b/app/views/account/MainAccountView.coffee @@ -1,5 +1,5 @@ View = require 'views/kinds/RootView' -template = require 'templates/account/home' +template = require 'templates/account/account_home' {me} = require 'lib/auth' User = require 'models/User' AuthModalView = require 'views/modal/AuthModal' @@ -7,7 +7,7 @@ RecentlyPlayedCollection = require 'collections/RecentlyPlayedCollection' ThangType = require 'models/ThangType' module.exports = class MainAccountView extends View - id: 'account-home-view' + id: 'account-home' template: template constructor: (options) -> @@ -24,9 +24,11 @@ module.exports = class MainAccountView extends View getRenderData: -> c = super() c.subs = {} - c.subs[sub] = 1 for sub in c.me.getEnabledEmails() - c.hasEmailNotes = _.any c.me.getEnabledEmails(), (sub) -> sub.contains 'Notes' - c.hasEmailNews = _.any c.me.getEnabledEmails(), (sub) -> sub.contains 'News' + enabledEmails = c.me.getEnabledEmails() + c.subs[sub] = 1 for sub in enabledEmails + c.hasEmailNotes = _.any enabledEmails, (sub) -> sub.contains 'Notes' + c.hasEmailNews = _.any enabledEmails, (sub) -> sub.contains('News') and sub isnt 'generalNews' + c.hasGeneralNews = 'generalNews' in enabledEmails c.wizardSource = @wizardType.getPortraitSource colorConfig: me.get('wizard')?.colorConfig if @wizardType.loaded c.recentlyPlayed = @recentlyPlayed.models c diff --git a/app/views/achievements/AchievementPopup.coffee b/app/views/achievements/AchievementPopup.coffee index c8e68760f..e1307a4b7 100644 --- a/app/views/achievements/AchievementPopup.coffee +++ b/app/views/achievements/AchievementPopup.coffee @@ -58,6 +58,7 @@ module.exports = class AchievementPopup extends CocoView _.extend c, @calculateData() c.style = @achievement.getStyle() c.popup = true + c.$ = $ # Allows the jade template to do i18n c render: -> diff --git a/app/views/user/MainUserView.coffee b/app/views/user/MainUserView.coffee index cf0913913..a200ab989 100644 --- a/app/views/user/MainUserView.coffee +++ b/app/views/user/MainUserView.coffee @@ -1,7 +1,7 @@ UserView = require 'views/kinds/UserView' CocoCollection = require 'collections/CocoCollection' LevelSession = require 'models/LevelSession' -template = require 'templates/user/home' +template = require 'templates/user/user_home' {me} = require 'lib/auth' EarnedAchievementCollection = require 'collections/EarnedAchievementCollection' @@ -13,7 +13,7 @@ class LevelSessionsCollection extends CocoCollection super() module.exports = class MainUserView extends UserView - id: 'user-home-view' + id: 'user-home' template: template constructor: (userID, options) -> From 8ae116200ff7d8b1348e9f490be066f6c64f4bae Mon Sep 17 00:00:00 2001 From: Ruben Vereecken Date: Tue, 12 Aug 2014 17:59:33 +0200 Subject: [PATCH 69/83] Achievements are now i18n'able --- app/locale/en.coffee | 2 ++ app/models/Achievement.coffee | 4 ++++ app/models/EarnedAchievement.coffee | 2 +- app/schemas/models/achievement.coffee | 4 ++++ app/styles/user/{home.sass => user_home.sass} | 2 +- app/templates/user/achievements.jade | 8 ++++---- app/templates/user/user_home.jade | 6 ++++-- app/views/achievements/AchievementPopup.coffee | 5 ++--- test/demo/fixtures/achievements.coffee | 3 +++ test/demo/views/achievement/AchievementGet.demo.coffee | 1 + 10 files changed, 26 insertions(+), 11 deletions(-) rename app/styles/user/{home.sass => user_home.sass} (97%) diff --git a/app/locale/en.coffee b/app/locale/en.coffee index 43fe6fc8c..84108d651 100644 --- a/app/locale/en.coffee +++ b/app/locale/en.coffee @@ -939,6 +939,8 @@ no_singleplayer: "No Singleplayer games played yet." no_multiplayer: "No Multiplayer games played yet." no_achievements: "No Achievements earned yet." + favorite_prefix: "Favorite language is " + favorite_postfix: "." achievements: last_earned: "Last Earned" diff --git a/app/models/Achievement.coffee b/app/models/Achievement.coffee index 3ae32aa84..723b2b0bf 100644 --- a/app/models/Achievement.coffee +++ b/app/models/Achievement.coffee @@ -51,3 +51,7 @@ module.exports = class Achievement extends CocoModel defer getLockedImageURL: -> @lockedImageURL + + i18nName: -> utils.i18n @attributes, 'name' + + i18nDescription: -> utils.i18n @attributes, 'description' diff --git a/app/models/EarnedAchievement.coffee b/app/models/EarnedAchievement.coffee index 28588db81..2fa36037c 100644 --- a/app/models/EarnedAchievement.coffee +++ b/app/models/EarnedAchievement.coffee @@ -1,5 +1,5 @@ CocoModel = require './CocoModel' -util = require '../lib/utils' +utils = require '../lib/utils' module.exports = class EarnedAchievement extends CocoModel @className: 'EarnedAchievement' diff --git a/app/schemas/models/achievement.coffee b/app/schemas/models/achievement.coffee index eed560e74..473e53cc8 100644 --- a/app/schemas/models/achievement.coffee +++ b/app/schemas/models/achievement.coffee @@ -78,6 +78,10 @@ _.extend AchievementSchema.properties, default: {kind: 'linear', parameters: a: 1} required: ['kind', 'parameters'] additionalProperties: false + i18n: c.object + format: 'i18n' + props: ['name', 'description'] + description: 'Help translate this achievement' _.extend AchievementSchema, # Let's have these on the bottom # TODO We really need some required properties in my opinion but this makes creating new achievements impossible as it is now diff --git a/app/styles/user/home.sass b/app/styles/user/user_home.sass similarity index 97% rename from app/styles/user/home.sass rename to app/styles/user/user_home.sass index 55eec3fb2..ebd67a391 100644 --- a/app/styles/user/home.sass +++ b/app/styles/user/user_home.sass @@ -40,7 +40,7 @@ width: 100% > a border-radius: 0 - border-width: 1px 0px + border-width: 1px 0px 0px 0px border-color: darkgrey &:hover border-color: #888 diff --git a/app/templates/user/achievements.jade b/app/templates/user/achievements.jade index 37cbad2b3..830c3dea6 100644 --- a/app/templates/user/achievements.jade +++ b/app/templates/user/achievements.jade @@ -11,8 +11,8 @@ block append content .row h2.achievement-category-title(data-i18n="category_#{category}")=category each achievement, index in achievements - - var title = achievement.get('name'); - - var description = achievement.get('description'); + - var title = achievement.i18nName(); + - var description = achievement.i18nDescription(); - var locked = ! achievement.get('unlocked'); - var style = achievement.getStyle() - var imgURL = achievement.getImageURL(); @@ -35,8 +35,8 @@ block append content each earnedAchievement in earnedAchievements.models - var achievement = earnedAchievement.get('achievement'); tr - td= achievement.get('name') - td= achievement.get('description') + td= achievement.i18nName() + td= achievement.i18nDescription() td= moment().format("MMMM Do YYYY", earnedAchievement.get('changed')) if achievement.isRepeatable() td= earnedAchievement.get('achievedAmount') diff --git a/app/templates/user/user_home.jade b/app/templates/user/user_home.jade index d3e6f22db..0008ce24b 100644 --- a/app/templates/user/user_home.jade +++ b/app/templates/user/user_home.jade @@ -10,8 +10,10 @@ block append content div.profile-info h3.name= user.get('name') if favoriteLanguage - div.extra-info Favorite language is - strong.spl.spr= favoriteLanguage + div.extra-info + span(data-i18n="user.favorite_prefix") Favorite language is + strong.favorite-language= favoriteLanguage + span(data-i18n="user.favorite_postfix") . .btn-group-vertical.profile-menu a.btn.btn-default(href="/user/#{user.getSlugOrID()}/profile") i.glyphicon.glyphicon-briefcase diff --git a/app/views/achievements/AchievementPopup.coffee b/app/views/achievements/AchievementPopup.coffee index e1307a4b7..82e464734 100644 --- a/app/views/achievements/AchievementPopup.coffee +++ b/app/views/achievements/AchievementPopup.coffee @@ -42,9 +42,9 @@ module.exports = class AchievementPopup extends CocoView #console.debug "Need a total of #{nextLevelXP - currentLevelExp}, already had #{previousXP} and just now earned #{achievedXP} totalling on #{currentXP}" data = - title: @achievement.get('name') + title: @achievement.i18nName() imgURL: @achievement.getImageURL() - description: @achievement.get('description') + description: @achievement.i18nDescription() level: currentLevel currentXP: currentXP newXP: achievedXP @@ -75,7 +75,6 @@ module.exports = class AchievementPopup extends CocoView @$el.remove() @destroy() - getContainer: -> unless @container @container = $('.achievement-popup-container') diff --git a/test/demo/fixtures/achievements.coffee b/test/demo/fixtures/achievements.coffee index 30d4c1bd3..b54580d39 100644 --- a/test/demo/fixtures/achievements.coffee +++ b/test/demo/fixtures/achievements.coffee @@ -10,6 +10,9 @@ module.exports.DungeonArenaStarted = DungeonArenaStarted = collection: 'level.session' query: "{\"level.original\":\"dungeon-arena\"}" userField: 'creator' + i18n: + es: + name: 'Dungeon Arenos Started' module.exports.Simulated = Simulated = _id: '53ba76249259823746b6b482' diff --git a/test/demo/views/achievement/AchievementGet.demo.coffee b/test/demo/views/achievement/AchievementGet.demo.coffee index a1cbafa20..697843c2e 100644 --- a/test/demo/views/achievement/AchievementGet.demo.coffee +++ b/test/demo/views/achievement/AchievementGet.demo.coffee @@ -7,6 +7,7 @@ fixtures = require '../../fixtures/achievements' module.exports = -> me.set 'points', 48 + me.set 'preferredLanguage', 'es' unlockableObj = fixtures.DungeonArenaStarted earnedUnlockableObj = From 6d04b583f9c0af8ac1283f4fa91126384d9e5b4d Mon Sep 17 00:00:00 2001 From: Nick Winter Date: Tue, 12 Aug 2014 10:10:54 -0700 Subject: [PATCH 70/83] Added volume slider to game options. --- app/styles/game-menu/options-view.sass | 6 +++++- app/templates/game-menu/options-view.jade | 7 +++++++ app/views/game-menu/OptionsView.coffee | 14 ++++++++++++++ 3 files changed, 26 insertions(+), 1 deletion(-) diff --git a/app/styles/game-menu/options-view.sass b/app/styles/game-menu/options-view.sass index ae9542bff..b796029e6 100644 --- a/app/styles/game-menu/options-view.sass +++ b/app/styles/game-menu/options-view.sass @@ -1,7 +1,7 @@ @import "app/styles/bootstrap/variables" #options-view - .select-group + .select-group, .slider-group display: block min-height: 20px margin-top: 10px @@ -14,6 +14,9 @@ margin-right: 20px margin-bottom: 0 + .slider + width: 200px + .form-group.radio-inline input margin-left: 0px @@ -22,6 +25,7 @@ .radio-inline-parent-label padding-left: 0 + #player-avatar-container position: relative margin: 0px 0px 15px 15px diff --git a/app/templates/game-menu/options-view.jade b/app/templates/game-menu/options-view.jade index 732392eda..ee4010dd2 100644 --- a/app/templates/game-menu/options-view.jade +++ b/app/templates/game-menu/options-view.jade @@ -8,6 +8,13 @@ .form h3(data-i18n="options.general_options") General Options + .form-group.slider-group + label(for="option-volume") + span(data-i18n="options.volume") Volume + span.spr : + span#option-volume-value= (me.get('volume') * 100).toFixed(0) + '%' + #option-volume.slider + .form-group.checkbox label(for="option-music") input#option-music(name="option-music", type="checkbox", checked=music) diff --git a/app/views/game-menu/OptionsView.coffee b/app/views/game-menu/OptionsView.coffee index 8654554ef..e1bd2c53e 100644 --- a/app/views/game-menu/OptionsView.coffee +++ b/app/views/game-menu/OptionsView.coffee @@ -48,6 +48,20 @@ module.exports = class OptionsView extends CocoView afterRender: -> super() + @volumeSlider = @$el.find('#option-volume').slider(animate: 'fast', min: 0, max: 1, step: 0.05) + @volumeSlider.slider('value', me.get('volume')) + @volumeSlider.on('slide', @onVolumeSliderChange) + @volumeSlider.on('slidechange', @onVolumeSliderChange) + + destroy: -> + @volumeSlider?.slider?('destroy') + super() + + onVolumeSliderChange: (e) => + volume = @volumeSlider.slider('value') + me.set 'volume', volume + @$el.find('#option-volume-value').text (volume * 100).toFixed(0) + '%' + Backbone.Mediator.publish 'level-set-volume', volume: volume onHidden: -> if @playerName and @playerName isnt me.get('name') From 2ab83328f3b169413bb247d922f521595e70d64e Mon Sep 17 00:00:00 2001 From: Nick Winter Date: Tue, 12 Aug 2014 12:50:41 -0700 Subject: [PATCH 71/83] Added item editor level preview window. --- app/models/Level.coffee | 4 +- app/styles/editor/thang/home.sass | 2 +- .../editor/thang/thang-type-edit-view.sass | 2 + .../editor/thang/thang-type-edit-view.jade | 10 +++++ .../editor/thang/ThangTypeEditView.coffee | 38 +++++++++++++++++++ app/views/play/level/PlayLevelView.coffee | 12 +++++- 6 files changed, 64 insertions(+), 4 deletions(-) diff --git a/app/models/Level.coffee b/app/models/Level.coffee index 4f95d269a..d2972366b 100644 --- a/app/models/Level.coffee +++ b/app/models/Level.coffee @@ -80,7 +80,7 @@ module.exports = class Level extends CocoModel visit = (c) -> return if c in sorted lc = _.find levelComponents, {original: c.original} - console.error thang.id, 'couldn\'t find lc for', c, 'of', levelComponents unless lc + console.error thang.id or thang.name, 'couldn\'t find lc for', c, 'of', levelComponents unless lc return unless lc if lc.name is 'Programmable' # Programmable always comes last @@ -88,7 +88,7 @@ module.exports = class Level extends CocoModel else for d in lc.dependencies or [] c2 = _.find thang.components, {original: d.original} - console.error thang.id, 'couldn\'t find dependent Component', d.original, 'from', lc.name unless c2 + console.error thang.id or thang.name, 'couldn\'t find dependent Component', d.original, 'from', lc.name unless c2 visit c2 if c2 if lc.name is 'Collides' allied = _.find levelComponents, {name: 'Allied'} diff --git a/app/styles/editor/thang/home.sass b/app/styles/editor/thang/home.sass index 3509c1f27..193ea50d6 100644 --- a/app/styles/editor/thang/home.sass +++ b/app/styles/editor/thang/home.sass @@ -6,4 +6,4 @@ width: 30px td - vertical-align: middle \ No newline at end of file + vertical-align: middle diff --git a/app/styles/editor/thang/thang-type-edit-view.sass b/app/styles/editor/thang/thang-type-edit-view.sass index 135923348..109651a3b 100644 --- a/app/styles/editor/thang/thang-type-edit-view.sass +++ b/app/styles/editor/thang/thang-type-edit-view.sass @@ -77,6 +77,8 @@ background-color: white border-radius: 4px + .play-with-level-input + margin: 5px #spritesheets diff --git a/app/templates/editor/thang/thang-type-edit-view.jade b/app/templates/editor/thang/thang-type-edit-view.jade index f3560d212..dc83dc257 100644 --- a/app/templates/editor/thang/thang-type-edit-view.jade +++ b/app/templates/editor/thang/thang-type-edit-view.jade @@ -33,6 +33,16 @@ block header span.navbar-brand #{thangType.attributes.name} ul.nav.navbar-nav.navbar-right + li.dropdown + a(data-toggle='dropdown').play-with-level-parent + span.glyphicon-play.glyphicon + ul.dropdown-menu + li.dropdown-header Play Which Level? + li + for level in recentlyPlayedLevels + a.play-with-level-button(data-level=level)= level + input.play-with-level-input(placeholder="Type in a level name") + if authorized li#save-button a diff --git a/app/views/editor/thang/ThangTypeEditView.coffee b/app/views/editor/thang/ThangTypeEditView.coffee index 28e2aab39..a208ef1ac 100644 --- a/app/views/editor/thang/ThangTypeEditView.coffee +++ b/app/views/editor/thang/ThangTypeEditView.coffee @@ -12,6 +12,7 @@ ThangTypeColorsTabView = require './ThangTypeColorsTabView' PatchesView = require 'views/editor/PatchesView' SaveVersionModal = require 'views/modal/SaveVersionModal' template = require 'templates/editor/thang/thang-type-edit-view' +storage = require 'lib/storage' CENTER = {x: 200, y: 300} @@ -37,6 +38,9 @@ module.exports = class ThangTypeEditView extends RootView 'click #history-button': 'showVersionHistory' 'click #save-button': 'openSaveModal' 'click #patches-tab': -> @patchesView.load() + 'click .play-with-level-button': 'onPlayLevel' + 'click .play-with-level-parent': 'onPlayLevelSelect' + 'keyup .play-with-level-input': 'onPlayLevelKeyUp' subscriptions: 'save-new-version': 'saveNewThangType' @@ -58,6 +62,7 @@ module.exports = class ThangTypeEditView extends RootView context.thangType = @thangType context.animations = @getAnimationNames() context.authorized = not me.get('anonymous') + context.recentlyPlayedLevels = storage.load('recently-played-levels') ? ['items'] context getAnimationNames: -> @@ -408,6 +413,39 @@ module.exports = class ThangTypeEditView extends RootView openSaveModal: -> @openModalView(new SaveVersionModal({model: @thangType})) + onPlayLevelSelect: (e) -> + if @childWindow and not @childWindow.closed + # We already have a child window open, so we don't need to ask for a level; we'll use its existing level. + e.stopImmediatePropagation() + @onPlayLevel e + _.defer -> $('.play-with-level-input').focus() + + onPlayLevelKeyUp: (e) -> + return unless e.keyCode is 13 # return + input = @$el.find('.play-with-level-input') + input.parents('.dropdown').find('.play-with-level-parent').dropdown('toggle') + level = _.string.slugify input.val() + return unless level + @onPlayLevel null, level + recentlyPlayedLevels = storage.load('recently-played-levels') ? [] + recentlyPlayedLevels.push level + storage.save 'recently-played-levels', recentlyPlayedLevels + + onPlayLevel: (e, level=null) -> + level ?= $(e.target).data('level') + level = _.string.slugify level + if @childWindow and not @childWindow.closed + # Reset the LevelView's world, but leave the rest of the state alone + @childWindow.Backbone.Mediator.publish 'level-reload-thang-type', thangType: @thangType + else + # Create a new Window with a blank LevelView + scratchLevelID = level + '?dev=true' + if me.get('name') is 'Nick' + @childWindow = window.open("/play/level/#{scratchLevelID}", 'child_window', 'width=2560,height=1080,left=0,top=-1600,location=1,menubar=1,scrollbars=1,status=0,titlebar=1,toolbar=1', true) + else + @childWindow = window.open("/play/level/#{scratchLevelID}", 'child_window', 'width=1024,height=560,left=10,top=10,location=0,menubar=0,scrollbars=0,status=0,titlebar=0,toolbar=0', true) + @childWindow.focus() + destroy: -> @camera?.destroy() super() diff --git a/app/views/play/level/PlayLevelView.coffee b/app/views/play/level/PlayLevelView.coffee index 663dbd70f..eb47c167a 100644 --- a/app/views/play/level/PlayLevelView.coffee +++ b/app/views/play/level/PlayLevelView.coffee @@ -55,6 +55,7 @@ module.exports = class PlayLevelView extends RootView 'god:new-world-created': 'onNewWorld' 'god:infinite-loop': 'onInfiniteLoop' 'level-reload-from-data': 'onLevelReloadFromData' + 'level-reload-thang-type': 'onLevelReloadThangType' 'play-next-level': 'onPlayNextLevel' 'edit-wizard-settings': 'showWizardSettingsModal' 'surface:world-set-up': 'onSurfaceSetUpNewWorld' @@ -321,11 +322,20 @@ module.exports = class PlayLevelView extends RootView onLevelReloadFromData: (e) -> isReload = Boolean @world - @setLevel e.level, e.supermodel + @setLevel @level, e.supermodel if isReload @scriptManager.setScripts(e.level.get('scripts')) Backbone.Mediator.publish 'tome:cast-spell' # a bit hacky + onLevelReloadThangType: (e) -> + tt = e.thangType + for url, model of @supermodel.models + if model.id is tt.id + for key, val of tt.attributes + model.attributes[key] = val + break + Backbone.Mediator.publish 'tome:cast-spell' + onWindowResize: (s...) -> $('#pointer').css('opacity', 0.0) From 291b44f0a675fbf85e7a6207e73ef6e3ba85600d Mon Sep 17 00:00:00 2001 From: Nick Winter Date: Tue, 12 Aug 2014 12:56:55 -0700 Subject: [PATCH 72/83] Fix typo introduced in last commit. Like a boss. --- app/views/play/level/PlayLevelView.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/play/level/PlayLevelView.coffee b/app/views/play/level/PlayLevelView.coffee index eb47c167a..391c99f4c 100644 --- a/app/views/play/level/PlayLevelView.coffee +++ b/app/views/play/level/PlayLevelView.coffee @@ -322,7 +322,7 @@ module.exports = class PlayLevelView extends RootView onLevelReloadFromData: (e) -> isReload = Boolean @world - @setLevel @level, e.supermodel + @setLevel e.level, e.supermodel if isReload @scriptManager.setScripts(e.level.get('scripts')) Backbone.Mediator.publish 'tome:cast-spell' # a bit hacky From 1b1174d460cb8342ad9b7dd78dc6a2e808d935c9 Mon Sep 17 00:00:00 2001 From: Darredevil Date: Tue, 12 Aug 2014 23:23:43 +0300 Subject: [PATCH 73/83] Added 'Classic Algorithms' level section --- app/views/play/MainPlayView.coffee | 64 ++++++++++++++++-------------- 1 file changed, 34 insertions(+), 30 deletions(-) diff --git a/app/views/play/MainPlayView.coffee b/app/views/play/MainPlayView.coffee index fa5aba4b6..c46f906c4 100644 --- a/app/views/play/MainPlayView.coffee +++ b/app/views/play/MainPlayView.coffee @@ -192,36 +192,8 @@ module.exports = class MainPlayView extends RootView levelPath: 'ladder' } ] - - playerCreated = [ - { - name: 'Extra Extrapolation' - difficulty: 2 - id: 'extra-extrapolation' - image: '/file/db/level/526bda3fe79aefde2a003e36/mobile_artillery_icon.png' - description: 'Predict your target\'s position for deadly aim. - by Sootn' - } - { - name: 'The Right Route' - difficulty: 1 - id: 'the-right-route' - image: '/file/db/level/526fd3043c637ece50001bb2/the_herd_icon.png' - description: 'Strike at the weak point in an array of enemies. - by Aftermath' - } - { - name: 'Sword Loop' - difficulty: 2 - id: 'sword-loop' - image: '/file/db/level/525dc5589a0765e496000006/drink_me_icon.png' - description: 'Kill the ogres and save the peasants with for-loops. - by Prabh Simran Singh Baweja' - } - { - name: 'Coin Mania' - difficulty: 2 - id: 'coin-mania' - image: '/file/db/level/529662dfe0df8f0000000007/grab_the_mushroom_icon.png' - description: 'Learn while-loops to grab coins and potions. - by Prabh Simran Singh Baweja' - } + + classicAlgorithms = [ { name: 'Bubble Sort Bootcamp Battle' difficulty: 3 @@ -257,6 +229,37 @@ module.exports = class MainPlayView extends RootView image: '/file/db/level/525ef8ef06e1ab0962000003/commanding_followers_icon.png' description: 'Learn Quicksort while sorting a spiral of ogres! - by Alexandru Caciulescu' } + ] + + playerCreated = [ + { + name: 'Extra Extrapolation' + difficulty: 2 + id: 'extra-extrapolation' + image: '/file/db/level/526bda3fe79aefde2a003e36/mobile_artillery_icon.png' + description: 'Predict your target\'s position for deadly aim. - by Sootn' + } + { + name: 'The Right Route' + difficulty: 1 + id: 'the-right-route' + image: '/file/db/level/526fd3043c637ece50001bb2/the_herd_icon.png' + description: 'Strike at the weak point in an array of enemies. - by Aftermath' + } + { + name: 'Sword Loop' + difficulty: 2 + id: 'sword-loop' + image: '/file/db/level/525dc5589a0765e496000006/drink_me_icon.png' + description: 'Kill the ogres and save the peasants with for-loops. - by Prabh Simran Singh Baweja' + } + { + name: 'Coin Mania' + difficulty: 2 + id: 'coin-mania' + image: '/file/db/level/529662dfe0df8f0000000007/grab_the_mushroom_icon.png' + description: 'Learn while-loops to grab coins and potions. - by Prabh Simran Singh Baweja' + } { name: 'Find the Spy' difficulty: 2 @@ -291,6 +294,7 @@ module.exports = class MainPlayView extends RootView {id: 'beginner', name: 'Beginner Campaign', description: '... in which you learn the wizardry of programming.', levels: tutorials} {id: 'multiplayer', name: 'Multiplayer Arenas', description: '... in which you code head-to-head against other players.', levels: arenas} {id: 'dev', name: 'Random Harder Levels', description: '... in which you learn the interface while doing something a little harder.', levels: experienced} + {id: 'classic' ,name: 'Classic Algorithms', description: '... in which you learn the most popular algorithms in Computer Science.', levels: classicAlgorithms} {id: 'player_created', name: 'Player-Created', description: '... in which you battle against the creativity of your fellow
Artisan Wizards.', levels: playerCreated} ] context.levelStatusMap = @levelStatusMap From 2ec8a69de0248cec8584459bf7bbff7018954b35 Mon Sep 17 00:00:00 2001 From: Imperadeiro98 Date: Tue, 12 Aug 2014 22:09:46 +0100 Subject: [PATCH 74/83] Update locale.coffee Updated pt-PT description and added Catalan. --- app/locale/locale.coffee | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/locale/locale.coffee b/app/locale/locale.coffee index 1fcf2e202..5b970d28f 100644 --- a/app/locale/locale.coffee +++ b/app/locale/locale.coffee @@ -26,7 +26,7 @@ module.exports = ar: require './ar' # العربية, Arabic pt: require './pt' # português, Portuguese 'pt-BR': require './pt-BR' # português do Brasil, Portuguese (Brazil) - 'pt-PT': require './pt-PT' # Português europeu, Portuguese (Portugal) + 'pt-PT': require './pt-PT' # Português (Portugal), Portuguese (Portugal) pl: require './pl' # język polski, Polish it: require './it' # italiano, Italian tr: require './tr' # Türkçe, Turkish @@ -58,3 +58,4 @@ module.exports = hi: require './hi' # मानक हिन्दी, Hindi ur: require './ur' # اُردُو, Urdu ms: require './ms' # Bahasa Melayu, Bahasa Malaysia + ca: require './ca' # Català, Catalan From eccf148793d12907b1c121571067192c55afd2b6 Mon Sep 17 00:00:00 2001 From: Imperadeiro98 Date: Tue, 12 Aug 2014 22:11:14 +0100 Subject: [PATCH 75/83] Big update to pt-PT.coffee --- app/locale/pt-PT.coffee | 218 ++++++++++++++++++++-------------------- 1 file changed, 109 insertions(+), 109 deletions(-) diff --git a/app/locale/pt-PT.coffee b/app/locale/pt-PT.coffee index bd6e4fbce..1ae60b284 100644 --- a/app/locale/pt-PT.coffee +++ b/app/locale/pt-PT.coffee @@ -213,10 +213,10 @@ module.exports = nativeDescription: "Português (Portugal)", englishDescription: # active: "Looking for interview offers now" # inactive: "Not looking for offers right now" # complete: "complete" -# next: "Next" -# next_city: "city?" + next: "Seguinte" + next_city: "cidade?" # next_country: "pick your country." -# next_name: "name?" + next_name: "nome?" # next_short_description: "write a short description." # next_long_description: "describe your desired position." # next_skills: "list at least five skills." @@ -226,39 +226,39 @@ module.exports = nativeDescription: "Português (Portugal)", englishDescription: # next_links: "add any personal or social links." # next_photo: "add an optional professional photo." # next_active: "mark yourself open to offers to show up in searches." -# example_blog: "Blog" -# example_personal_site: "Personal Site" -# links_header: "Personal Links" + example_blog: "Blog" + example_personal_site: "Sítio Pessoal" + links_header: "Ligações Pessoais" # links_blurb: "Link any other sites or profiles you want to highlight, like your GitHub, your LinkedIn, or your blog." -# links_name: "Link Name" -# links_name_help: "What are you linking to?" -# links_link_blurb: "Link URL" + links_name: "Nome da Ligação" + links_name_help: "A que é que está a ligar?" + links_link_blurb: "URL da Ligação" # basics_header: "Update basic info" # basics_active: "Open to Offers" # basics_active_help: "Want interview offers right now?" # basics_job_title: "Desired Job Title" # basics_job_title_help: "What role are you looking for?" -# basics_city: "City" + basics_city: "Cidade" # basics_city_help: "City you want to work in (or live in now)." -# basics_country: "Country" + basics_country: "País" # basics_country_help: "Country you want to work in (or live in now)." # basics_visa: "US Work Status" # basics_visa_help: "Are you authorized to work in the US, or do you need visa sponsorship? (If you live in Canada or Australia, mark authorized.)" -# basics_looking_for: "Looking For" -# basics_looking_for_full_time: "Full-time" -# basics_looking_for_part_time: "Part-time" -# basics_looking_for_remote: "Remote" + basics_looking_for: "À Procura De" + basics_looking_for_full_time: "Tempo Inteiro" + basics_looking_for_part_time: "Part-time" + basics_looking_for_remote: "Remoto" # basics_looking_for_contracting: "Contracting" # basics_looking_for_internship: "Internship" # basics_looking_for_help: "What kind of developer position do you want?" # name_header: "Fill in your name" -# name_anonymous: "Anonymous Developer" + name_anonymous: "Desenvolvedor Anónimo" # name_help: "Name you want employers to see, like 'Nick Winter'." # short_description_header: "Write a short description of yourself" # short_description_blurb: "Add a tagline to help an employer quickly learn more about you." # short_description: "Tagline" # short_description_help: "Who are you, and what are you looking for? 140 characters max." -# skills_header: "Skills" + skills_header: "Habilidades" # skills_help: "Tag relevant developer skills in order of proficiency." # long_description_header: "Describe your desired position" # long_description_blurb: "Tell employers how awesome you are and what role you want." @@ -266,22 +266,22 @@ module.exports = nativeDescription: "Português (Portugal)", englishDescription: # long_description_help: "Describe yourself to potential employers. Keep it short and to the point. We recommend outlining the position that would most interest you. Tasteful markdown okay; 600 characters max." # work_experience: "Work Experience" # work_header: "Chronicle your work history" -# work_years: "Years of Experience" + work_years: "Anos de Experiência" # work_years_help: "How many years of professional experience (getting paid) developing software do you have?" # work_blurb: "List your relevant work experience, most recent first." -# work_employer: "Employer" -# work_employer_help: "Name of your employer." -# work_role: "Job Title" + work_employer: "Empregador" + work_employer_help: "Nome do seu empregador." + work_role: "Título do Emprego" # work_role_help: "What was your job title or role?" -# work_duration: "Duration" + work_duration: "Duração" # work_duration_help: "When did you hold this gig?" -# work_description: "Description" + work_description: "Descrição" # work_description_help: "What did you do there? (140 chars; optional)" -# education: "Education" + education: "Educação" # education_header: "Recount your academic ordeals" # education_blurb: "List your academic ordeals." -# education_school: "School" -# education_school_help: "Name of your school." + education_school: "Escola" + education_school_help: "Nome da sua escola." # education_degree: "Degree" # education_degree_help: "What was your degree and field of study?" # education_duration: "Dates" @@ -312,18 +312,18 @@ module.exports = nativeDescription: "Português (Portugal)", englishDescription: filter_visa: "Visa" filter_visa_yes: "Autorizado Para Trabalhar Nos EUA" filter_visa_no: "Não Autorizado" -# filter_education_top: "Top School" -# filter_education_other: "Other" -# filter_role_web_developer: "Web Developer" -# filter_role_software_developer: "Software Developer" -# filter_role_mobile_developer: "Mobile Developer" -# filter_experience: "Experience" -# filter_experience_senior: "Senior" -# filter_experience_junior: "Junior" + filter_education_top: "Universidade" + filter_education_other: "Outro" + filter_role_web_developer: "Desenvolvedor da Web" + filter_role_software_developer: "Desenvolvedor de Software" + filter_role_mobile_developer: "Desenvolvedor Mobile" + filter_experience: "Experiência" + filter_experience_senior: "Sénior" + filter_experience_junior: "Júnior" # filter_experience_recent_grad: "Recent Grad" -# filter_experience_student: "College Student" -# filter_results: "results" -# start_hiring: "Start hiring." + filter_experience_student: "Estudante Universitário" + filter_results: "resultados" + start_hiring: "Começar a contratar." # reasons: "Three reasons you should hire through us:" # everyone_looking: "Everyone here is looking for their next opportunity." # everyone_looking_blurb: "Forget about 20% LinkedIn InMail response rates. Everyone that we list on this site wants to find their next position and will respond to your request for an introduction." @@ -408,11 +408,11 @@ module.exports = nativeDescription: "Português (Portugal)", englishDescription: tip_morale_improves: "O carregamento irá continuar até que a moral melhore." tip_all_species: "Acreditamos em oportunidades iguais para todas as espécies, em relação a aprenderem a programar." tip_reticulating: "A reticular espinhas." - tip_harry: "Você é um Feitiçeiro, " -# tip_great_responsibility: "With great coding skill comes great debug responsibility." -# tip_munchkin: "If you don't eat your vegetables, a munchkin will come after you while you're asleep." + tip_harry: "Você é um Feiticeiro, " + tip_great_responsibility: "Com uma grande habilidade de programação vem uma grande responsabilidade de depuração." + tip_munchkin: "Se não comer os seus vegetais, virá um ogre atrás de si enquanto estiver a dormir." tip_binary: "Há apenas 10 tipos de pessoas no mundo: aquelas que percebem binário e aquelas que não." -# tip_commitment_yoda: "A programmer must have the deepest commitment, the most serious mind. ~ Yoda" + tip_commitment_yoda: "Um programador deve ter o compromisso mais profundo, a mente mais séria. ~ Yoda" tip_no_try: "Fazer. Ou não fazer. Não há nenhum tentar. - Yoda" tip_patience: "Paciência tu deves ter, jovem Padawan. - Yoda" tip_documented_bug: "Um erro documentado não é um erro; é uma funcionalidade." @@ -448,7 +448,7 @@ module.exports = nativeDescription: "Português (Portugal)", englishDescription: # temp: "Temp" save_load: -# granularity_saved_games: "Saved" + granularity_saved_games: "Guardados" granularity_change_history: "Histórico" options: @@ -504,7 +504,7 @@ module.exports = nativeDescription: "Português (Portugal)", englishDescription: # toggle_grid: "Toggle grid overlay." # toggle_pathfinding: "Toggle pathfinding overlay." # beautify: "Beautify your code by standardizing its formatting." - move_wizard: "Mover o seu Feitiçeiro pelo nível." + move_wizard: "Mover o seu Feiticeiro pelo nível." admin: av_title: "Vistas de Administrador" @@ -547,7 +547,7 @@ module.exports = nativeDescription: "Português (Portugal)", englishDescription: grassy: "Com Relva" fork_title: "Bifurcar Nova Versão" fork_creating: "A Criar Bifurcação..." -# randomize: "Randomize" + randomize: "Randomizar" more: "Mais" wiki: "Wiki" live_chat: "Chat Ao Vivo" @@ -561,8 +561,8 @@ module.exports = nativeDescription: "Português (Portugal)", englishDescription: level_tab_thangs_all: "Todos" level_tab_thangs_conditions: "Condições Iniciais" level_tab_thangs_add: "Adicionar Thangs" -# delete: "Delete" -# duplicate: "Duplicate" + delete: "Eliminar" + duplicate: "Duplicar" level_settings_title: "Configurações" level_component_tab_title: "Componentes Atuais" level_component_btn_new: "Criar Novo Componente" @@ -572,7 +572,7 @@ module.exports = nativeDescription: "Português (Portugal)", englishDescription: level_components_title: "Voltar para Todos os Thangs" level_components_type: "Tipo" level_component_edit_title: "Editar Componente" -# level_component_config_schema: "Config Schema" + level_component_config_schema: "Configurar Esquema" level_component_settings: "Configurações" level_system_edit_title: "Editar Sistema" create_system_title: "Criar Novo Sistema" @@ -632,22 +632,22 @@ module.exports = nativeDescription: "Português (Portugal)", englishDescription: who_description_prefix: "começaram juntos o CodeCombat em 2013. Também criaram o " who_description_suffix: "em 2008, tornando-o a aplicação nº1 da web e iOS para aprender a escrever caracteteres Chineses e Japoneses." who_description_ending: "Agora, está na altura de ensinar as pessoas a escrever código." -# why_paragraph_1: "When making Skritter, George didn't know how to program and was constantly frustrated by his inability to implement his ideas. Afterwards, he tried learning, but the lessons were too slow. His housemate, wanting to reskill and stop teaching, tried Codecademy, but \"got bored.\" Each week another friend started Codecademy, then dropped off. We realized it was the same problem we'd solved with Skritter: people learning a skill via slow, intensive lessons when what they need is fast, extensive practice. We know how to fix that." -# why_paragraph_2: "Need to learn to code? You don't need lessons. You need to write a lot of code and have a great time doing it." -# why_paragraph_3_prefix: "That's what programming is about. It's gotta be fun. Not fun like" -# why_paragraph_3_italic: "yay a badge" -# why_paragraph_3_center: "but fun like" -# why_paragraph_3_italic_caps: "NO MOM I HAVE TO FINISH THE LEVEL!" -# why_paragraph_3_suffix: "That's why CodeCombat is a multiplayer game, not a gamified lesson course. We won't stop until you can't stop--but this time, that's a good thing." -# why_paragraph_4: "If you're going to get addicted to some game, get addicted to this one and become one of the wizards of the tech age." -# why_ending: "And hey, it's free. " -# why_ending_url: "Start wizarding now!" -# george_description: "CEO, business guy, web designer, game designer, and champion of beginning programmers everywhere." -# scott_description: "Programmer extraordinaire, software architect, kitchen wizard, and master of finances. Scott is the reasonable one." -# nick_description: "Programming wizard, eccentric motivation mage, and upside-down experimenter. Nick can do anything and chooses to build CodeCombat." -# jeremy_description: "Customer support mage, usability tester, and community organizer; you've probably already spoken with Jeremy." -# michael_description: "Programmer, sys-admin, and undergrad technical wunderkind, Michael is the person keeping our servers online." -# glen_description: "Programmer and passionate game developer, with the motivation to make this world a better place, by developing things that matter. The word impossible can't be found in his dictionary. Learning new skills is his joy!" + why_paragraph_1: "Aquando da conceção do Skritter, o George não sabia programar e estava constantemente frustrado devido à sua inabilidade para implementar as ideias dele. Mais tarde, tentou aprender, mas as aulas eram muito lentas. O seu colega de quarto, numa tentativa de melhorar as suas habilidades e parar de ensinar, tentou o Codecademy, mas \"aborreceu-se.\" A cada semana, um outro amigo começava no Codecademy, mas desistia sempre. Apercebemo-nos de que era o mesmo problema que resolveríamos com o Skritter: pessoas a aprender uma habilidade através de aulas lentas e intensivas, quando o que precisam é de praticar rápida e extensivamente. Nós sabemos como resolver isso." + why_paragraph_2: "Precisa de aprender a programar? Não precisa de aulas. Precisa sim de escrever muito código e passar um bom bocado enquanto o faz." + why_paragraph_3_prefix: "Afinal, é sobre isso que é a programação. Tem de ser divertida. Não divertida do género" + why_paragraph_3_italic: "yay uma medalha" + why_paragraph_3_center: "mas sim divertida do género" + why_paragraph_3_italic_caps: "NÃO MÃE, TENHO DE ACABAR O NÍVEL!" + why_paragraph_3_suffix: "É por isso que o CodeCombat é um jogo multijogador, e não um jogo que não passa de um curso com lições. Nós não vamos parar enquanto não puderes parar--mas desta vez, isso é uma coisa boa." + why_paragraph_4: "Se vais ficar viciado em algum jogo, vicia-te neste e torna-te num dos feiticeiros da idade da tecnologia." + why_ending: "E vejam só, é gratuito. " + why_ending_url: "Comece a enfeitiçar agora!" + george_description: "CEO, homem de negócios, designer da web, designer de jogos e campeão dos programadores iniciantes de todo o lado." + scott_description: "Programador extraordinário, arquiteto de software, feiticeiro da cozinha e mestre das finanças. O Scott é sensato." + nick_description: "Feiticeiro da programção, mago da motivação excêntrico e experimentador de pernas para o ar. O Nick pode fazer qualquer coisa e escolhe construir o CodeCombat." + jeremy_description: "Mago do suporte ao cliente, testador do uso e organizador da comunidade; provavelmente já falou com o Jeremy." + michael_description: "Programador, administrador do sistema e técnico de graduação prodígio, o Michael é a pessoa que mantém os nossos servidores online." + glen_description: "Programador e desenvolvedor de jogos apaixonado, com a motivação necessária para tornar este mundo um lugar melhor, ao desenvolver coisas que importam. A palavra impossível não pode ser encontrada no dicionário dele. Aprender novas habilidades é a alegria dele!" legal: page_title: "Legal" @@ -834,11 +834,11 @@ module.exports = nativeDescription: "Português (Portugal)", englishDescription: rank_failed: "Falhou a Classificar" rank_being_ranked: "Jogo a ser Classificado" # rank_last_submitted: "submitted " -# help_simulate: "Help simulate games?" - code_being_simulated: "O teu código está a ser simulado por outros jogadores, para ser classificado. Isto será actualizado quando surgirem novas partidas." + help_simulate: "Ajudar a simular jogos?" + code_being_simulated: "O seu novo código está a ser simulado por outros jogadores, para ser classificado. Isto será atualizado quando surgirem novas partidas." no_ranked_matches_pre: "Sem jogos classificados pela equipa " no_ranked_matches_post: "! Joga contra alguns adversários e volta aqui para veres o teu jogo classificado." - choose_opponent: "Escolhe um Adversário" + choose_opponent: "Escolha um Adversário" select_your_language: "Selecione a sua linguagem!" tutorial_play: "Jogar Tutorial" tutorial_recommended: "Recomendado se nunca jogou antes" @@ -848,40 +848,40 @@ module.exports = nativeDescription: "Português (Portugal)", englishDescription: simple_ai: "Inteligência Artificial Simples" warmup: "Aquecimento" vs: "VS" -# friends_playing: "Friends Playing" -# log_in_for_friends: "Log in to play with your friends!" -# social_connect_blurb: "Connect and play against your friends!" -# invite_friends_to_battle: "Invite your friends to join you in battle!" -# fight: "Fight!" -# watch_victory: "Watch your victory" -# defeat_the: "Defeat the" -# tournament_ends: "Tournament ends" -# tournament_ended: "Tournament ended" -# tournament_rules: "Tournament Rules" -# tournament_blurb: "Write code, collect gold, build armies, crush foes, win prizes, and upgrade your career in our $40,000 Greed tournament! Check out the details" -# tournament_blurb_blog: "on our blog" + friends_playing: "Amigos a Jogar" + log_in_for_friends: "Inicie sessão para jogar com os seus amigos!" + social_connect_blurb: "Conecte-se e jogue contra os seus amigos!" + invite_friends_to_battle: "Convide os seus amigos para se juntarem a si em batalha!" + fight: "Lutar!" + watch_victory: "Veja a sua vitória" + defeat_the: "Derrote o" + tournament_ends: "O Torneio acaba" + tournament_ended: "O Torneio acabou" + tournament_rules: "Regras do Torneio" + tournament_blurb: "Escreva código, recolha ouro, construa exércitos, esmague inimigos, ganhe prémios e melhore a sua carreira no nosso torneio $40,000 Greed! Confira os detalhes" + tournament_blurb_blog: "no nosso blog" rules: "Regras" winners: "Vencedores" -# ladder_prizes: -# title: "Tournament Prizes" -# blurb_1: "These prizes will be awarded according to" -# blurb_2: "the tournament rules" -# blurb_3: "to the top human and ogre players." -# blurb_4: "Two teams means double the prizes!" -# blurb_5: "(There will be two first place winners, two second-place winners, etc.)" -# rank: "Rank" -# prizes: "Prizes" -# total_value: "Total Value" -# in_cash: "in cash" -# custom_wizard: "Custom CodeCombat Wizard" -# custom_avatar: "Custom CodeCombat avatar" -# heap: "for six months of \"Startup\" access" -# credits: "credits" -# one_month_coupon: "coupon: choose either Rails or HTML" -# one_month_discount: "discount, 30% off: choose either Rails or HTML" -# license: "license" -# oreilly: "ebook of your choice" + ladder_prizes: + title: "Prémios do Torneio" + blurb_1: "Estes prémios serão entregues de acordo com" + blurb_2: "as regras do torneio" + blurb_3: "aos melhores jogadores humanos e ogres." + blurb_4: "Duas equipas significam o dobro dos prémios!" + blurb_5: "(Haverá dois vencedores em primeiro lugar, dois em segundo, etc.)" + rank: "Classificação" + prizes: "Prémios" + total_value: "Valor Total" + in_cash: "em dinheiro" + custom_wizard: "Um Feiticeiro do CodeCombat Personalizado" + custom_avatar: "Um Avatar do CodeCombat Personalizado" + heap: "para seis meses de acesso \"Startup\"" + credits: "créditos" + one_month_coupon: "cupão: escolha Rails ou HTML" + one_month_discount: "desconto de 30%: escolha Rails ou HTML" + license: "licença" + oreilly: "ebook à sua escolha" loading_error: could_not_load: "Erro ao carregar do servidor" @@ -909,8 +909,8 @@ module.exports = nativeDescription: "Português (Portugal)", englishDescription: # user_schema: "User Schema" # user_profile: "User Profile" # patches: "Patches" -# patched_model: "Source Document" -# model: "Model" + patched_model: "Documento Fonte" + model: "Modelo" system: "Sistema" component: "Componente" components: "Componentes" @@ -921,20 +921,20 @@ module.exports = nativeDescription: "Português (Portugal)", englishDescription: article: "Artigo" # user_names: "User Names" # thang_names: "Thang Names" -# files: "Files" + files: "Ficheiros" # top_simulators: "Top Simulators" -# source_document: "Source Document" + source_document: "Documento Fonte" document: "Documento" # sprite_sheet: "Sprite Sheet" # candidate_sessions: "Candidate Sessions" # user_remark: "User Remark" versions: "Versões" -# delta: -# added: "Added" -# modified: "Modified" -# deleted: "Deleted" -# moved_index: "Moved Index" -# text_diff: "Text Diff" -# merge_conflict_with: "MERGE CONFLICT WITH" -# no_changes: "No Changes" + delta: + added: "Adicionados/as" + modified: "Modificados/as" + deleted: "Eliminados/as" + moved_index: "Índice Movido" + text_diff: "Diferença de Texto" + merge_conflict_with: "FUNDIR CONFLITO COM" + no_changes: "Sem Alterações" From 04e86802634ddef38dff956f04eb27effb995109 Mon Sep 17 00:00:00 2001 From: Scott Erickson Date: Tue, 12 Aug 2014 10:09:53 -0700 Subject: [PATCH 76/83] Added an endpoint for loading all items. --- server/levels/thangs/thang_type_handler.coffee | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/server/levels/thangs/thang_type_handler.coffee b/server/levels/thangs/thang_type_handler.coffee index 9e51a80f3..33c23a2bb 100644 --- a/server/levels/thangs/thang_type_handler.coffee +++ b/server/levels/thangs/thang_type_handler.coffee @@ -26,4 +26,16 @@ ThangTypeHandler = class ThangTypeHandler extends Handler hasAccess: (req) -> req.method is 'GET' or req.user?.isAdmin() + get: (req, res) -> + if req.query.view is 'items' + projection = {} + if req.query.project + projection[field] = 1 for field in req.query.project.split(',') + ThangType.find({ 'kind': 'Item' }, projection).exec (err, documents) => + return @sendDatabaseError(res, err) if err + documents = (@formatEntity(req, doc) for doc in documents) + @sendSuccess(res, documents) + else + super(arguments...) + module.exports = new ThangTypeHandler() From 19b9a99167ac2e9385cf467c737b0cdaf2a1e7c7 Mon Sep 17 00:00:00 2001 From: Scott Erickson Date: Wed, 13 Aug 2014 10:48:22 -0700 Subject: [PATCH 77/83] Fixed some tests. --- test/app/lib/LevelLoader.spec.coffee | 14 +++++++------- .../app/{vendor => vendorTests}/lodash.spec.coffee | 0 .../component/ThangComponentsEditView.spec.coffee | 12 ++++++++---- 3 files changed, 15 insertions(+), 11 deletions(-) rename test/app/{vendor => vendorTests}/lodash.spec.coffee (100%) diff --git a/test/app/lib/LevelLoader.spec.coffee b/test/app/lib/LevelLoader.spec.coffee index c8d83338a..d9102a299 100644 --- a/test/app/lib/LevelLoader.spec.coffee +++ b/test/app/lib/LevelLoader.spec.coffee @@ -93,15 +93,15 @@ describe 'LevelLoader', -> jasmine.Ajax.requests.sendResponses(responses) requests = jasmine.Ajax.requests.all() urls = (r.url for r in requests) - expect('/db/thang.type/helmet/version?project=name,components' in urls).toBeTruthy() - expect('/db/thang.type/tharin/version?project=name,components' in urls).toBeTruthy() + expect('/db/thang.type/helmet/version?project=name,components,original' in urls).toBeTruthy() + expect('/db/thang.type/tharin/version?project=name,components,original' in urls).toBeTruthy() it 'loads components for the hero in the heroConfig in the LevelSession', -> new LevelLoader({supermodel:new SuperModel(), sessionID: 'id', levelID: 'id'}) responses = { '/db/level_session/id': sessionWithTharinWithHelmet - '/db/thang.type/tharin/version?project=name,components': thangTypeTharinWithHealsComponent + '/db/thang.type/tharin/version?project=name,components,original': thangTypeTharinWithHealsComponent } jasmine.Ajax.requests.sendResponses(responses) @@ -119,7 +119,7 @@ describe 'LevelLoader', -> jasmine.Ajax.requests.sendResponses(responses) requests = jasmine.Ajax.requests.all() urls = (r.url for r in requests) - expect('/db/thang.type/mace/version?project=name,components' in urls).toBeTruthy() + expect('/db/thang.type/mace/version?project=name,components,original' in urls).toBeTruthy() it 'loads components which are inherited by level thangs from thang type default components', -> new LevelLoader({supermodel:new SuperModel(), sessionID: 'id', levelID: 'id'}) @@ -143,7 +143,7 @@ describe 'LevelLoader', -> jasmine.Ajax.requests.sendResponses(responses) requests = jasmine.Ajax.requests.all() urls = (r.url for r in requests) - expect('/db/thang.type/wand/version?project=name,components' in urls).toBeTruthy() + expect('/db/thang.type/wand/version?project=name,components,original' in urls).toBeTruthy() it 'loads components for item thang types which are inherited by level thangs from thang type default equips component configs', -> new LevelLoader({supermodel:new SuperModel(), sessionID: 'id', levelID: 'id'}) @@ -151,7 +151,7 @@ describe 'LevelLoader', -> responses = '/db/level/id': levelWithShaman '/db/thang.type/names': [thangTypeShamanWithWandEquipped] - '/db/thang.type/wand/version?project=name,components': thangTypeWand + '/db/thang.type/wand/version?project=name,components,original': thangTypeWand jasmine.Ajax.requests.sendResponses(responses) requests = jasmine.Ajax.requests.all() @@ -168,4 +168,4 @@ describe 'LevelLoader', -> jasmine.Ajax.requests.sendResponses(responses) requests = jasmine.Ajax.requests.all() urls = (r.url for r in requests) - expect('/db/thang.type/wand/version?project=name,components' in urls).toBeFalsy() + expect('/db/thang.type/wand/version?project=name,components,original' in urls).toBeFalsy() diff --git a/test/app/vendor/lodash.spec.coffee b/test/app/vendorTests/lodash.spec.coffee similarity index 100% rename from test/app/vendor/lodash.spec.coffee rename to test/app/vendorTests/lodash.spec.coffee diff --git a/test/app/views/editor/component/ThangComponentsEditView.spec.coffee b/test/app/views/editor/component/ThangComponentsEditView.spec.coffee index 7ea96fb74..e9f25bf33 100644 --- a/test/app/views/editor/component/ThangComponentsEditView.spec.coffee +++ b/test/app/views/editor/component/ThangComponentsEditView.spec.coffee @@ -25,17 +25,21 @@ componentC = new LevelComponent({ name: 'C (depends on B)' dependencies: [{original:'B', majorVersion: 0}] }) +componentC.loaded = true describe 'ThangComponentsEditView', -> view = null - beforeEach -> + beforeEach (done) -> supermodel = new SuperModel() supermodel.registerModel(componentC) view = new ThangComponentEditView({ components: [], supermodel: supermodel }) - view.render() - view.componentsTreema.set('/', [ { original: 'C', majorVersion: 0 }]) - spyOn(window, 'noty') + jasmine.Ajax.requests.sendResponses { '/db/thang.type': [] } + _.delay -> + view.render() + view.componentsTreema.set('/', [ { original: 'C', majorVersion: 0 }]) + spyOn(window, 'noty') + done() it 'loads dependencies when you add a component with the left side treema', -> success = jasmine.Ajax.requests.sendResponses(responses) From b7d5428d5f51780091cba7114668cda66a175908 Mon Sep 17 00:00:00 2001 From: Nick Winter Date: Wed, 13 Aug 2014 11:36:00 -0700 Subject: [PATCH 78/83] Made Forking more abstract to apply to ThangTypes, too. --- app/templates/editor/level/edit.jade | 2 +- app/templates/editor/level/fork.jade | 17 ------- .../editor/thang/thang-type-edit-view.jade | 2 + app/views/editor/level/LevelEditView.coffee | 9 ++-- .../editor/level/modals/ForkLevelModal.coffee | 45 ------------------- .../editor/thang/ThangTypeEditView.coffee | 11 +++-- 6 files changed, 14 insertions(+), 72 deletions(-) delete mode 100644 app/templates/editor/level/fork.jade delete mode 100644 app/views/editor/level/modals/ForkLevelModal.coffee diff --git a/app/templates/editor/level/edit.jade b/app/templates/editor/level/edit.jade index 3f16f5559..22687228a 100644 --- a/app/templates/editor/level/edit.jade +++ b/app/templates/editor/level/edit.jade @@ -83,7 +83,7 @@ block header span.spl(data-i18n="common.unwatch") Unwatch li(class=anonymous ? "disabled": "") - a(data-i18n="common.fork")#fork-level-start-button Fork + a(data-i18n="common.fork")#fork-start-button Fork li(class=anonymous ? "disabled": "") a(data-toggle="coco-modal", data-target="modal/RevertModal", data-i18n="editor.revert")#revert-button Revert li(class=anonymous ? "disabled": "") diff --git a/app/templates/editor/level/fork.jade b/app/templates/editor/level/fork.jade deleted file mode 100644 index 6c4f43553..000000000 --- a/app/templates/editor/level/fork.jade +++ /dev/null @@ -1,17 +0,0 @@ -extends /templates/modal/modal_base - -block modal-header-content - h3(data-i18n="editor.fork_title") Fork New Version - -block modal-body-content - form#save-level-form.form - .form-group - label(for="level-name", data-i18n="general.name") Name - input#level-name(name="name", type="text").form-control - -block modal-footer-content - button.btn(data-dismiss="modal", data-i18n="common.cancel") Cancel - button.btn.btn-primary#fork-level-confirm-button(data-i18n="common.save") Save - -block modal-body-wait-content - h3(data-i18n="editor.fork_creating") Creating Fork... diff --git a/app/templates/editor/thang/thang-type-edit-view.jade b/app/templates/editor/thang/thang-type-edit-view.jade index dc83dc257..394c1f97a 100644 --- a/app/templates/editor/thang/thang-type-edit-view.jade +++ b/app/templates/editor/thang/thang-type-edit-view.jade @@ -52,6 +52,8 @@ block header span.glyphicon-chevron-down.glyphicon ul.dropdown-menu li.dropdown-header Actions + li(class=anonymous ? "disabled": "") + a(data-i18n="common.fork")#fork-start-button Fork li(class=anonymous ? "disabled": "") a(data-toggle="coco-modal", data-target="modal/RevertModal", data-i18n="editor.revert")#revert-button Revert li.divider diff --git a/app/views/editor/level/LevelEditView.coffee b/app/views/editor/level/LevelEditView.coffee index 8e489291e..adf82ba96 100644 --- a/app/views/editor/level/LevelEditView.coffee +++ b/app/views/editor/level/LevelEditView.coffee @@ -12,7 +12,7 @@ ScriptsTabView = require './scripts/ScriptsTabView' ComponentsTabView = require './components/ComponentsTabView' SystemsTabView = require './systems/SystemsTabView' SaveLevelModal = require './modals/SaveLevelModal' -LevelForkView = require './modals/ForkLevelModal' +ForkModal = require 'views/editor/ForkModal' SaveVersionModal = require 'views/modal/SaveVersionModal' PatchesView = require 'views/editor/PatchesView' VersionHistoryView = require './modals/LevelVersionsModal' @@ -29,7 +29,7 @@ module.exports = class LevelEditView extends RootView 'click .play-with-team-button': 'onPlayLevel' 'click .play-with-team-parent': 'onPlayLevelTeamSelect' 'click #commit-level-start-button': 'startCommittingLevel' - 'click #fork-level-start-button': 'startForkingLevel' + 'click #fork-start-button': 'startForking' 'click #level-history-button': 'showVersionHistory' 'click #undo-button': 'onUndo' 'click #redo-button': 'onRedo' @@ -128,9 +128,8 @@ module.exports = class LevelEditView extends RootView @openModalView new SaveLevelModal level: @level, supermodel: @supermodel Backbone.Mediator.publish 'level:view-switched', e - startForkingLevel: (e) -> - levelForkView = new LevelForkView level: @level - @openModalView levelForkView + startForking: (e) -> + @openModalView new ForkModal model: @level, editorPath: 'level' Backbone.Mediator.publish 'level:view-switched', e showVersionHistory: (e) -> diff --git a/app/views/editor/level/modals/ForkLevelModal.coffee b/app/views/editor/level/modals/ForkLevelModal.coffee deleted file mode 100644 index 335ffc4a1..000000000 --- a/app/views/editor/level/modals/ForkLevelModal.coffee +++ /dev/null @@ -1,45 +0,0 @@ -ModalView = require 'views/kinds/ModalView' -template = require 'templates/editor/level/fork' -forms = require 'lib/forms' -Level = require 'models/Level' - -module.exports = class ForkLevelModal extends ModalView - id: 'editor-level-fork-modal' - template: template - instant: false - modalWidthPercent: 60 - - events: - 'click #fork-level-confirm-button': 'forkLevel' - 'submit form': 'forkLevel' - - constructor: (options) -> - super options - @level = options.level - - getRenderData: (context={}) -> - context = super(context) - context.level = @level - context - - forkLevel: -> - @showLoading() - forms.clearFormAlerts(@$el) - newLevel = new Level($.extend(true, {}, @level.attributes)) - newLevel.unset '_id' - newLevel.unset 'version' - newLevel.unset 'creator' - newLevel.unset 'created' - newLevel.unset 'original' - newLevel.unset 'parent' - newLevel.set 'commitMessage', "Forked from #{@level.get('name')}" - newLevel.set 'name', @$el.find('#level-name').val() - newLevel.set 'permissions', [access: 'owner', target: me.id] - res = newLevel.save() - return unless res - res.error => - @hideLoading() - forms.applyErrorsToForm(@$el.find('form'), JSON.parse(res.responseText)) - res.success => - @hide() - application.router.navigate('editor/level/' + newLevel.get('slug'), {trigger: true}) diff --git a/app/views/editor/thang/ThangTypeEditView.coffee b/app/views/editor/thang/ThangTypeEditView.coffee index a208ef1ac..4ddeadecb 100644 --- a/app/views/editor/thang/ThangTypeEditView.coffee +++ b/app/views/editor/thang/ThangTypeEditView.coffee @@ -10,6 +10,7 @@ ThangComponentsEditView = require 'views/editor/component/ThangComponentsEditVie ThangTypeVersionsModal = require './ThangTypeVersionsModal' ThangTypeColorsTabView = require './ThangTypeColorsTabView' PatchesView = require 'views/editor/PatchesView' +ForkModal = require 'views/editor/ForkModal' SaveVersionModal = require 'views/modal/SaveVersionModal' template = require 'templates/editor/thang/thang-type-edit-view' storage = require 'lib/storage' @@ -36,6 +37,7 @@ module.exports = class ThangTypeEditView extends RootView 'click #marker-button': 'toggleDots' 'click #end-button': 'endAnimation' 'click #history-button': 'showVersionHistory' + 'click #fork-start-button': 'startForking' 'click #save-button': 'openSaveModal' 'click #patches-tab': -> @patchesView.load() 'click .play-with-level-button': 'onPlayLevel' @@ -406,12 +408,13 @@ module.exports = class ThangTypeEditView extends RootView @showingSelectedNode = false showVersionHistory: (e) -> - versionHistoryModal = new ThangTypeVersionsModal thangType: @thangType, @thangTypeID - @openModalView versionHistoryModal - Backbone.Mediator.publish 'level:view-switched', e + @openModalView new ThangTypeVersionsModal thangType: @thangType, @thangTypeID openSaveModal: -> - @openModalView(new SaveVersionModal({model: @thangType})) + @openModalView new SaveVersionModal model: @thangType + + startForking: (e) -> + @openModalView new ForkModal model: @thangType, editorPath: 'thang' onPlayLevelSelect: (e) -> if @childWindow and not @childWindow.closed From f9e8bfad9f619cccca1acbc03053ef1c3cad1480 Mon Sep 17 00:00:00 2001 From: Scott Erickson Date: Wed, 13 Aug 2014 13:45:24 -0700 Subject: [PATCH 79/83] Made some minor changes. --- app/views/DemoView.coffee | 2 +- app/views/editor/level/RelatedAchievementsView.coffee | 1 - test/app/models/CocoModel.spec.coffee | 4 +++- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/app/views/DemoView.coffee b/app/views/DemoView.coffee index 5cbe2d662..24fe9090d 100644 --- a/app/views/DemoView.coffee +++ b/app/views/DemoView.coffee @@ -25,7 +25,7 @@ DEMO_URL_PREFIX = '/demo/' ### module.exports = DemoView = class DemoView extends RootView - id: "demo-view" + id: 'demo-view' template: template # INITIALIZE diff --git a/app/views/editor/level/RelatedAchievementsView.coffee b/app/views/editor/level/RelatedAchievementsView.coffee index 598f9ddc3..e0ff97d54 100644 --- a/app/views/editor/level/RelatedAchievementsView.coffee +++ b/app/views/editor/level/RelatedAchievementsView.coffee @@ -18,7 +18,6 @@ module.exports = class RelatedAchievementsView extends CocoView @level = options.level @relatedID = @level.id @achievements = new RelatedAchievementsCollection @relatedID - console.debug @achievements @supermodel.loadCollection @achievements, 'achievements' onLoaded: -> diff --git a/test/app/models/CocoModel.spec.coffee b/test/app/models/CocoModel.spec.coffee index a2fc655bf..059eeb2cf 100644 --- a/test/app/models/CocoModel.spec.coffee +++ b/test/app/models/CocoModel.spec.coffee @@ -92,9 +92,11 @@ describe 'CocoModel', -> request = jasmine.Ajax.requests.mostRecent() expect(request).toBeUndefined() - describe 'Achievement polling', -> + xdescribe 'Achievement polling', -> NewAchievementCollection = require 'collections/NewAchievementCollection' EarnedAchievement = require 'models/EarnedAchievement' + + # TODO: Figure out how to do debounce in tests so that this test doesn't need to use keepDoingUntil it 'achievements are polled upon saving a model', (done) -> #spyOn(CocoModel, 'pollAchievements') From bdb3e14189ba933bde6e64639f085f36d6c37bd1 Mon Sep 17 00:00:00 2001 From: Nick Winter Date: Wed, 13 Aug 2014 14:26:41 -0700 Subject: [PATCH 80/83] Added missing ForkModal. --- app/templates/editor/fork-modal.jade | 17 +++++++++++ app/views/editor/ForkModal.coffee | 43 ++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+) create mode 100644 app/templates/editor/fork-modal.jade create mode 100644 app/views/editor/ForkModal.coffee diff --git a/app/templates/editor/fork-modal.jade b/app/templates/editor/fork-modal.jade new file mode 100644 index 000000000..2b4db3463 --- /dev/null +++ b/app/templates/editor/fork-modal.jade @@ -0,0 +1,17 @@ +extends /templates/modal/modal_base + +block modal-header-content + h3(data-i18n="editor.fork_title") Fork New Version + +block modal-body-content + form.form + .form-group + label(for="model-name", data-i18n="general.name") Name + input#fork-model-name(name="name", type="text").form-control + +block modal-footer-content + button.btn(data-dismiss="modal", data-i18n="common.cancel") Cancel + button.btn.btn-primary#fork-model-confirm-button(data-i18n="common.save") Save + +block modal-body-wait-content + h3(data-i18n="editor.fork_creating") Creating Fork... diff --git a/app/views/editor/ForkModal.coffee b/app/views/editor/ForkModal.coffee new file mode 100644 index 000000000..5acbaa1df --- /dev/null +++ b/app/views/editor/ForkModal.coffee @@ -0,0 +1,43 @@ +ModalView = require 'views/kinds/ModalView' +template = require 'templates/editor/fork-modal' +forms = require 'lib/forms' + +module.exports = class ForkModal extends ModalView + id: 'fork-modal' + template: template + instant: false + modalWidthPercent: 60 + + events: + 'click #fork-model-confirm-button': 'forkModel' + 'submit form': 'forkModel' + + constructor: (options) -> + super options + @editorPath = options.editorPath # like 'level' or 'thang' + @model = options.model + @modelClass = @model.constructor + + forkModel: -> + @showLoading() + forms.clearFormAlerts(@$el) + newModel = new @modelClass($.extend(true, {}, @model.attributes)) + newModel.unset '_id' + newModel.unset 'version' + newModel.unset 'creator' + newModel.unset 'created' + newModel.unset 'original' + newModel.unset 'parent' + newModel.set 'commitMessage', "Forked from #{@model.get('name')}" + newModel.set 'name', @$el.find('#fork-model-name').val() + if @model.get 'permissions' + newModel.set 'permissions', [access: 'owner', target: me.id] + newPathPrefix = "editor/#{@editorPath}/" + res = newModel.save() + return unless res + res.error => + @hideLoading() + forms.applyErrorsToForm(@$el.find('form'), JSON.parse(res.responseText)) + res.success => + @hide() + application.router.navigate(newPathPrefix + newModel.get('slug'), {trigger: true}) From 10d2a4bf8db242eddd408ff80586262957573ca2 Mon Sep 17 00:00:00 2001 From: Scott Erickson Date: Wed, 13 Aug 2014 14:40:01 -0700 Subject: [PATCH 81/83] Updated treema. --- bower.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bower.json b/bower.json index a909afb13..c4dcd249f 100644 --- a/bower.json +++ b/bower.json @@ -40,7 +40,7 @@ "jsondiffpatch": "~0.1.5", "nanoscroller": "~0.8.0", "jquery.tablesorter": "~2.15.13", - "treema": "~0.0.12", + "treema": "~0.0.14", "bootstrap": "~3.1.1", "validated-backbone-mediator": "~0.1.3", "jquery.browser": "~0.0.6", From 4f3af4d64fb0217a9cb1ad9ece3971c1d8297abe Mon Sep 17 00:00:00 2001 From: Nick Winter Date: Wed, 13 Aug 2014 14:57:01 -0700 Subject: [PATCH 82/83] Fixed #1433. --- app/templates/game-menu/choose-hero-view.jade | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/templates/game-menu/choose-hero-view.jade b/app/templates/game-menu/choose-hero-view.jade index 961ac33a2..acef1c498 100644 --- a/app/templates/game-menu/choose-hero-view.jade +++ b/app/templates/game-menu/choose-hero-view.jade @@ -2,10 +2,10 @@ h3(data-i18n="play_level.reload_title") Reload All Code? p(data-i18n="play_level.reload_really") Are you sure you want to reload this level back to the beginning? -if showDevBits - p - a(href='#', data-dismiss="modal", aria-hidden="true", data-i18n="play_level.reload_confirm").btn.btn-primary#restart-level-confirm-button Reload All +p + a(href='#', data-dismiss="modal", aria-hidden="true", data-i18n="play_level.reload_confirm").btn.btn-primary#restart-level-confirm-button Reload All +if showDevBits img(src="/images/pages/game-menu/choose-hero-stub.png") div(data-i18n="choose_hero.temp") Temp From 648e05ee343d13b22c9c583af8ee53b746dbf2b2 Mon Sep 17 00:00:00 2001 From: Nick Winter Date: Wed, 13 Aug 2014 16:39:16 -0700 Subject: [PATCH 83/83] Fixed #1124 in Aether 0.2.28. --- bower.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bower.json b/bower.json index c4dcd249f..69204f667 100644 --- a/bower.json +++ b/bower.json @@ -32,7 +32,7 @@ "firepad": "~0.1.2", "marked": "~0.3.0", "moment": "~2.5.0", - "aether": "~0.2.22", + "aether": "~0.2.28", "underscore.string": "~2.3.3", "firebase": "~1.0.2", "catiline": "~2.9.3", diff --git a/package.json b/package.json index 47aa890e8..7bf7b7893 100644 --- a/package.json +++ b/package.json @@ -66,7 +66,7 @@ "redis": "", "webworker-threads": "~0.4.11", "node-gyp": "~0.13.0", - "aether": "~0.2.22", + "aether": "~0.2.28", "JASON": "~0.1.3", "JQDeferred": "~2.1.0" },

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 JhCuj36;_W4%+%;J0V8s+Y-k< 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%0A7l1l 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