Working on level-specific coding languages, with non-writable code in JavaScript.

This commit is contained in:
Nick Winter 2014-05-14 21:54:36 -07:00
parent 2a17ec5cb9
commit dbcafbb29b
10 changed files with 76 additions and 53 deletions

View file

@ -162,11 +162,11 @@ module.exports = class LevelLoader extends CocoClass
@loadLevelSounds()
@denormalizeSession()
app.tracker.updatePlayState(@level, @session) unless @headless
buildLoop: =>
return if @lastBuilt and new Date().getTime() - @lastBuilt < 10
return clearInterval @buildLoopInterval unless @spriteSheetsToBuild.length
for spriteSheetResource, i in @spriteSheetsToBuild
if spriteSheetResource.thangType.loaded
@buildSpriteSheetsForThangType spriteSheetResource.thangType
@ -267,7 +267,7 @@ module.exports = class LevelLoader extends CocoClass
# everything else sound wise is loaded as needed as worlds are generated
progress: -> @supermodel.progress
destroy: ->
clearInterval @buildLoopInterval if @buildLoopInterval
super()

View file

@ -263,8 +263,10 @@
skip_tutorial: "Skip (esc)"
editor_config: "Editor Config"
editor_config_title: "Editor Configuration"
editor_config_language_label: "Programming Language"
editor_config_language_description: "Define the programming language you want to code in."
editor_config_level_language_label: "Language for This Level"
editor_config_level_language_description: "Define the programming language for this particular level."
editor_config_default_language_label: "Default Programming Language"
editor_config_default_language_description: "Define the programming language you want to code in when starting new levels."
editor_config_keybindings_label: "Key Bindings"
editor_config_keybindings_default: "Default (Ace)"
editor_config_keybindings_description: "Adds additional shortcuts known from the common editors."
@ -725,4 +727,3 @@
text_diff: "Text Diff"
merge_conflict_with: "MERGE CONFLICT WITH"
no_changes: "No Changes"

View file

@ -6,14 +6,19 @@ block modal-header-content
block modal-body-content
.form
.form-group.select-group
label.control-label(for="tome-language" data-i18n="play_level.editor_config_language_label") Programming Language
label.control-label(for="tome-session-language" data-i18n="play_level.editor_config_level_language_label") Language for This Level
select#tome-session-language(name="language")
for option in languages
option(value=option.id selected=(sessionLanguage === option.id))= option.name
span.help-block(data-i18n="play_level.editor_config_level_language_description") Define the programming language for this particular level.
.form-group.select-group
label.control-label(for="tome-language" data-i18n="play_level.editor_config_default_language_label") Default Programming Language
select#tome-language(name="language")
option(value="javascript" selected=(language === "javascript")) JavaScript
option(value="coffeescript" selected=(language === "coffeescript")) CoffeeScript
option(value="python" selected=(language === "python")) Python (Experimental)
option(value="clojure" selected=(language === "clojure")) Clojure (Experimental)
option(value="lua" selected=(language === "lua")) Lua (Experimental)
span.help-block(data-i18n="play_level.editor_config_language_description") Define the programming language you want to code in.
for option in languages
option(value=option.id selected=(language === option.id))= option.name
span.help-block(data-i18n="play_level.editor_config_default_language_description") Define the programming language you want to code in when starting new levels.
.form-group.select-group
label.control-label(for="tome-key-bindings" data-i18n="play_level.editor_config_keybindings_label") Key Bindings
select#tome-key-bindings(name="keyBindings")
@ -21,16 +26,19 @@ block modal-body-content
option(value="vim" selected=(keyBindings === "vim")) Vim
option(value="emacs" selected=(keyBindings === "emacs")) Emacs
span.help-block(data-i18n="play_level.editor_config_keybindings_description") Adds additional shortcuts known from the common editors.
.form-group.checkbox
label(for="tome-invisibles")
input#tome-invisibles(name="invisibles", type="checkbox", checked=invisibles)
span(data-i18n="play_level.editor_config_invisibles_label") Show Invisibles
span.help-block(data-i18n="play_level.editor_config_invisibles_description") Displays invisibles such as spaces or tabs.
.form-group.checkbox
label(for="tome-indent-guides")
input#tome-indent-guides(name="indentGuides", type="checkbox", checked=indentGuides)
span(data-i18n="play_level.editor_config_indentguides_label") Show Indent Guides
span.help-block(data-i18n="play_level.editor_config_indentguides_description") Displays vertical lines to see indentation better.
.form-group.checkbox
label(for="tome-behaviors")
input#tome-behaviors(name="behaviors", type="checkbox", checked=behaviors)

