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

@ -4,3 +4,8 @@ ThangType = require 'models/ThangType'
module.exports = class ThangTypeCollection extends CocoCollection
url: '/db/thang.type'
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
else if session is @opponentSession
@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']
heroConfig = session.get('heroConfig')
heroConfig ?= me.get('heroConfig') if session is @session and not @headless

View file

@ -157,6 +157,8 @@ module.exports =
session = _.find classroom.sessions.models, (session) ->
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
courseProgress.started ||= false #no-op
courseProgress.completed = false

View file

@ -1314,6 +1314,9 @@
sent_verification: "We've sent a verification email to:"
you_can_edit: "You can edit your email address in "
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_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
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
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_suffix: "Je kan toch proberen, maar het zal waarschijnlijk niet werken!"
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 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."
campaign: "Verhaallijn"
for_beginners: "Voor Beginners"
@ -24,25 +24,25 @@ module.exports = nativeDescription: "Nederlands (Nederland)", englishDescription
im_a_teacher: "Ik ben een leraar"
im_a_student: "Ik ben een leerling"
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}
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"
real_code: "Echte, getypte code"
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"
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."
accessible_to: "Bereikbaar voor"
accessible_to: "Toegankelijk voor"
everyone: "iedereen"
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."
wanted_to_do: " Ik wilde altijd al leren programmeren, maar op school was hier nooit aandacht voor."
why_games: "Waarom is spelenderwijs leren belangrijk?"
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"
struggle: "productiviteit-strijd"
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."
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_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_subtitle: "Maak een klas aan, voeg je leerlingen toe, en monitor hun vooruitgang."
request_demo: "Vraag een demo aan"
@ -74,7 +74,7 @@ module.exports = nativeDescription: "Nederlands (Nederland)", englishDescription
coming_soon: "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."
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."
teachers: "Docenten!"
teachers_and_educators: "Docenten & Mentoren"
@ -92,7 +92,7 @@ module.exports = nativeDescription: "Nederlands (Nederland)", englishDescription
check_out_wiki: "Bekijk onze nieuwe leraren Wiki"
want_coco: "Wil je CodeCombat op jouw school?"
form_select_role: "Selecteer je rol"
form_select_range: "Selecteer klassengrootte"
form_select_range: "Selecteer klasomvang"
nav:
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"
"+": "plus"
"-": "min"
# "+=": "add and assign"
# "-=": "subtract and assign"
"+=": "tel op en wijs toe"
"-=": "trek af en wijs toe"
True: "Waar"
true: "waar"
False: "onwaar"
false: "onwaar"
undefined: "ongedefinieerd"
# null: "null"
# nil: "nil"
null: "nul"
nil: "nihil"
None: "Geen"
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."
form_blurb: "Vul het e-mailadres van je ouders hieronder in en we zullen het ze laten zien!"
form_label: "E-mailadres"
@ -472,19 +472,19 @@ module.exports = nativeDescription: "Nederlands (Nederland)", englishDescription
tip_reticulating: "Paden aan het verknopen."
tip_harry: "Je bent een tovenaar, "
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_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_patience: "Geduld moet je hebben, jonge Padawan. - Yoda"
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_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_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_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_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"
@ -820,7 +820,7 @@ module.exports = nativeDescription: "Nederlands (Nederland)", englishDescription
teachers_quote:
name: "Demo Formulier"
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 emailadres."
phone_number: "Telefoonnummer"
phone_number_help: "Waarop kunnen we je bereiken tijdens kantooruren?"

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
# read_only: "read-only"
action: "Aкција"
# spell: "Spell"
# action_name: "name"
# action_cooldown: "Takes"
# action_specific_cooldown: "Cooldown"
# action_damage: "Damage"
# action_range: "Range"
# action_radius: "Radius"
# action_duration: "Duration"
# example: "Example"
# ex: "ex" # Abbreviation of "example"
# current_value: "Current Value"
# default_value: "Default value"
# parameters: "Parameters"
# returns: "Returns"
# granted_by: "Granted by"
spell: "Магија"
action_name: "име"
action_cooldown: "Потребно"
action_specific_cooldown: "Хлађење"
action_damage: "Штета"
action_range: "Домет"
action_radius: "Опсег"
action_duration: "Трајање"
example: "Пример"
ex: "нпр." # Abbreviation of "example"
current_value: "Тренутна вредност"
default_value: "Подразумевана вредност"
parameters: "Параметри"
returns: "Враћа"
granted_by: "Додељено од"
save_load:
granularity_saved_games: "Сачувано"
@ -860,12 +860,12 @@ module.exports = nativeDescription: "српски", englishDescription: "Serbian
finish_signup_p: "Направи налог да оснујеш разред, додаш своје ученике и пратиш њихов напредак док уче компјутерске науке."
signup_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."
# 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."
conversion_warning: "УПОЗОРЕЊЕ: Твој тренутни налог је <em>Студентски Налог</em>. Након што пошаљеш овај формулар, твој налог ће бити надограђен у Учитељски Налог."
learn_more_modal: "Учитељски налози на CodeCombat-у имају могућност посматрања напретка ученика, додељивања уписа и управљања учионицама. Учитељски налози не могу бити део учионице - ако си тренутно уписан у разред преко овог налога, нећеш више моћи да му приступиш кад ажурираш у Учитељски Налог."
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>!"
# convert_account_title: "Update to Teacher Account"
# not: "Not"
create_account_subtitle: "Добиј приступ алатима само за учитеље за коришћење CodeCombat-а у учионици. <strong>Подеси разред</strong>, додај своје ученике, и <strong>посматрај њихов напредак</strong>!"
convert_account_title: "Ажурирај у Учитељски Налог"
not: "Није"
setup_a_class: "Подеси разред"
versions:
@ -880,18 +880,18 @@ module.exports = nativeDescription: "српски", englishDescription: "Serbian
contact:
contact_us: "Контактирај CodeCombat"
welcome: "Драго нам је што нас контактираш! Искористи ову форму да нам пошаљеш мејл. "
welcome: "Драго нам је што нас контактираш! Искористи овај формулар да нам пошаљеш мејл. "
forum_prefix: "За било шта јавно, посети "
forum_page: "наш форум."
# forum_suffix: " instead."
# faq_prefix: "There's also a"
# faq: "FAQ"
# subscribe_prefix: "If you need help figuring out a level, please"
# subscribe: "buy a CodeCombat subscription"
# subscribe_suffix: "and we'll be happy to help you with your code."
# subscriber_support: "Since you're a CodeCombat subscriber, your email will get our priority support."
# screenshot_included: "Screenshot included."
# where_reply: "Where should we reply?"
forum_page: "наш форум"
forum_suffix: " уместо тога."
faq_prefix: "Такође, ту је"
faq: "FAQ"
subscribe_prefix: "Ако ти треба помоћ да разумеш ниво, молимо да"
subscribe: "купиш CodeCombat претплату"
subscribe_suffix: "и радо ћемо ти помоћи у твом коду."
subscriber_support: "Пошто си CodeCombat претплатник, твој мејл ће имати приоритет у нашој подршци."
screenshot_included: "Снимак екрана укључен."
where_reply: "Где треба да одговоримо?"
send: "Пошаљи повратну информацију"
account_settings:
@ -910,24 +910,24 @@ module.exports = nativeDescription: "српски", englishDescription: "Serbian
# god_mode: "God Mode"
password_tab: "Шифра"
emails_tab: "Мејлови"
# admin: "Admin"
admin: "Администратор"
manage_subscription: "Кликни овде да би управљао својом претплатом."
new_password: "Нова Шифра"
new_password_verify: "Потврди"
# type_in_email: "Type in your email to confirm account deletion."
# type_in_email_progress: "Type in your email to confirm deleting your progress."
# type_in_password: "Also, type in your password."
type_in_email: "Упиши свој мејл да потврдиш брисање налога."
type_in_email_progress: "Упиши свој мејл да потврдиш брисање свог напретка."
type_in_password: "Такође, упиши своју шифру."
email_subscriptions: "Мејл претплате"
# email_subscriptions_none: "No Email Subscriptions."
email_subscriptions_none: "Без мејл претплата."
email_announcements: "Обавештења"
email_announcements_description: "Прими мејл за најновије вести и достигнућа на CodeCombat-у"
# email_notifications: "Notifications"
# email_notifications_summary: "Controls for personalized, automatic email notifications related to your CodeCombat activity."
# email_any_notes: "Any Notifications"
# email_any_notes_description: "Disable to stop all activity notification emails."
email_notifications: "Обавештења"
email_notifications_summary: "Контроле за персонализована, аутоматска мејл обавештења вазана за твоју CodeCombat активност."
email_any_notes: "Сва обавештења"
email_any_notes_description: "Онемогући да би прекинуо сва мејл обавештења о активности."
email_news: "Вести"
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: "Мејлови реда сарадника"
contribute_prefix: "Тражимо људе који би нам се придружили! Погледај "
contribute_page: "страницу за сарадњу"
@ -961,13 +961,13 @@ module.exports = nativeDescription: "српски", englishDescription: "Serbian
community:
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!"
# level_editor_prefix: "Use the 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!"
# thang_editor_prefix: "We call units within the game 'thangs'. Use the"
# 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."
# 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_suffix: "and help CodeCombat players get the most out of their playtime."
introduction: "Погледај испод како можеш да се укључиш и одлучи шта звучи најзанимљивије. Радујемо се прилици да радимо са тобом!"
level_editor_prefix: "Користи CodeCombat"
level_editor_suffix: "да правиш и уређујеш нивое. Корисници су направили нивое за њихове разреде, пријатеље, хакатоне, ученике и браћу и сестре. Ако прављење новог нивоа звучи застрашујуће, можеш да почнеш форковањем једног од наших!"
thang_editor_prefix: "Ми зовемо јединице у игри 'thangs'. Користи"
thang_editor_suffix: "да модификујеш CodeCombat изворне илустрације. Дозволи јединицама да бацају пројектиле, измени дирекцију анимације, промени хит поене јединице или отпреми сопствене векторске спрајтове."
article_editor_prefix: "Видиш грешку у неком од наших докумената? Желиш да направиш инструкције за сопствене креације? Погледај"
article_editor_suffix: "и помози CodeCombat играчима да добију највише од свог играња."
find_us: "Нађи нас на овим сајтовима"
social_github: "Погледај цео наш код на GitHub-у"
social_blog: "Читај CodeCombat блог на Sett-у"
@ -986,57 +986,57 @@ module.exports = nativeDescription: "српски", englishDescription: "Serbian
make_private: "Направи клан приватним"
subs_only: "само за претплатнике"
create_clan: "Направи нови клан"
private_preview: "Preview"
private_preview: "Приказ"
private_clans: "Приватни кланови"
public_clans: "Јавни кланови"
my_clans: "Моји кланови"
clan_name: "Име клана"
name: "Име"
# chieftain: "Chieftain"
# type: "Type"
chieftain: "Поглавица"
type: "Врста"
edit_clan_name: "Измени име клана"
edit_clan_description: "Измени опис клана"
edit_name: "измени име"
edit_description: "измени опис"
private: "(приватан)"
# summary: "Summary"
# average_level: "Average Level"
# average_achievements: "Average Achievements"
summary: "Преглед"
average_level: "Просечни ниво"
average_achievements: "Просечна достигнућа"
delete_clan: "Избриши клан"
leave_clan: "Напусти клан"
join_clan: "Придружи се клану"
invite_1: "Позови:"
# invite_2: "*Invite players to this Clan by sending them this link."
invite_2: "*Позови играче у овај Клан тако што ћеш им послати овај линк."
members: "Чланови"
progress: "Напредак"
# not_started_1: "not started"
# started_1: "started"
# complete_1: "complete"
# exp_levels: "Expand levels"
# rem_hero: "Remove Hero"
not_started_1: "није започето"
started_1: "започето"
complete_1: "заврши"
exp_levels: "Прошири нивое"
rem_hero: "Уклони Хероја"
status: "Статус"
# complete_2: "Complete"
# started_2: "Started"
# not_started_2: "Not Started"
# view_solution: "Click to view solution."
# view_attempt: "Click to view attempt."
# latest_achievement: "Latest Achievement"
# playtime: "Playtime"
# last_played: "Last played"
# leagues_explanation: "Play in a league against other clan members in these multiplayer arena instances."
# track_concepts1: "Track concepts"
# track_concepts2a: "learned by each student"
# track_concepts2b: "learned by each member"
# track_concepts3a: "Track levels completed for each student"
# track_concepts3b: "Track levels completed for each member"
# track_concepts4a: "See your students'"
# track_concepts4b: "See your members'"
# track_concepts5: "solutions"
# track_concepts6a: "Sort students by name or progress"
# track_concepts6b: "Sort members by name or progress"
# track_concepts7: "Requires invitation"
# track_concepts8: "to join"
# private_require_sub: "Private clans require a subscription to create or join."
complete_2: "Заврши"
started_2: "Започето"
not_started_2: "Није започето"
view_solution: "Кликни да видиш решење."
view_attempt: "Кликни да видиш покушај."
latest_achievement: "Последње достигнуће"
playtime: "Време игања"
last_played: "Последњи пут играно"
leagues_explanation: "Играј у лиги против других чланова клана у овим мултиплејер инстанцама арене."
track_concepts1: "Прати концепте"
track_concepts2a: "научене од сваког ученика"
track_concepts2b: "научене од сваког члана"
track_concepts3a: "Прати завршене нивое за сваког ученика"
track_concepts3b: "Прати завршене нивое за сваког члана"
track_concepts4a: "Види од својих ученика"
track_concepts4b: "Види од својих чланова"
track_concepts5: "решења"
track_concepts6a: "Сортирај ученике према имену или напретку"
track_concepts6b: "Сортирај чланове према имену или напретку"
track_concepts7: "Захтева позив"
track_concepts8: "за придруживање"
private_require_sub: "Приватни кланови захтевају претплату да би могао да их направиш или да им се придружиш."
# courses:
# course: "Course"
@ -1488,12 +1488,12 @@ module.exports = nativeDescription: "српски", englishDescription: "Serbian
# 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: "Priority"
polls:
priority: "Приоритет"
# contribute:
# page_title: "Contributing"
@ -1645,17 +1645,17 @@ module.exports = nativeDescription: "српски", englishDescription: "Serbian
# favorite_postfix: "."
# not_member_of_clans: "Not a member of any clans yet."
# achievements:
# last_earned: "Last Earned"
# amount_achieved: "Amount"
# achievement: "Achievement"
# current_xp_prefix: ""
# current_xp_postfix: " in total"
# new_xp_prefix: ""
# new_xp_postfix: " earned"
# left_xp_prefix: ""
# left_xp_infix: " until level "
# left_xp_postfix: ""
achievements:
last_earned: "Последње стечено"
amount_achieved: "Количина"
achievement: "Достигнуће"
current_xp_prefix: ""
current_xp_postfix: " укупно"
new_xp_prefix: ""
new_xp_postfix: " стечено"
left_xp_prefix: ""
left_xp_infix: " до нивоа "
left_xp_postfix: ""
account:
payments: "Уплате"

