Merge branch 'master' into production

This commit is contained in:
Matt Lott 2015-10-12 21:14:09 -07:00
commit 4a85290af8
18 changed files with 541 additions and 169 deletions

View file

@ -96,6 +96,8 @@ module.exports = class CocoRouter extends Backbone.Router
'github/*path': 'routeToServer'
'hoc': go('courses/CoursesView')
'i18n': go('i18n/I18NHomeView')
'i18n/thang/:handle': go('i18n/I18NEditThangTypeView')
'i18n/component/:handle': go('i18n/I18NEditComponentView')

View file

@ -0,0 +1,98 @@
SpriteBuilder = require('./SpriteBuilder')
ThangType = require('models/ThangType')
CocoClass = require('core/CocoClass')
class SpriteExporter extends CocoClass
'''
To be used by the ThangTypeEditView to export ThangTypes to single sprite sheets which can be uploaded to
GridFS and used in gameplay, avoiding rendering vector images.
Code has been copied and reworked and simplified from LayerAdapter. Some shared code has been refactored into
ThangType, but more work could be done to rethink and reorganize Sprite rendering.
'''
constructor: (thangType, options) ->
@thangType = thangType
options ?= {}
@colorConfig = options.colorConfig or {}
@resolutionFactor = options.resolutionFactor or 1
@actionNames = options.actionNames or (action.name for action in @thangType.getDefaultActions())
super()
build: (renderType) ->
spriteSheetBuilder = new createjs.SpriteSheetBuilder()
if (renderType or @thangType.get('spriteType') or 'segmented') is 'segmented'
@renderSegmentedThangType(spriteSheetBuilder)
else
@renderSingularThangType(spriteSheetBuilder)
try
spriteSheetBuilder.buildAsync()
catch e
@resolutionFactor *= 0.9
return @build()
spriteSheetBuilder.on 'complete', @onBuildSpriteSheetComplete, @, true, spriteSheetBuilder
@asyncBuilder = spriteSheetBuilder
renderSegmentedThangType: (spriteSheetBuilder) ->
containersToRender = @thangType.getContainersForActions(@actionNames)
spriteBuilder = new SpriteBuilder(@thangType, {colorConfig: @colorConfig})
for containerGlobalName in containersToRender
container = spriteBuilder.buildContainerFromStore(containerGlobalName)
frame = spriteSheetBuilder.addFrame(container, null, @resolutionFactor * (@thangType.get('scale') or 1))
spriteSheetBuilder.addAnimation(containerGlobalName, [frame], false)
renderSingularThangType: (spriteSheetBuilder) ->
actionObjects = _.values(@thangType.getActions())
animationActions = []
for a in actionObjects
continue unless a.animation
continue unless a.name in @actionNames
animationActions.push(a)
spriteBuilder = new SpriteBuilder(@thangType, {colorConfig: @colorConfig})
animationGroups = _.groupBy animationActions, (action) -> action.animation
for animationName, actions of animationGroups
scale = actions[0].scale or @thangType.get('scale') or 1
mc = spriteBuilder.buildMovieClip(animationName, null, null, null, {'temp':0})
spriteSheetBuilder.addMovieClip(mc, null, scale * @resolutionFactor)
frames = spriteSheetBuilder._animations['temp'].frames
framesMap = _.zipObject _.range(frames.length), frames
for action in actions
if action.frames
frames = (framesMap[parseInt(frame)] for frame in action.frames.split(','))
else
frames = _.sortBy(_.values(framesMap))
next = @nextForAction(action)
spriteSheetBuilder.addAnimation(action.name, frames, next)
containerActions = []
for a in actionObjects
continue unless a.container
continue unless a.name in @actionNames
containerActions.push(a)
containerGroups = _.groupBy containerActions, (action) -> action.container
for containerName, actions of containerGroups
container = spriteBuilder.buildContainerFromStore(containerName)
scale = actions[0].scale or @thangType.get('scale') or 1
frame = spriteSheetBuilder.addFrame(container, null, scale * @resolutionFactor)
for action in actions
spriteSheetBuilder.addAnimation(action.name, [frame], false)
onBuildSpriteSheetComplete: (e, builder) ->
if builder.spriteSheet._images.length > 1
total = 0
# get a rough estimate of how much smaller the spritesheet needs to be
for image, index in builder.spriteSheet._images
total += image.height / builder.maxHeight
@resolutionFactor /= (Math.max(1.1, Math.sqrt(total)))
@_renderNewSpriteSheet(e.async)
return
@trigger 'build', { spriteSheet: builder.spriteSheet }
module.exports = SpriteExporter

View file

