Merge remote-tracking branch 'codecombat/master'

This commit is contained in:
Cat Sync 2016-06-23 15:01:44 -04:00
commit 70fe21f645
12 changed files with 148 additions and 34 deletions

View file

@ -1,7 +1,18 @@
var window = self;
var Global = self;
importScripts("/javascripts/lodash.js", "/javascripts/aether.js", "/javascripts/esper.js");
importScripts("/javascripts/lodash.js", "/javascripts/aether.js");
try {
//Detect very modern javascript support.
(0,eval("'use strict'; let test = (class Test { *gen(a=7) { yield yield * () => WeakMap; } });"));
console.log("Modern javascript detected, aw yeah!");
self.importScripts('/javascripts/esper.modern.js');
} catch (e) {
console.log("Legacy javascript detected, falling back...", e.message);
self.importScripts('/javascripts/esper.js');
}
//console.log("Aether Tome worker has finished importing scripts.");
var aethers = {};
var languagesImported = {};

View file

@ -63,7 +63,18 @@ var console = {
console.error = console.warn = console.info = console.debug = console.log;
self.console = console;
self.importScripts('/javascripts/lodash.js', '/javascripts/world.js', '/javascripts/aether.js', '/javascripts/esper.js');
self.importScripts('/javascripts/lodash.js', '/javascripts/world.js', '/javascripts/aether.js');
try {
//Detect very modern javascript support.
(0,eval("'use strict'; let test = (class Test { *gen(a=7) { yield yield * () => WeakMap; } });"));
console.log("Modern javascript detected, aw yeah!");
self.importScripts('/javascripts/esper.modern.js');
} catch (e) {
console.log("Legacy javascript detected, falling back...", e.message);
self.importScripts('/javascripts/esper.js');
}
var myImportScripts = importScripts;
var languagesImported = {};
@ -402,6 +413,17 @@ self.serializeFramesSoFar = function serializeFramesSoFar() {
self.world.framesSerializedSoFar = self.world.frames.length;
};
function trySerialize() {
try {
var serialized = self.world.serialize();
}
catch(error) {
console.log("World serialization error:", error.toString() + "\n" + error.stack || error.stackTrace);
return false;
}
return serialized;
}
self.onWorldLoaded = function onWorldLoaded() {
if(self.world.framesSerializedSoFar == self.world.frames.length) return;
if(self.world.ended)
@ -419,12 +441,9 @@ self.onWorldLoaded = function onWorldLoaded() {
return console.log('Headless simulation completed in ' + diff + 'ms.');
var worldEnded = self.world.ended;
var serialized;
var transferableSupported = self.transferableSupported();
try {
var serialized = self.world.serialize();
}
catch(error) {
console.log("World serialization error:", error.toString() + "\n" + error.stack || error.stackTrace);
if ( !( serialized = trySerialize()) ) {
self.destroyWorld();
return;
}

View file

@ -162,11 +162,25 @@ module.exports = class World
shouldContinueLoading: (t1, loadProgressCallback, skipDeferredLoading, continueLaterFn) ->
t2 = now()
chunkSize = @frames.length - @framesSerializedSoFar
simedTime = @frames.length / @frameRate
chunkTime = switch
when simedTime > 15 then 7
when simedTime > 10 then 5
when simedTime > 5 then 3
when simedTime > 2 then 1
else 0.5
bailoutTime = Math.max(2000*chunkTime, 10000)
dt = t2 - t1
if @realTime
shouldUpdateProgress = @shouldUpdateRealTimePlayback t2
shouldDelayRealTimeSimulation = not shouldUpdateProgress and @shouldDelayRealTimeSimulation t2
else
shouldUpdateProgress = t2 - t1 > PROGRESS_UPDATE_INTERVAL# and (@frames.length - @framesSerializedSoFar >= @frameRate or t2 - t1 > 1000)
shouldUpdateProgress = (dt > PROGRESS_UPDATE_INTERVAL and (chunkSize / @frameRate >= chunkTime) or dt > bailoutTime)
shouldDelayRealTimeSimulation = false
return true unless shouldUpdateProgress or shouldDelayRealTimeSimulation
# Stop loading frames for now; continue in a moment.

View file

@ -57,6 +57,8 @@ block content
select.level-select.form-control
if view.campaigns.loaded
each level, levelIndex in view.campaigns.get(course.get('campaignID')).getLevels().models
if level.get('type') === 'hero-practice'
- continue;
option(value=level.get('slug'))
span
= levelIndex + 1

View file

@ -239,6 +239,14 @@ module.exports = class CampaignEditorView extends RootView
@campaign.set key, value for key, value of @treema.data
@campaignView.setCampaign(@campaign)
onTreemaSelectionChanged: (e, node) =>
return unless node[0]?.data?.original?
elem = @$("div[data-level-original='#{node[0].data.original}']")
elem.toggle('pulsate')
setTimeout ()->
elem.toggle('pulsate')
, 1000
onTreemaDoubleClicked: (e, node) =>
path = node.getPath()
return unless _.string.startsWith path, '/levels/'

View file

@ -15,7 +15,7 @@ module.exports = class SettingsTabView extends CocoView
editableSettings: [
'name', 'description', 'documentation', 'nextLevel', 'background', 'victory', 'i18n', 'icon', 'goals',
'type', 'terrain', 'showsGuide', 'banner', 'employerDescription', 'loadingTip', 'requiresSubscription',
'helpVideos', 'replayable', 'scoreTypes', 'concepts', 'picoCTFProblem'
'helpVideos', 'replayable', 'scoreTypes', 'concepts', 'picoCTFProblem', 'practiceThresholdMinutes'
]
subscriptions:

View file

@ -316,7 +316,7 @@ module.exports = class SpellView extends CocoView
xstart = startOfRow(row)
if language is 'python'
requiredIndent = new RegExp '^' + new Array(xstart / 4 + 1).join('( |\t)') + '( |\t)+(\\S|\\s*$)'
requiredIndent = new RegExp '^' + new Array(Math.floor(xstart / 4 + 1)).join('( |\t)') + '( |\t)+(\\S|\\s*$)'
for crow in [docRange.start.row+1..docRange.end.row]
unless requiredIndent.test lines[crow]
docRange.end.row = crow - 1

View file

@ -62,7 +62,7 @@ module.exports = class TeachersContactModal extends ModalView
return unless _.isEmpty(formErrors)
@state.set('sendingState', 'sending')
data = _.extend({ country: me.get('country'), recipientID: 'schools@codecombat.com' }, formValues)
data = _.extend({ country: me.get('country') }, formValues)
contact.send({
data
context: @

View file

@ -207,6 +207,7 @@ exports.config =
copyTo:
'lib/ace': ['node_modules/ace-builds/src-min-noconflict/*']
'fonts': ['bower_components/openSansCondensed/*', 'bower_components/openSans/*']
'javascripts': ['bower_components/esper.js/esper.modern.js']
autoReload:
delay: 1000

View file

@ -67,6 +67,7 @@ LevelHandler = class LevelHandler extends Handler
'scoreTypes'
'concepts'
'picoCTFProblem'
'practiceThresholdMinutes'
]
postEditableProperties: ['name']

View file

@ -66,33 +66,81 @@ module.exports =
return done("Unexpected activities format: " + body) unless activities.data?
for activity in activities.data when activity._type is 'Email'
if /@codecombat\.(?:com)|(?:nl)/ig.test(activity.sender) and not activity.sender?.indexOf(config.mail.username) >= 0
return done(null, activity.sender, lead.id)
return done(null, activity.sender, activity.user_id, lead.id)
return done(null, config.mail.supportSchools, lead.id)
catch error
log.error("closeIO.getSalesContactEmail Error for #{email}: #{JSON.stringify(error)}")
return done(error)
sendMail: (fromAddress, subject, content, done) ->
sendMail: (fromAddress, subject, content, salesContactEmail, leadID, done) ->
# log.info("DEBUG: closeIO.sendMail #{fromAddress} #{subject} #{content}")
@getSalesContactEmail fromAddress, (err, salesContactEmail, leadID) ->
return done("Error getting sales contact for #{fromAddress}: #{err}") if err
matches = salesContactEmail.match(/^[a-zA-Z_]+ <(\w+@[a-zA-Z_]+?\.[a-zA-Z]{2,3})>$|(\w+@[a-zA-Z_]+?\.[a-zA-Z]{2,3})/i)
salesContactEmail = matches?[1] ? matches?[2] ? config.mail.supportSchools
salesContactEmail = config.mail.supportSchools if salesContactEmail?.indexOf('brian@codecombat.com') >= 0
matches = salesContactEmail.match(/^[a-zA-Z_]+ <(\w+@[a-zA-Z_]+?\.[a-zA-Z]{2,3})>$|(\w+@[a-zA-Z_]+?\.[a-zA-Z]{2,3})/i)
salesContactEmail = matches?[1] ? matches?[2] ? config.mail.supportSchools
salesContactEmail = config.mail.supportSchools if salesContactEmail?.indexOf('brian@codecombat.com') >= 0
postData =
to: [salesContactEmail]
sender: config.mail.username
subject: subject
body_text: content
lead_id: leadID
status: 'outbox'
options =
uri: "https://#{apiKey}:X@app.close.io/api/v1/activity/email/"
body: JSON.stringify(postData)
request.post options, (error, response, body) =>
return done(error) if error
result = JSON.parse(body)
if result.errors or result['field-errors']
errorMessage = "Close.io Send email POST error for #{fromAddress} #{JSON.stringify(result.errors)} #{JSON.stringify(result['field-errors'])}"
return done(errorMessage)
return done()
processLicenseRequest: (teacherEmail, userID, leadID, licensesRequested, amount, done) ->
# Update lead with licenses requested
putData = 'custom.licensesRequested': licensesRequested
options =
uri: "https://#{apiKey}:X@app.close.io/api/v1/lead/#{leadID}/"
body: JSON.stringify(putData)
request.put options, (error, response, body) =>
return done(error) if error
result = JSON.parse(body)
if result.errors or result['field-errors']
errorMessage = "Update Close.io lead PUT error for #{teacherEmail} #{leadID}"
return done(errorMessage)
# Create call task
postData =
to: [salesContactEmail]
sender: config.mail.username
subject: subject
body_text: content
_type: "lead"
lead_id: leadID
status: 'outbox'
assigned_to: userID
text: "Call #{teacherEmail}"
is_complete: false
options =
uri: "https://#{apiKey}:X@app.close.io/api/v1/activity/email/"
uri: "https://#{apiKey}:X@app.close.io/api/v1/task/"
body: JSON.stringify(postData)
request.post options, (error, response, body) =>
return done(error) if error
result = JSON.parse(body);
return done(error) if error
result = JSON.parse(body)
if result.errors or result['field-errors']
errorMessage = "Close.io Send email POST error for #{fromAddress} #{JSON.stringify(result.errors)} #{JSON.stringify(result['field-errors'])}";
errorMessage = "Create Close.io call task POST error for #{teacherEmail} #{leadID}"
return done(errorMessage)
return done()
# Create opportunity
postData =
note: "#{licensesRequested} licenses requested"
confidence: 5
lead_id: leadID
status: 'Active'
value: parseInt(licensesRequested) * amount
value_period: "annual"
options =
uri: "https://#{apiKey}:X@app.close.io/api/v1/opportunity/"
body: JSON.stringify(postData)
request.post options, (error, response, body) =>
return done(error) if error
result = JSON.parse(body)
if result.errors or result['field-errors']
errorMessage = "Create Close.io opportunity POST error for #{teacherEmail} #{leadID}"
return done(errorMessage)
return done()

View file

@ -3,8 +3,9 @@ log = require 'winston'
User = require '../models/User'
sendwithus = require '../sendwithus'
async = require 'async'
LevelSession = require '../models/LevelSession'
moment = require 'moment'
LevelSession = require '../models/LevelSession'
Product = require '../models/Product'
closeIO = require '../lib/closeIO'
module.exports.setup = (app) ->
@ -13,10 +14,19 @@ module.exports.setup = (app) ->
# log.info "Sending mail from #{req.body.email} saying #{req.body.message}"
fromAddress = req.body.sender or req.body.email or req.user.get('email')
createMailContent req, fromAddress, (subject, content) ->
if req.body.recipientID is 'schools@codecombat.com' or req.user.isTeacher()
req.user.update({$set: { enrollmentRequestSent: true }}).exec(_.noop) if req.body.recipientID is 'schools@codecombat.com'
closeIO.sendMail fromAddress, subject, content, (err) ->
log.error "Error sending contact form email via Close.io: #{err.message or err}" if err
if req.body.licensesNeeded or req.user.isTeacher()
closeIO.getSalesContactEmail fromAddress, (err, salesContactEmail, userID, leadID) ->
return log.error("Error getting sales contact for #{fromAddress}: #{err.message or err}") if err
closeIO.sendMail fromAddress, subject, content, salesContactEmail, leadID, (err) ->
return log.error("Error sending contact form email via Close.io: #{err.message or err}") if err
if licensesNeeded = req.body.licensesNeeded
Product.findOne({name: 'course'}).exec (err, product) =>
return log.error(err) if err
return log.error('course product not found') if not product
amount = product.get('amount')
closeIO.processLicenseRequest fromAddress, userID, leadID, licensesNeeded, amount, (err) ->
return log.error("Error processing license request via Close.io: #{err.message or err}") if err
req.user.update({$set: { enrollmentRequestSent: true }}).exec(_.noop)
else
createSendWithUsContext req, fromAddress, subject, content, (context) ->
sendwithus.api.send context, (err, result) ->
@ -53,7 +63,7 @@ createSendWithUsContext = (req, fromAddress, subject, content, done) ->
premium = user?.isPremium()
teacher = user?.isTeacher()
if recipientID is 'schools@codecombat.com' or teacher
if teacher or req.body.licensesNeeded
return done("Tried to send a teacher contact us email via sendwithus #{fromAddress} #{subject}")
toAddress = switch