View file

@ -23,11 +23,20 @@ module.exports = class EditorConfigModal extends View
constructor: (options) ->
super(options)
@session = options.session
getRenderData: ->
@aceConfig = _.cloneDeep me.get('aceConfig') ? {}
@aceConfig = _.defaults @aceConfig, @defaultConfig
c = super()
c.languages = [
{id: 'javascript', name: 'JavaScript'}
{id: 'coffeescript', name: 'CoffeeScript'}
{id: 'python', name: 'Python (Experimental)'}
{id: 'clojure', name: 'Clojure (Experimental)'}
{id: 'lua', name: 'Lua (Experimental)'}
]
c.sessionLanguage = @session.get('codeLanguage') ? @aceConfig.language
c.language = @aceConfig.language
c.keyBindings = @aceConfig.keyBindings
c.invisibles = @aceConfig.invisibles
@ -35,6 +44,9 @@ module.exports = class EditorConfigModal extends View
c.behaviors = @aceConfig.behaviors
c
updateSessionLanguage: ->
@session.set 'codeLanguage', @$el.find('#tome-session-language').val()
updateLanguage: ->
@aceConfig.language = @$el.find('#tome-language').val()
@ -54,7 +66,9 @@ module.exports = class EditorConfigModal extends View
super()
onHidden: ->
oldLanguage = @aceConfig.language
oldLanguage = @session.get('codeLanguage') ? @aceConfig.language
newLanguage = @$el.find('#tome-session-language').val()
@session.set 'codeLanguage', newLanguage
@aceConfig.language = @$el.find('#tome-language').val()
@aceConfig.invisibles = @$el.find('#tome-invisibles').prop('checked')
@aceConfig.keyBindings = @$el.find('#tome-key-bindings').val()
@ -62,7 +76,8 @@ module.exports = class EditorConfigModal extends View
@aceConfig.behaviors = @$el.find('#tome-behaviors').prop('checked')
me.set 'aceConfig', @aceConfig
Backbone.Mediator.publish 'tome:change-config'
Backbone.Mediator.publish 'tome:change-language' unless @aceConfig.language isnt oldLanguage
Backbone.Mediator.publish 'tome:change-language', language: newLanguage unless newLanguage is oldLanguage
@session.save() unless newLanguage is oldLanguage
me.save()
destroy: ->

View file

@ -156,16 +156,14 @@ module.exports = class PlaybackView extends View
@timePopup ?= new HoverPopup
#TODO: Why do we need defaultValues here at all? Fallback language has been set to 'en'... oO
t = $.i18n.t
@second = t 'units.second', defaultValue: 'second'
@seconds = t 'units.seconds', defaultValue: 'seconds'
@minute = t 'units.minute', defaultValue: 'minute'
@minutes = t 'units.minutes', defaultValue: 'minutes'
@goto = t 'play_level.time_goto', defaultValue: "Go to:"
@current = t 'play_level.time_current', defaultValue: "Now:"
@total = t 'play_level.time_total', defaultValue: "Max:"
@second = t 'units.second'
@seconds = t 'units.seconds'
@minute = t 'units.minute'
@minutes = t 'units.minutes'
@goto = t 'play_level.time_goto'
@current = t 'play_level.time_current'
@total = t 'play_level.time_total'
onToggleDebug: ->
return if @shouldIgnore()
@ -181,7 +179,7 @@ module.exports = class PlaybackView extends View
Backbone.Mediator.publish 'edit-wizard-settings'
onEditEditorConfig: ->
@openModalView(new EditorConfigModal())
@openModalView new EditorConfigModal session: @options.session
onCastSpells: (e) ->
return if e.preload
@ -194,8 +192,8 @@ module.exports = class PlaybackView extends View
$('button', @$el).addClass('disabled')
try
@$progressScrubber.slider('disable', true)
catch e
#console.warn('error disabling scrubber')
catch error
console.warn('error disabling scrubber', error)
@timePopup?.disable()
$('#volume-button', @$el).removeClass('disabled')
@ -205,8 +203,8 @@ module.exports = class PlaybackView extends View
$('button', @$el).removeClass('disabled')
try
@$progressScrubber.slider('enable', true)
catch e
#console.warn('error enabling scrubber')
catch error
console.warn('error enabling scrubber', error)
@timePopup?.enable()
onSetPlaying: (e) ->

