Merge branch 'master' into production

This commit is contained in:
Nick Winter 2014-12-20 20:02:03 -08:00
commit 69118206ab
26 changed files with 149 additions and 37 deletions

View file

@ -58,7 +58,7 @@ module.exports = GPlusHandler = class GPlusHandler extends CocoClass
console.error 'Unable to save G+ token key', e
@accessToken = e
@trigger 'logged-in'
loginCodeCombat: ->
# email and profile data loaded separately
gapi.client.request(path: plusURL, callback: @onPersonEntityReceived)
@ -71,7 +71,8 @@ module.exports = GPlusHandler = class GPlusHandler extends CocoClass
for gpProp, userProp of userPropsToSave
keys = gpProp.split('.')
value = r
value = value[key] for key in keys
for key in keys
value = value[key]
if value and not me.get(userProp)
@shouldSave = true
me.set(userProp, value)

View file

@ -459,6 +459,44 @@ class SlugPropsObject extends TreemaNode.nodeMap.object
return res if @workingSchema.properties?[res]?
_.string.slugify(res)
class TaskTreema extends TreemaNode.nodeMap.string
buildValueForDisplay: (valEl) ->
@taskCheckbox = $('<input type="checkbox">').prop 'checked', @data.complete
task = $("<span>#{@data.name}</span>")
valEl.append(@taskCheckbox).append(task)
@taskCheckbox.on 'change', @onTaskChanged
buildValueForEditing: (valEl, data) ->
@nameInput = @buildValueForEditingSimply(valEl, data.name)
@nameInput.parent().prepend(@taskCheckbox)
onTaskChanged: (e) =>
@markAsChanged()
@saveChanges()
@flushChanges()
@broadcastChanges()
onEditInputBlur: (e) =>
@markAsChanged()
@saveChanges()
if @isValid() then @display() if @isEditing() else @nameInput.focus().select()
@flushChanges()
@broadcastChanges()
saveChanges: (oldData) ->
@data ?= {}
@data.name = @nameInput.val() if @nameInput
@data.complete = Boolean(@taskCheckbox.prop 'checked')
destroy: ->
@taskCheckbox.off()
super()
#class CheckboxTreema extends TreemaNode.nodeMap.boolean
# TODO: try this out
module.exports.setup = ->
TreemaNode.setNodeSubclass('date-time', DateTimeTreema)
TreemaNode.setNodeSubclass('version', VersionTreema)
@ -475,3 +513,5 @@ module.exports.setup = ->
TreemaNode.setNodeSubclass('i18n', InternationalizationNode)
TreemaNode.setNodeSubclass('sound-file', SoundFileTreema)
TreemaNode.setNodeSubclass 'slug-props', SlugPropsObject
TreemaNode.setNodeSubclass 'task', TaskTreema
#TreemaNode.setNodeSubclass 'checkbox', CheckboxTreema

View file

@ -62,9 +62,9 @@ module.exports = class SpriteParser
break
continue unless container.bounds and instructions.length
@addContainer {c: instructions, b: container.bounds}, container.name
childrenMovieClips = []
for movieClip, index in movieClips
lastBounds = null
# fill in bounds which are null...
@ -73,7 +73,7 @@ module.exports = class SpriteParser
movieClip.frameBounds[boundsIndex] = _.clone(lastBounds)
else
lastBounds = bounds
localGraphics = @getGraphicsFromBlock(movieClip, source)
[shapeKeys, localShapes] = @getShapesFromBlock movieClip, source
localContainers = @getContainersFromMovieClip movieClip, source, true
@ -90,7 +90,7 @@ module.exports = class SpriteParser
bounds: movieClip.bounds
frameBounds: movieClip.frameBounds
}, movieClip.name
for movieClip in movieClips
if movieClip.name not in childrenMovieClips
for bounds in movieClip.frameBounds
@ -390,7 +390,7 @@ module.exports = class SpriteParser
name = node.callee.property?.name
return unless name in ['get', 'to', 'wait']
return if name is 'get' and callExpressions.length # avoid Ease calls in the tweens
flattenedRanges = _.flatten [a.range for a in node.arguments]
flattenedRanges = _.flatten [(a.range for a in node.arguments)]
range = [_.min(flattenedRanges), _.max(flattenedRanges)]
# Replace 'this.<local>' references with just the 'name'
argsSource = @subSourceFromRange(range, source)

