Remove code for multiple spells; rename SpellListTabEntryView to SpellTopBarView; remove hero avatar from SpellTopBarView

This commit is contained in:
Nick Winter 2016-07-14 22:43:25 -07:00
parent 220db3106c
commit e3670165e7
20 changed files with 78 additions and 593 deletions

View file

@ -115,9 +115,7 @@ module.exports = class God extends CocoClass
getUserCodeMap: (spells) ->
userCodeMap = {}
for spellKey, spell of spells
for thangID, spellThang of spell.thangs
continue if spellThang.thang?.programmableMethods[spell.name].cloneOf
(userCodeMap[thangID] ?= {})[spell.name] = spellThang.aether.serialize()
(userCodeMap[spell.thang.thang.id] ?= {})[spell.name] = spell.thang.aether.serialize()
userCodeMap

View file

@ -481,9 +481,7 @@
tome_cast_button_ran: "Ran"
tome_submit_button: "Submit"
tome_reload_method: "Reload original code for this method" # Title text for individual method reload button.
tome_select_method: "Select a Method"
tome_see_all_methods: "See all methods you can edit" # Title text for method list selector (shown when there are multiple programmable methods).
tome_select_a_thang: "Select Someone for "
tome_available_spells: "Available Spells"
tome_your_skills: "Your Skills"
tome_current_method: "Current Method"

View file

@ -39,8 +39,6 @@ module.exports =
variableChain: c.array {}, {type: 'string'}
frame: {type: 'integer', minimum: 0}
'tome:toggle-spell-list': c.object {title: 'Toggle Spell List', description: 'Published when you toggle the dropdown for a thang\'s spells'}
'tome:reload-code': c.object {title: 'Reload Code', description: 'Published when you reset a spell to its original source', required: []},
spell: {type: 'object'}
@ -91,10 +89,6 @@ module.exports =
problems: {type: 'array'}
isCast: {type: 'boolean'}
'tome:spell-shown': c.object {title: 'Spell Shown', description: 'Published when we show a spell', required: ['thang', 'spell']},
thang: {type: 'object'}
spell: {type: 'object'}
'tome:change-language': c.object {title: 'Tome Change Language', description: 'Published when the Tome should update its programming language', required: ['language']},
language: {type: 'string'}
reload: {type: 'boolean', description: 'Whether player code should reload to the default when the language changes.'}

View file