View file

@ -25,6 +25,7 @@ module.exports = class Spell
@parameters = p.parameters
if @permissions.readwrite.length and sessionSource = @session.getSourceFor(@spellKey)
@source = sessionSource
@language = if @canWrite() then options.language else 'javascript'
@thangs = {}
@view = new SpellView {spell: @, session: @session, worker: @worker}
@view.render() # Get it ready and code loaded in advance
@ -100,6 +101,7 @@ module.exports = class Spell
createAether: (thang) ->
aceConfig = me.get('aceConfig') ? {}
writable = @permissions.readwrite.length > 0
aetherOptions =
problems:
jshint_W040: {level: "ignore"}
@ -109,13 +111,13 @@ module.exports = class Spell
jshint_E043: {level: "ignore"} # https://github.com/codecombat/codecombat/issues/813 -- since we can't actually tell JSHint to really ignore things
jshint_Unknown: {level: "ignore"} # E043 also triggers Unknown, so ignore that, too
aether_MissingThis: {level: 'error'}
language: aceConfig.language ? 'javascript'
language: if @canWrite() then @language else 'javascript'
functionName: @name
functionParameters: @parameters
yieldConditionally: thang.plan?
globals: ['Vector', '_']
# TODO: Gridmancer doesn't currently work with protectAPI, so hack it off
protectAPI: not (@skipProtectAPI or window.currentView?.level.get('name').match("Gridmancer")) and @permissions.readwrite.length > 0 # If anyone can write to this method, we must protect it.
protectAPI: not (@skipProtectAPI or window.currentView?.level.get('name').match("Gridmancer")) and writable # If anyone can write to this method, we must protect it.
includeFlow: false
#console.log "creating aether with options", aetherOptions
aether = new Aether aetherOptions
@ -126,15 +128,13 @@ module.exports = class Spell
@worker.postMessage JSON.stringify workerMessage
aether
updateLanguageAether: ->
aceConfig = me.get('aceConfig') ? {}
newLanguage = (aceConfig.language ? 'javascript')
updateLanguageAether: (@language) ->
for thangId, spellThang of @thangs
spellThang.aether?.setLanguage newLanguage
spellThang.aether?.setLanguage @language
spellThang.castAether = null
workerMessage =
function: "updateLanguageAether"
newLanguage: newLanguage
newLanguage: @language
@worker.postMessage JSON.stringify workerMessage
@transpile()

View file

@ -76,7 +76,7 @@ module.exports = class SpellView extends View
@aceSession = @ace.getSession()
@aceDoc = @aceSession.getDocument()
@aceSession.setUseWorker false
@aceSession.setMode @editModes[aceConfig.language ? 'javascript']
@aceSession.setMode @editModes[@spell.language]
@aceSession.setWrapLimitRange null
@aceSession.setUseWrapMode true
@aceSession.setNewLineMode "unix"
@ -360,7 +360,7 @@ module.exports = class SpellView extends View
displayAether: (aether) ->
@displayedAether = aether
isCast = not _.isEmpty(aether.metrics) or _.some aether.problems.errors, {type: 'runtime'}
isCast = isCast or (me.get('aceConfig') ? {})['language'] isnt 'javascript' # Since we don't have linting for other languages
isCast = isCast or @language isnt 'javascript' # Since we don't have linting for other languages
problem.destroy() for problem in @problems # Just in case another problem was added since clearAetherDisplay() ran.
@problems = []
annotations = []
@ -601,17 +601,15 @@ module.exports = class SpellView extends View
@ace.setDisplayIndentGuides aceConfig.indentGuides # default false
@ace.setShowInvisibles aceConfig.invisibles # default false
@ace.setKeyboardHandler @keyBindings[aceConfig.keyBindings ? 'default']
# @aceSession.setMode @editModes[aceConfig.language ? 'javascript']
onChangeLanguage: (e) ->
aceConfig = me.get('aceConfig') ? {}
@aceSession.setMode @editModes[aceConfig.language ? 'javascript']
if @spell.canWrite()
@aceSession.setMode @editModes[e.language]
dismiss: ->
@spell.hasChangedSignificantly @getSource(), null, (hasChanged) =>
@recompile() if hasChanged
destroy: ->
$(@ace?.container).find('.ace_gutter').off 'click', '.ace_error, .ace_warning, .ace_info', @onAnnotationClick
@firepad?.dispose()