View file

@ -326,7 +326,7 @@ module.exports = Lank = class Lank extends CocoClass
newScaleFactorX = @thang?.scaleFactorX ? @thang?.scaleFactor ? 1
newScaleFactorY = @thang?.scaleFactorY ? @thang?.scaleFactor ? 1
if @thang?.spriteName is 'Beam'
if @layer?.name is 'Land' or @thang?.spriteName is 'Beam'
@scaleFactorX = newScaleFactorX
@scaleFactorY = newScaleFactorY
else if @thang and (newScaleFactorX isnt @targetScaleFactorX or newScaleFactorY isnt @targetScaleFactorY)

View file

@ -65,12 +65,12 @@ module.exports = class SingularSprite extends createjs.Sprite
@regY = -reg.y * scale
@scaleX = @scaleY = 1 / @resolutionFactor
if @camera and @thangType.get('name') in floors
@baseScaleY *= @camera.y2x
@scaleX *= -1 if action.flipX
@scaleY *= -1 if action.flipY
@baseScaleX = @scaleX
@baseScaleY = @scaleY
if @camera and @thangType.get('name') in floors
@baseScaleY *= @camera.y2x
@currentAnimation = actionName
return

View file

@ -46,7 +46,8 @@ module.exports = class System
hashString: (s) ->
return @hashes[s] if s of @hashes
hash = 0
hash = hash * 31 + s.charCodeAt(i) for i in [0 ... Math.min(s.length, 100)]
for i in [0 ... Math.min(s.length, 100)]
hash = hash * 31 + s.charCodeAt(i)
hash = @hashes[s] = hash % 3.141592653589793
hash

View file

@ -480,6 +480,8 @@
forum_prefix: "For anything public, please try "
forum_page: "our forum"
forum_suffix: " instead."
faq_prefix: "There's also a"
faq: "FAQ"
subscribe_prefix: "If you need help figuring out a level, please"
subscribe: "buy a CodeCombat subscription"
subscribe_suffix: "and we'll be happy to help you with your code."

View file

@ -1,6 +1,48 @@
c = require './../schemas'
ThangComponentSchema = require './thang_component'
defaultTasks = [
'Name the level.'
'Create a Referee stub, if needed.'
'Build the level.'
'Set up goals.'
'Choose the Existence System lifespan and frame rate.'
'Choose the UI System paths and coordinate hover if needed.'
'Choose the AI System pathfinding and Vision System line of sight.'
'Write the sample code.'
'Do basic set decoration.'
'Adjust script camera bounds.'
'Choose music file in Introduction script.'
'Add to a campaign.'
'Publish for playtesting.'
'Choose level options like required/restricted gear.'
'Create achievements, including unlocking next level.'
'Playtest with a slow/tough hero.'
'Playtest with a fast/weak hero.'
'Playtest with a couple random seeds.'
'Make sure the level ends promptly on success and failure.'
'Remove/simplify unnecessary doodad collision.'
'Release to adventurers.'
'Write the description.'
'Translate the sample code comments.'
'Add Io/Clojure/Lua/CoffeeScript.'
'Write the guide.'
'Write a loading tip, if needed.'
'Populate i18n.'
'Mark whether it requires a subscription (after adventurer week).'
'Release to everyone.'
'Check completion/engagement/problem analytics.'
'Do any custom scripting, if needed.'
'Do thorough set decoration.'
'Add a walkthrough video.'
]
SpecificArticleSchema = c.object()
c.extendNamedProperties SpecificArticleSchema # name first
SpecificArticleSchema.properties.body = {type: 'string', title: 'Content', description: 'The body content of the article, in Markdown.', format: 'markdown'}
@ -252,6 +294,7 @@ _.extend LevelSchema.properties,
terrain: c.terrainString
showsGuide: c.shortString(title: 'Shows Guide', description: 'If the guide is shown at the beginning of the level.', 'enum': ['first-time', 'always'])
requiresSubscription: {title: 'Requires Subscription', description: 'Whether this level is available to subscribers only.', type: 'boolean'}
tasks: c.array {title: 'Tasks', description: 'Tasks to be completed for this level.', default: (name: t for t in defaultTasks)}, c.task
c.extendBasicProperties LevelSchema, 'level'
c.extendSearchableProperties LevelSchema