@ -22,6 +22,7 @@ SpriteBuilder = require 'lib/sprites/SpriteBuilder'
CocoClass = require 'core/CocoClass'
SegmentedSprite = require './SegmentedSprite'
SingularSprite = require './SingularSprite'
ThangType = require 'models/ThangType'
NEVER_RENDER_ANYTHING = false # set to true to test placeholders
@ -42,7 +43,6 @@ module.exports = LayerAdapter = class LayerAdapter extends CocoClass
buildAutomatically: true
buildAsync: true
resolutionFactor: SPRITE_RESOLUTION_FACTOR
defaultActions: ['idle', 'die', 'move', 'attack']
numThingsLoading: 0
lanks: null
spriteSheet: null
@ -196,7 +196,7 @@ module.exports = LayerAdapter = class LayerAdapter extends CocoClass
@upsertActionToRender(lank.thangType)
else
for action in _.values(lank.thangType.getActions())
continue unless _.any @defaultActions, (prefix) -> _.string.startsWith(action.name, prefix)
continue unless _.any ThangType.defaultActions, (prefix) -> _.string.startsWith(action.name, prefix)
@upsertActionToRender(lank.thangType, action.name, lank.options.colorConfig)
upsertActionToRender: (thangType, actionName, colorConfig) ->
@ -346,17 +346,9 @@ module.exports = LayerAdapter = class LayerAdapter extends CocoClass
#- Rendering containers for segmented thang types
renderSegmentedThangType: (thangType, colorConfig, actionNames, spriteSheetBuilder) ->
containersToRender = {}
for actionName in actionNames
action = _.find(thangType.getActions(), {name: actionName})
if action.container
containersToRender[action.container] = true
else if action.animation
animationContainers = @getContainersForAnimation(thangType, action.animation, action)
containersToRender[container.gn] = true for container in animationContainers
containersToRender = thangType.getContainersForActions(actionNames)
spriteBuilder = new SpriteBuilder(thangType, {colorConfig: colorConfig})
for containerGlobalName in _.keys(containersToRender)
for containerGlobalName in containersToRender
containerKey = @renderGroupingKey(thangType, containerGlobalName, colorConfig)
if @spriteSheet?.resolutionFactor is @resolutionFactor and containerKey in @spriteSheet.getAnimations()
container = new createjs.Sprite(@spriteSheet)
@ -367,15 +359,6 @@ module.exports = LayerAdapter = class LayerAdapter extends CocoClass
frame = spriteSheetBuilder.addFrame(container, null, @resolutionFactor * (thangType.get('scale') or 1))
spriteSheetBuilder.addAnimation(containerKey, [frame], false)
getContainersForAnimation: (thangType, animation, action) ->
rawAnimation = thangType.get('raw').animations[animation]
if not rawAnimation
console.error 'thang type', thangType.get('name'), 'is missing animation', animation, 'from action', action
containers = rawAnimation.containers
for animation in thangType.get('raw').animations[animation].animations
containers = containers.concat(@getContainersForAnimation(thangType, animation.gn, action))
return containers
#- Rendering sprite sheets for singular thang types
renderSingularThangType: (thangType, colorConfig, actionNames, spriteSheetBuilder) ->

View file

@ -913,7 +913,7 @@
see_the: "See the"
more_info: "for more information."
choose_course: "Choose Your Course:"
enter_code: "Enter an unlock code"
enter_code: "Enter an unlock code to join an existing class" # {change}
enter_code1: "Enter unlock code"
enroll: "Enroll"
pick_from_classes: "Pick from your current classes"

View file

