Many improvements to the spell palette.

This commit is contained in:
Nick Winter 2014-02-16 15:30:00 -08:00
parent 1f7cea4e54
commit e3824d7698
13 changed files with 159 additions and 46 deletions

View file

@ -2,7 +2,7 @@
class Vector
@className: "Vector"
# Class methods for nondestructively operating
for name in ['add', 'subtract', 'multiply', 'divide']
for name in ['add', 'subtract', 'multiply', 'divide', 'limit', 'normalize']
do (name) ->
Vector[name] = (a, b, useZ) ->
a.copy()[name](b, useZ)

View file

@ -89,6 +89,7 @@
margin: 8px 8px 0 0
overflow-y: scroll
float: left
@include user-select(text)
.prop
img

View file

@ -6,12 +6,10 @@
left: 10px
right: 10px
height: 140px
padding-top: 50px
// Height and padding-top relate to .tab-content height
padding-left: 35px
padding-right: 60px
// Docs' popovers flicker when too far right, so we have big padding-right
// twilight: color: #E2E2E2
// 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
background-color: transparent
@ -36,3 +34,23 @@
line-height: 16px
margin: 0 4px
font-weight: normal
.nav > li > a
padding: 2px 20px 0px 20px
margin-bottom: 3px
ul.nav.nav-pills
li.active a
background-color: transparent
&.multiple-tabs li.active a
background-color: lighten(rgb(230, 212, 146), 10%)
&.multiple-tabs li:not(.active) a
cursor: pointer
//.nav-pills > li.active > a, .nav-pills > li.active > a:hover, .nav-pills > li.active > a:focus
// background-color: lighten(rgb(230, 212, 146), 10%)
.property-entry-column
display: inline-block
margin-right: 3px
vertical-align: top

View file

