This commit is contained in:
Scott Erickson 2014-07-17 16:31:03 -07:00
commit 2b4b91733c
13 changed files with 488 additions and 91 deletions

View file

@ -82,7 +82,7 @@ module.exports = CocoSprite = class CocoSprite extends CocoClass
if @thangType.isFullyLoaded()
@setupSprite()
else
@thangType.fetch()
@thangType.fetch() unless @thangType.loading
@listenToOnce(@thangType, 'sync', @setupSprite)
setupSprite: ->
@ -442,7 +442,7 @@ module.exports = CocoSprite = class CocoSprite extends CocoClass
console.warn 'Cannot show action', action, 'for', @thangType.get('name'), 'because it DNE' unless @warnedFor[action]
@warnedFor[action] = true
return if @action is 'idle' then null else 'idle'
action = 'break' if @actions.break? and @thang?.erroredOut
#action = 'break' if @actions.break? and @thang?.erroredOut # This makes it looks like it's dead when it's not: bad in Brawlwood.
action = 'die' if @actions.die? and thang?.health? and thang.health <= 0
@actions[action]

View file

@ -115,7 +115,8 @@ module.exports = Surface = class Surface extends CocoClass
setWorld: (@world) ->
@worldLoaded = true
@world.getFrame(Math.min(@getCurrentFrame(), @world.totalFrames - 1)).restoreState() unless @options.choosing
lastFrame = Math.min(@getCurrentFrame(), @world.totalFrames - 1)
@world.getFrame(lastFrame).restoreState() unless @options.choosing
@spriteBoss.world = @world
@showLevel()
@ -241,7 +242,7 @@ module.exports = Surface = class Surface extends CocoClass
@onFrameChanged()
getCurrentFrame: ->
return Math.max(0, Math.min(Math.floor(@currentFrame), @world.totalFrames - 1))
return Math.max(0, Math.min(Math.floor(@currentFrame), @world.frames.length - 1))
getProgress: -> @currentFrame / @world.totalFrames

View file