@ -602,7 +602,7 @@ module.exports = nativeDescription: "繁體中文", englishDescription: "Chinese
intro_2: "無需經驗!"
free_title: "要多少錢?"
cost_china: "CodeCombat的前5個關卡在中國是免費的在這之後需花費每月9.99美元來訪問我們架設在中國專屬服務器上的190多個關卡。" # Deprecated
# cost_premium_server: "CodeCombat is free for the first five levels, after which it costs $9.99 USD per month for access to our other 190+ levels on our exclusive country-specific servers."
cost_premium_server: "CodeCombat的前5個關卡在中國是免費的在這之後需花費每月9.99美元來訪問我們架設在中國專屬服務器上的190多個關卡。"
free_1: "有110多個覆蓋了所有理論的免費關卡。"
free_2: "包月訂閱可以訪問視頻教程和額外的練習關卡。"
teacher_subs_title: "教師可免費訂閱!"
@ -835,93 +835,94 @@ module.exports = nativeDescription: "繁體中文", englishDescription: "Chinese
last_played: "最後玩了"
leagues_explanation: "在部落裡與其他成員組成聯盟一起參加下面的多人競技場。"
# courses:
# course: "Course"
# courses: "courses"
# not_enrolled: "You are not enrolled in this course."
# visit_pref: "Please visit the"
# visit_suf: "page to enroll."
# select_class: "Select one of your classes"
# unnamed: "*unnamed*"
# select: "Select"
# unnamed_class: "Unnamed Class"
# edit_settings: "edit class settings"
# edit_settings1: "Edit Class Settings"
# progress: "Class Progress"
# add_students: "Add Students"
# stats: "Statistics"
# total_students: "Total students:"
# average_time: "Average level play time:"
# total_time: "Total play time:"
# average_levels: "Average levels completed:"
# total_levels: "Total levels completed:"
# furthest_level: "Furthest level completed:"
# concepts_covered: "Concepts Covered"
# students: "Students"
# students1: "students"
# expand_details: "Expand details"
# concepts: "Concepts"
# levels: "levels"
# played: "Played"
# play_time: "Play time:"
# completed: "Completed:"
# invite_students: "Invite students to join this class."
# invite_link_header: "Link to join course"
# invite_link_p_1: "Give this link to students you would like to have join the course."
# invite_link_p_2: "Or have us email them directly:"
# capacity_used: "Course slots used:"
# enter_emails: "Enter student emails to invite, one per line"
# send_invites: "Send Invites"
# title: "Title"
# description: "Description"
# languages_available: "Select programming languages available to the class:"
# all_lang: "All Languages"
# show_progress: "Show student progress to everyone in the class"
# creating_class: "Creating class..."
# purchasing_course: "Purchasing course..."
# buy_course: "Buy Course"
# buy_course1: "Buy this course"
# create_class: "Create Class"
# select_all_courses: "Select 'All Courses' for a 50% discount!"
# all_courses: "All Courses"
# number_students: "Number of students"
# enter_number_students: "Enter the number of students you need for this class."
# name_class: "Name your class"
# displayed_course_page: "This will be displayed on the course page for you and your students. It can be changed later."
# buy: "Buy"
# purchasing_for: "You are purchasing a license for"
# creating_for: "You are creating a class for"
# for: "for" # Like in 'for 30 students'
# receive_code: "Afterwards you will receive an unlock code to distribute to your students, which they can use to enroll in your class."
# free_trial: "Free trial for teachers!"
# get_access: "to get individual access to all courses for evalutaion purposes."
# questions: "Questions?"
# faq: "Courses FAQ"
# question: "Q:" # Like in 'Question'
# question1: "What's the difference between these courses and the single player game?"
# answer: "A:" # Like in 'Answer'
# answer1: "The single player game is designed for individuals, while the courses are designed for classes."
# answer2: "The single player game has items, gems, hero selection, leveling up, and in-app purchases. Courses have classroom management features and streamlined student-focused level pacing."
# teachers_click: "Teachers Click Here"
# students_click: "Students Click Here"
# courses_on_coco: "Courses on CodeCombat"
# designed_to: "Courses are designed to introduce computer science concepts using CodeCombat's fun and engaging environment. CodeCombat levels are organized around key topics to encourage progressive learning, over the course of 5 hours."
# more_in_less: "Learn more in less time"
# no_experience: "No coding experience necesssary"
# easy_monitor: "Easily monitor student progress"
# purchase_for_class: "Purchase a course for your entire class. It's easy to sign up your students!"
# see_the: "See the"
# more_info: "for more information."
# choose_course: "Choose Your Course:"
# enter_code: "Enter an unlock code"
# enter_code1: "Enter unlock code"
# enroll: "Enroll"
# pick_from_classes: "Pick from your current classes"
# enter: "Enter"
# or: "Or"
# topics: "Topics"
# hours_content: "Hours of content:"
# get_free: "Get FREE course"
courses:
course: "課程"
courses: "課程"
not_enrolled: "您没有註冊這一節課。"
visit_pref: "請到這個"
visit_suf: "網頁註冊。"
select_class: "請選其中一門課堂"
unnamed: "*未命名*"
select: "選擇"
unnamed_class: "課堂未命名"
edit_settings: "編輯課堂設定"
edit_settings1: "編輯課堂設定"
progress: "課堂進度"
add_students: "添加學生"
stats: "統計"
total_students: "學生人數:"
average_time: "平均遊戲時間:"
total_time: "總計遊戲時間:"
average_levels: "平均完成關卡:"
total_levels: "總共完成關卡:"
furthest_level: "最高關卡完成:"
concepts_covered: "課目覆蓋"
students: "學生"
students1: "個學生。"
expand_details: "展開細節"
concepts: "課目"
levels: "關卡"
played: "已通關"
play_time: "遊戲時間:"
completed: "遊戲時間:"
invite_students: "邀請學生加入此課堂。"
invite_link_header: "參與課堂的縺結"
invite_link_p_1: "把這個參與課堂的連結發給你認可的學生。" # there has few problem of my translation
invite_link_p_2: "或讓我們代你直接發送電郵:"
capacity_used: "Course slots used:"
enter_emails: "輸入學生電郵地址來邀請,每行一個"
send_invites: "發送邀請"
title: "發送邀請"
description: "描述"
languages_available: "選擇編程語言:"
all_lang: "所有編程語言"
show_progress: "向所有該課堂的人展示學生的進度"
creating_class: "課堂創建中···"
purchasing_course: "購買課程中···"
buy_course: "購買課程"
buy_course1: "購買這個課程"
create_class: "創建課堂"
select_all_courses: "可半價一次購買所有課程!"
all_courses: "所有課程"
number_students: "學生人數"
enter_number_students: "輪入該課堂的學生上限人數"
name_class: "命名您的課堂"
displayed_course_page: "這將會在課程頁面顯示,可被修改。"
buy: "購買"
purchasing_for: "你正在購買許可證,課程為"
creating_for: "您正在創建一個課程為"
for: ",人數上限為" # Like in 'for 30 students'
receive_code: "然後您會收到一個解鎖碼,把它分發給你的學生用來註冊你的課堂。"
free_trial: "老師可免費試用!"
get_access: "獲得個人使用權在評估目的下來使用所有課程。"
questions: "有疑問?"
faq: "課程FAQ"
question: "問:" # Like in 'Question'
question1: "這些課程和單人遊戲之間的有什麼區別?"
answer: "答:" # Like in 'Answer'
answer1: "單人遊戲是專為個人而設,而課程是專為課堂而設。"
answer2: "在單人遊戲中有物品、寶石、英雄選擇、練級、和內購應用。課程有課堂管理功能,和老師可根據學生的水平而調整教學進度。"
teachers_click: "老師點擊這裡"
students_click: "學生點擊這裡"
courses_on_coco: "CodeCombat上的課程"
designed_to: "CodeCombat課程的宗旨是在使用CodeCombat生動有趣的環境下教授計算機科學的課目。整個CodeCombat的關卡是圍繞著計算機科學的重點並激勵學生們自主向上學習在5小時的過程。"
more_in_less: "以最少的時間學習最多的知識"
no_experience: "無需編程經驗"
easy_monitor: "容易管理學生的進程"
purchase_for_class: "為你的班級購買CodeCombat課程讓簽到和管理變得更容易"
see_the: "詳細資訊請看"
more_info: ""
choose_course: "選擇您的課程:"
enter_code: "輸入一個解銷碼"
enter_code1: "輸入解銷碼"
enroll: "註冊"
pick_from_classes: "從目前的課程選擇"
enter: "輪入"
or: ""
topics: "題目"
hours_content: "內容時間:"
get_free: "取得免費課程!"
classes:
archmage_title: "大法師"
@ -1207,7 +1208,7 @@ module.exports = nativeDescription: "繁體中文", englishDescription: "Chinese
recently_played: "最近玩過"
no_recent_games: "在過去兩個星期沒有玩過遊戲。"
payments: "付款"
# prepaid: "Prepaid"
prepaid: "充值"
purchased: "已購買"
sale: "促銷"
subscription: "訂閱"
@ -1238,13 +1239,13 @@ module.exports = nativeDescription: "繁體中文", englishDescription: "Chinese
retrying: "服務器錯誤,重試中。"
success: "支付成功。謝謝!"
# account_prepaid:
# purchase_code: "Purchase a Subscription Code"
# purchase_amount: "Amount"
# purchase_total: "Total"
# purchase_button: "Submit Purchase"
# your_codes: "Your Codes:"
# redeem_codes: "Redeem a Subscription Code"
account_prepaid:
purchase_code: "購買訂閱碼"
purchase_amount: "數量"
purchase_total: "總共"
purchase_button: "提交購買"
your_codes: "你的訂閱碼:"
redeem_codes: "兌換訂閱碼"
loading_error:
could_not_load: "從伺服器載入失敗"
@ -1258,7 +1259,7 @@ module.exports = nativeDescription: "繁體中文", englishDescription: "Chinese
bad_input: "錯誤輸入。"
server_error: "伺服器錯誤。"
unknown: "未知錯誤。"
# error: "ERROR"
error: "錯誤"
resources:
sessions: "會話"

