Merge hero selector and misc fixes

This commit is contained in:
phoenixeliot 2016-06-06 11:47:53 -07:00
commit 79de50126d
29 changed files with 503 additions and 146 deletions

View file

@ -3,4 +3,9 @@ ThangType = require 'models/ThangType'
module.exports = class ThangTypeCollection extends CocoCollection module.exports = class ThangTypeCollection extends CocoCollection
url: '/db/thang.type' url: '/db/thang.type'
model: ThangType model: ThangType
fetchHeroes: ->
@fetch {
url: '/db/thang.type?view=heroes'
}

View file

@ -168,6 +168,13 @@ module.exports = class LevelLoader extends CocoClass
@consolidateFlagHistory() if @opponentSession?.loaded @consolidateFlagHistory() if @opponentSession?.loaded
else if session is @opponentSession else if session is @opponentSession
@consolidateFlagHistory() if @session.loaded @consolidateFlagHistory() if @session.loaded
if @level.get('type', true) in ['course'] # course-ladder is hard to handle because there's 2 sessions
heroConfig = me.get('heroConfig')
return if not heroConfig
url = "/db/thang.type/#{heroConfig.thangType}/version"
if heroResource = @maybeLoadURL(url, ThangType, 'thang')
@worldNecessities.push heroResource
return
return unless @level.get('type', true) in ['hero', 'hero-ladder', 'hero-coop'] return unless @level.get('type', true) in ['hero', 'hero-ladder', 'hero-coop']
heroConfig = session.get('heroConfig') heroConfig = session.get('heroConfig')
heroConfig ?= me.get('heroConfig') if session is @session and not @headless heroConfig ?= me.get('heroConfig') if session is @session and not @headless

View file

@ -156,6 +156,8 @@ module.exports =
courseProgress[levelID][userID] = { completed: true, started: false } # These don't matter, will always be set courseProgress[levelID][userID] = { completed: true, started: false } # These don't matter, will always be set
session = _.find classroom.sessions.models, (session) -> session = _.find classroom.sessions.models, (session) ->
session.get('creator') is userID and session.get('level').original is levelID session.get('creator') is userID and session.get('level').original is levelID
courseProgress[levelID][userID].session = session
if not session # haven't gotten to this level yet, but might have completed others before if not session # haven't gotten to this level yet, but might have completed others before
courseProgress.started ||= false #no-op courseProgress.started ||= false #no-op

View file

@ -1314,6 +1314,9 @@
sent_verification: "We've sent a verification email to:" sent_verification: "We've sent a verification email to:"
you_can_edit: "You can edit your email address in " you_can_edit: "You can edit your email address in "
account_settings: "Account Settings" account_settings: "Account Settings"
select_your_hero: "Select Your Hero"
select_your_hero_description: "You can always change your hero by going to your Courses page and clicking \"Select Hero\""
select_this_hero: "Select this Hero"
teacher: teacher:
teacher_dashboard: "Teacher Dashboard" # Navbar teacher_dashboard: "Teacher Dashboard" # Navbar

View file

