Merged. Turn off chained rankings for now since they are slow and don't work anyway

This commit is contained in:
Nick Winter 2015-12-06 04:36:41 -08:00
commit f38eea3590
25 changed files with 290 additions and 203 deletions

View file

@ -3,7 +3,7 @@ sudo: false
language: node_js
node_js:
- 0.10
- 5.1.1
addons:
apt:

View file

@ -0,0 +1,23 @@
CocoCollection = require 'collections/CocoCollection'
Prepaid = require 'models/Prepaid'
sum = (numbers) -> _.reduce(numbers, (a, b) -> a + b)
module.exports = class Prepaids extends CocoCollection
model: Prepaid
url: "/db/prepaid"
totalMaxRedeemers: ->
sum((prepaid.get('maxRedeemers') for prepaid in @models)) or 0
totalRedeemers: ->
sum((_.size(prepaid.get('redeemers')) for prepaid in @models)) or 0
totalAvailable: -> Math.max(@totalMaxRedeemers() - @totalRedeemers(), 0)
fetchByCreator: (creatorID, opts) ->
opts ?= {}
opts.data ?= {}
opts.data.creator = creatorID
@fetch opts

View file

@ -79,6 +79,7 @@ module.exports = class Tracker
trackPageView: ->
name = Backbone.history.getFragment()
console.log "Would track analytics pageview: '/#{name}'" if debugAnalytics
@trackEventInternal 'Pageview', url: name unless me?.isAdmin() and @isProduction
return unless @isProduction and not me.isAdmin()
# Google Analytics

View file

@ -139,6 +139,8 @@ module.exports = class Simulator extends CocoClass
fetchAndSimulateTask: =>
return if @destroyed
# Because there's some bug where the chained rankings don't work, let's just do getTwoGames until we fix it.
return @fetchAndSimulateOneGame()
if @options.headlessClient
if @dumpThisTime # The first heapdump would be useless to find leaks.

View file

@ -480,10 +480,15 @@ module.exports = LayerAdapter = class LayerAdapter extends CocoClass
unless thangType.rasterImage
console.error("Cannot render the LayerAdapter SpriteSheet until the raster image for <#{thangType.get('name')}> is loaded.")
# hack for IE9, otherwise width/height are not set
$img = $(thangType.rasterImage[0])
$('body').append($img)
bm = new createjs.Bitmap(thangType.rasterImage[0])
scale = thangType.get('scale') or 1
frame = spriteSheetBuilder.addFrame(bm, null, scale)
spriteSheetBuilder.addAnimation(@renderGroupingKey(thangType), [frame], false)
$img.remove()
#- Distributing new Segmented/Singular/RasterSprites to Lanks

View file