View file

@ -35,6 +35,7 @@ module.exports = class ThangType extends CocoModel
urlRoot: '/db/thang.type'
building: {}
editableByArtisans: true
@defaultActions: ['idle', 'die', 'move', 'attack']
initialize: ->
super()
@ -78,6 +79,14 @@ module.exports = class ThangType extends CocoModel
return {} unless @isFullyLoaded()
return @actions or @buildActions()
getDefaultActions: ->
actions = []
for action in _.values(@getActions())
continue unless _.any ThangType.defaultActions, (prefix) ->
_.string.startsWith(action.name, prefix)
actions.push(action)
return actions
buildActions: ->
return null unless @isFullyLoaded()
@actions = $.extend(true, {}, @get('actions'))
@ -479,3 +488,24 @@ module.exports = class ThangType extends CocoModel
playerLevel = me.constructor.levelForTier playerTier
#console.log 'Level required for', @get('name'), 'is', playerLevel, 'player tier', playerTier, 'because it is itemTier', itemTier, 'which is normally level', me.constructor.levelForTier(itemTier)
playerLevel
getContainersForAnimation: (animation, action) ->
rawAnimation = @get('raw').animations[animation]
if not rawAnimation
console.error 'thang type', @get('name'), 'is missing animation', animation, 'from action', action
containers = rawAnimation.containers
for animation in @get('raw').animations[animation].animations
containers = containers.concat(@getContainersForAnimation(animation.gn, action))
return containers
getContainersForActions: (actionNames) ->
containersToRender = {}
actions = @getActions()
for actionName in actionNames
action = _.find(actions, {name: actionName})
if action.container
containersToRender[action.container] = true
else if action.animation
animationContainers = @getContainersForAnimation(action.animation, action)
containersToRender[container.gn] = true for container in animationContainers
return _.keys(containersToRender)