@ -1,15 +1,7 @@
@import "app/styles/mixins"
@import "app/styles/bootstrap/variables"
.spell-list-entry-view
.method-signature
background-color: transparent
border: 0
font-size: 1.1em
display: inline-block
padding: 4px
.spell-list-entry-view.spell-tab
#spell-top-bar-view
$height: 87px
$paddingTop: 10px
$paddingBottom: 25px
@ -46,12 +38,6 @@
> *:not(.spell-tool-buttons)
@include opacity(0.5)
.thang-avatar-view
width: $childSize - 10px
margin: 5px 0.4vw
display: inline-block
float: left
.btn.btn-small
margin-top: 15px
margin-right: 1.3vw
@ -97,46 +83,8 @@
.thang-avatar-wrapper
border-width: 0
.spell-list-entry-view:not(.spell-tab)
cursor: pointer
@include opacity(0.90)
clear: both
padding: 5px
position: relative
&:hover
@include opacity(1)
background-color: hsla(240, 40, 80, 0.25)
&.shows-top-divider:not(:first-child)
border-top: 1px dashed #ccc
.method-signature
margin-top: 5px
.thang-names
float: right
margin: 8px
font-variant: small-caps
color: darken(#ca8, 50%)
white-space: nowrap
overflow: hidden
text-overflow: ellipsis
font-size: 13px
max-width: 35%
text-align: right
.thang-avatar-view
width: 40px
float: right
.thang-avatar-wrapper
margin: 0 5px 0 0
//margin: 2px 10px 2px 5px
//html.no-borderimage
// .spell-list-entry-view.spell-tab
// .spell-top-bar-view
// border-width: 0
// border-image: none
// background: transparent url(/images/level/code_editor_tab_background.png) no-repeat

View file

@ -1,20 +0,0 @@
@import "app/styles/mixins"
@import "app/styles/bootstrap/variables"
#spell-list-view
display: none
position: absolute
z-index: 10
top: 50px
left: 0%
right: 10%
padding: 4%
border-style: solid
border-image: url(/images/level/popover_border_background.png) 16 12 fill round
border-width: 16px 12px
html.no-borderimage
#spell-list-view
background: transparent url(/images/level/popover_background.png)
background-size: 100% 100%
border: 0

View file

@ -1,30 +0,0 @@
@import "app/styles/mixins"
@import "app/styles/bootstrap/variables"
.spell-list-entry-view
.spell-list-entry-thangs-view
position: absolute
z-index: 11
top: 50px
right: -10%
max-width: 70%
max-height: 500px
overflow: scroll
padding: 4%
border-style: solid
border-image: url(/images/level/popover_border_background.png) 16 12 fill round
border-width: 16px 12px
.thang-avatar-view
cursor: pointer
max-width: 100px
width: 20%
display: inline-block
html.no-borderimage
.spell-list-entry-view
.spell-list-entry-thangs-view
background: transparent url(/images/level/popover_background.png)
background-size: 100% 100%
border: 0

View file

@ -3,12 +3,6 @@
.hinge.hinge-2
.hinge.hinge-3
if includeSpellList
.btn.btn-small.btn-illustrated.spell-list-button(data-i18n="[title]play_level.tome_see_all_methods", title="See all methods you can edit")
.glyphicon.glyphicon-chevron-down
.thang-avatar-placeholder
.spell-tool-buttons
.btn.btn-small.btn-illustrated.btn-warning.reload-code(data-i18n="[title]play_level.tome_reload_method", title="Reload original code for this method")
.glyphicon.glyphicon-repeat

View file

@ -1 +0,0 @@
h5(data-i18n="play_level.tome_select_method") Select a Method

View file

@ -1,7 +0,0 @@
if showTopDivider
// Don't repeat Thang names when not changed from previous entry
.thang-names(title=thangNames)= thangNames
code #{spell.name}(#{parameters})

View file

@ -1,3 +0,0 @@
h4
span(data-i18n="play_level.tome_select_a_thang") Select Someone for
code #{view.spell.name}(#{(view.spell.parameters || []).join(", ")})

View file

@ -1,11 +1,7 @@
#spell-list-tab-entry-view
#spell-list-view
#spell-top-bar-view
#cast-button-view
#spell-view
#spell-palette-view

View file

@ -57,12 +57,9 @@ module.exports = class ThangAvatarView extends CocoView
@$el.toggleClass 'selected', Boolean(selected)
onProblemsUpdated: (e) ->
return unless @thang?.id of e.spell.thangs
myProblems = []
for thangID, spellThang of e.spell.thangs when thangID is @thang.id
#aether = if e.isCast and spellThang.castAether then spellThang.castAether else spellThang.aether
aether = spellThang.castAether # try only paying attention to the actually cast ones
myProblems = myProblems.concat aether.getAllProblems() if aether
return unless @thang?.id is e.spell.thang?.thang.id
aether = e.spell.thang.castAether
myProblems = aether?.getAllProblems() ? []
worstLevel = null
for level in ['error', 'warning', 'info'] when _.some myProblems, {level: level}
worstLevel = level

View file

@ -1,5 +1,5 @@
SpellView = require './SpellView'
SpellListTabEntryView = require './SpellListTabEntryView'
SpellTopBarView = require './SpellTopBarView'
{me} = require 'core/auth'
{createAetherOptions} = require 'lib/aether_utils'
utils = require 'core/utils'
@ -7,7 +7,7 @@ utils = require 'core/utils'
module.exports = class Spell
loaded: false
view: null
entryView: null
topBarView: null
constructor: (options) ->
@spellKey = options.spellKey
@ -45,23 +45,22 @@ module.exports = class Spell
if p.aiSource and not @otherSession and not @canWrite()
@source = @originalSource = p.aiSource
@isAISource = true
@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, @supermodel, levelID: options.levelID}
@view.render() # Get it ready and code loaded in advance
@tabView = new SpellListTabEntryView
@topBarView = new SpellTopBarView
hintsState: options.hintsState
spell: @
supermodel: @supermodel
codeLanguage: @language
level: options.level
@tabView.render()
@topBarView.render()
Backbone.Mediator.publish 'tome:spell-created', spell: @
destroy: ->
@view?.destroy()
@tabView?.destroy()
@thangs = null
@topBarView?.destroy()
@thang = null
@worker = null
setLanguage: (@language) ->
@ -115,13 +114,13 @@ module.exports = class Spell
("// #{line}" for line in description.split('\n')).join('\n') + '\n' + @originalSource
addThang: (thang) ->
if @thangs[thang.id]
@thangs[thang.id].thang = thang
if @thang?.thang.id is thang.id
@thang.thang = thang
else
@thangs[thang.id] = {thang: thang, aether: @createAether(thang), castAether: null}
@thang = {thang: thang, aether: @createAether(thang), castAether: null}
removeThangID: (thangID) ->
delete @thangs[thangID]
@thang = null if @thang?.thang.id is thangID
canRead: (team) ->
(team ? me.team) in @permissions.read or (team ? me.team) in @permissions.readwrite
@ -137,30 +136,16 @@ module.exports = class Spell
@source = source
else
source = @getSource()
[pure, problems] = [null, null]
if @language is 'html'
[pure, problems] = [source, []] # TODO: problems? Actually do something when transpiling
for thangID, spellThang of @thangs
unless pure
pure = spellThang.aether.transpile source
problems = spellThang.aether.problems
#console.log 'aether transpiled', source.length, 'to', spellThang.aether.pure.length, 'for', thangID, @spellKey
else
spellThang.aether.raw = source
spellThang.aether.pure = pure
spellThang.aether.problems = problems
#console.log 'aether reused transpilation for', thangID, @spellKey
unless @language is 'html'
@thang?.aether.transpile source
null
hasChanged: (newSource=null, currentSource=null) ->
(newSource ? @originalSource) isnt (currentSource ? @source)
hasChangedSignificantly: (newSource=null, currentSource=null, cb) ->
for thangID, spellThang of @thangs
aether = spellThang.aether
break
unless aether
console.error @toString(), 'couldn\'t find a spellThang with aether of', @thangs
unless aether = @thang?.aether
console.error @toString(), 'couldn\'t find a spellThang with aether', @thang
cb false
if @worker
workerMessage =
@ -202,10 +187,9 @@ module.exports = class Spell
aether
updateLanguageAether: (@language) ->
for thangId, spellThang of @thangs
spellThang.aether?.setLanguage @language
spellThang.castAether = null
Backbone.Mediator.publish 'tome:spell-changed-language', spell: @, language: @language
@thang?.aether?.setLanguage @language
@thang?.castAether = null
Backbone.Mediator.publish 'tome:spell-changed-language', spell: @, language: @language
if @worker
workerMessage =
function: 'updateLanguageAether'