View file

@ -368,6 +368,7 @@ class CocoModel extends Backbone.Model
return if _.isString @url then @url else @url()
@pollAchievements: ->
return if application.testing
CocoCollection = require 'collections/CocoCollection'
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]
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) ->
[sorted, originalsSeen] = [[], {}]
visit = (system) ->

View file

@ -239,6 +239,25 @@ module.exports = class ThangType extends CocoModel
portraitOnly = !!options.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) ->
src = @getPortraitSource(spriteOptionsOrKey, size)
return null unless src

View file

@ -41,3 +41,22 @@
#join-class-form
.alert, .progress
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
padding: 20px
font-weight: bold
.alert-report
font-size: 20px

View file

@ -40,6 +40,18 @@ block content
.text-center
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()
h3.text-uppercase(data-i18n="courses.my_classes")
hr

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
each level, index in levels
- 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)
//- TODO: Refactor with TeacherClassesView jade
@ -342,7 +342,7 @@ mixin studentLevelProgressDot(progress, level, levelNumber)
//- TODO: Refactor with TeacherClassesView jade
- dotClass = progress.completed ? 'forest' : (progress.started ? 'gold' : '');
- 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))
+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
.small-details.nowrap
span= levelNumber
@ -7,6 +14,7 @@ if completed
span.spr(data-i18n='teacher.completed')
| Completed
span= new Date(dateFirstCompleted).toLocaleString()
+timePlayed
//- .small-details
//- i(data-i18n='teacher.click_to_view_solution')
//- | click to view solution
@ -19,6 +27,7 @@ else if started
span.spr(data-i18n='teacher.last_played')
| Last played
span= new Date(lastPlayed).toLocaleString()
+timePlayed
//- .small-details
//- i(data-i18n='teacher.click_to_view_progress')
//- | click to view progress