@ -12,14 +12,14 @@ module.exports = nativeDescription: "Ελληνικά", englishDescription: "Gre
multiplayer: "Πολλαπλοί Παίκτες" # Not currently shown on home page
for_developers: "Για προγραμματιστές" # Not currently shown on home page.
or_ipad: "Ή κατεβάστε για iPad"
# hoc_class_code: "I Have a Class Code"
# hoc_enter: "Enter"
# hoc_title: "Hour of Code?"
hoc_class_code: "Έχω Κωδικό Τάξης "
hoc_enter: "Είσοδος"
hoc_title: "Ώρα του Κώδικα?"
nav:
play: "Επίπεδα" # The top nav bar entry where players choose which levels to play
community: "Κοινότητα"
# courses: "Courses"
courses: "Μαθήματα"
editor: "Συγγραφέας"
blog: "Ιστολόγιο"
forum: "Φόρουμ"
@ -54,7 +54,7 @@ module.exports = nativeDescription: "Ελληνικά", englishDescription: "Gre
play:
play_as: "Παίξτε ως" # Ladder page
# compete: "Compete!" # Course details page
compete: "Αγωνίσου!" # Course details page
spectate: "Θεατής" # Ladder page
players: "παίκτες" # Hover over a level on /play
hours_played: "ώρες παιχνιδιού" # Hover over a level on /play
@ -65,7 +65,7 @@ module.exports = nativeDescription: "Ελληνικά", englishDescription: "Gre
locked: "Κλειδωμένο"
purchasable: "Διαθέσιμο για αγορά" # For a hero you unlocked but haven't purchased
available: "Διαθέσιμο"
# skills_granted: "Skills Granted" # Property documentation details
skills_granted: "Παρεχόμενες Ικανότητες" # Property documentation details
heroes: "Ήρωες" # Tooltip on hero shop button from /play
achievements: "Επιτεύγματα" # Tooltip on achievement list button from /play
account: "Λογαριασμός" # Tooltip on account button from /play
@ -187,7 +187,7 @@ module.exports = nativeDescription: "Ελληνικά", englishDescription: "Gre
password: "Κωδικός"
message: "Μήνυμα"
code: "Κώδικας"
# ladder: "Ladder"
ladder: "Κατατάξη"
when: "Όταν"
opponent: "Αντίπαλος"
rank: "Κατηγορία"
@ -221,10 +221,10 @@ module.exports = nativeDescription: "Ελληνικά", englishDescription: "Gre
years: "χρόνια"
play_level:
# completed_level: "Completed Level:"
# course: "Course:"
completed_level: "Ολοκληρωμένο Επίπεδο:"
course: "Μάθημα:"
done: "Έτοιμο"
# next_level: "Next Level:"
next_level: "Επομένο Επίπεδο:"
next_game: "Επόμενο παιχνίδι"
show_menu: "Εμφάνιση μενού παιχνιδιού"
home: "Αρχική" # Not used any more, will be removed soon.
@ -254,7 +254,7 @@ module.exports = nativeDescription: "Ελληνικά", englishDescription: "Gre
victory_sign_up: "Εγγράψου για ενημερώσεις"
victory_sign_up_poke: "Θέλεις να λαμβάνεις τα τελευταία νέα μέσω e-mail; Δημιούργησε έναν δωρεάν λογαριασμό και θα σε κρατάμε ενήμερο!"
victory_rate_the_level: "Βαθμολογήστε το επίπεδο: " # {change}
victory_return_to_ladder: "Επιστροφή στη Σκάλα"
victory_return_to_ladder: "Επιστροφή στη Κατατάξη"
victory_play_continue: "Συνέχισε"
victory_saving_progress: "Αποθήκευση προόδου"
victory_go_home: "Πήγαινε στην Αρχική"
@ -262,8 +262,8 @@ module.exports = nativeDescription: "Ελληνικά", englishDescription: "Gre
victory_review_placeholder: "Πως σου φάνηκε το επίπεδο;"
victory_hour_of_code_done: "Τελείωσες;"
victory_hour_of_code_done_yes: "Ναι, έχω τελειώσει με την Hour of Code!"
victory_experience_gained: "Κερδισμενη Εμπειρία"
victory_gems_gained: "Κερδισμενα Πετραδια"
victory_experience_gained: "Εμπειρία"
victory_gems_gained: "Πετραδια"
victory_new_item: "Νέο αντικείμενο"
victory_viking_code_school: "Μπράβο σου, μόλις πέρασες ένα δύσκολο επίπεδο! Αν δεν είσαι ήδη προγραμματιστής, πρέπει να γίνεις. Είσαι σε καλό δρόμο για να γίνεις δεκτός στο Σχολείο Προγρααματισμού των Βίκινγκ, όπου μπορείς να πας τις ικανότητες στο επόμενο επίπεδο και να γίνεις ένας επαγγελματίας προγραμματιστής ιστού σε 14 weeks."
victory_become_a_viking: "Γίνε Βίκιγκ"
@ -371,11 +371,11 @@ module.exports = nativeDescription: "Ελληνικά", englishDescription: "Gre
leaderboard:
view_other_solutions: "Προβολή Πίνακα Πρωτοπόρων"
scores: "Σκορ"
top_players: "Κορυφαίοι Παίκτες Κατά"
day: "Ημέρα"
week: "Αυτήν την εβδομάδα"
all: "Παντοτινά"
time: "Χρόνος"
top_players: "Κορυφαιοι Παικτες "
day: "Ημερας"
week: "Εβδομαδας"
all: "Ολων των Εποχων"
time: "τη διαρκεια της"
damage_taken: "Ζημιά που δέχθηκες"
damage_dealt: "Ζημιά που αντιμετώπησες"
difficulty: "δυσκολία"
@ -621,10 +621,10 @@ module.exports = nativeDescription: "Ελληνικά", englishDescription: "Gre
cost_premium_server: "Το CodeCombat είναι δωρεάν για τα πέντε πρώτα επίπεδα, μετά τα οποία κοστίζει $9.99 Δολλάρια ΗΠΑ το μήνα για να έχετε πρόσβαση στα πάνω από 190 επίπεδα στους αποκλειστικούς για τη χώρα εξυπηρετητές."
free_1: "Υπάρχουν πάνω από 100 ΔΩΡΕΑΝ επίπεδα που καλύπτουν όλα τα αντικείμενα."
free_2: "Η μηνιαία συνδρομή παρέχει πρόσβαση σε βίντεο βοηθήματα και παραπάνω επίπεδα για εξάσκηση."
# free_3: "The CodeCombat content is divided into"
# free_4: "courses"
# free_5: ". The first course is free, and about an hour of material."
# free_6: "Access to the additional courses can be unlocked with a one-time purchase."
free_3: "Το περιεχομένο του CodeCombat χωρίζεται σε"
free_4: "μαθήματα"
free_5: ". Η πρώτη σειρά μαθημάτων είναι δωρεάν και περιέχει περίπου μια ώρα υλικού."
free_6: "Πρόσβαση σε πρόσθετα μαθημάτα μπορεί να αποκτηθεί με αγορά συνδρομής."
teacher_subs_title: "Οι εκπαιδευτικοί παίρνουν δωρεάν συνδρομές!" # {change}
teacher_subs_0: "Προσφέρουμε δωρεάν συνδρομές σε εκπαιδευτικούς για να μπορέσουν να αξιολογήσουν το παιχνίδι." # {change}
teacher_subs_1: "παρακαλούμε συμπληρώστε το"
@ -678,15 +678,15 @@ module.exports = nativeDescription: "Ελληνικά", englishDescription: "Gre
approved_1: "Η αίτησή σας για δωρεάν δοκιμαστική συνδρομή" # {change}
approved_2: "εγκρίθηκε."
approved_3: "Σας στείλαμε περισσότερες οδηγίες στο"
# approved_4: "Enroll your students on the"
# approved_5: "courses"
# approved_6: "page."
approved_4: "Γράψτε τους μάθητες σας στη"
approved_5: "μαθήματα"
approved_6: "σελίδα."
denied_1: "Η αίτησή σας για δωρεάν δοκιμαστική συνδρομή" # {change}
denied_2: "απορίφθηκε."
contact_1: "Παρακαλούμε επικοινωνείστε"
contact_2: "αν έχετε περαιτέρω ερωτήσεις."
description_1: "Δίνουμε δωρεάν συνδρομές σε εκπαιδευτικούς για να αξιολογήσουν το παιχνίδι. Μπορείτε να βρείτε περισσότερες πληροφορίες στις"
# description_1b: "You can find more information on our"
description_1b: "Μπορείτε να βρείτε περισσότερες πληροφορίες στη"
description_2: "εκπαιδευτική"
description_3: "σελίδα."
description_4: "Παρακαλούμε, συμπληρώστε αυτή τη φόρμα και θα σας στείλουμε οδηγίες."
@ -779,7 +779,7 @@ module.exports = nativeDescription: "Ελληνικά", englishDescription: "Gre
view_profile: "Προβολή του προφίλ σου"
keyboard_shortcuts:
keyboard_shortcuts: "Συντομεύσει πληκτρολογίοθ"
keyboard_shortcuts: "Συντομεύσεις πληκτρολογίου"
space: "Κενό"
enter: "Enter"
press_enter: "πατείστε enter"
@ -1289,9 +1289,9 @@ module.exports = nativeDescription: "Ελληνικά", englishDescription: "Gre
quantity: "Ποσότητα"
redeemed: "Εξαγοράστηκε"
no_codes: "No codes yet!"
# you_can1: "You can"
# you_can2: "purchase a prepaid code"
# you_can3: "that can be applied to your own account or given to others."
you_can1: "Μπορείτε"
you_can2: "να αγοράσετε ένα προπληρωμένο κωδικό"
you_can3: "που θα χρησιμοποιήσετε στο δικό σας λογαριασμό ή θα το δώσετε σε άλλους."
loading_error:
could_not_load: "Σφάλμα φόρτωσης από τον εξυπηρετητή"