View file

@ -228,3 +228,7 @@ me.RewardSchema = (descriptionFragment='earned by achievements') ->
levels: me.array {uniqueItems: true, description: "Levels #{descriptionFragment}."},
me.stringID(links: [{rel: 'db', href: '/db/level/{($)}/version'}], title: 'Level', description: 'A reference to the earned Level.', format: 'latest-version-original-reference')
gems: me.int {description: "Gems #{descriptionFragment}."}
me.task = me.object {title: 'Task', description: 'A task to be completed', format: 'task', default: {name: 'TODO', complete: false}},
name: {title: 'Name', description: 'What must be done?', type: 'string'}
complete: {title: 'Complete', description: 'Whether this task is done.', type: 'boolean', format: 'checkbox'}

View file

@ -378,3 +378,8 @@ body > iframe[src^="https://apis.google.com"]
.progress-bar
background-color: lightblue
.treema-node input[type='checkbox']
@include scale(1.25)
width: auto
margin: 8px 15px 8px 15px

View file

@ -33,6 +33,9 @@
#components-treema
z-index: 11
.not-present
opacity: 0.75
.edit-component-container
margin-left: 290px
position: absolute

View file

@ -9,10 +9,11 @@ block content
li.active(data-i18n="#{currentEditor}")
| #{currentEditor}
if me.get('anonymous')
a.btn.btn-primary.open-modal-button(data-toggle="coco-modal", data-target="core/AuthModal", role="button", data-i18n="#{currentNewSignup}") Log in to Create a New Content
else
a.btn.btn-primary.open-modal-button#new-model-button(data-i18n="#{currentNew}") Create a New Something
if me.isAdmin() || !newModelsAdminOnly
if me.get('anonymous')
a.btn.btn-primary.open-modal-button(data-toggle="coco-modal", data-target="core/AuthModal", role="button", data-i18n="#{currentNewSignup}") Log in to Create a New Something
else
a.btn.btn-primary.open-modal-button#new-model-button(data-i18n="#{currentNew}") Create a New Something
input#search(data-i18n="[placeholder]#{currentSearch}")
hr
div.results

View file

@ -9,6 +9,9 @@ block modal-body-content
span.spl(data-i18n="contact.forum_prefix") For anything public, please try
a(href="http://discourse.codecombat.com/", data-i18n="contact.forum_page") our forum
span(data-i18n="contact.forum_suffix") instead.
span.spl.spr(data-i18n="contact.faq_prefix") There's also a
a(data-i18n="contact.faq", href="http://discourse.codecombat.com/t/faq-check-before-posting/1027") FAQ
| .
if me.isPremium()
p(data-i18n="contact.subscriber_support") Since you're a CodeCombat subscriber, your email will get our priority support.
else

View file

@ -92,9 +92,9 @@ block header
li(class=anonymous ? "disabled": "")
a(data-i18n="common.fork")#fork-start-button Fork
li(class=anonymous ? "disabled": "")
a(data-toggle="coco-modal", data-target="modal/RevertModal", data-i18n="editor.revert")#revert-button Revert
a(data-toggle="coco-modal", data-target="modal/RevertModal", data-i18n="editor.revert", disabled=anonymous)#revert-button Revert
li(class=anonymous ? "disabled": "")
a(data-toggle="coco-modal", data-target="editor/level/modals/GenerateTerrainModal", data-i18n="editor.generate_terrain").generate-terrain-button Generate Terrain
a(data-toggle="coco-modal", data-target="editor/level/modals/GenerateTerrainModal", data-i18n="editor.generate_terrain", disabled=anonymous).generate-terrain-button Generate Terrain
li(class=anonymous ? "disabled": "")
a(data-i18n="editor.pop_i18n")#pop-level-i18n-button Populate i18n
li.divider

View file