View file

@ -11,6 +11,20 @@ ol.breadcrumb
.container-fluid
.row
.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
#testing-area

View file

@ -13,7 +13,7 @@ module.exports = TestView = class TestView extends RootView
id: 'test-view'
template: template
reloadOnClose: true
loadedFileIDs: []
className: 'style-flat'
events:
'click #show-demos-btn': 'onClickShowDemosButton'
@ -24,11 +24,13 @@ module.exports = TestView = class TestView extends RootView
initialize: (options, @subPath='') ->
@subPath = @subPath[1..] if @subPath[0] is '/'
@demosOn = storage.load('demos-on')
@failureReports = []
@loadedFileIDs = []
afterInsert: ->
@initSpecFiles()
@render()
TestView.runTests(@specFiles, @demosOn)
TestView.runTests(@specFiles, @demosOn, @)
window.runJasmine()
# EVENTS
@ -59,7 +61,30 @@ module.exports = TestView = class TestView extends RootView
prefix = TEST_REQUIRE_PREFIX + @subPath
@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
specFiles ?= @getAllSpecFiles()
if demosOn
@ -71,7 +96,6 @@ module.exports = TestView = class TestView extends RootView
jasmine.demoEl = _.noop
jasmine.demoModal = _.noop
describe 'CodeCombat Client', =>
jasmine.Ajax.install()
beforeEach ->
jasmine.Ajax.requests.reset()