View file

@ -17,7 +17,6 @@ module.exports = class SpellDebugView extends CocoView
'god:new-world-created': 'onNewWorld'
'god:debug-value-return': 'handleDebugValue'
'god:debug-world-load-progress-changed': 'handleWorldLoadProgressChanged'
'tome:spell-shown': 'changeCurrentThangAndSpell'
'tome:cast-spells': 'onTomeCast'
'surface:frame-changed': 'onFrameChanged'
'tome:spell-has-changed-significantly-calculation': 'onSpellChangedCalculation'
@ -98,10 +97,6 @@ module.exports = class SpellDebugView extends CocoView
currentObject = currentObject[key]
currentObject[keys[keys.length - 1]] = value
changeCurrentThangAndSpell: (thangAndSpellObject) ->
@thang = thangAndSpellObject.thang
@spell = thangAndSpellObject.spell
handleDebugValue: (e) ->
{key, value} = e
@workerIsSimulating = false

View file

@ -1,33 +0,0 @@
CocoView = require 'views/core/CocoView'
ThangAvatarView = require 'views/play/level/ThangAvatarView'
template = require 'templates/play/level/tome/spell_list_entry_thangs'
module.exports = class SpellListEntryThangsView extends CocoView
className: 'spell-list-entry-thangs-view'
template: template
constructor: (options) ->
super options
@thangs = options.thangs
@thang = options.thang
@spell = options.spell
@avatars = []
afterRender: ->
super()
avatar.destroy() for avatar in @avatars if @avatars
@avatars = []
spellName = @spell.name
for thang in @thangs
avatar = new ThangAvatarView thang: thang, includeName: true, supermodel: @supermodel, creator: @
@$el.append avatar.el
avatar.render()
avatar.setSelected thang is @thang
avatar.$el.data('thang-id', thang.id).click (e) ->
Backbone.Mediator.publish 'level:select-sprite', thangID: $(@).data('thang-id'), spellName: spellName
avatar.onProblemsUpdated spell: @spell
@avatars.push avatar
destroy: ->
avatar.destroy() for avatar in @avatars
super()

View file