View file

@ -169,6 +169,42 @@ _.extend ThangTypeSchema.properties,
extendedName: {type: 'string', title: 'Extended Hero Name', description: 'The long form of the hero\'s name. Ex.: "Captain Anya Weston".'}
unlockLevelName: {type: 'string', title: 'Unlock Level Name', description: 'The name of the level in which the hero is unlocked.'}
tasks: c.array {title: 'Tasks', description: 'Tasks to be completed for this ThangType.'}, c.task
spriteSheets: c.array {title: 'SpriteSheets'},
c.object {title: 'SpriteSheet'},
actionNames: { type: 'array' }
animations:
type: 'object'
description: 'Third EaselJS SpriteSheet animations format'
additionalProperties: {
description: 'EaselJS animation'
type: 'object'
properties: {
frames: { type: 'array' }
next: { type: ['string', 'null'] }
speed: { type: 'number' }
}
}
colorConfig: c.colorConfig()
colorLabel: { enum: ['red', 'green', 'blue'] }
frames:
type: 'array'
description: 'Second EaselJS SpriteSheet frames format'
items:
type: 'array'
items: [
{ type: 'number', title: 'x' }
{ type: 'number', title: 'y' }
{ type: 'number', title: 'width' }
{ type: 'number', title: 'height' }
{ type: 'number', title: 'imageIndex' }
{ type: 'number', title: 'regX' }
{ type: 'number', title: 'regY' }
]
image: { type: 'string', format: 'image-file' }
resolutionFactor: {
type: 'number'
}
spriteType: { enum: ['singular', 'segmented'], title: 'Sprite Type' }
ThangTypeSchema.required = []

View file

@ -1,5 +1,25 @@
#courses-view
.logged_out
font-size: 24px
.signup-button
background: red
color: white
font-size: 18px
font-variant: small-caps
line-height: 27px
text-transform: uppercase
margin-right: 20px
.login-button
background: white
color: black
font-size: 18px
font-variant: small-caps
line-height: 27px
text-transform: uppercase
.center
text-align: center
@ -25,9 +45,6 @@
margin-bottom: 20px
font-size: 14pt
.btn-enroll
margin-top: 20px
.center
text-align: center

View file

@ -10,29 +10,61 @@ block content
if state === 'enrolling'
.alert.alert-info Enrolling in course..
else if state === 'ppc_logged_out'
.alert.alert-success Log in or create an account to join this course.
.alert.alert-danger.logged_out Create account or log in to join this course.
button.btn.btn-sm.btn-primary.header-font.signup-button(data-i18n="login.sign_up")
button.btn.btn-sm.btn-default.header-font.login-button(data-i18n="login.log_in")
else
if state === 'unknown_error'
.alert.alert-danger.alert-dismissible= stateMessage
if studentMode
+student-main
if hocLandingPage
+hoc-landing
else
+teacher-main
.container-fluid
- var i = 0
while i < courses.length
.row
+course-block(courses[i], instances)
- i++
if i < courses.length
if studentMode
+student-main
else
if hocMode
+teacher-hoc
else
+teacher-main
.container-fluid
- var i = 0
while i < courses.length
.row
+course-block(courses[i], instances)
- i++
if i < courses.length
+course-block(courses[i], instances)
- i++
mixin hoc-landing
h1.center Welcome Hour of Code!
br
.container-fluid
.row
.col-md-6.center
button.btn.btn-lg.btn-success.btn-student(data-i18n="courses.students_click")
.col-md-6.center
button.btn.btn-lg.btn-default.btn-teacher(data-i18n="courses.teachers_click")
mixin student-main
button.btn.btn-warning.btn-teacher(data-i18n="courses.teachers_click")
h1.center(data-i18n="courses.courses_on_coco")
mixin teacher-hoc
button.btn.btn-warning.btn-student(data-i18n="courses.students_click")
h1.center Welcome Hour of Code!
p
strong How to use CodeCombat with your students:
ol
li Click the green 'Get FREE course' button below
li Follow the enrollment instructions
li Add students via the 'Add Students' tab
p
span.spr If you have any problems, please email
a(href='mailto:team@codecombat.com') team@codecombat.com
br
mixin teacher-main
button.btn.btn-warning.btn-student(data-i18n="courses.students_click")
h1.center(data-i18n="courses.courses_on_coco")
@ -87,6 +119,14 @@ mixin student-dialog(course)
input.code-input(type='text', data-course-id="#{course.id}", data-i18n="[placeholder]courses.enter_code1", placeholder="Enter unlock code")
.col-md-4
button.btn.btn-success.btn-enroll(data-course-id="#{course.id}", data-i18n="courses.enroll")
if hocMode && course.get('pricePerSeat') === 0
.row.button-row.center.row-pick-class
.col-md-12
br
div.or(data-i18n="courses.or")
.row.button-row.center
.col-md-12
button.btn.btn-success.btn-lg.btn-hoc-student-continue(data-course-id="#{course.id}") Continue by yourself
mixin teacher-dialog(course)
.modal.continue-dialog(id="continueModal#{course.id}")

