From f6217a56263b12938b2af9950c0db56240679295 Mon Sep 17 00:00:00 2001 From: AkaKaras Date: Sun, 11 Oct 2015 09:15:28 -0400 Subject: [PATCH 1/8] zh-hant translation --- app/locale/zh-HANT.coffee | 166 +++++++++++++++++++------------------- 1 file changed, 83 insertions(+), 83 deletions(-) diff --git a/app/locale/zh-HANT.coffee b/app/locale/zh-HANT.coffee index 3e07c8499..49773c6c8 100644 --- a/app/locale/zh-HANT.coffee +++ b/app/locale/zh-HANT.coffee @@ -843,89 +843,89 @@ 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." -# 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: "邀請學生加入此課堂。" + enter_emails: "輸入學生電郵地址來邀請,每行一個。" + send_invites: "發送邀請" + title: "課堂名字" + description: "描述" + languages_available: "Select programming languages available to the class:" + 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: "You are purchasing a license for" + creating_for: "您正在創建一個課程為" + for: ",人數上限為" # Like in 'for 30 students' + receive_code: "然後您會收到一個解鎖碼,把它分發給你的學生用來註冊你的課堂。" + free_trial: "老師可免費試用!" + get_access: "to get individual access to all courses for evalutaion purposes." + questions: "Questions?" + faq: "課程FAQ" + question: "問:" # Like in 'Question' + question1: "What's the difference between these courses and the single player game?" + answer: "答:" # 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: "老師點擊這裡" + students_click: "學生點擊這裡" + courses_on_coco: "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: "以最少的時間學習最多的知識" + no_experience: "無需編程經驗" + easy_monitor: "容易監控學生的進程" + purchase_for_class: "Purchase a course for your entire class. It's easy to sign up your students!" + see_the: "詳細資訊請看" + more_info: "。" + choose_course: "選擇您的課程:" + enter_code: "輸入一個解銷碼" + enter_code1: "輸入解銷碼" + enroll: "註冊" + pick_from_classes: "從目前的課程選擇" + enter: "輪入" + or: "或" + topics: "題目" + hours_content: "內容時間:" + get_free: "取得免費課程!" classes: archmage_title: "大法師" From 1a3ab303be0dd873da8775ae693c774f0116f26b Mon Sep 17 00:00:00 2001 From: AkaKaras Date: Mon, 12 Oct 2015 16:54:14 -0400 Subject: [PATCH 2/8] zh-hant latest translation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit finally finished the translation, proud of myself (●—●) b --- app/locale/zh-HANT.coffee | 149 ++++++++------------------------------ 1 file changed, 32 insertions(+), 117 deletions(-) diff --git a/app/locale/zh-HANT.coffee b/app/locale/zh-HANT.coffee index f135fd6e2..4193e8fdf 100644 --- a/app/locale/zh-HANT.coffee +++ b/app/locale/zh-HANT.coffee @@ -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,13 +835,13 @@ module.exports = nativeDescription: "繁體中文", englishDescription: "Chinese last_played: "最後玩了" leagues_explanation: "在部落裡與其他成員組成聯盟一起參加下面的多人競技場。" -<<<<<<< HEAD + courses: course: "課程" courses: "課程" - not_enrolled: "您没有註冊這一節課" + not_enrolled: "您没有註冊這一節課。" visit_pref: "請到這個" - visit_suf: "網頁註冊" + visit_suf: "網頁註冊。" select_class: "請選其中一門課堂" unnamed: "*未命名*" select: "選擇" @@ -864,16 +864,20 @@ module.exports = nativeDescription: "繁體中文", englishDescription: "Chinese concepts: "課目" levels: "關卡" played: "已通關" - play_time: "遊戲時間:" - completed: "完成:" + play_time: "遊戲時間:" + completed: "遊戲時間:" invite_students: "邀請學生加入此課堂。" - enter_emails: "輸入學生電郵地址來邀請,每行一個。" + 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: "課堂名字" + title: "發送邀請" description: "描述" - languages_available: "Select programming languages available to the class:" + languages_available: "選擇編程語言:" all_lang: "所有編程語言" - show_progress: "向所有該課堂的人展示學生的進度" + show_progress: "向所有該課堂的人展示學生的進度" creating_class: "課堂創建中···" purchasing_course: "購買課程中···" buy_course: "購買課程" @@ -886,27 +890,27 @@ module.exports = nativeDescription: "繁體中文", englishDescription: "Chinese name_class: "命名您的課堂" displayed_course_page: "這將會在課程頁面顯示,可被修改。" buy: "購買" - purchasing_for: "You are purchasing a license for" + purchasing_for: "你正在購買許可證,課程為" creating_for: "您正在創建一個課程為" for: ",人數上限為" # Like in 'for 30 students' receive_code: "然後您會收到一個解鎖碼,把它分發給你的學生用來註冊你的課堂。" free_trial: "老師可免費試用!" - get_access: "to get individual access to all courses for evalutaion purposes." - questions: "Questions?" + get_access: "獲得個人使用權在評估目的下來使用所有課程。" + questions: "有疑問?" faq: "課程FAQ" question: "問:" # Like in 'Question' - question1: "What's the difference between these courses and the single player game?" + question1: "這些課程和單人遊戲之間的有什麼區別?" answer: "答:" # 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." + answer1: "單人遊戲是專為個人而設,而課程是專為課堂而設。" + answer2: "在單人遊戲中有物品、寶石、英雄選擇、練級、和內購應用。課程有課堂管理功能,和老師可根據學生的水平而調整教學進度。" teachers_click: "老師點擊這裡" students_click: "學生點擊這裡" courses_on_coco: "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." + designed_to: "CodeCombat課程的宗旨是在使用CodeCombat生動有趣的環境下教授計算機科學的課目。整個CodeCombat的關卡是圍繞著計算機科學的重點並激勵學生們自主向上學習在5小時的過程。" more_in_less: "以最少的時間學習最多的知識" no_experience: "無需編程經驗" - easy_monitor: "容易監控學生的進程" - purchase_for_class: "Purchase a course for your entire class. It's easy to sign up your students!" + easy_monitor: "容易管理學生的進程" + purchase_for_class: "為你的班級購買CodeCombat課程,讓簽到和管理變得更容易!" see_the: "詳細資訊請看" more_info: "。" choose_course: "選擇您的課程:" @@ -919,95 +923,6 @@ module.exports = nativeDescription: "繁體中文", englishDescription: "Chinese topics: "題目" hours_content: "內容時間:" get_free: "取得免費課程!" -======= -# 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" ->>>>>>> codecombat/master classes: archmage_title: "大法師" @@ -1293,7 +1208,7 @@ module.exports = nativeDescription: "繁體中文", englishDescription: "Chinese recently_played: "最近玩過" no_recent_games: "在過去兩個星期沒有玩過遊戲。" payments: "付款" -# prepaid: "Prepaid" + prepaid: "充值" purchased: "已購買" sale: "促銷" subscription: "訂閱" @@ -1324,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: "從伺服器載入失敗" @@ -1344,7 +1259,7 @@ module.exports = nativeDescription: "繁體中文", englishDescription: "Chinese bad_input: "錯誤輸入。" server_error: "伺服器錯誤。" unknown: "未知錯誤。" -# error: "ERROR" + error: "錯誤" resources: sessions: "會話" From 708f78153bf1966ce6a1fbe5ba24b85aefc61e94 Mon Sep 17 00:00:00 2001 From: Matt Lott Date: Mon, 12 Oct 2015 16:25:23 -0700 Subject: [PATCH 3/8] Route /hoc to courses When in HoC mode: Students have an option to play through as a single player, in addition to entering a code to join an existing class. Teachers get the normal course enrollment flow, without a sales blurb on /courses. --- app/core/Router.coffee | 2 ++ app/locale/en.coffee | 2 +- app/styles/courses/courses.sass | 3 -- app/templates/courses/courses.jade | 48 ++++++++++++++++++++------ app/views/courses/CoursesView.coffee | 51 ++++++++++++++++++++++++++++ 5 files changed, 92 insertions(+), 14 deletions(-) diff --git a/app/core/Router.coffee b/app/core/Router.coffee index 29ce8a800..f9fe031e7 100644 --- a/app/core/Router.coffee +++ b/app/core/Router.coffee @@ -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') diff --git a/app/locale/en.coffee b/app/locale/en.coffee index d114391d5..04e41671f 100644 --- a/app/locale/en.coffee +++ b/app/locale/en.coffee @@ -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" diff --git a/app/styles/courses/courses.sass b/app/styles/courses/courses.sass index b1d53ca05..30c9d4192 100644 --- a/app/styles/courses/courses.sass +++ b/app/styles/courses/courses.sass @@ -25,9 +25,6 @@ margin-bottom: 20px font-size: 14pt - .btn-enroll - margin-top: 20px - .center text-align: center diff --git a/app/templates/courses/courses.jade b/app/templates/courses/courses.jade index 4d740ada5..f41522b23 100644 --- a/app/templates/courses/courses.jade +++ b/app/templates/courses/courses.jade @@ -15,24 +15,44 @@ block content 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(data-i18n="courses.courses_on_coco") + mixin teacher-main button.btn.btn-warning.btn-student(data-i18n="courses.students_click") h1.center(data-i18n="courses.courses_on_coco") @@ -87,6 +107,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}") diff --git a/app/views/courses/CoursesView.coffee b/app/views/courses/CoursesView.coffee index 7718f13da..2ebd5a53c 100644 --- a/app/views/courses/CoursesView.coffee +++ b/app/views/courses/CoursesView.coffee @@ -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 From 2aad347166b9dde3be830cff9f3c5d8a7a579f3b Mon Sep 17 00:00:00 2001 From: Matt Lott Date: Mon, 12 Oct 2015 16:38:28 -0700 Subject: [PATCH 4/8] Add HoC blurb to top of /teachers pages Replacing courses beta testing blurb. --- app/templates/teachers.jade | 29 ++++++++--------------------- 1 file changed, 8 insertions(+), 21 deletions(-) diff --git a/app/templates/teachers.jade b/app/templates/teachers.jade index d5d1ccc7a..9d058ea1c 100644 --- a/app/templates/teachers.jade +++ b/app/templates/teachers.jade @@ -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 From e1e2be574cdccfe9a360fc849e67743f9f9380ae Mon Sep 17 00:00:00 2001 From: Matt Lott Date: Mon, 12 Oct 2015 16:43:46 -0700 Subject: [PATCH 5/8] Add teacher HoC instructions to /courses --- app/templates/courses/courses.jade | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/app/templates/courses/courses.jade b/app/templates/courses/courses.jade index f41522b23..425585e4b 100644 --- a/app/templates/courses/courses.jade +++ b/app/templates/courses/courses.jade @@ -51,7 +51,17 @@ mixin student-main mixin teacher-hoc button.btn.btn-warning.btn-student(data-i18n="courses.students_click") - h1.center(data-i18n="courses.courses_on_coco") + 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") From b38a6c5060bec391236bca2a1f1905fd8246d302 Mon Sep 17 00:00:00 2001 From: Scott Erickson Date: Mon, 12 Oct 2015 16:46:30 -0700 Subject: [PATCH 6/8] Pass all arguments from constructor to initialize in CoreViews, add lodash to default view context --- app/views/core/CocoView.coffee | 3 ++- app/views/core/ModalView.coffee | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/app/views/core/CocoView.coffee b/app/views/core/CocoView.coffee index b313a9f46..f63cf6e5a 100644 --- a/app/views/core/CocoView.coffee +++ b/app/views/core/CocoView.coffee @@ -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: -> diff --git a/app/views/core/ModalView.coffee b/app/views/core/ModalView.coffee index 0a5b8afc3..801563fa5 100644 --- a/app/views/core/ModalView.coffee +++ b/app/views/core/ModalView.coffee @@ -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) From 919e0605e956dc7ee9e68a0a78af0c7102304330 Mon Sep 17 00:00:00 2001 From: Scott Erickson Date: Mon, 12 Oct 2015 16:47:48 -0700 Subject: [PATCH 7/8] Add spriteSheets to ThangType, export modal to Thang Editor Units can be exported as rastered sprite sheets. This is the first part of the project, the second part will be having the game use them. --- app/lib/sprites/SpriteExporter.coffee | 98 +++++++++++++++++++ app/lib/surface/LayerAdapter.coffee | 25 +---- app/models/ThangType.coffee | 30 ++++++ app/schemas/models/thang_type.coffee | 36 +++++++ .../editor/thang/export-thang-type-modal.jade | 34 +++++++ .../editor/thang/thang-type-edit-view.jade | 32 +++--- .../editor/thang/ExportThangTypeModal.coffee | 78 +++++++++++++++ .../editor/thang/ThangTypeEditView.coffee | 7 ++ .../levels/thangs/thang_type_handler.coffee | 1 + 9 files changed, 307 insertions(+), 34 deletions(-) create mode 100644 app/lib/sprites/SpriteExporter.coffee create mode 100644 app/templates/editor/thang/export-thang-type-modal.jade create mode 100644 app/views/editor/thang/ExportThangTypeModal.coffee diff --git a/app/lib/sprites/SpriteExporter.coffee b/app/lib/sprites/SpriteExporter.coffee new file mode 100644 index 000000000..bf872e6ef --- /dev/null +++ b/app/lib/sprites/SpriteExporter.coffee @@ -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 \ No newline at end of file diff --git a/app/lib/surface/LayerAdapter.coffee b/app/lib/surface/LayerAdapter.coffee index b295dcd01..aaeb083f0 100644 --- a/app/lib/surface/LayerAdapter.coffee +++ b/app/lib/surface/LayerAdapter.coffee @@ -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) -> diff --git a/app/models/ThangType.coffee b/app/models/ThangType.coffee index 4c309f41a..d02d5aa2b 100644 --- a/app/models/ThangType.coffee +++ b/app/models/ThangType.coffee @@ -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) diff --git a/app/schemas/models/thang_type.coffee b/app/schemas/models/thang_type.coffee index 5d4d9624e..58d018b61 100644 --- a/app/schemas/models/thang_type.coffee +++ b/app/schemas/models/thang_type.coffee @@ -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 = [] diff --git a/app/templates/editor/thang/export-thang-type-modal.jade b/app/templates/editor/thang/export-thang-type-modal.jade new file mode 100644 index 000000000..1af4a0cf4 --- /dev/null +++ b/app/templates/editor/thang/export-thang-type-modal.jade @@ -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 \ No newline at end of file diff --git a/app/templates/editor/thang/thang-type-edit-view.jade b/app/templates/editor/thang/thang-type-edit-view.jade index 2d9160f2f..d8a7afbd7 100644 --- a/app/templates/editor/thang/thang-type-edit-view.jade +++ b/app/templates/editor/thang/thang-type-edit-view.jade @@ -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 diff --git a/app/views/editor/thang/ExportThangTypeModal.coffee b/app/views/editor/thang/ExportThangTypeModal.coffee new file mode 100644 index 000000000..572b659e6 --- /dev/null +++ b/app/views/editor/thang/ExportThangTypeModal.coffee @@ -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 \ No newline at end of file diff --git a/app/views/editor/thang/ThangTypeEditView.coffee b/app/views/editor/thang/ThangTypeEditView.coffee index b79d3ff9a..e3d2c13d6 100644 --- a/app/views/editor/thang/ThangTypeEditView.coffee +++ b/app/views/editor/thang/ThangTypeEditView.coffee @@ -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() diff --git a/server/levels/thangs/thang_type_handler.coffee b/server/levels/thangs/thang_type_handler.coffee index 6ca514c47..3fd06e429 100644 --- a/server/levels/thangs/thang_type_handler.coffee +++ b/server/levels/thangs/thang_type_handler.coffee @@ -35,6 +35,7 @@ ThangTypeHandler = class ThangTypeHandler extends Handler 'unlockLevelName' 'tasks' 'terrains' + 'spriteSheets' ] hasAccess: (req) -> From eda10a55ec42aa6fc130cda8dd80cd053fa0c999 Mon Sep 17 00:00:00 2001 From: Matt Lott Date: Mon, 12 Oct 2015 21:13:01 -0700 Subject: [PATCH 8/8] Update /courses?_ppc= logged out instructions --- app/styles/courses/courses.sass | 20 ++++++++++++++++++++ app/templates/courses/courses.jade | 4 +++- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/app/styles/courses/courses.sass b/app/styles/courses/courses.sass index 30c9d4192..390d0fcea 100644 --- a/app/styles/courses/courses.sass +++ b/app/styles/courses/courses.sass @@ -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 diff --git a/app/templates/courses/courses.jade b/app/templates/courses/courses.jade index 425585e4b..e34a571bc 100644 --- a/app/templates/courses/courses.jade +++ b/app/templates/courses/courses.jade @@ -10,7 +10,9 @@ 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