@ -5,8 +5,8 @@ module.exports = nativeDescription: "Nederlands (Nederland)", englishDescription
no_mobile: "CodeCombat is niet gemaakt voor mobiele apparaten en werkt misschien niet!" # Warning that shows up on mobile devices no_mobile: "CodeCombat is niet gemaakt voor mobiele apparaten en werkt misschien niet!" # Warning that shows up on mobile devices
play: "Speel" # The big play button that opens up the campaign view. play: "Speel" # The big play button that opens up the campaign view.
play_campaign_version: "Speel de Verhaallijn" # Shows up under big play button if you only play /courses play_campaign_version: "Speel de Verhaallijn" # Shows up under big play button if you only play /courses
old_browser: "Oh oh, jouw browser is te oud om CodeCombat te kunnen spelen, Sorry!" # Warning that shows up on really old Firefox/Chrome/Safari old_browser: "uh-oh, jouw browser is te oud om CodeCombat te kunnen spelen, Sorry!" # Warning that shows up on really old Firefox/Chrome/Safari
old_browser_suffix: "Je kan toch proberen, maar het zal waarschijnlijk niet werken!" old_browser_suffix: "Je kan alsnog proberen, maar het zal waarschijnlijk niet werken!"
ipad_browser: "Slecht nieuws: CodeCombat draait niet in je browser op de iPad. Goed nieuws: onze iPad-app wordt op het moment beoordeeld door Apple." ipad_browser: "Slecht nieuws: CodeCombat draait niet in je browser op de iPad. Goed nieuws: onze iPad-app wordt op het moment beoordeeld door Apple."
campaign: "Verhaallijn" campaign: "Verhaallijn"
for_beginners: "Voor Beginners" for_beginners: "Voor Beginners"
@ -24,25 +24,25 @@ module.exports = nativeDescription: "Nederlands (Nederland)", englishDescription
im_a_teacher: "Ik ben een leraar" im_a_teacher: "Ik ben een leraar"
im_a_student: "Ik ben een leerling" im_a_student: "Ik ben een leerling"
learn_more: "Lees verder" learn_more: "Lees verder"
classroom_in_a_box: "Een kant-en-klare digitale klas voor programmeerlessen." classroom_in_a_box: "Kant-en-klare programmeerlessen."
codecombat_is: "CodeCombat is een platform waarmee leerlingen leren programmeren door het spelen van een spel." # {change} codecombat_is: "CodeCombat is een platform waarmee leerlingen leren programmeren door het spelen van een spel." # {change}
our_courses: "Onze lessen zijn specifiek ontwikkeld voor een klasomgeving, zelfs voor leraren zonder programmeerervaring." # {change} our_courses: "Onze lessen zijn specifiek ontwikkeld voor een klasomgeving, zelfs voor leraren zonder programmeerervaring." # {change}
top_screenshots_hint: "Leerlingen schrijven code en zien direct het resultaat van de verandering." top_screenshots_hint: "Leerlingen schrijven code en zien direct resultaat."
designed_with: "Gemaakt voor leraren" designed_with: "Gemaakt voor leraren"
real_code: "Echte, getypte code" real_code: "Echte, getypte code"
from_the_first_level: "vanaf het eerste level" from_the_first_level: "vanaf het eerste level"
getting_students: "Leerlingen zo snel mogelijk echte code laten schrijven is noodzakelijk voor het leren van programmeer syntax en correcte structuur." getting_students: "Doordat leerlingen code schrijven in 'echte programmeertaal', leren ze niet alleen hoe computers denken, maar kunnen ze het ook echt toepassen."
educator_resources: "Lesbrieven voor docenten" educator_resources: "Lesbrieven voor docenten"
course_guides: "en ondersteuningsmateriaal" course_guides: "en ondersteuningsmateriaal"
teaching_computer_science: "Je hebt geen informatica diploma nodig om te kunnen programmeren, wij verschaffen de materialen waarmee elke docent programmeerles kan geven." teaching_computer_science: "Je hebt geen informatica diploma nodig om te kunnen programmeren, wij verschaffen de materialen waarmee elke docent programmeerles kan geven."
accessible_to: "Bereikbaar voor" accessible_to: "Toegankelijk voor"
everyone: "iedereen" everyone: "iedereen"
democratizing: "Programmeerles toegankelijk maken is onze filosofie. Iedereen moet de kans krijgen om te leren programmeren." democratizing: "Programmeerles toegankelijk maken is onze filosofie. Iedereen moet de kans krijgen om te leren programmeren."
forgot_learning: "Volgens mij hadden ze niet meer door dat ze eigenlijk bezig waren met leren." forgot_learning: "Volgens mij hadden ze niet meer door dat ze eigenlijk bezig waren met leren."
wanted_to_do: " Ik wilde altijd al leren programmeren, maar op school was hier nooit aandacht voor." wanted_to_do: " Ik wilde altijd al leren programmeren, maar op school was hier nooit aandacht voor."
why_games: "Waarom is spelenderwijs leren belangrijk?" why_games: "Waarom is spelenderwijs leren belangrijk?"
games_reward: "Games vergroten de productiviteit." games_reward: "Games vergroten de productiviteit."
encourage: "Gaming is een middel dat interactie, nieuwschierigheid, en trial-and-error aanmoedigt. Een goed spel daagt de speler uit zijn vaardigheden te perfectioneren, wat hetzelfde noodzakelijke proces is waar leerlingen doorheen gaan wanneer zij iets leren." encourage: "Iedereen wordt geboren als klein onderzoekertje dat de wereld ontdekt door vallen en opstaan. Deze intrinsieke motivatie om te leren ziet men ook terug in een spelomgeving. Voor de speler wordt 'leren' een middel om het spel te winnen, in plaats van een doel op zich."
excel: "Games helpen bij de" excel: "Games helpen bij de"
struggle: "productiviteit-strijd" struggle: "productiviteit-strijd"
kind_of_struggle: "het soort worsteling dat uitmondt in een leerproces dat uitdagend is en " kind_of_struggle: "het soort worsteling dat uitmondt in een leerproces dat uitdagend is en "
@ -58,7 +58,7 @@ module.exports = nativeDescription: "Nederlands (Nederland)", englishDescription
great_game: "Een goed spel is meer dan alleen medailles en prestaties - het gaat om een reis, nauwkeurig ontworpen puzzels, en de mogelijkheid om uitdagingen vol zelfvertrouwen aan te pakken." great_game: "Een goed spel is meer dan alleen medailles en prestaties - het gaat om een reis, nauwkeurig ontworpen puzzels, en de mogelijkheid om uitdagingen vol zelfvertrouwen aan te pakken."
agency: "CodeCombat is een game die de mogelijkheid en het zelfvertrouwen geeft om met echte code te werken, wat zowel beginners als gevorderde helpt bij het schrijven van goede, valide code." agency: "CodeCombat is een game die de mogelijkheid en het zelfvertrouwen geeft om met echte code te werken, wat zowel beginners als gevorderde helpt bij het schrijven van goede, valide code."
request_demo_title: "Laat je leerlingen vandaag nog starten!" request_demo_title: "Laat je leerlingen vandaag nog starten!"
request_demo_subtitle: "Vraag een demo aan en start binnen een uur met programmeerlessen." request_demo_subtitle: "Vraag een demo aan en start met programmeerlessen."
get_started_title: "Maak vandaag nog een klas aan!" get_started_title: "Maak vandaag nog een klas aan!"
get_started_subtitle: "Maak een klas aan, voeg je leerlingen toe, en monitor hun vooruitgang." get_started_subtitle: "Maak een klas aan, voeg je leerlingen toe, en monitor hun vooruitgang."
request_demo: "Vraag een demo aan" request_demo: "Vraag een demo aan"
@ -74,7 +74,7 @@ module.exports = nativeDescription: "Nederlands (Nederland)", englishDescription
coming_soon: "Binnenkort beschikbaar!" coming_soon: "Binnenkort beschikbaar!"
courses_available_in: "Lessen zijn beschikbaar in JavaScript, Python, en Java (Java is binnenkort beschikbaar!)" courses_available_in: "Lessen zijn beschikbaar in JavaScript, Python, en Java (Java is binnenkort beschikbaar!)"
boast: "Uitdagende raadsels die zowel gamers als fanatieke programmeurs weten te prikkelen." boast: "Uitdagende raadsels die zowel gamers als fanatieke programmeurs weten te prikkelen."
winning: "Een gouden combinatie van spel-elementen en programmeerhuiswerk, dat samen zorgt voor kind-vriendelijk en oprecht aangenaam onderwijs." winning: "Een gouden combinatie van spel-elementen en programmeerhuiswerk, dat samen zorgt voor kindvriendelijk en oprecht aangenaam onderwijs."
run_class: "Alles wat je nodig hebt om vandaag nog programmeerlessen in jouw klas te geven, geen voorkennis vereist." run_class: "Alles wat je nodig hebt om vandaag nog programmeerlessen in jouw klas te geven, geen voorkennis vereist."
teachers: "Docenten!" teachers: "Docenten!"
teachers_and_educators: "Docenten & Mentoren" teachers_and_educators: "Docenten & Mentoren"
@ -92,7 +92,7 @@ module.exports = nativeDescription: "Nederlands (Nederland)", englishDescription
check_out_wiki: "Bekijk onze nieuwe leraren Wiki" check_out_wiki: "Bekijk onze nieuwe leraren Wiki"
want_coco: "Wil je CodeCombat op jouw school?" want_coco: "Wil je CodeCombat op jouw school?"
form_select_role: "Selecteer je rol" form_select_role: "Selecteer je rol"
form_select_range: "Selecteer klassengrootte" form_select_range: "Selecteer klasomvang"
nav: nav:
play: "Levels" # The top nav bar entry where players choose which levels to play play: "Levels" # The top nav bar entry where players choose which levels to play
@ -221,19 +221,19 @@ module.exports = nativeDescription: "Nederlands (Nederland)", englishDescription
"/": "gedeeld door" "/": "gedeeld door"
"+": "plus" "+": "plus"
"-": "min" "-": "min"
# "+=": "add and assign" "+=": "tel op en wijs toe"
# "-=": "subtract and assign" "-=": "trek af en wijs toe"
True: "Waar" True: "Waar"
true: "waar" true: "waar"
False: "onwaar" False: "onwaar"
false: "onwaar" false: "onwaar"
undefined: "ongedefinieerd" undefined: "ongedefinieerd"
# null: "null" null: "nul"
# nil: "nil" nil: "nihil"
None: "Geen" None: "Geen"
share_progress_modal: share_progress_modal:
blurb: "Je gaat snel vooruit! Vertel aan je ouders hoeveel je geleerd hebt van CodeCombat." blurb: "Je gaat snel vooruit! Vertel je ouders hoeveel je geleerd hebt van CodeCombat."
email_invalid: "E-mailadres klopt niet." email_invalid: "E-mailadres klopt niet."
form_blurb: "Vul het e-mailadres van je ouders hieronder in en we zullen het ze laten zien!" form_blurb: "Vul het e-mailadres van je ouders hieronder in en we zullen het ze laten zien!"
form_label: "E-mailadres" form_label: "E-mailadres"
@ -472,19 +472,19 @@ module.exports = nativeDescription: "Nederlands (Nederland)", englishDescription
tip_reticulating: "Paden aan het verknopen." tip_reticulating: "Paden aan het verknopen."
tip_harry: "Je bent een tovenaar, " tip_harry: "Je bent een tovenaar, "
tip_great_responsibility: "Met een groots talent voor programmeren komt een grootse debug verantwoordelijkheid." tip_great_responsibility: "Met een groots talent voor programmeren komt een grootse debug verantwoordelijkheid."
tip_munchkin: "Als je je groentjes niet opeet zal een munchkin je ontvoeren terwijl je slaapt." tip_munchkin: "Als je je groenten niet opeet zal een munchkin je ontvoeren terwijl je slaapt."
tip_binary: "Er zijn 10 soorten mensen in de wereld: Mensen die binair kunnen tellen en mensen die dat niet kunnen." tip_binary: "Er zijn 10 soorten mensen in de wereld: Mensen die binair kunnen tellen en mensen die dat niet kunnen."
tip_commitment_yoda: "Een programmeur moet de grootste inzet hebben, een meest serieuze geest. ~ Yoda" tip_commitment_yoda: "Een programmeur moet de grootste inzet hebben, een meest serieuze geest. ~ Yoda"
tip_no_try: "Doe het. Of doe het niet. Je kunt niet proberen. - Yoda" tip_no_try: "Doe het. Of doe het niet. Je kunt niet proberen. - Yoda"
tip_patience: "Geduld moet je hebben, jonge Padawan. - Yoda" tip_patience: "Geduld moet je hebben, jonge Padawan. - Yoda"
tip_documented_bug: "Een gedocumenteerde fout is geen fout; het is deel van het programma." tip_documented_bug: "Een gedocumenteerde fout is geen fout; het is deel van het programma."
tip_impossible: "Het lijkt altijd onmogelijk tot het gedaan wordt. - Nelson Mandela" tip_impossible: "Het lijkt altijd onmogelijk totdat iemand het doet. - Nelson Mandela"
tip_talk_is_cheap: "Je kunt het goed uitleggen, maar toon me de code. - Linus Torvalds" tip_talk_is_cheap: "Je kunt het goed uitleggen, maar toon me de code. - Linus Torvalds"
tip_first_language: "Het ergste dat je kan leren is je eerste programmeertaal. - Alan Kay" tip_first_language: "Het ergste dat je kan leren is je eerste programmeertaal. - Alan Kay"
tip_hardware_problem: "Q: Hoeveel programmeurs heb je nodig om een lampje te vervangen? A: Nul, het is een a hardware probleem." tip_hardware_problem: "Q: Hoeveel programmeurs heb je nodig om een lampje te vervangen? A: Nul, het is een a hardware probleem."
tip_hofstadters_law: "De Wet van Hofstadter: Het duurt altijd langer dan je verwacht, zelfs wanneer je rekening houdt met de Wet van Hofstadter." tip_hofstadters_law: "De Wet van Hofstadter: Het duurt altijd langer dan je verwacht, zelfs wanneer je rekening houdt met de Wet van Hofstadter."
tip_premature_optimization: "vroegtijdig optimaliseren is de wortel van al het kwaad. - Donald Knuth" tip_premature_optimization: "vroegtijdig optimaliseren is de wortel van al het kwaad. - Donald Knuth"
tip_brute_force: "Wanneer je twijfelt, gebruik brute force. - Ken Thompson" tip_brute_force: "Wanneer je twijfelt, gebruik dan brute force. - Ken Thompson"
tip_extrapolation: "Er zijn twee soorten mensen: Zij die iets kunnen afleiden van onvolledige gegevens..." tip_extrapolation: "Er zijn twee soorten mensen: Zij die iets kunnen afleiden van onvolledige gegevens..."
tip_superpower: "Van alle dingen komt programmeren het dichtst in de buurt van een superkracht." tip_superpower: "Van alle dingen komt programmeren het dichtst in de buurt van een superkracht."
tip_control_destiny: "In echte open source, hebt je het recht om je eigen toekomst te bepalen. - Linus Torvalds" tip_control_destiny: "In echte open source, hebt je het recht om je eigen toekomst te bepalen. - Linus Torvalds"
@ -820,8 +820,8 @@ module.exports = nativeDescription: "Nederlands (Nederland)", englishDescription
teachers_quote: teachers_quote:
name: "Demo Formulier" name: "Demo Formulier"
title: "Demo aanvragen" title: "Demo aanvragen"
subtitle: "Haal CodeCombat jouw klaslokaal of club!" subtitle: "Gebruik CodeCombat voor jouw klas of programmeerclub!"
email_exists: "Er bestaat al een gebruiker met dit email adres." email_exists: "Er bestaat al een gebruiker met dit emailadres."
phone_number: "Telefoonnummer" phone_number: "Telefoonnummer"
phone_number_help: "Waarop kunnen we je bereiken tijdens kantooruren?" phone_number_help: "Waarop kunnen we je bereiken tijdens kantooruren?"
primary_role_label: "Uw primaire rol" primary_role_label: "Uw primaire rol"