View file

@ -0,0 +1,34 @@
extends /templates/core/modal-base
block modal-header-content
h4.modal-title Export #{view.thangType.get('name')} SpriteSheet
block modal-body-content
.form-horizontal
.form-group
label.col-sm-3.control-label Team Color
.col-sm-9
select#color-config-select.form-control
option(value='') None
option(value="red") Red
option(value="blue") Blue
option(value="green") Green
.form-group
label.col-sm-3.control-label Resolution Factor
.col-sm-9
input#resolution-input.form-control(value=3)
.form-group
label.col-sm-3.control-label Actions
.col-sm-9
- var defaultActionNames = _.pluck(view.thangType.getDefaultActions(), 'name')
- var actions = view.thangType.getActions()
for action in actions
.checkbox
label
input(type="checkbox" name="action" value=action.name checked=_.contains(defaultActionNames, action.name))
| #{action.name}
block modal-footer-content
button.btn.btn-default(data-dismiss="modal") Cancel
button#save-btn.btn.btn-primary Save

View file

@ -79,19 +79,25 @@ block outer_content
div.tab-pane#editor-thang-colors-tab-view
div.tab-pane#editor-thang-main-tab-view.active
div#settings-col.well
img#portrait.img-thumbnail
div.file-controls
button(disabled=authorized === true ? undefined : "true").btn.btn-sm.btn-info#upload-button
span.glyphicon.glyphicon-upload
span.spl Upload Animation
button(disabled=authorized === true ? undefined : "true").btn.btn-sm.btn-danger#clear-button
span.glyphicon.glyphicon-remove
span.spl Clear Data
button#set-vector-icon(disabled=authorized === true ? undefined : "true").btn.btn-sm
span.glyphicon.glyphicon-gbp
span.spl Vector Icon Setup
input#real-upload-button(type="file")
#thang-type-file-size= fileSizeString
.row
.col-sm-3
img#portrait.img-thumbnail
.col-sm-9
div.file-controls
button(disabled=authorized === true ? undefined : "true").btn.btn-sm.btn-info#upload-button
span.glyphicon.glyphicon-upload
span.spl Upload Animation
button(disabled=authorized === true ? undefined : "true").btn.btn-sm.btn-danger#clear-button
span.glyphicon.glyphicon-remove
span.spl Clear Data
button#set-vector-icon(disabled=authorized === true ? undefined : "true").btn.btn-sm
span.glyphicon.glyphicon-gbp
span.spl Vector Icon Setup
button#export-sprite-sheet-btn.btn.btn-sm(disabled=authorized === true ? undefined : "true")
span.glyphicon.glyphicon-export
span.spl Export SpriteSheet
input#real-upload-button(type="file")
#thang-type-file-size= fileSizeString
div#thang-type-treema
.clearfix
div#display-col.well

View file

@ -2,37 +2,24 @@ extends /templates/base
block content
h2(style='color:#CC0000;') Try CodeCombat Courses!
p
strong What are CodeCombat Courses?
h2 Hour of Code(Combat)
p
a.spr(href='/courses') Courses
span organize the same great levels into groups. They make it easier for you to manage a class of students and monitor their progress.
strong Hi Teachers!
p We're excited to participate in Hour of Code this year!
p We've set up an Introduction to Computer Science course, just for you.
p
strong How to use them:
strong How to use CodeCombat with your students:
ol
li
span.spr Navigate to the
a.spr(href='/courses') Courses
a.spr(href='/courses/teachers?hoc=true') Courses
span page
li Click the green 'Get FREE course' button
li Click the green 'Get FREE course' button under Introduction to Computer Science
li Follow the enrollment instructions
li Add students via the 'Add Students' tab
p
strong We Need Your Help!
p Courses are still in early development, and we need your feedback to make them great for the classroom.
p
strong How you can help:
ul
li
spa.spr Spend 5 minutes checking out
a.spr(href='/courses') Courses
li Enroll in a course and add a student
li Monitor a group of students working through the first course
p
span.spr Send your feedback to
span.spr If you have any problems, please email
a(href='mailto:team@codecombat.com') team@codecombat.com
p Thanks!
br
h2 More Info for Teachers

