diff --git a/app/locale/en.coffee b/app/locale/en.coffee index 8d326a9e4..2de659342 100644 --- a/app/locale/en.coffee +++ b/app/locale/en.coffee @@ -195,6 +195,50 @@ campaign_multiplayer_description: "... in which you code head-to-head against other players." campaign_old_multiplayer: "(Deprecated) Old Multiplayer Arenas" campaign_old_multiplayer_description: "Relics of a more civilized age. No simulations are run for these older, hero-less multiplayer arenas." + + code: + if: "if" # Keywords + else: "else" + elif: "elif" + while: "while" + loop: "loop" + for: "for" + break: "break" + continue: "continue" + then: "then" + do: "do" + end: "end" + function: "function" + def: "def" + self: "self" + hero: "hero" + this: "this" + Date: "Date" # Globals + Vector: "Vector" + Array: "Array" + Function: "Function" + Math: "Math" + Number: "Number" + Object: "Object" + RegExp: "RegExp" + String: "String" + isFinite: "isFinite" # Built-ins + isNaN: "isNaN" + parseFloat: "parseFloat" + parseInt: "parseInt" + decodeURI: "decodeURI" + decodeURIComponent: "decodeURIComponent" + encodeURI: "encodeURI" + encodeURIComponent: "encodeURIComponent" + escape: "escape" + unescape: "unescape" + Infinity: "Infinity" + NaN: "NaN" + undefined: "undefined" + null: "null" + Boolean: "Boolean" + Error: "Error" + arguments: "arguments" share_progress_modal: blurb: "You’re making great progress! Tell your parent how much you've learned with CodeCombat." diff --git a/app/styles/play/level/tome/spell_translation.sass b/app/styles/play/level/tome/spell_translation.sass new file mode 100644 index 000000000..daca32ef5 --- /dev/null +++ b/app/styles/play/level/tome/spell_translation.sass @@ -0,0 +1,18 @@ +@import "app/styles/mixins" +@import "app/styles/bootstrap/variables" + +.spell-translation-view + position: absolute + z-index: 9001 + max-width: 400px + pre + margin-bottom: 0 + code + white-space: nowrap + +html.no-borderimage + .spell-translation-view + background: transparent url(/images/level/popover_background.png) + background-size: 100% 100% + border: 0 + diff --git a/app/templates/play/level/tome/spell_translation.jade b/app/templates/play/level/tome/spell_translation.jade new file mode 100644 index 000000000..991b488e3 --- /dev/null +++ b/app/templates/play/level/tome/spell_translation.jade @@ -0,0 +1,3 @@ +pre + + code diff --git a/app/views/play/level/tome/Spell.coffee b/app/views/play/level/tome/Spell.coffee index 9d6ad2ba3..43650c064 100644 --- a/app/views/play/level/tome/Spell.coffee +++ b/app/views/play/level/tome/Spell.coffee @@ -48,7 +48,7 @@ module.exports = class Spell @source = @originalSource = p.aiSource @thangs = {} if @canRead() # We can avoid creating these views if we'll never use them. - @view = new SpellView {spell: @, level: options.level, session: @session, otherSession: @otherSession, worker: @worker, god: options.god} + @view = new SpellView {spell: @, level: options.level, session: @session, otherSession: @otherSession, worker: @worker, god: options.god, @supermodel} @view.render() # Get it ready and code loaded in advance @tabView = new SpellListTabEntryView spell: @, supermodel: @supermodel, codeLanguage: @language, level: options.level @tabView.render() diff --git a/app/views/play/level/tome/SpellTranslationView.coffee b/app/views/play/level/tome/SpellTranslationView.coffee new file mode 100644 index 000000000..bdb46636a --- /dev/null +++ b/app/views/play/level/tome/SpellTranslationView.coffee @@ -0,0 +1,99 @@ +CocoView = require 'views/core/CocoView' +LevelComponent = require 'models/LevelComponent' +template = require 'templates/play/level/tome/spell_translation' +Range = ace.require('ace/range').Range +TokenIterator = ace.require('ace/token_iterator').TokenIterator +serializedClasses = + Thang: require 'lib/world/thang' + Vector: require 'lib/world/vector' + Rectangle: require 'lib/world/rectangle' + Ellipse: require 'lib/world/ellipse' + LineSegment: require 'lib/world/line_segment' +utils = require 'core/utils' + +module.exports = class SpellTranslationView extends CocoView + className: 'spell-translation-view' + template: template + + events: + 'mousemove': -> + @$el.hide() + + constructor: (options) -> + super options + @ace = options.ace + @thang = options.thang + @spell = options.spell + @supermodel = options.supermodel + + + levelComponents = @supermodel.getModels LevelComponent + @componentTranslations = levelComponents.reduce((acc, lc) -> + for doc in (lc.get('propertyDocumentation') ? []) + translated = utils.i18n(doc, 'name', null, false) + acc[doc.name] = translated if translated isnt doc.name + acc + , {}) + + @onMouseMove = _.throttle @onMouseMove, 25 + + afterRender: -> + super() + @ace.on 'mousemove', @onMouseMove + + setTooltipText: (text) => + @$el.find('code').text text + @$el.show().css(@pos) + + isIdentifier: (t) -> + t and (t.type in ['identifier', 'keyword'] or t.value is 'this') + + onMouseMove: (e) => + return if @destroyed + pos = e.getDocumentPosition() + it = new TokenIterator e.editor.session, pos.row, pos.column + endOfLine = it.getCurrentToken()?.index is it.$rowTokens.length - 1 + while it.getCurrentTokenRow() is pos.row and not @isIdentifier(token = it.getCurrentToken()) + break if endOfLine or not token # Don't iterate beyond end or beginning of line + it.stepBackward() + unless @isIdentifier(token) + @word = null + @update() + return + try + # Ace was breaking under some (?) conditions, dependent on mouse location. + # with $rowTokens = [] (but should have things) + start = it.getCurrentTokenColumn() + catch error + start = 0 + end = start + token.value.length + if @isIdentifier(token) + @word = token.value + @markerRange = new Range pos.row, start, pos.row, end + @reposition(e.domEvent) + @update() + + reposition: (e) -> + offsetX = e.offsetX ? e.clientX - $(e.target).offset().left + offsetY = e.offsetY ? e.clientY - $(e.target).offset().top + w = $(document).width() - 20 + offsetX = w - $(e.target).offset().left - @$el.width() if e.clientX + @$el.width() > w + @pos = {left: offsetX + 80, top: offsetY - 20} + @$el.css(@pos) + + onMouseOut: -> + @word = null + @markerRange = null + @update() + + update: -> + i18nKey = 'code.'+@word + translation = @componentTranslations[@word] or $.t(i18nKey) + if @word and translation and translation not in [i18nKey, @word] + @setTooltipText translation + else + @$el.hide() + + destroy: -> + @ace?.removeEventListener 'mousemove', @onMouseMove + super() diff --git a/app/views/play/level/tome/SpellView.coffee b/app/views/play/level/tome/SpellView.coffee index 7af9134eb..f40b2772f 100644 --- a/app/views/play/level/tome/SpellView.coffee +++ b/app/views/play/level/tome/SpellView.coffee @@ -6,6 +6,7 @@ Range = ace.require('ace/range').Range UndoManager = ace.require('ace/undomanager').UndoManager Problem = require './Problem' SpellDebugView = require './SpellDebugView' +SpellTranslationView = require './SpellTranslationView' SpellToolbarView = require './SpellToolbarView' LevelComponent = require 'models/LevelComponent' UserCodeProblem = require 'models/UserCodeProblem' @@ -56,6 +57,7 @@ module.exports = class SpellView extends CocoView constructor: (options) -> super options + @supermodel = options.supermodel @worker = options.worker @session = options.session @listenTo(@session, 'change:multiplayer', @onMultiplayerChanged) @@ -638,6 +640,10 @@ module.exports = class SpellView extends CocoView return if @options.level.get('type', true) in ['hero', 'hero-ladder', 'hero-coop', 'course', 'course-ladder'] # We'll turn this on later, maybe, but not yet. @debugView = new SpellDebugView ace: @ace, thang: @thang, spell:@spell @$el.append @debugView.render().$el.hide() + + createTranslationView: -> + @translationView = new SpellTranslationView { @ace, @thang, @spell, @supermodel } + @$el.append @translationView.render().$el.hide() createToolbarView: -> @toolbarView = new SpellToolbarView ace: @ace @@ -661,6 +667,8 @@ module.exports = class SpellView extends CocoView @spellThang = @spell.thangs[@thang.id] @createDebugView() unless @debugView @debugView?.thang = @thang + @createTranslationView() unless @translationView + @translationView?.thang = @thang @toolbarView?.toggleFlow false @updateAether false, false # @addZatannaSnippets() @@ -1336,6 +1344,7 @@ module.exports = class SpellView extends CocoView @aceSession?.selection.off 'changeCursor', @onCursorActivity @destroyAceEditor(@ace) @debugView?.destroy() + @translationView?.destroy() @toolbarView?.destroy() @zatanna.addSnippets [], @editorLang if @editorLang? $(window).off 'resize', @onWindowResize