View file

@ -686,21 +686,21 @@ module.exports = nativeDescription: "српски", englishDescription: "Serbian
# writable: "writable" # Hover over "attack" in Your Skills while playing a level to see most of this # writable: "writable" # Hover over "attack" in Your Skills while playing a level to see most of this
# read_only: "read-only" # read_only: "read-only"
action: "Aкција" action: "Aкција"
# spell: "Spell" spell: "Магија"
# action_name: "name" action_name: "име"
# action_cooldown: "Takes" action_cooldown: "Потребно"
# action_specific_cooldown: "Cooldown" action_specific_cooldown: "Хлађење"
# action_damage: "Damage" action_damage: "Штета"
# action_range: "Range" action_range: "Домет"
# action_radius: "Radius" action_radius: "Опсег"
# action_duration: "Duration" action_duration: "Трајање"
# example: "Example" example: "Пример"
# ex: "ex" # Abbreviation of "example" ex: "нпр." # Abbreviation of "example"
# current_value: "Current Value" current_value: "Тренутна вредност"
# default_value: "Default value" default_value: "Подразумевана вредност"
# parameters: "Parameters" parameters: "Параметри"
# returns: "Returns" returns: "Враћа"
# granted_by: "Granted by" granted_by: "Додељено од"
save_load: save_load:
granularity_saved_games: "Сачувано" granularity_saved_games: "Сачувано"
@ -860,12 +860,12 @@ module.exports = nativeDescription: "српски", englishDescription: "Serbian
finish_signup_p: "Направи налог да оснујеш разред, додаш своје ученике и пратиш њихов напредак док уче компјутерске науке." finish_signup_p: "Направи налог да оснујеш разред, додаш своје ученике и пратиш њихов напредак док уче компјутерске науке."
signup_with: "Пријави се са:" signup_with: "Пријави се са:"
connect_with: "Повежи се са:" connect_with: "Повежи се са:"
# conversion_warning: "WARNING: Your current account is a <em>Student Account</em>. Once you submit this form, your account will be updated to a Teacher Account." conversion_warning: "УПОЗОРЕЊЕ: Твој тренутни налог је <em>Студентски Налог</em>. Након што пошаљеш овај формулар, твој налог ће бити надограђен у Учитељски Налог."
# learn_more_modal: "Teacher accounts on CodeCombat have the ability to monitor student progress, assign enrollments and manage classrooms. Teacher accounts cannot be a part of a classroom - if you are currently enrolled in a class using this account, you will no longer be able to access it once you update to a Teacher Account." learn_more_modal: "Учитељски налози на CodeCombat-у имају могућност посматрања напретка ученика, додељивања уписа и управљања учионицама. Учитељски налози не могу бити део учионице - ако си тренутно уписан у разред преко овог налога, нећеш више моћи да му приступиш кад ажурираш у Учитељски Налог."
create_account: "Направи учитељски налог" create_account: "Направи учитељски налог"
# create_account_subtitle: "Get access to teacher-only tools for using CodeCombat in the classroom. <strong>Set up a class</strong>, add your students, and <strong>monitor their progress</strong>!" create_account_subtitle: "Добиј приступ алатима само за учитеље за коришћење CodeCombat-а у учионици. <strong>Подеси разред</strong>, додај своје ученике, и <strong>посматрај њихов напредак</strong>!"
# convert_account_title: "Update to Teacher Account" convert_account_title: "Ажурирај у Учитељски Налог"
# not: "Not" not: "Није"
setup_a_class: "Подеси разред" setup_a_class: "Подеси разред"
versions: versions:
@ -880,18 +880,18 @@ module.exports = nativeDescription: "српски", englishDescription: "Serbian
contact: contact:
contact_us: "Контактирај CodeCombat" contact_us: "Контактирај CodeCombat"
welcome: "Драго нам је што нас контактираш! Искористи ову форму да нам пошаљеш мејл. " welcome: "Драго нам је што нас контактираш! Искористи овај формулар да нам пошаљеш мејл. "
forum_prefix: "За било шта јавно, посети " forum_prefix: "За било шта јавно, посети "
forum_page: "наш форум." forum_page: "наш форум"
# forum_suffix: " instead." forum_suffix: " уместо тога."
# faq_prefix: "There's also a" faq_prefix: "Такође, ту је"
# faq: "FAQ" faq: "FAQ"
# subscribe_prefix: "If you need help figuring out a level, please" subscribe_prefix: "Ако ти треба помоћ да разумеш ниво, молимо да"
# subscribe: "buy a CodeCombat subscription" subscribe: "купиш CodeCombat претплату"
# subscribe_suffix: "and we'll be happy to help you with your code." subscribe_suffix: "и радо ћемо ти помоћи у твом коду."
# subscriber_support: "Since you're a CodeCombat subscriber, your email will get our priority support." subscriber_support: "Пошто си CodeCombat претплатник, твој мејл ће имати приоритет у нашој подршци."
# screenshot_included: "Screenshot included." screenshot_included: "Снимак екрана укључен."
# where_reply: "Where should we reply?" where_reply: "Где треба да одговоримо?"
send: "Пошаљи повратну информацију" send: "Пошаљи повратну информацију"
account_settings: account_settings:
@ -910,24 +910,24 @@ module.exports = nativeDescription: "српски", englishDescription: "Serbian
# god_mode: "God Mode" # god_mode: "God Mode"
password_tab: "Шифра" password_tab: "Шифра"
emails_tab: "Мејлови" emails_tab: "Мејлови"
# admin: "Admin" admin: "Администратор"
manage_subscription: "Кликни овде да би управљао својом претплатом." manage_subscription: "Кликни овде да би управљао својом претплатом."
new_password: "Нова Шифра" new_password: "Нова Шифра"
new_password_verify: "Потврди" new_password_verify: "Потврди"
# type_in_email: "Type in your email to confirm account deletion." type_in_email: "Упиши свој мејл да потврдиш брисање налога."
# type_in_email_progress: "Type in your email to confirm deleting your progress." type_in_email_progress: "Упиши свој мејл да потврдиш брисање свог напретка."
# type_in_password: "Also, type in your password." type_in_password: "Такође, упиши своју шифру."
email_subscriptions: "Мејл претплате" email_subscriptions: "Мејл претплате"
# email_subscriptions_none: "No Email Subscriptions." email_subscriptions_none: "Без мејл претплата."
email_announcements: "Обавештења" email_announcements: "Обавештења"
email_announcements_description: "Прими мејл за најновије вести и достигнућа на CodeCombat-у" email_announcements_description: "Прими мејл за најновије вести и достигнућа на CodeCombat-у"
# email_notifications: "Notifications" email_notifications: "Обавештења"
# email_notifications_summary: "Controls for personalized, automatic email notifications related to your CodeCombat activity." email_notifications_summary: "Контроле за персонализована, аутоматска мејл обавештења вазана за твоју CodeCombat активност."
# email_any_notes: "Any Notifications" email_any_notes: "Сва обавештења"
# email_any_notes_description: "Disable to stop all activity notification emails." email_any_notes_description: "Онемогући да би прекинуо сва мејл обавештења о активности."
email_news: "Вести" email_news: "Вести"
email_recruit_notes: "Пословне могућности" email_recruit_notes: "Пословне могућности"
# email_recruit_notes_description: "If you play really well, we may contact you about getting you a (better) job." email_recruit_notes_description: "Ако играш јако добро, можда ћемо те контактирати о томе да добијеш (бољи) посао."
contributor_emails: "Мејлови реда сарадника" contributor_emails: "Мејлови реда сарадника"
contribute_prefix: "Тражимо људе који би нам се придружили! Погледај " contribute_prefix: "Тражимо људе који би нам се придружили! Погледај "
contribute_page: "страницу за сарадњу" contribute_page: "страницу за сарадњу"
@ -961,13 +961,13 @@ module.exports = nativeDescription: "српски", englishDescription: "Serbian
community: community:
main_title: "CodeCombat Заједница" main_title: "CodeCombat Заједница"
# introduction: "Check out the ways you can get involved below and decide what sounds the most fun. We look forward to working with you!" introduction: "Погледај испод како можеш да се укључиш и одлучи шта звучи најзанимљивије. Радујемо се прилици да радимо са тобом!"
# level_editor_prefix: "Use the CodeCombat" level_editor_prefix: "Користи CodeCombat"
# level_editor_suffix: "to create and edit levels. Users have created levels for their classes, friends, hackathons, students, and siblings. If create a new level sounds intimidating you can start by forking one of ours!" level_editor_suffix: "да правиш и уређујеш нивое. Корисници су направили нивое за њихове разреде, пријатеље, хакатоне, ученике и браћу и сестре. Ако прављење новог нивоа звучи застрашујуће, можеш да почнеш форковањем једног од наших!"
# thang_editor_prefix: "We call units within the game 'thangs'. Use the" thang_editor_prefix: "Ми зовемо јединице у игри 'thangs'. Користи"
# thang_editor_suffix: "to modify the CodeCombat source artwork. Allow units to throw projectiles, alter the direction of an animation, change a unit's hit points, or upload your own vector sprites." thang_editor_suffix: "да модификујеш CodeCombat изворне илустрације. Дозволи јединицама да бацају пројектиле, измени дирекцију анимације, промени хит поене јединице или отпреми сопствене векторске спрајтове."
# article_editor_prefix: "See a mistake in some of our docs? Want to make some instructions for your own creations? Check out the" article_editor_prefix: "Видиш грешку у неком од наших докумената? Желиш да направиш инструкције за сопствене креације? Погледај"
# article_editor_suffix: "and help CodeCombat players get the most out of their playtime." article_editor_suffix: "и помози CodeCombat играчима да добију највише од свог играња."
find_us: "Нађи нас на овим сајтовима" find_us: "Нађи нас на овим сајтовима"
social_github: "Погледај цео наш код на GitHub-у" social_github: "Погледај цео наш код на GitHub-у"
social_blog: "Читај CodeCombat блог на Sett-у" social_blog: "Читај CodeCombat блог на Sett-у"
@ -986,57 +986,57 @@ module.exports = nativeDescription: "српски", englishDescription: "Serbian
make_private: "Направи клан приватним" make_private: "Направи клан приватним"
subs_only: "само за претплатнике" subs_only: "само за претплатнике"
create_clan: "Направи нови клан" create_clan: "Направи нови клан"
private_preview: "Preview" private_preview: "Приказ"
private_clans: "Приватни кланови" private_clans: "Приватни кланови"
public_clans: "Јавни кланови" public_clans: "Јавни кланови"
my_clans: "Моји кланови" my_clans: "Моји кланови"
clan_name: "Име клана" clan_name: "Име клана"
name: "Име" name: "Име"
# chieftain: "Chieftain" chieftain: "Поглавица"
# type: "Type" type: "Врста"
edit_clan_name: "Измени име клана" edit_clan_name: "Измени име клана"
edit_clan_description: "Измени опис клана" edit_clan_description: "Измени опис клана"
edit_name: "измени име" edit_name: "измени име"
edit_description: "измени опис" edit_description: "измени опис"
private: "(приватан)" private: "(приватан)"
# summary: "Summary" summary: "Преглед"
# average_level: "Average Level" average_level: "Просечни ниво"
# average_achievements: "Average Achievements" average_achievements: "Просечна достигнућа"
delete_clan: "Избриши клан" delete_clan: "Избриши клан"
leave_clan: "Напусти клан" leave_clan: "Напусти клан"
join_clan: "Придружи се клану" join_clan: "Придружи се клану"
invite_1: "Позови:" invite_1: "Позови:"
# invite_2: "*Invite players to this Clan by sending them this link." invite_2: "*Позови играче у овај Клан тако што ћеш им послати овај линк."
members: "Чланови" members: "Чланови"
progress: "Напредак" progress: "Напредак"
# not_started_1: "not started" not_started_1: "није започето"
# started_1: "started" started_1: "започето"
# complete_1: "complete" complete_1: "заврши"
# exp_levels: "Expand levels" exp_levels: "Прошири нивое"
# rem_hero: "Remove Hero" rem_hero: "Уклони Хероја"
status: "Статус" status: "Статус"
# complete_2: "Complete" complete_2: "Заврши"
# started_2: "Started" started_2: "Започето"
# not_started_2: "Not Started" not_started_2: "Није започето"
# view_solution: "Click to view solution." view_solution: "Кликни да видиш решење."
# view_attempt: "Click to view attempt." view_attempt: "Кликни да видиш покушај."
# latest_achievement: "Latest Achievement" latest_achievement: "Последње достигнуће"
# playtime: "Playtime" playtime: "Време игања"
# last_played: "Last played" last_played: "Последњи пут играно"
# leagues_explanation: "Play in a league against other clan members in these multiplayer arena instances." leagues_explanation: "Играј у лиги против других чланова клана у овим мултиплејер инстанцама арене."
# track_concepts1: "Track concepts" track_concepts1: "Прати концепте"
# track_concepts2a: "learned by each student" track_concepts2a: "научене од сваког ученика"
# track_concepts2b: "learned by each member" track_concepts2b: "научене од сваког члана"
# track_concepts3a: "Track levels completed for each student" track_concepts3a: "Прати завршене нивое за сваког ученика"
# track_concepts3b: "Track levels completed for each member" track_concepts3b: "Прати завршене нивое за сваког члана"
# track_concepts4a: "See your students'" track_concepts4a: "Види од својих ученика"
# track_concepts4b: "See your members'" track_concepts4b: "Види од својих чланова"
# track_concepts5: "solutions" track_concepts5: "решења"
# track_concepts6a: "Sort students by name or progress" track_concepts6a: "Сортирај ученике према имену или напретку"
# track_concepts6b: "Sort members by name or progress" track_concepts6b: "Сортирај чланове према имену или напретку"
# track_concepts7: "Requires invitation" track_concepts7: "Захтева позив"
# track_concepts8: "to join" track_concepts8: "за придруживање"
# private_require_sub: "Private clans require a subscription to create or join." private_require_sub: "Приватни кланови захтевају претплату да би могао да их направиш или да им се придружиш."
# courses: # courses:
# course: "Course" # course: "Course"
@ -1488,12 +1488,12 @@ module.exports = nativeDescription: "српски", englishDescription: "Serbian
# add_system_title: "Add Systems to Level" # add_system_title: "Add Systems to Level"
# done_adding: "Done Adding" # done_adding: "Done Adding"
# article: article:
# edit_btn_preview: "Preview" edit_btn_preview: "Приказ"
# edit_article_title: "Edit Article" edit_article_title: "Измени Чланак"
# polls: polls:
# priority: "Priority" priority: "Приоритет"
# contribute: # contribute:
# page_title: "Contributing" # page_title: "Contributing"
@ -1645,17 +1645,17 @@ module.exports = nativeDescription: "српски", englishDescription: "Serbian
# favorite_postfix: "." # favorite_postfix: "."
# not_member_of_clans: "Not a member of any clans yet." # not_member_of_clans: "Not a member of any clans yet."
# achievements: achievements:
# last_earned: "Last Earned" last_earned: "Последње стечено"
# amount_achieved: "Amount" amount_achieved: "Количина"
# achievement: "Achievement" achievement: "Достигнуће"
# current_xp_prefix: "" current_xp_prefix: ""
# current_xp_postfix: " in total" current_xp_postfix: " укупно"
# new_xp_prefix: "" new_xp_prefix: ""
# new_xp_postfix: " earned" new_xp_postfix: " стечено"
# left_xp_prefix: "" left_xp_prefix: ""
# left_xp_infix: " until level " left_xp_infix: " до нивоа "
# left_xp_postfix: "" left_xp_postfix: ""
account: account:
payments: "Уплате" payments: "Уплате"