View file

@ -4,6 +4,7 @@ template = require 'templates/courses/courses-view'
AuthModal = require 'views/core/AuthModal'
CreateAccountModal = require 'views/core/CreateAccountModal'
ChangeCourseLanguageModal = require 'views/courses/ChangeCourseLanguageModal'
HeroSelectModal = require 'views/courses/HeroSelectModal'
ChooseLanguageModal = require 'views/courses/ChooseLanguageModal'
JoinClassModal = require 'views/courses/JoinClassModal'
CourseInstance = require 'models/CourseInstance'
@ -13,6 +14,7 @@ Classroom = require 'models/Classroom'
Classrooms = require 'collections/Classrooms'
LevelSession = require 'models/LevelSession'
Campaign = require 'models/Campaign'
ThangType = require 'models/ThangType'
utils = require 'core/utils'
# TODO: Test everything
@ -24,6 +26,7 @@ module.exports = class CoursesView extends RootView
events:
'click #log-in-btn': 'onClickLogInButton'
'click #start-new-game-btn': 'openSignUpModal'
'click .change-hero-btn': 'onClickChangeHeroButton'
'click #join-class-btn': 'onClickJoinClassButton'
'submit #join-class-form': 'onSubmitJoinClassForm'
'click #change-language-link': 'onClickChangeLanguageLink'
@ -43,6 +46,16 @@ module.exports = class CoursesView extends RootView
@courses = new CocoCollection([], { url: "/db/course", model: Course})
@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: ->
map = {}
for courseInstance in @courseInstances.models
@ -76,6 +89,16 @@ module.exports = class CoursesView extends RootView
@openModalView(modal)
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) ->
e.preventDefault()
@joinClass()

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)
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]
unless requiredIndent.test lines[crow]
docRange.end.row = crow - 1

View file

@ -52,7 +52,8 @@
"esper.js": "http://files.codecombat.com/esper.tar.gz",
"algoliasearch": "^3.13.1",
"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": {
"algolia-autocomplete.js": {

View file

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

View file

@ -65,7 +65,7 @@ module.exports =
activities = JSON.parse(body)
return done("Unexpected activities format: " + body) unless activities.data?
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, config.mail.supportSchools, lead.id)
catch error

View file

@ -22,7 +22,7 @@ module.exports =
fetchByCode: wrap (req, res, next) ->
code = req.query.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
log.debug("classrooms.fetchByCode: Couldn't find Classroom with code: #{code}")
throw new errors.NotFound('Classroom not found.')
@ -170,7 +170,7 @@ module.exports =
if req.user.isTeacher()
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')
code = req.body.code.toLowerCase()
code = req.body.code.toLowerCase().replace(/ /g, '')
classroom = yield Classroom.findOne({code: code})
if not classroom
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)
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', ->
beforeEach utils.wrap (done) ->
@ -295,6 +307,18 @@ describe 'POST /db/classroom/-/members', ->
fail('student should be added to the free course instance.')
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) ->
yield utils.loginUser(@teacher)
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({
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]
describe 'given a course level with a next level and no item or hero rewards', ->