View file

@ -19,7 +19,7 @@ module.exports = nativeDescription: "日本語", englishDescription: "Japanese",
nav:
play: "ゲームマップへ" # The top nav bar entry where players choose which levels to play
community: "コミュニティー"
# courses: "Courses"
courses: "コース"
editor: "レベルエディター"
blog: "ブログ"
forum: "掲示板"
@ -53,8 +53,8 @@ module.exports = nativeDescription: "日本語", englishDescription: "Japanese",
subscribe_as_diplomat: "外交官登録"
play:
play_as: "としてプレー" # Ladder page
# compete: "Compete!" # Course details page
play_as: "プレイモード" # Ladder page
compete: "コンプリート!" # Course details page
spectate: "観戦" # Ladder page
players: "プレイヤー" # Hover over a level on /play
hours_played: "プレイ時間" # Hover over a level on /play
@ -221,10 +221,10 @@ module.exports = nativeDescription: "日本語", englishDescription: "Japanese",
years: ""
play_level:
# completed_level: "Completed Level:"
# course: "Course:"
completed_level: "コンプリートレベル:"
course: "コース:"
done: "完了"
# next_level: "Next Level:"
next_level: "次のレベル:"
next_game: "次のゲーム"
show_menu: "ゲームメニューを見る"
home: "ホーム" # Not used any more, will be removed soon.
@ -418,7 +418,7 @@ module.exports = nativeDescription: "日本語", englishDescription: "Japanese",
feature7: "プライベート<strong>クラン</strong>"
free: "無料"
month: ""
# must_be_logged: "You must be logged in first. Please create an account or log in from the menu above."
must_be_logged: "まずログインしてください。 アカウントを作るかメニューの上からログインをお願いします。"
subscribe_title: "課金"
unsubscribe: "無課金"
confirm_unsubscribe: "課金の解約確認"
@ -606,12 +606,12 @@ module.exports = nativeDescription: "日本語", englishDescription: "Japanese",
jose_blurb: "テークオフ"
retrostyle_title: "イラスト"
retrostyle_blurb: "レトロスタイルのゲーム"
# rob_title: "Compiler Engineer"
# rob_blurb: "Codes things and stuff"
# josh_c_title: "Game Designer"
# josh_c_blurb: "Designs games"
# carlos_title: "Region Manager, Brazil"
# carlos_blurb: "Celery Man"
rob_title: "コンパイラーエンジニアー"
rob_blurb: "コードの道具と材料"
josh_c_title: "ゲームデザイナー"
josh_c_blurb: "ゲームのデザイン"
carlos_title: "地域部長, ブラジル"
carlos_blurb: "サラリーマン"
teachers:
more_info: "教育関係者へのお知らせ"
@ -1010,25 +1010,25 @@ module.exports = nativeDescription: "日本語", englishDescription: "Japanese",
thang_main: "メイン"
thang_spritesheets: "スプライトシート"
thang_colors: ""
# level_some_options: "Some Options?"
# level_tab_thangs: "Thangs"
# level_tab_scripts: "Scripts"
# level_tab_settings: "Settings"
# level_tab_components: "Components"
# level_tab_systems: "Systems"
# level_tab_docs: "Documentation"
# level_tab_thangs_title: "Current Thangs"
# level_tab_thangs_all: "All"
# level_tab_thangs_conditions: "Starting Conditions"
# level_tab_thangs_add: "Add Thangs"
# level_tab_thangs_search: "Search thangs"
# add_components: "Add Components"
# component_configs: "Component Configurations"
# config_thang: "Double click to configure a thang"
# delete: "Delete"
# duplicate: "Duplicate"
# stop_duplicate: "Stop Duplicate"
# rotate: "Rotate"
level_some_options: "オプションを付けますか?"
level_tab_thangs: "サング"
level_tab_scripts: "スクリプト"
level_tab_settings: "セッティング"
level_tab_components: "コンポーネント"
level_tab_systems: "システム"
level_tab_docs: "文章"
level_tab_thangs_title: "現在のサング"
level_tab_thangs_all: "すべて"
level_tab_thangs_conditions: "コンディションの開始"
level_tab_thangs_add: "サングの追加"
level_tab_thangs_search: "サングの検索"
add_components: "コンポーネントの追加"
component_configs: "コンポーネントの設定"
config_thang: "ダブルクリックでサングの設定"
delete: "削除"
duplicate: "重複"
stop_duplicate: "重複をやめる"
rotate: "回転"
# level_settings_title: "Settings"
# level_component_tab_title: "Current Components"
# level_component_btn_new: "Create New Component"
@ -1070,9 +1070,9 @@ module.exports = nativeDescription: "日本語", englishDescription: "Japanese",
# add_system_title: "Add Systems to Level"
# done_adding: "Done Adding"
# article:
# edit_btn_preview: "Preview"
# edit_article_title: "Edit Article"
article:
edit_btn_preview: "プレビュー"
edit_article_title: "アーティクルの設定"
polls:
priority: "プライオリティ"