View file

@ -368,6 +368,7 @@ class CocoModel extends Backbone.Model
return if _.isString @url then @url else @url() return if _.isString @url then @url else @url()
@pollAchievements: -> @pollAchievements: ->
return if application.testing
CocoCollection = require 'collections/CocoCollection' CocoCollection = require 'collections/CocoCollection'
EarnedAchievement = require 'models/EarnedAchievement' EarnedAchievement = require 'models/EarnedAchievement'

View file

@ -145,6 +145,11 @@ module.exports = class Level extends CocoModel
for original, placeholderComponent of placeholders when not placeholdersUsed[original] for original, placeholderComponent of placeholders when not placeholdersUsed[original]
levelThang.components.push placeholderComponent levelThang.components.push placeholderComponent
# Load the user's chosen hero AFTER getting stats from default char
if /Hero Placeholder/.test(levelThang.id) and @get('type', true) in ['course', 'course-ladder']
heroThangType = me.get('heroConfig')?.thangType
levelThang.thangType = heroThangType if heroThangType
sortSystems: (levelSystems, systemModels) -> sortSystems: (levelSystems, systemModels) ->
[sorted, originalsSeen] = [[], {}] [sorted, originalsSeen] = [[], {}]
visit = (system) -> visit = (system) ->

View file

