diff --git a/app/schemas/models/level.coffee b/app/schemas/models/level.coffee index eba42f599..262c02d18 100644 --- a/app/schemas/models/level.coffee +++ b/app/schemas/models/level.coffee @@ -230,12 +230,14 @@ _.extend LevelSchema.properties, title: 'Next Level', description: 'Reference to the next level players will play after beating this one.' } + employerDescription: { type:'string', format: 'markdown', title: 'Employer Description' } scripts: c.array {title: 'Scripts', description: 'An array of scripts that trigger based on what the player does and affect things outside of the core level simulation.', 'default': []}, ScriptSchema thangs: c.array {title: 'Thangs', description: 'An array of Thangs that make up the level.', 'default': []}, LevelThangSchema systems: c.array {title: 'Systems', description: 'Levels are configured by changing the Systems attached to them.', uniqueItems: true, default: []}, LevelSystemSchema # TODO: uniqueness should be based on 'original', not whole thing victory: c.object {title: 'Victory Screen', default: {}, properties: {'body': {type: 'string', format: 'markdown', title: 'Body Text', description: 'Inserted into the Victory Modal once this level is complete. Tell the player they did a good job and what they accomplished!'}, i18n: {type: 'object', format: 'i18n', props: ['body'], description: 'Help translate this victory message'}}} i18n: {type: 'object', format: 'i18n', props: ['name', 'description'], description: 'Help translate this level'} icon: {type: 'string', format: 'image-file', title: 'Icon'} + banner: {type: 'string', format: 'image-file', title: 'Banner'} goals: c.array {title: 'Goals', description: 'An array of goals which are visible to the player and can trigger scripts.'}, GoalSchema type: c.shortString(title: 'Type', description: 'What kind of level this is.', 'enum': ['campaign', 'ladder', 'ladder-tutorial']) showsGuide: c.shortString(title: 'Shows Guide', description: 'If the guide is shown at the beginning of the level.', 'enum': ['first-time', 'always']) diff --git a/app/schemas/models/level_session.coffee b/app/schemas/models/level_session.coffee index f4f629914..92ff0f5ba 100644 --- a/app/schemas/models/level_session.coffee +++ b/app/schemas/models/level_session.coffee @@ -13,7 +13,7 @@ LevelSessionPlayerSchema = c.object changes: type: 'Number' -LevelSessionLevelSchema = c.object {required: ['original', 'majorVersion']}, +LevelSessionLevelSchema = c.object {required: ['original', 'majorVersion'], links: [{rel: 'db', href: '/db/level/{(original)}/version/{(majorVersion)}'}]}, original: c.objectId({}) majorVersion: type: 'integer' diff --git a/app/styles/account/profile.sass b/app/styles/account/profile.sass index 05a06f6ee..a661ea611 100644 --- a/app/styles/account/profile.sass +++ b/app/styles/account/profile.sass @@ -127,13 +127,14 @@ text-align: center ul.links + text-align: center li.has-icon display: inline-block img margin: 0 0 10px 0 li.has-icon:not(:nth-child(5)) img - margin: 0 10px 10px 0 + margin: 0 5px 10px 5px #contact-candidate margin-top: 20px diff --git a/app/styles/base.sass b/app/styles/base.sass index eae0d473b..c873d3d0f 100644 --- a/app/styles/base.sass +++ b/app/styles/base.sass @@ -24,13 +24,16 @@ h1 h2 h3 h4 @include box-sizing(border-box) #outer-content-wrapper + background: #B4B4B4 + +#outer-content-wrapper.show-background background: #8cc63f url(/images/pages/base/repeat-tile.png) top center -#intermediate-content-wrapper - background: url(/images/pages/base/sky_repeater.png) repeat-x + #intermediate-content-wrapper + background: url(/images/pages/base/sky_repeater.png) repeat-x -#inner-content-wrapper - background: url(/images/pages/base/background_texture.png) top center no-repeat + #inner-content-wrapper + background: url(/images/pages/base/background_texture.png) top center no-repeat #front-summary-points-left width: 250px @@ -70,7 +73,7 @@ h1 h2 h3 h4 &:hover color: $white -a[data-toggle="modal"] +a cursor: pointer .share-buttons, .partner-badges diff --git a/app/styles/common/level_session_code_view.sass b/app/styles/common/level_session_code_view.sass new file mode 100644 index 000000000..270d9a0d3 --- /dev/null +++ b/app/styles/common/level_session_code_view.sass @@ -0,0 +1,25 @@ +.level-session-code-view + #level-icon + max-width: 60% + max-height: 150px + margin-right: 10px + display: inline-block + float: right + margin-bottom: 20px + + #level-meta-data + width: 35% + button + float: right + + #session-code + clear: both + margin-top: 20px + + h3 + font-family: Arial + margin: 0 + + .code + height: 600px + border: 2px solid black \ No newline at end of file diff --git a/app/templates/account/job_profile_code_modal.jade b/app/templates/account/job_profile_code_modal.jade new file mode 100644 index 000000000..6f5703ef6 --- /dev/null +++ b/app/templates/account/job_profile_code_modal.jade @@ -0,0 +1,12 @@ +extends /templates/modal/modal_base + +block modal-header-content + h3 Applicant Code for + span.spl= session.get('levelName') + +block modal-body-content + .level-session-code-view + +block modal-footer + .modal-footer + button(data-dismiss="modal", data-i18n="modal.close").btn Close diff --git a/app/templates/account/profile.jade b/app/templates/account/profile.jade index 11c0d6956..2699a3ac1 100644 --- a/app/templates/account/profile.jade +++ b/app/templates/account/profile.jade @@ -181,13 +181,12 @@ block content ul.sessions each session in sessions li - - var sessionLink = "/play/level/" + session.levelID + "?team=" + (session.team || 'humans') + (myProfile ? '' : "&session=" + session._id); - a(href=sessionLink) + a.session-link(data-session-id=session._id) span= session.levelName if session.team - span #{session.team} + span.spl - #{session.team} if session.codeLanguage != 'javascript' - span - #{{coffeescript: 'CoffeeScript', python: 'Python', lua: 'Lua', io: 'Io', clojure: 'Clojure'}[session.codeLanguage]} + span.spl - #{{coffeescript: 'CoffeeScript', python: 'Python', lua: 'Lua', io: 'Io', clojure: 'Clojure'}[session.codeLanguage]} .middle-column.full-height-column .sub-column diff --git a/app/templates/base.jade b/app/templates/base.jade index 01a67687c..6c18d420c 100644 --- a/app/templates/base.jade +++ b/app/templates/base.jade @@ -49,7 +49,7 @@ body a.header-font(href='/community', data-i18n="nav.community") Community block outer_content - #outer-content-wrapper + #outer-content-wrapper(class=showBackground ? 'show-background' : '') #intermediate-content-wrapper #inner-content-wrapper .main-content-area diff --git a/app/templates/common/level_session_code.jade b/app/templates/common/level_session_code.jade new file mode 100644 index 000000000..ad998e89d --- /dev/null +++ b/app/templates/common/level_session_code.jade @@ -0,0 +1,13 @@ +div#session-info + img(src='/file/'+levelIcon alt='levelIcon')#level-icon + div#level-meta-data + a.btn.btn-primary(href=sessionLink, target=_blank) Spectate + p!= levelDescription + +div#session-code + for spell in levelSpells + .panel.panel-success + .panel-heading + h3= spell.name + .panel-body + .code(data-height=spell.height)= spell.code \ No newline at end of file diff --git a/app/views/account/JobProfileCodeModal.coffee b/app/views/account/JobProfileCodeModal.coffee new file mode 100644 index 000000000..397de97cf --- /dev/null +++ b/app/views/account/JobProfileCodeModal.coffee @@ -0,0 +1,25 @@ +ModalView = require 'views/kinds/ModalView' +template = require 'templates/account/job_profile_code_modal' +LevelSessionCodeView = require 'views/common/LevelSessionCodeView' +console.log 'template', template + +module.exports = class JobProfileCodeModal extends ModalView + id: 'job_profile_code_modal' + template: template + modalWidthPercent: 90 + plain: true + + constructor: (options) -> + super(arguments...) + @session = options.session + + getRenderData: -> + c = super() + c.session = @session + c + + afterRender: -> + super() + codeView = new LevelSessionCodeView({session:@session}) + @insertSubView(codeView, @$el.find('.level-session-code-view')) + \ No newline at end of file diff --git a/app/views/account/profile_view.coffee b/app/views/account/profile_view.coffee index 213b89a97..ca33ba8e8 100644 --- a/app/views/account/profile_view.coffee +++ b/app/views/account/profile_view.coffee @@ -1,4 +1,4 @@ -View = require 'views/kinds/RootView' +RootView = require 'views/kinds/RootView' template = require 'templates/account/profile' User = require 'models/User' LevelSession = require 'models/LevelSession' @@ -9,6 +9,7 @@ JobProfileView = require 'views/account/job_profile_view' UserRemark = require 'models/UserRemark' forms = require 'lib/forms' ModelModal = require 'views/modal/model_modal' +JobProfileCodeModal = require './JobProfileCodeModal' class LevelSessionsCollection extends CocoCollection url: -> "/db/user/#{@userID}/level.sessions/employer" @@ -25,9 +26,11 @@ adminContacts = [ {id: '52a57252a89409700d0000d9', name: 'Ignore'} ] -module.exports = class ProfileView extends View +module.exports = class ProfileView extends RootView id: 'profile-view' template: template + showBackground: false + subscriptions: 'linkedin-loaded': 'onLinkedInLoaded' @@ -49,6 +52,7 @@ module.exports = class ProfileView extends View 'keyup .editable-profile .editable-array input': 'onEditArray' 'click .editable-profile a': 'onClickLinkWhileEditing' 'change #admin-contact': 'onAdminContactChanged' + 'click .session-link': 'onSessionLinkPressed' constructor: (options, @userID) -> @userID ?= me.id @@ -584,3 +588,9 @@ module.exports = class ProfileView extends View {name: t('account_profile.next_photo'), weight: 2, container: '#profile-photo-container', fn: modified 'photoURL'} {name: t('account_profile.next_active'), weight: 1, fn: modified 'active'} ] + + onSessionLinkPressed: (e) -> + sessionID = $(e.target).closest('.session-link').data('session-id') + session = _.find @sessions.models, (session) -> session.id is sessionID + modal = new JobProfileCodeModal({session:session}) + @openModalView modal \ No newline at end of file diff --git a/app/views/common/LevelSessionCodeView.coffee b/app/views/common/LevelSessionCodeView.coffee new file mode 100644 index 000000000..768793115 --- /dev/null +++ b/app/views/common/LevelSessionCodeView.coffee @@ -0,0 +1,50 @@ +CocoView = require 'views/kinds/CocoView' +template = require 'templates/common/level_session_code' + +Level = require 'models/Level' +LevelSession = require 'models/LevelSession' + +module.exports = class LevelSessionCodeView extends CocoView + className: 'level-session-code-view' + template: template + + constructor: (options) -> + super(options) + @session = options.session + @level = LevelSession.getReferencedModel(@session.get('level'), LevelSession.schema.properties.level) + @level.setProjection ['employerDescription', 'name', 'icon', 'banner', 'slug'] + @supermodel.loadModel @level, 'level' + + getRenderData: -> + c = super() + c.levelIcon = @level.get('banner') or @level.get('icon') + c.levelName = @level.get('name') + c.levelDescription = marked(@level.get('employerDescription') or '') + c.levelSpells = @organizeCode() + c.sessionLink = "/play/level/" + (@level.get('slug') or @level.id) + "?team=" + (@session.get('team') || 'humans') + "&session=" + @session.id + c + + afterRender: -> + super() + @$el.find('.code').each (index, codeEl) -> + height = parseInt($(codeEl).data('height')) + $(codeEl).height(height) + editor = ace.edit codeEl + editor.setReadOnly true + aceSession = editor.getSession() + aceSession.setMode 'ace/mode/javascript' + + organizeCode: -> + team = @session.get('team') or 'humans' + teamSpells = @session.get('teamSpells')[team] or [] + filteredSpells = [] + for spell in teamSpells + code = @session.getSourceFor(spell) + lines = code.split('\n').length + height = lines * 16 + 20 + filteredSpells.push { + code: code + name: spell + height: height + } + filteredSpells \ No newline at end of file diff --git a/app/views/editor/level/settings_tab_view.coffee b/app/views/editor/level/settings_tab_view.coffee index b3f99150c..21504830a 100644 --- a/app/views/editor/level/settings_tab_view.coffee +++ b/app/views/editor/level/settings_tab_view.coffee @@ -13,7 +13,7 @@ module.exports = class SettingsTabView extends View # not thangs or scripts or the backend stuff editableSettings: [ 'name', 'description', 'documentation', 'nextLevel', 'background', 'victory', 'i18n', 'icon', 'goals', - 'type', 'showsGuide' + 'type', 'showsGuide', 'banner', 'employerDescription' ] subscriptions: diff --git a/app/views/kinds/RootView.coffee b/app/views/kinds/RootView.coffee index aac5dc64f..858c9fb48 100644 --- a/app/views/kinds/RootView.coffee +++ b/app/views/kinds/RootView.coffee @@ -17,6 +17,8 @@ filterKeyboardEvents = (allowedEvents, func) -> return func(splat...) module.exports = class RootView extends CocoView + showBackground: true + events: 'click #logout-button': 'logoutAccount' 'change .language-dropdown': 'onLanguageChanged' @@ -112,6 +114,11 @@ module.exports = class RootView extends CocoView @renderScrollbar() #@$('.antiscroll-wrap').antiscroll() # not yet, buggy + getRenderData: -> + c = super() + c.showBackground = @showBackground + c + afterRender: -> super(arguments...) @chooseTab(location.hash.replace('#', '')) if location.hash diff --git a/server/levels/level_handler.coffee b/server/levels/level_handler.coffee index bcda60e1c..76d0ed8ac 100644 --- a/server/levels/level_handler.coffee +++ b/server/levels/level_handler.coffee @@ -25,6 +25,8 @@ LevelHandler = class LevelHandler extends Handler 'goals' 'type' 'showsGuide' + 'banner' + 'employerDescription' ] postEditableProperties: ['name'] diff --git a/server/users/user_handler.coffee b/server/users/user_handler.coffee index 275807ad7..fbd13ec19 100644 --- a/server/users/user_handler.coffee +++ b/server/users/user_handler.coffee @@ -233,7 +233,7 @@ UserHandler = class UserHandler extends Handler getLevelSessionsForEmployer: (req, res, userID) -> return @sendUnauthorizedError(res) unless req.user._id+'' is userID or req.user.isAdmin() or ('employer' in req.user.get('permissions')) query = creator: userID, levelID: {$in: ['gridmancer', 'greed', 'dungeon-arena', 'brawlwood', 'gold-rush']} - projection = 'levelName levelID team playtime codeLanguage submitted code totalScore' + projection = 'levelName levelID team playtime codeLanguage submitted code totalScore teamSpells level' LevelSession.find(query).select(projection).exec (err, documents) => return @sendDatabaseError(res, err) if err documents = (LevelSessionHandler.formatEntity(req, doc) for doc in documents) diff --git a/test/demo/views/common/LevelSessionCodeView.demo.coffee b/test/demo/views/common/LevelSessionCodeView.demo.coffee new file mode 100644 index 000000000..a7aa3810e --- /dev/null +++ b/test/demo/views/common/LevelSessionCodeView.demo.coffee @@ -0,0 +1,13 @@ +LevelSessionCodeView = require 'views/common/LevelSessionCodeView' +LevelSession = require 'models/LevelSession' + +levelSessionData = {"_id":"5317ad4909098828ed071f4d","level":{"original":"53173f76c269d400000543c2","majorVersion":0},"team":"humans","levelID":"dungeon-arena","levelName":"Dungeon Arena","submitted":true,"totalScore":38.4584087145667,"code":{"programmable-librarian":{"chooseAction":"// The Librarian is a spellcaster with a fireball attack\n// plus three useful spells: 'slow', 'regen', and 'haste'.\n// Slow makes a target move and attack at half speed for 5s.\n// Regen makes a target heal 10 hp/s for 10s.\n// Haste speeds up a target by 4x for 5s, once per match.\n\nvar enemies = this.getEnemies();\nif (enemies.length === 0) return; // Chill if all enemies are dead.\nvar enemy = this.getNearest(enemies);\nif (this.canCast('slow', enemy)) {\n // Slow the enemy, or chase if out of range (30m).\n this.castSlow(enemy);\n if (this.distance(enemy) <= 50)\n this.say(\"Not so fast, \" + enemy.type + \" \" + enemy.id);\n}\nelse {\n this.attack(enemy);\n}\nvar base = this.getFriends()[0];\nvar d = base.distance(enemy);\n// You can also command your troops with this.say():\n//this.say(\"Defend!\", {targetPos: {x: 30, y: 30}}));\n//this.say(\"Attack!\", {target: enemy});\n//this.say(\"Move!\", {targetPos: {x: 50, y: 40});\n"},"human-base":{"chooseAction":"// This is the code for your base. Decide which unit to build each frame.\n// Units you build will go into the this.built array.\n// Destroy the enemy base within 60 seconds!\n// Check out the Guide at the top for more info.\n\n// CHOOSE YOUR HERO! You can only build one hero.\nvar hero;\n//hero = 'tharin'; // A fierce knight with battlecry abilities.\nhero = 'hushbaum'; // A fiery spellcaster hero.\n\nif(hero && !this.builtHero) {\n this.builtHero = this.build(hero);\n return;\n}\n\n// Soldiers are hard-to-kill, low damage melee units with 2s build cooldown.\n// Archers are fragile but deadly ranged units with 2.5s build cooldown.\nvar buildOrder = ['soldier', 'soldier', 'soldier', 'soldier', 'archer'];\nvar type = buildOrder[this.built.length % buildOrder.length];\n//this.say('Unit #' + this.built.length + ' will be a ' + type);\nthis.build(type);"},"hushbaum":{"chooseAction":"var enemy = this.getNearestEnemy();\nif (enemy) {\n if (!enemy.hasEffect('slow')) {\n this.say(\"Not so fast, \" + enemy.type + \" \" + enemy.id);\n this.castSlow(enemy);\n }\n else {\n this.attack(enemy);\n }\n}\nelse {\n this.move({x: 70, y: 30});\n}\n"},"tharin":{"chooseAction":"var enemies = this.getEnemies();\nvar enemy = this.getNearest(enemies);\nif (!this.getCooldown('warcry')) {\n this.warcry();\n}\nelse if (enemy) {\n this.attack(enemy);\n}\nelse {\n this.move({x: 10, y: 30});\n}\n"},"tharin-1":{"chooseAction":"var __interceptThis=(function(){var G=this;return function($this,sandbox){if($this==G){return sandbox;}return $this;};})();\nreturn (function (__global) {\n var tmp0, tmp1;\n tmp1 = function () {\n _aether.logCallStart(this._aetherUserInfo); var enemies, enemy, tmp2, tmp3, tmp4, tmp5, tmp6, tmp7, tmp8, tmp9, tmp10, tmp11, tmp12, tmp13, tmp14, tmp15, tmp16, tmp17, tmp18, tmp19, tmp20, tmp21, tmp22, tmp23, tmp24, tmp25, tmp26;\n tmp2 = 'use strict';\n tmp3 = __interceptThis(this, __global);\n tmp4 = 'getEnemies';\n _aether.logStatementStart([{ofs: 0, row: 0, col: 0}, {ofs: 32, row: 0, col: 32}]); enemies = tmp3[tmp4](); _aether.vars['enemies'] = typeof enemies == 'undefined' ? undefined : enemies; _aether.vars['enemy'] = typeof enemy == 'undefined' ? undefined : enemy; _aether.vars['chooseAction'] = typeof chooseAction == 'undefined' ? undefined : chooseAction; _aether.logStatement([{ofs: 0, row: 0, col: 0}, {ofs: 32, row: 0, col: 32}], \"var enemies = this.getEnemies();\", this._aetherUserInfo);\n tmp5 = __interceptThis(this, __global);\n tmp6 = 'getNearest';\n tmp7 = enemies;\n _aether.logStatementStart([{ofs: 33, row: 1, col: 0}, {ofs: 70, row: 1, col: 37}]); enemy = tmp5[tmp6](tmp7); _aether.vars['enemies'] = typeof enemies == 'undefined' ? undefined : enemies; _aether.vars['enemy'] = typeof enemy == 'undefined' ? undefined : enemy; _aether.vars['chooseAction'] = typeof chooseAction == 'undefined' ? undefined : chooseAction; _aether.logStatement([{ofs: 33, row: 1, col: 0}, {ofs: 70, row: 1, col: 37}], \"var enemy = this.getNearest(enemies);\", this._aetherUserInfo);\n tmp10 = __interceptThis(this, __global);\n tmp11 = 'getCooldown';\n _aether.logStatementStart([{ofs: 93, row: 2, col: 22}, {ofs: 101, row: 2, col: 30}]); tmp12 = 'warcry'; _aether.vars['enemies'] = typeof enemies == 'undefined' ? undefined : enemies; _aether.vars['enemy'] = typeof enemy == 'undefined' ? undefined : enemy; _aether.vars['chooseAction'] = typeof chooseAction == 'undefined' ? undefined : chooseAction; _aether.logStatement([{ofs: 93, row: 2, col: 22}, {ofs: 101, row: 2, col: 30}], \"'warcry'\", this._aetherUserInfo);\n _aether.logStatementStart([{ofs: 76, row: 2, col: 5}, {ofs: 102, row: 2, col: 31}]); tmp9 = tmp10[tmp11](tmp12); _aether.vars['enemies'] = typeof enemies == 'undefined' ? undefined : enemies; _aether.vars['enemy'] = typeof enemy == 'undefined' ? undefined : enemy; _aether.vars['chooseAction'] = typeof chooseAction == 'undefined' ? undefined : chooseAction; _aether.logStatement([{ofs: 76, row: 2, col: 5}, {ofs: 102, row: 2, col: 31}], \"this.getCooldown('warcry')\", this._aetherUserInfo);\n _aether.logStatementStart([{ofs: 75, row: 2, col: 4}, {ofs: 102, row: 2, col: 31}]); tmp8 = !tmp9; _aether.vars['enemies'] = typeof enemies == 'undefined' ? undefined : enemies; _aether.vars['enemy'] = typeof enemy == 'undefined' ? undefined : enemy; _aether.vars['chooseAction'] = typeof chooseAction == 'undefined' ? undefined : chooseAction; _aether.logStatement([{ofs: 75, row: 2, col: 4}, {ofs: 102, row: 2, col: 31}], \"!this.getCooldown('warcry')\", this._aetherUserInfo);\n if (tmp8) {\n tmp13 = __interceptThis(this, __global);\n tmp14 = 'warcry';\n _aether.logStatementStart([{ofs: 110, row: 3, col: 4}, {ofs: 123, row: 3, col: 17}]); tmp15 = tmp13[tmp14](); _aether.vars['enemies'] = typeof enemies == 'undefined' ? undefined : enemies; _aether.vars['enemy'] = typeof enemy == 'undefined' ? undefined : enemy; _aether.vars['chooseAction'] = typeof chooseAction == 'undefined' ? undefined : chooseAction; _aether.logStatement([{ofs: 110, row: 3, col: 4}, {ofs: 123, row: 3, col: 17}], \"this.warcry()\", this._aetherUserInfo);\n } else {\n tmp16 = enemy;\n if (tmp16) {\n tmp17 = __interceptThis(this, __global);\n tmp18 = 'attack';\n tmp19 = enemy;\n _aether.logStatementStart([{ofs: 149, row: 6, col: 4}, {ofs: 167, row: 6, col: 22}]); tmp20 = tmp17[tmp18](tmp19); _aether.vars['enemies'] = typeof enemies == 'undefined' ? undefined : enemies; _aether.vars['enemy'] = typeof enemy == 'undefined' ? undefined : enemy; _aether.vars['chooseAction'] = typeof chooseAction == 'undefined' ? undefined : chooseAction; _aether.logStatement([{ofs: 149, row: 6, col: 4}, {ofs: 167, row: 6, col: 22}], \"this.attack(enemy)\", this._aetherUserInfo);\n } else {\n tmp21 = __interceptThis(this, __global);\n tmp22 = 'move';\n _aether.logStatementStart([{ofs: 196, row: 9, col: 18}, {ofs: 198, row: 9, col: 20}]); tmp24 = 10; _aether.vars['enemies'] = typeof enemies == 'undefined' ? undefined : enemies; _aether.vars['enemy'] = typeof enemy == 'undefined' ? undefined : enemy; _aether.vars['chooseAction'] = typeof chooseAction == 'undefined' ? undefined : chooseAction; _aether.logStatement([{ofs: 196, row: 9, col: 18}, {ofs: 198, row: 9, col: 20}], \"10\", this._aetherUserInfo);\n _aether.logStatementStart([{ofs: 203, row: 9, col: 25}, {ofs: 205, row: 9, col: 27}]); tmp25 = 30; _aether.vars['enemies'] = typeof enemies == 'undefined' ? undefined : enemies; _aether.vars['enemy'] = typeof enemy == 'undefined' ? undefined : enemy; _aether.vars['chooseAction'] = typeof chooseAction == 'undefined' ? undefined : chooseAction; _aether.logStatement([{ofs: 203, row: 9, col: 25}, {ofs: 205, row: 9, col: 27}], \"30\", this._aetherUserInfo);\n _aether.logStatementStart([{ofs: 192, row: 9, col: 14}, {ofs: 206, row: 9, col: 28}]); tmp23 = {\n x: tmp24,\n y: tmp25\n }; _aether.vars['enemies'] = typeof enemies == 'undefined' ? undefined : enemies; _aether.vars['enemy'] = typeof enemy == 'undefined' ? undefined : enemy; _aether.vars['chooseAction'] = typeof chooseAction == 'undefined' ? undefined : chooseAction; _aether.logStatement([{ofs: 192, row: 9, col: 14}, {ofs: 206, row: 9, col: 28}], \"{x: 10, y: 30}\", this._aetherUserInfo);\n _aether.logStatementStart([{ofs: 182, row: 9, col: 4}, {ofs: 207, row: 9, col: 29}]); tmp26 = tmp21[tmp22](tmp23); _aether.vars['enemies'] = typeof enemies == 'undefined' ? undefined : enemies; _aether.vars['enemy'] = typeof enemy == 'undefined' ? undefined : enemy; _aether.vars['chooseAction'] = typeof chooseAction == 'undefined' ? undefined : chooseAction; _aether.logStatement([{ofs: 182, row: 9, col: 4}, {ofs: 207, row: 9, col: 29}], \"this.move({x: 10, y: 30})\", this._aetherUserInfo);\n }\n }\n _aether.logCallEnd(); return;\n };\n tmp0 = 'chooseAction';\n __global[tmp0] = tmp1;\n}(this));"},"programmable-tharin":{"chooseAction":"/*this.getFriends();\nthis.attack(this.getEnemies()[0]);\nreturn;\n*/\n \n\n/* TODO:\n If they fully base race us, we actually do want to produce archers since they DPS faster\n The effective DPS on soldiers is better if they attack us\n but worse if they straight race us\n\n //not sure if this is good but...\n if they're attacking our base with a small number of units\n we should make archers and get them to defend\n*/\n/*\nreturn;\n// Tharin is a melee fighter with shield, warcry, and terrify skills.\n// this.shield() lets him take one-third damage while defending.\n// this.warcry() gives allies within 10m 30% haste for 5s, every 10s.\n// this.terrify() sends foes within 30m fleeing for 5s, once per match.\nvar friends = this.getFriends();\nvar enemies = this.getEnemies();\nif (enemies.length === 0) return; // Chill if all enemies are dead.\nvar enemy = this.getNearest(enemies);\nvar furthestFriendX = 30;\nfor (var i = 0; i < friends.length; ++i) {\n var friend = friends[i];\n furthestFriendX = Math.max(friend.pos.x, furthestFriendX);\n} \nif (!this.getCooldown('warcry') && friends.length > 5) {\n this.warcry();\n} \nelse if ((this.now() > 15 || this.health < 150) && !this.getCooldown('terrify')) {\n this.terrify();\n}\nelse if (this.health < 75 && this.pos.x > furthestFriendX - 5) {\n this.move({x: 10, y: 27});\n}\nelse if (this.pos.x > furthestFriendX - 1 && this.now() < 50) {\n this.shield();\n}\nelse {\n this.attack(enemy);\n}\nthis.say(\"Defend!\", {targetPos: {x: 30, y: Infinity}});\n\n// You can also command your troops with this.say():\n//this.say(\"Defend!\", {targetPos: {x: 30, y: 30}}));\n//this.say(\"Attack!\", {target: enemy});\n//this.say(\"Move!\", {targetPos: {x: 40, y: 40});\n\n// You can store state on this across frames:\n//this.lastHealth = this.health;\n*/"}},"teamSpells":{"ogres":["programmable-brawler/chooseAction","programmable-shaman/chooseAction","ogre-base/chooseAction"],"humans":["programmable-librarian/chooseAction","programmable-tharin/chooseAction","human-base/chooseAction"]},"submittedCodeLanguage":"javascript","playtime":9753,"codeLanguage":"javascript"} +levelData = {"_id":"53c997066567c600002a43d0","name":"Dungeon Arena","icon":"db/level/53173f76c269d400000543c2/11_dungeon.png","banner":"db/level/53173f76c269d400000543c2/dungeon_arena.png","employerDescription":"Players:\n* Attempt to destroy the enemy base.\n* Choose and control heroes to attack with.\n* Choose which types of lesser units to build and have limited control over them.\n* Try to write strategies that counter other enemy strategies.\n* Play on a small map.","systems":[],"thangs":[],"scripts":[],"documentation":{"generalArticles":[],"specificArticles":[]},"description":"This level is indescribably flarmy!","version":{"minor":0,"major":0,"isLatestMajor":true,"isLatestMinor":true}}; + +module.exports = -> + session = new LevelSession(levelSessionData) + v = new LevelSessionCodeView({session:session}) + request = jasmine.Ajax.requests.mostRecent() + request.response({status: 200, responseText: JSON.stringify(levelData)}) + console.log 'okay should be fine' + v diff --git a/test/demo/views/user/JobProfileView.demo.coffee b/test/demo/views/user/JobProfileView.demo.coffee new file mode 100644 index 000000000..17ac8767b --- /dev/null +++ b/test/demo/views/user/JobProfileView.demo.coffee @@ -0,0 +1,576 @@ +ProfileView = require 'views/account/profile_view' + +responses = + '/db/user/joe/nameToID':'512ef4805a67a8c507000001' + + '/db/user/512ef4805a67a8c507000001': { + "_id": "512ef4805a67a8c507000001", + "__v": 47, + "email": "livelily@gmail.com", + "emailSubscriptions": [ + "announcement", + "notification", + "developer", + "level_creator", + "tester", + "article_editor", + "translator", + "support" + ], + "facebookID": "4301215", + "firstName": "Nick", + "gender": "male", + "lastName": "Winter", + "name": "Nick!", + "photoURL": "db/user/512ef4805a67a8c507000001/nick_wizard.png", + "volume": 0, + "wizardColor1": 0.4, + "testGroupNumber": 217, + "mailChimp": { + "leid": "70264209", + "euid": "c4418e2abd", + "email": "livelily@gmail.com" + }, + "hourOfCode": true, + "hourOfCodeComplete": true, + "signedCLA": "Fri Jan 03 2014 14:40:18 GMT-0800 (PST)", + "wizard": { + "colorConfig": { + "boots": { + "lightness": 0.1647058823529412, + "saturation": 0.023809523809523805, + "hue": 0 + }, + "spell": { + "hue": 0.7490196078431373, + "saturation": 0.4106280193236715, + "lightness": 0.5941176470588235 + }, + "cloud": { + "lightness": 0.14, + "saturation": 1, + "hue": 0 + }, + "clothes": { + "lightness": 0.1411764705882353, + "saturation": 0, + "hue": 0 + }, + "trim": { + "hue": 0.5, + "saturation": 0.009900990099009936, + "lightness": 0.19803921568627453 + } + } + }, + "aceConfig": { + "liveCompletion": true, + "indentGuides": true, + "invisibles": true, + "keyBindings": "emacs", + "behaviors": true, + "language": "javascript" + }, + "lastLevel": "drink-me", + "gplusID": "110703832132860599877", + "jobProfile": { + "photoURL": "db/user/512ef4805a67a8c507000001/nick_bokeh_small.jpg", + "links": [ +# { +# "name": "Twitter", +# "link": "https://twitter.com/nwinter" +# }, +# { +# "name": "Facebook", +# "link": "https://www.facebook.com/nwinter" +# }, + { + "name": "LinkedIn", + "link": "https://www.linkedin.com/in/nwinter" + }, + { + "name": "Blog", + "link": "http://blog.nickwinter.net/" + }, + { + "name": "Personal Site", + "link": "http://www.nickwinter.net/" + }, + { + "name": "GitHub", + "link": "https://github.com/nwinter" + }, + { + "name": "G+", + "link": "https://plus.google.com/u/0/+NickWinter" + } + ], + "projects": [ + { + "name": "The Motivation Hacker", + "description": "I wrote a book. *The Motivation Hacker* shows you how to summon extreme amounts of motivation to accomplish anything you can think of. From precommitment to rejection therapy, this is your field guide to getting yourself to want to do everything you always wanted to want to do.", + "picture": "db/user/512ef4805a67a8c507000001/the_motivation_hacker_thumb.jpg", + "link": "http://www.nickwinter.net/motivation-hacker" + }, + { + "name": "Quantified Mind", + "description": "Quantified Mind is a tool that quickly, reliably, and comprehensively measures your basic cognitive abilities. We've adapted tests used by psychologists to a practical web application that you can use whenever, wherever, and as often as you want.", + "picture": "db/user/512ef4805a67a8c507000001/screenshot.png", + "link": "http://www.quantified-mind.com/" + }, + { + "link": "https://github.com/nwinter/telepath-logger", + "name": "Telepath", + "description": "A happy Mac keylogger for Quantified Self purposes. It also now serves as a time lapse heads-up-display thing. I used it to make a [time-lapse video of myself working an 120-hour workweek](http://blog.nickwinter.net/the-120-hour-workweek-epic-coding-time-lapse).", + "picture": "db/user/512ef4805a67a8c507000001/687474703a2f2f63646e2e736574742e636f6d2f696d616765732f757365722f32303133313131303139353534393937375a30356665633666623234623937323263373733636231303537613130626336365f66726f6e742e6a7067" + } + ], + "education": [ + { + "school": "Oberlin College", + "degree": "BA Computer Science, Mathematics, and East Asian Studies, highest honors in CS", + "duration": "Aug 2004 - May 2008", + "description": "Cofounded Oberlin Street Art and did all sorts of crazy missions without telling anyone about it." + } + ], + "work": [ + { + "employer": "CodeCombat", + "role": "Cofounder", + "duration": "Jan 2013 - present", + "description": "Programming a programming game for learning programming to be a programming programmer of programmatic programs." + }, + { + "employer": "Skritter", + "role": "Cofounder", + "duration": "May 2008 - present", + "description": "I coded, I designed, I marketed, I businessed, I wrote, I drudged, I cheffed, I laughed, I cried. But mostly I emailed. God, so much email." + } + ], + "visa": "Authorized to work in the US", + "longDescription": "I cofounded Skritter, am working on CodeCombat, helped with Quantified Mind, live in San Francisco, went to Oberlin College, wrote a book about motivation hacking, and can do anything.\n\nI like hacking on startups, pigs with dogs for feet, and Smash Bros. I dislike shoes, mortality, and Java.\n\nDo you love hiring renegade maverick commandos who can't abide the system? Are you looking to hire the sample job profile candidate of the job profile system? Are you just testing this thing? If your answer is yes, yes yes!–then let us talk.", + "shortDescription": "Maniac two-time startup cofounder looking to test the system and see what a job profile might look like. Can't nobody hold him down.", + "experience": 6, + "skills": [ + "python", + "coffeescript", + "node", + "ios", + "objective-c", + "javascript", + "app-engine", + "mongodb", + "web dev", + "django", + "backbone", + "chinese", + "qs", + "writing" + ], + "country": "USA", + "city": "San Francisco", + "active": false, + "lookingFor": "Full-time", + "name": "Nick Winter", + "updated": "2014-07-12T01:48:42.980Z", + "jobTitle": "Mutant Code Gorilla" + }, + "jobProfileApproved": false, + "emails": { + "anyNotes": { + "enabled": true + }, + "generalNews": { + "enabled": true + }, + "archmageNews": { + "enabled": true + }, + "artisanNews": { + "enabled": true + }, + "adventurerNews": { + "enabled": true + }, + "scribeNews": { + "enabled": true + }, + "diplomatNews": { + "enabled": true + }, + "ambassadorNews": { + "enabled": true + } + }, + "activity": { + "viewed_by_employer": { + "last": "2014-06-19T20:21:43.747Z", + "count": 6, + "first": "2014-06-12T01:37:38.278Z" + }, + "view_candidate": { + "first": "2014-06-10T19:59:30.773Z", + "count": 661, + "last": "2014-07-11T02:14:40.131Z" + }, + "login": { + "first": "2014-06-10T21:55:08.968Z", + "count": 22, + "last": "2014-07-16T16:32:31.661Z" + }, + "contacted_by_employer": { + "first": "2014-06-19T20:24:51.870Z", + "count": 1, + "last": "2014-06-19T20:24:51.870Z" + } + }, + "slug": "nick", + "jobProfileNotes": "Nick used to be the **#1 Brawlwood player** on CodeCombat. He wrote most of the game engine, so that's totally cheating. Now other players have surpassed him by emulating his moves and improving his strategy. If you like the sixth Rocky movie, you might still want to hire this aging hero even in his fading senescence.", + "simulatedFor": 2363, + "simulatedBy": 103674, + "preferredLanguage": "en-US", + "anonymous": false, + "permissions": [ + "admin" + ], + "autocastDelay": 90019001, + "music": false, + "dateCreated": "2013-02-28T06:09:04.743Z" + }, + + '/db/user/512ef4805a67a8c507000001/level.sessions/employer': [ + { + "_id": "53179b49b483edfcdb7ef13e", + "level": { + "original": "53173f76c269d400000543c2", + "majorVersion": 0 + }, + "code": { + }, + "submitted": false, + "teamSpells": { + "ogres": [ + "programmable-brawler/chooseAction", + "programmable-shaman/chooseAction", + "ogre-base/chooseAction" + ], + "humans": [ + "programmable-librarian/chooseAction", + "programmable-tharin/chooseAction", + "human-base/chooseAction" + ] + }, + "levelID": "dungeon-arena", + "levelName": "Dungeon Arena", + "submittedCodeLanguage": "javascript", + "playtime": 33, + "codeLanguage": "javascript" + }, + { + "_id": "53336ee91506ed33756f73e5", + "level": { + "original": "533353722a61b7ca6832840c", + "majorVersion": 0 + }, + "code": { + }, + "teamSpells": { + "humans": [ + "programmable-coin/chooseAction", + "tharin/chooseAction", + "wizard-purple/chooseAction" + ] + }, + "levelID": "gold-rush", + "levelName": "Resource gathering multiplayer", + "submittedCodeLanguage": "javascript", + "playtime": 0, + "codeLanguage": "javascript" + }, + { + "_id": "52ae32cbef42c52f1300000d", + "level": { + "original": "52ae2460ef42c52f13000008", + "majorVersion": 0 + }, + "levelID": "gridmancer", + "levelName": "Gridmancer", + "code": { + }, + "teamSpells": { + "humans": [ + "thoktar" + ] + }, + "submitted": false, + "submittedCodeLanguage": "javascript", + "playtime": 302, + "codeLanguage": "javascript" + }, + { + "_id": "5334901f0a0f9b286f57382c", + "level": { + "original": "533353722a61b7ca6832840c", + "majorVersion": 0 + }, + "team": "humans", + "code": { + }, + "teamSpells": { + "common": [ + "coin-generator-9000/chooseAction" + ], + "humans": [ + "tharin/chooseAction" + ], + "ogres": [ + "mak-fod/chooseAction" + ] + }, + "levelID": "gold-rush", + "levelName": "Gold Rush", + "totalScore": 39.23691444835561, + "submitted": true, + "submittedCodeLanguage": "javascript", + "playtime": 1158, + "codeLanguage": "javascript" + }, + { + "_id": "52dea9b77e486eeb97000001", + "level": { + "original": "52d97ecd32362bc86e004e87", + "majorVersion": 0 + }, + "levelID": "brawlwood", + "levelName": "Brawlwood", + "code": { + }, + "totalScore": 24.138610165979667, + "teamSpells": { + "humans": [ + "programmable-artillery/chooseAction", + "programmable-artillery/hear", + "programmable-soldier/chooseAction", + "programmable-soldier/hear", + "s-arrow-tower/chooseAction", + "programmable-archer/chooseAction", + "programmable-archer/hear", + "human-base/chooseAction", + "human-base/hear" + ], + "ogres": [ + "programmable-shaman/chooseAction", + "programmable-shaman/hear", + "n-beam-tower/chooseAction", + "programmable-thrower/chooseAction", + "programmable-thrower/hear", + "programmable-munchkin/chooseAction", + "programmable-munchkin/hear", + "ogre-base/chooseAction", + "ogre-base/hear" + ] + }, + "team": "humans", + "submitted": true, + "submittedCodeLanguage": "javascript", + "playtime": 0, + "codeLanguage": "javascript" + }, + { + "_id": "535701331bfa9bba14b5e03d", + "level": { + "original": "53558b5a9914f5a90d7ccddb", + "majorVersion": 0 + }, + "team": "ogres", + "levelID": "greed", + "levelName": "Greed", + "code": { + }, + "teamSpells": { + "humans": [ + "human-base/chooseAction" + ], + "ogres": [ + "ogre-base/chooseAction" + ], + "common": [ + "well/chooseAction" + ] + }, + "totalScore": 36.77589873873074, + "submitted": true, + "submittedCodeLanguage": "javascript", + "playtime": 12893, + "codeLanguage": "javascript" + }, + { + "_id": "5356fc2e1bfa9bba14b5e039", + "level": { + "original": "53558b5a9914f5a90d7ccddb", + "majorVersion": 0 + }, + "team": "humans", + "levelID": "greed", + "levelName": "Greed", + "code": { + }, + "teamSpells": { + "humans": [ + "human-base/chooseAction" + ], + "ogres": [ + "ogre-base/chooseAction" + ], + "common": [ + "well/chooseAction" + ] + }, + "totalScore": 31.538998178536794, + "submitted": true, + "submittedCodeLanguage": "javascript", + "playtime": 15648, + "codeLanguage": "javascript" + }, + { + "_id": "52fd5bf7e3c53130231726e1", + "level": { + "original": "52d97ecd32362bc86e004e87", + "majorVersion": 0 + }, + "team": "ogres", + "levelID": "brawlwood", + "levelName": "Brawlwood", + "submitted": true, + "totalScore": 53.73511062513137, + "teamSpells": { + "humans": [ + "programmable-artillery/chooseAction", + "programmable-artillery/hear", + "programmable-soldier/chooseAction", + "programmable-soldier/hear", + "s-arrow-tower/chooseAction", + "programmable-archer/chooseAction", + "programmable-archer/hear", + "human-base/chooseAction", + "human-base/hear" + ], + "ogres": [ + "programmable-shaman/chooseAction", + "programmable-shaman/hear", + "n-beam-tower/chooseAction", + "programmable-thrower/chooseAction", + "programmable-thrower/hear", + "programmable-munchkin/chooseAction", + "programmable-munchkin/hear", + "ogre-base/chooseAction", + "ogre-base/hear" + ] + }, + "code": { + }, + "submittedCodeLanguage": "javascript", + "playtime": 178, + "codeLanguage": "javascript" + }, + { + "_id": "5317ad4909098828ed071f4d", + "level": { + "original": "53173f76c269d400000543c2", + "majorVersion": 0 + }, + "team": "humans", + "levelID": "dungeon-arena", + "levelName": "Dungeon Arena", + "submitted": true, + "totalScore": 38.19039674380126, + "code": { + }, + "teamSpells": { + "ogres": [ + "programmable-brawler/chooseAction", + "programmable-shaman/chooseAction", + "ogre-base/chooseAction" + ], + "humans": [ + "programmable-librarian/chooseAction", + "programmable-tharin/chooseAction", + "human-base/chooseAction" + ] + }, + "submittedCodeLanguage": "javascript", + "playtime": 9753, + "codeLanguage": "javascript" + }, + { + "_id": "53361c80948ad7a777a10d9c", + "level": { + "original": "533353722a61b7ca6832840c", + "majorVersion": 0 + }, + "team": "ogres", + "levelID": "gold-rush", + "levelName": "Gold Rush", + "code": { + }, + "teamSpells": { + "common": [ + "coin-generator-9000/chooseAction" + ], + "humans": [ + "tharin/chooseAction" + ], + "ogres": [ + "mak-fod/chooseAction" + ] + }, + "totalScore": 40.73558595296533, + "submitted": true, + "submittedCodeLanguage": "javascript", + "playtime": 1014, + "codeLanguage": "javascript" + }, + { + "_id": "531920069f44be00001a7aef", + "level": { + "original": "53173f76c269d400000543c2", + "majorVersion": 0 + }, + "team": "ogres", + "levelID": "dungeon-arena", + "levelName": "Dungeon Arena", + "submitted": true, + "totalScore": 26.50666470188054, + "code": { + }, + "teamSpells": { + "ogres": [ + "programmable-brawler/chooseAction", + "programmable-shaman/chooseAction", + "ogre-base/chooseAction" + ], + "humans": [ + "programmable-librarian/chooseAction", + "programmable-tharin/chooseAction", + "human-base/chooseAction" + ] + }, + "submittedCodeLanguage": "javascript", + "playtime": 1786, + "codeLanguage": "javascript" + } + ] + +module.exports = -> + me.isAdmin = -> false + me.set('permissions', ['employer']) + v = new ProfileView({}, 'joe') + for url, responseBody of responses + requests = jasmine.Ajax.requests.filter(url) + if not requests.length + console.error "could not find response for <#{url}>", responses + continue + request = requests[0] + request.response({status: 200, responseText: JSON.stringify(responseBody)}) + # v.$el = v.$el.find('.main-content-area') + v