@ -186,7 +186,7 @@ module.exports = nativeDescription: "Português (Portugal)", englishDescription:
error_saving: "Erro ao Guardar"
saved: "Alterações Guardadas"
password_mismatch: "As palavras-passe não coincidem."
# password_repeat: "Please repeat your password."
password_repeat: "Por favor repita a sua palavra-passe."
job_profile: "Perfil de Emprego"
job_profile_approved: "O seu perfil de emprego foi aprovado pelo CodeCombat. Os empregadores poderão vê-lo até que o defina como inativo ou não o tenha alterado à 4 semanas."
job_profile_explanation: "Olá! Preencha isto e entraremos em contacto consigo sobre encontrar um emprego de desenvolvedor de software para si."
@ -327,7 +327,7 @@ module.exports = nativeDescription: "Português (Portugal)", englishDescription:
# pass_screen_blurb: "Review each candidate's code before reaching out. One employer found that 5x as many of our devs passed their technical screen than hiring from Hacker News."
# make_hiring_easier: "Make my hiring easier, please."
what: "O que é o CodeCombat?"
what_blurb: "O CodeCombat é um jogo de programação, no navegador e multijogador. Os jogadores escrevem código para controlar as forças deles em batalha contra outros desenvolvedores. Nós suportamos JavaScript, Python, Lua, Clojure, CoffeeScript e Io."
what_blurb: "O CodeCombat é um jogo de programação, no navegador e multijogador. Os jogadores escrevem código para controlar as forças deles em batalha contra outros desenvolvedores. Os nossos jogadores têm experiência com todos os conceitos tecnológicos principais."
cost: "Quanto é que cobramos?"
cost_blurb: "Cobramos 15% do salário do primeiro ano e ofereçemos uma garantia de devolução de 100% do dinheiro durante 90 dias. Não cobramos por candidatos que já estejam a ser ativamente entrevistados na sua companhia."
candidate_name: "Nome"
@ -503,13 +503,13 @@ module.exports = nativeDescription: "Português (Portugal)", englishDescription:
pick_a_terrain: "Escolha Um Terreno"
small: "Pequeno"
grassy: "Com Relva"
# fork_title: "Fork New Version"
# fork_creating: "Creating Fork..."
fork_title: "Bifurcar Nova Versão"
fork_creating: "A Criar Bifurcação..."
# randomize: "Randomize"
# more: "More"
# wiki: "Wiki"
# live_chat: "Live Chat"
level_some_options: "Algumas opções?"
more: "Mais"
wiki: "Wiki"
live_chat: "Chat Ao Vivo"
level_some_options: "Algumas Opções?"
level_tab_thangs: "Thangs"
level_tab_scripts: "Scripts"
level_tab_settings: "Configurações"
@ -522,19 +522,19 @@ module.exports = nativeDescription: "Português (Portugal)", englishDescription:
# delete: "Delete"
# duplicate: "Duplicate"
level_settings_title: "Configurações"
level_component_tab_title: "Componentes atuais"
level_component_btn_new: "Cria um novo Componente"
level_systems_tab_title: "Sistemas atuais"
level_systems_btn_new: "Cria um novo Sistema"
level_systems_btn_add: "Adiciona um Sistema"
level_component_tab_title: "Componentes Atuais"
level_component_btn_new: "Criar Novo Componente"
level_systems_tab_title: "Sistemas Atuais"
level_systems_btn_new: "Cria Novo Sistema"
level_systems_btn_add: "Adicionar Sistema"
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_settings: "Configurações"
level_system_edit_title: "Editar Sistema"
create_system_title: "Criar novo Sistema"
new_component_title: "Criar novo Componente"
create_system_title: "Criar Novo Sistema"
new_component_title: "Criar Novo Componente"
new_component_field_system: "Sistema"
new_article_title: "Criar um Novo Artigo"
new_thang_title: "Criar um Novo Tipo de Thang"
@ -559,20 +559,20 @@ module.exports = nativeDescription: "Português (Portugal)", englishDescription:
name: "Nome"
body: "Corpo"
version: "Versão"
commit_msg: "Mensagem de Commit"
# version_history: "Version History"
version_history_for: "Histórico de versões por: "
commit_msg: "Enviar Mensagem"
version_history: "Histórico de Versões"
version_history_for: "Histórico de Versões para: "
result: "Resultado"
results: "Resultados"
description: "Descrição"
or: "ou"
# subject: "Subject"
subject: "Assunto"
email: "E-mail"
password: "Palavra-passe"
message: "Mensagem"
code: "Código"
ladder: "Classificação"
when: "quando"
when: "Quando"
opponent: "Adversário"
rank: "Classificação"
score: "Resultado"
@ -768,17 +768,17 @@ module.exports = nativeDescription: "Português (Portugal)", englishDescription:
ambassador_title_description: "(Suporte)"
ladder:
please_login: "Por favor, faz log in antes de jogar um jogo para o campeonato."
my_matches: "Os meus jogos"
please_login: "Por favor inicie sessão antes de jogar um jogo do campeonato."
my_matches: "Os Meus Jogos"
simulate: "Simular"
simulation_explanation: "Simulando jogos podes fazer com que o teu jogo seja classificado mais rapidamente!"
simulation_explanation: "Ao simular jogos pode ter o seu jogo classificado mais rapidamente!"
simulate_games: "Simular Jogos!"
# simulate_all: "RESET AND SIMULATE GAMES"
# games_simulated_by: "Games simulated by you:"
# games_simulated_for: "Games simulated for you:"
# games_simulated: "Games simulated"
# games_played: "Games played"
# ratio: "Ratio"
games_simulated_by: "Jogos simulados por si:"
games_simulated_for: "Jogos simulados para si:"
games_simulated: "Jogos simulados"
games_played: "Jogos jogados"
ratio: "Rácio"
leaderboard: "Tabela de Classificação"
battle_as: "Lutar como "
summary_your: "As tuas "
@ -868,29 +868,29 @@ module.exports = nativeDescription: "Português (Portugal)", englishDescription:
# server_error: "Server error."
# unknown: "Unknown error."
# resources:
# your_sessions: "Your Sessions"
# level: "Level"
resources:
your_sessions: "As Suas Sessões"
level: "vel"
# social_network_apis: "Social Network APIs"
# facebook_status: "Facebook Status"
# facebook_friends: "Facebook Friends"
facebook_friends: "Amigos do Facebook"
# facebook_friend_sessions: "Facebook Friend Sessions"
# gplus_friends: "G+ Friends"
gplus_friends: "Amigos do Google+"
# gplus_friend_sessions: "G+ Friend Sessions"
# leaderboard: "Leaderboard"
leaderboard: "Tabela de Classificação"
# user_schema: "User Schema"
# user_profile: "User Profile"
# patches: "Patches"
# patched_model: "Source Document"
# model: "Model"
# system: "System"
# component: "Component"
# components: "Components"
# thang: "Thang"
# thangs: "Thangs"
# level_session: "Your Session"
# opponent_session: "Opponent Session"
# article: "Article"
system: "Sistema"
component: "Componente"
components: "Componentes"
thang: "Thang"
thangs: "Thangs"
level_session: "A Sua Sessão"
opponent_session: "Sessão Do Oponente"
article: "Artigo"
# user_names: "User Names"
# thang_names: "Thang Names"
# files: "Files"
@ -900,7 +900,7 @@ module.exports = nativeDescription: "Português (Portugal)", englishDescription:
# sprite_sheet: "Sprite Sheet"
# candidate_sessions: "Candidate Sessions"
# user_remark: "User Remark"
# versions: "Versions"
versions: "Versões"
# delta:
# added: "Added"