@ -239,6 +239,25 @@ module.exports = class ThangType extends CocoModel
portraitOnly = !!options.portraitOnly portraitOnly = !!options.portraitOnly
"#{@get('name')} - #{options.resolutionFactor} - #{colorConfigs} - #{portraitOnly}" "#{@get('name')} - #{options.resolutionFactor} - #{colorConfigs} - #{portraitOnly}"
getHeroShortName: ->
map = {
"Assassin": "Ritic"
"Captain": "Anya"
"Forest Archer": "Naria"
"Goliath": "Okar"
"Guardian": "Illia"
"Knight": "Tharin"
"Librarian": "Hushbaum"
"Necromancer": "Nalfar"
"Ninja": "Amara"
"Potion Master": "Omarn"
"Raider": "Arryn"
"Samurai": "Hattori"
"Sorcerer": "Pender"
"Trapper": "Senick"
}
map[@get('name')]
getPortraitImage: (spriteOptionsOrKey, size=100) -> getPortraitImage: (spriteOptionsOrKey, size=100) ->
src = @getPortraitSource(spriteOptionsOrKey, size) src = @getPortraitSource(spriteOptionsOrKey, size)
return null unless src return null unless src

View file

@ -41,3 +41,22 @@
#join-class-form #join-class-form
.alert, .progress .alert, .progress
margin-top: 20px margin-top: 20px
// Hero display
.current-hero-container
display: flex
justify-content: center
.current-hero-text
font-size: 16pt
.hero-avatar
background-color: #f8f8f8
box-shadow: 0 0 0 1px gray
margin-right: 25px
.current-hero-right-col
display: flex
flex-direction: column
justify-content: space-between
align-items: flex-start

View file

@ -0,0 +1,40 @@
@import "app/styles/style-flat-variables"
#hero-select-modal
.modal-dialog
width: auto
max-width: 900px
.modal-header, .modal-body:not(.secret), .modal-footer
display: flex
flex-direction: column
align-items: center
.modal-footer
margin: 30px
h4
max-width: 500px
.hero-list
display: flex
flex-wrap: wrap
justify-content: center
margin-bottom: -50px
.hero-option
display: flex
flex-direction: column
align-items: center
margin: 0 50px 50px
.hero-avatar
margin: 6px
background-color: #f8f8f8
box-shadow: 0 0 0 1px gray
.current .hero-avatar
box-shadow: 0 0 0 6px gray
.selected .hero-avatar
box-shadow: 0 0 0 6px $gold

View file

@ -7,3 +7,7 @@
font-family: Arial, Geneva, sans-serif font-family: Arial, Geneva, sans-serif
padding: 20px padding: 20px
font-weight: bold font-weight: bold
.alert-report
font-size: 20px

View file

@ -39,6 +39,18 @@ block content
.text-center .text-center
h1(data-i18n="courses.welcome_to_page") Welcome to your Courses page! h1(data-i18n="courses.welcome_to_page") Welcome to your Courses page!
.current-hero-container.text-center.row
.hero-avatar
img(src=view.hero.getPortraitURL())
.current-hero-right-col
.semibold.current-hero-text
span.spr(data-i18n="TODO")
| Current Hero:
span.current-hero-name= view.hero.getHeroShortName()
button.change-hero-btn.btn.btn-lg.btn-forest
span(data-i18n="TODO")
| Change Hero
if view.classrooms.size() if view.classrooms.size()
h3.text-uppercase(data-i18n="courses.my_classes") h3.text-uppercase(data-i18n="courses.my_classes")

View file

