2014-11-28 20:49:41 -05:00
CocoView = require ' views/core/CocoView '
{ me } = require ' core/auth '
2014-01-03 13:32:13 -05:00
filters = require ' lib/image_filter '
2014-07-23 10:02:45 -04:00
SpellPaletteEntryView = require ' ./SpellPaletteEntryView '
2014-02-12 19:42:09 -05:00
LevelComponent = require ' models/LevelComponent '
2014-10-17 00:38:11 -04:00
ThangType = require ' models/ThangType '
2014-12-18 03:19:40 -05:00
GameMenuModal = require ' views/play/menu/GameMenuModal '
2015-08-13 08:58:37 -04:00
LevelSetupManager = require ' lib/LevelSetupManager '
2014-01-03 13:32:13 -05:00
2014-02-16 18:30:00 -05:00
N_ROWS = 4
2014-07-17 20:20:11 -04:00
module.exports = class SpellPaletteView extends CocoView
2014-01-03 13:32:13 -05:00
id: ' spell-palette-view '
2016-05-26 20:46:49 -04:00
template: require ' templates/play/level/tome/spell-palette-view '
2014-01-03 13:32:13 -05:00
controlsEnabled: true
subscriptions:
2014-08-27 15:24:03 -04:00
' level:disable-controls ' : ' onDisableControls '
' level:enable-controls ' : ' onEnableControls '
2014-06-30 22:16:26 -04:00
' surface:frame-changed ' : ' onFrameChanged '
2014-06-25 00:07:36 -04:00
' tome:change-language ' : ' onTomeChangedLanguage '
2014-12-18 03:19:40 -05:00
events:
' click # spell-palette-help-button ' : ' onClickHelp '
2016-05-26 20:46:49 -04:00
initialize: (options) ->
{ @ level , @ session , @ supermodel , @ thang , @ useHero } = options
2015-03-25 19:47:11 -04:00
docs = @ options . level . get ( ' documentation ' ) ? { }
@showsHelp = docs . specificArticles ? . length or docs . generalArticles ? . length
2014-02-16 18:30:00 -05:00
@ createPalette ( )
2014-10-17 00:38:11 -04:00
$ ( window ) . on ' resize ' , @ onResize
2014-02-16 18:30:00 -05:00
getRenderData: ->
c = super ( )
c.entryGroups = @ entryGroups
c.entryGroupSlugs = @ entryGroupSlugs
2014-06-25 23:19:11 -04:00
c.entryGroupNames = @ entryGroupNames
2014-02-16 18:30:00 -05:00
c.tabbed = _ . size ( @ entryGroups ) > 1
2014-03-13 12:02:19 -04:00
c.defaultGroupSlug = @ defaultGroupSlug
2015-03-25 19:47:11 -04:00
c.showsHelp = @ showsHelp
2015-06-10 18:18:37 -04:00
c.tabs = @ tabs # For hero-based, non-this-owned tabs like Vector, Math, etc.
2016-01-04 19:41:24 -05:00
c.thisName = { coffeescript: ' @ ' , lua: ' self ' , python: ' self ' , java: ' hero ' } [ @ options . language ] or ' this '
2015-06-10 18:18:37 -04:00
c._ = _
2014-02-16 18:30:00 -05:00
c
2014-01-03 13:32:13 -05:00
afterRender: ->
super ( )
2014-10-17 00:38:11 -04:00
if @ entryGroupSlugs
for group , entries of @ entryGroups
groupSlug = @ entryGroupSlugs [ group ]
for columnNumber , entryColumn of entries
col = $ ( ' <div class= " property-entry-column " ></div> ' ) . appendTo @ $el . find ( " .properties- #{ groupSlug } " )
for entry in entryColumn
col . append entry . el
entry . render ( ) # Render after appending so that we can access parent container for popover
$ ( ' .nano ' ) . nanoScroller ( )
@ updateCodeLanguage @ options . language
else
@entryGroupElements = { }
for group , entries of @ entryGroups
2015-06-10 18:18:37 -04:00
@ entryGroupElements [ group ] = itemGroup = $ ( ' <div class= " property-entry-item-group " ></div> ' ) . appendTo @ $el . find ( ' .properties-this ' )
2014-11-19 21:36:09 -05:00
if entries [ 0 ] . options . item ? . getPortraitURL
itemImage = $ ( ' <img class= " item-image " draggable=false></img> ' ) . attr ( ' src ' , entries [ 0 ] . options . item . getPortraitURL ( ) ) . css ( ' top ' , Math . max ( 0 , 19 * ( entries . length - 2 ) / 2 ) + 2 )
itemGroup . append itemImage
firstEntry = entries [ 0 ]
do (firstEntry) ->
itemImage . on " mouseenter " , (e) -> firstEntry . onMouseEnter e
itemImage . on " mouseleave " , (e) -> firstEntry . onMouseLeave e
for entry , entryIndex in entries
2014-10-17 00:38:11 -04:00
itemGroup . append entry . el
2014-02-16 18:30:00 -05:00
entry . render ( ) # Render after appending so that we can access parent container for popover
2014-11-19 21:36:09 -05:00
if entries . length is 1
entry . $el . addClass ' single-entry '
if entryIndex is 0
entry . $el . addClass ' first-entry '
2015-06-10 18:18:37 -04:00
for tab , entries of @ tabs or { }
tabSlug = _ . string . slugify tab
itemsInGroup = 0
for entry , entryIndex in entries
if itemsInGroup is 0 or ( itemsInGroup is 2 and entryIndex isnt entries . length - 1 )
itemGroup = $ ( ' <div class= " property-entry-item-group " ></div> ' ) . appendTo @ $el . find ( " .properties- #{ tabSlug } " )
itemsInGroup = 0
++ itemsInGroup
itemGroup . append entry . el
entry . render ( ) # Render after appending so that we can access parent container for popover
if itemsInGroup is 0
entry . $el . addClass ' first-entry '
2014-10-17 00:38:11 -04:00
@ $el . addClass ' hero '
2015-03-25 19:47:11 -04:00
@ $el . toggleClass ' shortenize ' , Boolean @ shortenize
2014-10-18 09:46:07 -04:00
@ updateMaxHeight ( ) unless application . isIPadApp
2014-06-25 00:07:36 -04:00
2014-10-16 15:08:21 -04:00
afterInsert: ->
super ( )
_ . delay => @ $el ? . css ( ' bottom ' , 0 ) unless $ ( ' # spell-view ' ) . is ( ' .shown ' )
2014-06-25 00:07:36 -04:00
updateCodeLanguage: (language) ->
@options.language = language
2014-01-03 13:32:13 -05:00
2014-10-17 00:38:11 -04:00
updateMaxHeight: ->
return unless @ isHero
2015-03-25 19:47:11 -04:00
# We figure out how many columns we can fit, width-wise, and then guess how many rows will be needed.
# We can then assign a height based on the number of rows, and the flex layout will do the rest.
columnWidth = if @ shortenize then 175 else 212
2015-06-10 18:18:37 -04:00
nColumns = Math . floor @ $el . find ( ' .properties-this ' ) . innerWidth ( ) / columnWidth # will always have 2 columns, since at 1024px screen we have 424px .properties
2014-10-17 00:38:11 -04:00
columns = ( { items: [ ] , nEntries: 0 } for i in [ 0 . . . nColumns ] )
2015-03-25 19:47:11 -04:00
orderedColumns = [ ]
2014-10-17 00:38:11 -04:00
nRows = 0
2015-03-25 19:47:11 -04:00
entryGroupsByLength = _ . sortBy _ . keys ( @ entryGroups ) , (group) => @ entryGroups [ group ] . length
entryGroupsByLength . reverse ( )
for group in entryGroupsByLength
entries = @ entryGroups [ group ]
2014-10-23 19:36:59 -04:00
continue unless shortestColumn = _ . sortBy ( columns , (column) -> column . nEntries ) [ 0 ]
2015-03-25 19:47:11 -04:00
shortestColumn . nEntries += Math . max 2 , entries . length # Item portrait is two rows tall
2014-10-17 00:38:11 -04:00
shortestColumn . items . push @ entryGroupElements [ group ]
2015-03-25 19:47:11 -04:00
orderedColumns . push shortestColumn unless shortestColumn in orderedColumns
2014-10-17 00:38:11 -04:00
nRows = Math . max nRows , shortestColumn . nEntries
2015-03-25 19:47:11 -04:00
for column in orderedColumns
2014-10-17 00:38:11 -04:00
for item in column . items
2015-06-10 18:18:37 -04:00
item . detach ( ) . appendTo @ $el . find ( ' .properties-this ' )
2015-03-25 19:47:11 -04:00
desiredHeight = 19 * ( nRows + 1 )
@ $el . find ( ' .properties ' ) . css ( ' height ' , desiredHeight )
2014-10-17 00:38:11 -04:00
onResize: (e) =>
@ updateMaxHeight ( )
2014-01-03 13:32:13 -05:00
createPalette: ->
2014-09-08 15:29:49 -04:00
Backbone . Mediator . publish ' tome:palette-cleared ' , { thangID: @ thang . id }
2014-02-12 19:42:09 -05:00
lcs = @ supermodel . getModels LevelComponent
allDocs = { }
2014-07-15 21:54:55 -04:00
excludedDocs = { }
2014-02-18 14:23:01 -05:00
for lc in lcs
for doc in ( lc . get ( ' propertyDocumentation ' ) ? [ ] )
2014-07-15 21:54:55 -04:00
if doc . codeLanguages and not ( @ options . language in doc . codeLanguages )
2014-07-15 22:25:53 -04:00
excludedDocs [ ' __ ' + doc . name ] = doc
2014-07-15 21:54:55 -04:00
continue
2014-02-28 15:43:31 -05:00
allDocs [ ' __ ' + doc . name ] ? = [ ]
allDocs [ ' __ ' + doc . name ] . push doc
2014-02-24 18:48:30 -05:00
if doc . type is ' snippet ' then doc.owner = ' snippets '
2014-01-03 13:32:13 -05:00
2014-04-25 19:57:42 -04:00
if @ options . programmable
propStorage =
' this ' : ' programmableProperties '
more: ' moreProgrammableProperties '
Math : ' programmableMathProperties '
Array : ' programmableArrayProperties '
Object : ' programmableObjectProperties '
String : ' programmableStringProperties '
2014-06-27 03:36:03 -04:00
Global: ' programmableGlobalProperties '
Function : ' programmableFunctionProperties '
RegExp : ' programmableRegExpProperties '
Date : ' programmableDateProperties '
Number : ' programmableNumberProperties '
JSON: ' programmableJSONProperties '
LoDash: ' programmableLoDashProperties '
2014-04-25 19:57:42 -04:00
Vector: ' programmableVectorProperties '
snippets: ' programmableSnippets '
else
propStorage =
2014-07-23 11:59:42 -04:00
' this ' : [ ' apiProperties ' , ' apiMethods ' ]
2016-06-25 11:38:59 -04:00
if not ( @ options . level . get ( ' type ' , true ) in [ ' hero ' , ' hero-ladder ' , ' hero-coop ' , ' course ' , ' course-ladder ' , ' game-dev ' ] ) or not @ options . programmable
2014-10-17 00:38:11 -04:00
@ organizePalette propStorage , allDocs , excludedDocs
else
@ organizePaletteHero propStorage , allDocs , excludedDocs
organizePalette: (propStorage, allDocs, excludedDocs) ->
2014-02-24 18:48:30 -05:00
count = 0
propGroups = { }
2014-07-23 11:59:42 -04:00
for owner , storages of propStorage
storages = [ storages ] if _ . isString storages
for storage in storages
props = _ . reject @ thang [ storage ] ? [ ] , (prop) -> prop [ 0 ] is ' _ ' # no private properties
2014-11-12 18:28:08 -05:00
props = _ . uniq props
2014-07-23 11:59:42 -04:00
added = _ . sortBy ( props ) . slice ( )
propGroups [ owner ] = ( propGroups [ owner ] ? [ ] ) . concat added
count += added . length
2014-10-17 00:38:11 -04:00
Backbone . Mediator . publish ' tome:update-snippets ' , propGroups: propGroups , allDocs: allDocs , language: @ options . language
2014-02-24 18:48:30 -05:00
2015-03-25 19:47:11 -04:00
@shortenize = count > 6
2014-02-24 18:48:30 -05:00
tabbify = count >= 10
2014-02-12 19:42:09 -05:00
@entries = [ ]
2014-02-24 18:48:30 -05:00
for owner , props of propGroups
2014-02-18 14:23:01 -05:00
for prop in props
2014-02-28 15:43:31 -05:00
doc = _ . find ( allDocs [ ' __ ' + prop ] ? [ ] ) , (doc) ->
2014-02-24 18:48:30 -05:00
return true if doc . owner is owner
2014-02-24 20:53:35 -05:00
return ( owner is ' this ' or owner is ' more ' ) and ( not doc . owner ? or doc . owner is ' this ' )
2014-07-15 22:25:53 -04:00
if not doc and not excludedDocs [ ' __ ' + prop ]
console . log ' could not find doc for ' , prop , ' from ' , allDocs [ ' __ ' + prop ] , ' for ' , owner , ' of ' , propGroups
2014-07-15 21:54:55 -04:00
doc ? = prop
if doc
2015-06-10 18:18:37 -04:00
@ entries . push @ addEntry ( doc , @ shortenize , owner is ' snippets ' )
2014-02-24 18:48:30 -05:00
groupForEntry = (entry) ->
2014-04-25 19:57:42 -04:00
return ' more ' if entry . doc . owner is ' this ' and entry . doc . name in ( propGroups . more ? [ ] )
2014-02-24 18:48:30 -05:00
entry . doc . owner
2014-02-16 18:30:00 -05:00
@entries = _ . sortBy @ entries , (entry) ->
2014-06-25 23:19:11 -04:00
order = [ ' this ' , ' more ' , ' Math ' , ' Vector ' , ' String ' , ' Object ' , ' Array ' , ' Function ' , ' snippets ' ]
2014-02-24 18:48:30 -05:00
index = order . indexOf groupForEntry entry
2014-02-16 18:30:00 -05:00
index = String . fromCharCode if index is - 1 then order . length else index
index += entry . doc . name
if tabbify and _ . find @ entries , ( (entry) -> entry . doc . owner isnt ' this ' )
2014-02-24 18:48:30 -05:00
@entryGroups = _ . groupBy @ entries , groupForEntry
2014-02-16 18:30:00 -05:00
else
2016-06-25 11:38:59 -04:00
i18nKey = if @ options . level . get ( ' type ' , true ) in [ ' hero ' , ' hero-ladder ' , ' hero-coop ' , ' course ' , ' course-ladder ' , ' game-dev ' ] then ' play_level.tome_your_skills ' else ' play_level.tome_available_spells '
2014-09-24 18:48:18 -04:00
defaultGroup = $ . i18n . t i18nKey
2014-02-16 18:30:00 -05:00
@entryGroups = { }
@ entryGroups [ defaultGroup ] = @ entries
2014-03-13 12:02:19 -04:00
@defaultGroupSlug = _ . string . slugify defaultGroup
2014-02-16 18:30:00 -05:00
@entryGroupSlugs = { }
2014-06-25 23:19:11 -04:00
@entryGroupNames = { }
2014-02-16 18:30:00 -05:00
for group , entries of @ entryGroups
@ entryGroups [ group ] = _ . groupBy entries , (entry, i) -> Math . floor i / N_ROWS
2014-06-25 23:19:11 -04:00
@ entryGroupSlugs [ group ] = _ . string . slugify group
@ entryGroupNames [ group ] = group
2015-06-10 18:18:37 -04:00
if thisName = { coffeescript: ' @ ' , lua: ' self ' , python: ' self ' } [ @ options . language ]
2014-06-25 23:19:11 -04:00
if @ entryGroupNames . this
@entryGroupNames.this = thisName
2014-02-16 18:30:00 -05:00
2014-10-17 00:38:11 -04:00
organizePaletteHero: (propStorage, allDocs, excludedDocs) ->
# Assign any kind of programmable properties to the items that grant them.
@isHero = true
itemThangTypes = { }
2014-11-05 18:43:08 -05:00
itemThangTypes [ tt . get ( ' name ' ) ] = tt for tt in @ supermodel . getModels ThangType # Also heroes
2014-10-17 00:38:11 -04:00
propsByItem = { }
propCount = 0
itemsByProp = { }
2014-11-12 18:28:08 -05:00
# Make sure that we get the spellbook first, then the primary hand, then anything else.
slots = _ . sortBy _ . keys ( @ thang . inventoryThangTypeNames ? { } ) , (slot) ->
if slot is ' left-hand ' then 0 else if slot is ' right-hand ' then 1 else 2
for slot in slots
thangTypeName = @ thang . inventoryThangTypeNames [ slot ]
2014-11-05 18:43:08 -05:00
if item = itemThangTypes [ thangTypeName ]
2014-11-01 12:35:19 -04:00
unless item . get ( ' components ' )
console . error ' Item ' , item , ' did not have any components when we went to assemble docs. '
for component in item . get ( ' components ' ) ? [ ] when component . config
2014-10-17 00:38:11 -04:00
for owner , storages of propStorage
if props = component . config [ storages ]
2014-11-12 18:28:08 -05:00
for prop in _ . sortBy ( props ) when prop [ 0 ] isnt ' _ ' and not itemsByProp [ prop ] # no private properties
2015-03-04 14:28:30 -05:00
continue if prop is ' moveXY ' and @ options . level . get ( ' slug ' ) is ' slalom ' # Hide for Slalom
2014-10-17 00:38:11 -04:00
propsByItem [ item . get ( ' name ' ) ] ? = [ ]
propsByItem [ item . get ( ' name ' ) ] . push owner: owner , prop: prop , item: item
itemsByProp [ prop ] = item
++ propCount
else
2014-11-05 18:43:08 -05:00
console . log @ thang . id , " couldn ' t find item ThangType for " , slot , thangTypeName
2014-10-17 00:38:11 -04:00
2015-06-10 18:18:37 -04:00
# Get any Math-, Vector-, etc.-owned properties into their own tabs
for owner , storage of propStorage when not ( owner in [ ' this ' , ' more ' , ' snippets ' ] )
continue unless @ thang [ storage ] ? . length
@ tabs ? = { }
@ tabs [ owner ] = [ ]
programmaticonName = @ thang . inventoryThangTypeNames [ ' programming-book ' ]
programmaticon = itemThangTypes [ programmaticonName ]
sortedProps = @ thang [ storage ] . slice ( ) . sort ( )
for prop in sortedProps
if doc = _ . find ( allDocs [ ' __ ' + prop ] ? [ ] ) , { owner: owner } # Not all languages have all props
entry = @ addEntry doc , false , false , programmaticon
@ tabs [ owner ] . push entry
2014-10-17 00:38:11 -04:00
# Assign any unassigned properties to the hero itself.
for owner , storage of propStorage
2015-06-10 18:18:37 -04:00
continue unless owner in [ ' this ' , ' more ' , ' snippets ' ]
2014-10-17 00:38:11 -04:00
for prop in _ . reject ( @ thang [ storage ] ? [ ] , (prop) -> itemsByProp [ prop ] or prop [ 0 ] is ' _ ' ) # no private properties
2015-03-04 14:28:30 -05:00
continue if prop is ' say ' and @ options . level . get ' hidesSay ' # Hide for Dungeon Campaign
continue if prop is ' moveXY ' and @ options . level . get ( ' slug ' ) is ' slalom ' # Hide for Slalom
2014-10-17 00:38:11 -04:00
propsByItem [ ' Hero ' ] ? = [ ]
2014-11-05 18:43:08 -05:00
propsByItem [ ' Hero ' ] . push owner: owner , prop: prop , item: itemThangTypes [ @ thang . spriteName ]
2014-10-17 00:38:11 -04:00
++ propCount
Backbone . Mediator . publish ' tome:update-snippets ' , propGroups: propsByItem , allDocs: allDocs , language: @ options . language
2015-03-25 19:47:11 -04:00
@shortenize = propCount > 6
2014-10-17 00:38:11 -04:00
@entries = [ ]
for itemName , props of propsByItem
for prop , propIndex in props
item = prop . item
owner = prop . owner
prop = prop . prop
doc = _ . find ( allDocs [ ' __ ' + prop ] ? [ ] ) , (doc) ->
return true if doc . owner is owner
return ( owner is ' this ' or owner is ' more ' ) and ( not doc . owner ? or doc . owner is ' this ' )
if not doc and not excludedDocs [ ' __ ' + prop ]
2014-10-18 17:51:43 -04:00
console . log ' could not find doc for ' , prop , ' from ' , allDocs [ ' __ ' + prop ] , ' for ' , owner , ' of ' , propsByItem , ' with item ' , item
2014-10-17 00:38:11 -04:00
doc ? = prop
if doc
2015-06-10 18:18:37 -04:00
@ entries . push @ addEntry ( doc , @ shortenize , owner is ' snippets ' , item , propIndex > 0 )
2014-10-17 00:38:11 -04:00
@entryGroups = _ . groupBy @ entries , (entry) -> itemsByProp [ entry . doc . name ] ? . get ( ' name ' ) ? ' Hero '
iOSEntryGroups = { }
for group , entries of @ entryGroups
2014-10-25 19:26:03 -04:00
iOSEntryGroups [ group ] =
item: { name: group , imageURL: itemThangTypes [ group ] ? . getPortraitURL ( ) }
props: ( entry . doc for entry in entries )
2014-10-17 00:38:11 -04:00
Backbone . Mediator . publish ' tome:palette-updated ' , thangID: @ thang . id , entryGroups: JSON . stringify ( iOSEntryGroups )
2015-06-10 18:18:37 -04:00
addEntry: (doc, shortenize, isSnippet=false, item=null, showImage=false) ->
2014-07-15 22:25:53 -04:00
writable = ( if _ . isString ( doc ) then doc else doc . name ) in ( @ thang . apiUserProperties ? [ ] )
2016-04-16 17:59:18 -04:00
new SpellPaletteEntryView doc: doc , thang: @ thang , shortenize: shortenize , isSnippet: isSnippet , language: @ options . language , writable: writable , level: @ options . level , item: item , showImage: showImage , useHero: @ useHero
2014-01-03 13:32:13 -05:00
onDisableControls: (e) -> @ toggleControls e , false
onEnableControls: (e) -> @ toggleControls e , true
toggleControls: (e, enabled) ->
return if e . controls and not ( ' palette ' in e . controls )
return if enabled is @ controlsEnabled
@controlsEnabled = enabled
@ $el . find ( ' * ' ) . attr ( ' disabled ' , not enabled )
2014-11-08 11:58:36 -05:00
@ $el . toggleClass ' controls-disabled ' , not enabled
2014-01-03 13:32:13 -05:00
onFrameChanged: (e) ->
return unless e . selectedThang ? . id is @ thang . id
2014-01-15 16:04:48 -05:00
@options.thang = @thang = e . selectedThang # Update our thang to the current version
2014-01-03 13:32:13 -05:00
2014-06-25 00:07:36 -04:00
onTomeChangedLanguage: (e) ->
@ updateCodeLanguage e . language
2014-06-25 23:19:11 -04:00
entry . destroy ( ) for entry in @ entries
@ createPalette ( )
@ render ( )
2014-12-28 16:25:20 -05:00
2014-12-18 03:19:40 -05:00
onClickHelp: (e) ->
application . tracker ? . trackEvent ' Spell palette help clicked ' , levelID: @ level . get ( ' slug ' )
2015-08-13 08:58:37 -04:00
gameMenuModal = new GameMenuModal showTab: ' guide ' , level: @ level , session: @ session , supermodel: @ supermodel
@ openModalView gameMenuModal
@ listenToOnce gameMenuModal , ' change-hero ' , ->
@ setupManager ? . destroy ( )
2015-11-12 14:00:54 -05:00
@setupManager = new LevelSetupManager ( { supermodel: @ supermodel , level: @ level , levelID: @ level . get ( ' slug ' ) , parent: @ , session: @ session , courseID: @ options . courseID , courseInstanceID: @ options . courseInstanceID } )
2015-08-13 08:58:37 -04:00
@ setupManager . open ( )
2014-06-25 00:07:36 -04:00
2014-01-03 13:32:13 -05:00
destroy: ->
entry . destroy ( ) for entry in @ entries
2014-02-12 15:41:41 -05:00
@toggleBackground = null
2014-10-17 00:38:11 -04:00
$ ( window ) . off ' resize ' , @ onResize
2015-08-13 08:58:37 -04:00
@ setupManager ? . destroy ( )
2014-02-14 13:57:47 -05:00
super ( )