View file

@ -0,0 +1,74 @@
#admin-candidates-view
h1, h2, h3
font: Arial
.see-candidates-header
margin: 30px
text-align: center
#see-candidates
cursor: pointer
.employer_icon
width: 125px
float: left
margin: 0px 15px 15px 0px
.information_row
height: 150px
padding-right: 15px
#leftside
width: 500px
float: left
#rightside
width: 500px
float: left
.tablesorter
//img
// display: none
.tablesorter-header
cursor: pointer
&:hover
color: black
&:first-child
// Make sure that "Developer #56" doesn't wrap onto second row
min-width: 110px
.tablesorter-headerAsc
background-color: #cfc
.tablesorter-headerDesc
background-color: #ccf
tr
cursor: pointer
tr.expired
opacity: 0.5
code
background-color: rgb(220, 220, 220)
color: #555
margin: 2px 0
display: inline-block
text-transform: lowercase
td:nth-child(3) select
min-width: 100px
td:nth-child(6) select
min-width: 50px
td:nth-child(7) select
min-width: 100px
#employers-view, #profile-view.viewed-by-employer
#outer-content-wrapper, #intermediate-content-wrapper, #inner-content-wrapper
background: #949494
.main-content-area
background-color: #EAEAEA

View file

@ -25,6 +25,8 @@ block content
a(href="/admin/level_sessions", data-i18n="admin.av_entities_active_instances_url") Active Instances
li
a(href="/admin/employer_list", data-i18n="admin.av_entities_employer_list_url") Employer List
li
a(href="/admin/candidates") Candidate List
h4(data-i18n="admin.av_other_sub_title") Other

View file