@ -0,0 +1,27 @@
extends /templates/core/modal-base-flat
block modal-header-content
.text-center
h3(data-i18n="courses.select_your_hero")
h4(data-i18n="courses.select_your_hero_description")
block modal-body-content
.hero-list
if view.heroes.loaded
each hero in view.heroes.models
if hero.get('heroClass') === 'Warrior'
+heroOption(hero)
mixin heroOption(hero)
- var heroID = hero.id
- var selectedState = (state.get('selectedHeroID') === heroID ? 'selected' : (state.get('currentHeroID') === heroID ? 'current' : ''))
.hero-option(data-hero-id=heroID class=selectedState)
.hero-avatar
img(src=hero.getPortraitURL())
.text-h5.hero-name
span= hero.getHeroShortName()
block modal-footer-content
.select-hero-btn.btn.btn-lg.btn-forest
span(data-i18n="courses.select_this_hero")

View file

@ -321,7 +321,7 @@ mixin studentLevelsRow(student)
- var levels = view.classroom.getLevels({courseID: course.id, withoutLadderLevels: true}).models - var levels = view.classroom.getLevels({courseID: course.id, withoutLadderLevels: true}).models
each level, index in levels each level, index in levels
- var progress = state.get('progressData').get({ classroom: view.classroom, course: course, level: level, user: student }) - var progress = state.get('progressData').get({ classroom: view.classroom, course: course, level: level, user: student })
+studentLevelProgressDot(progress, level, index+1) +studentLevelProgressDot(progress, level, index+1, session)
mixin studentCourseProgressDot(progress, levelsTotal, level, label) mixin studentCourseProgressDot(progress, levelsTotal, level, label)
//- TODO: Refactor with TeacherClassesView jade //- TODO: Refactor with TeacherClassesView jade
@ -342,7 +342,7 @@ mixin studentLevelProgressDot(progress, level, levelNumber)
//- TODO: Refactor with TeacherClassesView jade //- TODO: Refactor with TeacherClassesView jade
- dotClass = progress.completed ? 'forest' : (progress.started ? 'gold' : ''); - dotClass = progress.completed ? 'forest' : (progress.started ? 'gold' : '');
- levelName = level.get('name') - levelName = level.get('name')
- context = _.merge(progress, { levelName: levelName, levelNumber: levelNumber }) - context = _.merge(progress, { levelName: levelName, levelNumber: levelNumber, moment: moment })
.progress-dot.level-progress-dot(class=dotClass, data-html='true', data-title=view.singleStudentLevelProgressDotTemplate(context)) .progress-dot.level-progress-dot(class=dotClass, data-html='true', data-title=view.singleStudentLevelProgressDotTemplate(context))
+progressDotLabel(levelNumber) +progressDotLabel(levelNumber)

View file

@ -1,3 +1,10 @@
mixin timePlayed()
if session.get('playtime') > 0
.small-details.nowrap
span.spr(data-i18n='teacher.time_played')
| Played for
span= moment.duration({ seconds: session.get('playtime') }).humanize()
if completed if completed
.small-details.nowrap .small-details.nowrap
span= levelNumber span= levelNumber
@ -7,6 +14,7 @@ if completed
span.spr(data-i18n='teacher.completed') span.spr(data-i18n='teacher.completed')
| Completed | Completed
span= new Date(dateFirstCompleted).toLocaleString() span= new Date(dateFirstCompleted).toLocaleString()
+timePlayed
//- .small-details //- .small-details
//- i(data-i18n='teacher.click_to_view_solution') //- i(data-i18n='teacher.click_to_view_solution')
//- | click to view solution //- | click to view solution
@ -19,6 +27,7 @@ else if started
span.spr(data-i18n='teacher.last_played') span.spr(data-i18n='teacher.last_played')
| Last played | Last played
span= new Date(lastPlayed).toLocaleString() span= new Date(lastPlayed).toLocaleString()
+timePlayed
//- .small-details //- .small-details
//- i(data-i18n='teacher.click_to_view_progress') //- i(data-i18n='teacher.click_to_view_progress')
//- | click to view progress //- | click to view progress

View file

@ -11,6 +11,20 @@ ol.breadcrumb
.container-fluid .container-fluid
.row .row
.col-md-8 .col-md-8
#failure-reports
for report in view.failureReports
.alert.alert-danger.alert-report
ul.suite-list
for description in report.suiteDescriptions
li= description
li
strong ... #{report.testDescription}
hr
ol.error-list
for message in report.failMessages
li
strong= message
#test-wrapper.well #test-wrapper.well
#testing-area #testing-area

View file

@ -13,7 +13,7 @@ module.exports = TestView = class TestView extends RootView
id: 'test-view' id: 'test-view'
template: template template: template
reloadOnClose: true reloadOnClose: true
loadedFileIDs: [] className: 'style-flat'
events: events:
'click #show-demos-btn': 'onClickShowDemosButton' 'click #show-demos-btn': 'onClickShowDemosButton'
@ -24,11 +24,13 @@ module.exports = TestView = class TestView extends RootView
initialize: (options, @subPath='') -> initialize: (options, @subPath='') ->
@subPath = @subPath[1..] if @subPath[0] is '/' @subPath = @subPath[1..] if @subPath[0] is '/'
@demosOn = storage.load('demos-on') @demosOn = storage.load('demos-on')
@failureReports = []
@loadedFileIDs = []
afterInsert: -> afterInsert: ->
@initSpecFiles() @initSpecFiles()
@render() @render()
TestView.runTests(@specFiles, @demosOn) TestView.runTests(@specFiles, @demosOn, @)
window.runJasmine() window.runJasmine()
# EVENTS # EVENTS
@ -59,7 +61,30 @@ module.exports = TestView = class TestView extends RootView
prefix = TEST_REQUIRE_PREFIX + @subPath prefix = TEST_REQUIRE_PREFIX + @subPath
@specFiles = (f for f in @specFiles when _.string.startsWith f, prefix) @specFiles = (f for f in @specFiles when _.string.startsWith f, prefix)
@runTests: (specFiles, demosOn=false) -> @runTests: (specFiles, demosOn=false, view) ->
jasmine.getEnv().addReporter({
suiteStack: []
specDone: (result) ->
if result.status is 'failed'
console.log 'result', result
report = {
suiteDescriptions: _.clone(@suiteStack)
failMessages: (fe.message for fe in result.failedExpectations)
testDescription: result.description
}
view.failureReports.push(report)
view.renderSelectors('#failure-reports')
suiteStarted: (result) ->
@suiteStack.push(result.description)
suiteDone: (result) ->
@suiteStack.pop()
})
application.testing = true application.testing = true
specFiles ?= @getAllSpecFiles() specFiles ?= @getAllSpecFiles()
if demosOn if demosOn
@ -71,23 +96,22 @@ module.exports = TestView = class TestView extends RootView
jasmine.demoEl = _.noop jasmine.demoEl = _.noop
jasmine.demoModal = _.noop jasmine.demoModal = _.noop
describe 'CodeCombat Client', => jasmine.Ajax.install()
jasmine.Ajax.install() beforeEach ->
beforeEach -> jasmine.Ajax.requests.reset()
jasmine.Ajax.requests.reset() Backbone.Mediator.init()
Backbone.Mediator.init() Backbone.Mediator.setValidationEnabled false
Backbone.Mediator.setValidationEnabled false spyOn(application.tracker, 'trackEvent')
spyOn(application.tracker, 'trackEvent') # TODO Stubbify more things
# TODO Stubbify more things # * document.location
# * document.location # * firebase
# * firebase # * all the services that load in main.html
# * all the services that load in main.html
afterEach -> afterEach ->
# TODO Clean up more things # TODO Clean up more things
# * Events # * Events
require f for f in specFiles # runs the tests require f for f in specFiles # runs the tests
@getAllSpecFiles = -> @getAllSpecFiles = ->
allFiles = window.require.list() allFiles = window.require.list()

View file