@ -1,115 +0,0 @@
# TODO: This still needs a way to send problem states to its Thang
CocoView = require 'views/core/CocoView'
ThangAvatarView = require 'views/play/level/ThangAvatarView'
SpellListEntryThangsView = require 'views/play/level/tome/SpellListEntryThangsView'
template = require 'templates/play/level/tome/spell_list_entry'
module.exports = class SpellListEntryView extends CocoView
tagName: 'div' #'li'
className: 'spell-list-entry-view'
template: template
controlsEnabled: true
subscriptions:
'tome:problems-updated': 'onProblemsUpdated'
'tome:spell-changed-language': 'onSpellChangedLanguage'
'level:disable-controls': 'onDisableControls'
'level:enable-controls': 'onEnableControls'
'god:new-world-created': 'onNewWorld'
events:
'click': 'onClick'
'mouseenter .thang-avatar-view': 'onMouseEnterAvatar'
'mouseleave .thang-avatar-view': 'onMouseLeaveAvatar'
constructor: (options) ->
super options
@spell = options.spell
@showTopDivider = options.showTopDivider
getRenderData: (context={}) ->
context = super context
context.spell = @spell
context.thangNames = (thangID for thangID, spellThang of @spell.thangs when spellThang.thang.exists).join(', ') # + ', Marcus, Robert, Phoebe, Will Smith, Zap Brannigan, You, Gandaaaaalf'
context.showTopDivider = @showTopDivider
context
getPrimarySpellThang: ->
if @lastSelectedThang
spellThang = _.find @spell.thangs, (spellThang) => spellThang.thang.id is @lastSelectedThang.id
return spellThang if spellThang
for thangID, spellThang of @spell.thangs
continue unless spellThang.thang.exists
return spellThang # Just do the first one else
afterRender: ->
super()
return unless @options.showTopDivider # Don't repeat Thang avatars when not changed from previous entry
return @$el.hide() unless spellThang = @getPrimarySpellThang()
@$el.show()
@avatar?.destroy()
@avatar = new ThangAvatarView thang: spellThang.thang, includeName: false, supermodel: @supermodel
@$el.prepend @avatar.el # Before rendering, so render can use parent for popover
@avatar.render()
@avatar.setSharedThangs _.size @spell.thangs
@$el.addClass 'shows-top-divider' if @options.showTopDivider
setSelected: (selected, @lastSelectedThang) ->
@avatar?.setSelected selected
onClick: (e) ->
spellThang = @getPrimarySpellThang()
Backbone.Mediator.publish 'level:select-sprite', thangID: spellThang.thang.id, spellName: @spell.name
onMouseEnterAvatar: (e) ->
return unless @controlsEnabled and _.size(@spell.thangs) > 1
@showThangs()
onMouseLeaveAvatar: (e) ->
return unless @controlsEnabled and _.size(@spell.thangs) > 1
@hideThangsTimeout = _.delay @hideThangs, 100
showThangs: ->
clearTimeout @hideThangsTimeout if @hideThangsTimeout
return if @thangsView
spellThang = @getPrimarySpellThang()
return unless spellThang
@thangsView = new SpellListEntryThangsView thangs: (spellThang.thang for thangID, spellThang of @spell.thangs), thang: spellThang.thang, spell: @spell, supermodel: @supermodel
@thangsView.render()
@$el.append @thangsView.el
@thangsView.$el.mouseenter (e) => @onMouseEnterAvatar()
@thangsView.$el.mouseleave (e) => @onMouseLeaveAvatar()
hideThangs: =>
return unless @thangsView
@thangsView.off 'mouseenter mouseleave'
@thangsView.$el.remove()
@thangsView.destroy()
@thangsView = null
onProblemsUpdated: (e) ->
return unless e.spell is @spell
@$el.toggleClass 'user-code-problem', e.problems.length
onSpellChangedLanguage: (e) ->
return unless e.spell is @spell
@render() # So that we can update parameters if needed
onDisableControls: (e) -> @toggleControls e, false
onEnableControls: (e) -> @toggleControls e, true
toggleControls: (e, enabled) ->
return if e.controls and not ('editor' in e.controls)
return if enabled is @controlsEnabled
@controlsEnabled = enabled
disabled = not enabled
# Should refactor the disabling list so we can target the spell list separately?
# Should not call it 'editor' any more?
@$el.toggleClass('disabled', disabled).find('*').prop('disabled', disabled)
onNewWorld: (e) ->
@lastSelectedThang = e.world.thangMap[@lastSelectedThang.id] if @lastSelectedThang
destroy: ->
@avatar?.destroy()
super()

View file

@ -1,96 +0,0 @@
# The SpellListView has SpellListEntryViews, which have ThangAvatarViews.
# The SpellListView serves as a dropdown triggered from a SpellListTabEntryView, which actually isn't in a list, just had a lot of similar parts.
# There is only one SpellListView, and it belongs to the TomeView.
# TODO: showTopDivider should change when we reorder
CocoView = require 'views/core/CocoView'
template = require 'templates/play/level/tome/spell_list'
{me} = require 'core/auth'
SpellListEntryView = require './SpellListEntryView'
module.exports = class SpellListView extends CocoView
className: 'spell-list-view'
id: 'spell-list-view'
template: template
subscriptions:
'god:new-world-created': 'onNewWorld'
constructor: (options) ->
super options
@entries = []
@sortSpells()
sortSpells: ->
# Keep only spells for which we have permissions
spells = _.filter @options.spells, (s) -> s.canRead()
@spells = _.sortBy spells, @sortScoreForSpell
#console.log 'Kept sorted spells', @spells
sortScoreForSpell: (s) =>
# Sort by most spells per fewest Thangs
# Lower comes first
score = 0
# Selected spell at the top
score -= 9001900190019001 if s is @spell
# Spells for selected thang at the top
score -= 900190019001 if @thang and @thang.id of s.thangs
# Read-only spells at the bottom
score += 90019001 unless s.canWrite()
# The more Thangs sharing a spell, the lower
score += 9001 * _.size(s.thangs)
# The more spells per Thang, the higher
score -= _.filter(@spells, (s2) -> thangID of s2.thangs).length for thangID of s.thangs
score
sortEntries: ->
# Call sortSpells before this
@entries = _.sortBy @entries, (entry) => _.indexOf @spells, entry.spell
@$el.append entry.$el for entry in @entries
afterRender: ->
super()
@addSpellListEntries()
addSpellListEntries: ->
newEntries = []
lastThangs = null
for spell, index in @spells
continue if _.find @entries, spell: spell
theseThangs = _.keys(spell.thangs)
changedThangs = not lastThangs or not _.isEqual theseThangs, lastThangs
lastThangs = theseThangs
newEntries.push entry = new SpellListEntryView spell: spell, showTopDivider: changedThangs, supermodel: @supermodel, level: @options.level
@entries.push entry
for entry in newEntries
@$el.append entry.el
entry.render() # Render after appending so that we can access parent container for popover
rerenderEntries: ->
entry.render() for entry in @entries
onNewWorld: (e) ->
@thang = e.world.thangMap[@thang.id] if @thang
setThangAndSpell: (@thang, @spell) ->
@entries[0]?.setSelected false
@sortSpells()
@sortEntries()
@entries[0].setSelected true, @thang
addThang: (thang) ->
@sortSpells()
@addSpellListEntries()
adjustSpells: (spells) ->
for entry in @entries when _.isEmpty entry.spell.thangs
entry.$el.remove()
entry.destroy()
@spells = @options.spells = spells
@sortSpells()
@addSpellListEntries()
destroy: ->
entry.destroy() for entry in @entries
super()