View file

@ -1,11 +1,15 @@
#classroom-view
.enable-btn
margin: 1px
#main-button-area
.btn
margin-left: 10px
#student-stats-row
margin-bottom: 20px
.progress
margin-bottom: 5px
@ -14,4 +18,4 @@
.progress-bar-default
background-color: white
color: grey
color: grey

View file

@ -39,6 +39,10 @@ block modal-body-content
- var paid = user.get('coursePrepaidID')
input(type="checkbox", disabled=paid, checked=true, data-user-id=user.id, name='user')
span.spr= user.broadName()
if paid
span (
span already enrolled
span )
#error-alert.alert.alert-danger.hide
@ -61,7 +65,7 @@ block modal-body-content
| )
p
button#activate-licenses-btn.btn.btn-success.text-uppercase(type="submit") Activate Licenses
button#activate-licenses-btn.btn.btn-success.text-uppercase(type="submit") Enroll Students
p
a#get-more-licenses-btn.btn.btn-info.text-uppercase(href="/courses/purchase") Get More Licenses

View file

@ -2,109 +2,119 @@ extends /templates/base
block content
- var isOwner = view.classroom.get('ownerID') === me.id;
- var isOwner = view.classroom ? view.classroom.get('ownerID') === me.id : false;
if isOwner
a(href="/courses/teachers") Back to my classrooms
else
a(href="/courses") Back to my courses
h1
span.spr= view.classroom.get('name')
if isOwner
a#edit-class-details-link
small Edit class details
if view.classroom.get('description')
p= view.classroom.get('description')
h3(data-i18n="courses.stats")
table.progress-stats-container
- var stats = view.classStats()
tr
td(data-i18n="courses.total_students")
td= _.size(view.classroom.get('members'))
tr
td(data-i18n="courses.average_time")
td= stats.averagePlaytime
tr
td(data-i18n="courses.total_time")
td= stats.totalPlaytime
tr
td(data-i18n="courses.average_levels")
td= stats.averageLevelsComplete
tr
td(data-i18n="courses.total_levels")
td= stats.totalLevelsComplete
h1
| Students
if view.teacherMode
.pull-right#main-button-area
button#add-students-btn.btn.btn-success Add Students
button#activate-licenses-btn.btn.btn-warning Activate Licenses
a.btn.btn-warning(href="/courses/purchase?from-classroom="+view.classroom.id) Purchase Licenses
hr
for user in view.users.models
if view.teacherMode
a.remove-student-link.pull-right.text-uppercase(data-user-id=user.id)
span.glyphicon.glyphicon-remove
span.spl remove student
h2= user.broadName()
- var lastPlayedString = view.userLastPlayedString(user);
- var playtime = view.userPlaytimeString(user);
if lastPlayedString || playtime
#student-stats-row.row
if lastPlayedString
.col-sm-6 Last Played: #{lastPlayedString}
if playtime
.col-sm-6 Playtime: #{playtime}
- var paidFor = user.get('coursePrepaidID');
for courseInstance in view.courseInstances.models
- var inCourse = _.contains(courseInstance.get('members'), user.id);
if !(inCourse || view.teacherMode)
- continue;
- var course = view.courses.get(courseInstance.get('courseID'));
- var campaign = view.campaigns.get(course.get('campaignID'));
- var sessions = courseInstance.sessionsByUser[user.id] || [];
if !(course.get('free') || paidFor)
- continue;
.row
.col-sm-3.text-right= campaign.get('fullName')
.col-sm-9
if inCourse
- var levels = campaign.get('levels');
- var numLevels = Object.keys(levels).length;
- var sessionMap = _.zipObject(_.map(sessions, function(s) { return s.get('level').original; }), sessions);
- var levelCellWidth = 100.00;
if numLevels > 0
- levelCellWidth = 100.00 / numLevels;
- var css = "width:"+levelCellWidth+"%;"
- var i = 0;
.progress
each level, levelID in campaign.get('levels')
- i++
- var session = sessionMap[levelID];
a(href=view.getLevelURL(level, course, courseInstance, session))
- var content = view.levelPopoverContent(level, session, i);
if session && session.get('state') && session.get('state').complete
.progress-bar.progress-bar-success(style=css, data-content=content, data-toggle='popover')= i
else if session
.progress-bar.progress-bar-warning(style=css, data-content=content, data-toggle='popover')= i
else
.progress-bar.progress-bar-default(style=css, data-content=content, data-toggle='popover')= i
else if paidFor
button.enable-btn.btn.btn-info.btn-sm(data-user-id=user.id, data-course-instance-cid=courseInstance.cid) Enable
if view.teacherMode && !paidFor
.text-center
p
em Activate a license to enable more courses for this student.
p
button.activate-single-license-btn.btn.btn-info.btn-sm(data-user-id=user.id) Activate
if !me.isAnonymous()
h1
span.spr= view.classroom.get('name')
if isOwner
a#edit-class-details-link
small Edit class details
if view.classroom.get('description')
p= view.classroom.get('description')
h3(data-i18n="courses.stats")
table.progress-stats-container
- var stats = view.classStats()
tr
td(data-i18n="courses.total_students")
td
span.spr= _.size(view.classroom.get('members'))
span (
span.spr enrolled in paid courses:
span= stats.enrolledUsers
span )
tr
td(data-i18n="courses.average_time")
td= stats.averagePlaytime
tr
td(data-i18n="courses.total_time")
td= stats.totalPlaytime
tr
td(data-i18n="courses.average_levels")
td= stats.averageLevelsComplete
tr
td(data-i18n="courses.total_levels")
td= stats.totalLevelsComplete
h1
| Students
if view.teacherMode
.pull-right#main-button-area
button#add-students-btn.btn.btn-primary.text-uppercase Add Students
button#activate-licenses-btn.btn.btn-info.text-uppercase Enroll Students in Paid Courses
a.btn.btn-success.text-uppercase(href="/courses/purchase?from-classroom="+view.classroom.id) Purchase Enrollments
hr
for user in view.users.models
if view.teacherMode
a.remove-student-link.pull-right.text-uppercase(data-user-id=user.id)
span.glyphicon.glyphicon-remove
span.spl remove student
h2= user.broadName()
- var lastPlayedString = view.userLastPlayedString(user);
- var playtime = view.userPlaytimeString(user);
if lastPlayedString || playtime
#student-stats-row.row
if lastPlayedString
.col-sm-6 Last Played: #{lastPlayedString}
if playtime
.col-sm-6 Playtime: #{playtime}
- var paidFor = user.get('coursePrepaidID');
for courseInstance in view.courseInstances.models
- var inCourse = _.contains(courseInstance.get('members'), user.id);
if !(inCourse || view.teacherMode)
- continue;
- var course = view.courses.get(courseInstance.get('courseID'));
- var campaign = view.campaigns.get(course.get('campaignID'));
- var sessions = courseInstance.sessionsByUser[user.id] || [];
if !(course.get('free') || paidFor)
- continue;
if inCourse
.row
.col-sm-3.text-right= course.get('name')
.col-sm-9
if inCourse
- var levels = campaign.get('levels');
- var numLevels = Object.keys(levels).length;
- var sessionMap = _.zipObject(_.map(sessions, function(s) { return s.get('level').original; }), sessions);
- var levelCellWidth = 100.00;
if numLevels > 0
- levelCellWidth = 100.00 / numLevels;
- var css = "width:"+levelCellWidth+"%;"
- var i = 0;
.progress
each level, levelID in campaign.get('levels')
- i++
- var session = sessionMap[levelID];
a(href=view.getLevelURL(level, course, courseInstance, session))
- var content = view.levelPopoverContent(level, session, i);
if session && session.get('state') && session.get('state').complete
.progress-bar.progress-bar-success(style=css, data-content=content, data-toggle='popover')= i
else if session
.progress-bar.progress-bar-warning(style=css, data-content=content, data-toggle='popover')= i
else
.progress-bar.progress-bar-default(style=css, data-content=content, data-toggle='popover')= i
else if paidFor
.text-center
button.enable-btn.btn.btn-info.btn-sm.text-uppercase(data-user-id=user.id, data-course-instance-cid=courseInstance.cid) Assign #{course.get('name')}
if view.teacherMode && !paidFor
.text-center
p
em
span.spr Enroll
strong.spr= user.broadName()
span to assign paid courses.
p
button.activate-single-license-btn.btn.btn-info.btn-sm.text-uppercase(data-user-id=user.id) Enroll #{user.broadName()}
hr