@ -1,28 +1,32 @@
@import "../../../bootstrap/mixins"
@import "app/styles/bootstrap/mixins"
@import "app/styles/mixins"
.spell-palette-entry-view
display: inline-block
margin: 0px 4px
display: block
padding: 0px 4px
font-family: Menlo, Monaco, Consolas, "Courier New", monospace
font-size: 12px
border: 1px solid transparent
cursor: pointer
@include user-select(all)
&:hover
border: 1px solid #BFF
&.pinned
background-color: darken(#BFF, 20%)
// Pulling these colors from the most relevant textmate-theme classes
&.function
// twilight: color: #7587A6
color: #0000A2
&.object
// twilight: color: #AC885B
color: rgb(6, 150, 14)
&.string
// twilight: color: #CDA869
color: rgb(3, 106, 7)
&.number
// twilight: color: #F9EE98
color: rgb(0, 0, 205)
&.boolean
// twilight: color: #C9CEA8
color: rgb(88, 92, 246)
&.undefined
// twilight: color: #CF6A4C
color: rgb(197, 6, 11)

View file

@ -1,15 +1,24 @@
@import "../../../bootstrap/mixins"
@import "app/styles/bootstrap/mixins"
@import "app/styles/mixins"
#tome-view
height: 100%
.popover
padding: 10px
max-width: 400px
min-width: 400px
background: transparent url(/images/level/popover_background.png)
background-size: 100% 100%
border: 0
left: auto !important
top: auto !important
right: 100%
bottom: 151px
@include box-shadow(0 0 0 #000)
@include user-select(text)
&.pinned
@include box-shadow(0 0 500px white)
h1:not(.not-code), h2:not(.not-code), h3:not(.not-code), h4:not(.not-code), h5:not(.not-code), h6:not(.not-code)
font-family: Menlo, Monaco, Consolas, "Courier New", monospace

View file

@ -1,3 +1,10 @@
img(src="/images/level/code_palette_background.png").code-palette-background
h4(data-i18n="play_level.tome_available_spells") Available Spells
.properties
ul(class="nav nav-pills" + (tabbed ? ' multiple-tabs' : ''))
each slug, group in entryGroupSlugs
li(class=group == "this" || slug == "available-spells" ? "active" : "")
a(data-toggle="pill", data-target='#palette-tab-' + slug)
h4= group
.tab-content
each slug, group in entryGroupSlugs
div(id="palette-tab-" + slug, class="tab-pane" + (group == "this" || slug == "available-spells" ? " active" : ""))
div(class="properties properties-" + slug)

View file

@ -16,7 +16,7 @@ module.exports = class LevelSaveView extends SaveVersionModal
constructor: (options) ->
super options
@level = options.level
getRenderData: (context={}) ->
context = super(context)
context.level = @level
@ -63,8 +63,8 @@ module.exports = class LevelSaveView extends SaveVersionModal
console.log "Got errors:", JSON.parse(res.responseText)
forms.applyErrorsToForm($(form), JSON.parse(res.responseText))
res.success =>
@hide()
modelsToSave = _.without modelsToSave, newModel
unless modelsToSave.length
url = "/editor/level/#{@level.get('slug') or @level.id}"
document.location.href = url
@hide() # This will destroy everything, so do it last

View file

@ -24,12 +24,15 @@ module.exports = class HUDView extends View
'god:new-world-created': 'onNewWorld'
events:
'click': -> Backbone.Mediator.publish 'focus-editor'
'click': 'onClick'
afterRender: ->
super()
@$el.addClass 'no-selection'
onClick: (e) ->
Backbone.Mediator.publish 'focus-editor' unless $(e.target).parents('.thang-props').length
onFrameChanged: (e) ->
@timeProgress = e.progress
@update()

View file

@ -64,7 +64,7 @@ module.exports = class SpellListTabEntryView extends SpellListEntryView
@$el.find('code').popover(
animation: true
html: true
placement: 'bottom'
placement: 'left'
trigger: 'hover'
content: @formatPopover doc
container: @$el.parent()
@ -126,4 +126,5 @@ module.exports = class SpellListTabEntryView extends SpellListEntryView
destroy: ->
@avatar?.destroy()
@$el.find('code').popover 'destroy'
super()

View file

@ -4,6 +4,7 @@ popoverTemplate = require 'templates/play/level/tome/spell_palette_entry_popover
{me} = require 'lib/auth'
filters = require 'lib/image_filter'
{downTheChain} = require 'lib/world/world_utils'
window.Vector = require 'lib/world/vector' # So we can document it
# If we use marked somewhere else, we'll have to make sure to preserve options
marked.setOptions {gfm: true, sanitize: false, smartLists: true, breaks: true}
@ -41,16 +42,32 @@ safeJSONStringify = (input, maxDepth) ->
output = input
JSON.stringify output, null, 1
# http://stackoverflow.com/a/987376/540620
$.fn.selectText = ->
el = @[0]
if document.body.createTextRange
range = document.body.createTextRange()
range.moveToElementText(el)
range.select()
else if window.getSelection
selection = window.getSelection()
range = document.createRange()
range.selectNodeContents(el)
selection.removeAllRanges()
selection.addRange(range)
module.exports = class SpellPaletteEntryView extends View
tagName: 'div' # Could also try <code> instead of <div>, but would need to adjust colors
className: 'spell-palette-entry-view'
template: template
popoverPinned: false
subscriptions:
'surface:frame-changed': "onFrameChanged"
events:
'mouseover': 'onMouseOver'
'mouseenter': 'onMouseEnter'
'mouseleave': 'onMouseLeave'
'click': 'onClick'
constructor: (options) ->
@ -59,14 +76,14 @@ module.exports = class SpellPaletteEntryView extends View
@doc = options.doc
if _.isString @doc
@doc = name: @doc, type: typeof @thang[@doc]
@doc.owner ?= 'this'
if options.isSnippet
@doc.type = 'snippet'
@doc.type = @doc.owner = 'snippet'
@doc.shortName = @doc.shorterName = @doc.title = @doc.name
else
@doc.owner ?= 'this'
suffix = if @doc.type is 'function' then '()' else ''
@doc.shortName = "#{@doc.owner}.#{@doc.name}#{suffix};"
if @doc.owner is 'this'
if @doc.owner is 'this' or options.tabbify
@doc.shorterName = "#{@doc.name}#{suffix}"
else
@doc.shorterName = @doc.shortName.replace ';', ''
@ -81,12 +98,12 @@ module.exports = class SpellPaletteEntryView extends View
super()
@$el.addClass(@doc.type)
@$el.popover(
animation: true
animation: false
html: true
placement: 'top'
trigger: 'hover'
placement: 'left'
trigger: 'manual' # Hover, until they click, which will then pin it until unclick.
content: @formatPopover()
container: @$el.parent().parent().parent()
container: '#tome-view'
)
@$el.on 'show.bs.popover', =>
Backbone.Mediator.publish 'tome:palette-hovered', thang: @thang, prop: @doc.name
@ -98,13 +115,14 @@ module.exports = class SpellPaletteEntryView extends View
content.replace /\#\{(.*?)\}/g, (s, properties) => @formatValue downTheChain(owner, properties.split('.'))
formatValue: (v) ->
return null if @doc.type is 'snippet'
return @thang.now() if @doc.name is 'now'
return '[Function]' if not v and @doc.type is 'function'
unless v?
if @doc.owner is 'this'
v = @thang[@doc.name]
else
v = window[@doc.owner][@doc.name]
v = window[@doc.owner][@doc.name] # grab Math or Vector
if @doc.type is 'number' and not isNaN v
if v == Math.round v
return v
@ -121,12 +139,29 @@ module.exports = class SpellPaletteEntryView extends View
return safeJSONStringify v, 2
v
onMouseOver: (e) ->
onMouseEnter: (e) ->
# Make sure the doc has the updated Thang so it can regenerate its prop value
@$el.data('bs.popover').options.content = @formatPopover()
@$el.popover('setContent')
@$el.popover 'show' unless @popoverPinned
onMouseLeave: (e) ->
@$el.popover 'hide' unless @popoverPinned
togglePinned: ->
if @popoverPinned
@popoverPinned = false
@$el.add('#tome-view .popover').removeClass 'pinned'
@$el.popover 'hide'
else
@popoverPinned = true
@$el.add('#tome-view .popover').addClass 'pinned'
onClick: (e) ->
unless @popoverPinned
$(e.target).selectText()
e.stopPropagation() # don't re-focus editor since we might want to select text
@togglePinned()
Backbone.Mediator.publish 'tome:palette-clicked', thang: @thang, prop: @doc.name
onFrameChanged: (e) ->
@ -134,5 +169,8 @@ module.exports = class SpellPaletteEntryView extends View
@options.thang = @thang = e.selectedThang # Update our thang to the current version
destroy: ->
$('.popover.pinned').remove() if @popoverPinned # @$el.popover('destroy') doesn't work
@togglePinned() if @popoverPinned
@$el.popover 'destroy'
@$el.off()
super()

View file

@ -5,6 +5,8 @@ filters = require 'lib/image_filter'
SpellPaletteEntryView = require './spell_palette_entry_view'
LevelComponent = require 'models/LevelComponent'
N_ROWS = 4
module.exports = class SpellPaletteView extends View
id: 'spell-palette-view'
template: template
@ -18,28 +20,56 @@ module.exports = class SpellPaletteView extends View
constructor: (options) ->
super options
@thang = options.thang
@createPalette()
getRenderData: ->
c = super()
c.entryGroups = @entryGroups
c.entryGroupSlugs = @entryGroupSlugs
c.tabbed = _.size(@entryGroups) > 1
c
afterRender: ->
super()
@createPalette()
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
createPalette: ->
lcs = @supermodel.getModels LevelComponent
allDocs = {}
allDocs[doc.name] = doc for doc in (lc.get('propertyDocumentation') ? []) for lc in lcs
props = @thang.programmableProperties ? []
snippets = @thang.programmableSnippets ? []
props = _.sortBy @thang.programmableProperties ? []
snippets = _.sortBy @thang.programmableSnippets ? []
shortenize = props.length + snippets.length > 6
tabbify = props.length + snippets.length >= 10
@entries = []
@entries.push @addEntry(allDocs[prop] ? prop, shortenize) for prop in props
@entries.push @addEntry(allDocs[prop] ? prop, shortenize, true) for prop in snippets
@entries.push @addEntry(allDocs[prop] ? prop, shortenize, tabbify) for prop in props
@entries.push @addEntry(allDocs[prop] ? prop, shortenize, tabbify, true) for prop in snippets
@entries = _.sortBy @entries, (entry) ->
order = ['this', 'Math', 'Vector', 'snippets']
index = order.indexOf entry.doc.owner
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')
@entryGroups = _.groupBy @entries, (entry) -> entry.doc.owner
else
defaultGroup = $.i18n.t("play_level.tome_available_spells", defaultValue: "Available Spells")
@entryGroups = {}
@entryGroups[defaultGroup] = @entries
@entryGroupSlugs = {}
for group, entries of @entryGroups
@entryGroupSlugs[group] = _.string.slugify group
@entryGroups[group] = _.groupBy entries, (entry, i) -> Math.floor i / N_ROWS
null
addEntry: (doc, shortenize, isSnippet=false) ->
entry = new SpellPaletteEntryView doc: doc, thang: @thang, shortenize: shortenize, isSnippet: isSnippet
@$el.find('.properties').append entry.el
entry.render() # Render after appending so that we can access parent container for popover
entry
addEntry: (doc, shortenize, tabbify, isSnippet=false) ->
new SpellPaletteEntryView doc: doc, thang: @thang, shortenize: shortenize, tabbify: tabbify, isSnippet: isSnippet
onDisableControls: (e) -> @toggleControls e, false
onEnableControls: (e) -> @toggleControls e, true

View file

@ -51,7 +51,7 @@ module.exports = class TomeView extends View
events:
'click #spell-view': 'onSpellViewClick'
'click': -> Backbone.Mediator.publish 'focus-editor'
'click': 'onClick'
afterRender: ->
super()
@ -140,6 +140,9 @@ module.exports = class TomeView extends View
onSpellViewClick: (e) ->
@spellList.$el.hide()
onClick: (e) ->
Backbone.Mediator.publish 'focus-editor' unless $(e.target).parents('.popover').length
clearSpellView: ->
@spellView?.dismiss()
@spellView?.$el.after('<div id="' + @spellView.id + '"></div>').detach()

View file

@ -387,7 +387,6 @@ module.exports = class PlayLevelView extends View
team = team?.team unless _.isString team
team ?= 'humans'
me.team = team
console.log "level:team-set to", team
Backbone.Mediator.publish 'level:team-set', team: team
destroy: ->