View file

@ -1,25 +1,21 @@
SpellListEntryView = require './SpellListEntryView'
ThangAvatarView = require 'views/play/level/ThangAvatarView'
template = require 'templates/play/level/tome/spell_list_tab_entry'
LevelComponent = require 'models/LevelComponent'
DocFormatter = require './DocFormatter'
template = require 'templates/play/level/tome/spell-top-bar-view'
ReloadLevelModal = require 'views/play/level/modal/ReloadLevelModal'
CocoView = require 'views/core/CocoView'
module.exports = class SpellListTabEntryView extends SpellListEntryView
module.exports = class SpellTopBarView extends CocoView
template: template
id: 'spell-list-tab-entry-view'
id: 'spell-top-bar-view'
controlsEnabled: true
subscriptions:
'level:disable-controls': 'onDisableControls'
'level:enable-controls': 'onEnableControls'
'tome:spell-loaded': 'onSpellLoaded'
'tome:spell-changed': 'onSpellChanged'
'god:new-world-created': 'onNewWorld'
'tome:spell-changed-language': 'onSpellChangedLanguage'
'tome:toggle-maximize': 'onToggleMaximize'
events:
'click .spell-list-button': 'onDropdownClick'
'click .reload-code': 'onCodeReload'
'click .beautify-code': 'onBeautifyClick'
'click .fullscreen-code': 'onToggleMaximize'
@ -27,6 +23,7 @@ module.exports = class SpellListTabEntryView extends SpellListEntryView
constructor: (options) ->
@hintsState = options.hintsState
@spell = options.spell
super(options)
getRenderData: (context={}) ->
@ -35,7 +32,6 @@ module.exports = class SpellListTabEntryView extends SpellListEntryView
shift = $.i18n.t 'keyboard_shortcuts.shift'
context.beautifyShortcutVerbose = "#{ctrl}+#{shift}+B: #{$.i18n.t 'keyboard_shortcuts.beautify'}"
context.maximizeShortcutVerbose = "#{ctrl}+#{shift}+M: #{$.i18n.t 'keyboard_shortcuts.maximize_editor'}"
context.includeSpellList = @options.level.get('slug') in ['break-the-prison', 'zone-of-danger', 'k-means-cluster-wars', 'brawlwood', 'dungeon-arena', 'sky-span', 'minimax-tic-tac-toe']
context.codeLanguage = @options.codeLanguage
context
@ -44,52 +40,6 @@ module.exports = class SpellListTabEntryView extends SpellListEntryView
@$el.addClass 'spell-tab'
@attachTransitionEventListener()
onNewWorld: (e) ->
@thang = e.world.thangMap[@thang.id] if @thang
setThang: (thang) ->
return if thang.id is @thang?.id
@thang = thang
@spellThang = @spell.thangs[@thang.id]
@buildAvatar()
@buildDocs() unless @docsBuilt
buildAvatar: ->
return unless @thang.world
avatar = new ThangAvatarView thang: @thang, includeName: false, supermodel: @supermodel
if @avatar
@avatar.$el.replaceWith avatar.$el
@avatar.destroy()
else
@$el.find('.thang-avatar-placeholder').replaceWith avatar.$el
@avatar = avatar
@avatar.render()
buildDocs: ->
return if @spell.name is 'plan' # Too confusing for beginners
@docsBuilt = true
lcs = @supermodel.getModels LevelComponent
found = false
for lc in lcs when not found
for doc in lc.get('propertyDocumentation') ? []
if doc.name is @spell.name
found = true
break
return unless found
docFormatter = new DocFormatter doc: doc, thang: @thang, language: @options.codeLanguage, selectedMethod: true
@$el.find('.method-signature').popover(
animation: true
html: true
placement: 'bottom'
trigger: 'hover'
content: docFormatter.formatPopover()
container: @$el.parent()
).on 'show.bs.popover', =>
@playSound 'spell-tab-entry-open', 0.75
onMouseEnterAvatar: (e) -> # Don't call super
onMouseLeaveAvatar: (e) -> # Don't call super
onClick: (e) -> # Don't call super
onDisableControls: (e) -> @toggleControls e, false
onEnableControls: (e) -> @toggleControls e, true
@ -98,15 +48,8 @@ module.exports = class SpellListTabEntryView extends SpellListEntryView
@hintsState.set('hidden', not @hintsState.get('hidden'))
window.tracker?.trackEvent 'Hints Clicked', category: 'Students', levelSlug: @options.level.get('slug'), hintCount: @hintsState.get('hints')?.length ? 0, ['Mixpanel']
onDropdownClick: (e) ->
return unless @controlsEnabled
Backbone.Mediator.publish 'tome:toggle-spell-list', {}
@playSound 'spell-list-open'
onCodeReload: (e) ->
#return unless @controlsEnabled
#Backbone.Mediator.publish 'tome:reload-code', spell: @spell # Old: just reload the current code
@openModalView new ReloadLevelModal() # New: prompt them to restart the level
@openModalView new ReloadLevelModal()
onBeautifyClick: (e) ->
return unless @controlsEnabled
@ -134,14 +77,10 @@ module.exports = class SpellListTabEntryView extends SpellListEntryView
onSpellChangedLanguage: (e) ->
return unless e.spell is @spell
@options.codeLanguage = e.language
@$el.find('.method-signature').popover 'destroy'
@render()
@docsBuilt = false
@buildDocs() if @thang
@updateReloadButton()
toggleControls: (e, enabled) ->
# Don't call super; do it differently
return if e.controls and not ('editor' in e.controls)
return if enabled is @controlsEnabled
@controlsEnabled = enabled
@ -163,8 +102,5 @@ module.exports = class SpellListTabEntryView extends SpellListEntryView
$codearea.on transitionListener, =>
$codearea.css 'z-index', 2 unless $('html').hasClass 'fullscreen-editor'
destroy: ->
@avatar?.destroy()
@$el.find('.method-signature').popover 'destroy'
super()

