Implementing more of new code editor design. Removed ThangList, EditorConfigModal, and a few other obsolete things.

This commit is contained in:
Nick Winter 2014-11-07 16:04:35 -08:00
parent fbc2799211
commit 5090a47afc
54 changed files with 278 additions and 754 deletions

Binary file not shown.

After

(image error) Size: 4.2 KiB

Binary file not shown.

After

(image error) Size: 3.5 KiB

Binary file not shown.

After

(image error) Size: 3.7 KiB

Binary file not shown.

After

(image error) Size: 3.1 KiB

Binary file not shown.

After

(image error) Size: 4.1 KiB

Binary file not shown.

After

(image error) Size: 3.7 KiB

Binary file not shown.

After

(image error) Size: 34 KiB

Binary file not shown.

After

(image error) Size: 1.7 KiB

Binary file not shown.

After

(image error) Size: 25 KiB

Binary file not shown.

After

(image error) Size: 65 KiB

Binary file not shown.

After

(image error) Size: 7.6 KiB

Binary file not shown.

After

(image error) Size: 4.8 KiB

Binary file not shown.

After

(image error) Size: 4.7 KiB

Binary file not shown.

After

(image error) Size: 5.3 KiB

Binary file not shown.

After

(image error) Size: 5.2 KiB

Binary file not shown.

After

(image error) Size: 5.2 KiB

Binary file not shown.

After

(image error) Size: 5.2 KiB

Binary file not shown.

After

(image error) Size: 5.5 KiB

Binary file not shown.

After

(image error) Size: 5.5 KiB

Binary file not shown.

After

(image error) Size: 2.1 KiB

Binary file not shown.

After

(image error) Size: 4.7 KiB

View file

@ -198,6 +198,7 @@
failing: "Failing"
action_timeline: "Action Timeline"
click_to_select: "Click on a unit to select it."
reload: "Reload"
reload_title: "Reload All Code?"
reload_really: "Are you sure you want to reload this level back to the beginning?"
reload_confirm: "Reload All"
@ -447,7 +448,7 @@
enter: "Enter"
escape: "Escape"
shift: "Shift"
cast_spell: "Cast current spell."
run_code: "Run current code."
run_real_time: "Run in real time."
continue_script: "Continue past current script."
skip_scripts: "Skip past all skippable scripts."

View file

@ -85,9 +85,6 @@ module.exports =
problems: {type: 'array'}
isCast: {type: 'boolean'}
'tome:thang-list-entry-popover-shown': c.object {title: 'Thang List Entry Popover Shown', description: 'Published when we show the popover for a thang in the master list', required: ['entry']},
entry: {type: 'object'}
'tome:spell-shown': c.object {title: 'Spell Shown', description: 'Published when we show a spell', required: ['thang', 'spell']},
thang: {type: 'object'}
spell: {type: 'object'}

View file

@ -312,9 +312,56 @@ kbd
.arrow
display: none
.btn.btn-illustrated
background: 0
border: 0
border-radius: 0
@include box-shadow(none)
border-image: url(/images/common/button-background-active-border.png) 14 20 20 20 fill round
border-width: 7px 10px 10px 10px
padding: 0
font-family: Open Sans Condensed
text-transform: uppercase
font-weight: bold
color: rgb(248, 197, 146)
&:active
border-image: url(/images/common/button-background-pressed-border.png) 14 16 16 20 fill round
padding: 2px 0 0 2px
border-width: 7px 8px 8px 10px
&.disabled, &:disabled
border-image: url(/images/common/button-background-disabled-border.png) 14 20 20 20 fill round
@include opacity(1)
> *
@include opacity(0.5)
> *
@include opacity(0.75)
&:hover > *
@include opacity(1)
html.no-borderimage
.popover
border: 0
background: transparent url(/images/level/popover_background.png)
background-size: 100% 100%
padding: 10px 20px
.btn.btn-illustrated
border: 0
background-image: url(/images/common/button-background-active.png)
background-size: 100% 100%
padding: 7px 10px 10px 10px
&:active
background-image: url(/images/common/button-background-pressed.png)
padding: 9px 8px 8px 12px
border: 0
&.disabled, &:disabled
background-image: url(/images/common/button-background-disabled.png)

View file

@ -97,6 +97,7 @@ $level-resize-transition-time: 0.5s
top: 0px
bottom: 0
@include transition(width $level-resize-transition-time ease-in-out, right $level-resize-transition-time ease-in-out)
overflow: hidden
// Level Docs
.ui-effects-transfer

View file

@ -1,12 +0,0 @@
#level-editor-config-modal
.select-group
display: block
min-height: 20px
margin-top: 10px
margin-bottom: 10px
padding-left: 20px
vertical-align: middle
label
font-weight: normal
margin-right: 20px

View file

@ -28,9 +28,9 @@
.avatar-frame
position: absolute
left: -13%
top: -14%
width: 132%
left: -18%
top: -19%
width: 145%
.badge
$radius: 8px

View file