@ -0,0 +1,110 @@
extends /templates/base
block content
if !me.isAdmin()
h1 Admins Only
if me.isAdmin()
h1(data-i18n="employers.want_to_hire_our_players") Hire CodeCombat Players
if !isEmployer && !me.isAdmin()
div#info_wrapper
div#leftside
div.information_row
img(class="employer_icon" src="/images/pages/employer/employer_icon1.png")
h2(data-i18n="employers.what") What is CodeCombat?
p(data-i18n="employers.what_blurb") CodeCombat is a multiplayer browser programming game. Players write code to control their forces in battle against other developers. We support JavaScript, Python, Lua, Clojure, CoffeeScript, and Io.
div.information_row
img(class="employer_icon" src="/images/pages/employer/employer_icon3.png")
h2(data-i18n="employers.who") Who Are the Players?
p(data-i18n="employers.who_blurb") CodeCombateers are CTOs, VPs of Engineering, and graduates of top 20 engineering schools. No junior developers here. Our players enjoy playing with code and solving problems.
div.information_row
img(class="employer_icon" src="/images/pages/employer/employer_icon5.png")
h2(data-i18n="employers.cost") Who Are the Players?
p(data-i18n="employers.cost_blurb") CodeCombateers are CTOs, VPs of Engineering, and graduates of top 20 engineering schools. No junior developers here. Our players enjoy playing with code and solving problems.
div#rightside
div.information_row
img(class="employer_icon" src="/images/pages/employer/employer_icon2.png")
h2(data-i18n="employers.how") How Much Do we Charge?
p(data-i18n="employers.how_blurb") We charge 15% of first year's salary and offer a 100% money back guarantee for 90 days. We don't charge for candidates who are already actively being interviewed at your company.
div.information_row
img(class="employer_icon" src="/images/pages/employer/employer_icon4.png")
h2(data-i18n="employers.why") Why Hire Through Us?
p
span(data-i18n="employers.why_blurb_1") We will save you time. Every CodeCombateer we feaure is
strong(data-i18n="employers.why_blurb_2") looking for work
span(data-i18n="employers.why_blurb_3") , has
strong(data-i18n="employers.why_blurb_4") demonstrated top notch technical skills
span(data-i18n="employers.why_blurb_5") , and has been
strong(data-i18n="employers.why_blurb_6") personally screened by us
span(data-i18n="employers.why_blurb_7") . Stop screening and start hiring.
div.information_row
img(class="employer_icon" src="/images/pages/employer/employer_icon6.png")
h2(data-i18n="employers.response") What's the Response Rate?
p(data-i18n="employers.response_blurb") Almost every developer you contact on CodeCombat will respond to inquires whether or not they want to interivew. If you would like help finding a candidate for your role, we can make recommendations.
if candidates.length
ul.nav.nav-pills
li.active
a(href="#featured-candidates", data-toggle="tab")
span(data-i18n="employers.featured_developers") Featured Developers
| (#{featuredCandidates.length})
if otherCandidates.length
li
a(href="#other-candidates", data-toggle="tab")
span(data-i18n="employers.other_developers") Other Developers
| (#{otherCandidates.length})
if me.isAdmin() && inactiveCandidates.length
li
a(href="#inactive-candidates", data-toggle="tab")
span(data-i18n="employers.inactive_developers") Inactive Developers
| (#{inactiveCandidates.length})
div.tab-content
for area, tabIndex in [{id: "featured-candidates", candidates: featuredCandidates}, {id: "other-candidates", candidates: otherCandidates}, {id: "inactive-candidates", candidates: inactiveCandidates}]
div(class="tab-pane well" + (tabIndex ? "" : " active"), id=area.id)
table.table.table-condensed.table-hover.table-responsive.tablesorter
thead
tr
th(data-i18n="general.name") Name
th(data-i18n="employers.candidate_location") Location
th(data-i18n="employers.candidate_looking_for") Looking For
th(data-i18n="employers.candidate_role") Role
th(data-i18n="employers.candidate_top_skills") Top Skills
th(data-i18n="employers.candidate_years_experience") Yrs Exp
th(data-i18n="employers.candidate_last_updated") Last Updated
if me.isAdmin()
th(data-i18n="employers.candidate_who") Who
if me.isAdmin() && area.id == 'inactive-candidates'
th ✓?
tbody
for candidate, index in area.candidates
- var profile = candidate.get('jobProfile');
- var authorized = candidate.id; // If we have the id, then we are authorized.
- var profileAge = (new Date() - new Date(profile.updated)) / 86400 / 1000;
- var expired = profileAge > 2 * 30.4;
tr(data-candidate-id=candidate.id, id=candidate.id, class=expired ? "expired" : "")
td
if authorized
img(src=candidate.getPhotoURL(50), alt=profile.name, title=profile.name, height=50)
if profile.name
p= profile.name
else if me.isAdmin()
p (#{candidate.get('name')})
else
img(src="/images/pages/contribute/archmage.png", alt="", title="Sign up as an employer to see our candidates", width=50)
p Developer ##{index + 1 + (area.id == 'featured-candidates' ? 0 : featuredCandidates.length)}
if profile.country == 'USA'
td= profile.city
else
td= profile.country
td= profile.lookingFor
td= profile.jobTitle
td
each skill in profile.skills
code= skill
span
td= profile.experience
td(data-profile-age=profileAge)= moment(profile.updated).fromNow()
if me.isAdmin()
td= remarks[candidate.id] ? remarks[candidate.id].get('contactName') : ''
if me.isAdmin() && area.id == 'inactive-candidates'
if candidate.get('jobProfileApproved')
td ✓
else
td ✗

View file

@ -26,8 +26,8 @@ block content
li(id="#{component.get('name')}#{doc.name}")
| #{doc.name}
ul.specialList
if doc.description[language.substring(1,language.length-1)]
li!=marked(doc.description[language.substring(1,language.length-1)])
if doc.description[language]
li!=marked(doc.description[language])
else
li!=marked(doc.description)

View file

@ -0,0 +1,207 @@
View = require 'views/kinds/RootView'
template = require 'templates/admin/candidates'
app = require 'application'
User = require 'models/User'
UserRemark = require 'models/UserRemark'
{me} = require 'lib/auth'
CocoCollection = require 'collections/CocoCollection'
EmployerSignupView = require 'views/modal/employer_signup_modal'
class CandidatesCollection extends CocoCollection
url: '/db/user/x/candidates'
model: User
class UserRemarksCollection extends CocoCollection
url: '/db/user.remark?project=contact,contactName,user'
model: UserRemark
module.exports = class EmployersView extends View
id: "admin-candidates-view"
template: template
events:
'click tbody tr': 'onCandidateClicked'
constructor: (options) ->
super options
@getCandidates()
afterRender: ->
super()
@sortTable() if @candidates.models.length
afterInsert: ->
super()
_.delay @checkForEmployerSignupHash, 500
getRenderData: ->
ctx = super()
ctx.isEmployer = @isEmployer()
ctx.candidates = _.sortBy @candidates.models, (c) -> c.get('jobProfile').updated
ctx.activeCandidates = _.filter ctx.candidates, (c) -> c.get('jobProfile').active
ctx.inactiveCandidates = _.reject ctx.candidates, (c) -> c.get('jobProfile').active
ctx.featuredCandidates = _.filter ctx.activeCandidates, (c) -> c.get('jobProfileApproved')
ctx.otherCandidates = _.reject ctx.activeCandidates, (c) -> c.get('jobProfileApproved')
ctx.remarks = {}
ctx.remarks[remark.get('user')] = remark for remark in @remarks.models
ctx.moment = moment
ctx._ = _
ctx
isEmployer: ->
userPermissions = me.get('permissions') ? []
_.contains userPermissions, "employer"
getCandidates: ->
@candidates = new CandidatesCollection()
@candidates.fetch()
@remarks = new UserRemarksCollection()
@remarks.fetch()
# Re-render when we have fetched them, but don't wait and show a progress bar while loading.
@listenToOnce @candidates, 'all', @renderCandidatesAndSetupScrolling
@listenToOnce @remarks, 'all', @renderCandidatesAndSetupScrolling
renderCandidatesAndSetupScrolling: =>
@render()
$(".nano").nanoScroller()
if window.history?.state?.lastViewedCandidateID
$(".nano").nanoScroller({scrollTo:$("#" + window.history.state.lastViewedCandidateID)})
else if window.location.hash.length is 25
$(".nano").nanoScroller({scrollTo:$(window.location.hash)})
checkForEmployerSignupHash: =>
if window.location.hash is "#employerSignupLoggingIn" and not ("employer" in me.get("permissions"))
@openModalView application.router.getView("modal/employer_signup","_modal")
window.location.hash = ""
sortTable: ->
# http://mottie.github.io/tablesorter/docs/example-widget-bootstrap-theme.html
$.extend $.tablesorter.themes.bootstrap,
# these classes are added to the table. To see other table classes available,
# look here: http://twitter.github.com/bootstrap/base-css.html#tables
table: "table table-bordered"
caption: "caption"
header: "bootstrap-header" # give the header a gradient background
footerRow: ""
footerCells: ""
icons: "" # add "icon-white" to make them white; this icon class is added to the <i> in the header
sortNone: "bootstrap-icon-unsorted"
sortAsc: "icon-chevron-up" # glyphicon glyphicon-chevron-up" # we are still using v2 icons
sortDesc: "icon-chevron-down" # glyphicon-chevron-down" # we are still using v2 icons
active: "" # applied when column is sorted
hover: "" # use custom css here - bootstrap class may not override it
filterRow: "" # filter row class
even: "" # odd row zebra striping
odd: "" # even row zebra striping
# e = exact text from cell
# n = normalized value returned by the column parser
# f = search filter input value
# i = column index
# $r = ???
filterSelectExactMatch = (e, n, f, i, $r) -> e is f
# call the tablesorter plugin and apply the uitheme widget
@$el.find(".tablesorter").tablesorter
theme: "bootstrap"
widthFixed: true
headerTemplate: "{content} {icon}"
textSorter:
6: (a, b, direction, column, table) ->
days = []
for s in [a, b]
n = parseInt s
n = 0 unless _.isNumber n
n = 1 if /^a/.test s
for [duration, factor] in [
[/second/i, 1 / (86400 * 1000)]
[/minute/i, 1 / 1440]
[/hour/i, 1 / 24]
[/week/i, 7]
[/month/i, 30.42]
[/year/i, 365.2425]
]
if duration.test s
n *= factor
break
if /^in /i.test s
n *= -1
days.push n
days[0] - days[1]
sortList: if @isEmployer() or me.isAdmin() then [[6, 0]] else [[0, 1]]
# widget code contained in the jquery.tablesorter.widgets.js file
# use the zebra stripe widget if you plan on hiding any rows (filter widget)
widgets: ["uitheme", "zebra", "filter"]
widgetOptions:
# using the default zebra striping class name, so it actually isn't included in the theme variable above
# this is ONLY needed for bootstrap theming if you are using the filter widget, because rows are hidden
zebra: ["even", "odd"]
# extra css class applied to the table row containing the filters & the inputs within that row
filter_cssFilter: ""
# If there are child rows in the table (rows with class name from "cssChildRow" option)
# and this option is true and a match is found anywhere in the child row, then it will make that row
# visible; default is false
filter_childRows: false
# if true, filters are collapsed initially, but can be revealed by hovering over the grey bar immediately
# below the header row. Additionally, tabbing through the document will open the filter row when an input gets focus
filter_hideFilters: false
# Set this option to false to make the searches case sensitive
filter_ignoreCase: true
# jQuery selector string of an element used to reset the filters
filter_reset: ".reset"
# Use the $.tablesorter.storage utility to save the most recent filters
filter_saveFilters: true
# Delay in milliseconds before the filter widget starts searching; This option prevents searching for
# every character while typing and should make searching large tables faster.
filter_searchDelay: 150
# Set this option to true to use the filter to find text from the start of the column
# So typing in "a" will find "albert" but not "frank", both have a's; default is false
filter_startsWith: false
filter_functions:
2:
"Full-time": filterSelectExactMatch
"Part-time": filterSelectExactMatch
"Contracting": filterSelectExactMatch
"Remote": filterSelectExactMatch
"Internship": filterSelectExactMatch
5:
"0-1": (e, n, f, i, $r) -> n <= 1
"2-5": (e, n, f, i, $r) -> 2 <= n <= 5
"6+": (e, n, f, i, $r) -> 6 <= n
6:
"Last day": (e, n, f, i, $r) ->
days = parseFloat $($r.find('td')[i]).data('profile-age')
days <= 1
"Last week": (e, n, f, i, $r) ->
days = parseFloat $($r.find('td')[i]).data('profile-age')
days <= 7
"Last 4 weeks": (e, n, f, i, $r) ->
days = parseFloat $($r.find('td')[i]).data('profile-age')
days <= 28
8:
"": filterSelectExactMatch
"": filterSelectExactMatch
onCandidateClicked: (e) ->
id = $(e.target).closest('tr').data('candidate-id')
if id
if window.history
oldState = _.cloneDeep window.history.state ? {}
oldState["lastViewedCandidateID"] = id
window.history.replaceState(oldState,"")
else
window.location.hash = id
url = "/account/profile/#{id}"
window.open url,"_blank"
else
@openModalView new EmployerSignupView

View file

@ -34,5 +34,5 @@ module.exports = class UnnamedView extends RootView
if (me.get('aceConfig')?.language?) is false
c.language = 'javascript'
else
c.language = JSON.stringify(me.get('aceConfig').language)
c.language = me.get('aceConfig').language
c

View file

@ -40,7 +40,7 @@ module.exports = class EditorLevelView extends View
constructor: (options, @levelID) ->
super options
@supermodel.shouldSaveBackups = (model) ->
model.constructor.className in ['Level', 'LevelComponent', 'LevelSystem']
model.constructor.className in ['Level', 'LevelComponent', 'LevelSystem', 'ThangType']
@levelLoader = new LevelLoader supermodel: @supermodel, levelID: @levelID, headless: true, editorMode: true
@level = @levelLoader.level
@files = new DocumentFiles(@levelLoader.level)

View file

@ -84,8 +84,9 @@ module.exports = class PlayLevelView extends View
@saveScreenshot = _.throttle @saveScreenshot, 30000
if @isEditorPreview
# wait to see if it's just given to us through setLevel
f = => @load() unless @levelLoader
@supermodel.shouldSaveBackups = (model) -> # Make sure to load possibly changed things from localStorage.
model.constructor.className in ['Level', 'LevelComponent', 'LevelSystem', 'ThangType']
f = => @load() unless @levelLoader # Wait to see if it's just given to us through setLevel.
setTimeout f, 100
else
@load()

View file

@ -117,7 +117,7 @@ module.exports = class PlayView extends View
difficulty: 2
id: 'emphasis-on-aim'
image: '/file/db/level/525f384d96cd77000000000f/munchkin_masher_icon.png'
description: 'Chose your targets carefully.'
description: 'Choose your targets carefully.'
}
{
name: 'Zone of Danger'

View file

@ -8,15 +8,15 @@ LevelSession = require '../levels/sessions/LevelSession'
Level = require '../levels/Level'
log = require 'winston'
sendwithus = require '../sendwithus'
if config.isProduction
if config.isProduction and config.redis.host isnt 'localhost'
lockManager = require '../commons/LockManager'
module.exports.setup = (app) ->
app.all config.mail.mailchimpWebhook, handleMailchimpWebHook
app.get '/mail/cron/ladder-update', handleLadderUpdate
if lockManager
setupScheduledEmails()
setupScheduledEmails = ->
testForLockManager()
mailTasks = [
@ -31,15 +31,15 @@ setupScheduledEmails = ->
]
for mailTask in mailTasks
setInterval mailTask.taskFunction, mailTask.frequencyMs
setInterval mailTask.taskFunction, mailTask.frequencyMs
testForLockManager = -> unless lockManager then throw "The system isn't configured to do distributed locking!"
### Candidate Update Reminder Task ###
candidateUpdateProfileTask = ->
mailTaskName = "candidateUpdateProfileTask"
lockDurationMs = 2 * 60 * 1000
lockDurationMs = 2 * 60 * 1000
currentDate = new Date()
timeRanges = []
for weekPair in [[4, 2,'two weeks'], [8, 4, 'four weeks'], [52, 8, 'eight weeks']]
@ -66,12 +66,12 @@ emailTimeRange = (timeRange, emailTimeRangeCallback) ->
"mailTaskName": @mailTaskName
async.waterfall [
findAllCandidatesWithinTimeRange.bind(waterfallContext)
(unfilteredCandidates, cb) ->
(unfilteredCandidates, cb) ->
async.reject unfilteredCandidates, candidateFilter.bind(waterfallContext), cb.bind(null, null)
(filteredCandidates, cb) ->
async.each filteredCandidates, sendReminderEmailToCandidate.bind(waterfallContext), cb
], emailTimeRangeCallback
findAllCandidatesWithinTimeRange = (cb) ->
findParameters =
"jobProfile.updated":
@ -80,7 +80,7 @@ findAllCandidatesWithinTimeRange = (cb) ->
"jobProfileApproved": true
selection = "_id email jobProfile.name jobProfile.updated emails" #make sure to check for anyNotes too.
User.find(findParameters).select(selection).lean().exec cb
candidateFilter = (candidate, sentEmailFilterCallback) ->
if candidate.emails?.anyNotes?.enabled is false or candidate.emails?.recruitNotes?.enabled is false
return sentEmailFilterCallback true
@ -102,7 +102,7 @@ findEmployersSignedUpAfterDate = (dateObject, cb) ->
employerAt: {$exists: true}
permissions: "employer"
User.count countParameters, cb
sendReminderEmailToCandidate = (candidate, sendEmailCallback) ->
findEmployersSignedUpAfterDate new Date(candidate.jobProfile.updated), (err, employersAfterCount) =>
if err?
@ -136,7 +136,7 @@ sendReminderEmailToCandidate = (candidate, sendEmailCallback) ->
### Internal Candidate Update Reminder Email ###
internalCandidateUpdateTask = ->
mailTaskName = "internalCandidateUpdateTask"
lockDurationMs = 2 * 60 * 1000
lockDurationMs = 2 * 60 * 1000
lockManager.setLock mailTaskName, lockDurationMs, (err) ->
if err? then return log.error "Error getting a distributed lock for task #{mailTaskName}: #{err}"
emailInternalCandidateUpdateReminder.call {"mailTaskName":mailTaskName}, (err) ->
@ -162,31 +162,31 @@ emailInternalCandidateUpdateReminder = (internalCandidateUpdateReminderCallback)
(filteredCandidates, cb) ->
async.each filteredCandidates, sendInternalCandidateUpdateReminder.bind(asyncContext), cb
], internalCandidateUpdateReminderCallback
findNonApprovedCandidatesWhoUpdatedJobProfileToday = (cb) ->
findParameters =
findParameters =
"jobProfile.updated":
$lte: @currentTime.toISOString()
$gt: @beginningOfUTCDay.toISOString()
"jobProfileApproved": false
"jobProfileApproved": false
User.find(findParameters).select("_id jobProfile.name jobProfile.updated").lean().exec cb
candidatesUpdatedTodayFilter = (candidate, cb) ->
findParameters =
"user": candidate._id
"mailTask": @mailTaskName
"metadata.beginningOfUTCDay": @beginningOfUTCDay
"metadata.beginningOfUTCDay": @beginningOfUTCDay
MailSent.find(findParameters).lean().exec (err, sentMail) ->
if err?
log.error "Error finding mail sent for task #{@mailTaskName} and user #{candidate._id}!"
cb true
else
cb Boolean(sentMail.length)
sendInternalCandidateUpdateReminder = (candidate, cb) ->
context =
email_id: "tem_Ac7nhgKqatTHBCgDgjF5pE"
recipient:
recipient:
address: "team@codecombat.com"
name: "The CodeCombat Team"
email_data:
@ -197,7 +197,7 @@ sendInternalCandidateUpdateReminder = (candidate, cb) ->
user: candidate._id
metadata:
beginningOfUTCDay: @beginningOfUTCDay
MailSent.create newSentMail, (err) ->
if err? then return cb err
sendwithus.api.send context, (err, result) ->
@ -208,7 +208,7 @@ sendInternalCandidateUpdateReminder = (candidate, cb) ->
### Employer New Candidates Available Email ###
employerNewCandidatesAvailableTask = ->
mailTaskName = "employerNewCandidatesAvailableTask"
lockDurationMs = 2 * 60 * 1000
lockDurationMs = 2 * 60 * 1000
lockManager.setLock mailTaskName, lockDurationMs, (err) ->
if err? then return log.error "Error getting a distributed lock for task #{mailTaskName}: #{err}"
emailEmployerNewCandidatesAvailable.call {"mailTaskName":mailTaskName}, (err) ->
@ -221,10 +221,10 @@ employerNewCandidatesAvailableTask = ->
emailEmployerNewCandidatesAvailable = (emailEmployerNewCandidatesAvailableCallback) ->
currentTime = new Date()
asyncContext =
asyncContext =
"currentTime": currentTime
"mailTaskName": @mailTaskName
async.waterfall [
findAllEmployers
makeEmployerNamesEasilyAccessible
@ -233,15 +233,15 @@ emailEmployerNewCandidatesAvailable = (emailEmployerNewCandidatesAvailableCallba
(employersToEmail, cb) ->
async.each employersToEmail, sendEmployerNewCandidatesAvailableEmail.bind(asyncContext), cb
], emailEmployerNewCandidatesAvailableCallback
findAllEmployers = (cb) ->
findParameters =
findParameters =
"employerAt":
$exists: true
$exists: true
permissions: "employer"
selection = "_id email employerAt signedEmployerAgreement.data.firstName signedEmployerAgreement.data.lastName activity dateCreated emails"
User.find(findParameters).select(selection).lean().exec cb
makeEmployerNamesEasilyAccessible = (allEmployers, cb) ->
for employer, index in allEmployers
if employer.signedEmployerAgreement?.data?.firstName
@ -249,15 +249,17 @@ makeEmployerNamesEasilyAccessible = (allEmployers, cb) ->
delete employer.signedEmployerAgreement
allEmployers[index] = employer
cb null, allEmployers
employersEmailedDigestMoreThanWeekAgoFilter = (employer, cb) ->
if employer.emails?.employerNotes?.enabled is false
return cb true
findParameters =
if not employer.signedEmployerAgreement and not employer.activity?.login?
return cb true
findParameters =
"user": employer._id
"mailTask": @mailTaskName
"sent":
$gt: new Date(@currentTime.getTime() - 7 * 24 * 60 * 60 * 1000)
$gt: new Date(@currentTime.getTime() - 7 * 24 * 60 * 60 * 1000)
MailSent.find(findParameters).lean().exec (err, sentMail) ->
if err?
log.error "Error finding mail sent for task #{@mailTaskName} and employer #employer._id}!"
@ -266,17 +268,17 @@ employersEmailedDigestMoreThanWeekAgoFilter = (employer, cb) ->
cb Boolean(sentMail.length)
sendEmployerNewCandidatesAvailableEmail = (employer, cb) ->
lastLoginDate = employer.activity?.login?.last ? employer.dateCreated
lastLoginDate = employer.activity?.login?.last ? employer.dateCreated
countParameters =
"jobProfileApproved": true
$or: [
jobProfileApprovedDate:
jobProfileApprovedDate:
$gt: lastLoginDate.toISOString()
,
jobProfileApprovedDate:
$exists: false
"jobProfile.updated":
$gt: lastLoginDate.toISOString()
$gt: lastLoginDate.toISOString()
]
User.count countParameters, (err, numberOfCandidatesSinceLogin) =>
if err? then return cb err
@ -313,8 +315,8 @@ newRecruitLeaderboardEmailTask = ->
lockManager.setLock mailTaskName, lockDurationMs, (err, lockResult) ->
###
### End New Recruit Leaderboard Email ###
### Employer Matching Candidate Notification Email ###
### Employer Matching Candidate Notification Email ###
###
employerMatchingCandidateNotificationTask = ->
# tem_mYsepTfWQ265noKfZJcbBH
@ -325,7 +327,7 @@ employerMatchingCandidateNotificationTask = ->
###
### End Employer Matching Candidate Notification Email ###
### Ladder Update Email ###
### Employer ignore ###
DEBUGGING = false
LADDER_PREGAME_INTERVAL = 2 * 3600 * 1000 # Send emails two hours before players last submitted.
getTimeFromDaysAgo = (now, daysAgo) ->
@ -342,7 +344,7 @@ isRequestFromDesignatedCronHandler = (req, res) ->
return true
handleLadderUpdate = (req, res) ->
log.info('Going to see about sending ladder update emails.')
requestIsFromDesignatedCronHandler = isRequestFromDesignatedCronHandler req, res