View file

@ -569,7 +569,7 @@ module.exports = class SpellView extends CocoView
@updateLines()
return if thang.id is @thang?.id
@thang = thang
@spellThang = @spell.thangs[@thang.id]
@spellThang = @spell.thang
@createDebugView() unless @debugView
@debugView?.thang = @thang
@createTranslationView() unless @translationView
@ -612,11 +612,11 @@ module.exports = class SpellView extends CocoView
lineHeight = @ace.renderer.lineHeight or 20
tomeHeight = $('#tome-view').innerHeight()
spellPaletteView = $('#spell-palette-view')
spellListTabEntryHeight = $('#spell-list-tab-entry-view').outerHeight()
spellTopBarHeight = $('#spell-top-bar-view').outerHeight()
spellToolbarHeight = $('.spell-toolbar-view').outerHeight()
@spellPaletteHeight ?= spellPaletteView.outerHeight() # Remember this until resize, since we change it afterward
spellPaletteAllowedHeight = Math.min @spellPaletteHeight, tomeHeight / 3
maxHeight = tomeHeight - spellListTabEntryHeight - spellToolbarHeight - spellPaletteAllowedHeight
maxHeight = tomeHeight - spellTopBarHeight - spellToolbarHeight - spellPaletteAllowedHeight
linesAtMaxHeight = Math.floor(maxHeight / lineHeight)
lines = Math.max 8, Math.min(screenLineCount + 2, linesAtMaxHeight)
# 2 lines buffer is nice
@ -903,15 +903,12 @@ module.exports = class SpellView extends CocoView
return if @spellThang?.castAether?.metrics?.statementsExecuted > 2000 # Don't preload if they are running significant amounts of user code
return if @options.level.isType('web-dev')
oldSource = @spell.source
oldSpellThangAethers = {}
for thangID, spellThang of @spell.thangs
oldSpellThangAethers[thangID] = spellThang.aether.serialize() # Get raw, pure, and problems
oldSpellThangAether = @spell.thang?.aether.serialize()
@spell.transpile @getSource()
@cast true
@spell.source = oldSource
for thangID, spellThang of @spell.thangs
for key, value of oldSpellThangAethers[thangID]
spellThang.aether[key] = value
for key, value of oldSpellThangAether
@spell.thang.aether[key] = value
onSpellChanged: (e) ->
@spellHasChanged = true
@ -928,10 +925,10 @@ module.exports = class SpellView extends CocoView
return unless e.god is @options.god
return @onInfiniteLoop e if e.problem.id is 'runtime_InfiniteLoop'
return unless e.problem.userInfo.methodName is @spell.name
return unless spellThang = _.find @spell.thangs, (spellThang, thangID) -> thangID is e.problem.userInfo.thangID
return unless @spell.thang?.thang.id is e.problem.userInfo.thangID
@spell.hasChangedSignificantly @getSource(), null, (hasChanged) =>
return if hasChanged
spellThang.aether.addProblem e.problem
@spell.thang.aether.addProblem e.problem
@lastUpdatedAetherSpellThang = null # force a refresh without a re-transpile
@updateAether false, false
@ -952,13 +949,14 @@ module.exports = class SpellView extends CocoView
@updateAether false, false
onNewWorld: (e) ->
@spell.removeThangID thangID for thangID of @spell.thangs when not e.world.getThangByID thangID
for thangID, spellThang of @spell.thangs
thang = e.world.getThangByID(thangID)
aether = e.world.userCodeMap[thangID]?[@spell.name] # Might not be there if this is a new Programmable Thang.
spellThang.castAether = aether
spellThang.aether = @spell.createAether thang
#console.log thangID, @spell.spellKey, 'ran', aether.metrics.callsExecuted, 'times over', aether.metrics.statementsExecuted, 'statements, with max recursion depth', aether.metrics.maxDepth, 'and full flow/metrics', aether.metrics, aether.flow
if thang = e.world.getThangByID @spell.thang?.thang.id
aether = e.world.userCodeMap[thang.id]?[@spell.name]
@spell.thang.castAether = aether
@spell.thang.aether = @spell.createAether thang
#console.log thang.id, @spell.spellKey, 'ran', aether.metrics.callsExecuted, 'times over', aether.metrics.statementsExecuted, 'statements, with max recursion depth', aether.metrics.maxDepth, 'and full flow/metrics', aether.metrics, aether.flow
else
@spell.thang = null
@spell.transpile() # TODO: is there any way we can avoid doing this if it hasn't changed? Causes a slight hang.
@updateAether false, false
@ -1149,7 +1147,7 @@ module.exports = class SpellView extends CocoView
toggleBackground: =>
# TODO: make the background an actual background and do the CSS trick
# used in spell_list_entry.sass for disabling
# used in spell-top-bar-view.sass for disabling
background = @$el.find('img.code-background')[0]
if background.naturalWidth is 0 # not loaded yet
return _.delay @toggleBackground, 100