View file

@ -25,7 +25,7 @@ block content
.alert.alert-danger= view.stateMessage
p.text-center
strong How many enrollments do you need?
strong How many additional paid enrollments do you need?
br
p.text-center
@ -38,7 +38,9 @@ block content
.container-fluid
.row
.col-md-offset-3.col-md-6 One enrollment per student is required to assign them to paid CodeCombat courses. A single student does not need multiple enrollments to access all paid courses.
.col-md-offset-3.col-md-6
| Each student in a class will get access to Course 2 and up once they are enrolled in paid courses.
| You may assign each course to each student individually.
br
p.text-center#price-form-group

View file

@ -54,6 +54,18 @@ block content
br
.section-header Available Courses
if !me.isAnonymous()
p.text-center
strong.spr Unused enrollments available:
strong.spr= view.prepaids.totalAvailable()
a.btn.btn-success.btn(href="/courses/purchase") Purchase Enrollments
p
| All students get access to Introduction to Computer Science for free.
| One enrollment per student is required to assign them to paid CodeCombat courses.
| A single student does not need multiple enrollments to access all paid courses.
.container-fluid
- var courses = view.courses.models;
- var i = 0;

View file

@ -11,13 +11,13 @@ p.simulation-count
span.spr(data-i18n="ladder.games_simulated_by") Games simulated by you:
span#simulated-by-you= me.get('simulatedBy') || 0
p.simulation-count
span.spr(data-i18n="ladder.games_simulated_for") Games simulated for you:
span#simulated-for-you= me.get('simulatedFor') || 0
//p.simulation-count
// span.spr(data-i18n="ladder.games_simulated_for") Games simulated for you:
// span#simulated-for-you= me.get('simulatedFor') || 0
p.simulation-count
span.spr(data-i18n="ladder.games_in_queue") Games currently in the queue:
span#games-in-queue= numberOfGamesInQueue || 0
//p.simulation-count
// span.spr(data-i18n="ladder.games_in_queue") Games currently in the queue:
// span#games-in-queue= numberOfGamesInQueue || 0
table.table.table-bordered.table-condensed.table-hover
thead
@ -25,8 +25,8 @@ table.table.table-bordered.table-condensed.table-hover
th
th(data-i18n="general.player").name-col-cell Player
th(data-i18n="ladder.games_simulated") Games simulated
th(data-i18n="ladder.games_played") Games played
th(data-i18n="ladder.ratio") Ratio
//th(data-i18n="ladder.games_played") Games played
//th(data-i18n="ladder.ratio") Ratio
tbody
- var topSimulators = simulatorsLeaderboardData.topSimulators.models;
- var showJustTop = simulatorsLeaderboardData.inTopSimulators() || me.get('anonymous');
@ -37,8 +37,8 @@ table.table.table-bordered.table-condensed.table-hover
td.simulator-leaderboard-cell= rank + 1
td.name-col-cell= user.get('name') || "Anonymous"
td.simulator-leaderboard-cell= user.get('simulatedBy')
td.simulator-leaderboard-cell= user.get('simulatedFor')
td.simulator-leaderboard-cell= Math.round((user.get('simulatedBy') / user.get('simulatedFor')) * 10) / 10
//td.simulator-leaderboard-cell= user.get('simulatedFor')
//td.simulator-leaderboard-cell= Math.round((user.get('simulatedBy') / user.get('simulatedFor')) * 10) / 10
if !showJustTop && simulatorsLeaderboardData.nearbySimulators().length
tr(class="active")
@ -50,5 +50,5 @@ table.table.table-bordered.table-condensed.table-hover
td.simulator-leaderboard-cell= user.rank
td.name-col-cell= user.get('name') || "Anonymous"
td.simulator-leaderboard-cell= user.get('simulatedBy')
td.simulator-leaderboard-cell= user.get('simulatedFor')
td.simulator-leaderboard-cell= _.isNaN(ratio) || ratio == Infinity ? '' : ratio.toFixed(1)
//td.simulator-leaderboard-cell= user.get('simulatedFor')
//td.simulator-leaderboard-cell= _.isNaN(ratio) || ratio == Infinity ? '' : ratio.toFixed(1)