@ -4,6 +4,7 @@ template = require 'templates/courses/courses-view'
AuthModal = require 'views/core/AuthModal' AuthModal = require 'views/core/AuthModal'
CreateAccountModal = require 'views/core/CreateAccountModal' CreateAccountModal = require 'views/core/CreateAccountModal'
ChangeCourseLanguageModal = require 'views/courses/ChangeCourseLanguageModal' ChangeCourseLanguageModal = require 'views/courses/ChangeCourseLanguageModal'
HeroSelectModal = require 'views/courses/HeroSelectModal'
ChooseLanguageModal = require 'views/courses/ChooseLanguageModal' ChooseLanguageModal = require 'views/courses/ChooseLanguageModal'
JoinClassModal = require 'views/courses/JoinClassModal' JoinClassModal = require 'views/courses/JoinClassModal'
CourseInstance = require 'models/CourseInstance' CourseInstance = require 'models/CourseInstance'
@ -13,6 +14,7 @@ Classroom = require 'models/Classroom'
Classrooms = require 'collections/Classrooms' Classrooms = require 'collections/Classrooms'
LevelSession = require 'models/LevelSession' LevelSession = require 'models/LevelSession'
Campaign = require 'models/Campaign' Campaign = require 'models/Campaign'
ThangType = require 'models/ThangType'
utils = require 'core/utils' utils = require 'core/utils'
# TODO: Test everything # TODO: Test everything
@ -24,6 +26,7 @@ module.exports = class CoursesView extends RootView
events: events:
'click #log-in-btn': 'onClickLogInButton' 'click #log-in-btn': 'onClickLogInButton'
'click #start-new-game-btn': 'openSignUpModal' 'click #start-new-game-btn': 'openSignUpModal'
'click .change-hero-btn': 'onClickChangeHeroButton'
'click #join-class-btn': 'onClickJoinClassButton' 'click #join-class-btn': 'onClickJoinClassButton'
'submit #join-class-form': 'onSubmitJoinClassForm' 'submit #join-class-form': 'onSubmitJoinClassForm'
'click #change-language-link': 'onClickChangeLanguageLink' 'click #change-language-link': 'onClickChangeLanguageLink'
@ -43,6 +46,16 @@ module.exports = class CoursesView extends RootView
@courses = new CocoCollection([], { url: "/db/course", model: Course}) @courses = new CocoCollection([], { url: "/db/course", model: Course})
@supermodel.loadCollection(@courses) @supermodel.loadCollection(@courses)
# TODO: Trim this section for only what's necessary
@hero = new ThangType
defaultHeroOriginal = ThangType.heroes.captain
heroOriginal = me.get('heroConfig')?.thangType or defaultHeroOriginal
@hero.url = "/db/thang.type/#{heroOriginal}/version"
# @hero.setProjection ['name','slug','soundTriggers','featureImages','gems','heroClass','description','components','extendedName','unlockLevelName','i18n']
@supermodel.loadModel(@hero, 'hero')
@listenTo @hero, 'all', ->
@render()
onCourseInstancesLoaded: -> onCourseInstancesLoaded: ->
map = {} map = {}
for courseInstance in @courseInstances.models for courseInstance in @courseInstances.models
@ -76,6 +89,16 @@ module.exports = class CoursesView extends RootView
@openModalView(modal) @openModalView(modal)
application.tracker?.trackEvent 'Started Student Signup', category: 'Courses' application.tracker?.trackEvent 'Started Student Signup', category: 'Courses'
onClickChangeHeroButton: ->
modal = new HeroSelectModal({ currentHeroID: @hero.id })
@openModalView(modal)
@listenTo modal, 'hero-select:success', (newHero) =>
# @hero.url = "/db/thang.type/#{me.get('heroConfig').thangType}/version"
# @hero.fetch()
@hero.set(newHero.attributes)
@listenTo modal, 'hide', ->
@stopListening modal
onSubmitJoinClassForm: (e) -> onSubmitJoinClassForm: (e) ->
e.preventDefault() e.preventDefault()
@joinClass() @joinClass()
@ -136,7 +159,7 @@ module.exports = class CoursesView extends RootView
classroomCourseInstances.fetch({ data: {classroomID: newClassroom.id} }) classroomCourseInstances.fetch({ data: {classroomID: newClassroom.id} })
@listenToOnce classroomCourseInstances, 'sync', -> @listenToOnce classroomCourseInstances, 'sync', ->
# TODO: Smoother system for joining a classroom and course instances, without requiring page reload, # TODO: Smoother system for joining a classroom and course instances, without requiring page reload,
# and showing which class was just joined. # and showing which class was just joined.
document.location.search = '' # Using document.location.reload() causes an infinite loop of reloading document.location.search = '' # Using document.location.reload() causes an infinite loop of reloading
onClickChangeLanguageLink: -> onClickChangeLanguageLink: ->

View file

@ -0,0 +1,42 @@
ModalView = require 'views/core/ModalView'
template = require 'templates/courses/hero-select-modal'
Classroom = require 'models/Classroom'
ThangTypes = require 'collections/ThangTypes'
State = require 'models/State'
ThangType = require 'models/ThangType'
User = require 'models/User'
module.exports = class HeroSelectModal extends ModalView
id: 'hero-select-modal'
template: template
events:
'click .select-hero-btn': 'onClickSelectHeroButton'
'click .hero-option': 'onClickHeroOption'
initialize: ({ currentHeroID }) ->
@debouncedRender = _.debounce @render, 0
@state = new State({
currentHeroID
selectedHeroID: currentHeroID
})
@heroes = new ThangTypes({}, { project: ['original', 'name', 'heroClass'] })
@supermodel.trackRequest @heroes.fetchHeroes()
@listenTo @state, 'all', -> @debouncedRender()
@listenTo @heroes, 'all', -> @debouncedRender()
onClickHeroOption: (e) ->
heroID = $(e.currentTarget).data('hero-id')
@state.set selectedHeroID: heroID
hero = @heroes.get(heroID)
me.set(heroConfig: {}) unless me.get('heroConfig')
heroConfig = _.assign me.get('heroConfig'), { thangType: hero.get('original') }
me.set({ heroConfig })
me.save().then =>
@trigger 'hero-select:success', hero
onClickSelectHeroButton: () ->
@hide()

View file

@ -316,7 +316,7 @@ module.exports = class SpellView extends CocoView
xstart = startOfRow(row) xstart = startOfRow(row)
if language is 'python' if language is 'python'
requiredIndent = new RegExp '^' + new Array(xstart / 4 + 2).join '( |\t)' + '(\\S|\\s*$)' requiredIndent = new RegExp '^' + new Array(xstart / 4 + 1).join('( |\t)') + '( |\t)+(\\S|\\s*$)'
for crow in [docRange.start.row+1..docRange.end.row] for crow in [docRange.start.row+1..docRange.end.row]
unless requiredIndent.test lines[crow] unless requiredIndent.test lines[crow]
docRange.end.row = crow - 1 docRange.end.row = crow - 1

View file