View file

@ -2,36 +2,26 @@
# - a CastButtonView, which has
# - a cast button
# - a submit/done button
# - for each spell (programmableMethod):
# - for each spell (programmableMethod) (which is now just always only 'plan')
# - a Spell, which has
# - a list of Thangs that share that Spell, with one aether per Thang per Spell
# - a Thang that uses that Spell, with an aether and a castAether
# - a SpellView, which has
# - tons of stuff; the meat
# - a SpellListView, which has
# - for each spell:
# - a SpellListEntryView, which has
# - icons for each Thang
# - the spell name
# - a reload button
# - documentation for that method (in a popover)
# - a SpellTopBarView, which has some controls
# - a SpellPaletteView, which has
# - for each programmableProperty:
# - a SpellPaletteEntryView
#
# The CastButtonView and SpellListView always show.
# The CastButtonView always shows.
# The SpellPaletteView shows the entries for the currently selected Programmable Thang.
# The SpellView shows the code and runtime state for the currently selected Spell and, specifically, Thang.
# The SpellView obscures most of the SpellListView when present. We might mess with this.
# You can switch a SpellView to showing the runtime state of another Thang sharing that Spell.
# SpellPaletteViews are destroyed and recreated whenever you switch Thangs.
# The SpellListView shows spells to which your team has read or readwrite access.
# It doubles as a Thang selector, since it's there when nothing is selected.
CocoView = require 'views/core/CocoView'
template = require 'templates/play/level/tome/tome'
{me} = require 'core/auth'
Spell = require './Spell'
SpellListView = require './SpellListView'
SpellPaletteView = require './SpellPaletteView'
CastButtonView = require './CastButtonView'
@ -44,7 +34,6 @@ module.exports = class TomeView extends CocoView
subscriptions:
'tome:spell-loaded': 'onSpellLoaded'
'tome:cast-spell': 'onCastSpell'
'tome:toggle-spell-list': 'onToggleSpellList'
'tome:change-language': 'updateLanguageForAllSpells'
'surface:sprite-selected': 'onSpriteSelected'
'god:new-world-created': 'onNewWorld'
@ -52,7 +41,6 @@ module.exports = class TomeView extends CocoView
'tome:select-primary-sprite': 'onSelectPrimarySprite'
events:
'click #spell-view': 'onSpellViewClick'
'click': 'onClick'
afterRender: ->
@ -62,9 +50,7 @@ module.exports = class TomeView extends CocoView
if @options.level.isType('web-dev')
if @fakeProgrammableThang = @createFakeProgrammableThang()
programmableThangs = [@fakeProgrammableThang]
@createSpells programmableThangs, programmableThangs[0]?.world # Do before spellList, thangList, and castButton
unless @options.level.isType('hero', 'hero-ladder', 'hero-coop', 'course', 'course-ladder', 'game-dev', 'web-dev')
@spellList = @insertSubView new SpellListView spells: @spells, supermodel: @supermodel, level: @options.level
@createSpells programmableThangs, programmableThangs[0]?.world # Do before castButton
@castButton = @insertSubView new CastButtonView spells: @spells, level: @options.level, session: @options.session, god: @options.god
@teamSpellMap = @generateTeamSpellMap(@spells)
unless programmableThangs.length
@ -75,10 +61,8 @@ module.exports = class TomeView extends CocoView
delete @options.thangs
onNewWorld: (e) ->
thangs = _.filter e.world.thangs, 'inThangList'
programmableThangs = _.filter thangs, (t) -> t.isProgrammable and t.programmableMethods
programmableThangs = _.filter e.thangs, (t) -> t.isProgrammable and t.programmableMethods and t.inThangList
@createSpells programmableThangs, e.world
@spellList?.adjustSpells @spells
onCommentMyCode: (e) ->
for spellKey, spell of @spells when spell.canWrite()
@ -117,30 +101,27 @@ module.exports = class TomeView extends CocoView
@thangSpells[thang.id] = []
for methodName, method of thang.programmableMethods
pathComponents = [thang.id, methodName]
if method.cloneOf
pathComponents[0] = method.cloneOf # referencing another Thang's method
pathComponents[0] = _.string.slugify pathComponents[0]
spellKey = pathComponents.join '/'
@thangSpells[thang.id].push spellKey
unless method.cloneOf
skipProtectAPI = @getQueryVariable 'skip_protect_api', (@options.levelID in ['gridmancer', 'minimax-tic-tac-toe'])
spell = @spells[spellKey] = new Spell
hintsState: @options.hintsState
programmableMethod: method
spellKey: spellKey
pathComponents: pathPrefixComponents.concat(pathComponents)
session: @options.session
otherSession: @options.otherSession
supermodel: @supermodel
skipProtectAPI: skipProtectAPI
worker: @worker
language: language
spectateView: @options.spectateView
spectateOpponentCodeLanguage: @options.spectateOpponentCodeLanguage
observing: @options.observing
levelID: @options.levelID
level: @options.level
god: @options.god
skipProtectAPI = @getQueryVariable 'skip_protect_api', false
spell = @spells[spellKey] = new Spell
hintsState: @options.hintsState
programmableMethod: method
spellKey: spellKey
pathComponents: pathPrefixComponents.concat(pathComponents)
session: @options.session
otherSession: @options.otherSession
supermodel: @supermodel
skipProtectAPI: skipProtectAPI
worker: @worker
language: language
spectateView: @options.spectateView
spectateOpponentCodeLanguage: @options.spectateOpponentCodeLanguage
observing: @options.observing
levelID: @options.levelID
level: @options.level
god: @options.god
for thangID, spellKeys of @thangSpells
thang = @fakeProgrammableThang ? world.getThangByID thangID
@ -176,53 +157,24 @@ module.exports = class TomeView extends CocoView
difficulty = Math.max 0, difficulty - 1 # Show the difficulty they won, not the next one.
Backbone.Mediator.publish 'tome:cast-spells', spells: @spells, preload: preload, realTime: realTime, submissionCount: sessionState.submissionCount ? 0, flagHistory: sessionState.flagHistory ? [], difficulty: difficulty, god: @options.god, fixedSeed: @options.fixedSeed
onToggleSpellList: (e) ->
@spellList?.rerenderEntries()
@spellList?.$el.toggle()
onSpellViewClick: (e) ->
@spellList?.$el.hide()
onClick: (e) ->
Backbone.Mediator.publish 'tome:focus-editor', {} unless $(e.target).parents('.popover').length
clearSpellView: ->
@spellView?.dismiss()
@spellView?.$el.after('<div id="' + @spellView.id + '"></div>').detach()
@spellView = null
@spellTabView?.$el.after('<div id="' + @spellTabView.id + '"></div>').detach()
@spellTabView = null
@removeSubView @spellPaletteView if @spellPaletteView
@spellPaletteView = null
@$el.find('#spell-palette-view').hide()
@castButton?.$el.hide()
onSpriteSelected: (e) ->
return if @spellView and @options.level.get('type', true) in ['hero', 'hero-ladder', 'hero-coop', 'course', 'course-ladder', 'game-dev', 'web-dev'] # Never deselect the hero in the Tome.
thang = e.thang
spellName = e.spellName
@spellList?.$el.hide()
return @clearSpellView() unless thang
spell = @spellFor thang, spellName
unless spell?.canRead()
@clearSpellView()
@updateSpellPalette thang, spell if spell
return
@setSpellView spell, thang
spell = @spellFor e.thang, e.spellName
if spell?.canRead()
@setSpellView spell, e.thang
setSpellView: (spell, thang) ->
unless spell.view is @spellView
@clearSpellView()
@spellView = spell.view
@spellTabView = spell.tabView
@spellTopBarView = spell.topBarView
@$el.find('#' + @spellView.id).after(@spellView.el).remove()
@$el.find('#' + @spellTabView.id).after(@spellTabView.el).remove()
@$el.find('#' + @spellTopBarView.id).after(@spellTopBarView.el).remove()
@castButton?.attachTo @spellView
Backbone.Mediator.publish 'tome:spell-shown', thang: thang, spell: spell
@updateSpellPalette thang, spell
@spellList?.setThangAndSpell thang, spell
@spellView?.setThang thang
@spellTabView?.setThang thang
updateSpellPalette: (thang, spell) ->
return unless thang and @spellPaletteView?.thang isnt thang and (thang.programmableProperties or thang.apiProperties or thang.programmableHTMLProperties)
@ -256,7 +208,7 @@ module.exports = class TomeView extends CocoView
if @options.level.isType('web-dev')
@setSpellView @spells['hero-placeholder/plan'], @fakeProgrammableThang
return
# This is only fired by PlayLevelView for hero levels currently
# This is fired by PlayLevelView
# TODO: Don't hard code these hero names
if @options.session.get('team') is 'ogres'
Backbone.Mediator.publish 'level:select-sprite', thangID: 'Hero Placeholder 1'