@ -17,8 +17,8 @@
@include box-shadow(0px 0px 8px #333)
color: white
50%
@include box-shadow(0px 0px 35px #87FFCE)
color: #87FFFF
@include box-shadow(0px 0px 35px #87CEFF)
color: #87CEFF
to
@include box-shadow(0px 0px 8px #333)
color: white
@ -31,40 +31,113 @@
width: 100%
border-radius: 6px
.btn
padding: 3px 10px
height: 40px
font-size: 22px
background: transparent url(/images/level/code_toolbar_background.png)
background-size: 100% 100%
left: -15px
right: 15px
height: 94px
margin-top: -8px
padding: 20px 2.8%
.submit-button
margin-left: 20px
min-width: 150px
.btn.btn-illustrated
height: 46px
font-size: 24px
line-height: 24px
width: 45%
width: -webkit-calc(50% - 10px)
width: calc(50% - 10px)
border-image: url(/images/level/code_toolbar_run_button_active.png) 14 20 20 20 fill round
border-width: 7px 10px 10px 10px
&:active
border-image: url(/images/level/code_toolbar_run_button_active_pressed.png) 14 20 20 20 fill round
padding: 2px 0 0 2px
&.submit-button
margin-left: 10px
border-image: url(/images/level/code_toolbar_submit_button_active.png) 14 20 20 20 fill round
&:active
border-image: url(/images/level/code_toolbar_submit_button_active_pressed.png) 14 20 20 20 fill round
.cast-button
margin-left: 10px
min-width: 150px
@include opacity(0.77)
&:hover, &.castable
@include opacity(1)
.submit-button > *
@include opacity(0.9)
&:hover
@include opacity(1)
&:not(.winnable)
.cast-button.castable
font-weight: bold
-webkit-animation-name: castablePulse
-webkit-animation-duration: 3s
-webkit-animation-iteration-count: infinite
.btn.btn-illustrated
.submit-button
font-size: 16px
&.cast-button.castable
font-weight: bold
@include animation(castablePulse 3s infinite)
border-image: url(/images/level/code_toolbar_run_button_zazz.png) 14 20 20 20 fill round
&:active
border-image: url(/images/level/code_toolbar_run_button_zazz_pressed.png) 14 20 20 20 fill round
&.submit-button
font-size: 16px
&.winnable
.submit-button
font-weight: bold
-webkit-animation-name: winnablePulse
-webkit-animation-duration: 3s
-webkit-animation-iteration-count: infinite
.cast-button
font-size: 16px
.btn.btn-illustrated
&.submit-button
font-weight: bold
@include animation(winnablePulse 3s infinite)
border-image: url(/images/level/code_toolbar_submit_button_zazz.png) 14 20 20 20 fill round
&:active
border-image: url(/images/level/code_toolbar_submit_button_zazz_pressed.png) 14 20 20 20 fill round
&.cast-button
font-size: 16px
html.no-borderimage #cast-button-view
.btn.btn-illustrated
border: 0
background-image: url(/images/level/code_toolbar_run_button_active.png)
background-size: 100% 100%
padding: 7px 10px 10px 10px
&:active
background-image: url(/images/level/code_toolbar_run_button_active_pressed.png)
padding: 9px 8px 8px 12px
border: 0
&.submit-button
background-image: url(/images/level/code_toolbar_submit_button_active_pressed.png)
border: 0
&:active
background-image: url(/images/level/code_toolbar_submit_button_active_pressed.png)
border: 0
&:not(.winnable)
.btn.btn-illustrated
&.cast-button.castable
border: 0
background-image: url(/images/level/code_toolbar_run_button_zazz.png)
&:active
background-image: url(/images/level/code_toolbar_run_button_zazz_pressed.png)
&.winnable
.btn.btn-illustrated
&.submit-button
border: 0
background-image: url(/images/level/code_toolbar_submit_button_zazz.png)
&:active
border: 0
background-image: url(/images/level/code_toolbar_submit_button_zazz_pressed.png)

View file

@ -2,38 +2,37 @@
@import "app/styles/bootstrap/variables"
@mixin editor-height($extraHeight)
@include box-sizing(border-box)
width: 98%
height: 83%
height: unquote("-webkit-calc(100% - 60px -")$extraHeight unquote(")")
height: unquote("calc(100% - 60px -")$extraHeight unquote(")")
height: unquote("-webkit-calc(100% - 100px -")$extraHeight unquote(")")
height: unquote("calc(100% - 100px -")$extraHeight unquote(")")
#spell-view
position: absolute
left: 10px
top: 140px
//top: 48px
right: 10px
padding-bottom: 10px
z-index: 1
// Set z-index above palette
margin-top: 10px
padding-top: 20px
padding-left: 30px
padding-bottom: 95px
display: none
position: relative
z-index: 1
&.shown
display: block
.code-background
position: absolute
top: 0
top: -68px
left: 0px
height: 100%
width: 100%
right: -10px
span.code-background
border-width: 40px
border-image: url(/images/level/code_editor_background.png) 40 fill round
border-width: 124px 76px 64px 40px
border-image: url(/images/level/code_editor_background_border.png) 124 76 64 40 fill round
img.code-background
display: none
width: 100%
.save-status
display: none
@ -44,7 +43,7 @@
.firepad
// When Firepad is active, it wraps .ace_editor in .firepad.
@include editor-height(0px)
width: 98%
width: 94%
.ace_editor
width: 100%
@ -56,7 +55,7 @@
.ace_editor
// When Firepad isn't active, .ace_editor needs the width/height set itself.
@include editor-height(0px)
width: 98%
width: 94%
position: relative
background-color: transparent
line-height: 20px
@ -70,14 +69,18 @@
@include opacity(20)
.ace_gutter
background-color: rgba(255, 255, 255, 0.25)
background-color: transparent
border-right: 1px solid rgb(195, 178, 156)
//background-color: rgba(255, 255, 255, 0.25)
width: 47px
margin-left: 4px
border-bottom: 1px dotted #2f261d
.ace_scroller
background-color: transparent
border-bottom: 1px dotted #2f261d
//padding-left: 10px // Interrupts gutter and line highlighting
.ace_active-line, .ace_gutter-active-line
background-color: rgba(255, 255, 255, 0.4)
.ace_content
.executing, .executed, .problem-marker-info, .problem-marker-warning, .problem-marker-error
@ -147,10 +150,10 @@
.ace_searchbtn, .ace_replacebtn
padding: 0px 4px
html.no-borderimage
#spell-view
span.code-background
display: none
img.code-background
display: block

View file

@ -13,7 +13,6 @@
$height: 87px
$paddingTop: 10px
$paddingBottom: 25px
$childMargin: 2px
$childSize: $height - $paddingTop - $paddingBottom
width: 100%
height: $height
@ -22,6 +21,7 @@
position: relative
background: transparent url(/images/level/code_editor_top_bar_wood_background.png)
background-size: 100% 100%
z-index: 2
.hinge
position: absolute
@ -30,7 +30,6 @@
width: 24px
height: 20px
background-size: contain
z-index: 100
.hinge-0
left: 20%
@ -45,15 +44,24 @@
background: linear-gradient(to bottom, rgba(0,0,0,0.5) 0%,rgba(0,0,0,0.5) 100%), url(/images/level/code_editor_top_bar_wood_background.png)
background-size: 100% 100%
.spell-list-button, .thang-avatar-view, .reload-code, .beautify-code, .fullscreen-code
width: $childSize
height: $childSize
margin: 0 $childMargin
.thang-avatar-view
width: $childSize - 10px
margin: 5px
display: inline-block
.spell-list-button, .thang-avatar-view
float: left
.btn.btn-small
margin-top: 15px
margin-right: 15px
font-size: 18px
.glyphicon
font-size: 16px
.btn.btn-small.spell-list-button
float: left
margin-top: 5px
.spell-tool-buttons
position: absolute
right: 0px
@ -69,43 +77,32 @@
.fullscreen-code
float: right
&:not(.maximized)
.icon-resize-small
.glyphicon-resize-small
display: none
&.maximized
.icon-fullscreen
.glyphicon-fullscreen
display: none
.btn.btn-small
background: transparent
padding: 0
&:not(:hover)
border-color: transparent
@include box-shadow(none)
.icon-chevron-down, .icon-repeat, .icon-magnet, .icon-fullscreen, .icon-resize-small
margin-top: 7px
.thang-avatar-wrapper
border-width: 0
.method-label
.method-name-area
margin-left: 10px
line-height: $childSize
font-size: 1vw
display: inline-block
font-weight: bold
color: white
margin-top: 10px
text-transform: uppercase
display: inline-block
font-family: Open Sans Condensed
font-weight: bold
.method-signature
margin-left: 10px
line-height: $childSize
font-size: 1vw
display: inline-block
font-weight: bold
color: white
text-transform: uppercase
.method-label
font-size: 12px
color: rgb(243, 211, 59)
margin-bottom: -5px
.method-signature
color: white
font-size: 18px
padding: 0
.spell-list-entry-view:not(.spell-tab)
cursor: pointer

View file

@ -3,55 +3,44 @@
#spell-palette-view
position: absolute
padding-bottom: 10px
left: 10px
right: 10px
//height: 140px
// Height relates to .tab-content height
padding-top: 35px
padding-left: 12px
padding-right: 4px
color: #333
// Get crazy with the backgrounds so that we can lower the opacity on the editor background above it, making a gradient of the disabled background color on the top around where it's usually covered
padding: 0 4px 10px 40px
background-color: transparent
background-size: 100% 100%
z-index: 0
z-index: 2
//overflow-y: auto
.code-palette-background
position: absolute
left: 0
top: 0
width: 100%
height: 100%
z-index: -1
span.code-palette-background
border-width: 25px
border-image: url(/images/level/code_palette_background.png) 25 fill round
img.code-palette-background
display: none
.code-palette-background
width: 100%
position: absolute
left: 0px
z-index: -1
&.disabled
@include opacity(0.80)
h4
color: #333
color: white
font-size: 16px
line-height: 16px
margin: 0 4px
margin: 25px 0 5px 2px
font-weight: normal
text-transform: uppercase
.nav > li > a
padding: 2px 20px 0px 20px
margin-bottom: 3px
ul.nav.nav-pills
margin-top: 15px
h4
margin-top: 2px
li.active a
background-color: transparent
&.multiple-tabs li.active a
background-color: lighten(rgb(230, 212, 146), 10%)
background-color: darken(rgb(230, 212, 146), 30%)
&.multiple-tabs li:not(.active) a
cursor: pointer
@ -82,8 +71,9 @@
@include flexbox()
@include flex-wrap()
@include flex-center()
outline: 1px dashed #b86
position: relative
background-color: rgb(20, 13, 8)
margin: 1px
img.item-image
width: 38px
@ -91,49 +81,11 @@
position: absolute
&:not(:hover) img.item-image
-webkit-filter: sepia(100%)
filter: sepia(100%)
-webkit-filter: contrast(50%) sepia(100%) saturate(500%) hue-rotate(7deg)
filter: contrast(50%) sepia(100%) saturate(1000%) hue-rotate(7deg)
.spell-palette-entry-view
margin-left: 38px
width: 174px
width: -webkit-calc(100% - 38px)
width: calc(100% - 38px)
.code-language-logo
position: absolute
width: 16px
height: 16px
left: 16px
top: 36px
z-index: 10
background-color: transparent
background-repeat: no-repeat
background-size: contain
cursor: pointer
&.javascript
background-image: url(/images/common/code_languages/javascript_icon.png)
&.python
background-image: url(/images/common/code_languages/python_icon.png)
&.coffeescript
background-image: url(/images/common/code_languages/coffeescript_icon.png)
&.clojure
background-image: url(/images/common/code_languages/clojure_icon.png)
&.lua
background-image: url(/images/common/code_languages/lua_icon.png)
&.io
background-image: url(/images/common/code_languages/io_icon.png)
&:hover
outline: 1px outset #ccc
&:active
outline: 1px inset #ccc
html.no-borderimage
#spell-palette-view
span.code-palette-background
display: none
img.code-palette-background
display: block

View file

@ -25,20 +25,21 @@
background-color: darken(#FFFFFF, 25%)
// Originally pulled these colors from the most relevant textmate-theme classes, but then fudged them a lot.
&.function
color: black
&.object
color: rgb(6, 150, 14)
&.string
color: rgb(3, 106, 7)
&.number
color: rgb(0, 0, 205)
&.boolean
color: rgb(88, 92, 246)
&.snippet
color: blue
&.undefined
color: rgb(197, 6, 11)
//&.function
// color: black
//&.object
// color: rgb(6, 150, 14)
//&.string
// color: rgb(3, 106, 7)
//&.number
// color: rgb(0, 0, 205)
//&.boolean
// color: rgb(88, 92, 246)
//&.snippet
// color: blue
//&.undefined
// color: rgb(197, 6, 11)
color: rgb(243, 169, 49)
.spell-palette-popover.popover
// Only those popovers which are our direct children (spell documentation)

View file

@ -1,22 +0,0 @@
@import "app/styles/mixins"
@import "app/styles/bootstrap/variables"
#thang-list-view
margin: 50px 15px 15px 15px
overflow: auto
height: 80%
height: -webkit-calc(100% - 65px)
height: calc(100% - 65px)
h3
line-height: 25px
margin: 0
text-align: center
letter-spacing: 0.1em
.thang-list-section
margin: 5px
padding: 5px
background-color: rgba(200, 200, 200, 0.25)
border-radius: 5px
overflow: hidden

View file

@ -1,38 +0,0 @@
@import "app/styles/mixins"
@import "app/styles/bootstrap/variables"
.thang-list-entry-view
@include opacity(0.90)
cursor: pointer
float: left
box-sizing: border-box
width: 20%
max-width: 100px
position: relative
&.dead
@include opacity(0.75)
&:after
content: "×"
font-size: 160px
color: red
@include opacity(0.75)
position: absolute
bottom: 0
height: 100%
width: 100%
text-align: center
line-height: 100px
font-family: monospace
.thang-name
text-decoration: line-through
&.disabled
@include opacity(0.25)
cursor: default
&:hover:not(.disabled)
@include opacity(1)

View file

@ -1,16 +0,0 @@
@import "app/styles/mixins"
@import "app/styles/bootstrap/variables"
.thang-list-entry-spells
.thang-list-entry-spell
padding: 4px
cursor: pointer
&:hover
background-color: hsla(240, 40, 80, 0.25)
code
background-color: transparent
border: 0
font-size: 1.1em

View file

@ -1,55 +0,0 @@
extends /templates/modal/modal_base
block modal-header-content
h3(data-i18n="play_level.editor_config_title") Editor Configuration
block modal-body-content
.form
.form-group.select-group
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")
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")
option(value="default" selected=(keyBindings === "default") data-i18n="play_level.editor_config_keybindings_default") Default (Ace)
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-live-completion")
input#tome-live-completion(name="liveCompletion", type="checkbox", checked=liveCompletion)
span(data-i18n="play_level.editor_config_livecompletion_label") Live Autocompletion
span.help-block(data-i18n="play_level.editor_config_livecompletion_description") Displays autocomplete suggestions while typing.
.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)
span(data-i18n="play_level.editor_config_behaviors_label") Smart Behaviors
span.help-block(data-i18n="play_level.editor_config_behaviors_description") Autocompletes brackets, braces, and quotes.
block modal-footer-content
a(href='#', data-dismiss="modal", aria-hidden="true", data-i18n="modal.close").btn.btn-primary Close

View file

@ -19,7 +19,7 @@ block modal-body-content
dl.dl-horizontal
dt(title=shift + " " + enter)
kbd ⇧ #{enter}
dd(data-i18n="keyboard_shortcuts.cast_spell") Cast current spell.
dd(data-i18n="keyboard_shortcuts.run_code") Run current code.
dl.dl-horizontal
dt(title=ctrlName + " " + shift + " " + enter)
kbd #{ctrl} ⇧ #{enter}

View file

@ -1,6 +1,8 @@
button.btn.btn-lg.btn-inverse.banner.cast-button(title=castVerbose, data-i18n="play_level.tome_run_button_ran") Ran
button.btn.btn-lg.btn-illustrated.cast-button(title=castVerbose)
span(data-i18n="play_level.tome_run_button_ran") Ran
if testSubmitText != null && testSubmitText.length > 0
button.btn.btn-lg.btn-success.banner.submit-button(title=castRealTimeVerbose) #{testSubmitText}
else
button.btn.btn-lg.btn-success.banner.submit-button(title=castRealTimeVerbose, data-i18n="play_level.tome_submit_button") Submit
button.btn.btn-lg.btn-illustrated.submit-button(title=castRealTimeVerbose)
if testSubmitText != null && testSubmitText.length > 0
span= testSubmitText
else
span(data-i18n="play_level.tome_submit_button") Submit

View file

@ -4,25 +4,27 @@
.hinge.hinge-3
if includeSpellList
.btn.btn-small.spell-list-button(data-i18n="[title]play_level.tome_see_all_methods", title="See all methods you can edit")
i.icon-chevron-down
.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
.method-label(data-i18n="play_level.tome_current_method") Current Method
.method-signature #{methodSignature}
.method-name-area
.method-label(data-i18n="play_level.tome_current_method") Current Method
.method-signature #{methodSignature}
.spell-tool-buttons
if levelType !== 'hero'
.btn.btn-small.fullscreen-code(title=maximizeShortcutVerbose)
i.icon-fullscreen
i.icon-resize-small
.btn.btn-small.reload-code(data-i18n="[title]play_level.tome_reload_method", title="Reload original code for this method")
i.icon-repeat
.btn.btn-small.btn-illustrated.reload-code(data-i18n="[title]play_level.tome_reload_method", title="Reload original code for this method")
.glyphicon.glyphicon-repeat
span.spl(data-i18n="play_level.reload") Reload
if codeLanguage === 'javascript'
.btn.btn-small.beautify-code(title=beautifyShortcutVerbose)
i.icon-magnet
if levelType !== 'hero' && levelType !== 'hero-ladder' && levelType !== 'hero-coop'
.btn.btn-small.btn-illustrated.fullscreen-code(title=maximizeShortcutVerbose)
.glyphicon.glyphicon-fullscreen
.glyphicon.glyphicon-resize-small
if codeLanguage === 'javascript' && levelType !== 'hero' && levelType !== 'hero-ladder' && levelType !== 'hero-coop'
.btn.btn-small.btn-illustrated.beautify-code(title=beautifyShortcutVerbose)
.glyphicon.glyphicon-magnet
.clearfix

View file

@ -1,8 +1,7 @@
img(src="/images/level/code_palette_background.png").code-palette-background
img(src="/images/level/code_palette_wood_background.png").code-palette-background
span.code-palette-background
if entryGroupSlugs
// Non-hero; group by entry groups, or maybe nothing.
.code-language-logo
ul(class="nav nav-pills" + (tabbed ? ' multiple-tabs' : ''))
each slug, group in entryGroupSlugs
li(class=group == "this" || slug == "available-spells" ? "active" : "")

View file

@ -1,14 +0,0 @@
#readwrite-thangs.thang-list-section
h3(data-i18n="play_level.tome_minion_spells") Your Minions' Spells
.thang-list
.clearfix
#read-thangs.thang-list-section
h3(data-i18n="play_level.tome_read_only_spells") Read-Only Spells
.thang-list
.clearfix
#muggle-thangs.thang-list-section
h3(data-i18n="play_level.tome_other_units") Other Units
.thang-list
.clearfix

View file

@ -1,5 +0,0 @@
h4.not-code(data-i18n="play_level.tome_select_spell") Select a Spell
.thang-list-entry-spells
for spell in spells
div.thang-list-entry-spell
code(data-spell-name=spell.name) #{spell.name}(#{parameters})

View file

@ -2,8 +2,6 @@
#spell-list-view
#thang-list-view
#cast-button-view
#spell-view

View file

@ -2,9 +2,6 @@ CocoView = require 'views/kinds/CocoView'
template = require 'templates/play/level/playback'
{me} = require 'lib/auth'
EditorConfigModal = require './modal/EditorConfigModal'
KeyboardShortcutsModal = require './modal/KeyboardShortcutsModal'
module.exports = class LevelPlaybackView extends CocoView
id: 'playback-view'
template: template

View file

@ -1,92 +0,0 @@
ModalView = require 'views/kinds/ModalView'
template = require 'templates/play/level/modal/editor_config'
{me} = require 'lib/auth'
module.exports = class EditorConfigModal extends ModalView
id: 'level-editor-config-modal'
template: template
aceConfig: {}
defaultConfig:
language: 'python'
keyBindings: 'default'
invisibles: false
indentGuides: false
behaviors: false
liveCompletion: true
events:
'change #tome-invisibles': 'updateInvisibles'
'change #tome-language': 'updateLanguage'
'change #tome-key-bindings': 'updateKeyBindings'
'change #tome-indent-guides': 'updateIndentGuides'
'change #tome-behaviors': 'updateBehaviors'
'change #tome-live-completion': 'updateLiveCompletion'
constructor: (options) ->
super(options)
@session = options.session
getRenderData: ->
@aceConfig = _.cloneDeep me.get('aceConfig') ? {}
@aceConfig = _.defaults @aceConfig, @defaultConfig
c = super()
c.languages = [
{id: 'python', name: 'Python'}
{id: 'javascript', name: 'JavaScript'}
{id: 'coffeescript', name: 'CoffeeScript'}
{id: 'clojure', name: 'Clojure (Experimental)'}
{id: 'lua', name: 'Lua (Experimental)'}
{id: 'io', name: 'Io (Experimental)'}
]
c.sessionLanguage = @session.get('codeLanguage') ? @aceConfig.language
c.language = @aceConfig.language
c.keyBindings = @aceConfig.keyBindings
c.invisibles = @aceConfig.invisibles
c.indentGuides = @aceConfig.indentGuides
c.behaviors = @aceConfig.behaviors
c.liveCompletion = @aceConfig.liveCompletion
c
updateSessionLanguage: ->
@session.set 'codeLanguage', @$el.find('#tome-session-language').val()
updateLanguage: ->
@aceConfig.language = @$el.find('#tome-language').val()
updateInvisibles: ->
@aceConfig.invisibles = @$el.find('#tome-invisibles').prop('checked')
updateKeyBindings: ->
@aceConfig.keyBindings = @$el.find('#tome-key-bindings').val()
updateIndentGuides: ->
@aceConfig.indentGuides = @$el.find('#tome-indent-guides').prop('checked')
updateBehaviors: ->
@aceConfig.behaviors = @$el.find('#tome-behaviors').prop('checked')
updateLiveCompletion: ->
@aceConfig.liveCompletion = @$el.find('#tome-live-completion').prop('checked')
afterRender: ->
super()
onHidden: ->
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()
@aceConfig.indentGuides = @$el.find('#tome-indent-guides').prop('checked')
@aceConfig.behaviors = @$el.find('#tome-behaviors').prop('checked')
@aceConfig.liveCompletion = @$el.find('#tome-live-completion').prop('checked')
me.set 'aceConfig', @aceConfig
Backbone.Mediator.publish 'tome:change-config', {}
Backbone.Mediator.publish 'tome:change-language', language: newLanguage unless newLanguage is oldLanguage
@session.save() unless newLanguage is oldLanguage
me.patch()
destroy: ->
super()

View file

@ -33,7 +33,7 @@ module.exports = class CastButtonView extends CocoView
enter = $.i18n.t 'keyboard_shortcuts.enter'
castShortcutVerbose = "#{shift}+#{enter}"
castRealTimeShortcutVerbose = (if @isMac() then 'Cmd' else 'Ctrl') + '+' + castShortcutVerbose
context.castVerbose = castShortcutVerbose + ': ' + $.i18n.t('keyboard_shortcuts.cast_spell')
context.castVerbose = castShortcutVerbose + ': ' + $.i18n.t('keyboard_shortcuts.run_code')
context.castRealTimeVerbose = castRealTimeShortcutVerbose + ': ' + $.i18n.t('keyboard_shortcuts.run_real_time')
# A/B test submit button text
context.testSubmitText = @testButtonsText.submit if @testGroup? and @testGroup isnt 0
@ -105,7 +105,7 @@ module.exports = class CastButtonView extends CocoView
, (castable) =>
Backbone.Mediator.publish 'tome:spell-has-changed-significantly-calculation', hasChangedSignificantly: castable
@castButton.toggleClass('castable', castable).toggleClass('casting', @casting)
# A/B testing cast button text for en-US
if $.i18n.lng() isnt 'en-US' or not @testGroup? or @testGroup is 0
if @casting
@ -160,5 +160,3 @@ module.exports = class CastButtonView extends CocoView
Action: 'Loaded'
levelID: @levelID
castButtonText: @testButtonsText.run + ' ' + @testButtonsText.submit

View file

@ -37,6 +37,7 @@ module.exports = class SpellListEntryView extends CocoView
context
createMethodSignature: ->
return @spell.name if @options.level.get('type', true) in ['hero', 'hero-ladder', 'hero-coop']
parameters = (@spell.parameters or []).slice()
if @spell.language in ['python', 'lua']
parameters.unshift 'self'

View file

@ -61,7 +61,7 @@ module.exports = class SpellListView extends CocoView
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, includeSpellList: @spells.length > 1
newEntries.push entry = new SpellListEntryView spell: spell, showTopDivider: changedThangs, supermodel: @supermodel, includeSpellList: @spells.length > 1, level: @options.level
@entries.push entry
for entry in newEntries
@$el.append entry.el

View file

@ -5,7 +5,6 @@ filters = require 'lib/image_filter'
SpellPaletteEntryView = require './SpellPaletteEntryView'
LevelComponent = require 'models/LevelComponent'
ThangType = require 'models/ThangType'
EditorConfigModal = require '../modal/EditorConfigModal'
N_ROWS = 4
@ -20,9 +19,6 @@ module.exports = class SpellPaletteView extends CocoView
'surface:frame-changed': 'onFrameChanged'
'tome:change-language': 'onTomeChangedLanguage'
events:
'click .code-language-logo': 'onEditEditorConfig'
constructor: (options) ->
super options
@thang = options.thang
@ -54,7 +50,7 @@ module.exports = class SpellPaletteView extends CocoView
@entryGroupElements = {}
for group, entries of @entryGroups
@entryGroupElements[group] = itemGroup = $('<div class="property-entry-item-group"></div>').appendTo @$el.find('.properties')
itemGroup.append $('<img class="item-image"></img>').attr('src', entries[0].options.item.getPortraitURL()).css('top', Math.max(0, 19 * (entries.length - 2) / 2)) if entries[0].options.item?.getPortraitURL
itemGroup.append $('<img class="item-image"></img>').attr('src', entries[0].options.item.getPortraitURL()).css('top', Math.max(0, 19 * (entries.length - 2) / 2) + 2) if entries[0].options.item?.getPortraitURL
for entry in entries
itemGroup.append entry.el
entry.render() # Render after appending so that we can access parent container for popover
@ -67,7 +63,6 @@ module.exports = class SpellPaletteView extends CocoView
updateCodeLanguage: (language) ->
@options.language = language
@$el.find('.code-language-logo').removeClass().addClass 'code-language-logo ' + language
updateMaxHeight: ->
return unless @isHero
@ -266,9 +261,6 @@ module.exports = class SpellPaletteView extends CocoView
@createPalette()
@render()
onEditEditorConfig: (e) ->
@openModalView new EditorConfigModal session: @options.session
destroy: ->
entry.destroy() for entry in @entries
@toggleBackground = null

View file

@ -360,10 +360,10 @@ module.exports = class SpellView extends CocoView
spellPaletteHeight = $('#spell-palette-view').outerHeight()
maxHeight = tomeHeight - spellListTabEntryHeight - spellToolbarHeight - spellPaletteHeight
linesAtMaxHeight = Math.floor(maxHeight / lineHeight)
lines = Math.max 8, Math.min(screenLineCount + 4, linesAtMaxHeight)
# 2 lines buffer is nice, but 4 leaves room to put problem alerts.
lines = Math.max 8, Math.min(screenLineCount + 2, linesAtMaxHeight)
# 2 lines buffer is nice
@ace.setOptions minLines: lines, maxLines: lines
$('#spell-palette-view').css('top', 38 + 45 + lineHeight * lines) # Move spell palette up, slightly underlapping us.
$('#spell-palette-view').css('top', 175 + lineHeight * lines) # Move spell palette up, slightly overlapping us.
onManualCast: (e) ->
cast = @$el.parent().length

View file

@ -1,178 +0,0 @@
# TODO: be useful to add error indicator states to the spellsPopoverTemplate
# TODO: reordering based on errors isn't working yet
CocoView = require 'views/kinds/CocoView'
ThangAvatarView = require 'views/play/level/ThangAvatarView'
template = require 'templates/play/level/tome/thang_list_entry'
spellsPopoverTemplate = require 'templates/play/level/tome/thang_list_entry_spells'
{me} = require 'lib/auth'
module.exports = class ThangListEntryView extends CocoView
tagName: 'div' #'li'
className: 'thang-list-entry-view'
template: template
controlsEnabled: true
reasonsToBeDisabled: {}
subscriptions:
'tome:problems-updated': 'onProblemsUpdated'
'level:disable-controls': 'onDisableControls'
'level:enable-controls': 'onEnableControls'
'surface:frame-changed': 'onFrameChanged'
'level:set-letterbox': 'onSetLetterbox'
'tome:thang-list-entry-popover-shown': 'onThangListEntryPopoverShown'
'surface:coordinates-shown': 'onSurfaceCoordinatesShown'
events:
'click': 'onClick'
'mouseenter': 'onMouseEnter'
'mouseleave': 'onMouseLeave'
constructor: (options) ->
super options
@thang = options.thang
@spells = options.spells
@permission = options.permission
@reasonsToBeDisabled = {}
@sortSpells()
getRenderData: (context={}) ->
context = super context
context.thang = @thang
context.spell = @spells
context
afterRender: ->
super()
@avatar?.destroy()
@avatar = new ThangAvatarView thang: @thang, includeName: true, supermodel: @supermodel
@$el.append @avatar.el # Before rendering, so render can use parent for popover
@avatar.render()
@avatar.setSharedThangs @spells.length # A bit weird to call it sharedThangs; could refactor if we like this
@$el.toggle Boolean(@thang.exists)
@$el.popover(
animation: false
html: true
placement: 'bottom'
trigger: 'manual'
content: @getSpellListHTML()
container: @$el.parent().parent().parent()
)
sortSpells: ->
return if @sorted
# Keep only spells for which we have permissions
spells = _.filter @spells, (s) => @options.permission and me.team in s.permissions[@options.permission]
@spells = _.sortBy spells, @sortScoreForSpell
@sorted = true
sortScoreForSpell: (s) =>
# Sort by errored-out spells first, then spells shared with fewest other Thangs
# Lower comes first
score = 0
# My errors are highest priority
score -= 9001900190019001 * (s.thangs[@thang.id].aether?.getAllProblems().length or 0)
# Other shared Thangs errors are also high priority
score -= _.reduce s.thangs, (spellThang, num) -> 900190019001 * (spellThang.aether?.getAllProblems().length or 0)
# Read-only spells at the bottom
score += 90019001 unless s.canWrite()
# The more Thangs sharing a spell, the lower
score += 9001 * _.size(s.thangs)
score
select: ->
@sortSpells()
Backbone.Mediator.publish 'level:select-sprite', thangID: @thang.id, spellName: @spells[0]?.name
onClick: (e) ->
return unless @controlsEnabled
@select()
onMouseEnter: (e) ->
return unless @controlsEnabled and @spells.length
@clearTimeouts()
@showSpellsTimeout = _.delay @showSpells, 100
onMouseLeave: (e) ->
return unless @controlsEnabled and @spells.length
@clearTimeouts()
@hideSpellsTimeout = _.delay @hideSpells, 100
clearTimeouts: ->
clearTimeout @showSpellsTimeout if @showSpellsTimeout
clearTimeout @hideSpellsTimeout if @hideSpellsTimeout
@showSpellsTimeout = @hideSpellsTimeout = null
onThangListEntryPopoverShown: (e) ->
# I couldn't figure out how to get the mouseenter / mouseleave to always work, so this is a fallback
# to hide our popover if another Thang's popover gets shown.
return if e.entry is @
@hideSpells()
onSurfaceCoordinatesShown: (e) ->
# Definitely aren't hovering over this.
@hideSpells()
showSpells: =>
@clearTimeouts()
@sortSpells()
@$el.data('bs.popover').options.content = @getSpellListHTML()
@$el.popover('setContent').popover('show')
@$el.parent().parent().parent().i18n()
@popover = @$el.parent().parent().parent().find('.popover')
@popover.off 'mouseenter mouseleave'
@popover.mouseenter (e) => @showSpells() if @controlsEnabled
@popover.mouseleave (e) => @hideSpells()
thangID = @thang.id
@popover.find('code').click (e) ->
Backbone.Mediator.publish 'level:select-sprite', thangID: thangID, spellName: $(@).data 'spell-name'
Backbone.Mediator.publish 'tome:thang-list-entry-popover-shown', entry: @
hideSpells: =>
@clearTimeouts()
@$el.popover('hide')
getSpellListHTML: ->
spellsPopoverTemplate {spells: @spells}
onProblemsUpdated: (e) ->
return unless e.spell in @spells
@sorted = false
onSetLetterbox: (e) ->
if e.on then @reasonsToBeDisabled.letterbox = true else delete @reasonsToBeDisabled.letterbox
@updateControls()
onDisableControls: (e) ->
return if e.controls and not ('surface' in e.controls) # disable selection?
@reasonsToBeDisabled.controls = true
@updateControls()
onEnableControls: (e) ->
delete @reasonsToBeDisabled.controls
@updateControls()
updateControls: ->
enabled = _.keys(@reasonsToBeDisabled).length is 0
return if enabled is @controlsEnabled
@controlsEnabled = enabled
@$el.toggleClass('disabled', not enabled)
onFrameChanged: (e) ->
# Optimize
return unless currentThang = e.world.thangMap[@thang.id]
exists = Boolean currentThang.exists
if @thangDidExist isnt exists
@$el.toggle exists
@thangDidExist = exists
dead = exists and currentThang.health <= 0
if @thangWasDead isnt dead
@$el.toggleClass 'dead', dead
@thangWasDead = dead
destroy: ->
@avatar?.destroy()
@popover?.remove()
@popover?.off 'mouseenter mouseleave'
@popover?.find('code').off 'click'
super()

View file

@ -1,95 +0,0 @@
# The ThangListView lives in the code area behind the SpellView, so that when you don't have a spell, you can select any Thang.
# It just ha a bunch of ThangListEntryViews (which are mostly ThangAvatarViews) in a few sections.
CocoView = require 'views/kinds/CocoView'
template = require 'templates/play/level/tome/thang_list'
{me} = require 'lib/auth'
ThangListEntryView = require './ThangListEntryView'
module.exports = class ThangListView extends CocoView
className: 'thang-list-view'
id: 'thang-list-view'
template: template
constructor: (options) ->
super options
@spells = options.spells
@thangs = _.filter options.thangs, 'isSelectable'
@sortThangs()
sortThangs: ->
@readwriteThangs = _.sortBy _.filter(@thangs, (thang) =>
return true for spellKey, spell of @spells when thang.id of spell.thangs and spell.canWrite()
false
), @sortScoreForThang
@readThangs = _.sortBy _.filter(@thangs, (thang) =>
return true for spellKey, spell of @spells when thang.id of spell.thangs and spell.canRead() and not spell.canWrite()
false
), @sortScoreForThang
@muggleThangs = _.sortBy _.without(@thangs, @readwriteThangs..., @readThangs...), @sortScoreForThang
if @muggleThangs.length > 15
@muggleThangs = [] # Don't render a zillion of these. Slow, too long, maybe not useful.
sortScoreForThang: (t) =>
# Sort by my team, then most spells and fewest shared Thangs per spell,
# then by thang.spriteName alpha, then by thang.id alpha.
# Lower comes first
score = 0
# Thangs on my team are highest priority
score -= 9001900190019001 if t.team is me.team
# The more spells per Thang, the lower
score -= 900190019001 for spellKey, spell of @spells when t.id of spell.thangs and spell.canRead()
# The more Thangs per spell, the higher
score += 90019001 for t2 of spell.thangs for spellKey, spell of @spells when t.id of spell.thangs
alpha = (s) -> _.reduce [0 ... s.length], ((acc, i) -> acc + s.charCodeAt(i) / Math.pow(100, i)), 0
# Alpha by spriteName
score += 9001 * alpha t.spriteName
# Alpha by id
score += alpha t.id
score
afterRender: ->
super()
@addThangListEntries()
addThangListEntries: ->
@entries = []
for [thangs, section, permission] in [
[@readwriteThangs, '#readwrite-thangs', 'readwrite'] # Your Minions
[@readThangs, '#read-thangs', 'read'] # Read-Only
[@muggleThangs, '#muggle-thangs', null] # Non-Castable
]
section = @$el.find(section).toggle thangs.length > 0
for thang in thangs
spells = _.filter @spells, (s) -> thang.id of s.thangs
entry = new ThangListEntryView thang: thang, spells: spells, permission: permission, supermodel: @supermodel
section.find('.thang-list').append entry.el # Render after appending so that we can access parent container for popover
entry.render()
@entries.push entry
topSpellForThang: (thang) ->
for entry in @entries when entry.thang.id is thang.id
return entry.spells[0]
null
selectPrimarySprite: ->
@entries[0]?.select()
adjustThangs: (spells, thangs) ->
# TODO: it would be nice to not have to do this any more, like if we migrate to the hero levels.
# Recreating all the ThangListEntryViews and their ThangAvatarViews is pretty slow.
# So they aren't even kept up-to-date during world streaming.
# Updating the existing subviews? Would be kind of complicated to get all the new thangs and spells propagated.
# I would do it, if I didn't think we were perhaps soon to not do the ThangList any more.
# Will temporary reduce the number of muggle thangs we're willing to draw.
@spells = @options.spells = spells
for entry in @entries
entry.$el.remove()
entry.destroy()
@thangs = @options.thangs = thangs
@sortThangs()
@addThangListEntries()
destroy: ->
entry.destroy() for entry in @entries
super()

View file

@ -32,7 +32,6 @@ template = require 'templates/play/level/tome/tome'
{me} = require 'lib/auth'
Spell = require './Spell'
SpellListView = require './SpellListView'
ThangListView = require './ThangListView'
SpellPaletteView = require './SpellPaletteView'
CastButtonView = require './CastButtonView'
@ -62,8 +61,7 @@ module.exports = class TomeView extends CocoView
#programmableThangs = _.filter @options.thangs, (t) -> t.isProgrammable and t.spriteName isnt 'Hero Placeholder'
programmableThangs = _.filter @options.thangs, 'isProgrammable'
@createSpells programmableThangs, programmableThangs[0]?.world # Do before spellList, thangList, and castButton
@spellList = @insertSubView new SpellListView spells: @spells, supermodel: @supermodel
@thangList = @insertSubView new ThangListView spells: @spells, thangs: @options.thangs, supermodel: @supermodel unless @options.level.get('type', true) is 'hero'
@spellList = @insertSubView new SpellListView spells: @spells, supermodel: @supermodel, level: @options.level
@castButton = @insertSubView new CastButtonView spells: @spells, levelID: @options.levelID
@teamSpellMap = @generateTeamSpellMap(@spells)
unless programmableThangs.length
@ -77,7 +75,6 @@ module.exports = class TomeView extends CocoView
thangs = _.filter e.world.thangs, 'inThangList'
programmableThangs = _.filter thangs, 'isProgrammable'
@createSpells programmableThangs, e.world
@thangList?.adjustThangs @spells, thangs
@spellList.adjustSpells @spells
onCommentMyCode: (e) ->
@ -187,7 +184,6 @@ module.exports = class TomeView extends CocoView
@spellPaletteView = null
@$el.find('#spell-palette-view').hide()
@castButton?.$el.hide()
@thangList?.$el.show()
onSpriteSelected: (e) ->
return if @spellView and @options.level.get('type', true) in ['hero', 'hero-ladder', 'hero-coop'] # Never deselect the hero in the Tome.
@ -207,7 +203,6 @@ module.exports = class TomeView extends CocoView
@$el.find('#' + @spellView.id).after(@spellView.el).remove()
@$el.find('#' + @spellTabView.id).after(@spellTabView.el).remove()
@castButton.attachTo @spellView
@thangList?.$el.hide()
Backbone.Mediator.publish 'tome:spell-shown', thang: thang, spell: spell
@updateSpellPalette thang, spell
@spellList.setThangAndSpell thang, spell
@ -217,7 +212,7 @@ module.exports = class TomeView extends CocoView
updateSpellPalette: (thang, spell) ->
return unless thang and @spellPaletteView?.thang isnt thang and thang.programmableProperties or thang.apiProperties
@spellPaletteView = @insertSubView new SpellPaletteView thang: thang, supermodel: @supermodel, programmable: spell?.canRead(), language: spell?.language ? @options.session.get('codeLanguage'), session: @options.session, level: @options.level
@spellPaletteView.toggleControls {}, spell.view.controlsEnabled if spell # TODO: know when palette should have been disabled but didn't exist
@spellPaletteView.toggleControls {}, spell.view.controlsEnabled if spell?.view # TODO: know when palette should have been disabled but didn't exist
spellFor: (thang, spellName) ->
return null unless thang?.isProgrammable
@ -225,9 +220,6 @@ module.exports = class TomeView extends CocoView
selectedThangSpells = (@spells[spellKey] for spellKey in @thangSpells[thang.id])
if spellName
spell = _.find selectedThangSpells, {name: spellName}
else if @thangList
spell = @thangList.topSpellForThang thang
#spell = selectedThangSpells[0] # TODO: remember last selected spell for this thang
else
spell = _.find selectedThangSpells, (spell) -> true # Just grab one
spell
@ -241,10 +233,8 @@ module.exports = class TomeView extends CocoView
@cast()
onSelectPrimarySprite: (e) ->
if @thangList
@thangList.selectPrimarySprite()
else
Backbone.Mediator.publish 'level:select-sprite', thangID: 'Hero Placeholder'
# TODO: this may not be correct
Backbone.Mediator.publish 'level:select-sprite', thangID: 'Hero Placeholder'
destroy: ->
spell.destroy() for spellKey, spell of @spells