View file

@ -111,7 +111,7 @@ module.exports = class DiplomatView extends ContributeClassView
cs: ['Martin005', 'Gygram', 'vanous'] # čeština, Czech
sv: ['iamhj', 'Galaky'] # Svenska, Swedish
id: ['mlewisno-oberlin'] # Bahasa Indonesia, Indonesian
el: ['Stergios'] # ελληνικά, Greek
el: ['Stergios','micman','zsdregas'] # ελληνικά, Greek
ro: [] # limba română, Romanian
vi: ['An Nguyen Hoang Thien'] # Tiếng Việt, Vietnamese
hu: ['Anon', 'atlantisguru', 'bbeasmile', 'csuvsaregal', 'divaDseidnA', 'ferpeter', 'kinez'] # magyar, Hungarian

View file

@ -1,7 +1,7 @@
ModalView = require 'views/core/ModalView'
template = require 'templates/courses/activate-licenses-modal'
CocoCollection = require 'collections/CocoCollection'
Prepaid = require 'models/Prepaid'
Prepaids = require 'collections/Prepaids'
User = require 'models/User'
module.exports = class ActivateLicensesModal extends ModalView
@ -16,12 +16,10 @@ module.exports = class ActivateLicensesModal extends ModalView
@classroom = options.classroom
@users = options.users
@user = options.user
@prepaids = new CocoCollection([], { url: "/db/prepaid", model: Prepaid })
sum = (numbers) -> _.reduce(numbers, (a, b) -> a + b)
@prepaids.totalMaxRedeemers = -> sum((prepaid.get('maxRedeemers') for prepaid in @models)) or 0
@prepaids.totalRedeemers = -> sum((_.size(prepaid.get('redeemers')) for prepaid in @models)) or 0
@prepaids = new Prepaids()
@prepaids.comparator = '_id'
@supermodel.loadCollection(@prepaids, 'prepaids', {data: {creator: me.id}})
@prepaids.fetchByCreator(me.id)
@supermodel.loadCollection(@prepaids, 'prepaids')
afterRender: ->
super()
@ -39,7 +37,7 @@ module.exports = class ActivateLicensesModal extends ModalView
depleted = remaining < 0
@$('#not-depleted-span').toggleClass('hide', depleted)
@$('#depleted-span').toggleClass('hide', !depleted)
@$('#activate-licenses-btn').toggleClass('disabled', depleted)
@$('#activate-licenses-btn').toggleClass('disabled', depleted).toggleClass('btn-success', not depleted).toggleClass('btn-default', depleted)
showProgress: ->
@$('#submit-form-area').addClass('hide')