@ -52,11 +52,11 @@ block header
span.glyphicon-chevron-down.glyphicon
ul.dropdown-menu
li.dropdown-header(data-i18n="common.actions") Actions
li(class=anonymous ? "disabled": "")
li(class=!me.isAdmin() ? "disabled": "")
a(data-i18n="common.fork")#fork-start-button Fork
li(class=anonymous ? "disabled": "")
a(data-toggle="coco-modal", data-target="modal/RevertModal", data-i18n="editor.revert")#revert-button Revert
li(class=anonymous ? "disabled": "")
li(class=!authorized ? "disabled": "")
a(data-toggle="coco-modal", data-target="modal/RevertModal", data-i18n="editor.revert", disabled=!authorized)#revert-button Revert
li(class=!authorized ? "disabled": "")
a(data-i18n="editor.pop_i18n")#pop-level-i18n-button Populate i18n
li.divider
li.dropdown-header(data-i18n="common.info") Info

View file

@ -199,6 +199,7 @@ module.exports = class CocoView extends Backbone.View
# special handler for opening modals that are dynamically loaded, rather than static in the page. It works (or should work) like Bootstrap's modals, except use coco-modal for the data-toggle value.
elem = $(e.target)
return unless elem.data('toggle') is 'coco-modal'
return if elem.attr('disabled')
target = elem.data('target')
Modal = require 'views/'+target
e.stopPropagation()

View file

@ -6,7 +6,6 @@ module.exports = class ForkModal extends ModalView
id: 'fork-modal'
template: template
instant: false
modalWidthPercent: 60
events:
'click #fork-model-confirm-button': 'forkModel'

View file

@ -14,6 +14,7 @@ module.exports = class AchievementSearchView extends SearchView
context.currentNew = 'editor.new_achievement_title'
context.currentNewSignup = 'editor.new_achievement_title_login'
context.currentSearch = 'editor.achievement_search_title'
context.newModelsAdminOnly = true
context.unauthorized = true unless me.isAdmin()
@$el.i18n()
context

View file

@ -14,5 +14,6 @@ module.exports = class ArticleSearchView extends SearchView
context.currentNew = 'editor.new_article_title'
context.currentNewSignup = 'editor.new_article_title_login'
context.currentSearch = 'editor.article_search_title'
context.newModelsAdminOnly = true
@$el.i18n()
context

View file

@ -40,7 +40,7 @@ module.exports = class LevelEditView extends RootView
'click .play-with-team-button': 'onPlayLevel'
'click .play-with-team-parent': 'onPlayLevelTeamSelect'
'click #commit-level-start-button': 'startCommittingLevel'
'click #fork-start-button': 'startForking'
'click li:not(.disabled) > #fork-start-button': 'startForking'
'click #level-history-button': 'showVersionHistory'
'click #undo-button': 'onUndo'
'mouseenter #undo-button': 'showUndoDescription'
@ -50,7 +50,7 @@ module.exports = class LevelEditView extends RootView
'click #components-tab': -> @subviews.editor_level_components_tab_view.refreshLevelThangsTreema @level.get('thangs')
'click #level-patch-button': 'startPatchingLevel'
'click #level-watch-button': 'toggleWatchLevel'
'click #pop-level-i18n-button': 'onPopulateI18N'
'click li:not(.disabled) > #pop-level-i18n-button': 'onPopulateI18N'
'click a[href="#editor-level-documentation"]': 'onClickDocumentationTab'
'mouseup .nav-tabs > li a': 'toggleTab'
@ -66,7 +66,7 @@ module.exports = class LevelEditView extends RootView
showLoading: ($el) ->
$el ?= @$el.find('.outer-content')
super($el)
getTitle: -> "LevelEditor - " + (@level.get('name') or '...')
onLoaded: ->
@ -170,7 +170,7 @@ module.exports = class LevelEditView extends RootView
button = @$el.find('#level-watch-button')
@level.watch(button.find('.watch').is(':visible'))
button.find('> span').toggleClass('secret')
onPopulateI18N: ->
@level.populateI18N()
f = -> document.location.reload()

View file