View file

@ -51,7 +51,7 @@ module.exports = class CocoView extends Backbone.View
@listenTo(@supermodel, 'failed', @onResourceLoadFailed)
@warnConnectionError = _.throttle(@warnConnectionError, 3000)
super options
super arguments...
destroy: ->
@stopListening()
@ -130,6 +130,7 @@ module.exports = class CocoView extends Backbone.View
context.moment = moment
context.translate = $.i18n.t
context.view = @
context._ = _
context
afterRender: ->

View file

@ -22,7 +22,7 @@ module.exports = class ModalView extends CocoView
@className = @className.replace ' fade', '' if options.instant or @instant
@closeButton = options.closeButton if options.closeButton?
@modalWidthPercent = options.modalWidthPercent if options.modalWidthPercent
super options
super arguments...
getRenderData: (context={}) ->
context = super(context)

View file

@ -7,6 +7,8 @@ RootView = require 'views/core/RootView'
template = require 'templates/courses/courses'
utils = require 'core/utils'
# TODO: Hour of Code (HoC) integration is a mess
module.exports = class CoursesView extends RootView
id: 'courses-view'
template: template
@ -15,12 +17,15 @@ module.exports = class CoursesView extends RootView
'click .btn-buy': 'onClickBuy'
'click .btn-enroll': 'onClickEnroll'
'click .btn-enter': 'onClickEnter'
'click .btn-hoc-student-continue': 'onClickHocStudentContinue'
'click .btn-student': 'onClickStudent'
'click .btn-teacher': 'onClickTeacher'
constructor: (options) ->
super(options)
@praise = utils.getCoursePraise()
@hocLandingPage = Backbone.history.getFragment()?.indexOf('hoc') >= 0
@hocMode = utils.getQueryVariable('hoc', false)
@studentMode = Backbone.history.getFragment()?.indexOf('courses/students') >= 0
@courses = new CocoCollection([], { url: "/db/course", model: Course})
@supermodel.loadCollection(@courses, 'courses')
@ -38,6 +43,8 @@ module.exports = class CoursesView extends RootView
context = super()
context.courses = @courses.models ? []
context.enrolledCourses = @enrolledCourses ? {}
context.hocLandingPage = @hocLandingPage
context.hocMode = @hocMode
context.instances = @courseInstances.models ? []
context.praise = @praise
context.state = @state
@ -95,14 +102,58 @@ module.exports = class CoursesView extends RootView
navigationEvent = route: route, viewClass: viewClass, viewArgs: viewArgs
Backbone.Mediator.publish 'router:navigate', navigationEvent
onClickHocStudentContinue: (e) ->
$('.continue-dialog').modal('hide')
return @openModalView new AuthModal() if me.isAnonymous()
courseID = $(e.target).data('course-id')
@state = 'enrolling'
@stateMessage = undefined
@render?()
# TODO: Copied from CourseEnrollView
data =
name: 'Single Player'
seats: 9999
courseID: courseID
jqxhr = $.post('/db/course_instance/-/create', data)
jqxhr.done (data, textStatus, jqXHR) =>
application.tracker?.trackEvent 'Finished HoC student course creation', {courseID: courseID}
# TODO: handle fetch errors
me.fetch(cache: false).always =>
courseID = courseID
route = "/courses/#{courseID}"
viewArgs = [{}, courseID]
if data?.length > 0
courseInstanceID = data[0]._id
route += "/#{courseInstanceID}"
viewArgs[0].courseInstanceID = courseInstanceID
Backbone.Mediator.publish 'router:navigate',
route: route
viewClass: 'views/courses/CourseDetailsView'
viewArgs: viewArgs
jqxhr.fail (xhr, textStatus, errorThrown) =>
console.error 'Got an error purchasing a course:', textStatus, errorThrown
application.tracker?.trackEvent 'Failed HoC student course creation', status: textStatus
if xhr.status is 402
@state = 'declined'
@stateMessage = arguments[2]
else
@state = 'unknown_error'
@stateMessage = "#{xhr.status}: #{xhr.responseText}"
@render?()
onClickStudent: (e) ->
route = "/courses/students"
route += "?hoc=true" if @hocLandingPage or @hocMode
viewClass = require 'views/courses/CoursesView'
navigationEvent = route: route, viewClass: viewClass, viewArgs: []
Backbone.Mediator.publish 'router:navigate', navigationEvent
onClickTeacher: (e) ->
route = "/courses/teachers"
route += "?hoc=true" if @hocLandingPage or @hocMode
viewClass = require 'views/courses/CoursesView'
navigationEvent = route: route, viewClass: viewClass, viewArgs: []
Backbone.Mediator.publish 'router:navigate', navigationEvent