View file

@ -11,6 +11,10 @@ module.exports = class AddLevelSystemModal extends ModalView
initialize: (options) ->
@classroom = options.classroom
if @classroom
application.tracker?.trackEvent 'Classroom started edit settings', category: 'Courses', classroomID: @classroom.id
else
application.tracker?.trackEvent 'Create new class', category: 'Courses'
onClickSaveSettingsButton: ->
name = $('.settings-name-input').val()

View file

@ -29,6 +29,7 @@ module.exports = class ClassroomView extends RootView
'click .remove-student-link': 'onClickRemoveStudentLink'
initialize: (options, classroomID) ->
return if me.isAnonymous()
@classroom = new Classroom({_id: classroomID})
@supermodel.loadModel @classroom, 'classroom'
@courses = new CocoCollection([], { url: "/db/course", model: Course})
@ -99,6 +100,7 @@ module.exports = class ClassroomView extends RootView
})
@openModalView(modal)
modal.once 'redeem-users', -> document.location.reload()
application.tracker?.trackEvent 'Classroom started enroll students', category: 'Courses'
onClickActivateSingleLicenseButton: (e) ->
userID = $(e.target).data('user-id')
@ -110,6 +112,7 @@ module.exports = class ClassroomView extends RootView
})
@openModalView(modal)
modal.once 'redeem-users', -> document.location.reload()
application.tracker?.trackEvent 'Classroom started enroll student', category: 'Courses', userID: userID
onClickEditClassDetailsLink: ->
modal = new ClassroomSettingsModal({classroom: @classroom})
@ -144,16 +147,21 @@ module.exports = class ClassroomView extends RootView
completeSessions = @sessions.filter (s) -> s.get('state')?.complete
stats.averageLevelsComplete = if @users.size() then (_.size(completeSessions) / @users.size()).toFixed(1) else 'N/A'
stats.totalLevelsComplete = _.size(completeSessions)
enrolledUsers = @users.filter (user) -> user.get('coursePrepaidID')
stats.enrolledUsers = _.size(enrolledUsers)
return stats
onClickAddStudentsButton: (e) ->
modal = new InviteToClassroomModal({classroom: @classroom})
@openModalView(modal)
application.tracker?.trackEvent 'Classroom started add students', category: 'Courses', classroomID: @classroom.id
onClickEnableButton: (e) ->
courseInstance = @courseInstances.get($(e.target).data('course-instance-cid'))
userID = $(e.target).data('user-id')
$(e.target).attr('disabled', true)
application.tracker?.trackEvent 'Course assign student', category: 'Courses', courseInstanceID: courseInstance.id, userID: userID
onCourseInstanceCreated = =>
courseInstance.addMember(userID)
@ -179,6 +187,7 @@ module.exports = class ClassroomView extends RootView
onStudentRemoved: (e) ->
@users.remove(e.user)
@render()
application.tracker?.trackEvent 'Classroom removed student', category: 'Courses', courseInstanceID: @courseInstance.id, userID: e.user.id
levelPopoverContent: (level, session, i) ->
return null unless level

View file

@ -199,8 +199,8 @@ module.exports = class CourseDetailsView extends RootView
@loadAllCourses()
onClickPlayLevel: (e) ->
levelSlug = $(e.target).data('level-slug')
levelID = $(e.target).data('level-id')
levelSlug = $(e.target).closest('.btn-play-level').data('level-slug')
levelID = $(e.target).closest('.btn-play-level').data('level-id')
level = @campaign.get('levels')[levelID]
if level.type is 'course-ladder'
viewClass = 'views/ladder/LadderView'

View file

@ -139,6 +139,7 @@ module.exports = class CoursesView extends RootView
classroomName: newClassroom.get('name')
ownerID: newClassroom.get('ownerID')
}
location.search = '' # Prevent another student from logging in later with the _cc query param in place
@classrooms.add(newClassroom)
@render()
@classroomJustAdded = newClassroom.id

View file

@ -28,7 +28,7 @@ module.exports = class HourOfCodeView extends RootView
@courseInstances = new CocoCollection([], { url: "/db/user/#{me.id}/course_instances", model: CourseInstance})
@listenToOnce @courseInstances, 'sync', @onCourseInstancesLoaded
@courseInstances.comparator = (ci) -> return ci.get('classroomID') + ci.get('courseID')
@supermodel.loadCollection(@courseInstances, 'course_instances')
@supermodel.loadCollection(@courseInstances, 'course_instances', { cache: false })
onCourseInstancesLoaded: ->
@hourOfCodeCourseInstance = @courseInstances.findWhere({hourOfCode: true})
@ -39,7 +39,7 @@ module.exports = class HourOfCodeView extends RootView
})
@sessions.comparator = 'created'
@listenTo @sessions, 'sync', @onSessionsLoaded
@supermodel.loadCollection(@sessions, 'sessions')
@supermodel.loadCollection(@sessions, 'sessions', { cache: false })
onSessionsLoaded: ->
@lastSession = @sessions.last()
@ -88,10 +88,10 @@ module.exports = class HourOfCodeView extends RootView
@$('#main-content').hide()
@$('#begin-hoc-area').removeClass('hide')
hocCourseInstance = new CourseInstance()
hocCourseInstance.upsertForHOC()
hocCourseInstance.upsertForHOC({cache: false})
@listenToOnce hocCourseInstance, 'sync', ->
url = hocCourseInstance.firstLevelURL()
app.router.navigate(url, { trigger: true })
document.location.href = url
onClickLogInButton: ->
modal = new StudentLogInModal()