@ -31,7 +31,7 @@ module.exports = class ComponentsTabView extends CocoView
thangType = @supermodel.getModelByOriginal ThangType, thang.thangType
for component in thangType.get('components') ? []
componentMap[component.original] = component
for component in thang.components
componentMap[component.original] = component
@ -45,9 +45,10 @@ module.exports = class ComponentsTabView extends CocoView
componentModelMap = {}
componentModelMap[comp.get('original')] = comp for comp in componentModels
components = ({original: key.split('.')[0], majorVersion: parseInt(key.split('.')[1], 10), thangs: value, count: value.length} for key, value of @presentComponents)
treemaData = _.sortBy components, (comp) ->
comp = componentModelMap[comp.original]
res = [comp.get('system'), comp.get('name')]
components = components.concat ({original: c.get('original'), majorVersion: c.get('version').major, thangs: [], count: 0} for c in componentModels when not @presentComponents[c.get('original') + '.' + c.get('version').major])
treemaData = _.sortBy components, (comp) =>
component = componentModelMap[comp.original]
res = [(if comp.count then 0 else 1), component.get('system'), component.get('name')]
return res
treemaOptions =
@ -82,7 +83,7 @@ module.exports = class ComponentsTabView extends CocoView
onLevelComponentEditingEnded: (e) ->
@removeSubView @levelComponentEditView
@levelComponentEditView = null
destroy: ->
@componentsTreema?.destroy()
super()
@ -98,4 +99,6 @@ class LevelComponentNode extends TreemaObjectNode
comp = _.find @settings.supermodel.getModels(LevelComponent), (m) =>
m.get('original') is data.original and m.get('version').major is data.majorVersion
name = "#{comp.get('system')}.#{comp.get('name')} v#{comp.get('version').major}"
@buildValueForDisplaySimply valEl, "#{name} (#{count})"
result = @buildValueForDisplaySimply valEl, "#{name} (#{count})"
result.addClass 'not-present' unless data.count
result

View file

@ -14,7 +14,8 @@ module.exports = class SettingsTabView extends CocoView
# not thangs or scripts or the backend stuff
editableSettings: [
'name', 'description', 'documentation', 'nextLevel', 'background', 'victory', 'i18n', 'icon', 'goals',
'type', 'terrain', 'showsGuide', 'banner', 'employerDescription', 'loadingTip', 'requiresSubscription'
'type', 'terrain', 'showsGuide', 'banner', 'employerDescription', 'loadingTip', 'requiresSubscription',
'tasks'
]
subscriptions:

View file

@ -45,13 +45,13 @@ module.exports = class ThangTypeEditView extends RootView
'click #stop-button': 'stopAnimation'
'click #play-button': 'playAnimation'
'click #history-button': 'showVersionHistory'
'click #fork-start-button': 'startForking'
'click li:not(.disabled) > #fork-start-button': 'startForking'
'click #save-button': 'openSaveModal'
'click #patches-tab': -> @patchesView.load()
'click .play-with-level-button': 'onPlayLevel'
'click .play-with-level-parent': 'onPlayLevelSelect'
'keyup .play-with-level-input': 'onPlayLevelKeyUp'
'click #pop-level-i18n-button': 'onPopulateLevelI18N'
'click li:not(.disabled) > #pop-level-i18n-button': 'onPopulateLevelI18N'
onClickSetVectorIcon: ->

View file

@ -15,6 +15,7 @@ module.exports = class ThangTypeSearchView extends SearchView
context.currentNew = 'editor.new_thang_title'
context.currentNewSignup = 'editor.new_thang_title_login'
context.currentSearch = 'editor.thang_search_title'
context.newModelsAdminOnly = true
@$el.i18n()
context

View file

@ -312,7 +312,8 @@ module.exports = class SpellView extends CocoView
# Lock contiguous section of default code
# Only works for languages without closing delimeters on blocks currently
lines = @aceDoc.getAllLines()
lastRow = row for line, row in lines when not /^\s*$/.test(line)
for line, row in lines when not /^\s*$/.test(line)
lastRow = row
if lastRow?
@readOnlyRanges.push new Range 0, 0, lastRow, lines[lastRow].length - 1

View file

@ -31,6 +31,7 @@ LevelHandler = class LevelHandler extends Handler
'i18nCoverage'
'loadingTip'
'requiresSubscription'
'tasks'
]
postEditableProperties: ['name']
@ -72,7 +73,7 @@ LevelHandler = class LevelHandler extends Handler
Session.findOne(sessionQuery).exec (err, doc) =>
return @sendDatabaseError(res, err) if err
return @sendSuccess(res, doc) if doc?
return @sendPaymentRequiredError(res, err) if (not req.user.isPremium()) and level.get('requiresSubscription')
return @sendPaymentRequiredError(res, err) if (not req.user.isPremium()) and level.get('requiresSubscription')
@createAndSaveNewSession sessionQuery, req, res
createAndSaveNewSession: (sessionQuery, req, res) =>