View file

@ -0,0 +1,78 @@
ModalView = require 'views/core/ModalView'
template = require 'templates/editor/thang/export-thang-type-modal'
SpriteExporter = require 'lib/sprites/SpriteExporter'
module.exports = class ExportThangTypeModal extends ModalView
id: "export-thang-type-modal"
template: template
plain: true
events:
'click #save-btn': 'onClickSaveButton'
initialize: (options, @thangType) ->
@builder = null
@getFilename = _.once(@getFilename)
colorMap: {
red: { hue: 0, saturation: 0.75, lightness: 0.5 }
blue: { hue: 0.66, saturation: 0.75, lightness: 0.5 }
green: { hue: 0.33, saturation: 0.75, lightness: 0.5 }
}
getColorLabel: -> @$('#color-config-select').val()
getColorConfig: -> @colorMap[@getColorLabel()]
getActionNames: -> _.map @$('input[name="action"]:checked'), (el) -> $(el).val()
getResolutionFactor: -> parseInt(@$('#resolution-input').val()) or SPRITE_RESOLUTION_FACTOR
getFilename: -> 'spritesheet-'+_.string.slugify(moment().format())+'.png'
onClickSaveButton: ->
options = {
resolutionFactor: @getResolutionFactor()
actionNames: @getActionNames()
colorConfig: @getColorConfig()
}
console.log 'options?', options
@exporter = new SpriteExporter(@thangType, options)
@exporter.build()
@listenToOnce @exporter, 'build', @onExporterBuild
onExporterBuild: (e) ->
@spriteSheet = e.spriteSheet
$('body').empty().append(@spriteSheet._images[0])
return
src = @spriteSheet._images[0].toDataURL()
src = src.replace('data:image/png;base64,', '').replace(/\ /g, '+')
body =
filename: @getFilename()
mimetype: 'image/png'
path: "db/thang.type/#{@thangType.get('original')}"
b64png: src
$.ajax('/file', {type: 'POST', data: body, success: @onSpriteSheetUploaded})
onSpriteSheetUploaded: =>
spriteSheetData = {
actionNames: @getActionNames()
animations: @spriteSheet._data
frames: ([
f.rect.x
f.rect.y
f.rect.width
f.rect.height
0
f.regX
f.regY
] for f in @spriteSheet._frames)
image: "db/thang.type/#{@thangType.get('original')}/"+@getFilename()
resolutionFactor: @getResolutionFactor()
}
if config = @getColorConfig()
spriteSheetData.colorConfig = config
if label = @getColorLabel()
spriteSheetData.colorLabel = label
spriteSheets = _.clone(@thangType.get('spriteSheets') or [])
spriteSheets.push(spriteSheetData)
@thangType.set('spriteSheets', spriteSheets)
@thangType.save()
@listenToOnce @thangType, 'sync', @hide
window.SomeModal = module.exports

View file

@ -20,6 +20,7 @@ VectorIconSetupModal = require 'views/editor/thang/VectorIconSetupModal'
SaveVersionModal = require 'views/editor/modal/SaveVersionModal'
template = require 'templates/editor/thang/thang-type-edit-view'
storage = require 'core/storage'
ExportThangTypeModal = require './ExportThangTypeModal'
CENTER = {x: 200, y: 400}
@ -157,6 +158,7 @@ module.exports = class ThangTypeEditView extends RootView
'mousedown #canvas': 'onCanvasMouseDown'
'mouseup #canvas': 'onCanvasMouseUp'
'mousemove #canvas': 'onCanvasMouseMove'
'click #export-sprite-sheet-btn': 'onClickExportSpriteSheetButton'
onClickSetVectorIcon: ->
modal = new VectorIconSetupModal({}, @thangType)
@ -208,6 +210,7 @@ module.exports = class ThangTypeEditView extends RootView
@patchesView = @insertSubView(new PatchesView(@thangType), @$el.find('.patches-view'))
@showReadOnly() if me.get('anonymous')
@updatePortrait()
@onClickExportSpriteSheetButton()
initComponents: =>
options =
@ -651,6 +654,10 @@ module.exports = class ThangTypeEditView extends RootView
@canvasDragOffset = null
node.set '/', offset
onClickExportSpriteSheetButton: ->
modal = new ExportThangTypeModal({}, @thangType)
@openModalView(modal)
destroy: ->
@camera?.destroy()
super()

View file

@ -35,6 +35,7 @@ ThangTypeHandler = class ThangTypeHandler extends Handler
'unlockLevelName'
'tasks'
'terrains'
'spriteSheets'
]
hasAccess: (req) ->