@ -52,7 +52,8 @@
"esper.js": "http://files.codecombat.com/esper.tar.gz", "esper.js": "http://files.codecombat.com/esper.tar.gz",
"algoliasearch": "^3.13.1", "algoliasearch": "^3.13.1",
"algolia-autocomplete.js": "^0.17.0", "algolia-autocomplete.js": "^0.17.0",
"algolia-autocomplete-no-conflict": "1.0.0" "algolia-autocomplete-no-conflict": "1.0.0",
"bluebird": "^3.4.0"
}, },
"overrides": { "overrides": {
"algolia-autocomplete.js": { "algolia-autocomplete.js": {

View file

@ -84,7 +84,7 @@ ClassroomHandler = class ClassroomHandler extends Handler
return @sendDatabaseError(res, err) if err return @sendDatabaseError(res, err) if err
return @sendSuccess(res, (@formatEntity(req, classroom) for classroom in classrooms)) return @sendSuccess(res, (@formatEntity(req, classroom) for classroom in classrooms))
else if code = req.query.code else if code = req.query.code
code = code.toLowerCase() code = code.toLowerCase().replace(/ /g, '')
Classroom.findOne {code: code}, (err, classroom) => Classroom.findOne {code: code}, (err, classroom) =>
return @sendDatabaseError(res, err) if err return @sendDatabaseError(res, err) if err
return @sendNotFoundError(res) unless classroom return @sendNotFoundError(res) unless classroom

View file

@ -65,7 +65,7 @@ module.exports =
activities = JSON.parse(body) activities = JSON.parse(body)
return done("Unexpected activities format: " + body) unless activities.data? return done("Unexpected activities format: " + body) unless activities.data?
for activity in activities.data when activity._type is 'Email' for activity in activities.data when activity._type is 'Email'
if /@codecombat\.com/ig.test(activity.sender) and not activity.sender?.indexOf(config.mail.username) >= 0 if /@codecombat\.(?:com)|(?:nl)$/ig.test(activity.sender) and not activity.sender?.indexOf(config.mail.username) >= 0
return done(null, activity.sender, lead.id) return done(null, activity.sender, lead.id)
return done(null, config.mail.supportSchools, lead.id) return done(null, config.mail.supportSchools, lead.id)
catch error catch error

View file

@ -22,7 +22,7 @@ module.exports =
fetchByCode: wrap (req, res, next) -> fetchByCode: wrap (req, res, next) ->
code = req.query.code code = req.query.code
return next() unless code return next() unless code
classroom = yield Classroom.findOne({ code: code.toLowerCase() }).select('name ownerID aceConfig') classroom = yield Classroom.findOne({ code: code.toLowerCase().replace(/ /g, '') }).select('name ownerID aceConfig')
if not classroom if not classroom
log.debug("classrooms.fetchByCode: Couldn't find Classroom with code: #{code}") log.debug("classrooms.fetchByCode: Couldn't find Classroom with code: #{code}")
throw new errors.NotFound('Classroom not found.') throw new errors.NotFound('Classroom not found.')
@ -170,7 +170,7 @@ module.exports =
if req.user.isTeacher() if req.user.isTeacher()
log.debug("classrooms.join: Cannot join a classroom as a teacher: #{req.user.id}") log.debug("classrooms.join: Cannot join a classroom as a teacher: #{req.user.id}")
throw new errors.Forbidden('Cannot join a classroom as a teacher') throw new errors.Forbidden('Cannot join a classroom as a teacher')
code = req.body.code.toLowerCase() code = req.body.code.toLowerCase().replace(/ /g, '')
classroom = yield Classroom.findOne({code: code}) classroom = yield Classroom.findOne({code: code})
if not classroom if not classroom
log.debug("classrooms.join: Classroom not found with code #{code}") log.debug("classrooms.join: Classroom not found with code #{code}")

View file

@ -60,6 +60,18 @@ describe 'GET /db/classroom/:id', ->
expect(body._id).toBe(classroomID = body._id) expect(body._id).toBe(classroomID = body._id)
done() done()
describe 'GET /db/classroom by classCode', ->
it 'Returns the class if you include spaces', utils.wrap (done) ->
user = yield utils.initUser()
yield utils.loginUser(user)
teacher = yield utils.initUser()
classroom = new Classroom({ name: "some class", ownerID: teacher.id, camelCode: "FooBarBaz", code: "foobarbaz" })
yield classroom.save()
[res, body] = yield request.getAsync(getURL('/db/classroom?code=foo bar baz'), { json: true })
expect(res.statusCode).toBe(200)
expect(res.body.data?.name).toBe(classroom.get('name'))
done()
describe 'POST /db/classroom', -> describe 'POST /db/classroom', ->
beforeEach utils.wrap (done) -> beforeEach utils.wrap (done) ->
@ -295,6 +307,18 @@ describe 'POST /db/classroom/-/members', ->
fail('student should be added to the free course instance.') fail('student should be added to the free course instance.')
done() done()
it 'joins the class even with spaces in the classcode', utils.wrap (done) ->
yield utils.loginUser(@student)
url = getURL("/db/classroom/anything-here/members")
code = @classroom.get('code')
codeWithSpaces = code.split("").join(" ")
[res, body] = yield request.postAsync { uri: url, json: { code: codeWithSpaces } }
expect(res.statusCode).toBe(200)
classroom = yield Classroom.findById(@classroom.id)
if classroom.get('members').length isnt 1
fail 'expected classCode with spaces to work too'
done()
it 'returns 403 if the user is a teacher', utils.wrap (done) -> it 'returns 403 if the user is a teacher', utils.wrap (done) ->
yield utils.loginUser(@teacher) yield utils.loginUser(@teacher)
url = getURL("/db/classroom/~/members") url = getURL("/db/classroom/~/members")

View file

@ -0,0 +1,32 @@
CoursesView = require 'views/courses/CoursesView'
HeroSelectModal = require 'views/courses/HeroSelectModal'
Classrooms = require 'collections/Classrooms'
CourseInstances = require 'collections/CourseInstances'
Courses = require 'collections/Courses'
auth = require 'core/auth'
factories = require 'test/app/factories'
describe 'CoursesView', ->
modal = null
view = null
describe 'Change Hero button', ->
beforeEach (done) ->
view = new CoursesView()
classrooms = new Classrooms([factories.makeClassroom()])
courseInstances = new CourseInstances([factories.makeCourseInstance()])
courses = new Courses([factories.makeCourse()])
view.classrooms.fakeRequests[0].respondWith({ status: 200, responseText: classrooms.stringify() })
view.ownedClassrooms.fakeRequests[0].respondWith({ status: 200, responseText: classrooms.stringify() })
view.courseInstances.fakeRequests[0].respondWith({ status: 200, responseText: courseInstances.stringify() })
view.render()
jasmine.demoEl(view.$el)
done()
it 'opens the modal when you click Change Hero', ->
spyOn(view, 'openModalView')
view.$('.change-hero-btn').click()
expect(view.openModalView).toHaveBeenCalled()
args = view.openModalView.calls.argsFor(0)
expect(args[0] instanceof HeroSelectModal).toBe(true)

View file

@ -0,0 +1,38 @@
HeroSelectModal = require 'views/courses/HeroSelectModal'
auth = require 'core/auth'
factories = require 'test/app/factories'
describe 'HeroSelectModal', ->
modal = null
coursesView = null
user = null
hero1 = factories.makeThangType({ original: "hero1original", _id: "hero1id", heroClass: "Warrior", name: "Hero 1" })
hero2 = factories.makeThangType({ original: "hero2original", _id: "hero2id", heroClass: "Warrior", name: "Hero 2" })
heroesResponse = JSON.stringify([hero1, hero2])
beforeEach (done) ->
window.me = user = factories.makeUser({ heroConfig: { thangType: hero1.get('original') } })
auth.loginUser(user.attributes)
modal = new HeroSelectModal({ currentHeroID: hero1.id })
modal.heroes.fakeRequests[0].respondWith({ status: 200, responseText: heroesResponse })
jasmine.demoModal(modal)
_.defer ->
modal.render()
done()
afterEach ->
modal.stopListening()
it 'highlights the current hero', ->
expect(modal.$(".hero-option[data-hero-id='#{hero1.id}']")[0].className.split(" ")).toContain('selected')
it 'saves when you change heroes', (done) ->
modal.$(".hero-option[data-hero-id='#{hero2.id}']").click()
_.defer ->
expect(user.fakeRequests.length).toBe(1)
request = user.fakeRequests[0]
expect(request.method).toBe("PUT")
expect(JSON.parse(request.params).heroConfig?.thangType).toBe(hero2.get('original'))
done()

View file

@ -40,6 +40,12 @@ describe 'CourseVictoryModal', ->
modal.classroom.fakeRequests[0].respondWith({ modal.classroom.fakeRequests[0].respondWith({
status: 200, responseText: factories.makeClassroom().stringify() status: 200, responseText: factories.makeClassroom().stringify()
}) })
if me.fakeRequests
lastRequest = _.last(me.fakeRequests)
if not lastRequest.response
lastRequest.respondWith({
status: 200, responseText: factories.makeUser().stringify()
})
nextLevelRequest = modal.nextLevel.fakeRequests[0] nextLevelRequest = modal.nextLevel.fakeRequests[0]
describe 'given a course level with a next level and no item or hero rewards', -> describe 'given a course level with a next level and no item or hero rewards', ->