View file

@ -105,6 +105,7 @@ module.exports = class TomeView extends View
return teamSpellMap
createSpells: (programmableThangs, world) ->
language = @options.session.get('codeLanguage') ? me.get('aceConfig')?.language ? 'javascript'
pathPrefixComponents = ['play', 'level', @options.levelID, @options.session.id, 'code']
@spells ?= {}
@thangSpells ?= {}
@ -120,7 +121,7 @@ module.exports = class TomeView extends View
@thangSpells[thang.id].push spellKey
unless method.cloneOf
skipProtectAPI = @getQueryVariable "skip_protect_api", (@options.levelID in ['gridmancer'])
spell = @spells[spellKey] = new Spell programmableMethod: method, spellKey: spellKey, pathComponents: pathPrefixComponents.concat(pathComponents), session: @options.session, supermodel: @supermodel, skipProtectAPI: skipProtectAPI, worker: @worker
spell = @spells[spellKey] = new Spell programmableMethod: method, spellKey: spellKey, pathComponents: pathPrefixComponents.concat(pathComponents), session: @options.session, supermodel: @supermodel, skipProtectAPI: skipProtectAPI, worker: @worker, language: language
for thangID, spellKeys of @thangSpells
thang = world.getThangByID thangID
if thang
@ -207,8 +208,9 @@ module.exports = class TomeView extends View
spell.view.reloadCode false for spellKey, spell of @spells when spell.team is me.team or (spell.team in ["common", "neutral", null])
Backbone.Mediator.publish 'tome:cast-spells', spells: @spells, preload: false
updateLanguageForAllSpells: ->
spell.updateLanguageAether() for spellKey, spell of @spells
updateLanguageForAllSpells: (e) ->
spell.updateLanguageAether e.language for spellKey, spell of @spells when spell.canWrite()
@cast()
destroy: ->
spell.destroy() for spellKey, spell of @spells

View file

@ -242,7 +242,7 @@ module.exports = class PlayLevelView extends View
insertSubviews: ->
@insertSubView @tome = new TomeView levelID: @levelID, session: @session, thangs: @world.thangs, supermodel: @supermodel
@insertSubView new PlaybackView {}
@insertSubView new PlaybackView session: @session
@insertSubView new GoalsView {}
@insertSubView new GoldView {}
@insertSubView new HUDView {}

View file

@ -63,7 +63,7 @@ LevelHandler = class LevelHandler extends Handler
# TODO: generalize this for levels based on their teams
else if level.get('type') is 'ladder'
sessionQuery.team = 'humans'
Session.findOne(sessionQuery).exec (err, doc) =>
return @sendDatabaseError(res, err) if err
return @sendSuccess(res, doc) if doc?
@ -88,6 +88,7 @@ LevelHandler = class LevelHandler extends Handler
access:'write'
}
]
initVals.codeLanguage = req.user.get('aceConfig')?.language ? 'javascript'
session = new Session(initVals)
session.save (err) =>
@ -107,7 +108,7 @@ LevelHandler = class LevelHandler extends Handler
query = Level.findOne(findParameters)
.select(selectString)
.lean()
query.exec (err, level) =>
return @sendDatabaseError(res, err) if err
return @sendNotFoundError(res) unless level?
@ -116,22 +117,22 @@ LevelHandler = class LevelHandler extends Handler
original: level.original.toString()
majorVersion: level.version.major
creator: req.user._id+''
query = Session.find(sessionQuery).select('-screenshot')
query.exec (err, results) =>
if err then @sendDatabaseError(res, err) else @sendSuccess res, results
getHistogramData: (req, res,slug) ->
query = Session.aggregate [
{$match: {"levelID":slug, "submitted": true, "team":req.query.team}}
{$project: {totalScore: 1, _id: 0}}
]
query.exec (err, data) =>
if err? then return @sendDatabaseError res, err
valueArray = _.pluck data, "totalScore"
@sendSuccess res, valueArray
checkExistence: (req, res, slugOrID) ->
findParameters = {}
if Handler.isID slugOrID
@ -155,7 +156,7 @@ LevelHandler = class LevelHandler extends Handler
sortParameters =
"totalScore": req.query.order
selectProperties = ['totalScore', 'creatorName', 'creator']
query = Session
.find(sessionsQueryParameters)
.limit(req.query.limit)