View file

@ -4,7 +4,7 @@ template = require 'templates/courses/invite-to-classroom-modal'
module.exports = class InviteToClassroomModal extends ModalView
id: 'invite-to-classroom-modal'
template: template
events:
'click #send-invites-btn': 'onClickSendInvitesButton'
'click #copy-url-btn, #join-url-input': 'copyURL'
@ -23,6 +23,7 @@ module.exports = class InviteToClassroomModal extends ModalView
url = @classroom.url() + '/invite-members'
@$('#send-invites-btn, #invite-emails-textarea').addClass('hide')
@$('#invite-emails-sending-alert').removeClass('hide')
application.tracker?.trackEvent 'Classroom invite via email', category: 'Courses', classroomID: @classroom.id, emails: emails
$.ajax({
url: url
@ -39,6 +40,7 @@ module.exports = class InviteToClassroomModal extends ModalView
try
document.execCommand('copy')
@$('#copied-alert').removeClass('hide')
application.tracker?.trackEvent 'Classroom copy URL', category: 'Courses', classroomID: @classroom.id, url: @joinURL
catch err
console.log('Oops, unable to copy', err)
@$('#copy-failed-alert').removeClass('hide')

View file

@ -10,6 +10,7 @@ CourseInstance = require 'models/CourseInstance'
RootView = require 'views/core/RootView'
template = require 'templates/courses/teacher-courses-view'
ClassroomSettingsModal = require 'views/courses/ClassroomSettingsModal'
Prepaids = require 'collections/Prepaids'
module.exports = class TeacherCoursesView extends RootView
id: 'teacher-courses-view'
@ -32,6 +33,11 @@ module.exports = class TeacherCoursesView extends RootView
@courseInstances.comparator = 'courseID'
@courseInstances.sliceWithMembers = -> return @filter (courseInstance) -> _.size(courseInstance.get('members')) and courseInstance.get('classroomID')
@supermodel.loadCollection(@courseInstances, 'course_instances', {data: {ownerID: me.id}})
@prepaids = new Prepaids()
@prepaids.comparator = '_id'
if not me.isAnonymous()
@prepaids.fetchByCreator(me.id)
@supermodel.loadCollection(@prepaids, 'prepaids') # just registers
@members = new CocoCollection([], { model: User })
@listenTo @members, 'sync', @render
@
@ -51,6 +57,7 @@ module.exports = class TeacherCoursesView extends RootView
return
modal = new InviteToClassroomModal({classroom: classroom})
@openModalView(modal)
application.tracker?.trackEvent 'Classroom started add students', category: 'Courses', classroomID: classroom.id
onClickCreateNewClassButton: ->
return @openModalView new AuthModal() if me.get('anonymous')
@ -70,7 +77,7 @@ module.exports = class TeacherCoursesView extends RootView
modal = new ClassroomSettingsModal({classroom: classroom})
@openModalView(modal)
@listenToOnce modal, 'hide', @render
onLoaded: ->
super()
@addFreeCourseInstances()
@ -92,4 +99,4 @@ module.exports = class TeacherCoursesView extends RootView
courseInstance.save(null, {validate: false})
@courseInstances.add(courseInstance)
@listenToOnce courseInstance, 'sync', @addFreeCourseInstances
return
return

View file

@ -69,6 +69,7 @@
"moment": "~2.5.0",
"mongodb": "^2.0.28",
"mongoose": "3.8.x",
"mongoose": "^4.2.9",
"mongoose-cache": "https://github.com/nwinter/mongoose-cache/tarball/master",
"node-force-domain": "~0.1.0",
"node-gyp": "~0.13.0",
@ -125,7 +126,7 @@
"license": "MIT for the code, and CC-BY for the art and music",
"private": true,
"engines": {
"node": "0.10.x",
"node": "^5.1.1",
"npm": "^3.0.0"
}
}

View file

@ -64,6 +64,7 @@ LevelSessionSchema.pre 'save', (next) ->
log.error err if err?
oldCopy = user.toObject()
oldCopy.stats = _.clone oldCopy.stats
oldCopy.stats ?= {gamesCompleted: 0}
--oldCopy.stats.gamesCompleted
oldCopy.stats.concepts ?= {}
for concept in level?.concepts ? []

View file

@ -18,8 +18,9 @@ module.exports = createNewTask = (req, res) ->
fetchAndVerifyLevelType.bind(yetiGuru, currentLevelID)
fetchSessionObjectToSubmit.bind(yetiGuru, requestSessionID)
updateSessionToSubmit.bind(yetiGuru, transpiledCode, req.user)
fetchInitialSessionsToRankAgainst.bind(yetiGuru, requestLevelMajorVersion, originalLevelID)
generateAndSendTaskPairsToTheQueue
# Because there's some bug where the chained rankings don't work, and this is super slow, let's just not do this until we fix it.
#fetchInitialSessionsToRankAgainst.bind(yetiGuru, requestLevelMajorVersion, originalLevelID)
#generateAndSendTaskPairsToTheQueue
], (err, successMessageObject) ->
if err? then return errors.serverError res, "There was an error submitting the game to the queue: #{err}"
scoringUtils.sendResponseObject res, successMessageObject